Refactor analysis display to full HTML

Replaces `analysis_preview` with `analysis_html` to display the full
markdown-rendered document content instead of just a plain-text preview.
This allows for richer content presentation and enables client-side
expansion for detailed views.

The HTML template has been updated to include a new `.analysis`
container that handles the expand/collapse functionality using
JavaScript. This approach avoids server-side truncation and relies on
CSS for presentation.
This commit is contained in:
2026-04-21 18:21:04 +02:00
parent 87a04c4b27
commit d13bd5307e
2 changed files with 37 additions and 93 deletions
+25 -4
View File
@@ -29,9 +29,18 @@ section h2 .count { font-weight: normal; font-size: 0.85em; color: #888; }
.case-row .case-link:hover { text-decoration: underline; }
.case-row .recordings-link { color: #4a90e2; text-decoration: none; font-size: 0.9em; white-space: nowrap; }
.case-row .recordings-link:hover { text-decoration: underline; }
.case-row .preview { margin: 0.3em 0 0; color: #666; font-size: 0.9em; display: -webkit-box; -webkit-line-clamp: var(--preview-lines); line-clamp: var(--preview-lines); -webkit-box-orient: vertical; overflow: hidden; }
.case-row .analysis { margin-top: 0.55em; padding-top: 0.45em; border-top: 1px solid #e5e5e5; display: flex; align-items: flex-start; gap: 0.4em; cursor: pointer; }
.case-row .analysis .arrow { flex-shrink: 0; color: #888; line-height: 1.2; transition: transform 0.15s ease; }
.case-row .analysis.open .arrow { transform: rotate(90deg); }
.case-row .analysis-content { color: #555; font-size: 0.9em; min-width: 0; flex: 1 1 auto; display: -webkit-box; -webkit-line-clamp: var(--preview-lines); line-clamp: var(--preview-lines); -webkit-box-orient: vertical; overflow: hidden; }
.case-row .analysis-content > :first-child { margin-top: 0; }
.case-row .analysis-content > :last-child { margin-bottom: 0; }
.case-row .analysis-content p { display: inline; margin: 0; }
.case-row .analysis-content mark { background: #fff3a0; padding: 0 0.15em; border-radius: 2px; }
.case-row .analysis.open .analysis-content { display: block; overflow: visible; }
.case-row .analysis.open .analysis-content p { display: block; margin: 0 0 0.5em; }
.case-row .uuid { color: #999; font-family: monospace; font-size: 0.7em; margin-top: 0.2em; }
.status-badge { display: inline-block; padding: 0.05em 0.5em; border-radius: 999px; font-size: 0.75em; font-weight: bold; color: white; }
.status-badge { display: inline-block; padding: 0.05em 0.5em; border-radius: 999px; font-size: 0.75em; font-weight: bold; color: white; margin-left: 0.7em; }
.status-badge.open { background: #4a90e2; }
.status-badge.done { background: #7ed321; }
.status-badge.fehler { background: #e24a4a; }
@@ -98,10 +107,10 @@ try {
<div class="check"><input type="checkbox" name="case_id" value="{{ case.case_id }}"{% if case.is_closed %} disabled{% endif %}></div>
<div class="body">
<div class="line1">
<a class="case-link" href="/web/cases/{{ case.case_id }}{% if case.is_closed %}?show_closed=1{% endif %}"><span class="time"><time datetime="{{ case.recorded_at_iso }}">{{ case.time_hms_utc }}</time></span> — {% call ol::render(case.oneliner) %}{% if case.has_failed_recording %} <span class="status-badge fehler">Fehler</span>{% else if case.has_document %} <span class="status-badge done">ausgewertet</span>{% else if !case.is_closed %} <span class="status-badge open">offen</span>{% endif %}{% if case.analyzing %} <span class="label analyzing">wird analysiert</span>{% endif %}{% if case.is_closed %} <span class="closed-badge">geschlossen{% match case.days_until_purge %}{% when Some with (d) %} — wird in {{ d }} Tagen entfernt{% when None %}{% endmatch %}</span>{% endif %}</a>
<a class="case-link" href="/web/cases/{{ case.case_id }}{% if case.is_closed %}?show_closed=1{% endif %}"><span class="time"><time datetime="{{ case.recorded_at_iso }}">{{ case.time_hms_utc }}</time></span> — {% call ol::render(case.oneliner) %}{% if case.has_failed_recording %} <span class="status-badge fehler">Fehler</span>{% else if case.has_document %} <span class="status-badge done">ausgewertet</span>{% else if !case.is_closed %} <span class="status-badge open">offen</span>{% endif %}{% if case.analyzing %} <span class="label analyzing">wird analysiert</span>{% endif %}{% if case.is_closed %} <span class="closed-badge">geschlossen{% match case.days_until_purge %}{% when Some with (d) %} — wird in {{ d }} Tagen entfernt{% when None %}{% endmatch %}</span>{% endif %}</a>
<a class="recordings-link" href="/web/cases/{{ case.case_id }}/recordings{% if case.is_closed %}?show_closed=1{% endif %}">{{ case.recordings_count }} Aufnahmen</a>
</div>
{% match case.analysis_preview %}{% when Some with (preview) %}<p class="preview">{{ preview }}</p>{% when None %}{% endmatch %}
{% match case.analysis_html %}{% when Some with (html) %}<div class="analysis" data-expand><span class="arrow"></span><div class="analysis-content">{{ html|safe }}</div></div>{% when None %}{% endmatch %}
{% if is_admin %}<div class="uuid admin-only">{{ case.case_id }}</div>{% endif %}
</div>
<div class="actions">
@@ -212,6 +221,18 @@ try {
window.addEventListener('pagehide', () => es.close());
es.addEventListener('case', () => scheduleReload());
})();
// Expandable analysis: click anywhere on .analysis toggles the `open`
// class. Links/buttons inside the analysis are preserved — a click on
// a <mark>-wrapped term shouldn't accidentally expand (no such case
// today, but the guard is free). Delegation keeps one listener across
// all case rows, no per-row wiring.
document.addEventListener('click', (e) => {
const exp = e.target.closest('[data-expand]');
if (!exp) return;
if (e.target.closest('a, button, input')) return;
exp.classList.toggle('open');
});
</script>
</body>
</html>