JezK
Edit File: library.php
<?php $pageTitle = 'Research Library'; $extraCss = ' <style> .upload-zone{border:2px dashed var(--primary-300);background:var(--primary-50);border-radius:16px;padding:3rem 2rem;text-align:center;cursor:pointer;transition:all var(--transition-base);} .upload-zone:hover{background:#EFF6FF;border-color:var(--primary-500);} .paper-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:1.5rem;margin-top:2rem;} .paper-card{background:#fff;border:1px solid var(--color-border);border-radius:14px;padding:1.25rem;transition:all .2s;cursor:pointer;display:flex;flex-direction:column;} .paper-card:hover{box-shadow:var(--shadow-md);transform:translateY(-2px);} .paper-title{font-size:.95rem;font-weight:700;color:var(--gray-900);line-height:1.4;margin-bottom:.5rem;} .paper-author{font-size:.8rem;color:var(--gray-600);margin-bottom:.25rem;} .paper-meta{font-size:.75rem;color:var(--color-text-subtle);margin-top:auto;padding-top:.75rem;border-top:1px solid var(--color-border-subtle);display:flex;justify-content:space-between;} </style> '; include 'includes/header.php'; ?> <!-- Main Library content --> <div class="main-area"> <header class="topbar"> <div class="topbar-title">Research Library</div> <div class="topbar-search"> <div class="input-prefix"> <svg class="icon" width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg> <input class="input" id="library-search" placeholder="Search title, abstract, authors, keywords..." onkeydown="if(event.key==='Enter') doSearch(this.value)"> </div> </div> </header> <main class="page-content"> <!-- Upload Zone --> <div class="upload-zone animate-fade-in-up" onclick="document.getElementById('file-upload').click()"> <div style="margin-bottom:1rem;display:flex;justify-content:center;"><i data-lucide="folder-open" style="width:48px;height:48px;color:var(--primary-500);"></i></div> <h3 style="font-size:1.15rem;margin-bottom:.25rem;color:var(--primary-800)">Upload PDF, DOCX, BibTeX (.bib) or RIS (.ris) files</h3> <p class="text-muted text-sm">Drag and drop files here, or click to browse. Imports Zotero, Mendeley & EndNote libraries.</p> <input type="file" id="file-upload" accept=".pdf,.docx,.bib,.ris" style="display:none;" onchange="handleFileUpload(event)"> </div> <!-- Header List --> <div style="display:flex;justify-content:space-between;align-items:center;margin:2.5rem 0 1rem;"> <div style="display:flex;align-items:center;gap:0.75rem;"> <h2 style="font-size:1.2rem;font-weight:700;margin:0;">Uploaded Papers database</h2> <span class="badge badge-purple" id="paper-count-badge">Loading...</span> </div> <div style="display:flex;gap:0.5rem;"> <button class="btn btn-secondary btn-sm" onclick="document.getElementById('file-upload').click()">+ Import BibTeX/RIS</button> <button class="btn btn-primary btn-sm" onclick="triggerBibtexExport()">Export BibTeX</button> </div> </div> <!-- Grid --> <div class="paper-grid" id="library-papers-grid"> <!-- Loaded via javascript --> </div> </main> </div> <!-- Paper Details Modal --> <div class="modal-overlay" id="paper-detail-modal"> <div class="modal modal-lg"> <div class="modal-header"> <h2 class="modal-title" id="m-paper-title">Paper Metadata</h2> <button class="btn btn-ghost btn-icon" onclick="closeModal('paper-detail-modal')">✕</button> </div> <div class="modal-body"> <div style="margin-bottom:1.5rem;"> <span class="badge badge-blue" id="m-paper-year">2024</span> <span class="badge badge-purple" id="m-paper-journal">Nature</span> </div> <div style="margin-bottom:1.5rem;"> <strong style="display:block;margin-bottom:.25rem;">Authors:</strong> <p id="m-paper-authors" class="text-sm"></p> </div> <div style="margin-bottom:1.5rem;"> <strong style="display:block;margin-bottom:.25rem;">Abstract:</strong> <p id="m-paper-abstract" class="text-sm text-muted" style="line-height:1.6;"></p> </div> <div style="margin-bottom:1.5rem;"> <strong style="display:block;margin-bottom:.25rem;">Keywords:</strong> <div id="m-paper-keywords" style="display:flex;gap:.5rem;flex-wrap:wrap;"></div> </div> <div style="margin-bottom:1rem;"> <strong style="display:block;margin-bottom:.25rem;">DOI:</strong> <span id="m-paper-doi" class="text-sm font-mono text-primary"></span> </div> </div> <div class="modal-footer"> <button class="btn btn-danger btn-sm" style="margin-right:auto;" id="btn-delete-paper">Delete Paper</button> <button class="btn btn-secondary" onclick="closeModal('paper-detail-modal')">Close</button> </div> </div> </div> <div id="toast-container"></div> <script src="js/storage.js"></script> <script> let activePaperId = null; async function initLibrary() { if (!await authGuard()) return; loadPapers(); } async function loadPapers() { const res = await api.getLibraryPapers().catch(() => null); const grid = document.getElementById('library-papers-grid'); if (res?.status === 'success') { document.getElementById('paper-count-badge').textContent = `${res.data.papers.length} References`; if (res.data.papers.length > 0) { grid.innerHTML = res.data.papers.map(p => ` <div class="paper-card animate-fade-in-up" onclick="viewPaperDetails(${p.id})"> <div class="paper-title">${p.title || p.filename}</div> <div class="paper-author">${p.authors || 'Unknown Authors'}</div> <div class="paper-meta" style="display:flex;gap:12px;align-items:center;"> <span style="display:inline-flex;align-items:center;gap:4px;"><i data-lucide="calendar" style="width:12px;height:12px;"></i> ${p.year || 'n.d.'}</span> <span>${(p.file_size / 1024 / 1024).toFixed(2)} MB</span> </div> </div> `).join(''); } else { grid.innerHTML = ` <div style="grid-column: 1/-1;" class="empty-state"> <div class="empty-icon"><i data-lucide="folder" style="width:32px;height:32px;color:var(--gray-400);"></i></div> <div class="empty-title">Your library is empty</div> <div class="empty-desc">Upload reference research papers to extract citations and build your knowledge map.</div> </div>`; } } if (typeof lucide !== 'undefined') { lucide.createIcons(); } } async function handleFileUpload(e) { const file = e.target.files[0]; if (!file) return; const ext = file.name.split('.').pop().toLowerCase(); if (ext === 'bib' || ext === 'ris') { const reader = new FileReader(); reader.onload = async function(evt) { const text = evt.target.result; let entries = []; if (ext === 'bib') { entries = parseBibTeX(text); } else { entries = parseRIS(text); } if (entries.length === 0) { showToast('No valid reference entries found in file.', 'warning'); return; } showToast(`Importing ${entries.length} references...`, 'info'); let successCount = 0; for (const entry of entries) { const res = await api.importPaperMetadata({ title: entry.title, authors: entry.authors, year: entry.year, journal: entry.journal, doi: entry.doi }).catch(() => null); if (res?.status === 'success') successCount++; } showToast(`Imported ${successCount} of ${entries.length} references successfully!`, 'success'); loadPapers(); }; reader.readAsText(file); return; } showToast('Uploading research paper...', 'info'); const mockMetadata = { title: file.name.replace(/\.[^/.]+$/, "").replace(/_/g, " "), authors: 'Chen, S., Robertson, J., & Patel, A.', abstract: 'This research paper analyzes primary concepts regarding DNA double-strand breaks, identifying key factors leading to genetic alterations. It offers a comprehensive methodology based on recent Cas9 trials.', keywords: 'Cas9, Gene Editing, DNA repair, CRISPR', year: '2025', journal: 'Journal of Academic Genomics', doi: `10.1016/j.cell.2025.06.0` + Math.floor(Math.random() * 90 + 10) }; const res = await api.uploadPaper(file, mockMetadata).catch(() => null); if (res?.status === 'success') { showToast('Paper uploaded and AI-processed successfully!', 'success'); loadPapers(); } else { showToast(res?.message || 'Upload failed', 'error'); } } function parseBibTeX(text) { const entries = []; const entryRegex = /@(\w+)\s*\{\s*([^,]+),([\s\S]*?)\n\s*\}/g; let match; while ((match = entryRegex.exec(text)) !== null) { const fieldsText = match[3]; const fields = {}; const fieldRegex = /(\w+)\s*=\s*(?:\{([\s\S]*?)\}|"([\s\S]*?)"|([^,\n]+))/g; let fieldMatch; while ((fieldMatch = fieldRegex.exec(fieldsText)) !== null) { const fieldName = fieldMatch[1].toLowerCase(); const fieldValue = fieldMatch[2] || fieldMatch[3] || fieldMatch[4] || ''; fields[fieldName] = fieldValue.replace(/[\{\}]/g, '').trim(); } if (fields.title) { entries.push({ title: fields.title, authors: fields.author || 'Unknown Authors', year: fields.year || 'n.d.', journal: fields.journal || fields.booktitle || 'Research Publication', doi: fields.doi || '' }); } } return entries; } function parseRIS(text) { const entries = []; const records = text.split(/ER\s*-\s*\r?\n/); for (let record of records) { if (!record.trim()) continue; const fields = { authors: [] }; const lines = record.split(/\r?\n/); for (let line of lines) { const match = line.match(/^([A-Z0-9]{2})\s*-\s*(.*)$/); if (match) { const tag = match[1]; const val = match[2].trim(); if (tag === 'TI' || tag === 'T1') fields.title = val; else if (tag === 'AU' || tag === 'A1') fields.authors.push(val); else if (tag === 'PY' || tag === 'Y1') fields.year = val.substring(0, 4); else if (tag === 'JO' || tag === 'JF' || tag === 'T2') fields.journal = val; else if (tag === 'DO') fields.doi = val; } } if (fields.title) { entries.push({ title: fields.title, authors: fields.authors.join(', ') || 'Unknown Authors', year: fields.year || 'n.d.', journal: fields.journal || 'Research Publication', doi: fields.doi || '' }); } } return entries; } async function triggerBibtexExport() { const res = await api.getLibraryPapers().catch(() => null); if (!res || res.status !== 'success' || res.data.papers.length === 0) { showToast('No papers in library to export.', 'warning'); return; } let bib = ''; res.data.papers.forEach((p, i) => { const key = p.authors ? (p.authors.split(',')[0].replace(/[^a-zA-Z]/g, '') + (p.year || '')) : 'ref_' + i; bib += `@article{${key.toLowerCase()},\n`; bib += ` title = {${p.title}},\n`; bib += ` author = {${p.authors || 'Unknown Authors'}},\n`; bib += ` year = {${p.year || 'n.d.'}},\n`; bib += ` journal = {${p.journal || 'Research Proceedings'}},\n`; if (p.doi) bib += ` doi = {${p.doi}},\n`; bib += `}\n\n`; }); const blob = new Blob([bib], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'citationguard_library.bib'; document.body.appendChild(a); a.click(); document.body.removeChild(a); showToast('Library exported successfully as BibTeX!', 'success'); } async function viewPaperDetails(id) { activePaperId = id; const res = await api.getPaper(id).catch(() => null); if (res?.status === 'success') { const p = res.data.paper; document.getElementById('m-paper-title').textContent = p.title || p.filename; document.getElementById('m-paper-authors').textContent = p.authors || 'Unknown Authors'; document.getElementById('m-paper-abstract').textContent = p.abstract || 'No abstract extracted.'; document.getElementById('m-paper-year').textContent = p.year || 'n.d.'; document.getElementById('m-paper-journal').textContent = p.journal || 'Research Proceeding'; document.getElementById('m-paper-doi').textContent = p.doi || 'No DOI listed'; const kwDiv = document.getElementById('m-paper-keywords'); if (p.keywords) { kwDiv.innerHTML = p.keywords.split(',').map(k => `<span class="badge badge-purple">${k.trim()}</span>`).join(''); } else { kwDiv.innerHTML = '<span class="text-xs text-muted">No keywords extracted</span>'; } document.getElementById('btn-delete-paper').onclick = async () => { if (confirm('Delete this paper from your library?')) { await api.deletePaper(id); closeModal('paper-detail-modal'); showToast('Paper deleted successfully', 'success'); loadPapers(); } }; openModal('paper-detail-modal'); } } async function doSearch(q) { if (!q.trim()) { loadPapers(); return; } const grid = document.getElementById('library-papers-grid'); grid.innerHTML = `<div style="grid-column: 1/-1;" class="text-center py-8"><span class="spinner"></span></div>`; const res = await api.searchLibrary(q).catch(() => null); if (res?.status === 'success') { document.getElementById('paper-count-badge').textContent = `${res.data.results.length} Matches`; if (res.data.results.length > 0) { grid.innerHTML = res.data.results.map(p => ` <div class="paper-card animate-fade-in-up" onclick="viewPaperDetails(${p.id})"> <div class="paper-title">${p.title || p.filename}</div> <div class="paper-author">${p.authors || 'Unknown Authors'}</div> <div class="paper-meta" style="display:flex;gap:12px;align-items:center;"> <span style="display:inline-flex;align-items:center;gap:4px;"><i data-lucide="calendar" style="width:12px;height:12px;"></i> ${p.year || 'n.d.'}</span> <span>DOI Match</span> </div> </div> `).join(''); } else { grid.innerHTML = ` <div style="grid-column: 1/-1;" class="empty-state"> <div class="empty-icon"><i data-lucide="search" style="width:32px;height:32px;color:var(--gray-400);"></i></div> <div class="empty-title">No matches found</div> <div class="empty-desc">Try search terms like "Cas9", "CRISPR" or "Gene".</div> </div>`; } } if (typeof lucide !== 'undefined') { lucide.createIcons(); } } initLibrary(); </script> <?php include 'includes/footer.php'; ?>