import { db } from '$lib/db/schema'; import { upsertContext } from '$lib/db/repositories'; import type { AgendaContext } from '@ka-note/shared'; export interface MentionItem { context: AgendaContext; mentionName: string; icon: string; insertText: string; } export interface CreateOption { type: 'person' | 'project' | 'company'; label: string; query: string; } export function extractMentionName(ctx: AgendaContext): string { return ctx.name.replace(/^(Person|Project|Firma)\s+/, ''); } export function extractAbbreviation(ctx: AgendaContext): string | undefined { if (ctx.type !== 'person') return undefined; return (ctx.meta as { abbreviation?: string } | null)?.abbreviation || undefined; } export function quoteMention(prefix: string, name: string): string { return name.includes(' ') ? `${prefix}"${name}"` : `${prefix}${name}`; } function buildMentionItem(ctx: AgendaContext): MentionItem { const mentionName = extractMentionName(ctx); const icon = ctx.type === 'company' ? '\u{1F3E2}' : ctx.type === 'person' ? '\u{1F464}' : '\u{1F4C1}'; const insertText = ctx.type === 'company' ? quoteMention('@F:', mentionName) : ctx.type === 'person' ? quoteMention('@', mentionName) : quoteMention('@P:', mentionName); return { context: ctx, mentionName, icon, insertText }; } export async function fetchMentionItems(query: string, mode: '@' | '->' = '@'): Promise<{ items: MentionItem[]; createOptions: CreateOption[] }> { const all = await db.contexts .filter(c => !c.deletedAt && !c.archivedAt && (c.type === 'person' || c.type === 'project' || c.type === 'company')) .toArray(); const q = query.replace(/_/g, ' ').toLowerCase(); if (mode === '->') { // Arrow mode: only persons with an abbreviation, match against abbreviation OR full name const items = all .filter(c => c.type === 'person') .flatMap(c => { const abbr = extractAbbreviation(c); if (!abbr) return []; const fullName = extractMentionName(c).toLowerCase(); if (!abbr.toLowerCase().includes(q) && !fullName.includes(q)) return []; return [{ context: c, mentionName: `${abbr} – ${extractMentionName(c)}`, icon: '\u{1F464}', insertText: `-> ${abbr}`, } as MentionItem]; }) .slice(0, 8); return { items, createOptions: [] }; } const items = all .map(buildMentionItem) .filter(m => m.mentionName.toLowerCase().includes(q)) .slice(0, 8); const exactMatch = items.some(m => m.mentionName.toLowerCase() === q); const createOptions: CreateOption[] = []; if (query.length > 0 && !exactMatch) { const displayName = query.replace(/_/g, ' '); createOptions.push( { type: 'person', label: `+ Person "${displayName}" anlegen`, query }, { type: 'project', label: `+ Projekt "${displayName}" anlegen`, query }, { type: 'company', label: `+ Firma "${displayName}" anlegen`, query } ); } return { items, createOptions }; } export async function createMentionContext(opt: CreateOption): Promise { const name = opt.query.replace(/_/g, ' '); const slug = name.toLowerCase().replace(/\s+/g, '-'); const id = opt.type === 'company' ? `f-${slug}` : opt.type === 'person' ? `u-${slug}` : `p-${slug}`; const contextName = opt.type === 'company' ? `Firma ${name}` : opt.type === 'person' ? `Person ${name}` : `Project ${name}`; const meta = opt.type === 'company' ? { website: '', address: '' } : opt.type === 'person' ? { fullName: '', email: '', phone: '', duSince: '' } : { status: '', owner: '', links: '' }; await upsertContext({ id, name: contextName, type: opt.type, sortOrder: 99, meta }); return id; } export function renderDropdown( container: HTMLDivElement, items: MentionItem[], createOptions: CreateOption[], selectedIndex: number, onSelect: (index: number) => void, onHover: (index: number) => void ): void { container.innerHTML = ''; items.forEach((item, i) => { const row = document.createElement('div'); row.className = 'mention-item' + (i === selectedIndex ? ' mention-item-active' : ''); row.textContent = `${item.icon} ${item.mentionName}`; row.classList.add(item.context.type === 'company' ? 'mention-company' : item.context.type === 'person' ? 'mention-person' : 'mention-project'); row.addEventListener('pointerdown', (e) => { e.preventDefault(); e.stopPropagation(); console.log('[mention] pointerdown item', i, item.mentionName); onSelect(i); }); row.addEventListener('mouseenter', () => onHover(i)); container.appendChild(row); }); createOptions.forEach((opt, i) => { const idx = items.length + i; const row = document.createElement('div'); row.className = 'mention-item mention-create' + (idx === selectedIndex ? ' mention-item-active' : ''); row.textContent = opt.label; row.addEventListener('pointerdown', (e) => { e.preventDefault(); e.stopPropagation(); console.log('[mention] pointerdown create', idx, opt.label); onSelect(idx); }); row.addEventListener('mouseenter', () => onHover(idx)); container.appendChild(row); }); }