managarten/games/worldream/apps/web/src/lib/services/referenceResolver.ts
Till-JS bb0e0cf5cb 🚚 feat(context): integrate context app into monorepo
Restructure the context app (formerly basetext) to follow the monorepo
pattern with proper workspace configuration.

Changes:
- Move app files to apps/context/apps/mobile/
- Rename package to @context/mobile
- Update bundle ID to com.manacore.context
- Create pnpm-workspace.yaml for project workspace
- Add dev scripts to root package.json
- Update CLAUDE.md with project documentation

The app structure is prepared for future web/backend additions.

Note: Existing TypeScript errors in the original codebase are preserved.
These should be fixed in a follow-up PR.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 15:09:04 +01:00

145 lines
3.7 KiB
TypeScript

import type { ContentNode } from '$lib/types/content';
export interface ReferenceData {
slug: string;
title: string;
kind: 'character' | 'place' | 'object';
image_url?: string;
}
// Cache für geladene Referenzen (Client-side)
const referenceCache = new Map<string, ReferenceData>();
const pendingRequests = new Map<string, Promise<ReferenceData | null>>();
/**
* Lädt Referenzdaten für einen Slug
*/
async function fetchReference(slug: string): Promise<ReferenceData | null> {
console.log('🔍 Fetching reference for slug:', slug);
// Check cache first
if (referenceCache.has(slug)) {
console.log('✅ Found in cache:', slug);
return referenceCache.get(slug)!;
}
// Check if request is already pending
if (pendingRequests.has(slug)) {
console.log('⏳ Request already pending for:', slug);
return pendingRequests.get(slug)!;
}
// Create new request
console.log('🌐 Making API request for:', slug);
const request = fetch(`/api/nodes/${slug}`)
.then(async (response) => {
console.log(`📡 API response for ${slug}:`, response.status);
if (!response.ok) {
console.error(`❌ Failed to fetch ${slug}:`, response.status);
return null;
}
const node: ContentNode = await response.json();
console.log(`✨ Got node data for ${slug}:`, node.title);
const reference: ReferenceData = {
slug: node.slug,
title: node.title,
kind: node.kind as 'character' | 'place' | 'object',
image_url: node.image_url,
};
// Cache the result
referenceCache.set(slug, reference);
return reference;
})
.catch((error) => {
console.error(`❌ Error fetching ${slug}:`, error);
return null;
})
.finally(() => {
pendingRequests.delete(slug);
});
pendingRequests.set(slug, request);
return request;
}
/**
* Lädt mehrere Referenzen parallel
*/
export async function fetchReferences(slugs: string[]): Promise<Map<string, ReferenceData>> {
const uniqueSlugs = [...new Set(slugs)];
const results = await Promise.all(uniqueSlugs.map((slug) => fetchReference(slug)));
const referenceMap = new Map<string, ReferenceData>();
results.forEach((data, index) => {
if (data) {
referenceMap.set(uniqueSlugs[index], data);
}
});
return referenceMap;
}
/**
* Extrahiert alle @-Referenzen aus einem Text
*/
export function extractReferences(text: string): string[] {
const matches = text.matchAll(/@([\w-]+)/g);
return [...new Set([...matches].map((m) => m[1]))];
}
/**
* Ersetzt @-Referenzen mit formatierten Links
*/
export function replaceReferences(
text: string,
references: Map<string, ReferenceData>,
options: {
showAvatar?: boolean;
linkClass?: string;
} = {}
): string {
const { showAvatar = false, linkClass = 'character-link' } = options;
// Replace each @reference with formatted link
let result = text;
for (const [slug, data] of references) {
const pattern = new RegExp(`@${slug}(?![-\\w])`, 'g');
let replacement = `<a href="/${slug}" class="${linkClass}" data-kind="${data.kind}">`;
if (showAvatar && data.image_url) {
replacement += `<img src="${data.image_url}" alt="${data.title}" class="inline-avatar" />`;
}
replacement += `${data.title}</a>`;
result = result.replace(pattern, replacement);
}
// Handle any remaining @references that weren't found
result = result.replace(/@([\w-]+)/g, (match, slug) => {
// Fallback: Zeige formatierten Slug
const displayName = slug
.split('-')
.map((word: string) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
return `<a href="/${slug}" class="${linkClass} reference-unknown">${displayName}</a>`;
});
return result;
}
/**
* Clear cache (z.B. nach Updates)
*/
export function clearReferenceCache(slug?: string) {
if (slug) {
referenceCache.delete(slug);
} else {
referenceCache.clear();
}
}