diff --git a/webapp/templates/viewer.html b/webapp/templates/viewer.html
index d7d81aa..05ed87b 100644
--- a/webapp/templates/viewer.html
+++ b/webapp/templates/viewer.html
@@ -142,10 +142,22 @@
.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;
+ .card-actions { display: flex; gap: 8px; margin-top: 10px; }
+ .card-actions button { font-size: 11px; padding: 5px 10px; }
+ .modal-backdrop {
+ display: none; position: fixed; inset: 0; background: rgba(20,24,36,.45);
+ z-index: 200; align-items: center; justify-content: center; padding: 24px;
}
+ .modal-backdrop.open { display: flex; }
+ .modal {
+ background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius-lg);
+ width: min(900px, 100%); max-height: min(86vh, 900px); overflow: auto;
+ box-shadow: 0 16px 48px rgba(0,0,0,.18); padding: 20px 22px;
+ }
+ .modal-head { display: flex; justify-content: space-between; gap: 12px; align-items: flex-start; margin-bottom: 12px; }
+ .modal-head h2 { font-size: 16px; font-weight: 500; line-height: 1.35; }
+ .modal-body { font-size: 13px; line-height: 1.65; color: var(--text); }
+ .modal-body img { max-width: 100%; max-height: 320px; display: block; margin: 10px 0; border: 1px solid var(--border); border-radius: 6px; }
.kw-cloud {
display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px;
padding: 12px 14px; background: var(--bg2); border: 1px solid var(--border);
@@ -286,7 +298,7 @@
-
ГЕНЕРИРАЙ ДОКУМЕНТ
+
ГЕНЕРИРАЙ HTML
-
-
+
+
+
+
Ключови думи
+
+
Извлечение
+
Зареждане…
+
+
+
@@ -556,12 +578,32 @@ function toggleKw(ev, kw) {
}
function tokenizeQuery(q) {
+ // Паразитни / служебни думи — прескачат се при търсене
+ const STOP = new Set([
+ 'аз','ти','той','тя','то','ние','вие','те','ме','ми','му','го','я','ги','ни','ви','се','си',
+ 'съм','си','е','сме','сте','са','бях','беше','бяха','ще','да','не','ли','дали',
+ 'и','или','но','а','нито','че','като','ако','защото','понеже',
+ 'за','от','на','в','във','с','със','по','към','до','при','след','преди','без','над','под','през','около','между',
+ 'този','тази','това','тези','онзи','онази','онова','онези','един','една','едно','едни',
+ 'как','кой','коя','кое','кои','къде','кога','защо','колко','какъв','каква','какво','какви',
+ 'инфо','информация','търся','търсим','търсите','искам','искаме','искате','моля','моляте',
+ 'покажи','покажете','кажи','кажете','дай','дайте','намери','намерете','имам','имаме','има','няма',
+ 'може','могат','трябва','също','още','вече','само','много','малко','добре','така','тук','там',
+ 'сега','тогава','пак','отново','примерно','например','относно','във','връзка','относно',
+ 'please','info','information','about','for','the','a','an','i','am','looking','find','show','get',
+ ]);
const tokens = [];
const re = /"([^"]+)"|(\S+)/g;
let m;
while ((m = re.exec(q)) !== null) {
- const t = (m[1] || m[2] || '').toLowerCase();
- if (t) tokens.push(t);
+ let t = (m[1] || m[2] || '').toLowerCase();
+ // махаме пунктуация в края/началото: "карти:," -> "карти"
+ t = t.replace(/^[\s.,;:!?()\[\]{}"'«»]+|[\s.,;:!?()\[\]{}"'«»]+$/g, '');
+ if (!t) continue;
+ // quoted phrases keep even if short; unquoted stop-words skip
+ const quoted = !!m[1];
+ if (!quoted && (STOP.has(t) || t.length < 2)) continue;
+ tokens.push(t);
}
return tokens;
}
@@ -572,7 +614,8 @@ function searchMode() {
}
function doSearch() {
- const q = document.getElementById('search-input').value.trim().toLowerCase();
+ const raw = document.getElementById('search-input').value.trim();
+ const q = raw.toLowerCase();
const tokens = q ? tokenizeQuery(q) : [];
const mode = searchMode();
const results = tokens.length === 0 ? ALL : ALL.filter(r => {
@@ -585,8 +628,11 @@ function doSearch() {
else hay = kw + ' ' + title + ' ' + text;
return tokens.every(t => hay.includes(t));
});
+ const skippedNote = (raw && tokens.length === 0)
+ ? ' · само паразитни думи — показани всички'
+ : (tokens.length ? ' · филтър: ' + tokens.join(', ') : '');
document.getElementById('search-stats').textContent =
- results.length + ' извлечения' + (tokens.length ? ' · филтър: ' + tokens.length + ' термина' : '');
+ results.length + ' извлечения' + skippedNote;
document.getElementById('search-results').innerHTML = results.map(r => `
Извлечение
${r.text_html || esc(r.text||'(няма текст — провери OUTPUT_DIR)')}
+
+
+
`).join('');
}
+async function openSection(code) {
+ const modal = document.getElementById('section-modal');
+ document.getElementById('modal-code').textContent = code;
+ document.getElementById('modal-title').textContent = 'Зареждане…';
+ document.getElementById('modal-tags').innerHTML = '';
+ document.getElementById('modal-body').textContent = 'Зареждане…';
+ modal.classList.add('open');
+ try {
+ const res = await fetch('/api/section/' + encodeURIComponent(code));
+ if (!res.ok) throw new Error('HTTP ' + res.status);
+ const r = await res.json();
+ document.getElementById('modal-title').textContent = r.title || code;
+ document.getElementById('modal-tags').innerHTML =
+ (r.keywords||'').split(',').filter(k=>k.trim()).map(k=>`${esc(k.trim())}`).join('')
+ || '—';
+ document.getElementById('modal-body').innerHTML =
+ r.text_html || esc(r.text || '(празно)').replace(/\n/g, '
');
+ } catch (e) {
+ document.getElementById('modal-body').textContent = 'Грешка: ' + e.message;
+ }
+}
+
+function closeSection() {
+ document.getElementById('section-modal').classList.remove('open');
+}
+document.addEventListener('keydown', (e) => {
+ if (e.key === 'Escape') closeSection();
+});
+
function toggleSelect(code, el) {
if (selected.has(code)) {
selected.delete(code);