Faktenkompass: UI-Rebrand, Admin-Assistent und Produktions-Setup.
Modernisiert die öffentliche Oberfläche mit Inter, Dark Mode und Live-Suche, ergänzt KI-gestützte Entwürfe im Admin mit Nachweis-Uploads und dokumentiert Daphne/Nginx für den Serverbetrieb. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
{% extends "admin/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}{% if subtitle %}{{ subtitle }} | {% endif %}{{ title }} | Faktenkompass Redaktion{% endblock %}
|
||||
|
||||
{% block extrastyle %}
|
||||
{{ block.super }}
|
||||
<link rel="stylesheet" href="{% static 'css/admin_custom.css' %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block extrahead %}
|
||||
{{ block.super }}
|
||||
<script src="{% static 'js/admin_custom.js' %}" defer></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block branding %}
|
||||
<div id="site-name"><a href="{% url 'admin:index' %}">Faktenkompass Redaktion</a></div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,97 @@
|
||||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block extrastyle %}
|
||||
{{ block.super }}
|
||||
<style>
|
||||
.assistant-wrap { max-width: 820px; }
|
||||
.chat-box {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
min-height: 120px;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.chat-hint { color: #6c757d; font-size: 0.9rem; }
|
||||
.assistant-card {
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 1.25rem;
|
||||
background: #fff;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<div class="breadcrumbs">
|
||||
<a href="{% url 'admin:index' %}">Start</a>
|
||||
› <a href="{% url 'admin:claims_claimdraft_changelist' %}">Fakten-Entwürfe</a>
|
||||
› Assistent
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="assistant-wrap">
|
||||
<h1>Fakten-Assistent</h1>
|
||||
<p class="chat-hint">
|
||||
Link, Bild und/oder Beschreibung eingeben. Die KI erstellt einen <strong>Entwurf zur Prüfung</strong> –
|
||||
nichts wird automatisch veröffentlicht.
|
||||
</p>
|
||||
|
||||
<div class="chat-box">
|
||||
<p class="mb-1"><strong>So funktioniert es:</strong></p>
|
||||
<ol class="mb-0 ps-3">
|
||||
<li>Eingaben senden (Artikel-URL, Beschreibung, optional Bild/PDF)</li>
|
||||
<li>KI analysiert den Kontext und schlägt Kategorie & Fakteneintrag vor</li>
|
||||
<li>Sie prüfen, bearbeiten und veröffentlichen erst nach Freigabe</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="assistant-card">
|
||||
<form method="post" enctype="multipart/form-data" novalidate>
|
||||
{% csrf_token %}
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger">{{ form.non_field_errors }}</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="{{ form.article_url.id_for_label }}">{{ form.article_url.label }}</label>
|
||||
{{ form.article_url }}
|
||||
{% if form.article_url.errors %}<div class="text-danger small">{{ form.article_url.errors }}</div>{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="{{ form.user_notes.id_for_label }}">{{ form.user_notes.label }}</label>
|
||||
{{ form.user_notes }}
|
||||
{% if form.user_notes.errors %}<div class="text-danger small">{{ form.user_notes.errors }}</div>{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="{{ form.uploaded_file.id_for_label }}">{{ form.uploaded_file.label }}</label>
|
||||
{{ form.uploaded_file }}
|
||||
<div class="form-text">JPG, PNG, GIF, WebP, SVG oder PDF. SVG wird für die KI intern in PNG umgewandelt.</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="button default">Entwurf generieren</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% if recent_drafts %}
|
||||
<div class="mt-4">
|
||||
<h2 class="h6 text-muted">Letzte Entwürfe</h2>
|
||||
<ul>
|
||||
{% for draft in recent_drafts %}
|
||||
<li>
|
||||
<a href="{% url 'admin:claims_claimdraft_review' draft.pk %}">
|
||||
{{ draft.title|default:"(ohne Titel)" }}
|
||||
</a>
|
||||
– {{ draft.get_status_display }}
|
||||
<span class="text-muted">({{ draft.created_at|date:"d.m.Y H:i" }})</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,17 @@
|
||||
{% extends "admin/change_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
{% if original and original.pk %}
|
||||
<div class="fk-claim-meta-links">
|
||||
<a href="{{ original.get_absolute_url }}" target="_blank" rel="noopener">↗ Auf Website ansehen</a>
|
||||
<a href="{% url 'admin:claims_claimdraft_assistant' %}">+ Fakten-Assistent</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="fk-claim-help">
|
||||
Neue Behauptung anlegen: zuerst Titel und Kurzantwort ausfüllen, dann speichern.
|
||||
Nachweise, Quellen und Gegenargumente können danach ergänzt werden.
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,8 @@
|
||||
{% extends "admin/change_list.html" %}
|
||||
|
||||
{% block object-tools-items %}
|
||||
<li>
|
||||
<a href="{% url 'admin:claims_claimdraft_assistant' %}" class="addlink">Fakten-Assistent</a>
|
||||
</li>
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,158 @@
|
||||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block extrastyle %}
|
||||
{{ block.super }}
|
||||
<style>
|
||||
.review-grid { display: grid; grid-template-columns: 1fr 320px; gap: 1.5rem; }
|
||||
@media (max-width: 960px) { .review-grid { grid-template-columns: 1fr; } }
|
||||
.preview-card, .chat-panel {
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
padding: 1rem;
|
||||
}
|
||||
.chat-panel { background: #f8f9fa; max-height: 420px; overflow-y: auto; }
|
||||
.chat-msg { margin-bottom: 0.75rem; font-size: 0.92rem; }
|
||||
.chat-msg.user { color: #0d3d66; }
|
||||
.chat-msg.assistant { color: #333; }
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: 0.2rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
background: #e9ecef;
|
||||
}
|
||||
.field-box { margin-bottom: 1rem; }
|
||||
.field-box label { display: block; font-weight: 600; margin-bottom: 0.25rem; }
|
||||
.actions-bar { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 1rem; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<div class="breadcrumbs">
|
||||
<a href="{% url 'admin:index' %}">Start</a>
|
||||
› <a href="{% url 'admin:claims_claimdraft_changelist' %}">Fakten-Entwürfe</a>
|
||||
› <a href="{% url 'admin:claims_claimdraft_assistant' %}">Assistent</a>
|
||||
› Prüfen
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Entwurf prüfen</h1>
|
||||
|
||||
<p>
|
||||
Status: <span class="status-badge">{{ draft.get_status_display }}</span>
|
||||
{% if draft.published_claim %}
|
||||
· <a href="{% url 'admin:claims_claim_change' draft.published_claim.pk %}">Veröffentlichte Behauptung öffnen</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
{% if draft.error_message %}
|
||||
<div class="alert alert-danger">{{ draft.error_message }}</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="review-grid">
|
||||
<div>
|
||||
<form method="post" class="preview-card">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="save" id="form-action">
|
||||
|
||||
<div class="field-box">
|
||||
<label for="{{ form.category_choice.id_for_label }}">{{ form.category_choice.label }}</label>
|
||||
{{ form.category_choice }}
|
||||
</div>
|
||||
|
||||
<div class="field-box">
|
||||
<label for="{{ form.new_category_name.id_for_label }}">Neue Kategorie (falls leer oben)</label>
|
||||
{{ form.new_category_name }}
|
||||
{{ form.new_category_description }}
|
||||
{{ form.new_category_icon }}
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div class="field-box">
|
||||
<label for="{{ form.title.id_for_label }}">{{ form.title.label }}</label>
|
||||
{{ form.title }}
|
||||
</div>
|
||||
|
||||
<div class="field-box">
|
||||
<label for="{{ form.claim_status.id_for_label }}">{{ form.claim_status.label }}</label>
|
||||
{{ form.claim_status }}
|
||||
</div>
|
||||
|
||||
<div class="field-box">
|
||||
<label for="{{ form.evidence_level.id_for_label }}">{{ form.evidence_level.label }}</label>
|
||||
{{ form.evidence_level }}
|
||||
</div>
|
||||
|
||||
<div class="field-box">
|
||||
<label for="{{ form.short_answer.id_for_label }}">{{ form.short_answer.label }}</label>
|
||||
{{ form.short_answer }}
|
||||
</div>
|
||||
|
||||
<div class="field-box">
|
||||
<label for="{{ form.detailed_explanation.id_for_label }}">{{ form.detailed_explanation.label }}</label>
|
||||
{{ form.detailed_explanation }}
|
||||
</div>
|
||||
|
||||
<div class="field-box">
|
||||
<label for="{{ form.tags_text.id_for_label }}">{{ form.tags_text.label }}</label>
|
||||
{{ form.tags_text }}
|
||||
</div>
|
||||
|
||||
<div class="field-box">
|
||||
<label for="{{ form.sources_json.id_for_label }}">{{ form.sources_json.label }}</label>
|
||||
{{ form.sources_json }}
|
||||
</div>
|
||||
|
||||
<div class="field-box">
|
||||
<label for="{{ form.counter_arguments_json.id_for_label }}">{{ form.counter_arguments_json.label }}</label>
|
||||
{{ form.counter_arguments_json }}
|
||||
</div>
|
||||
|
||||
<div class="actions-bar">
|
||||
<button type="submit" class="button" onclick="document.getElementById('form-action').value='save'">Speichern</button>
|
||||
{% if draft.status != 'published' %}
|
||||
<button type="submit" class="button default" onclick="document.getElementById('form-action').value='publish'">
|
||||
Veröffentlichen
|
||||
</button>
|
||||
<button type="submit" class="button" formaction="." formmethod="post" name="action" value="reject"
|
||||
onclick="return confirm('Entwurf wirklich verwerfen?')">Verwerfen</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="chat-panel mb-3">
|
||||
<h2 class="h6">Konversation</h2>
|
||||
{% for msg in draft.conversation %}
|
||||
<div class="chat-msg {{ msg.role }}">
|
||||
<strong>{% if msg.role == 'user' %}Sie{% else %}Assistent{% endif %}:</strong>
|
||||
{{ msg.content|linebreaksbr }}
|
||||
</div>
|
||||
{% empty %}
|
||||
<p class="text-muted mb-0">Noch keine Nachrichten.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if draft.uploaded_file %}
|
||||
<p class="small text-muted mb-2">Datei: {{ draft.uploaded_file.name }}</p>
|
||||
{% endif %}
|
||||
{% if draft.article_url %}
|
||||
<p class="small mb-3"><a href="{{ draft.article_url }}" target="_blank" rel="noopener">Artikel öffnen</a></p>
|
||||
{% endif %}
|
||||
|
||||
{% if draft.status != 'published' %}
|
||||
<form method="post" class="preview-card">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="refine">
|
||||
<h2 class="h6">Überarbeiten lassen</h2>
|
||||
{{ refine_form.refinement }}
|
||||
<button type="submit" class="button mt-2">KI erneut fragen</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,23 @@
|
||||
{% extends "admin/index.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="fk-admin-dashboard">
|
||||
<a class="fk-admin-card" href="{% url 'admin:claims_claim_add' %}">
|
||||
<strong>+ Neue Behauptung</strong>
|
||||
<span>Manuell anlegen und Texte, Quellen sowie Nachweise einpflegen.</span>
|
||||
</a>
|
||||
<a class="fk-admin-card" href="{% url 'admin:claims_claim_changelist' %}">
|
||||
<strong>Behauptungen bearbeiten</strong>
|
||||
<span>Bestehende Einträge suchen, aktualisieren und veröffentlichen.</span>
|
||||
</a>
|
||||
<a class="fk-admin-card" href="{% url 'admin:claims_claimdraft_assistant' %}">
|
||||
<strong>Fakten-Assistent (KI)</strong>
|
||||
<span>Aus Artikel oder Grafik einen Entwurf erstellen und prüfen.</span>
|
||||
</a>
|
||||
<a class="fk-admin-card" href="{% url 'admin:claims_category_changelist' %}">
|
||||
<strong>Themen / Kategorien</strong>
|
||||
<span>Kategorien verwalten, Icons und Beschreibungen anpassen.</span>
|
||||
</a>
|
||||
</div>
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
+32
-12
@@ -6,35 +6,55 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="{% block meta_description %}Faktenkompass – Überprüfbare Fakten und Quellen.{% endblock %}">
|
||||
<title>{% block title %}Faktenkompass{% endblock %}</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
|
||||
<link href="{% static 'css/custom.css' %}" rel="stylesheet">
|
||||
<script>
|
||||
(function () {
|
||||
var t = localStorage.getItem('fk-theme');
|
||||
if (!t) {
|
||||
t = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
}
|
||||
document.documentElement.setAttribute('data-theme', t);
|
||||
})();
|
||||
</script>
|
||||
<script src="{% static 'js/theme.js' %}" defer></script>
|
||||
<script src="{% static 'js/live-search.js' %}" defer></script>
|
||||
</head>
|
||||
<body class="d-flex flex-column min-vh-100">
|
||||
<nav class="navbar navbar-dark bg-primary">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="{% url 'claims:home' %}">
|
||||
<i class="bi bi-compass me-1"></i>Faktenkompass
|
||||
<header class="fk-nav">
|
||||
<div class="container fk-nav-inner">
|
||||
<a class="fk-brand" href="{% url 'claims:home' %}">
|
||||
<span class="fk-brand-icon"><i class="bi bi-compass"></i></span>
|
||||
Faktenkompass
|
||||
</a>
|
||||
<div class="d-flex gap-2">
|
||||
<a class="btn btn-sm btn-outline-light{% if request.resolver_match.url_name == 'home' %} active{% endif %}" href="{% url 'claims:home' %}">Themen</a>
|
||||
<a class="btn btn-sm btn-outline-light{% if request.resolver_match.url_name == 'search' %} active{% endif %}" href="{% url 'claims:search' %}">Suche</a>
|
||||
</div>
|
||||
<nav class="fk-nav-links" aria-label="Hauptnavigation">
|
||||
<a class="fk-nav-link{% if request.resolver_match.url_name == 'home' %} active{% endif %}" href="{% url 'claims:home' %}">Themen</a>
|
||||
<a class="fk-nav-link{% if request.resolver_match.url_name == 'search' %} active{% endif %}" href="{% url 'claims:search' %}">Suche</a>
|
||||
<button type="button" class="fk-theme-btn" id="fk-theme-toggle" aria-label="Farbschema umschalten">
|
||||
<i class="bi bi-sun fk-icon-sun" aria-hidden="true"></i>
|
||||
<i class="bi bi-moon fk-icon-moon" hidden aria-hidden="true"></i>
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="flex-grow-1 py-3">
|
||||
<main class="flex-grow-1 fk-main">
|
||||
<div class="container">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="border-top py-2 mt-auto">
|
||||
<footer class="fk-footer">
|
||||
<div class="container">
|
||||
<p class="text-muted text-center mb-0 small">© {% now "Y" %} Faktenkompass</p>
|
||||
<p>© {% now "Y" %} Faktenkompass</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
{% block extra_js %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -10,21 +10,21 @@
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<div class="d-flex align-items-center gap-2 mb-3">
|
||||
<div class="d-flex align-items-center gap-2 mb-4">
|
||||
<div class="category-tile-icon flex-shrink-0">
|
||||
<i class="bi {{ category.icon|default:'bi-book' }}"></i>
|
||||
</div>
|
||||
<div class="flex-grow-1 overflow-hidden">
|
||||
<h1 class="h5 mb-0 text-truncate">{{ category.name }}</h1>
|
||||
<h1 class="fk-page-title mb-0 text-truncate">{{ category.name }}</h1>
|
||||
<span class="small text-muted">{{ claims|length }} Behauptung{% if claims|length != 1 %}en{% endif %}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="get" class="row g-2 mb-3">
|
||||
<form method="get" class="row g-2 mb-4" data-live-search data-live-target="#category-results">
|
||||
<div class="col-12 col-sm">
|
||||
<input type="search" id="q" name="q" class="form-control form-control-sm" placeholder="Suchen…" value="{{ query }}">
|
||||
<input type="search" id="q" name="q" class="form-control form-control-sm" placeholder="Suchen…" value="{{ query }}" autocomplete="off">
|
||||
</div>
|
||||
<div class="col-7 col-sm-auto">
|
||||
<div class="col-12 col-sm-auto">
|
||||
<select id="status" name="status" class="form-select form-select-sm">
|
||||
<option value="">Alle Status</option>
|
||||
{% for value, label in status_choices %}
|
||||
@@ -32,23 +32,9 @@
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-5 col-sm-auto">
|
||||
<button type="submit" class="btn btn-primary btn-sm w-100">Filtern</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="d-flex flex-column gap-2">
|
||||
{% for claim in claims %}
|
||||
<a href="{{ claim.get_absolute_url }}" class="text-decoration-none">
|
||||
<div class="card claim-card">
|
||||
<div class="card-body d-flex align-items-start gap-2">
|
||||
<span class="badge text-bg-{{ claim.get_status_badge_class }} flex-shrink-0 mt-1">{{ claim.get_status_display }}</span>
|
||||
<span class="text-dark">{{ claim.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{% empty %}
|
||||
<p class="text-muted text-center py-4 mb-0">Keine Behauptungen gefunden.</p>
|
||||
{% endfor %}
|
||||
<div id="category-results">
|
||||
{% include "claims/partials/claim_list.html" %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static claims_extras %}
|
||||
|
||||
{% block title %}{{ claim.title }} – Faktenkompass{% endblock %}
|
||||
{% block meta_description %}{{ claim.short_answer|truncatewords:30 }}{% endblock %}
|
||||
@@ -13,43 +14,42 @@
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<div class="d-flex flex-wrap gap-1 mb-2">
|
||||
<span class="badge text-bg-{{ claim.get_status_badge_class }}">{{ claim.get_status_display }}</span>
|
||||
<span class="badge text-bg-light border text-dark">Evidenz: {{ claim.get_evidence_level_display }}</span>
|
||||
<h1 class="fk-claim-title">{{ claim.title }}</h1>
|
||||
|
||||
<div class="fk-meta-stack mb-3">
|
||||
{% status_badge claim %}
|
||||
{% evidence_badge claim %}
|
||||
</div>
|
||||
|
||||
<h1 class="h5 fw-semibold mb-3">{{ claim.title }}</h1>
|
||||
|
||||
<div class="card mb-3 border-start border-3 border-primary">
|
||||
<div class="card-body py-3">
|
||||
<p class="small text-muted text-uppercase fw-semibold mb-1">Kurzantwort</p>
|
||||
<p class="mb-0">{{ claim.short_answer }}</p>
|
||||
</div>
|
||||
<div class="fk-card fk-answer-card">
|
||||
<p class="fk-section-label mb-2">Kurzantwort</p>
|
||||
<p>{{ claim.short_answer }}</p>
|
||||
</div>
|
||||
|
||||
{% if claim.evidence_files.all %}
|
||||
<section class="mb-3">
|
||||
<p class="small text-muted text-uppercase fw-semibold mb-2">Nachweise</p>
|
||||
<section class="fk-section">
|
||||
<p class="fk-section-label">Nachweise</p>
|
||||
<div class="d-flex flex-column gap-2">
|
||||
{% for evidence in claim.evidence_files.all %}
|
||||
{% if evidence.is_image %}
|
||||
<figure class="card mb-0 evidence-card">
|
||||
<img src="{{ evidence.file.url }}" alt="{{ evidence.caption|default:evidence.display_title }}"
|
||||
class="evidence-img w-100" loading="lazy">
|
||||
{% if evidence.title or evidence.caption %}
|
||||
<figcaption class="card-body py-2 px-3 small">
|
||||
{% if evidence.title %}<strong class="d-block">{{ evidence.title }}</strong>{% endif %}
|
||||
{% if evidence.caption %}<span class="text-muted">{{ evidence.caption }}</span>{% endif %}
|
||||
</figcaption>
|
||||
<figure class="fk-card evidence-card mb-0">
|
||||
<button type="button" class="fk-img-open" data-src="{{ evidence.file.url }}"
|
||||
data-caption="{{ evidence.caption|default:evidence.image_alt }}"
|
||||
aria-label="Grafik im Vollbild anzeigen">
|
||||
<img src="{{ evidence.file.url }}" alt="{{ evidence.image_alt }}"
|
||||
class="evidence-img w-100" loading="lazy">
|
||||
</button>
|
||||
{% if evidence.caption %}
|
||||
<figcaption class="card-body py-2 px-3 small text-muted">{{ evidence.caption }}</figcaption>
|
||||
{% endif %}
|
||||
</figure>
|
||||
{% elif evidence.is_pdf %}
|
||||
<a href="{{ evidence.file.url }}" target="_blank" rel="noopener noreferrer" class="text-decoration-none">
|
||||
<div class="card evidence-card">
|
||||
<div class="fk-card evidence-card">
|
||||
<div class="card-body py-2 px-3 d-flex align-items-center gap-3">
|
||||
<i class="bi bi-file-earmark-pdf text-danger fs-3 flex-shrink-0"></i>
|
||||
<i class="bi bi-file-earmark-pdf fs-3 flex-shrink-0" style="color:#ef4444"></i>
|
||||
<div class="min-w-0">
|
||||
<span class="small fw-medium text-dark d-block">{{ evidence.display_title }}</span>
|
||||
<span class="small fw-medium d-block" style="color:var(--fk-text)">{{ evidence.display_title }}</span>
|
||||
{% if evidence.caption %}<span class="small text-muted">{{ evidence.caption }}</span>{% endif %}
|
||||
<span class="small text-primary">PDF öffnen</span>
|
||||
</div>
|
||||
@@ -63,29 +63,27 @@
|
||||
{% endif %}
|
||||
|
||||
{% if claim.detailed_explanation %}
|
||||
<section class="card mb-3">
|
||||
<div class="card-body py-3">
|
||||
<p class="small text-muted text-uppercase fw-semibold mb-1">Erklärung</p>
|
||||
<div class="small">{{ claim.detailed_explanation|linebreaks }}</div>
|
||||
<section class="fk-section">
|
||||
<p class="fk-section-label">Erklärung</p>
|
||||
<div class="fk-card fk-section-body">
|
||||
{{ claim.detailed_explanation|linebreaks }}
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
{% if claim.sources.all %}
|
||||
<section class="mb-3">
|
||||
<p class="small text-muted text-uppercase fw-semibold mb-2">Quellen</p>
|
||||
<section class="fk-section">
|
||||
<p class="fk-section-label">Quellen</p>
|
||||
<div class="d-flex flex-column gap-2">
|
||||
{% for source in claim.sources.all %}
|
||||
<div class="card">
|
||||
<div class="card-body py-2 px-3">
|
||||
<a href="{{ source.url }}" target="_blank" rel="noopener noreferrer" class="text-decoration-none fw-medium small">
|
||||
{{ source.title }} <i class="bi bi-box-arrow-up-right"></i>
|
||||
</a>
|
||||
<p class="text-muted small mb-0">{{ source.organization }}</p>
|
||||
{% if source.description %}
|
||||
<p class="small mb-0 mt-1">{{ source.description }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="fk-card fk-section-body">
|
||||
<a href="{{ source.url }}" target="_blank" rel="noopener noreferrer" class="fk-source-link">
|
||||
{{ source.title }} <i class="bi bi-box-arrow-up-right"></i>
|
||||
</a>
|
||||
<p class="text-muted small mb-0">{{ source.organization }}</p>
|
||||
{% if source.description %}
|
||||
<p class="small mb-0 mt-1">{{ source.description }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -93,11 +91,11 @@
|
||||
{% endif %}
|
||||
|
||||
{% if claim.counter_arguments.all %}
|
||||
<section class="mb-3">
|
||||
<p class="small text-muted text-uppercase fw-semibold mb-2">Gegenargumente</p>
|
||||
<div class="accordion accordion-flush" id="counterArgs">
|
||||
<section class="fk-section">
|
||||
<p class="fk-section-label">Gegenargumente</p>
|
||||
<div class="accordion fk-accordion" id="counterArgs">
|
||||
{% for ca in claim.counter_arguments.all %}
|
||||
<div class="accordion-item border mb-1 rounded">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed py-2 small" type="button"
|
||||
data-bs-toggle="collapse" data-bs-target="#collapse{{ forloop.counter }}">
|
||||
@@ -106,7 +104,7 @@
|
||||
</h2>
|
||||
<div id="collapse{{ forloop.counter }}" class="accordion-collapse collapse"
|
||||
data-bs-parent="#counterArgs">
|
||||
<div class="accordion-body py-2 small text-muted">
|
||||
<div class="accordion-body py-2">
|
||||
{{ ca.response|linebreaks }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -119,22 +117,22 @@
|
||||
{% if claim.tags.all %}
|
||||
<div class="d-flex flex-wrap gap-1">
|
||||
{% for tag in claim.tags.all %}
|
||||
<span class="badge text-bg-light border text-secondary">#{{ tag.name }}</span>
|
||||
<span class="fk-tag">#{{ tag.name }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</article>
|
||||
|
||||
{% if related_claims %}
|
||||
<aside class="mt-4 pt-3 border-top">
|
||||
<p class="small text-muted text-uppercase fw-semibold mb-2">Mehr zu {{ claim.category.name }}</p>
|
||||
<aside class="mt-4 pt-4" style="border-top:1px solid var(--fk-border)">
|
||||
<p class="fk-section-label">Mehr zu {{ claim.category.name }}</p>
|
||||
<div class="d-flex flex-column gap-2">
|
||||
{% for related in related_claims %}
|
||||
<a href="{{ related.get_absolute_url }}" class="text-decoration-none">
|
||||
<div class="card claim-card">
|
||||
<div class="card-body py-2 d-flex align-items-start gap-2">
|
||||
<span class="badge text-bg-{{ related.get_status_badge_class }} flex-shrink-0">{{ related.get_status_display }}</span>
|
||||
<span class="small text-dark">{{ related.title }}</span>
|
||||
<div class="claim-card">
|
||||
<div class="card-body py-2">
|
||||
<div class="mb-1">{% status_badge related %}</div>
|
||||
<span class="claim-title small d-block">{{ related.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
@@ -142,4 +140,17 @@
|
||||
</div>
|
||||
</aside>
|
||||
{% endif %}
|
||||
|
||||
<div id="fk-lightbox" class="fk-lightbox" hidden role="dialog" aria-modal="true" aria-label="Grafik Vollbild">
|
||||
<button type="button" class="fk-lightbox-backdrop" aria-label="Schließen"></button>
|
||||
<button type="button" class="fk-lightbox-close" aria-label="Schließen">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
<img class="fk-lightbox-img" src="" alt="">
|
||||
<p class="fk-lightbox-caption"></p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script src="{% static 'js/image-lightbox.js' %}" defer></script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
{% block title %}Faktenkompass{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<header class="page-header mb-3">
|
||||
<h1 class="h4 mb-3">Themen</h1>
|
||||
<form method="get" action="{% url 'claims:home' %}" class="mb-2">
|
||||
<header class="mb-4">
|
||||
<h1 class="fk-page-title">Themen</h1>
|
||||
<form method="get" action="{% url 'claims:home' %}" class="mb-2" data-live-search data-live-target="#home-results">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-white border-end-0"><i class="bi bi-search text-muted"></i></span>
|
||||
<input type="search" name="q" class="form-control border-start-0 ps-0" placeholder="Suchen…" value="{{ query }}" aria-label="Kategorien durchsuchen">
|
||||
<span class="input-group-text border-end-0"><i class="bi bi-search"></i></span>
|
||||
<input type="search" name="q" class="form-control border-start-0 ps-0" placeholder="Suchen…" value="{{ query }}" aria-label="Kategorien durchsuchen" autocomplete="off">
|
||||
</div>
|
||||
</form>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
@@ -17,31 +17,7 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{% if query and not categories %}
|
||||
<p class="text-muted small mb-3">Keine Kategorien für „{{ query }}".</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="row g-2 g-sm-3">
|
||||
{% for category in categories %}
|
||||
<div class="col-6 col-md-4 col-lg-3">
|
||||
<a href="{{ category.get_absolute_url }}" class="text-decoration-none d-block h-100">
|
||||
<div class="card category-tile h-100 text-center">
|
||||
<div class="card-body d-flex flex-column align-items-center justify-content-center py-3 py-sm-4">
|
||||
<div class="category-tile-icon mb-2">
|
||||
<i class="bi {{ category.icon|default:'bi-book' }}"></i>
|
||||
</div>
|
||||
<h2 class="h6 card-title text-dark mb-0">{{ category.name }}</h2>
|
||||
<span class="small text-muted mt-1">{{ category.claim_count }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{% empty %}
|
||||
{% if not query %}
|
||||
<div class="col-12">
|
||||
<p class="text-muted text-center py-5 mb-0">Noch keine Kategorien vorhanden.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<div id="home-results">
|
||||
{% include "claims/partials/category_list.html" %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
{% if query and not categories %}
|
||||
<p class="text-muted small mb-3">Keine Kategorien für „{{ query }}".</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="row g-2 g-sm-3">
|
||||
{% for category in categories %}
|
||||
<div class="col-6 col-md-4 col-lg-3">
|
||||
<a href="{{ category.get_absolute_url }}" class="text-decoration-none d-block h-100">
|
||||
<div class="category-tile h-100 text-center">
|
||||
<div class="card-body d-flex flex-column align-items-center justify-content-center py-3 py-sm-4">
|
||||
<div class="category-tile-icon mb-2">
|
||||
<i class="bi {{ category.icon|default:'bi-book' }}"></i>
|
||||
</div>
|
||||
<h2 class="h6 card-title mb-0">{{ category.name }}</h2>
|
||||
<span class="small text-muted mt-1">{{ category.claim_count }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{% empty %}
|
||||
{% if not query %}
|
||||
<div class="col-12">
|
||||
<p class="text-muted text-center py-5 mb-0">Noch keine Kategorien vorhanden.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
{% load claims_extras %}
|
||||
<div class="d-flex flex-column gap-2">
|
||||
{% for claim in claims %}
|
||||
<a href="{{ claim.get_absolute_url }}" class="text-decoration-none">
|
||||
<div class="claim-card">
|
||||
<div class="card-body">
|
||||
{% if show_category %}
|
||||
<div class="mb-1">
|
||||
<span class="fk-topic">{{ claim.category.name }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="mb-1">{% status_badge claim %}</div>
|
||||
<span class="claim-title d-block">{{ claim.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{% empty %}
|
||||
<p class="text-muted text-center py-4 mb-0">{{ empty_message|default:"Keine Behauptungen gefunden." }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -0,0 +1,4 @@
|
||||
<span class="fk-evidence">
|
||||
<i class="bi bi-bar-chart" aria-hidden="true"></i>
|
||||
Evidenz: {{ claim.get_evidence_level_display }}
|
||||
</span>
|
||||
@@ -0,0 +1,12 @@
|
||||
{% if claims is not None %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span class="small text-muted">{{ result_count }} Ergebnis{% if result_count != 1 %}se{% endif %}</span>
|
||||
{% if query or category_slug or status %}
|
||||
<a href="{% url 'claims:search' %}" class="small text-muted text-decoration-none">Zurücksetzen</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% include "claims/partials/claim_list.html" with claims=claims show_category=True empty_message="Keine Ergebnisse." %}
|
||||
{% else %}
|
||||
<p class="text-muted text-center py-4 mb-0 small">Suchbegriff oder Filter eingeben…</p>
|
||||
{% endif %}
|
||||
@@ -0,0 +1,4 @@
|
||||
<span class="fk-status fk-status--{{ claim.status }}">
|
||||
<span class="fk-status-dot" aria-hidden="true"></span>
|
||||
{{ claim.get_status_display }}
|
||||
</span>
|
||||
@@ -3,12 +3,12 @@
|
||||
{% block title %}Suche – Faktenkompass{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="h5 mb-3">Suche</h1>
|
||||
<h1 class="fk-page-title">Suche</h1>
|
||||
|
||||
<form method="get" class="row g-2 mb-3">
|
||||
<form method="get" action="{% url 'claims:search' %}" class="row g-2 mb-4" data-live-search data-live-target="#search-results">
|
||||
<div class="col-12">
|
||||
<input type="search" id="q" name="q" class="form-control form-control-sm"
|
||||
placeholder="Behauptung suchen…" value="{{ query }}" autofocus>
|
||||
placeholder="Behauptung suchen…" value="{{ query }}" autofocus autocomplete="off">
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<select id="kategorie" name="kategorie" class="form-select form-select-sm">
|
||||
@@ -26,35 +26,9 @@
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<button type="submit" class="btn btn-primary btn-sm w-100">Suchen</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% if claims is not None %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span class="small text-muted">{{ result_count }} Ergebnis{% if result_count != 1 %}se{% endif %}</span>
|
||||
{% if query or category_slug or status %}
|
||||
<a href="{% url 'claims:search' %}" class="small text-muted">Zurücksetzen</a>
|
||||
{% endif %}
|
||||
<div id="search-results">
|
||||
{% include "claims/partials/search_results.html" %}
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-column gap-2">
|
||||
{% for claim in claims %}
|
||||
<a href="{{ claim.get_absolute_url }}" class="text-decoration-none">
|
||||
<div class="card claim-card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex flex-wrap gap-1 mb-1">
|
||||
<span class="badge text-bg-secondary">{{ claim.category.name }}</span>
|
||||
<span class="badge text-bg-{{ claim.get_status_badge_class }}">{{ claim.get_status_display }}</span>
|
||||
</div>
|
||||
<span class="text-dark">{{ claim.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{% empty %}
|
||||
<p class="text-muted text-center py-4 mb-0">Keine Ergebnisse.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user