synops/tools/synops-render/src/templates/search.html
vegard 17e35b2644 Implementer synops-render CLI-verktøy (oppgave 21.3)
Nytt CLI-verktøy som rendrer artikler og forsider til HTML via
Tera-templates og lagrer resultatet i CAS. Erstatter rendering-logikken
i maskinrommet/src/publishing.rs som standalone verktøy.

Støtter to render-typer:
- article: Rendrer enkeltartikkel med SEO-metadata, presentasjonselementer,
  TipTap→HTML-konvertering, og tema-basert CSS.
- index: Rendrer forside med hero/featured/stream-artikler.

Fire innebygde temaer: avis, magasin, blogg, tidsskrift.
Templates er kopiert fra maskinrommet og innebygd via include_str!().
TipTap-modulen er duplisert inntil synops-common (21.16) samler felles kode.

Følger eksisterende CLI-mønster: --write gater DB-oppdateringer,
JSON til stdout, stderr for logging.

16 enhetstester dekker CSS-variabler, SEO, kategorisering, rendering
og TipTap-konvertering. Verifisert mot produksjons-DB.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 09:19:02 +00:00

162 lines
4.2 KiB
HTML

{% extends "base.html" %}
{% block title %}{% if query %}Søk: {{ query }} — {% endif %}{{ collection_title }}{% endblock %}
{% block extra_css %}
.dynamic-page {
max-width: var(--layout-max-width);
margin: 2rem auto;
padding: 0 1rem;
}
.dynamic-page__header {
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 2px solid var(--color-accent);
}
.dynamic-page__title {
font-family: var(--font-heading);
font-size: 2rem;
color: var(--color-primary);
}
.search-form {
margin-top: 1rem;
display: flex;
gap: 0.5rem;
}
.search-form__input {
flex: 1;
padding: 0.75rem 1rem;
border: 2px solid #e0e0e0;
border-radius: 6px;
font-size: 1rem;
font-family: var(--font-body);
color: var(--color-text);
background: var(--color-background);
}
.search-form__input:focus {
border-color: var(--color-accent);
outline: none;
}
.search-form__button {
padding: 0.75rem 1.5rem;
background: var(--color-accent);
color: #fff;
border: none;
border-radius: 6px;
font-size: 1rem;
cursor: pointer;
}
.search-form__button:hover { opacity: 0.9; }
.search-results__info {
color: var(--color-muted);
margin-bottom: 1rem;
font-size: 0.9rem;
}
.article-list { list-style: none; }
.article-list__item {
padding: 1.25rem 0;
border-bottom: 1px solid #f0f0f0;
}
.article-list__item:first-child { padding-top: 0; }
.article-list__title {
font-family: var(--font-heading);
font-size: 1.35rem;
color: var(--color-primary);
line-height: 1.3;
margin-bottom: 0.25rem;
}
.article-list__meta {
font-size: 0.8rem;
color: var(--color-muted);
margin-bottom: 0.4rem;
}
.article-list__summary {
font-size: 0.95rem;
line-height: 1.5;
}
.article-list__highlight {
background: rgba(233, 69, 96, 0.1);
padding: 0 0.15rem;
border-radius: 2px;
}
.pagination {
display: flex;
gap: 0.5rem;
justify-content: center;
margin-top: 2rem;
padding-top: 1rem;
border-top: 1px solid #f0f0f0;
}
.pagination a, .pagination span {
padding: 0.5rem 1rem;
border: 1px solid #e0e0e0;
border-radius: 4px;
font-size: 0.9rem;
}
.pagination .current {
background: var(--color-accent);
color: #fff;
border-color: var(--color-accent);
}
.empty-state {
text-align: center;
padding: 3rem 1rem;
color: var(--color-muted);
}
@media (max-width: 768px) {
.dynamic-page__title { font-size: 1.5rem; }
.article-list__title { font-size: 1.15rem; }
.search-form { flex-direction: column; }
}
{% endblock %}
{% block content %}
<div class="dynamic-page">
<div class="dynamic-page__header">
<h1 class="dynamic-page__title">Søk</h1>
<form class="search-form" method="get" action="{{ base_url }}/sok">
<input class="search-form__input" type="text" name="q" value="{{ query | default(value='') }}" placeholder="Søk i artikler..." autocomplete="off">
<button class="search-form__button" type="submit">Søk</button>
</form>
</div>
{% if query %}
{% if articles | length > 0 %}
<p class="search-results__info">{{ result_count }} treff for &laquo;{{ query }}&raquo;</p>
<ul class="article-list">
{% for item in articles %}
<li class="article-list__item">
<h2 class="article-list__title"><a href="{{ base_url }}/{{ item.short_id }}">{{ item.title }}</a></h2>
<div class="article-list__meta">{{ item.published_at_short }}</div>
{% if item.summary %}
<p class="article-list__summary">{{ item.summary }}</p>
{% endif %}
</li>
{% endfor %}
</ul>
{% if total_pages > 1 %}
<nav class="pagination">
{% if current_page > 1 %}
<a href="{{ base_url }}/sok?q={{ query | urlencode }}&side={{ current_page - 1 }}">Forrige</a>
{% endif %}
{% for p in page_range %}
{% if p == current_page %}
<span class="current">{{ p }}</span>
{% else %}
<a href="{{ base_url }}/sok?q={{ query | urlencode }}&side={{ p }}">{{ p }}</a>
{% endif %}
{% endfor %}
{% if current_page < total_pages %}
<a href="{{ base_url }}/sok?q={{ query | urlencode }}&side={{ current_page + 1 }}">Neste</a>
{% endif %}
</nav>
{% endif %}
{% else %}
<div class="empty-state">Ingen treff for &laquo;{{ query }}&raquo;.</div>
{% endif %}
{% else %}
<div class="empty-state">Skriv inn et søkeord for å søke i artiklene.</div>
{% endif %}
</div>
{% endblock %}