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>
162 lines
4.2 KiB
HTML
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 «{{ query }}»</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 «{{ query }}».</div>
|
|
{% endif %}
|
|
{% else %}
|
|
<div class="empty-state">Skriv inn et søkeord for å søke i artiklene.</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endblock %}
|