Fix viewer: safe JSON embed so search cards render again.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-26 10:51:37 +03:00
parent a31c8d678c
commit 1c7874a31e
2 changed files with 45 additions and 24 deletions

View File

@@ -216,12 +216,14 @@ def viewer(
home: Optional[str] = Query(None), home: Optional[str] = Query(None),
): ):
sections = fetch_sections(prefix) sections = fetch_sections(prefix)
# Escape < so </script> inside text_html cannot break the page script tag
sections_json = json.dumps(sections, ensure_ascii=False, default=str).replace("<", "\\u003c")
home_url = "/home-image" if (home or HOME_IMAGE or (BASE_DIR.parent / "Bairaci.png").exists()) else None home_url = "/home-image" if (home or HOME_IMAGE or (BASE_DIR.parent / "Bairaci.png").exists()) else None
return templates.TemplateResponse( return templates.TemplateResponse(
request, request,
"viewer.html", "viewer.html",
{ {
"sections_json": json.dumps(sections, ensure_ascii=False, default=str), "sections_json": sections_json,
"section_count": len(sections), "section_count": len(sections),
"prefix": prefix or "", "prefix": prefix or "",
"home_url": home_url, "home_url": home_url,

View File

@@ -335,21 +335,44 @@
<div id="toast"></div> <div id="toast"></div>
<script type="application/json" id="sections-data">{{ sections_json | safe }}</script>
<script> <script>
const ALL = {{ sections_json | safe }}; let ALL = [];
try {
ALL = JSON.parse(document.getElementById('sections-data').textContent || '[]');
} catch (e) {
console.error('sections JSON parse failed', e);
ALL = [];
}
document.getElementById('total-count').textContent = ALL.length + ' секции'; function esc(s) {
renderEditor(ALL); return String(s ?? '')
renderKwCloud(); .replace(/&/g,'&amp;').replace(/</g,'&lt;')
doSearch(); .replace(/>/g,'&gt;').replace(/"/g,'&quot;');
renderGenerator(); }
function shortPath(p) {
if (!p) return '';
const parts = String(p).replace(/\\/g,'/').split('/');
return parts.slice(-2).join('/');
}
function toast(msg, isErr) {
const el = document.getElementById('toast');
if (!el) return;
el.textContent = msg;
el.classList.toggle('error', !!isErr);
el.classList.add('show');
clearTimeout(toast._t);
toast._t = setTimeout(() => el.classList.remove('show'), 2800);
}
function switchTab(name) { function switchTab(name) {
const order = [{% if home_url %}'home',{% endif %}'editor','search','generator']; const order = [{% if home_url %}'home',{% endif %}'editor','search','generator'];
document.querySelectorAll('.tab').forEach((t,i) => t.classList.toggle('active', order[i] === name)); document.querySelectorAll('.tab').forEach((t,i) => t.classList.toggle('active', order[i] === name));
document.querySelectorAll('.panel').forEach(p => p.classList.remove('active')); document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
document.getElementById('tab-' + name).classList.add('active'); const panel = document.getElementById('tab-' + name);
if (panel) panel.classList.add('active');
if (name === 'generator') renderGenerator(); if (name === 'generator') renderGenerator();
if (name === 'search') doSearch();
} }
async function startRescan() { async function startRescan() {
@@ -703,28 +726,24 @@ ${bodyAbs}
</body></html>`; </body></html>`;
} }
// ── UTILS ─────────────────────────────
function esc(s) {
return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
function shortPath(p) {
if (!p) return '';
const parts = p.replace(/\\/g,'/').split('/');
return parts.slice(-2).join('/');
}
function toast(msg, isError) {
const el = document.getElementById('toast');
el.textContent = msg;
el.classList.toggle('error', !!isError);
el.classList.add('show');
setTimeout(() => el.classList.remove('show'), 3000);
}
function download(filename, content, mime) { function download(filename, content, mime) {
const a = document.createElement('a'); const a = document.createElement('a');
a.href = URL.createObjectURL(new Blob([content], {type: mime})); a.href = URL.createObjectURL(new Blob([content], {type: mime}));
a.download = filename; a.download = filename;
a.click(); a.click();
} }
// ── INIT ─────────────────────────────
document.getElementById('total-count').textContent = ALL.length + ' секции';
try {
renderEditor(ALL);
renderKwCloud();
doSearch();
renderGenerator();
} catch (e) {
console.error('init failed', e);
toast('Грешка при зареждане на UI: ' + e.message, true);
}
</script> </script>
</body> </body>
</html> </html>