|
|
|
const dynamicDesc = document.getElementById("dynamicDesc"); |
|
const dynamicTitle = document.getElementById("dynamicTitle"); |
|
|
|
const singleModeBtn = document.getElementById('single-mode-btn'); |
|
const batchModeBtn = document.getElementById('batch-mode-btn'); |
|
const keywordModeBtn = document.getElementById("keyword-mode-btn"); |
|
const expModeBtn = document.getElementById("exp-mode-btn"); |
|
|
|
const singleInputField = document.querySelector('.single-input'); |
|
const batchInputField = document.querySelector('.batch-input'); |
|
const keywordInputField = document.querySelector(".keyword-input"); |
|
const expKeywordInputField = document.querySelector(".experimental-input"); |
|
|
|
const docIdInput = document.getElementById('doc-id'); |
|
const batchIdsInput = document.getElementById('batch-ids'); |
|
const keywordInput = document.getElementById("keywords"); |
|
const expKeywordInput = document.getElementById("exp-keywords") |
|
const thresholdInput = document.getElementById("threshold"); |
|
|
|
const releaseFilter = document.querySelector("input[name=release]") |
|
const modeFilter = document.querySelector("select[name=mode]") |
|
const specTypeFilter = document.querySelector("select[name=spec_type]") |
|
const workingGroupFilter = document.querySelector("select[name=working_group]") |
|
const caseSensitiveFilter = document.querySelector("input[name=case_sensitive]") |
|
const searchMode = document.querySelector("select[name=search_mode]") |
|
|
|
const releaseFilter2 = document.querySelector("input[name=release2]") |
|
const specTypeFilter2 = document.querySelector("select[name=spec_type2]") |
|
const workingGroupFilter2 = document.querySelector("select[name=working_group2]") |
|
|
|
const searchBtn = document.getElementById('search-btn'); |
|
const batchSearchBtn = document.getElementById('batch-search-btn'); |
|
const keywordSearchBtn = document.getElementById("keyword-search-btn"); |
|
const expKeywordSearchBtn = document.getElementById("exp-search-btn"); |
|
|
|
const loader = document.getElementById('loader'); |
|
const resultsContainer = document.getElementById('results-container'); |
|
const resultsList = document.getElementById('results-list'); |
|
const resultsStats = document.getElementById('results-stats'); |
|
const errorMessage = document.getElementById('error-message'); |
|
|
|
const sectionPopup = document.getElementById('sectionPopup'); |
|
const popupTitle = document.getElementById('popupTitle'); |
|
const popupTextareas = document.getElementById('popupTextareas'); |
|
const copyAllBtn = document.getElementById('copyAllBtn'); |
|
const closePopupBtn = document.querySelector('.close-popup'); |
|
|
|
|
|
singleModeBtn.addEventListener('click', () => { |
|
dynamicTitle.textContent = "Find 3GPP Documents"; |
|
dynamicDesc.textContent = "Enter a TSG document ID / specification ID (e.g., S1-123456, C2-987654 or 31.102) to locate the document in the 3GPP FTP server."; |
|
|
|
singleModeBtn.classList.add('active'); |
|
keywordModeBtn.classList.remove("active"); |
|
batchModeBtn.classList.remove('active'); |
|
expModeBtn.classList.remove('active'); |
|
|
|
singleInputField.style.display = 'block'; |
|
batchInputField.style.display = 'none'; |
|
keywordInputField.style.display = "none"; |
|
expKeywordInputField.style.display = "none"; |
|
}); |
|
|
|
batchModeBtn.addEventListener('click', () => { |
|
dynamicTitle.textContent = "Find multiple 3GPP Documents"; |
|
dynamicDesc.textContent = "Enter a list of TSG document ID / specification ID (e.g., S1-123456, C2-987654 or 31.102) to locate all of the specified documents in the 3GPP FTP server."; |
|
|
|
batchModeBtn.classList.add('active'); |
|
keywordModeBtn.classList.remove("active"); |
|
singleModeBtn.classList.remove('active'); |
|
expModeBtn.classList.remove('active'); |
|
|
|
batchInputField.style.display = 'block'; |
|
keywordInputField.style.display = "none"; |
|
singleInputField.style.display = 'none'; |
|
expKeywordInputField.style.display = "none"; |
|
}); |
|
|
|
keywordModeBtn.addEventListener('click', () => { |
|
dynamicTitle.textContent = "Search 3GPP specifications"; |
|
dynamicDesc.textContent = "With keywords and filters, find all of 3GPP's specifications that matches your needs (with keywords, specification number, release or even working group (C1, S5, SP, CP: always the first letter of the group followed by the workgroup number)"; |
|
|
|
keywordModeBtn.classList.add("active"); |
|
singleModeBtn.classList.remove('active'); |
|
batchModeBtn.classList.remove("active"); |
|
expModeBtn.classList.remove('active'); |
|
|
|
singleInputField.style.display = "none"; |
|
batchInputField.style.display = "none"; |
|
expKeywordInputField.style.display = "none"; |
|
keywordInputField.style.display = "block"; |
|
}) |
|
|
|
expModeBtn.addEventListener('click', () => { |
|
dynamicTitle.textContent = "[EXPERIMENTAL] Search 3GPP specifications"; |
|
dynamicDesc.textContent = "With keywords and filters, find all of 3GPP's specifications that matches your needs (with keywords, specification number, release or even working group (C1, S5, SP, CP: always the first letter of the group followed by the workgroup number)"; |
|
|
|
keywordModeBtn.classList.remove("active"); |
|
singleModeBtn.classList.remove('active'); |
|
batchModeBtn.classList.remove("active"); |
|
expModeBtn.classList.add('active'); |
|
|
|
singleInputField.style.display = "none"; |
|
batchInputField.style.display = "none"; |
|
expKeywordInputField.style.display = "block"; |
|
keywordInputField.style.display = "none"; |
|
}) |
|
|
|
document.getElementById('toggleFilters').onclick = function() { |
|
var target = document.getElementById('filtersForm'); |
|
target.style.display = (target.style.display === 'none' || target.style.display === '') ? 'flex' : 'none'; |
|
}; |
|
|
|
document.getElementById('toggleFilters2').onclick = function() { |
|
var target = document.getElementById('filtersForm2'); |
|
target.style.display = (target.style.display === 'none' || target.style.display === '') ? 'flex' : 'none'; |
|
}; |
|
|
|
expKeywordSearchBtn.addEventListener("click", async ()=>{ |
|
let keywords = expKeywordInput.value.trim(); |
|
let release = releaseFilter2.value.trim(); |
|
let wg = workingGroupFilter2.value.trim(); |
|
let specType = specTypeFilter2.value.trim(); |
|
let threshold = thresholdInput.value.trim(); |
|
|
|
if (!keywords){ |
|
showError("Please enter at least one keyword"); |
|
return; |
|
} |
|
|
|
showLoader(); |
|
hideError(); |
|
|
|
try{ |
|
let body = { |
|
keywords, |
|
threshold |
|
}; |
|
if (release != ""){body["release"] = release} |
|
if (wg != ""){body["working_group"] = wg} |
|
if (specType != ""){body["spec_type"] = specType} |
|
const response = await fetch("/search-spec/experimental", { |
|
method: "POST", |
|
headers: { |
|
"Content-Type": "application/json" |
|
}, |
|
body: JSON.stringify(body) |
|
}); |
|
|
|
const data = await response.json(); |
|
if (response.ok){ |
|
displayKeywordResults(data, ""); |
|
} else if (response.status == 404) { |
|
showError('No specification has been found'); |
|
} else { |
|
showError(`Error processing keyword request: ${data.detail}`) |
|
} |
|
} catch (error) { |
|
showError('Error connecting to the server. Please check if the API is running.'); |
|
console.error('Error:', error); |
|
} finally { |
|
hideLoader(); |
|
} |
|
}) |
|
|
|
keywordSearchBtn.addEventListener("click", async ()=>{ |
|
let keywords = keywordInput.value.trim(); |
|
let release = releaseFilter.value; |
|
let wg = workingGroupFilter.value; |
|
let specType = specTypeFilter.value; |
|
let search = searchMode.value; |
|
let checked = caseSensitiveFilter.checked; |
|
let mode = modeFilter.value; |
|
|
|
if (!keywords && searchMode == "deep") { |
|
showError("Please enter at least one keyword in deep search mode"); |
|
return; |
|
} |
|
|
|
showLoader(); |
|
hideError(); |
|
|
|
try{ |
|
let body = { |
|
keywords, |
|
"search_mode": search, |
|
"case_sensitive": checked, |
|
"mode": mode |
|
}; |
|
if (release != ""){body.release = release} |
|
if (wg != ""){body["working_group"] = wg} |
|
if (specType != ""){body["spec_type"] = specType} |
|
const response = await fetch("/search-spec", { |
|
method: "POST", |
|
headers: { |
|
"Content-Type": "application/json" |
|
}, |
|
body: JSON.stringify(body) |
|
}); |
|
|
|
const data = await response.json(); |
|
if (response.ok){ |
|
displayKeywordResults(data, search); |
|
} else if (response.status == 404) { |
|
showError('No specification has been found'); |
|
} else { |
|
showError(`Error processing keyword request: ${data.detail}`) |
|
} |
|
} catch (error) { |
|
showError('Error connecting to the server. Please check if the API is running.'); |
|
console.error('Error:', error); |
|
} finally { |
|
hideLoader(); |
|
} |
|
}) |
|
|
|
|
|
|
|
searchBtn.addEventListener('click', async () => { |
|
const docId = docIdInput.value.trim(); |
|
if (!docId) { |
|
showError('Please enter a document ID'); |
|
return; |
|
} |
|
|
|
showLoader(); |
|
hideError(); |
|
|
|
try { |
|
const response = await fetch(`/find`, { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json' |
|
}, |
|
body: JSON.stringify({ doc_id: docId, release: null }) |
|
}); |
|
|
|
const data = await response.json(); |
|
|
|
if (response.ok) { |
|
displaySingleResult(data); |
|
} else { |
|
displaySingleNotFound(docId, data.detail); |
|
} |
|
} catch (error) { |
|
showError('Error connecting to the server. Please check if the API is running.'); |
|
console.error('Error:', error); |
|
} finally { |
|
hideLoader(); |
|
} |
|
}); |
|
|
|
|
|
batchSearchBtn.addEventListener('click', async () => { |
|
const batchText = batchIdsInput.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; |
|
} |
|
|
|
showLoader(); |
|
hideError(); |
|
|
|
try { |
|
const response = await fetch(`/batch`, { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json' |
|
}, |
|
body: JSON.stringify({ doc_ids: docIds }) |
|
}); |
|
|
|
const data = await response.json(); |
|
|
|
if (response.ok) { |
|
displayBatchResults(data); |
|
} else if (response.status == 404) { |
|
showError('No document has been found'); |
|
} else { |
|
showError('Error processing batch request') |
|
} |
|
} catch (error) { |
|
showError('Error connecting to the server. Please check if the API is running.'); |
|
console.error('Error:', error); |
|
} finally { |
|
hideLoader(); |
|
} |
|
}); |
|
|
|
|
|
function displaySingleResult(data) { |
|
resultsList.innerHTML = ''; |
|
|
|
const resultItem = document.createElement('div'); |
|
resultItem.className = 'result-item'; |
|
let scopeItem = data.scope ? `<p>Scope : ${data.scope}</p>` : "" |
|
resultItem.innerHTML = ` |
|
<div class="result-header"> |
|
<div class="result-id">${data.doc_id}</div> |
|
<div class="result-status status-found">Found</div> |
|
</div> |
|
<div class="result-url"> |
|
<a href="${data.url}" target="_blank">${data.url}</a> |
|
${scopeItem} |
|
</div> |
|
`; |
|
|
|
resultsList.appendChild(resultItem); |
|
resultsStats.textContent = `Found in ${data.search_time.toFixed(2)} seconds`; |
|
resultsContainer.style.display = 'block'; |
|
} |
|
|
|
|
|
function displaySingleNotFound(docId, message) { |
|
resultsList.innerHTML = ''; |
|
|
|
const resultItem = document.createElement('div'); |
|
resultItem.className = 'result-item'; |
|
resultItem.innerHTML = ` |
|
<div class="result-header"> |
|
<div class="result-id">${docId}</div> |
|
<div class="result-status status-not-found">Not Found</div> |
|
</div> |
|
<div>${message}</div> |
|
`; |
|
|
|
resultsList.appendChild(resultItem); |
|
resultsStats.textContent = 'Document not found'; |
|
resultsContainer.style.display = 'block'; |
|
} |
|
|
|
function displayKeywordResults(data, mode) { |
|
resultsList.innerHTML = ''; |
|
|
|
data.results.forEach(spec => { |
|
const resultItem = document.createElement("div"); |
|
resultItem.className = "result-item"; |
|
|
|
resultItem.innerHTML = ` |
|
<div class="result-header"> |
|
<div class="result-id">${spec.id}</div> |
|
<div class="result-status status-found">Found</div> |
|
</div> |
|
<div class="result-url"> |
|
<p>Title: ${spec.title}</p> |
|
<p>Type: ${spec.type}</p> |
|
<p>Release: ${spec.release}</p> |
|
<p>Version: ${spec.version}</p> |
|
<p>WG: ${spec.working_group}</p> |
|
<p>URL: <a target="_blank" href="${spec.url}">${spec.url}</a></p> |
|
<p>Scope: ${spec.scope}</p> |
|
</div> |
|
`; |
|
|
|
if(mode == "deep"){ |
|
resultItem.innerHTML += ` |
|
<div class="result-actions"> |
|
<button class="get-section-btn btn" data-spec-id="${spec.id}">Get section</button> |
|
</div> |
|
` |
|
} |
|
|
|
|
|
resultsList.appendChild(resultItem); |
|
|
|
|
|
if(mode == "deep"){ |
|
const button1 = resultItem.querySelector('.get-section-btn'); |
|
button1._sections = spec.contains; |
|
} |
|
}); |
|
|
|
document.querySelectorAll('.get-section-btn').forEach(button => { |
|
button.addEventListener('click', function() { |
|
let specId = this.getAttribute("data-spec-id"); |
|
let sections = this._sections; |
|
openSectionPopup(specId, sections); |
|
}); |
|
}); |
|
|
|
resultsStats.textContent = `Found ${data.results.length} in ${data.search_time.toFixed(2)} seconds` |
|
resultsContainer.style.display = 'block'; |
|
} |
|
|
|
function openSectionPopup(specId, sections) { |
|
const newTab = window.open('', '_blank'); |
|
let htmlContent = |
|
` |
|
<!DOCTYPE html> |
|
<html lang="fr"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<title>Sections of specification number ${specId}</title> |
|
<link rel="stylesheet" href="/static/style.css"> |
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap"> |
|
</head> |
|
<body> |
|
<div class="popup-header"> |
|
<h2 id="popupTitle">Sections of specification number ${specId}</h2> |
|
</div> |
|
<div id="popupTextareas" class="popup-textareas"> |
|
`; |
|
|
|
Object.entries(sections).forEach(([sectionTitle, content], index) => { |
|
htmlContent += |
|
` |
|
<div class="textarea-container"> |
|
<h2>${sectionTitle}</h2> |
|
<p>${content.replace(/\n/g, '<br>')}</p> |
|
</div> |
|
`; |
|
}); |
|
|
|
htmlContent += ` |
|
</div> |
|
</body> |
|
</html> |
|
`; |
|
|
|
newTab.document.open(); |
|
newTab.document.write(htmlContent); |
|
newTab.document.close() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
function displayBatchResults(data) { |
|
resultsList.innerHTML = ''; |
|
|
|
|
|
Object.entries(data.results).forEach(([docId, url]) => { |
|
const resultItem = document.createElement('div'); |
|
resultItem.className = 'result-item'; |
|
resultItem.innerHTML = ` |
|
<div class="result-header"> |
|
<div class="result-id">${docId}</div> |
|
<div class="result-status status-found">Found</div> |
|
</div> |
|
<div class="result-url"> |
|
<a href="${url}" target="_blank">${url}</a> |
|
</div> |
|
`; |
|
resultsList.appendChild(resultItem); |
|
}); |
|
|
|
|
|
data.missing.forEach(docId => { |
|
const resultItem = document.createElement('div'); |
|
resultItem.className = 'result-item'; |
|
resultItem.innerHTML = ` |
|
<div class="result-header"> |
|
<div class="result-id">${docId}</div> |
|
<div class="result-status status-not-found">Not Found</div> |
|
</div> |
|
`; |
|
resultsList.appendChild(resultItem); |
|
}); |
|
|
|
const foundCount = Object.keys(data.results).length; |
|
const totalCount = foundCount + data.missing.length; |
|
|
|
resultsStats.textContent = `Found ${foundCount} of ${totalCount} documents in ${data.search_time.toFixed(2)} seconds`; |
|
resultsContainer.style.display = 'block'; |
|
} |
|
|
|
|
|
function showLoader() { |
|
loader.style.display = 'block'; |
|
} |
|
|
|
|
|
function hideLoader() { |
|
loader.style.display = 'none'; |
|
} |
|
|
|
|
|
function showError(message) { |
|
resultsList.innerHTML = ""; |
|
resultsStats.textContent = `Found 0 documents`; |
|
errorMessage.textContent = message; |
|
errorMessage.style.display = 'block'; |
|
} |
|
|
|
|
|
function hideError() { |
|
errorMessage.style.display = 'none'; |
|
} |
|
|
|
|
|
docIdInput.addEventListener('keypress', (e) => { |
|
if (e.key === 'Enter') { |
|
searchBtn.click(); |
|
} |
|
}); |
|
|
|
keywordInput.addEventListener('keypress', (event)=>{ |
|
if (event.key === "Enter"){ |
|
keywordSearchBtn.click(); |
|
} |
|
}) |
|
|
|
expKeywordInput.addEventListener('keypress', (event)=>{ |
|
if (event.key === "Enter"){ |
|
keywordSearchBtn.click(); |
|
} |
|
}) |