DocFinder / static /script.js
om4r932's picture
Front-end implementation
2903edf
// Global application state
let currentResults = [];
let currentMode = 'single';
// Initialization
document.addEventListener('DOMContentLoaded', function() {
initializeApp();
});
function initializeApp() {
setupTabHandlers();
setupKeyboardHandlers();
updateHeaderStats('Ready');
}
// Tab management
function setupTabHandlers() {
document.querySelectorAll('.tab-button').forEach(button => {
button.addEventListener('click', function() {
const mode = this.dataset.mode;
switchMode(mode);
});
});
}
function switchMode(mode) {
currentMode = mode;
// Update tabs
document.querySelectorAll('.tab-button').forEach(btn => {
btn.classList.remove('active');
});
document.querySelector(`[data-mode="${mode}"]`).classList.add('active');
// Update forms
document.querySelectorAll('.search-form').forEach(form => {
form.classList.remove('active');
});
document.getElementById(`${mode}-form`).classList.add('active');
// Reset results
hideResults();
}
// Keyboard shortcuts management
function setupKeyboardHandlers() {
document.getElementById('doc-id').addEventListener('keypress', function(e) {
if (e.key === 'Enter') searchSingle();
});
document.getElementById('keywords').addEventListener('keypress', function(e) {
if (e.key === 'Enter') searchKeyword();
});
document.getElementById('bm25-keywords').addEventListener('keypress', function(e) {
if (e.key === 'Enter') searchBM25();
});
}
// Search functions
async function searchSingle() {
const docId = document.getElementById('doc-id').value.trim();
if (!docId) {
showError('Please enter a document ID');
return;
}
showLoading();
updateHeaderStats('Searching...');
try {
const response = await fetch(`/find/single`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ doc_id: docId })
});
const data = await response.json();
if (response.ok) {
displaySingleResult(data);
updateHeaderStats(`Found in ${data.search_time.toFixed(3)}s`);
} else {
showError(data.detail);
updateHeaderStats('Error');
}
} catch (error) {
showError('Error connecting to server');
updateHeaderStats('Error');
console.error('Error:', error);
} finally {
hideLoading();
}
}
async function searchBatch() {
const batchText = document.getElementById('batch-ids').value.trim();
if (!batchText) {
showError('Please enter at least one document ID');
return;
}
const docIds = batchText.split('\n')
.map(id => id.trim())
.filter(id => id !== '');
if (docIds.length === 0) {
showError('Please enter at least one valid document ID');
return;
}
showLoading();
updateHeaderStats('Searching...');
try {
const response = await fetch(`/find/batch`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ doc_ids: docIds })
});
const data = await response.json();
if (response.ok) {
displayBatchResults(data);
updateHeaderStats(`${Object.keys(data.results).length} found, ${data.missing.length} missing - ${data.search_time.toFixed(3)}s`);
} else {
showError(data.detail);
updateHeaderStats('Error');
}
} catch (error) {
showError('Error connecting to server');
updateHeaderStats('Error');
console.error('Error:', error);
} finally {
hideLoading();
}
}
async function searchKeyword() {
const keywords = document.getElementById('keywords').value.trim();
const searchMode = document.getElementById('search-mode-filter').value;
if (!keywords && searchMode === 'deep') {
showError('Please enter at least one keyword in deep search mode');
return;
}
showLoading();
updateHeaderStats('Searching...');
try {
const body = {
keywords: keywords,
search_mode: searchMode,
case_sensitive: document.getElementById('case-sensitive-filter').checked,
source: document.getElementById('source-filter').value,
mode: document.getElementById('mode-filter').value
};
const specType = document.getElementById('spec-type-filter').value;
if (specType) {
body.spec_type = specType;
}
const response = await fetch(`/search`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body)
});
const data = await response.json();
if (response.ok) {
displaySearchResults(data);
updateHeaderStats(`${data.results.length} result(s) - ${data.search_time.toFixed(3)}s`);
} else {
showError(data.detail);
updateHeaderStats('Error');
}
} catch (error) {
showError('Error connecting to server');
updateHeaderStats('Error');
console.error('Error:', error);
} finally {
hideLoading();
}
}
async function searchBM25() {
const keywords = document.getElementById('bm25-keywords').value.trim();
if (!keywords) {
showError('Please enter a search query');
return;
}
showLoading();
updateHeaderStats('Searching...');
try {
const body = {
keywords: keywords,
source: document.getElementById('bm25-source-filter').value,
threshold: parseInt(document.getElementById('threshold').value) || 60
};
const specType = document.getElementById('bm25-spec-type-filter').value;
if (specType) {
body.spec_type = specType;
}
const response = await fetch(`/search/bm25`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body)
});
const data = await response.json();
if (response.ok) {
displaySearchResults(data);
updateHeaderStats(`${data.results.length} result(s) - ${data.search_time.toFixed(3)}s`);
} else {
showError(data.detail);
updateHeaderStats('Error');
}
} catch (error) {
showError('Error connecting to server');
updateHeaderStats('Error');
console.error('Error:', error);
} finally {
hideLoading();
}
}
// Results display functions
function displaySingleResult(data) {
const resultsContent = document.getElementById('results-content');
resultsContent.innerHTML = `
<div class="result-item">
<div class="result-header">
<div class="result-id">${data.doc_id}</div>
<div class="result-status status-found">Found</div>
</div>
<div class="result-details">
${data.version ? `<div class="result-detail"><strong>Version:</strong> ${data.version}</div>` : ''}
${data.scope ? `<div class="result-detail"><strong>Scope:</strong> ${data.scope}</div>` : ''}
<div class="result-detail result-url">
<strong>URL:</strong> <a href="${data.url}" target="_blank">${data.url}</a>
</div>
</div>
</div>
`;
showResults();
}
function displayBatchResults(data) {
const resultsContent = document.getElementById('results-content');
let html = '';
// Found results
Object.entries(data.results).forEach(([docId, url]) => {
html += `
<div class="result-item">
<div class="result-header">
<div class="result-id">${docId}</div>
<div class="result-status status-found">Found</div>
</div>
<div class="result-details">
<div class="result-detail result-url">
<strong>URL:</strong> <a href="${url}" target="_blank">${url}</a>
</div>
</div>
</div>
`;
});
// Missing documents
data.missing.forEach(docId => {
html += `
<div class="result-item">
<div class="result-header">
<div class="result-id">${docId}</div>
<div class="result-status status-missing">Not Found</div>
</div>
<div class="result-details">
<div class="result-detail">Document not found or not indexed</div>
</div>
</div>
`;
});
resultsContent.innerHTML = html;
showResults();
}
function displaySearchResults(data) {
const resultsContent = document.getElementById('results-content');
currentResults = data.results;
let html = '';
data.results.forEach((spec, index) => {
const hasContent = spec.contains && Object.keys(spec.contains).length > 0;
html += `
<div class="result-item">
<div class="result-header">
<div class="result-id">${spec.id}</div>
<div class="result-status status-found">${spec.type || 'Specification'}</div>
</div>
<div class="result-details">
<div class="result-detail"><strong>Title:</strong> ${spec.title}</div>
${spec.version ? `<div class="result-detail"><strong>Version:</strong> ${spec.version}</div>` : ''}
${spec.working_group ? `<div class="result-detail"><strong>Working Group:</strong> ${spec.working_group}</div>` : ''}
${spec.type ? `<div class="result-detail"><strong>Type:</strong> ${spec.type}</div>` : ''}
${spec.scope ? `<div class="result-detail"><strong>Scope:</strong> ${spec.scope}</div>` : ''}
${hasContent ? `<button class="view-content-btn" onclick="viewContent(${index})">View Content</button>` : ''}
</div>
</div>
`;
});
resultsContent.innerHTML = html;
showResults();
}
// Content display functions
function viewContent(index) {
const spec = currentResults[index];
if (!spec.contains) return;
document.getElementById('content-title').textContent = `${spec.id} - ${spec.title}`;
const contentSections = document.getElementById('content-sections');
let html = '';
Object.entries(spec.contains).forEach(([sectionTitle, content]) => {
html += `
<div class="content-section">
<h3>${sectionTitle}</h3>
<p>${content}</p>
<button class="copy-section-btn" onclick="copyText('${content.replace(/'/g, "\\'")}')">
Copy this section
</button>
</div>
`;
});
contentSections.innerHTML = html;
showContentPage();
}
function closeContentPage() {
hideContentPage();
}
function copyAllContent() {
const sections = document.querySelectorAll('.content-section p');
const allText = Array.from(sections).map(p => p.textContent).join('\n\n');
copyText(allText);
}
function copyText(text) {
navigator.clipboard.writeText(text).then(() => {
showSuccess('Text copied to clipboard');
}).catch(() => {
showError('Error copying text');
});
}
// Interface utilities
function showLoading() {
document.getElementById('loading-container').style.display = 'flex';
hideResults();
hideError();
}
function hideLoading() {
document.getElementById('loading-container').style.display = 'none';
}
function showResults() {
document.getElementById('results-container').style.display = 'block';
hideError();
}
function hideResults() {
document.getElementById('results-container').style.display = 'none';
}
function showContentPage() {
document.getElementById('content-page').classList.add('active');
}
function hideContentPage() {
document.getElementById('content-page').classList.remove('active');
}
function showError(message) {
hideError();
const errorDiv = document.createElement('div');
errorDiv.className = 'error-message';
errorDiv.textContent = message;
document.querySelector('.search-container').appendChild(errorDiv);
setTimeout(() => {
hideError();
}, 5000);
}
function showSuccess(message) {
hideError();
const successDiv = document.createElement('div');
successDiv.className = 'success-message';
successDiv.textContent = message;
document.querySelector('.search-container').appendChild(successDiv);
setTimeout(() => {
hideError();
}, 3000);
}
function hideError() {
const existingMessages = document.querySelectorAll('.error-message, .success-message');
existingMessages.forEach(msg => msg.remove());
}
function updateHeaderStats(text) {
document.getElementById('header-stats').innerHTML = `<span class="stat-item">${text}</span>`;
}