mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 19:21:10 +02:00
chore: delete 25 web-archived directories, remove stale stubs, clean workspace config
- Delete all 25 apps/*/apps/web-archived/ directories (superseded by unified ManaCore app) - Remove stale +page.server.ts stubs from teams, organizations, settings (always returned empty data) - Simplify teams and organizations pages to static empty-state (no server load dependency) - Delete empty apps/context/apps/mobile/components/variants/index.ts - Remove commented-out apps-archived entries from pnpm-workspace.yaml Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e1077e261f
commit
6ced238571
1940 changed files with 41 additions and 223288 deletions
|
|
@ -1,2 +0,0 @@
|
|||
# News Hub Web App Configuration
|
||||
PUBLIC_NEWS_API_URL=http://localhost:3000
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
// @ts-check
|
||||
import {
|
||||
baseConfig,
|
||||
typescriptConfig,
|
||||
svelteConfig,
|
||||
prettierConfig,
|
||||
} from '@manacore/eslint-config';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['dist/**', '.svelte-kit/**', 'node_modules/**'],
|
||||
},
|
||||
...baseConfig,
|
||||
...typescriptConfig,
|
||||
...svelteConfig,
|
||||
...prettierConfig,
|
||||
];
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
{
|
||||
"name": "@news/web",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"prepare": "svelte-kit sync || echo ''",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-node": "^5.0.0",
|
||||
"@sveltejs/kit": "^2.22.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.4",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^6.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@manacore/local-store": "workspace:*",
|
||||
"@manacore/shared-auth": "workspace:*",
|
||||
"@manacore/shared-auth-stores": "workspace:*",
|
||||
"@manacore/shared-auth-ui": "workspace:*",
|
||||
"@manacore/shared-branding": "workspace:*",
|
||||
"@manacore/shared-ui": "workspace:*",
|
||||
"svelte-sonner": "^1.0.5"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
@import "tailwindcss";
|
||||
@import "@manacore/shared-tailwind/themes.css";
|
||||
|
||||
/* Scan shared packages for Tailwind classes */
|
||||
@source "../../../../packages/shared-ui/src";
|
||||
@source "../../../../packages/shared-auth-ui/src";
|
||||
@source "../../../../packages/shared-branding/src";
|
||||
@source "../../../../packages/shared-theme-ui/src";
|
||||
11
apps/news/apps/web-archived/src/app.d.ts
vendored
11
apps/news/apps/web-archived/src/app.d.ts
vendored
|
|
@ -1,11 +0,0 @@
|
|||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<script lang="ts">
|
||||
let { size = 48, color = '#10b981' }: { size?: number; color?: string } = $props();
|
||||
</script>
|
||||
|
||||
<svg width={size} height={size} viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="100" height="100" rx="22" fill={color} />
|
||||
<rect x="22" y="25" width="56" height="50" rx="4" stroke="white" stroke-width="4" fill="none" />
|
||||
<line x1="30" y1="38" x2="55" y2="38" stroke="white" stroke-width="3" stroke-linecap="round" />
|
||||
<line x1="30" y1="48" x2="70" y2="48" stroke="white" stroke-width="3" stroke-linecap="round" />
|
||||
<line x1="30" y1="58" x2="65" y2="58" stroke="white" stroke-width="3" stroke-linecap="round" />
|
||||
</svg>
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
import type { LocalArticle, LocalCategory } from './local-store';
|
||||
|
||||
export const guestCategories: LocalCategory[] = [
|
||||
{ id: 'cat-tech', name: 'Technologie', slug: 'technologie', color: '#3b82f6', order: 0 },
|
||||
{ id: 'cat-science', name: 'Wissenschaft', slug: 'wissenschaft', color: '#10b981', order: 1 },
|
||||
{ id: 'cat-world', name: 'Welt', slug: 'welt', color: '#f59e0b', order: 2 },
|
||||
{ id: 'cat-business', name: 'Wirtschaft', slug: 'wirtschaft', color: '#8b5cf6', order: 3 },
|
||||
];
|
||||
|
||||
export const guestArticles: LocalArticle[] = [
|
||||
{
|
||||
id: 'demo-1',
|
||||
type: 'feed',
|
||||
sourceOrigin: 'ai',
|
||||
title: 'Willkommen bei News Hub!',
|
||||
excerpt: 'Dein persönlicher Nachrichtenleser mit KI-Zusammenfassungen und Read-Later Funktion.',
|
||||
content:
|
||||
'News Hub kombiniert KI-kuratierte Nachrichten mit deiner persönlichen Leseliste. Speichere Artikel von jeder Website, lese sie offline und entdecke neue Perspektiven.',
|
||||
categoryId: 'cat-tech',
|
||||
isArchived: false,
|
||||
wordCount: 42,
|
||||
readingTimeMinutes: 1,
|
||||
publishedAt: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
id: 'demo-2',
|
||||
type: 'saved',
|
||||
sourceOrigin: 'user_saved',
|
||||
title: 'Beispiel: Gespeicherter Artikel',
|
||||
excerpt:
|
||||
'So sieht ein gespeicherter Artikel aus. Nutze die Browser-Extension um Artikel zu speichern.',
|
||||
originalUrl: 'https://example.com',
|
||||
content:
|
||||
'Dies ist ein Beispiel für einen Artikel, den du über die Browser-Extension oder die Web-App gespeichert hast.',
|
||||
isArchived: false,
|
||||
wordCount: 30,
|
||||
readingTimeMinutes: 1,
|
||||
},
|
||||
];
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
import { createLocalStore, type BaseRecord } from '@manacore/local-store';
|
||||
|
||||
const SYNC_SERVER_URL = import.meta.env.PUBLIC_SYNC_SERVER_URL || 'http://localhost:3050';
|
||||
|
||||
export interface LocalArticle extends BaseRecord {
|
||||
type: 'feed' | 'summary' | 'in_depth' | 'saved';
|
||||
sourceOrigin: 'ai' | 'user_saved';
|
||||
title: string;
|
||||
content?: string | null;
|
||||
htmlContent?: string | null;
|
||||
excerpt?: string | null;
|
||||
originalUrl?: string | null;
|
||||
author?: string | null;
|
||||
siteName?: string | null;
|
||||
imageUrl?: string | null;
|
||||
wordCount?: number | null;
|
||||
readingTimeMinutes?: number | null;
|
||||
categoryId?: string | null;
|
||||
isArchived: boolean;
|
||||
publishedAt?: string | null;
|
||||
}
|
||||
|
||||
export interface LocalCategory extends BaseRecord {
|
||||
name: string;
|
||||
slug: string;
|
||||
color?: string | null;
|
||||
order: number;
|
||||
}
|
||||
|
||||
import { guestArticles, guestCategories } from './guest-seed';
|
||||
|
||||
export const newsStore = createLocalStore({
|
||||
appId: 'news',
|
||||
collections: [
|
||||
{
|
||||
name: 'articles',
|
||||
indexes: ['type', 'sourceOrigin', 'isArchived', 'categoryId', '[type+isArchived]'],
|
||||
guestSeed: guestArticles,
|
||||
},
|
||||
{
|
||||
name: 'categories',
|
||||
indexes: ['slug', 'order'],
|
||||
guestSeed: guestCategories,
|
||||
},
|
||||
],
|
||||
sync: { serverUrl: SYNC_SERVER_URL },
|
||||
});
|
||||
|
||||
export const articleCollection = newsStore.collection<LocalArticle>('articles');
|
||||
export const categoryCollection = newsStore.collection<LocalCategory>('categories');
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
import { createManaAuthStore } from '@manacore/shared-auth-stores';
|
||||
|
||||
export const authStore = createManaAuthStore({
|
||||
devBackendPort: 3071,
|
||||
});
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { onMount } from 'svelte';
|
||||
import { PillNavigation } from '@manacore/shared-ui';
|
||||
import type { PillNavItem } from '@manacore/shared-ui';
|
||||
import { getPillAppItems, getManaApp } from '@manacore/shared-branding';
|
||||
import { AuthGate, GuestWelcomeModal, SessionExpiredBanner } from '@manacore/shared-auth-ui';
|
||||
import { shouldShowGuestWelcome } from '@manacore/shared-auth-ui';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { newsStore } from '$lib/data/local-store';
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
let appItems = $derived(getPillAppItems(undefined, undefined, undefined, authStore.user?.tier));
|
||||
let userEmail = $derived(authStore.isAuthenticated ? (authStore.user?.email ?? '') : '');
|
||||
|
||||
const navItems: PillNavItem[] = [
|
||||
{ href: '/feed', label: 'Feed', icon: 'rss' },
|
||||
{ href: '/saved', label: 'Gespeichert', icon: 'bookmark' },
|
||||
{ href: '/settings', label: 'Einstellungen', icon: 'settings' },
|
||||
];
|
||||
|
||||
let isCollapsed = $state(false);
|
||||
let isDark = $state(true);
|
||||
let showGuestWelcome = $state(false);
|
||||
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
const target = event.target as HTMLElement;
|
||||
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable)
|
||||
return;
|
||||
if ((event.ctrlKey || event.metaKey) && !event.shiftKey && !event.altKey) {
|
||||
const num = parseInt(event.key);
|
||||
const routes = ['/feed', '/saved', '/settings'];
|
||||
if (num >= 1 && num <= 3) {
|
||||
event.preventDefault();
|
||||
goto(routes[num - 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleCollapsedChange(collapsed: boolean) {
|
||||
isCollapsed = collapsed;
|
||||
localStorage?.setItem('news-nav-collapsed', String(collapsed));
|
||||
}
|
||||
|
||||
function handleToggleTheme() {
|
||||
isDark = !isDark;
|
||||
document.documentElement.classList.toggle('dark', isDark);
|
||||
localStorage?.setItem('news-dark-mode', String(isDark));
|
||||
}
|
||||
|
||||
async function handleLogout() {
|
||||
newsStore.stopSync();
|
||||
await authStore.signOut();
|
||||
goto('/auth/login');
|
||||
}
|
||||
|
||||
function handleAuthReady() {
|
||||
if (authStore.isAuthenticated) {
|
||||
newsStore.startSync(() => authStore.getValidToken());
|
||||
}
|
||||
if (!authStore.isAuthenticated && shouldShowGuestWelcome('news')) {
|
||||
showGuestWelcome = true;
|
||||
}
|
||||
const savedCollapsed = localStorage?.getItem('news-nav-collapsed');
|
||||
if (savedCollapsed === 'true') isCollapsed = true;
|
||||
const savedDark = localStorage?.getItem('news-dark-mode');
|
||||
isDark = savedDark !== 'false'; // default dark
|
||||
document.documentElement.classList.toggle('dark', isDark);
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window onkeydown={handleKeydown} />
|
||||
|
||||
<AuthGate
|
||||
{authStore}
|
||||
{goto}
|
||||
allowGuest={true}
|
||||
onReady={handleAuthReady}
|
||||
requiredTier={getManaApp('news')?.requiredTier}
|
||||
appName={getManaApp('news')?.name}
|
||||
>
|
||||
<div class="flex min-h-screen flex-col">
|
||||
<PillNavigation
|
||||
items={navItems}
|
||||
currentPath={$page.url.pathname}
|
||||
appName="News"
|
||||
homeRoute="/feed"
|
||||
onLogout={handleLogout}
|
||||
onToggleTheme={handleToggleTheme}
|
||||
{isDark}
|
||||
{isCollapsed}
|
||||
onCollapsedChange={handleCollapsedChange}
|
||||
showThemeToggle={true}
|
||||
primaryColor="#10b981"
|
||||
showAppSwitcher={true}
|
||||
{appItems}
|
||||
{userEmail}
|
||||
settingsHref="/settings"
|
||||
>
|
||||
{#snippet logo()}
|
||||
<span class="text-xl">📰</span>
|
||||
<span class="pill-label font-bold">News</span>
|
||||
{/snippet}
|
||||
</PillNavigation>
|
||||
|
||||
<main class="main-content flex-1 transition-all duration-300 {isCollapsed ? '' : 'pt-20'}">
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
{@render children()}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<GuestWelcomeModal
|
||||
appId="news"
|
||||
visible={showGuestWelcome}
|
||||
onClose={() => (showGuestWelcome = false)}
|
||||
onLogin={() => goto('/auth/login')}
|
||||
onRegister={() => goto('/auth/register')}
|
||||
locale="de"
|
||||
/>
|
||||
|
||||
{#if authStore.isAuthenticated}
|
||||
<SessionExpiredBanner locale="de" loginHref="/auth/login" />
|
||||
{/if}
|
||||
</AuthGate>
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
const NEWS_SERVER = import.meta.env.PUBLIC_NEWS_SERVER_URL || 'http://localhost:3071';
|
||||
|
||||
let articles = $state<Record<string, unknown>[]>([]);
|
||||
let loading = $state(true);
|
||||
let selectedType = $state<string>('');
|
||||
|
||||
async function loadArticles() {
|
||||
loading = true;
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (selectedType) params.set('type', selectedType);
|
||||
params.set('limit', '30');
|
||||
const res = await fetch(`${NEWS_SERVER}/api/v1/feed?${params}`);
|
||||
if (res.ok) articles = await res.json();
|
||||
} catch {
|
||||
// Server offline
|
||||
}
|
||||
loading = false;
|
||||
}
|
||||
|
||||
function changeType(type: string) {
|
||||
selectedType = type;
|
||||
loadArticles();
|
||||
}
|
||||
|
||||
onMount(loadArticles);
|
||||
</script>
|
||||
|
||||
<div class="mx-auto max-w-4xl">
|
||||
<div class="mb-6 flex items-center justify-between">
|
||||
<h1 class="text-3xl font-bold">Feed</h1>
|
||||
<div class="flex gap-1">
|
||||
{#each [{ value: '', label: 'Alle' }, { value: 'feed', label: 'News' }, { value: 'summary', label: 'Summaries' }, { value: 'in_depth', label: 'In-Depth' }] as tab}
|
||||
<button
|
||||
onclick={() => changeType(tab.value)}
|
||||
class="rounded-md px-3 py-1.5 text-sm font-medium transition-colors {selectedType ===
|
||||
tab.value
|
||||
? 'bg-emerald-600 text-white'
|
||||
: 'bg-gray-800 text-gray-300 hover:bg-gray-700'}"
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if loading}
|
||||
<div class="space-y-4">
|
||||
{#each Array(5) as _}
|
||||
<div class="h-24 animate-pulse rounded-xl bg-gray-800"></div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else if articles.length === 0}
|
||||
<div class="rounded-xl border-2 border-dashed border-gray-700 p-12 text-center">
|
||||
<p class="text-lg font-medium text-gray-400">Noch keine Artikel im Feed</p>
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
AI-kuratierte Nachrichten erscheinen hier automatisch.
|
||||
</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="space-y-4">
|
||||
{#each articles as article}
|
||||
<a
|
||||
href="/feed/{article.id}"
|
||||
class="block rounded-xl border border-gray-800 bg-gray-900 p-5 transition-all hover:border-gray-700 hover:bg-gray-800/80"
|
||||
>
|
||||
<div class="flex gap-4">
|
||||
{#if article.imageUrl}
|
||||
<img
|
||||
src={String(article.imageUrl)}
|
||||
alt=""
|
||||
class="h-20 w-28 shrink-0 rounded-lg object-cover"
|
||||
/>
|
||||
{/if}
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="mb-1 flex items-center gap-2">
|
||||
{#if article.type === 'summary'}
|
||||
<span class="rounded bg-blue-900 px-1.5 py-0.5 text-xs text-blue-300"
|
||||
>Summary</span
|
||||
>
|
||||
{:else if article.type === 'in_depth'}
|
||||
<span class="rounded bg-purple-900 px-1.5 py-0.5 text-xs text-purple-300"
|
||||
>In-Depth</span
|
||||
>
|
||||
{/if}
|
||||
{#if article.readingTimeMinutes}
|
||||
<span class="text-xs text-gray-500">{article.readingTimeMinutes} Min.</span>
|
||||
{/if}
|
||||
</div>
|
||||
<h2 class="truncate text-lg font-semibold text-gray-100">{article.title}</h2>
|
||||
{#if article.excerpt}
|
||||
<p class="mt-1 line-clamp-2 text-sm text-gray-400">{article.excerpt}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
@ -1,173 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { useLiveQuery } from '@manacore/local-store/svelte';
|
||||
import { articleCollection } from '$lib/data/local-store';
|
||||
import type { LocalArticle } from '$lib/data/local-store';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { Archive, Trash } from '@manacore/shared-icons';
|
||||
|
||||
const NEWS_SERVER = import.meta.env.PUBLIC_NEWS_SERVER_URL || 'http://localhost:3071';
|
||||
|
||||
const savedArticles = useLiveQuery(() =>
|
||||
articleCollection.getAll({ sourceOrigin: 'user_saved' })
|
||||
);
|
||||
|
||||
let saveUrl = $state('');
|
||||
let saving = $state(false);
|
||||
let showArchived = $state(false);
|
||||
|
||||
let filteredArticles = $derived.by(() => {
|
||||
const all = savedArticles.value ?? [];
|
||||
return showArchived ? all : all.filter((a) => !a.isArchived);
|
||||
});
|
||||
|
||||
async function saveFromUrl() {
|
||||
if (!saveUrl) return;
|
||||
saving = true;
|
||||
try {
|
||||
const token = authStore.isAuthenticated ? await authStore.getValidToken() : null;
|
||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||
|
||||
const res = await fetch(`${NEWS_SERVER}/api/v1/extract/preview`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({ url: saveUrl }),
|
||||
});
|
||||
|
||||
if (!res.ok) throw new Error('Extraction failed');
|
||||
const extracted = await res.json();
|
||||
|
||||
await articleCollection.insert({
|
||||
id: crypto.randomUUID(),
|
||||
type: 'saved',
|
||||
sourceOrigin: 'user_saved',
|
||||
title: extracted.title,
|
||||
content: extracted.content,
|
||||
htmlContent: extracted.htmlContent,
|
||||
excerpt: extracted.excerpt,
|
||||
originalUrl: saveUrl,
|
||||
author: extracted.byline,
|
||||
siteName: extracted.siteName,
|
||||
wordCount: extracted.wordCount,
|
||||
readingTimeMinutes: extracted.readingTimeMinutes,
|
||||
isArchived: false,
|
||||
});
|
||||
|
||||
toast.success(`"${extracted.title}" gespeichert`);
|
||||
saveUrl = '';
|
||||
} catch {
|
||||
toast.error('Artikel konnte nicht extrahiert werden');
|
||||
}
|
||||
saving = false;
|
||||
}
|
||||
|
||||
async function toggleArchive(article: LocalArticle) {
|
||||
await articleCollection.update(article.id, { isArchived: !article.isArchived });
|
||||
toast.success(article.isArchived ? 'Wiederhergestellt' : 'Archiviert');
|
||||
}
|
||||
|
||||
async function deleteArticle(article: LocalArticle) {
|
||||
if (!confirm(`"${article.title}" löschen?`)) return;
|
||||
await articleCollection.delete(article.id);
|
||||
toast.success('Gelöscht');
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="mx-auto max-w-4xl">
|
||||
<h1 class="mb-6 text-3xl font-bold">Gespeicherte Artikel</h1>
|
||||
|
||||
<!-- Save URL Form -->
|
||||
<div class="mb-6 rounded-xl border border-gray-800 bg-gray-900 p-5">
|
||||
<div class="flex gap-3">
|
||||
<input
|
||||
type="url"
|
||||
bind:value={saveUrl}
|
||||
placeholder="https://example.com/article — URL einfügen und speichern"
|
||||
class="flex-1 rounded-lg border border-gray-700 bg-gray-800 px-4 py-3 text-gray-100 placeholder-gray-500 focus:border-emerald-500 focus:outline-none"
|
||||
onkeydown={(e) => e.key === 'Enter' && saveFromUrl()}
|
||||
/>
|
||||
<button
|
||||
onclick={saveFromUrl}
|
||||
disabled={!saveUrl || saving}
|
||||
class="rounded-lg bg-emerald-600 px-6 py-3 font-medium text-white hover:bg-emerald-700 disabled:opacity-50"
|
||||
>
|
||||
{saving ? 'Wird gespeichert...' : 'Speichern'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter -->
|
||||
<div class="mb-4">
|
||||
<label class="flex cursor-pointer items-center gap-2 text-sm text-gray-400">
|
||||
<input type="checkbox" bind:checked={showArchived} class="rounded" />
|
||||
Archivierte anzeigen
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Articles List -->
|
||||
{#if savedArticles.loading}
|
||||
<div class="space-y-3">
|
||||
{#each Array(3) as _}
|
||||
<div class="h-20 animate-pulse rounded-xl bg-gray-800"></div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else if filteredArticles.length === 0}
|
||||
<div class="rounded-xl border-2 border-dashed border-gray-700 p-12 text-center">
|
||||
<p class="text-lg font-medium text-gray-400">Noch keine gespeicherten Artikel</p>
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
Füge eine URL oben ein oder nutze die Browser-Extension.
|
||||
</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="space-y-3">
|
||||
{#each filteredArticles as article (article.id)}
|
||||
<div
|
||||
class="group rounded-xl border border-gray-800 bg-gray-900 p-4 transition-all hover:border-gray-700 {article.isArchived
|
||||
? 'opacity-60'
|
||||
: ''}"
|
||||
>
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="min-w-0 flex-1">
|
||||
<h3 class="truncate font-semibold text-gray-100">{article.title}</h3>
|
||||
{#if article.excerpt}
|
||||
<p class="mt-1 line-clamp-2 text-sm text-gray-400">{article.excerpt}</p>
|
||||
{/if}
|
||||
<div class="mt-2 flex items-center gap-3 text-xs text-gray-500">
|
||||
{#if article.siteName}
|
||||
<span>{article.siteName}</span>
|
||||
{/if}
|
||||
{#if article.readingTimeMinutes}
|
||||
<span>{article.readingTimeMinutes} Min.</span>
|
||||
{/if}
|
||||
{#if article.originalUrl}
|
||||
<a
|
||||
href={article.originalUrl}
|
||||
target="_blank"
|
||||
class="text-emerald-500 hover:underline">Original</a
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-4 flex items-center gap-1">
|
||||
<button
|
||||
onclick={() => toggleArchive(article)}
|
||||
class="rounded-lg p-2 text-gray-500 opacity-0 transition-all hover:bg-gray-800 hover:text-gray-300 group-hover:opacity-100"
|
||||
title={article.isArchived ? 'Wiederherstellen' : 'Archivieren'}
|
||||
>
|
||||
<Archive size={16} />
|
||||
</button>
|
||||
<button
|
||||
onclick={() => deleteArticle(article)}
|
||||
class="rounded-lg p-2 text-gray-500 opacity-0 transition-all hover:bg-gray-800 hover:text-red-400 group-hover:opacity-100"
|
||||
title="Löschen"
|
||||
>
|
||||
<Trash size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
<script lang="ts">
|
||||
import '../app.css';
|
||||
import { onMount } from 'svelte';
|
||||
import { Toaster } from 'svelte-sonner';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { newsStore } from '$lib/data/local-store';
|
||||
|
||||
let { children } = $props();
|
||||
let loading = $state(true);
|
||||
|
||||
onMount(async () => {
|
||||
await authStore.initialize();
|
||||
await newsStore.initialize();
|
||||
loading = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if loading}
|
||||
<div class="flex min-h-screen items-center justify-center bg-gray-950">
|
||||
<div
|
||||
class="inline-block h-10 w-10 animate-spin rounded-full border-4 border-solid border-emerald-500 border-r-transparent"
|
||||
></div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="min-h-screen bg-gray-950 text-gray-100">
|
||||
{@render children()}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Toaster
|
||||
position="bottom-right"
|
||||
expand={false}
|
||||
richColors
|
||||
closeButton
|
||||
duration={4000}
|
||||
visibleToasts={3}
|
||||
/>
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
onMount(() => goto('/feed'));
|
||||
</script>
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { LoginPage } from '@manacore/shared-auth-ui';
|
||||
import NewsLogo from '$lib/components/NewsLogo.svelte';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
|
||||
async function handleSignIn(email: string, password: string) {
|
||||
return authStore.signIn(email, password);
|
||||
}
|
||||
</script>
|
||||
|
||||
<LoginPage
|
||||
appName="News Hub"
|
||||
logo={NewsLogo}
|
||||
primaryColor="#10b981"
|
||||
onSignIn={handleSignIn}
|
||||
onResendVerification={(email) => authStore.resendVerificationEmail(email)}
|
||||
passkeyAvailable={authStore.isPasskeyAvailable()}
|
||||
onSignInWithPasskey={() => authStore.signInWithPasskey()}
|
||||
onVerifyTwoFactor={(code, trust) => authStore.verifyTwoFactor(code, trust)}
|
||||
onVerifyBackupCode={(code) => authStore.verifyBackupCode(code)}
|
||||
onSendMagicLink={(email) => authStore.sendMagicLink(email)}
|
||||
{goto}
|
||||
successRedirect="/feed"
|
||||
registerPath="/auth/register"
|
||||
forgotPasswordPath="/auth/forgot-password"
|
||||
/>
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { RegisterPage } from '@manacore/shared-auth-ui';
|
||||
import NewsLogo from '$lib/components/NewsLogo.svelte';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
|
||||
async function handleSignUp(email: string, password: string) {
|
||||
return authStore.signUp(email, password);
|
||||
}
|
||||
</script>
|
||||
|
||||
<RegisterPage
|
||||
appName="News Hub"
|
||||
logo={NewsLogo}
|
||||
primaryColor="#10b981"
|
||||
onSignUp={handleSignUp}
|
||||
{goto}
|
||||
successRedirect="/feed"
|
||||
loginPath="/auth/login"
|
||||
/>
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
import adapter from '@sveltejs/adapter-node';
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
preprocess: [vitePreprocess()],
|
||||
kit: {
|
||||
adapter: adapter(),
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [tailwindcss(), sveltekit()],
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue