mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 19:01:08 +02:00
feat(shared-links): deep-link navigation to target app detail views
ManaLinkBadge now resolves the correct URL for the linked record's
detail view (e.g. /event/{id}, /contacts/{id}, /deck/{id}) instead
of just linking to the app's root page.
Uses an anchor tag by default for standard browser navigation, with
onclick prop override for custom behavior. Supports all 12 apps with
their specific routing patterns.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
fbe3c955e3
commit
4cee74e15d
3 changed files with 114 additions and 11 deletions
80
packages/shared-links/src/deep-links.ts
Normal file
80
packages/shared-links/src/deep-links.ts
Normal file
|
|
@ -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<string, Record<string, string>> = {
|
||||
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}`;
|
||||
}
|
||||
|
|
@ -24,3 +24,6 @@ export {
|
|||
|
||||
// Resolvers
|
||||
export { buildCachedData, isCacheStale } from './resolvers.js';
|
||||
|
||||
// Deep Links
|
||||
export { resolveDeepLink } from './deep-links.js';
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
</script>
|
||||
|
||||
<span class="manalink-badge" role="group">
|
||||
<button
|
||||
class="manalink-main"
|
||||
onclick={() => onclick?.(link)}
|
||||
title="{displayAppName}: {displayTitle}"
|
||||
>
|
||||
<span class="manalink-dot" style:background-color={displayColor}></span>
|
||||
<span class="manalink-title">{displayTitle}</span>
|
||||
{#if link.cachedTarget?.subtitle}
|
||||
<span class="manalink-subtitle">{link.cachedTarget.subtitle}</span>
|
||||
{/if}
|
||||
</button>
|
||||
{#if onclick}
|
||||
<button
|
||||
class="manalink-main"
|
||||
onclick={() => onclick(link)}
|
||||
title="{displayAppName}: {displayTitle}"
|
||||
>
|
||||
<span class="manalink-dot" style:background-color={displayColor}></span>
|
||||
<span class="manalink-title">{displayTitle}</span>
|
||||
{#if link.cachedTarget?.subtitle}
|
||||
<span class="manalink-subtitle">{link.cachedTarget.subtitle}</span>
|
||||
{/if}
|
||||
</button>
|
||||
{:else}
|
||||
<a
|
||||
class="manalink-main"
|
||||
href={deepLinkUrl}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
title="{displayAppName}: {displayTitle}"
|
||||
>
|
||||
<span class="manalink-dot" style:background-color={displayColor}></span>
|
||||
<span class="manalink-title">{displayTitle}</span>
|
||||
{#if link.cachedTarget?.subtitle}
|
||||
<span class="manalink-subtitle">{link.cachedTarget.subtitle}</span>
|
||||
{/if}
|
||||
</a>
|
||||
{/if}
|
||||
{#if onRemove}
|
||||
<button class="manalink-remove" onclick={() => onRemove?.(link.pairId)} title="Link entfernen">
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none">
|
||||
|
|
@ -70,6 +88,8 @@
|
|||
padding: 0.25rem 0.625rem;
|
||||
border: none;
|
||||
background: transparent;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue