Utdrag på forsiden og forenklet favicon

Forsiden viser første avsnitt av hvert innlegg som ren tekst (nytt
Jinja-filter `excerpt`, tilgjengelig for egne temaer). Favicon har
færre og større former med tykkere streker så det holder seg ved 16 px.
Siste to punkter fra UI-gjennomgangen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcEy43fNYpwg6K6oTKakWR
This commit is contained in:
Ole-Morten Duesund 2026-08-26 14:14:25 +02:00
commit 50722bf7c2
8 changed files with 35 additions and 29 deletions

View file

@ -107,7 +107,7 @@ cp soapbox/themes/green/static/style.css data/content/themes/skog/static/ # re
Et tema kan være **delvis**: bare filene som finnes overstyrer det innebygde `green`-temaet.
Vil du endre malene, legg `templates/base.html`, `index.html` eller `post.html` i temamappen.
Malene får `site`, `posts`/`post`, `html`, `source_url` og `rel(sti)` (relativ lenke fra
gjeldende side). `static/` havner under `/theme/`.
gjeldende side), samt filtrene `date` og `excerpt(n)`. `static/` havner under `/theme/`.
## Lisens

View file

@ -36,6 +36,7 @@ def _env(cfg: Config) -> Environment:
autoescape=select_autoescape(["html"]),
)
env.filters["date"] = lambda d, fmt="%d.%m.%Y": d.strftime(fmt)
env.filters["excerpt"] = excerpt
return env

View file

@ -12,6 +12,7 @@ interne lenker som forfatteren skriver (``/annet-innlegg/``) skrives om til
from __future__ import annotations
import posixpath
import re
from urllib.parse import urlsplit
from markdown_it import MarkdownIt
@ -69,5 +70,10 @@ def excerpt(text: str, limit: int = 300) -> str:
para = para.strip()
if para and not para.startswith(("#", "!", "```", "|")):
plain = " ".join(para.split())
# Fjern den vanligste inline-markeringen så utdraget leses som ren tekst
plain = re.sub(r"!\[[^\]]*\]\([^)]*\)", "", plain) # bilder
plain = re.sub(r"\[([^\]]+)\]\([^)]*\)", r"\1", plain) # lenker → tekst
plain = re.sub(r"[*_`~]{1,3}", "", plain)
plain = " ".join(plain.split()) # doble mellomrom etter fjernede bilder
return plain if len(plain) <= limit else plain[:limit].rsplit(" ", 1)[0] + ""
return ""

View file

@ -1,16 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<!-- Såpeboks: en trekasse man kan stå på, med et såpestykke oppå. -->
<!-- Kassen -->
<rect x="8" y="34" width="48" height="24" rx="2" fill="#1f6b3a"/>
<rect x="12" y="38" width="40" height="16" fill="none" stroke="#7fd39a" stroke-width="2"/>
<!-- Planker -->
<line x1="12" y1="46" x2="52" y2="46" stroke="#7fd39a" stroke-width="2"/>
<line x1="32" y1="38" x2="32" y2="54" stroke="#7fd39a" stroke-width="2"/>
<!-- Såpestykke -->
<rect x="17" y="21" width="30" height="13" rx="6" fill="#e8f5ec" stroke="#154d29" stroke-width="2"/>
<ellipse cx="27" cy="26" rx="5" ry="2" fill="#fff" opacity=".8"/>
<!-- Bobler -->
<circle cx="48" cy="14" r="4" fill="none" stroke="#7fd39a" stroke-width="2"/>
<circle cx="40" cy="8" r="2.5" fill="none" stroke="#7fd39a" stroke-width="2"/>
<circle cx="54" cy="6" r="1.5" fill="#7fd39a"/>
<!-- Såpeboks: en trekasse man kan stå på, med et såpestykke oppå.
Få, store former og tykke streker så ikonet holder seg ved 16 px. -->
<rect x="6" y="34" width="52" height="26" rx="3" fill="#1f6b3a"/>
<line x1="6" y1="47" x2="58" y2="47" stroke="#7fd39a" stroke-width="3"/>
<rect x="15" y="19" width="34" height="15" rx="7" fill="#e8f5ec" stroke="#154d29" stroke-width="3"/>
<circle cx="50" cy="11" r="5" fill="none" stroke="#7fd39a" stroke-width="3"/>
<circle cx="39" cy="7" r="2.5" fill="#7fd39a"/>
</svg>

Before

Width:  |  Height:  |  Size: 919 B

After

Width:  |  Height:  |  Size: 597 B

Before After
Before After

View file

@ -1,16 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<!-- Såpeboks: en trekasse man kan stå på, med et såpestykke oppå. -->
<!-- Kassen -->
<rect x="8" y="34" width="48" height="24" rx="2" fill="#1f6b3a"/>
<rect x="12" y="38" width="40" height="16" fill="none" stroke="#7fd39a" stroke-width="2"/>
<!-- Planker -->
<line x1="12" y1="46" x2="52" y2="46" stroke="#7fd39a" stroke-width="2"/>
<line x1="32" y1="38" x2="32" y2="54" stroke="#7fd39a" stroke-width="2"/>
<!-- Såpestykke -->
<rect x="17" y="21" width="30" height="13" rx="6" fill="#e8f5ec" stroke="#154d29" stroke-width="2"/>
<ellipse cx="27" cy="26" rx="5" ry="2" fill="#fff" opacity=".8"/>
<!-- Bobler -->
<circle cx="48" cy="14" r="4" fill="none" stroke="#7fd39a" stroke-width="2"/>
<circle cx="40" cy="8" r="2.5" fill="none" stroke="#7fd39a" stroke-width="2"/>
<circle cx="54" cy="6" r="1.5" fill="#7fd39a"/>
<!-- Såpeboks: en trekasse man kan stå på, med et såpestykke oppå.
Få, store former og tykke streker så ikonet holder seg ved 16 px. -->
<rect x="6" y="34" width="52" height="26" rx="3" fill="#1f6b3a"/>
<line x1="6" y1="47" x2="58" y2="47" stroke="#7fd39a" stroke-width="3"/>
<rect x="15" y="19" width="34" height="15" rx="7" fill="#e8f5ec" stroke="#154d29" stroke-width="3"/>
<circle cx="50" cy="11" r="5" fill="none" stroke="#7fd39a" stroke-width="3"/>
<circle cx="39" cy="7" r="2.5" fill="#7fd39a"/>
</svg>

Before

Width:  |  Height:  |  Size: 919 B

After

Width:  |  Height:  |  Size: 597 B

Before After
Before After

View file

@ -80,6 +80,7 @@ h2 { font-size: 1.4rem; line-height: 1.25; margin: 2rem 0 .5rem; }
.summary h2 a { color: var(--accent-strong); text-decoration: none; }
.summary h2 a:hover { text-decoration: underline; }
.summary .meta { margin: 0; }
.summary .excerpt { margin: .5rem 0 0; color: var(--fg); }
.body p, .body ul, .body ol { margin: 0 0 1.1rem; }
.body img { max-width: 100%; height: auto; display: block; margin: 1.5rem 0; border-radius: 4px; }
.body pre { background: var(--code-bg); padding: 1rem; overflow-x: auto; border-radius: 4px; }

View file

@ -10,6 +10,7 @@
<time datetime="{{ post.date.isoformat() }}">{{ post.date|date }}</time>
{% if post.guest %} · <a href="{{ rel(post.guest ~ '/') }}">{{ post.author or post.guest }}</a>{% endif %}
</p>
{% set text = post.body|excerpt(220) %}{% if text %}<p class="excerpt">{{ text }}</p>{% endif %}
</article>
{% endfor %}
{% endblock %}

View file

@ -57,3 +57,14 @@ def test_password_hashing(tmp_path):
assert db.authenticate(conn, "a", "feil") is None
assert db.has_owner(conn)
assert datetime.now(UTC) # sanity: UTC alias tilgjengelig
def test_excerpt_plain_text():
from soapbox.render import excerpt
assert (
excerpt("Hei **verden**. Se [forsiden](/) og ![alt](x.jpg) _her_.")
== "Hei verden. Se forsiden og her."
)
assert excerpt("# Overskrift\n\n```\nkode\n```\n\nTekst") == "Tekst"
assert excerpt("x " * 200, 50).endswith("")