CUSP-1256 (#1)
* Complete refactor Signed-off-by: Stephen Simpson <ssimpson89@users.noreply.github.com> * Complete refactor Signed-off-by: Stephen Simpson <ssimpson89@users.noreply.github.com> --------- Signed-off-by: Stephen Simpson <ssimpson89@users.noreply.github.com>
This commit is contained in:
359
templates/index.html
Normal file
359
templates/index.html
Normal file
@@ -0,0 +1,359 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block header_title %}Rocky Linux {{ version }} Man Pages{% endblock %}
|
||||
{% block header_subtitle %}Search and browse {{ total_pages }} man pages{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
.search-box {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 1rem;
|
||||
background-color: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
color: var(--text-primary);
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.search-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-primary);
|
||||
box-shadow: 0 0 0 3px rgba(88, 166, 255, 0.2);
|
||||
}
|
||||
|
||||
.search-input:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.search-stats {
|
||||
margin-top: 1rem;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.results-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.result-item {
|
||||
padding: 0.75rem 0;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.result-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.result-link {
|
||||
font-size: 1.1rem;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.result-package {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.85rem;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.spinner {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 3px solid var(--border-color);
|
||||
border-top-color: var(--accent-primary);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.no-results {
|
||||
text-align: center;
|
||||
padding: 3rem 1rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.quick-links {
|
||||
margin-top: 2rem;
|
||||
padding-top: 1.5rem;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.quick-links h3 {
|
||||
margin-bottom: 1rem;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.package-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.package-link {
|
||||
padding: 0.5rem;
|
||||
background-color: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
transition: background-color 0.2s, border-color 0.2s;
|
||||
min-height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.package-link:hover {
|
||||
background-color: var(--bg-primary);
|
||||
border-color: var(--accent-primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.view-all-container {
|
||||
text-align: center;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.view-all-button {
|
||||
display: inline-block;
|
||||
padding: 0.75rem 1.5rem;
|
||||
background-color: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
color: var(--accent-primary);
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s;
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.view-all-button:hover {
|
||||
background-color: var(--bg-primary);
|
||||
border-color: var(--accent-primary);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.search-input {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.package-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||
}
|
||||
|
||||
.result-link {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.result-package {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.package-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||||
}
|
||||
|
||||
.quick-links h3 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="content">
|
||||
<div class="search-box">
|
||||
<input type="text" id="searchInput" class="search-input" placeholder="Loading search index..." disabled>
|
||||
<div class="search-stats" id="searchStats">
|
||||
<div class="loading">
|
||||
<span class="spinner"></span>
|
||||
<span style="margin-left: 0.5rem;">Loading search index...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="resultsContainer">
|
||||
<ul class="results-list" id="resultsList"></ul>
|
||||
</div>
|
||||
|
||||
<div class="quick-links">
|
||||
<h3>Browse by Package</h3>
|
||||
<div class="package-grid">
|
||||
{% for package in packages[:50] %}
|
||||
<a href="#" class="package-link" data-package="{{ package }}">{{ package }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="view-all-container">
|
||||
<a href="packages.html" class="button view-all-button">
|
||||
View All Packages →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/fuse.js@7.0.0/dist/fuse.min.js"></script>
|
||||
<script>
|
||||
let fuse;
|
||||
let searchData = [];
|
||||
|
||||
// Load search index
|
||||
fetch('search.json.gz')
|
||||
.then(response => response.body.pipeThrough(new DecompressionStream('gzip')))
|
||||
.then(stream => new Response(stream))
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Flatten data for searching
|
||||
searchData = [];
|
||||
for (const [pkg, pages] of Object.entries(data)) {
|
||||
for (const [key, page] of Object.entries(pages)) {
|
||||
searchData.push({
|
||||
...page,
|
||||
package: pkg
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize Fuse.js
|
||||
fuse = new Fuse(searchData, {
|
||||
keys: [
|
||||
{ name: 'name', weight: 2.0 },
|
||||
{ name: 'display_name', weight: 1.5 },
|
||||
{ name: 'package', weight: 1.0 },
|
||||
{ name: 'full_name', weight: 0.8 }
|
||||
],
|
||||
threshold: 0.25,
|
||||
minMatchCharLength: 2,
|
||||
ignoreLocation: false,
|
||||
location: 0
|
||||
});
|
||||
|
||||
// Enable search
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
searchInput.disabled = false;
|
||||
searchInput.placeholder = 'Search man pages... (e.g., "bash", "printf", "systemd")';
|
||||
|
||||
document.getElementById('searchStats').innerHTML =
|
||||
`<strong>${searchData.length}</strong> man pages available`;
|
||||
|
||||
// Check for query parameter
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const query = urlParams.get('q');
|
||||
|
||||
if (query && query.trim()) {
|
||||
searchInput.value = query;
|
||||
searchInput.focus();
|
||||
runSearch(query.trim());
|
||||
} else {
|
||||
// Show all initially
|
||||
displayResults(searchData.slice(0, 50));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading search index:', error);
|
||||
document.getElementById('searchStats').innerHTML =
|
||||
'<span style="color: var(--warning)">Error loading search index</span>';
|
||||
});
|
||||
|
||||
// Search function
|
||||
function runSearch(query) {
|
||||
if (!query) {
|
||||
displayResults(searchData.slice(0, 50));
|
||||
document.getElementById('searchStats').innerHTML =
|
||||
`Showing 50 of <strong>${searchData.length}</strong> man pages`;
|
||||
return;
|
||||
}
|
||||
|
||||
const results = fuse.search(query, { limit: 100 });
|
||||
const items = results.map(r => r.item);
|
||||
|
||||
displayResults(items);
|
||||
|
||||
document.getElementById('searchStats').innerHTML =
|
||||
`Found <strong>${items.length}</strong> result${items.length !== 1 ? 's' : ''}`;
|
||||
}
|
||||
|
||||
// Search handler
|
||||
document.getElementById('searchInput').addEventListener('input', function (e) {
|
||||
const query = e.target.value.trim();
|
||||
runSearch(query);
|
||||
|
||||
// Update URL with search query
|
||||
const url = new URL(window.location);
|
||||
if (query) {
|
||||
url.searchParams.set('q', query);
|
||||
} else {
|
||||
url.searchParams.delete('q');
|
||||
}
|
||||
window.history.replaceState({}, '', url);
|
||||
});
|
||||
|
||||
// Package link handler
|
||||
document.querySelectorAll('.package-link').forEach(link => {
|
||||
link.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
const pkg = this.dataset.package;
|
||||
const pkgResults = searchData.filter(item => item.package === pkg);
|
||||
displayResults(pkgResults);
|
||||
document.getElementById('searchInput').value = pkg;
|
||||
document.getElementById('searchStats').innerHTML =
|
||||
`<strong>${pkgResults.length}</strong> man pages in <strong>${pkg}</strong>`;
|
||||
|
||||
// Update URL with package search
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.set('q', pkg);
|
||||
window.history.replaceState({}, '', url);
|
||||
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
});
|
||||
});
|
||||
|
||||
function displayResults(items) {
|
||||
const resultsList = document.getElementById('resultsList');
|
||||
|
||||
if (items.length === 0) {
|
||||
resultsList.innerHTML =
|
||||
'<div class="no-results">No man pages found. Try a different search term.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
resultsList.innerHTML = items.map(item => `
|
||||
<li class="result-item">
|
||||
<a href="${item.url}" class="result-link">
|
||||
<span>${item.display_name}</span>
|
||||
<span class="result-package">${item.package}</span>
|
||||
</a>
|
||||
</li>
|
||||
`).join('');
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user