feat: full-text search across all doc modules
This commit is contained in:
@@ -142,6 +142,42 @@ async function loadDoc(name) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function escapeHtml(s) {
|
||||||
|
return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fulltextSearch(q) {
|
||||||
|
el.innerHTML = '<p>Searching...</p>';
|
||||||
|
var results = [];
|
||||||
|
for (var i = 0; i < MODULES.length; i++) {
|
||||||
|
var m = MODULES[i];
|
||||||
|
try {
|
||||||
|
var resp = await fetch('/doc-wasm/modules/' + m[0] + '.md');
|
||||||
|
if (!resp.ok) continue;
|
||||||
|
var md = await resp.text();
|
||||||
|
var lines = md.split('\n');
|
||||||
|
for (var li = 0; li < lines.length; li++) {
|
||||||
|
if (lines[li].toLowerCase().indexOf(q) >= 0) {
|
||||||
|
results.push({ module: m[0], title: m[1] + ' / ' + m[2], line: li + 1, text: lines[li].trim() });
|
||||||
|
if (results.length > 50) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(e) {}
|
||||||
|
}
|
||||||
|
if (results.length === 0) {
|
||||||
|
el.innerHTML = '<p>No results for <strong>' + escapeHtml(q) + '</strong></p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var html = '<h2>Search: ' + escapeHtml(q) + '</h2><p>' + results.length + ' result(s)</p>';
|
||||||
|
for (var r of results) {
|
||||||
|
html += '<p style="margin:4px 0"><a href="#" onclick="loadDoc(\'' + r.module + '\');return false" style="font-weight:600">'
|
||||||
|
+ r.module + ' ' + escapeHtml(r.title) + '</a> <span style="color:#888;font-size:12px">line ' + r.line + '</span><br>'
|
||||||
|
+ '<code style="background:#f5f5f5;padding:2px 6px;border-radius:3px;font-size:12px;display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">'
|
||||||
|
+ escapeHtml(r.text) + '</code></p>';
|
||||||
|
}
|
||||||
|
el.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
async function loginUser(user, pass) {
|
async function loginUser(user, pass) {
|
||||||
var resp = await fetch('/api/v1/auth/login', {
|
var resp = await fetch('/api/v1/auth/login', {
|
||||||
method:'POST', headers:{'Content-Type':'application/json'},
|
method:'POST', headers:{'Content-Type':'application/json'},
|
||||||
@@ -187,14 +223,18 @@ async function initApp() {
|
|||||||
a.onclick = function(e) { e.preventDefault(); loadDoc(m[0]); };
|
a.onclick = function(e) { e.preventDefault(); loadDoc(m[0]); };
|
||||||
listEl.appendChild(a);
|
listEl.appendChild(a);
|
||||||
});
|
});
|
||||||
// Search filter
|
// Search — full text across all modules
|
||||||
|
var searchTimer = null;
|
||||||
var searchEl = document.getElementById('search');
|
var searchEl = document.getElementById('search');
|
||||||
if (searchEl) {
|
if (searchEl) {
|
||||||
searchEl.oninput = function() {
|
searchEl.oninput = function() {
|
||||||
var q = this.value.toLowerCase();
|
var q = this.value.toLowerCase().trim();
|
||||||
document.querySelectorAll('#module-list a').forEach(function(a) {
|
document.querySelectorAll('#module-list a').forEach(function(a) {
|
||||||
a.style.display = a.textContent.toLowerCase().indexOf(q) >= 0 ? '' : 'none';
|
a.style.display = a.textContent.toLowerCase().indexOf(q) >= 0 ? '' : 'none';
|
||||||
});
|
});
|
||||||
|
if (searchTimer) clearTimeout(searchTimer);
|
||||||
|
if (q.length < 2) { loadDoc(location.hash.slice(1) || '01_auth'); return; }
|
||||||
|
searchTimer = setTimeout(function() { fulltextSearch(q); }, 300);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// Watch for logout disappearing
|
// Watch for logout disappearing
|
||||||
|
|||||||
Reference in New Issue
Block a user