import React, { useState, useEffect, useMemo, useRef } from 'react'; import { Link as LinkIcon, FileText, Globe, X, Search, ExternalLink, Loader2, Plus, } from 'lucide-react'; import { api } from '../../lib/api'; import { Document } from '../../lib/types'; export interface LinkModalProps { isOpen: boolean; onClose: () => void; onInsertLink: (markdown: string) => void; availableDocs?: { id: string; title: string; icon?: string; folderPath?: string }[]; } export const LinkModal: React.FC = ({ isOpen, onClose, onInsertLink, availableDocs, }) => { const [tab, setTab] = useState<'note' | 'web'>('note'); const [search, setSearch] = useState(''); const [docs, setDocs] = useState([]); const [loading, setLoading] = useState(false); const [webTitle, setWebTitle] = useState(''); const [webUrl, setWebUrl] = useState(''); const searchInputRef = useRef(null); const webTitleInputRef = useRef(null); // Load documents when modal opens useEffect(() => { if (!isOpen) return; if (availableDocs && availableDocs.length > 0) { setDocs(availableDocs as Document[]); return; } let isCancelled = false; setLoading(true); api .getDocuments() .then((fetched) => { if (!isCancelled) { setDocs(fetched); setLoading(false); } }) .catch(() => { if (!isCancelled) { setLoading(false); } }); return () => { isCancelled = true; }; }, [isOpen, availableDocs]); // Focus input on mount or tab change useEffect(() => { if (!isOpen) return; const timer = setTimeout(() => { if (tab === 'note') { searchInputRef.current?.focus(); } else { webTitleInputRef.current?.focus(); } }, 50); return () => clearTimeout(timer); }, [isOpen, tab]); // Close on Escape useEffect(() => { if (!isOpen) return; const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') { e.preventDefault(); onClose(); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [isOpen, onClose]); const filteredDocs = useMemo(() => { if (!search.trim()) return docs; const q = search.toLowerCase(); return docs.filter( (d) => d.title.toLowerCase().includes(q) || (d.folderPath && d.folderPath.toLowerCase().includes(q)) ); }, [docs, search]); const handleSelectNote = (doc: { title: string }) => { const noteTitle = doc.title.trim() || 'Notiz'; onInsertLink(`[[${noteTitle}]]`); }; const handleWebLinkSubmit = (e: React.FormEvent) => { e.preventDefault(); let url = webUrl.trim(); if (!url) return; if (!/^https?:\/\//i.test(url) && !url.startsWith('#') && !url.startsWith('/')) { url = `https://${url}`; } const label = webTitle.trim() || url; onInsertLink(`[${label}](${url})`); }; if (!isOpen) return null; return (
{ if (e.target === e.currentTarget) onClose(); }} >
{/* Header - identical to UnifiedShareModal */}

Link einfügen

Notiz intern verknüpfen oder externen Web-Link einbetten

{/* Control Box - matching UnifiedShareModal form header */}
Link-Typ:
{tab === 'note' ? (
setSearch(e.target.value)} placeholder="Notiz suchen (Titel oder Ordner)…" className="input-field bg-panel h-8 pl-8 pr-2.5 text-xs w-full text-main placeholder:text-muted" />
) : (
setWebTitle(e.target.value)} placeholder="z. B. MindSift Dokumentation" className="input-field bg-panel h-8 px-2.5 text-xs w-full text-main placeholder:text-muted" />
setWebUrl(e.target.value)} placeholder="https://example.com" className="input-field bg-panel h-8 px-2.5 text-xs w-full text-main placeholder:text-muted" />
)}
{/* Note List Section (Matching Active Shares list in UnifiedShareModal) */} {tab === 'note' && (
Verfügbare Notizen {filteredDocs.length} {filteredDocs.length === 1 ? 'Notiz' : 'Notizen'}
{loading ? (
Notizen werden geladen…
) : filteredDocs.length === 0 ? (
{search ? `Keine Notiz für "${search}" gefunden.` : 'Keine Notizen im Workspace vorhanden.'}
) : ( filteredDocs.map((d) => (
handleSelectNote(d)} className="flex items-center justify-between p-2.5 bg-canvas border border-border rounded-card hover:border-accent/60 transition-colors group cursor-pointer" >
{d.icon || '📝'}
{d.title || 'Unbenannte Notiz'}
{d.folderPath && (
📁 {d.folderPath}
)}
)) )}
)}
); };