From 91116bf0f1d6253af4fc3b9f59df6ad8e9c03ba8 Mon Sep 17 00:00:00 2001 From: Till JS Date: Thu, 26 Mar 2026 21:41:14 +0100 Subject: [PATCH] feat(apps): integrate shared TagStrip into all 15 remaining apps Migrated apps with existing local tags (photos, storage, picture): - Replace local tag stores with createTagStore wrapper - Add shared TagStrip to layouts with tag filtering support - Storage: new tag store, /tags management page - Picture: migrated from Svelte 4 writables to createTagStore New TagStrip added to 12 apps without prior tag system: - chat, citycorners, clock, context, manadeck, manacore, matrix, mukke, planta, presi, questions, zitare - Each gets: tag store, Tags toggle pill in PillNav, TagStrip overlay, /tags management page, fetchTags on auth ready - All backed by central mana-core-auth Tags API All 18 apps now have: - Tags pill in PillNav (toggles TagStrip overlay) - Shared TagStrip component from @manacore/shared-ui - Tag store using createTagStore from @manacore/shared-stores - /tags management page - Cross-app tags via central mana-core-auth Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/chat/apps/web/package.json | 1 + .../apps/web/src/lib/stores/tags.svelte.ts | 20 ++ .../web/src/routes/(protected)/+layout.svelte | 35 +- .../src/routes/(protected)/tags/+page.svelte | 49 +++ apps/citycorners/apps/web/package.json | 1 + .../apps/web/src/lib/stores/tags.svelte.ts | 20 ++ .../apps/web/src/routes/(app)/+layout.svelte | 38 ++- .../web/src/routes/(app)/tags/+page.svelte | 49 +++ apps/clock/apps/web/package.json | 1 + .../apps/web/src/lib/stores/tags.svelte.ts | 20 ++ .../apps/web/src/routes/(app)/+layout.svelte | 35 +- .../web/src/routes/(app)/tags/+page.svelte | 49 +++ apps/context/apps/web/package.json | 1 + .../apps/web/src/lib/stores/tags.svelte.ts | 20 ++ .../apps/web/src/routes/(app)/+layout.svelte | 35 +- .../web/src/routes/(app)/tags/+page.svelte | 49 +++ apps/manacore/apps/web/package.json | 1 + .../apps/web/src/lib/stores/tags.svelte.ts | 20 ++ .../apps/web/src/routes/(app)/+layout.svelte | 35 +- .../web/src/routes/(app)/tags/+page.svelte | 49 +++ apps/manadeck/apps/web/package.json | 1 + .../apps/web/src/lib/stores/tags.svelte.ts | 20 ++ .../apps/web/src/routes/(app)/+layout.svelte | 35 +- .../web/src/routes/(app)/tags/+page.svelte | 49 +++ apps/matrix/apps/web/package.json | 2 + .../apps/web/src/lib/stores/tags.svelte.ts | 24 ++ .../apps/web/src/routes/(app)/+layout.svelte | 35 +- .../web/src/routes/(app)/tags/+page.svelte | 49 +++ apps/mukke/apps/web/package.json | 1 + .../apps/web/src/lib/stores/tags.svelte.ts | 20 ++ .../apps/web/src/routes/(app)/+layout.svelte | 33 +- .../web/src/routes/(app)/tags/+page.svelte | 49 +++ .../apps/web/src/lib/stores/tags.svelte.ts | 106 +++--- .../apps/web/src/routes/(app)/+layout.svelte | 44 ++- apps/picture/apps/web/package.json | 2 + apps/picture/apps/web/src/lib/stores/tags.ts | 55 +++- .../apps/web/src/routes/app/+layout.svelte | 47 ++- apps/planta/apps/web/package.json | 2 + .../apps/web/src/lib/stores/tags.svelte.ts | 20 ++ .../apps/web/src/routes/(app)/+layout.svelte | 34 +- .../web/src/routes/(app)/tags/+page.svelte | 49 +++ apps/presi/apps/web/package.json | 1 + .../apps/web/src/lib/stores/tags.svelte.ts | 20 ++ .../apps/web/src/routes/(app)/+layout.svelte | 39 ++- .../web/src/routes/(app)/tags/+page.svelte | 49 +++ apps/questions/apps/web/package.json | 2 + .../apps/web/src/lib/stores/tags.svelte.ts | 20 ++ .../apps/web/src/routes/(app)/+layout.svelte | 33 +- .../web/src/routes/(app)/tags/+page.svelte | 49 +++ apps/storage/apps/web/package.json | 1 + .../apps/web/src/lib/stores/tags.svelte.ts | 62 ++++ .../apps/web/src/routes/+layout.svelte | 49 ++- .../apps/web/src/routes/tags/+page.svelte | 307 ++++++++++++++++++ apps/zitare/apps/web/package.json | 1 + .../apps/web/src/lib/stores/tags.svelte.ts | 20 ++ .../apps/web/src/routes/(app)/+layout.svelte | 38 ++- .../web/src/routes/(app)/tags/+page.svelte | 49 +++ 57 files changed, 1852 insertions(+), 93 deletions(-) create mode 100644 apps/chat/apps/web/src/lib/stores/tags.svelte.ts create mode 100644 apps/chat/apps/web/src/routes/(protected)/tags/+page.svelte create mode 100644 apps/citycorners/apps/web/src/lib/stores/tags.svelte.ts create mode 100644 apps/citycorners/apps/web/src/routes/(app)/tags/+page.svelte create mode 100644 apps/clock/apps/web/src/lib/stores/tags.svelte.ts create mode 100644 apps/clock/apps/web/src/routes/(app)/tags/+page.svelte create mode 100644 apps/context/apps/web/src/lib/stores/tags.svelte.ts create mode 100644 apps/context/apps/web/src/routes/(app)/tags/+page.svelte create mode 100644 apps/manacore/apps/web/src/lib/stores/tags.svelte.ts create mode 100644 apps/manacore/apps/web/src/routes/(app)/tags/+page.svelte create mode 100644 apps/manadeck/apps/web/src/lib/stores/tags.svelte.ts create mode 100644 apps/manadeck/apps/web/src/routes/(app)/tags/+page.svelte create mode 100644 apps/matrix/apps/web/src/lib/stores/tags.svelte.ts create mode 100644 apps/matrix/apps/web/src/routes/(app)/tags/+page.svelte create mode 100644 apps/mukke/apps/web/src/lib/stores/tags.svelte.ts create mode 100644 apps/mukke/apps/web/src/routes/(app)/tags/+page.svelte create mode 100644 apps/planta/apps/web/src/lib/stores/tags.svelte.ts create mode 100644 apps/planta/apps/web/src/routes/(app)/tags/+page.svelte create mode 100644 apps/presi/apps/web/src/lib/stores/tags.svelte.ts create mode 100644 apps/presi/apps/web/src/routes/(app)/tags/+page.svelte create mode 100644 apps/questions/apps/web/src/lib/stores/tags.svelte.ts create mode 100644 apps/questions/apps/web/src/routes/(app)/tags/+page.svelte create mode 100644 apps/storage/apps/web/src/lib/stores/tags.svelte.ts create mode 100644 apps/storage/apps/web/src/routes/tags/+page.svelte create mode 100644 apps/zitare/apps/web/src/lib/stores/tags.svelte.ts create mode 100644 apps/zitare/apps/web/src/routes/(app)/tags/+page.svelte diff --git a/apps/chat/apps/web/package.json b/apps/chat/apps/web/package.json index e7a518df7..b834159cd 100644 --- a/apps/chat/apps/web/package.json +++ b/apps/chat/apps/web/package.json @@ -47,6 +47,7 @@ "@manacore/shared-icons": "workspace:*", "@manacore/shared-profile-ui": "workspace:*", "@manacore/shared-stores": "workspace:*", + "@manacore/shared-tags": "workspace:*", "@manacore/shared-subscription-ui": "workspace:*", "@manacore/shared-tailwind": "workspace:*", "@manacore/shared-theme": "workspace:*", diff --git a/apps/chat/apps/web/src/lib/stores/tags.svelte.ts b/apps/chat/apps/web/src/lib/stores/tags.svelte.ts new file mode 100644 index 000000000..0e08b03ef --- /dev/null +++ b/apps/chat/apps/web/src/lib/stores/tags.svelte.ts @@ -0,0 +1,20 @@ +/** + * Tag Store - Uses shared createTagStore backed by central mana-core-auth + */ +import { browser } from '$app/environment'; +import { createTagStore } from '@manacore/shared-stores'; +import { authStore } from '$lib/stores/auth.svelte'; + +function getAuthUrl(): string { + if (browser && typeof window !== 'undefined') { + const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string }) + .__PUBLIC_MANA_CORE_AUTH_URL__; + return injectedUrl || 'http://localhost:3001'; + } + return 'http://localhost:3001'; +} + +export const tagStore = createTagStore({ + authUrl: getAuthUrl(), + getToken: () => authStore.getValidToken(), +}); diff --git a/apps/chat/apps/web/src/routes/(protected)/+layout.svelte b/apps/chat/apps/web/src/routes/(protected)/+layout.svelte index 86689d01a..e8c8556a8 100644 --- a/apps/chat/apps/web/src/routes/(protected)/+layout.svelte +++ b/apps/chat/apps/web/src/routes/(protected)/+layout.svelte @@ -15,8 +15,9 @@ import type { ThemeVariant } from '@manacore/shared-theme'; import { filterHiddenNavItems } from '@manacore/shared-theme'; import { isNavCollapsed as collapsedStore } from '$lib/stores/navigation'; - import { PillNavigation } from '@manacore/shared-ui'; + import { PillNavigation, TagStrip } from '@manacore/shared-ui'; import type { PillNavItem, PillDropdownItem } from '@manacore/shared-ui'; + import { tagStore } from '$lib/stores/tags.svelte'; import { getPillAppItems } from '@manacore/shared-branding'; import { getLanguageDropdownItems, getCurrentLanguageLabel } from '@manacore/shared-i18n'; import { setLocale, supportedLocales } from '$lib/i18n'; @@ -78,6 +79,12 @@ ); let currentLanguageLabel = $derived(getCurrentLanguageLabel(currentLocale)); + // TagStrip visibility + let isTagStripVisible = $state(false); + function handleTagStripToggle() { + isTagStripVisible = !isTagStripVisible; + } + // Base navigation items for Chat (settings moved to user dropdown) const baseNavItems: PillNavItem[] = [ { href: '/chat', label: 'Chat', icon: 'home' }, @@ -86,6 +93,13 @@ { href: '/spaces', label: 'Spaces', icon: 'building' }, { href: '/documents', label: 'Dokumente', icon: 'archive' }, { href: '/archive', label: 'Archiv', icon: 'list' }, + { + href: '/', + label: 'Tags', + icon: 'tag', + onClick: handleTagStripToggle, + active: isTagStripVisible, + }, ]; // Navigation items filtered by visibility settings (with fallback for guest mode) @@ -153,8 +167,9 @@ collapsedStore.set(true); } - // Load user settings + // Load user settings and tags await userSettings.load(); + await tagStore.fetchTags(); // Check for session conversations to migrate if (conversationsStore.hasSessionConversations) { @@ -207,6 +222,22 @@ allAppsHref="/apps" /> + + {#if isTagStripVisible} + ({ + id: t.id, + name: t.name, + color: t.color || '#3b82f6', + }))} + selectedIds={[]} + onToggle={() => {}} + onClear={() => {}} + managementHref="/tags" + loading={tagStore.loading} + /> + {/if} +
{#if isChatPage} diff --git a/apps/chat/apps/web/src/routes/(protected)/tags/+page.svelte b/apps/chat/apps/web/src/routes/(protected)/tags/+page.svelte new file mode 100644 index 000000000..2e17c9cef --- /dev/null +++ b/apps/chat/apps/web/src/routes/(protected)/tags/+page.svelte @@ -0,0 +1,49 @@ + + + + Tags | ManaChat + + +
+

Tags verwalten

+

+ Tags sind app-übergreifend — Änderungen gelten in allen ManaCore-Apps. +

+ + {#if tagStore.loading} +

Lädt...

+ {:else if tagStore.tags.length === 0} +

Keine Tags vorhanden.

+ {:else} +
+ {#each tagStore.tags as tag} +
+ + {tag.name} +
+ {/each} +
+ {/if} +
+ + diff --git a/apps/citycorners/apps/web/package.json b/apps/citycorners/apps/web/package.json index 2bd0fd291..6a3f993e4 100644 --- a/apps/citycorners/apps/web/package.json +++ b/apps/citycorners/apps/web/package.json @@ -43,6 +43,7 @@ "@manacore/shared-icons": "workspace:*", "@manacore/shared-profile-ui": "workspace:*", "@manacore/shared-stores": "workspace:*", + "@manacore/shared-tags": "workspace:*", "@manacore/shared-tailwind": "workspace:*", "@manacore/shared-theme": "workspace:*", "@manacore/shared-theme-ui": "workspace:*", diff --git a/apps/citycorners/apps/web/src/lib/stores/tags.svelte.ts b/apps/citycorners/apps/web/src/lib/stores/tags.svelte.ts new file mode 100644 index 000000000..0e08b03ef --- /dev/null +++ b/apps/citycorners/apps/web/src/lib/stores/tags.svelte.ts @@ -0,0 +1,20 @@ +/** + * Tag Store - Uses shared createTagStore backed by central mana-core-auth + */ +import { browser } from '$app/environment'; +import { createTagStore } from '@manacore/shared-stores'; +import { authStore } from '$lib/stores/auth.svelte'; + +function getAuthUrl(): string { + if (browser && typeof window !== 'undefined') { + const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string }) + .__PUBLIC_MANA_CORE_AUTH_URL__; + return injectedUrl || 'http://localhost:3001'; + } + return 'http://localhost:3001'; +} + +export const tagStore = createTagStore({ + authUrl: getAuthUrl(), + getToken: () => authStore.getValidToken(), +}); diff --git a/apps/citycorners/apps/web/src/routes/(app)/+layout.svelte b/apps/citycorners/apps/web/src/routes/(app)/+layout.svelte index 7d16051e4..24fae8f85 100644 --- a/apps/citycorners/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/citycorners/apps/web/src/routes/(app)/+layout.svelte @@ -3,8 +3,9 @@ import { page } from '$app/stores'; import { onMount } from 'svelte'; import { _, locale } from 'svelte-i18n'; - import { PillNavigation, QuickInputBar } from '@manacore/shared-ui'; + import { PillNavigation, QuickInputBar, TagStrip } from '@manacore/shared-ui'; import type { PillNavItem, PillDropdownItem, QuickInputItem } from '@manacore/shared-ui'; + import { tagStore } from '$lib/stores/tags.svelte'; import { theme } from '$lib/stores/theme.svelte'; import { authStore } from '$lib/stores/auth.svelte'; import { favoritesStore } from '$lib/stores/favorites.svelte'; @@ -50,12 +51,25 @@ let userEmail = $derived(authStore.user?.email || $_('nav.settings')); + // TagStrip visibility + let isTagStripVisible = $state(false); + function handleTagStripToggle() { + isTagStripVisible = !isTagStripVisible; + } + let navItems = $derived([ { href: '/', label: $_('nav.explore'), icon: 'compass' }, { href: '/map', label: $_('nav.map'), icon: 'mappin' }, { href: '/add', label: $_('nav.add'), icon: 'plus' }, { href: '/favorites', label: $_('nav.favorites'), icon: 'heart' }, { href: '/settings', label: $_('nav.settings'), icon: 'settings' }, + { + href: '/', + label: 'Tags', + icon: 'tag', + onClick: handleTagStripToggle, + active: isTagStripVisible, + }, ]); function handleToggleTheme() { @@ -158,9 +172,13 @@ showNav = !showNav; } - onMount(() => { + onMount(async () => { const savedNav = localStorage.getItem('citycorners-nav-visible'); if (savedNav !== null) showNav = savedNav !== 'false'; + + if (authStore.isAuthenticated) { + await tagStore.fetchTags(); + } }); @@ -196,6 +214,22 @@ /> {/if} + + {#if isTagStripVisible} + ({ + id: t.id, + name: t.name, + color: t.color || '#3b82f6', + }))} + selectedIds={[]} + onToggle={() => {}} + onClear={() => {}} + managementHref="/tags" + loading={tagStore.loading} + /> + {/if} + + import { tagStore } from '$lib/stores/tags.svelte'; + import { onMount } from 'svelte'; + + onMount(() => { + if (tagStore.tags.length === 0) { + tagStore.fetchTags(); + } + }); + + + + Tags | CityCorners + + +
+

Tags verwalten

+

+ Tags sind app-übergreifend — Änderungen gelten in allen ManaCore-Apps. +

+ + {#if tagStore.loading} +

Lädt...

+ {:else if tagStore.tags.length === 0} +

Keine Tags vorhanden.

+ {:else} +
+ {#each tagStore.tags as tag} +
+ + {tag.name} +
+ {/each} +
+ {/if} +
+ + diff --git a/apps/clock/apps/web/package.json b/apps/clock/apps/web/package.json index 3df6a6d71..76315ab93 100644 --- a/apps/clock/apps/web/package.json +++ b/apps/clock/apps/web/package.json @@ -50,6 +50,7 @@ "@manacore/shared-icons": "workspace:*", "@manacore/shared-profile-ui": "workspace:*", "@manacore/shared-stores": "workspace:*", + "@manacore/shared-tags": "workspace:*", "@manacore/shared-subscription-ui": "workspace:*", "@manacore/shared-tailwind": "workspace:*", "@manacore/shared-theme": "workspace:*", diff --git a/apps/clock/apps/web/src/lib/stores/tags.svelte.ts b/apps/clock/apps/web/src/lib/stores/tags.svelte.ts new file mode 100644 index 000000000..0e08b03ef --- /dev/null +++ b/apps/clock/apps/web/src/lib/stores/tags.svelte.ts @@ -0,0 +1,20 @@ +/** + * Tag Store - Uses shared createTagStore backed by central mana-core-auth + */ +import { browser } from '$app/environment'; +import { createTagStore } from '@manacore/shared-stores'; +import { authStore } from '$lib/stores/auth.svelte'; + +function getAuthUrl(): string { + if (browser && typeof window !== 'undefined') { + const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string }) + .__PUBLIC_MANA_CORE_AUTH_URL__; + return injectedUrl || 'http://localhost:3001'; + } + return 'http://localhost:3001'; +} + +export const tagStore = createTagStore({ + authUrl: getAuthUrl(), + getToken: () => authStore.getValidToken(), +}); diff --git a/apps/clock/apps/web/src/routes/(app)/+layout.svelte b/apps/clock/apps/web/src/routes/(app)/+layout.svelte index 7f66550c6..cd214bb77 100644 --- a/apps/clock/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/clock/apps/web/src/routes/(app)/+layout.svelte @@ -2,7 +2,7 @@ import { goto } from '$app/navigation'; import { page } from '$app/stores'; import { locale } from 'svelte-i18n'; - import { PillNavigation, CommandBar } from '@manacore/shared-ui'; + import { PillNavigation, CommandBar, TagStrip } from '@manacore/shared-ui'; import type { PillNavItem, PillDropdownItem, @@ -32,6 +32,7 @@ import { clockOnboarding } from '$lib/stores/app-onboarding.svelte'; import { MiniOnboardingModal } from '@manacore/shared-app-onboarding'; import { SessionExpiredBanner, AuthGate } from '@manacore/shared-auth-ui'; + import { tagStore } from '$lib/stores/tags.svelte'; // App switcher items const appItems = getPillAppItems('clock'); @@ -164,6 +165,12 @@ // User email for user dropdown let userEmail = $derived(authStore.user?.email || 'Menü'); + // TagStrip visibility + let isTagStripVisible = $state(false); + function handleTagStripToggle() { + isTagStripVisible = !isTagStripVisible; + } + // Base navigation items for Clock const baseNavItems: PillNavItem[] = [ { href: '/', label: 'Übersicht', icon: 'home' }, @@ -174,6 +181,13 @@ { href: '/world-clock', label: 'Weltzeituhr', icon: 'globe' }, { href: '/life', label: 'Lebensuhr', icon: 'heart' }, { href: '/settings', label: 'Einstellungen', icon: 'settings' }, + { + href: '/', + label: 'Tags', + icon: 'tag', + onClick: handleTagStripToggle, + active: isTagStripVisible, + }, ]; // Navigation items filtered by visibility settings @@ -239,8 +253,9 @@ collapsedStore.set(true); } - // Load user settings + // Load user settings and tags await userSettings.load(); + await tagStore.fetchTags(); // Check for session data to migrate if (alarmsStore.hasSessionAlarms) { @@ -295,6 +310,22 @@ allAppsHref="/apps" /> + + {#if isTagStripVisible} + ({ + id: t.id, + name: t.name, + color: t.color || '#3b82f6', + }))} + selectedIds={[]} + onToggle={() => {}} + onClear={() => {}} + managementHref="/tags" + loading={tagStore.loading} + /> + {/if} +
{@render children()} diff --git a/apps/clock/apps/web/src/routes/(app)/tags/+page.svelte b/apps/clock/apps/web/src/routes/(app)/tags/+page.svelte new file mode 100644 index 000000000..f463ec24d --- /dev/null +++ b/apps/clock/apps/web/src/routes/(app)/tags/+page.svelte @@ -0,0 +1,49 @@ + + + + Tags | Clock + + +
+

Tags verwalten

+

+ Tags sind app-übergreifend — Änderungen gelten in allen ManaCore-Apps. +

+ + {#if tagStore.loading} +

Lädt...

+ {:else if tagStore.tags.length === 0} +

Keine Tags vorhanden.

+ {:else} +
+ {#each tagStore.tags as tag} +
+ + {tag.name} +
+ {/each} +
+ {/if} +
+ + diff --git a/apps/context/apps/web/package.json b/apps/context/apps/web/package.json index f2f6be313..2a3b57133 100644 --- a/apps/context/apps/web/package.json +++ b/apps/context/apps/web/package.json @@ -46,6 +46,7 @@ "@manacore/shared-icons": "workspace:*", "@manacore/shared-profile-ui": "workspace:*", "@manacore/shared-stores": "workspace:*", + "@manacore/shared-tags": "workspace:*", "@manacore/shared-subscription-ui": "workspace:*", "@manacore/shared-tailwind": "workspace:*", "@manacore/shared-theme": "workspace:*", diff --git a/apps/context/apps/web/src/lib/stores/tags.svelte.ts b/apps/context/apps/web/src/lib/stores/tags.svelte.ts new file mode 100644 index 000000000..0e08b03ef --- /dev/null +++ b/apps/context/apps/web/src/lib/stores/tags.svelte.ts @@ -0,0 +1,20 @@ +/** + * Tag Store - Uses shared createTagStore backed by central mana-core-auth + */ +import { browser } from '$app/environment'; +import { createTagStore } from '@manacore/shared-stores'; +import { authStore } from '$lib/stores/auth.svelte'; + +function getAuthUrl(): string { + if (browser && typeof window !== 'undefined') { + const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string }) + .__PUBLIC_MANA_CORE_AUTH_URL__; + return injectedUrl || 'http://localhost:3001'; + } + return 'http://localhost:3001'; +} + +export const tagStore = createTagStore({ + authUrl: getAuthUrl(), + getToken: () => authStore.getValidToken(), +}); diff --git a/apps/context/apps/web/src/routes/(app)/+layout.svelte b/apps/context/apps/web/src/routes/(app)/+layout.svelte index 2329264b9..531c6ea42 100644 --- a/apps/context/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/context/apps/web/src/routes/(app)/+layout.svelte @@ -3,7 +3,7 @@ import { page } from '$app/stores'; import { onMount } from 'svelte'; import { locale } from 'svelte-i18n'; - import { PillNavigation, CommandBar } from '@manacore/shared-ui'; + import { PillNavigation, CommandBar, TagStrip } from '@manacore/shared-ui'; import type { PillNavItem, PillDropdownItem, @@ -28,6 +28,7 @@ import { setLocale, supportedLocales } from '$lib/i18n'; import { contextOnboarding } from '$lib/stores/app-onboarding.svelte'; import { MiniOnboardingModal } from '@manacore/shared-app-onboarding'; + import { tagStore } from '$lib/stores/tags.svelte'; const appItems = getPillAppItems('context'); @@ -138,12 +139,25 @@ let userEmail = $derived(authStore.user?.email || 'Menü'); + // TagStrip visibility + let isTagStripVisible = $state(false); + function handleTagStripToggle() { + isTagStripVisible = !isTagStripVisible; + } + const baseNavItems: PillNavItem[] = [ { href: '/', label: 'Übersicht', icon: 'home' }, { href: '/spaces', label: 'Spaces', icon: 'folder' }, { href: '/documents', label: 'Dokumente', icon: 'file-text' }, { href: '/tokens', label: 'Tokens', icon: 'sparkle' }, { href: '/settings', label: 'Einstellungen', icon: 'settings' }, + { + href: '/', + label: 'Tags', + icon: 'tag', + onClick: handleTagStripToggle, + active: isTagStripVisible, + }, ]; const navItems = $derived( @@ -213,6 +227,9 @@ await userSettings.load(); + // Load tags + await tagStore.fetchTags(); + // Pre-load data for CommandBar search await Promise.all([spacesStore.load(), documentsStore.load()]); }); @@ -254,6 +271,22 @@ allAppsHref="/apps" /> + + {#if isTagStripVisible} + ({ + id: t.id, + name: t.name, + color: t.color || '#3b82f6', + }))} + selectedIds={[]} + onToggle={() => {}} + onClear={() => {}} + managementHref="/tags" + loading={tagStore.loading} + /> + {/if} +
{@render children()} diff --git a/apps/context/apps/web/src/routes/(app)/tags/+page.svelte b/apps/context/apps/web/src/routes/(app)/tags/+page.svelte new file mode 100644 index 000000000..8c54b6ce3 --- /dev/null +++ b/apps/context/apps/web/src/routes/(app)/tags/+page.svelte @@ -0,0 +1,49 @@ + + + + Tags | Context + + +
+

Tags verwalten

+

+ Tags sind app-übergreifend — Änderungen gelten in allen ManaCore-Apps. +

+ + {#if tagStore.loading} +

Lädt...

+ {:else if tagStore.tags.length === 0} +

Keine Tags vorhanden.

+ {:else} +
+ {#each tagStore.tags as tag} +
+ + {tag.name} +
+ {/each} +
+ {/if} +
+ + diff --git a/apps/manacore/apps/web/package.json b/apps/manacore/apps/web/package.json index e5d7005cd..4b0d51d35 100644 --- a/apps/manacore/apps/web/package.json +++ b/apps/manacore/apps/web/package.json @@ -57,6 +57,7 @@ "@manacore/shared-icons": "workspace:*", "@manacore/shared-profile-ui": "workspace:*", "@manacore/shared-stores": "workspace:*", + "@manacore/shared-tags": "workspace:*", "@manacore/shared-subscription-types": "workspace:*", "@manacore/shared-subscription-ui": "workspace:*", "@manacore/shared-tailwind": "workspace:*", diff --git a/apps/manacore/apps/web/src/lib/stores/tags.svelte.ts b/apps/manacore/apps/web/src/lib/stores/tags.svelte.ts new file mode 100644 index 000000000..0e08b03ef --- /dev/null +++ b/apps/manacore/apps/web/src/lib/stores/tags.svelte.ts @@ -0,0 +1,20 @@ +/** + * Tag Store - Uses shared createTagStore backed by central mana-core-auth + */ +import { browser } from '$app/environment'; +import { createTagStore } from '@manacore/shared-stores'; +import { authStore } from '$lib/stores/auth.svelte'; + +function getAuthUrl(): string { + if (browser && typeof window !== 'undefined') { + const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string }) + .__PUBLIC_MANA_CORE_AUTH_URL__; + return injectedUrl || 'http://localhost:3001'; + } + return 'http://localhost:3001'; +} + +export const tagStore = createTagStore({ + authUrl: getAuthUrl(), + getToken: () => authStore.getValidToken(), +}); diff --git a/apps/manacore/apps/web/src/routes/(app)/+layout.svelte b/apps/manacore/apps/web/src/routes/(app)/+layout.svelte index 095a5efa9..fb7f96a42 100644 --- a/apps/manacore/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/manacore/apps/web/src/routes/(app)/+layout.svelte @@ -6,8 +6,9 @@ import KeyboardShortcutsModal from '$lib/components/KeyboardShortcutsModal.svelte'; import SessionWarning from '$lib/components/SessionWarning.svelte'; import { locale } from 'svelte-i18n'; - import { PillNavigation } from '@manacore/shared-ui'; + import { PillNavigation, TagStrip } from '@manacore/shared-ui'; import type { PillNavItem, PillDropdownItem } from '@manacore/shared-ui'; + import { tagStore } from '$lib/stores/tags.svelte'; import { THEME_DEFINITIONS, DEFAULT_THEME_VARIANTS, @@ -83,6 +84,12 @@ // User email for user dropdown let userEmail = $derived(authStore.user?.email); + // TagStrip visibility + let isTagStripVisible = $state(false); + function handleTagStripToggle() { + isTagStripVisible = !isTagStripVisible; + } + // Navigation items for ManaCore const baseNavItems: PillNavItem[] = [ { href: '/home', label: 'Home', icon: 'home' }, @@ -93,6 +100,13 @@ { href: '/api-keys', label: 'API Keys', icon: 'key' }, { href: '/profile', label: 'Profil', icon: 'user' }, { href: '/settings', label: 'Settings', icon: 'settings' }, + { + href: '/', + label: 'Tags', + icon: 'tag', + onClick: handleTagStripToggle, + active: isTagStripVisible, + }, ]; // Only show admin link if user has admin role @@ -194,6 +208,9 @@ // Settings API not available - use defaults }); + // Load tags + tagStore.fetchTags(); + // Load onboarding state and show wizard if needed onboardingStore.load(); if (onboardingStore.shouldShow) { @@ -261,6 +278,22 @@ allAppsHref="/apps" /> + + {#if isTagStripVisible} + ({ + id: t.id, + name: t.name, + color: t.color || '#3b82f6', + }))} + selectedIds={[]} + onToggle={() => {}} + onClear={() => {}} + managementHref="/tags" + loading={tagStore.loading} + /> + {/if} +
diff --git a/apps/manacore/apps/web/src/routes/(app)/tags/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/tags/+page.svelte new file mode 100644 index 000000000..da2d6e611 --- /dev/null +++ b/apps/manacore/apps/web/src/routes/(app)/tags/+page.svelte @@ -0,0 +1,49 @@ + + + + Tags | ManaCore + + +
+

Tags verwalten

+

+ Tags sind app-übergreifend — Änderungen gelten in allen ManaCore-Apps. +

+ + {#if tagStore.loading} +

Lädt...

+ {:else if tagStore.tags.length === 0} +

Keine Tags vorhanden.

+ {:else} +
+ {#each tagStore.tags as tag} +
+ + {tag.name} +
+ {/each} +
+ {/if} +
+ + diff --git a/apps/manadeck/apps/web/package.json b/apps/manadeck/apps/web/package.json index 9ae70ac05..023660dbc 100644 --- a/apps/manadeck/apps/web/package.json +++ b/apps/manadeck/apps/web/package.json @@ -44,6 +44,7 @@ "@manacore/shared-icons": "workspace:*", "@manacore/shared-profile-ui": "workspace:*", "@manacore/shared-stores": "workspace:*", + "@manacore/shared-tags": "workspace:*", "@manacore/shared-subscription-types": "workspace:*", "@manacore/shared-subscription-ui": "workspace:*", "@manacore/shared-tailwind": "workspace:*", diff --git a/apps/manadeck/apps/web/src/lib/stores/tags.svelte.ts b/apps/manadeck/apps/web/src/lib/stores/tags.svelte.ts new file mode 100644 index 000000000..0e08b03ef --- /dev/null +++ b/apps/manadeck/apps/web/src/lib/stores/tags.svelte.ts @@ -0,0 +1,20 @@ +/** + * Tag Store - Uses shared createTagStore backed by central mana-core-auth + */ +import { browser } from '$app/environment'; +import { createTagStore } from '@manacore/shared-stores'; +import { authStore } from '$lib/stores/auth.svelte'; + +function getAuthUrl(): string { + if (browser && typeof window !== 'undefined') { + const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string }) + .__PUBLIC_MANA_CORE_AUTH_URL__; + return injectedUrl || 'http://localhost:3001'; + } + return 'http://localhost:3001'; +} + +export const tagStore = createTagStore({ + authUrl: getAuthUrl(), + getToken: () => authStore.getValidToken(), +}); diff --git a/apps/manadeck/apps/web/src/routes/(app)/+layout.svelte b/apps/manadeck/apps/web/src/routes/(app)/+layout.svelte index cf58b6281..8f392b7af 100644 --- a/apps/manadeck/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/manadeck/apps/web/src/routes/(app)/+layout.svelte @@ -7,8 +7,9 @@ import { userSettings } from '$lib/stores/user-settings.svelte'; import { theme } from '$lib/stores/theme'; import { isNavCollapsed as collapsedStore } from '$lib/stores/navigation'; - import { PillNavigation, QuickInputBar } from '@manacore/shared-ui'; + import { PillNavigation, QuickInputBar, TagStrip } from '@manacore/shared-ui'; import type { PillNavItem, PillDropdownItem, QuickInputItem } from '@manacore/shared-ui'; + import { tagStore } from '$lib/stores/tags.svelte'; import { THEME_DEFINITIONS, DEFAULT_THEME_VARIANTS, @@ -33,11 +34,24 @@ // Get theme state let isDark = $derived(theme.isDark); + // TagStrip visibility + let isTagStripVisible = $state(false); + function handleTagStripToggle() { + isTagStripVisible = !isTagStripVisible; + } + // Base navigation items for ManaDeck (Mana and Profile are in user dropdown) const baseNavItems: PillNavItem[] = [ { href: '/decks', label: 'Decks', icon: 'archive' }, { href: '/explore', label: 'Explore', icon: 'search' }, { href: '/progress', label: 'Progress', icon: 'chart' }, + { + href: '/', + label: 'Tags', + icon: 'tag', + onClick: handleTagStripToggle, + active: isTagStripVisible, + }, ]; // Navigation items filtered by visibility settings (with fallback for guest mode) @@ -165,8 +179,9 @@ return; } - // Load user settings + // Load user settings and tags await userSettings.load(); + await tagStore.fetchTags(); // Redirect to start page if on root and a custom start page is set const currentPath = window.location.pathname; @@ -229,6 +244,22 @@ allAppsHref="/apps" /> + + {#if isTagStripVisible} + ({ + id: t.id, + name: t.name, + color: t.color || '#3b82f6', + }))} + selectedIds={[]} + onToggle={() => {}} + onClear={() => {}} + managementHref="/tags" + loading={tagStore.loading} + /> + {/if} + + import { tagStore } from '$lib/stores/tags.svelte'; + import { onMount } from 'svelte'; + + onMount(() => { + if (tagStore.tags.length === 0) { + tagStore.fetchTags(); + } + }); + + + + Tags | ManaDeck + + +
+

Tags verwalten

+

+ Tags sind app-übergreifend — Änderungen gelten in allen ManaCore-Apps. +

+ + {#if tagStore.loading} +

Lädt...

+ {:else if tagStore.tags.length === 0} +

Keine Tags vorhanden.

+ {:else} +
+ {#each tagStore.tags as tag} +
+ + {tag.name} +
+ {/each} +
+ {/if} +
+ + diff --git a/apps/matrix/apps/web/package.json b/apps/matrix/apps/web/package.json index 851848e44..aa03aaae5 100644 --- a/apps/matrix/apps/web/package.json +++ b/apps/matrix/apps/web/package.json @@ -49,6 +49,8 @@ "@manacore/shared-icons": "workspace:*", "@manacore/shared-tailwind": "workspace:*", "@manacore/shared-theme": "workspace:*", + "@manacore/shared-stores": "workspace:*", + "@manacore/shared-tags": "workspace:*", "@manacore/shared-ui": "workspace:*", "@matrix-org/matrix-sdk-crypto-wasm": "^13.0.0", "buffer": "^6.0.3", diff --git a/apps/matrix/apps/web/src/lib/stores/tags.svelte.ts b/apps/matrix/apps/web/src/lib/stores/tags.svelte.ts new file mode 100644 index 000000000..189948067 --- /dev/null +++ b/apps/matrix/apps/web/src/lib/stores/tags.svelte.ts @@ -0,0 +1,24 @@ +/** + * Tag Store - Uses shared createTagStore backed by central mana-core-auth + * + * Matrix uses its own auth (Matrix homeserver), not mana-core-auth directly. + * The mana-core-auth token is obtained via session-to-token exchange and stored + * in localStorage. Tags will work when user has a mana-core-auth session. + */ +import { browser } from '$app/environment'; +import { createTagStore } from '@manacore/shared-stores'; +import { loadStoredAccessToken } from '$lib/stores/userSettings.svelte'; + +const AUTH_URL = import.meta.env.VITE_MANA_AUTH_URL || 'https://auth.mana.how'; + +function getAuthUrl(): string { + return AUTH_URL; +} + +export const tagStore = createTagStore({ + authUrl: getAuthUrl(), + getToken: () => { + if (!browser) return null; + return loadStoredAccessToken(); + }, +}); diff --git a/apps/matrix/apps/web/src/routes/(app)/+layout.svelte b/apps/matrix/apps/web/src/routes/(app)/+layout.svelte index 199c0db75..f2c82fa73 100644 --- a/apps/matrix/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/matrix/apps/web/src/routes/(app)/+layout.svelte @@ -20,8 +20,9 @@ } from '@manacore/shared-theme'; import type { ThemeVariant } from '@manacore/shared-theme'; import { isNavCollapsed as collapsedStore } from '$lib/stores/navigation.svelte'; - import { PillNavigation } from '@manacore/shared-ui'; + import { PillNavigation, TagStrip } from '@manacore/shared-ui'; import type { PillNavItem, PillDropdownItem, QuickInputItem } from '@manacore/shared-ui'; + import { tagStore } from '$lib/stores/tags.svelte'; import { MagnifyingGlass, X } from '@manacore/shared-icons'; import { getPillAppItems } from '@manacore/shared-branding'; import { getLanguageDropdownItems, getCurrentLanguageLabel } from '@manacore/shared-i18n'; @@ -71,6 +72,9 @@ // Load user settings (will use the token we just set) await userSettings.load(); + + // Load tags (uses mana-core-auth token) + await tagStore.fetchTags(); } // App switcher items @@ -121,11 +125,24 @@ ); let currentLanguageLabel = $derived(getCurrentLanguageLabel(currentLocale)); + // TagStrip visibility + let isTagStripVisible = $state(false); + function handleTagStripToggle() { + isTagStripVisible = !isTagStripVisible; + } + // Navigation items for Matrix const navItems: PillNavItem[] = [ { href: '/chat', label: 'Chat', icon: 'home' }, { href: '/bots', label: 'Bots', icon: 'robot' }, { href: '/settings', label: 'Einstellungen', icon: 'settings' }, + { + href: '/', + label: 'Tags', + icon: 'tag', + onClick: handleTagStripToggle, + active: isTagStripVisible, + }, ]; // User info from Matrix @@ -422,6 +439,22 @@ /> {/if} + + {#if isTagStripVisible && !isMobileRoomView} + ({ + id: t.id, + name: t.name, + color: t.color || '#3b82f6', + }))} + selectedIds={[]} + onToggle={() => {}} + onClear={() => {}} + managementHref="/tags" + loading={tagStore.loading} + /> + {/if} +
{@render children()} diff --git a/apps/matrix/apps/web/src/routes/(app)/tags/+page.svelte b/apps/matrix/apps/web/src/routes/(app)/tags/+page.svelte new file mode 100644 index 000000000..d21c0858b --- /dev/null +++ b/apps/matrix/apps/web/src/routes/(app)/tags/+page.svelte @@ -0,0 +1,49 @@ + + + + Tags | Manalink + + +
+

Tags verwalten

+

+ Tags sind app-übergreifend — Änderungen gelten in allen ManaCore-Apps. +

+ + {#if tagStore.loading} +

Lädt...

+ {:else if tagStore.tags.length === 0} +

Keine Tags vorhanden.

+ {:else} +
+ {#each tagStore.tags as tag} +
+ + {tag.name} +
+ {/each} +
+ {/if} +
+ + diff --git a/apps/mukke/apps/web/package.json b/apps/mukke/apps/web/package.json index 275912f41..1ae67a09b 100644 --- a/apps/mukke/apps/web/package.json +++ b/apps/mukke/apps/web/package.json @@ -50,6 +50,7 @@ "@manacore/shared-profile-ui": "workspace:*", "@manacore/shared-splitscreen": "workspace:^", "@manacore/shared-stores": "workspace:*", + "@manacore/shared-tags": "workspace:*", "@manacore/shared-tailwind": "workspace:*", "@manacore/shared-theme": "workspace:*", "@manacore/shared-theme-ui": "workspace:*", diff --git a/apps/mukke/apps/web/src/lib/stores/tags.svelte.ts b/apps/mukke/apps/web/src/lib/stores/tags.svelte.ts new file mode 100644 index 000000000..0e08b03ef --- /dev/null +++ b/apps/mukke/apps/web/src/lib/stores/tags.svelte.ts @@ -0,0 +1,20 @@ +/** + * Tag Store - Uses shared createTagStore backed by central mana-core-auth + */ +import { browser } from '$app/environment'; +import { createTagStore } from '@manacore/shared-stores'; +import { authStore } from '$lib/stores/auth.svelte'; + +function getAuthUrl(): string { + if (browser && typeof window !== 'undefined') { + const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string }) + .__PUBLIC_MANA_CORE_AUTH_URL__; + return injectedUrl || 'http://localhost:3001'; + } + return 'http://localhost:3001'; +} + +export const tagStore = createTagStore({ + authUrl: getAuthUrl(), + getToken: () => authStore.getValidToken(), +}); diff --git a/apps/mukke/apps/web/src/routes/(app)/+layout.svelte b/apps/mukke/apps/web/src/routes/(app)/+layout.svelte index 86ffc156d..981fb24b4 100644 --- a/apps/mukke/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/mukke/apps/web/src/routes/(app)/+layout.svelte @@ -1,7 +1,7 @@ @@ -211,6 +226,22 @@ ariaLabel="Main navigation" /> + + {#if isTagStripVisible} + ({ + id: t.id, + name: t.name, + color: t.color || '#3b82f6', + }))} + selectedIds={[]} + onToggle={() => {}} + onClear={() => {}} + managementHref="/tags" + loading={tagStore.loading} + /> + {/if} + + import { tagStore } from '$lib/stores/tags.svelte'; + import { onMount } from 'svelte'; + + onMount(() => { + if (tagStore.tags.length === 0) { + tagStore.fetchTags(); + } + }); + + + + Tags | Mukke + + +
+

Tags verwalten

+

+ Tags sind app-übergreifend — Änderungen gelten in allen ManaCore-Apps. +

+ + {#if tagStore.loading} +

Lädt...

+ {:else if tagStore.tags.length === 0} +

Keine Tags vorhanden.

+ {:else} +
+ {#each tagStore.tags as tag} +
+ + {tag.name} +
+ {/each} +
+ {/if} +
+ + diff --git a/apps/photos/apps/web/src/lib/stores/tags.svelte.ts b/apps/photos/apps/web/src/lib/stores/tags.svelte.ts index b8d045e75..90e78fa6e 100644 --- a/apps/photos/apps/web/src/lib/stores/tags.svelte.ts +++ b/apps/photos/apps/web/src/lib/stores/tags.svelte.ts @@ -1,48 +1,50 @@ /** - * Tags Store - Manages tag state using Svelte 5 runes + * Tags Store - Uses shared Tag Store backed by central mana-core-auth + * + * Wraps createTagStore for backward compatibility with existing tagStore interface. + * Also preserves photo-specific tag operations (getPhotoTags, addTagToPhoto, etc.) + * which go through the Photos backend, not mana-core-auth. */ +import { browser } from '$app/environment'; +import { createTagStore, type TagStore } from '@manacore/shared-stores'; +import { authStore } from '$lib/stores/auth.svelte'; import { api } from '$lib/api/client'; import type { Tag } from '@photos/shared'; -// State -let tags = $state([]); -let loading = $state(false); -let error = $state(null); +function getAuthUrl(): string { + if (browser && typeof window !== 'undefined') { + const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string }) + .__PUBLIC_MANA_CORE_AUTH_URL__; + return injectedUrl || 'http://localhost:3001'; + } + return 'http://localhost:3001'; +} +// Create the shared tag store +const sharedTagStore: TagStore = createTagStore({ + authUrl: getAuthUrl(), + getToken: () => authStore.getValidToken(), +}); + +// Backward-compatible tagStore wrapper export const tagStore = { - // Getters + // Getters (delegate to shared store) get tags() { - return tags; + return sharedTagStore.tags; }, get loading() { - return loading; + return sharedTagStore.loading; }, get error() { - return error; + return sharedTagStore.error; }, /** * Load all tags */ async loadTags() { - loading = true; - error = null; - - try { - const result = await api.get('/tags'); - if (result.error) { - error = result.error.message; - return; - } - if (result.data) { - tags = result.data; - } - } catch (e) { - error = e instanceof Error ? e.message : 'Failed to load tags'; - } finally { - loading = false; - } + return sharedTagStore.fetchTags(); }, /** @@ -50,18 +52,9 @@ export const tagStore = { */ async createTag(data: { name: string; color?: string }) { try { - const result = await api.post('/tags', data); - if (result.error) { - error = result.error.message; - return null; - } - if (result.data) { - tags = [...tags, result.data]; - return result.data; - } - return null; - } catch (e) { - error = e instanceof Error ? e.message : 'Failed to create tag'; + const tag = await sharedTagStore.createTag(data); + return tag; + } catch { return null; } }, @@ -71,18 +64,9 @@ export const tagStore = { */ async updateTag(id: string, data: { name?: string; color?: string }) { try { - const result = await api.patch(`/tags/${id}`, data); - if (result.error) { - error = result.error.message; - return null; - } - if (result.data) { - tags = tags.map((t) => (t.id === id ? result.data! : t)); - return result.data; - } - return null; - } catch (e) { - error = e instanceof Error ? e.message : 'Failed to update tag'; + const tag = await sharedTagStore.updateTag(id, data); + return tag; + } catch { return null; } }, @@ -92,21 +76,15 @@ export const tagStore = { */ async deleteTag(id: string) { try { - const result = await api.delete(`/tags/${id}`); - if (result.error) { - error = result.error.message; - return false; - } - tags = tags.filter((t) => t.id !== id); + await sharedTagStore.deleteTag(id); return true; - } catch (e) { - error = e instanceof Error ? e.message : 'Failed to delete tag'; + } catch { return false; } }, /** - * Get tags for a photo + * Get tags for a photo (from Photos backend) */ async getPhotoTags(mediaId: string): Promise { try { @@ -122,7 +100,7 @@ export const tagStore = { }, /** - * Add tag to photo + * Add tag to photo (from Photos backend) */ async addTagToPhoto(mediaId: string, tagId: string) { try { @@ -135,7 +113,7 @@ export const tagStore = { }, /** - * Remove tag from photo + * Remove tag from photo (from Photos backend) */ async removeTagFromPhoto(mediaId: string, tagId: string) { try { @@ -148,7 +126,7 @@ export const tagStore = { }, /** - * Set all tags for a photo + * Set all tags for a photo (from Photos backend) */ async setPhotoTags(mediaId: string, tagIds: string[]) { try { @@ -164,8 +142,6 @@ export const tagStore = { * Reset store */ reset() { - tags = []; - loading = false; - error = null; + sharedTagStore.clear(); }, }; diff --git a/apps/photos/apps/web/src/routes/(app)/+layout.svelte b/apps/photos/apps/web/src/routes/(app)/+layout.svelte index 805f14381..a80d91438 100644 --- a/apps/photos/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/photos/apps/web/src/routes/(app)/+layout.svelte @@ -2,7 +2,7 @@ import { goto } from '$app/navigation'; import { page } from '$app/stores'; import { _, locale } from 'svelte-i18n'; - import { PillNavigation, QuickInputBar } from '@manacore/shared-ui'; + import { PillNavigation, QuickInputBar, TagStrip } from '@manacore/shared-ui'; import type { PillNavItem, PillDropdownItem, QuickInputItem } from '@manacore/shared-ui'; import { theme } from '$lib/stores/theme'; import { authStore } from '$lib/stores/auth.svelte'; @@ -18,12 +18,39 @@ let isDark = $derived(theme.isDark); let userEmail = $derived(authStore.user?.email || 'Menu'); + // TagStrip state + let isTagStripVisible = $state(true); + let selectedTagIds = $state([]); + + function handleTagStripToggle() { + isTagStripVisible = !isTagStripVisible; + } + + function handleTagToggle(tagId: string) { + if (selectedTagIds.includes(tagId)) { + selectedTagIds = selectedTagIds.filter((id) => id !== tagId); + } else { + selectedTagIds = [...selectedTagIds, tagId]; + } + } + + function handleTagClear() { + selectedTagIds = []; + } + // Navigation items const navItems: PillNavItem[] = [ { href: '/', label: $_('nav.gallery'), icon: 'image' }, { href: '/albums', label: $_('nav.albums'), icon: 'folder' }, { href: '/favorites', label: $_('nav.favorites'), icon: 'heart' }, { href: '/upload', label: $_('nav.upload'), icon: 'upload' }, + { + href: '/', + label: 'Tags', + icon: 'tag', + onClick: handleTagStripToggle, + active: isTagStripVisible, + }, { href: '/settings', label: $_('nav.settings'), icon: 'settings' }, ]; @@ -111,6 +138,21 @@ profileHref="/profile" /> + + {#if isTagStripVisible} + ({ + id: t.id, + name: t.name, + color: t.color || '#6b7280', + }))} + selectedIds={selectedTagIds} + onToggle={handleTagToggle} + onClear={handleTagClear} + managementHref="/tags" + /> + {/if} + authStore.getValidToken(), +}); + +// Backward-compatible writable stores for existing consumers +// These are kept as writables so $tags and $selectedTags syntax still works export const tags = writable([]); export const selectedTags = writable([]); export const isLoadingTags = writable(false); + +/** + * Fetch tags from the shared store and sync to the writable store. + * Call this on mount instead of using the old getAllTags() API function. + */ +export async function fetchAndSyncTags(): Promise { + isLoadingTags.set(true); + try { + await sharedTagStore.fetchTags(); + tags.set(sharedTagStore.tags); + } catch (e) { + console.error('Failed to fetch tags:', e); + } finally { + isLoadingTags.set(false); + } +} + +/** + * Direct access to the shared tag store for components that want the runes-based API. + */ +export const tagStore = sharedTagStore; diff --git a/apps/picture/apps/web/src/routes/app/+layout.svelte b/apps/picture/apps/web/src/routes/app/+layout.svelte index 504ccfca0..fdf166873 100644 --- a/apps/picture/apps/web/src/routes/app/+layout.svelte +++ b/apps/picture/apps/web/src/routes/app/+layout.svelte @@ -4,7 +4,7 @@ import { goto } from '$app/navigation'; import { page } from '$app/stores'; import { locale } from 'svelte-i18n'; - import { PillNavigation } from '@manacore/shared-ui'; + import { PillNavigation, TagStrip } from '@manacore/shared-ui'; import type { PillNavItem, PillNavElement, PillDropdownItem } from '@manacore/shared-ui'; import { THEME_DEFINITIONS, @@ -19,6 +19,7 @@ import KeyboardShortcutsModal from '$lib/components/ui/KeyboardShortcutsModal.svelte'; import { theme } from '$lib/stores/theme'; import { isUIVisible, toggleUI, showKeyboardShortcuts } from '$lib/stores/ui'; + import { tagStore } from '$lib/stores/tags'; import { pictureOnboarding } from '$lib/stores/app-onboarding.svelte'; import { MiniOnboardingModal } from '@manacore/shared-app-onboarding'; import { SessionExpiredBanner, AuthGate } from '@manacore/shared-auth-ui'; @@ -42,6 +43,26 @@ } }); + // TagStrip state + let isTagStripVisible = $state(true); + let selectedTagIds = $state([]); + + function handleTagStripToggle() { + isTagStripVisible = !isTagStripVisible; + } + + function handleTagToggle(tagId: string) { + if (selectedTagIds.includes(tagId)) { + selectedTagIds = selectedTagIds.filter((id) => id !== tagId); + } else { + selectedTagIds = [...selectedTagIds, tagId]; + } + } + + function handleTagClear() { + selectedTagIds = []; + } + function handleCollapsedChange(collapsed: boolean) { isCollapsed = collapsed; if (browser) localStorage.setItem('picture-nav-collapsed', String(collapsed)); @@ -61,7 +82,7 @@ } async function handleAuthReady() { - await userSettings.load(); + await Promise.all([userSettings.load(), tagStore.fetchTags()]); // Redirect to start page if on /app and a custom start page is set const currentPath = window.location.pathname; @@ -85,7 +106,13 @@ { href: '/app/explore', label: 'Entdecken', icon: 'search' }, { href: '/app/generate', label: 'Generieren', icon: 'fire' }, { href: '/app/upload', label: 'Upload', icon: 'upload' }, - { href: '/app/tags', label: 'Tags', icon: 'tag' }, + { + href: '/', + label: 'Tags', + icon: 'tag', + onClick: handleTagStripToggle, + active: isTagStripVisible, + }, { href: '/app/archive', label: 'Archiv', icon: 'archive' }, ]; @@ -262,6 +289,20 @@ feedbackHref="/app/feedback" allAppsHref="/app/apps" /> + + {#if isTagStripVisible} + ({ + id: t.id, + name: t.name, + color: t.color || '#6b7280', + }))} + selectedIds={selectedTagIds} + onToggle={handleTagToggle} + onClear={handleTagClear} + managementHref="/app/tags" + /> + {/if} {/if} diff --git a/apps/planta/apps/web/package.json b/apps/planta/apps/web/package.json index 959213899..7da200d73 100644 --- a/apps/planta/apps/web/package.json +++ b/apps/planta/apps/web/package.json @@ -45,6 +45,8 @@ "@manacore/shared-tailwind": "workspace:*", "@manacore/shared-theme": "workspace:*", "@manacore/shared-theme-ui": "workspace:*", + "@manacore/shared-stores": "workspace:*", + "@manacore/shared-tags": "workspace:*", "@manacore/shared-ui": "workspace:*", "@manacore/shared-utils": "workspace:*", "@manacore/shared-app-onboarding": "workspace:*", diff --git a/apps/planta/apps/web/src/lib/stores/tags.svelte.ts b/apps/planta/apps/web/src/lib/stores/tags.svelte.ts new file mode 100644 index 000000000..0e08b03ef --- /dev/null +++ b/apps/planta/apps/web/src/lib/stores/tags.svelte.ts @@ -0,0 +1,20 @@ +/** + * Tag Store - Uses shared createTagStore backed by central mana-core-auth + */ +import { browser } from '$app/environment'; +import { createTagStore } from '@manacore/shared-stores'; +import { authStore } from '$lib/stores/auth.svelte'; + +function getAuthUrl(): string { + if (browser && typeof window !== 'undefined') { + const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string }) + .__PUBLIC_MANA_CORE_AUTH_URL__; + return injectedUrl || 'http://localhost:3001'; + } + return 'http://localhost:3001'; +} + +export const tagStore = createTagStore({ + authUrl: getAuthUrl(), + getToken: () => authStore.getValidToken(), +}); diff --git a/apps/planta/apps/web/src/routes/(app)/+layout.svelte b/apps/planta/apps/web/src/routes/(app)/+layout.svelte index 6fe119e8a..a15c8ed63 100644 --- a/apps/planta/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/planta/apps/web/src/routes/(app)/+layout.svelte @@ -1,8 +1,9 @@ - + tagStore.fetchTags()}>
+ + {#if isTagStripVisible} + ({ + id: t.id, + name: t.name, + color: t.color || '#3b82f6', + }))} + selectedIds={[]} + onToggle={() => {}} + onClear={() => {}} + managementHref="/tags" + loading={tagStore.loading} + /> + {/if} + + import { tagStore } from '$lib/stores/tags.svelte'; + import { onMount } from 'svelte'; + + onMount(() => { + if (tagStore.tags.length === 0) { + tagStore.fetchTags(); + } + }); + + + + Tags | Planta + + +
+

Tags verwalten

+

+ Tags sind app-übergreifend — Änderungen gelten in allen ManaCore-Apps. +

+ + {#if tagStore.loading} +

Lädt...

+ {:else if tagStore.tags.length === 0} +

Keine Tags vorhanden.

+ {:else} +
+ {#each tagStore.tags as tag} +
+ + {tag.name} +
+ {/each} +
+ {/if} +
+ + diff --git a/apps/presi/apps/web/package.json b/apps/presi/apps/web/package.json index 265398150..b774ead7f 100644 --- a/apps/presi/apps/web/package.json +++ b/apps/presi/apps/web/package.json @@ -44,6 +44,7 @@ "@manacore/shared-icons": "workspace:*", "@manacore/shared-profile-ui": "workspace:*", "@manacore/shared-stores": "workspace:*", + "@manacore/shared-tags": "workspace:*", "@manacore/shared-subscription-ui": "workspace:*", "@manacore/shared-tailwind": "workspace:*", "@manacore/shared-theme": "workspace:*", diff --git a/apps/presi/apps/web/src/lib/stores/tags.svelte.ts b/apps/presi/apps/web/src/lib/stores/tags.svelte.ts new file mode 100644 index 000000000..f810492dd --- /dev/null +++ b/apps/presi/apps/web/src/lib/stores/tags.svelte.ts @@ -0,0 +1,20 @@ +/** + * Tag Store - Uses shared createTagStore backed by central mana-core-auth + */ +import { browser } from '$app/environment'; +import { createTagStore } from '@manacore/shared-stores'; +import { auth } from '$lib/stores/auth.svelte'; + +function getAuthUrl(): string { + if (browser && typeof window !== 'undefined') { + const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string }) + .__PUBLIC_MANA_CORE_AUTH_URL__; + return injectedUrl || 'http://localhost:3001'; + } + return 'http://localhost:3001'; +} + +export const tagStore = createTagStore({ + authUrl: getAuthUrl(), + getToken: () => auth.getValidToken(), +}); diff --git a/apps/presi/apps/web/src/routes/(app)/+layout.svelte b/apps/presi/apps/web/src/routes/(app)/+layout.svelte index 0d48f4700..1a6dc3cfb 100644 --- a/apps/presi/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/presi/apps/web/src/routes/(app)/+layout.svelte @@ -3,8 +3,9 @@ import { page } from '$app/stores'; import { onMount } from 'svelte'; import { locale } from 'svelte-i18n'; - import { PillNavigation, QuickInputBar } from '@manacore/shared-ui'; + import { PillNavigation, QuickInputBar, TagStrip } from '@manacore/shared-ui'; import type { PillNavItem, PillDropdownItem, QuickInputItem } from '@manacore/shared-ui'; + import { tagStore } from '$lib/stores/tags.svelte'; import { auth } from '$lib/stores/auth.svelte'; import { userSettings } from '$lib/stores/user-settings.svelte'; import { theme } from '$lib/stores/theme'; @@ -58,8 +59,23 @@ // User email for user dropdown let userEmail = $derived(auth.user?.email); + // TagStrip visibility + let isTagStripVisible = $state(false); + function handleTagStripToggle() { + isTagStripVisible = !isTagStripVisible; + } + // Navigation items for Presi - const navItems: PillNavItem[] = [{ href: '/', label: 'Decks', icon: 'document' }]; + const navItems: PillNavItem[] = [ + { href: '/', label: 'Decks', icon: 'document' }, + { + href: '/', + label: 'Tags', + icon: 'tag', + onClick: handleTagStripToggle, + active: isTagStripVisible, + }, + ]; // Routes where nav should be hidden (present mode, shared view) const hideNavRoutes = ['/present/', '/shared/']; @@ -128,8 +144,9 @@ return; } - // Load user settings + // Load user settings and tags await userSettings.load(); + await tagStore.fetchTags(); // Redirect to start page if on root and a custom start page is set const currentPath = window.location.pathname; @@ -189,6 +206,22 @@ allAppsHref="/apps" /> + + {#if isTagStripVisible} + ({ + id: t.id, + name: t.name, + color: t.color || '#3b82f6', + }))} + selectedIds={[]} + onToggle={() => {}} + onClear={() => {}} + managementHref="/tags" + loading={tagStore.loading} + /> + {/if} + + import { tagStore } from '$lib/stores/tags.svelte'; + import { onMount } from 'svelte'; + + onMount(() => { + if (tagStore.tags.length === 0) { + tagStore.fetchTags(); + } + }); + + + + Tags | Presi + + +
+

Tags verwalten

+

+ Tags sind app-übergreifend — Änderungen gelten in allen ManaCore-Apps. +

+ + {#if tagStore.loading} +

Lädt...

+ {:else if tagStore.tags.length === 0} +

Keine Tags vorhanden.

+ {:else} +
+ {#each tagStore.tags as tag} +
+ + {tag.name} +
+ {/each} +
+ {/if} +
+ + diff --git a/apps/questions/apps/web/package.json b/apps/questions/apps/web/package.json index a90d34b00..f1277e03c 100644 --- a/apps/questions/apps/web/package.json +++ b/apps/questions/apps/web/package.json @@ -50,6 +50,8 @@ "@manacore/shared-tailwind": "workspace:*", "@manacore/shared-theme": "workspace:*", "@manacore/shared-theme-ui": "workspace:*", + "@manacore/shared-stores": "workspace:*", + "@manacore/shared-tags": "workspace:*", "@manacore/shared-ui": "workspace:*", "date-fns": "^4.1.0", "svelte-i18n": "^4.0.1" diff --git a/apps/questions/apps/web/src/lib/stores/tags.svelte.ts b/apps/questions/apps/web/src/lib/stores/tags.svelte.ts new file mode 100644 index 000000000..4287c2c19 --- /dev/null +++ b/apps/questions/apps/web/src/lib/stores/tags.svelte.ts @@ -0,0 +1,20 @@ +/** + * Tag Store - Uses shared createTagStore backed by central mana-core-auth + */ +import { browser } from '$app/environment'; +import { createTagStore } from '@manacore/shared-stores'; +import { authStore } from '$lib/stores'; + +function getAuthUrl(): string { + if (browser && typeof window !== 'undefined') { + const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string }) + .__PUBLIC_MANA_CORE_AUTH_URL__; + return injectedUrl || 'http://localhost:3001'; + } + return 'http://localhost:3001'; +} + +export const tagStore = createTagStore({ + authUrl: getAuthUrl(), + getToken: () => authStore.getValidToken(), +}); diff --git a/apps/questions/apps/web/src/routes/(app)/+layout.svelte b/apps/questions/apps/web/src/routes/(app)/+layout.svelte index 255b74f05..55034b9c7 100644 --- a/apps/questions/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/questions/apps/web/src/routes/(app)/+layout.svelte @@ -8,7 +8,7 @@ import { apiClient } from '$lib/api/client'; import { questionsApi } from '$lib/api/questions'; import { theme } from '$lib/stores/theme'; - import { PillNavigation, QuickInputBar } from '@manacore/shared-ui'; + import { PillNavigation, QuickInputBar, TagStrip } from '@manacore/shared-ui'; import type { PillNavItem, PillDropdownItem, @@ -16,6 +16,7 @@ CreatePreview, } from '@manacore/shared-ui'; import { getPillAppItems } from '@manacore/shared-branding'; + import { tagStore } from '$lib/stores/tags.svelte'; let { children } = $props(); @@ -55,6 +56,7 @@ // Load initial data await collectionsStore.load(); await questionsStore.load(); + await tagStore.fetchTags(); // Initialize mobile state updateMobileState(); @@ -174,17 +176,46 @@ } } + // TagStrip visibility + let isTagStripVisible = $state(false); + function handleTagStripToggle() { + isTagStripVisible = !isTagStripVisible; + } + // Navigation items let navItems = $derived([ { href: '/', label: 'Questions', icon: 'help-circle' }, { href: '/collections', label: 'Collections', icon: 'folder' }, { href: '/settings', label: 'Settings', icon: 'settings' }, + { + href: '/', + label: 'Tags', + icon: 'tag', + onClick: handleTagStripToggle, + active: isTagStripVisible, + }, ]);
+ + {#if isTagStripVisible} + ({ + id: t.id, + name: t.name, + color: t.color || '#3b82f6', + }))} + selectedIds={[]} + onToggle={() => {}} + onClear={() => {}} + managementHref="/tags" + loading={tagStore.loading} + /> + {/if} + + import { tagStore } from '$lib/stores/tags.svelte'; + import { onMount } from 'svelte'; + + onMount(() => { + if (tagStore.tags.length === 0) { + tagStore.fetchTags(); + } + }); + + + + Tags | Questions + + +
+

Tags verwalten

+

+ Tags sind app-übergreifend — Änderungen gelten in allen ManaCore-Apps. +

+ + {#if tagStore.loading} +

Lädt...

+ {:else if tagStore.tags.length === 0} +

Keine Tags vorhanden.

+ {:else} +
+ {#each tagStore.tags as tag} +
+ + {tag.name} +
+ {/each} +
+ {/if} +
+ + diff --git a/apps/storage/apps/web/package.json b/apps/storage/apps/web/package.json index 56b7e110f..4db57f5ce 100644 --- a/apps/storage/apps/web/package.json +++ b/apps/storage/apps/web/package.json @@ -52,6 +52,7 @@ "@manacore/shared-profile-ui": "workspace:*", "@manacore/shared-stores": "workspace:*", "@manacore/shared-subscription-ui": "workspace:*", + "@manacore/shared-tags": "workspace:*", "@manacore/shared-tailwind": "workspace:*", "@manacore/shared-theme": "workspace:*", "@manacore/shared-theme-ui": "workspace:*", diff --git a/apps/storage/apps/web/src/lib/stores/tags.svelte.ts b/apps/storage/apps/web/src/lib/stores/tags.svelte.ts new file mode 100644 index 000000000..583fd37cf --- /dev/null +++ b/apps/storage/apps/web/src/lib/stores/tags.svelte.ts @@ -0,0 +1,62 @@ +/** + * Tags Store - Uses shared Tag Store backed by central mana-core-auth + */ + +import { browser } from '$app/environment'; +import { createTagStore, type TagStore } from '@manacore/shared-stores'; +import { authStore } from '$lib/stores/auth.svelte'; +import type { Tag } from '@manacore/shared-tags'; + +// Re-export Tag for convenience +export type { Tag }; + +function getAuthUrl(): string { + if (browser && typeof window !== 'undefined') { + const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string }) + .__PUBLIC_MANA_CORE_AUTH_URL__; + return injectedUrl || 'http://localhost:3001'; + } + return 'http://localhost:3001'; +} + +// Create the shared tag store +const tagStore: TagStore = createTagStore({ + authUrl: getAuthUrl(), + getToken: () => authStore.getValidToken(), +}); + +// Export the store with a backward-compatible wrapper +export const tagsStore = { + get tags() { + return tagStore.tags; + }, + get loading() { + return tagStore.loading; + }, + get error() { + return tagStore.error; + }, + + async fetchTags() { + return tagStore.fetchTags(); + }, + getById(id: string) { + return tagStore.getById(id); + }, + getColor(tagId: string) { + return tagStore.getColor(tagId); + }, + + async createTag(data: { name: string; color?: string }) { + return tagStore.createTag(data); + }, + async updateTag(id: string, data: { name?: string; color?: string }) { + return tagStore.updateTag(id, data); + }, + async deleteTag(id: string) { + return tagStore.deleteTag(id); + }, + clear() { + tagStore.clear(); + }, +}; diff --git a/apps/storage/apps/web/src/routes/+layout.svelte b/apps/storage/apps/web/src/routes/+layout.svelte index 2f8515af9..b89104527 100644 --- a/apps/storage/apps/web/src/routes/+layout.svelte +++ b/apps/storage/apps/web/src/routes/+layout.svelte @@ -3,11 +3,12 @@ import { page } from '$app/stores'; import { onMount } from 'svelte'; import { locale } from 'svelte-i18n'; - import { PillNavigation, setupGlobalErrorHandler } from '@manacore/shared-ui'; + import { PillNavigation, setupGlobalErrorHandler, TagStrip } from '@manacore/shared-ui'; import type { PillNavItem, PillDropdownItem } from '@manacore/shared-ui'; import { theme } from '$lib/stores/theme.svelte'; import { authStore } from '$lib/stores/auth.svelte'; import { userSettings } from '$lib/stores/user-settings.svelte'; + import { tagsStore } from '$lib/stores/tags.svelte'; import { THEME_DEFINITIONS } from '@manacore/shared-theme'; import { isNavCollapsed as collapsedStore } from '$lib/stores/navigation'; import { getLanguageDropdownItems, getCurrentLanguageLabel } from '@manacore/shared-i18n'; @@ -65,12 +66,39 @@ // User email for user dropdown let userEmail = $derived(authStore.user?.email || 'Menü'); + // TagStrip state + let isTagStripVisible = $state(true); + let selectedTagIds = $state([]); + + function handleTagStripToggle() { + isTagStripVisible = !isTagStripVisible; + } + + function handleTagToggle(tagId: string) { + if (selectedTagIds.includes(tagId)) { + selectedTagIds = selectedTagIds.filter((id) => id !== tagId); + } else { + selectedTagIds = [...selectedTagIds, tagId]; + } + } + + function handleTagClear() { + selectedTagIds = []; + } + // Navigation items for Storage const navItems: PillNavItem[] = [ { href: '/files', label: 'Dateien', icon: 'folder' }, { href: '/shared', label: 'Geteilt', icon: 'share' }, { href: '/favorites', label: 'Favoriten', icon: 'heart' }, { href: '/trash', label: 'Papierkorb', icon: 'trash' }, + { + href: '/', + label: 'Tags', + icon: 'tag', + onClick: handleTagStripToggle, + active: isTagStripVisible, + }, { href: '/search', label: 'Suche', icon: 'search' }, ]; @@ -135,8 +163,8 @@ // Initialize theme theme.initialize(); - // Load user settings - await userSettings.load(); + // Load user settings and tags + await Promise.all([userSettings.load(), tagsStore.fetchTags()]); // Initialize collapsed state from localStorage const savedCollapsed = localStorage.getItem('storage-nav-collapsed'); @@ -197,6 +225,21 @@ allAppsHref="/apps" /> + + {#if isTagStripVisible} + ({ + id: t.id, + name: t.name, + color: t.color || '#6b7280', + }))} + selectedIds={selectedTagIds} + onToggle={handleTagToggle} + onClear={handleTagClear} + managementHref="/tags" + /> + {/if} +
{@render children()} diff --git a/apps/storage/apps/web/src/routes/tags/+page.svelte b/apps/storage/apps/web/src/routes/tags/+page.svelte new file mode 100644 index 000000000..2905c68c5 --- /dev/null +++ b/apps/storage/apps/web/src/routes/tags/+page.svelte @@ -0,0 +1,307 @@ + + + + Tags verwalten | Storage + + +
+
+ + {#snippet actions()} + + {/snippet} + + + + {#if tagsStore.loading} +
+
+
+ {:else if tagsStore.tags.length === 0} +
+ +

+ Keine Tags vorhanden +

+

+ Erstelle deinen ersten Tag, um deine Dateien zu organisieren +

+
+ {:else} +
+ {#each tagsStore.tags as tag (tag.id)} +
+
+
+ {#if tag.color} +
+ {/if} +
+

{tag.name}

+
+
+ +
+ + +
+
+
+ {/each} +
+ {/if} +
+
+ + +{#if showCreateModal} +
(showCreateModal = false)} + role="presentation" + > +
e.stopPropagation()} + role="dialog" + > +

Neuer Tag

+ +
+
+ + +
+ +
+ +
+ {#each predefinedColors as color} + + {/each} +
+
+
+ +
+ + +
+
+
+{/if} + + +{#if showEditModal && editingTag} +
(showEditModal = false)} + role="presentation" + > +
e.stopPropagation()} + role="dialog" + > +

Tag bearbeiten

+ +
+
+ + +
+ +
+ +
+ {#each predefinedColors as color} + + {/each} +
+
+
+ +
+ + +
+
+
+{/if} diff --git a/apps/zitare/apps/web/package.json b/apps/zitare/apps/web/package.json index 8667c5adc..9fbac3786 100644 --- a/apps/zitare/apps/web/package.json +++ b/apps/zitare/apps/web/package.json @@ -45,6 +45,7 @@ "@manacore/shared-icons": "workspace:*", "@manacore/shared-profile-ui": "workspace:*", "@manacore/shared-stores": "workspace:*", + "@manacore/shared-tags": "workspace:*", "@manacore/shared-subscription-ui": "workspace:*", "@manacore/shared-tailwind": "workspace:*", "@manacore/shared-theme": "workspace:*", diff --git a/apps/zitare/apps/web/src/lib/stores/tags.svelte.ts b/apps/zitare/apps/web/src/lib/stores/tags.svelte.ts new file mode 100644 index 000000000..0e08b03ef --- /dev/null +++ b/apps/zitare/apps/web/src/lib/stores/tags.svelte.ts @@ -0,0 +1,20 @@ +/** + * Tag Store - Uses shared createTagStore backed by central mana-core-auth + */ +import { browser } from '$app/environment'; +import { createTagStore } from '@manacore/shared-stores'; +import { authStore } from '$lib/stores/auth.svelte'; + +function getAuthUrl(): string { + if (browser && typeof window !== 'undefined') { + const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string }) + .__PUBLIC_MANA_CORE_AUTH_URL__; + return injectedUrl || 'http://localhost:3001'; + } + return 'http://localhost:3001'; +} + +export const tagStore = createTagStore({ + authUrl: getAuthUrl(), + getToken: () => authStore.getValidToken(), +}); diff --git a/apps/zitare/apps/web/src/routes/(app)/+layout.svelte b/apps/zitare/apps/web/src/routes/(app)/+layout.svelte index 8473d9066..64f2b533f 100644 --- a/apps/zitare/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/zitare/apps/web/src/routes/(app)/+layout.svelte @@ -2,8 +2,14 @@ import { goto } from '$app/navigation'; import { page } from '$app/stores'; import { locale, _ } from 'svelte-i18n'; - import { PillNavigation, QuickInputBar, ImmersiveModeToggle } from '@manacore/shared-ui'; + import { + PillNavigation, + QuickInputBar, + ImmersiveModeToggle, + TagStrip, + } from '@manacore/shared-ui'; import type { PillNavItem, PillDropdownItem, QuickInputItem } from '@manacore/shared-ui'; + import { tagStore } from '$lib/stores/tags.svelte'; // Extend QuickInputItem for zitare-specific search results with href navigation interface ZitareSearchItem extends QuickInputItem { @@ -88,12 +94,25 @@ // User email for user dropdown let userEmail = $derived(authStore.user?.email || $_('nav.menu')); + // TagStrip visibility + let isTagStripVisible = $state(false); + function handleTagStripToggle() { + isTagStripVisible = !isTagStripVisible; + } + // Base navigation items for Zitare let baseNavItems = $derived([ { href: '/', label: $_('nav.today'), icon: 'sun' }, { href: '/categories', label: $_('nav.categories'), icon: 'grid' }, { href: '/favorites', label: $_('nav.favorites'), icon: 'heart' }, { href: '/lists', label: $_('nav.lists'), icon: 'list' }, + { + href: '/', + label: 'Tags', + icon: 'tag', + onClick: handleTagStripToggle, + active: isTagStripVisible, + }, ]); // Filter hidden nav items @@ -216,6 +235,7 @@ userSettings.load(); favoritesStore.load(); listsStore.loadLists(); + tagStore.fetchTags(); } } @@ -260,6 +280,22 @@ /> {/if} + + {#if isTagStripVisible} + ({ + id: t.id, + name: t.name, + color: t.color || '#3b82f6', + }))} + selectedIds={[]} + onToggle={() => {}} + onClear={() => {}} + managementHref="/tags" + loading={tagStore.loading} + /> + {/if} + + import { tagStore } from '$lib/stores/tags.svelte'; + import { onMount } from 'svelte'; + + onMount(() => { + if (tagStore.tags.length === 0) { + tagStore.fetchTags(); + } + }); + + + + Tags | Zitare + + +
+

Tags verwalten

+

+ Tags sind app-übergreifend — Änderungen gelten in allen ManaCore-Apps. +

+ + {#if tagStore.loading} +

Lädt...

+ {:else if tagStore.tags.length === 0} +

Keine Tags vorhanden.

+ {:else} +
+ {#each tagStore.tags as tag} +
+ + {tag.name} +
+ {/each} +
+ {/if} +
+ +