Files
rocky-man/templates/index.html
Stephen Simpson ec32c72363 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>
2025-11-20 11:16:33 -06:00

359 lines
9.1 KiB
HTML

{% 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 %}