diff --git a/packages/shared-links/src/deep-links.ts b/packages/shared-links/src/deep-links.ts new file mode 100644 index 000000000..d8241e031 --- /dev/null +++ b/packages/shared-links/src/deep-links.ts @@ -0,0 +1,80 @@ +/** + * ManaLink — Deep-Link Resolution + * + * Maps app + collection + recordId to a full URL that opens + * the record's detail view in the target app. + */ + +import { APP_URLS } from '@manacore/shared-branding'; +import type { AppIconId } from '@manacore/shared-branding'; + +/** Route pattern per app and collection. Use {id} as placeholder. */ +const DEEP_LINK_PATTERNS: Record> = { + todo: { + // Todo uses inline editing, no detail route — link to app root + tasks: '/', + projects: '/', + }, + calendar: { + events: '/event/{id}', + calendars: '/', + }, + contacts: { + contacts: '/contacts/{id}', + }, + chat: { + conversations: '/chat/{id}', + messages: '/chat/{id}', // Navigate to conversation, not individual message + }, + picture: { + images: '/app/board/{id}', + boards: '/app/board/{id}', + }, + storage: { + files: '/', + folders: '/files/{id}', + }, + presi: { + decks: '/deck/{id}', + slides: '/deck/{id}', // Navigate to the deck containing the slide + }, + context: { + documents: '/documents/{id}', + spaces: '/spaces/{id}', + }, + manadeck: { + decks: '/decks/{id}', + cards: '/decks/{id}', // Navigate to deck containing the card + }, + mukke: { + songs: '/', + playlists: '/playlists/{id}', + }, + clock: { + alarms: '/', + timers: '/', + }, + zitare: { + favorites: '/', + }, +}; + +/** + * Resolve a deep link URL for a cross-app record. + * + * @param app - App ID (e.g. 'todo', 'calendar') + * @param collection - Collection name (e.g. 'tasks', 'events') + * @param recordId - Record UUID + * @returns Full URL to the record's detail view, or app root as fallback + */ +export function resolveDeepLink(app: string, collection: string, recordId: string): string { + const isDev = typeof window !== 'undefined' && window.location.hostname === 'localhost'; + const urls = APP_URLS[app as AppIconId]; + if (!urls) return '#'; + + const baseUrl = isDev ? urls.dev : urls.prod; + const pattern = DEEP_LINK_PATTERNS[app]?.[collection] ?? '/'; + const path = pattern.replace('{id}', recordId); + + return `${baseUrl}${path}`; +} diff --git a/packages/shared-links/src/index.ts b/packages/shared-links/src/index.ts index 7cd6b12de..bf5a8dcb1 100644 --- a/packages/shared-links/src/index.ts +++ b/packages/shared-links/src/index.ts @@ -24,3 +24,6 @@ export { // Resolvers export { buildCachedData, isCacheStale } from './resolvers.js'; + +// Deep Links +export { resolveDeepLink } from './deep-links.js'; diff --git a/packages/shared-links/src/ui/ManaLinkBadge.svelte b/packages/shared-links/src/ui/ManaLinkBadge.svelte index 3661de05a..2de8744c8 100644 --- a/packages/shared-links/src/ui/ManaLinkBadge.svelte +++ b/packages/shared-links/src/ui/ManaLinkBadge.svelte @@ -2,6 +2,7 @@ import type { LocalManaLink } from '../types.js'; import { getManaApp } from '@manacore/shared-branding'; import type { AppIconId } from '@manacore/shared-branding'; + import { resolveDeepLink } from '../deep-links.js'; interface Props { link: LocalManaLink; @@ -17,20 +18,37 @@ ); let displayColor = $derived(link.cachedTarget?.color ?? targetApp?.color ?? '#6b7280'); let displayAppName = $derived(link.cachedTarget?.appName ?? targetApp?.name ?? link.targetApp); + let deepLinkUrl = $derived(resolveDeepLink(link.targetApp, link.targetCollection, link.targetId)); - + {#if onclick} + + {:else} + + + {displayTitle} + {#if link.cachedTarget?.subtitle} + {link.cachedTarget.subtitle} + {/if} + + {/if} {#if onRemove}