diff --git a/webapp/templates/viewer.html b/webapp/templates/viewer.html
index 0e665c8..55b8e88 100644
--- a/webapp/templates/viewer.html
+++ b/webapp/templates/viewer.html
@@ -142,6 +142,20 @@
.card-tags { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 8px; }
.card-text { font-size: 12px; color: var(--muted); line-height: 1.6; max-height: 280px; overflow: hidden; }
.card-text img { max-width: 100%; max-height: 200px; display: block; margin: 6px 0; border-radius: 4px; border: 1px solid var(--border); }
+ .card-section-label {
+ font-family: var(--mono); font-size: 10px; color: var(--muted);
+ letter-spacing: .06em; text-transform: uppercase; margin: 10px 0 6px;
+ }
+ .kw-cloud {
+ display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px;
+ padding: 12px 14px; background: var(--bg2); border: 1px solid var(--border);
+ border-radius: var(--radius-lg); max-height: 140px; overflow-y: auto;
+ }
+ .kw-cloud .tag { cursor: pointer; user-select: none; }
+ .kw-cloud .tag:hover { outline: 2px solid var(--accent); }
+ .kw-cloud .tag.active { background: var(--accent); color: #fff; }
+ .search-mode { display: flex; gap: 8px; align-items: center; font-size: 12px; color: var(--muted); }
+ .search-mode label { display: flex; align-items: center; gap: 4px; cursor: pointer; font-family: var(--mono); font-size: 11px; }
.card-footer { margin-top: 8px; font-size: 11px; color: var(--muted); font-family: var(--mono); }
.check-icon { width: 18px; height: 18px; border-radius: 50%; border: 2px solid var(--border); flex-shrink: 0; margin-top: 2px; transition: all .15s; }
.card.selected .check-icon { background: var(--accent2); border-color: var(--accent2); }
@@ -272,11 +286,19 @@
+
Ключови думи — клик за филтър
+
+
Извлечения (резултати)
@@ -318,6 +340,7 @@ const ALL = {{ sections_json | safe }};
document.getElementById('total-count').textContent = ALL.length + ' секции';
renderEditor(ALL);
+renderKwCloud();
doSearch();
renderGenerator();
@@ -465,6 +488,49 @@ async function saveOne(inp) {
// ── SEARCH ────────────────────────────
const selected = new Set();
let genOrder = [];
+const activeKw = new Set();
+
+function allKeywords() {
+ const map = new Map();
+ ALL.forEach(r => {
+ (r.keywords || '').split(',').map(k => k.trim()).filter(Boolean).forEach(k => {
+ map.set(k, (map.get(k) || 0) + 1);
+ });
+ });
+ return [...map.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0], 'bg'));
+}
+
+function renderKwCloud() {
+ const host = document.getElementById('kw-cloud');
+ if (!host) return;
+ const kws = allKeywords();
+ if (!kws.length) {
+ host.innerHTML = 'Няма ключови думи в данните';
+ return;
+ }
+ host.innerHTML = kws.map(([k, n]) =>
+ `${esc(k)}`
+ ).join('');
+ host.onclick = (ev) => {
+ const t = ev.target.closest('.tag[data-kw]');
+ if (!t) return;
+ toggleKw(ev, t.getAttribute('data-kw'));
+ };
+}
+
+function toggleKw(ev, kw) {
+ ev.stopPropagation();
+ const key = kw.toLowerCase();
+ if (activeKw.has(key)) activeKw.delete(key);
+ else activeKw.add(key);
+ // sync into search box as quoted tokens for clarity
+ const box = document.getElementById('search-input');
+ const parts = [...activeKw].map(k => (/\s/.test(k) ? `"${k}"` : k));
+ box.value = parts.join(' ');
+ renderKwCloud();
+ doSearch();
+}
function tokenizeQuery(q) {
const tokens = [];
@@ -477,15 +543,27 @@ function tokenizeQuery(q) {
return tokens;
}
+function searchMode() {
+ const el = document.querySelector('input[name="search-mode"]:checked');
+ return el ? el.value : 'all';
+}
+
function doSearch() {
const q = document.getElementById('search-input').value.trim().toLowerCase();
const tokens = q ? tokenizeQuery(q) : [];
+ const mode = searchMode();
const results = tokens.length === 0 ? ALL : ALL.filter(r => {
- const hay = ((r.keywords||'') + ' ' + (r.title||'') + ' ' + (r.text||'')).toLowerCase();
+ const kw = (r.keywords || '').toLowerCase();
+ const title = (r.title || '').toLowerCase();
+ const text = (r.text || '').toLowerCase();
+ let hay = '';
+ if (mode === 'keywords') hay = kw;
+ else if (mode === 'text') hay = text + ' ' + title;
+ else hay = kw + ' ' + title + ' ' + text;
return tokens.every(t => hay.includes(t));
});
document.getElementById('search-stats').textContent =
- results.length + ' резултата' + (tokens.length > 1 ? ' (за ' + tokens.length + ' термина)' : '');
+ results.length + ' извлечения' + (tokens.length ? ' · филтър: ' + tokens.length + ' термина' : '');
document.getElementById('search-results').innerHTML = results.map(r => `
+ Ключови думи
- ${(r.keywords||'').split(',').filter(k=>k.trim()).map(k=>`${esc(k.trim())}`).join('')}
+ ${(r.keywords||'').split(',').filter(k=>k.trim()).map(k=>`${esc(k.trim())}`).join('') || '—'}
- ${r.text_html || esc(r.text||'(няма preview)')}
-
+ Извлечение
+ ${r.text_html || esc(r.text||'(няма текст — провери OUTPUT_DIR)')}
+
`).join('');
}