Translate the whole site to English: English routes with 301 redirects from the Dutch paths, English UI, docs, seeds and captions, one-time live content migration
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!-- retoor <retoor@molodetz.nl> -->
|
||||
<a class="presence-host" href="/mensen/{{ _user.username }}" aria-label="{{ _user.username }}">
|
||||
<a class="presence-host" href="/people/{{ _user.username }}" aria-label="{{ _user.username }}">
|
||||
<img class="avatar" src="{{ avatar_url(_user, _size or 40) }}" width="{{ _size or 40 }}" height="{{ _size or 40 }}" alt="" loading="lazy">
|
||||
<span class="presence-dot{% if is_online(_user.uid) %} online{% endif %}" data-presence-uid="{{ _user.uid }}"></span>
|
||||
</a>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- retoor <retoor@molodetz.nl> -->
|
||||
<nav class="breadcrumbs" aria-label="Kruimelpad">
|
||||
<nav class="breadcrumbs" aria-label="Breadcrumb">
|
||||
{% for name, path in seo.breadcrumbs %}
|
||||
{% if not loop.last %}<a href="{{ path }}">{{ name }}</a><span aria-hidden="true">/</span>{% else %}<span aria-current="page">{{ name }}</span>{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
<div class="post-meta">
|
||||
<span class="label">{{ TOPICS.get(_post.topic, _post.topic) }}</span>
|
||||
{{ local_dt(_post.published_at or _post.created_at, 'date') }}
|
||||
{% if _post.is_placeholder %}<span class="tag-placeholder" title="Overgenomen tekst; wordt vervangen door een eigen verhaal">Placeholder</span>{% endif %}
|
||||
{% if _post.status != 'published' %}<span class="tag-placeholder tag-draft">Concept</span>{% endif %}
|
||||
{% if _post.is_placeholder %}<span class="tag-placeholder" title="Carried over text; to be replaced with an original story">Placeholder</span>{% endif %}
|
||||
{% if _post.status != 'published' %}<span class="tag-placeholder tag-draft">Draft</span>{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!-- retoor <retoor@molodetz.nl> -->
|
||||
{% if _next_url %}
|
||||
<div class="load-more"><a class="button ghost" href="{{ _next_url }}" rel="next">Oudere notities</a></div>
|
||||
<div class="load-more"><a class="button ghost" href="{{ _next_url }}" rel="next">Older notes</a></div>
|
||||
{% endif %}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{% macro modal(id, title, wide=False) %}
|
||||
<div class="modal-overlay" id="{{ id }}" role="dialog" aria-modal="true" aria-labelledby="{{ id }}-title">
|
||||
<div class="modal{% if wide %} wide{% endif %}">
|
||||
<header><h2 id="{{ id }}-title">{{ title }}</h2><button class="modal-close" type="button" aria-label="Sluiten">×</button></header>
|
||||
<header><h2 id="{{ id }}-title">{{ title }}</h2><button class="modal-close" type="button" aria-label="Close">×</button></header>
|
||||
{{ caller() }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<!-- retoor <retoor@molodetz.nl> -->
|
||||
{% if _pages and _pages.pages > 1 %}
|
||||
<nav class="actions" aria-label="Paginering">
|
||||
{% if _pages.has_prev %}<a class="button ghost small" href="?page={{ _pages.page - 1 }}" rel="prev">Vorige</a>{% endif %}
|
||||
<nav class="actions" aria-label="Pagination">
|
||||
{% if _pages.has_prev %}<a class="button ghost small" href="?page={{ _pages.page - 1 }}" rel="prev">Previous</a>{% endif %}
|
||||
<span class="muted">{{ _pages.page }} / {{ _pages.pages }}</span>
|
||||
{% if _pages.has_next %}<a class="button ghost small" href="?page={{ _pages.page + 1 }}" rel="next">Volgende</a>{% endif %}
|
||||
{% if _pages.has_next %}<a class="button ghost small" href="?page={{ _pages.page + 1 }}" rel="next">Next</a>{% endif %}
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- retoor <retoor@molodetz.nl> -->
|
||||
<form class="field" method="get" action="/docs/search" role="search">
|
||||
<label for="docs-q">Zoeken</label>
|
||||
<input id="docs-q" type="search" name="q" value="{{ query or '' }}" placeholder="bijv. sessie">
|
||||
<label for="docs-q">Search</label>
|
||||
<input id="docs-q" type="search" name="q" value="{{ query or '' }}" placeholder="e.g. session">
|
||||
</form>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!-- retoor <retoor@molodetz.nl> -->
|
||||
<div class="field">
|
||||
<label for="topic">Onderwerp</label>
|
||||
<label for="topic">Topic</label>
|
||||
<select id="topic" name="topic">
|
||||
{% for key, label in TOPICS.items() %}
|
||||
<option value="{{ key }}" {% if _selected == key %}selected{% endif %}>{{ label }}</option>
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
<!-- retoor <retoor@molodetz.nl> -->
|
||||
<a class="user-link" href="/mensen/{{ _user.username }}">{{ _user.username }}</a>
|
||||
<a class="user-link" href="/people/{{ _user.username }}">{{ _user.username }}</a>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<!-- retoor <retoor@molodetz.nl> -->
|
||||
{% extends "admin/base_admin.html" %}
|
||||
{% block admin_content %}
|
||||
<h1>Auditlog</h1>
|
||||
<h1>Audit log</h1>
|
||||
<div class="table-wrap">
|
||||
<table class="admin-table">
|
||||
<thead><tr><th>Tijd</th><th>Gebeurtenis</th><th>Categorie</th><th>Actor</th><th>Resultaat</th></tr></thead>
|
||||
<thead><tr><th>Time</th><th>Event</th><th>Category</th><th>Actor</th><th>Result</th></tr></thead>
|
||||
<tbody>
|
||||
{% for row in rows %}
|
||||
<tr><td>{{ local_dt(row.created_at) }}</td><td><code>{{ row.event_key }}</code></td><td><a href="?category={{ row.category }}">{{ row.category }}</a></td><td>{{ row.actor_kind }}</td><td><span class="status {{ 'failed' if row.result == 'denied' else 'done' }}">{{ row.result }}</span></td></tr>
|
||||
{% else %}
|
||||
<tr><td colspan="5" class="muted">Nog geen regels.</td></tr>
|
||||
<tr><td colspan="5" class="muted">No rows yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<!-- retoor <retoor@molodetz.nl> -->
|
||||
{% extends "admin/base_admin.html" %}
|
||||
{% block admin_content %}
|
||||
<h1>Back-ups</h1>
|
||||
<h1>Backups</h1>
|
||||
<form class="actions" method="post" action="/admin/backups">
|
||||
<select name="target" aria-label="Doel">{% for target in ['database', 'uploads', 'keys', 'full'] %}<option value="{{ target }}">{{ target }}</option>{% endfor %}</select>
|
||||
<button class="button" type="submit">Back-up inplannen</button>
|
||||
<select name="target" aria-label="Target">{% for target in ['database', 'uploads', 'keys', 'full'] %}<option value="{{ target }}">{{ target }}</option>{% endfor %}</select>
|
||||
<button class="button" type="submit">Schedule backup</button>
|
||||
</form>
|
||||
<div class="table-wrap">
|
||||
<table class="admin-table">
|
||||
<thead><tr><th>Doel</th><th>Status</th><th>Grootte</th><th>Aangemaakt</th><th></th></tr></thead>
|
||||
<thead><tr><th>Target</th><th>Status</th><th>Size</th><th>Created</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{% for row in backups %}
|
||||
<tr>
|
||||
@@ -19,7 +19,7 @@
|
||||
<td>{% if can_download and row.status == 'done' %}<a class="button ghost small" href="/admin/backups/{{ row.uid }}/download">Download</a>{% endif %}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="5" class="muted">Nog geen back-ups.</td></tr>
|
||||
<tr><td colspan="5" class="muted">No backups yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -4,19 +4,19 @@
|
||||
{% block extra_head %}<link rel="stylesheet" href="{{ static_url('/static/css/admin.css') }}">{% block admin_head %}{% endblock %}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="admin-layout">
|
||||
<nav class="admin-nav" aria-label="Beheer">
|
||||
<a href="/admin" class="{{ nav_active(request, '/admin', exact=True) }}">Overzicht</a>
|
||||
<a href="/admin/posts" class="{{ nav_active(request, '/admin/posts') }}">Berichten</a>
|
||||
<a href="/admin/joins" class="{{ nav_active(request, '/admin/joins') }}">Aanmeldingen</a>
|
||||
<nav class="admin-nav" aria-label="Admin">
|
||||
<a href="/admin" class="{{ nav_active(request, '/admin', exact=True) }}">Overview</a>
|
||||
<a href="/admin/posts" class="{{ nav_active(request, '/admin/posts') }}">Posts</a>
|
||||
<a href="/admin/joins" class="{{ nav_active(request, '/admin/joins') }}">Join requests</a>
|
||||
<a href="/admin/services" class="{{ nav_active(request, '/admin/services') }}">Services</a>
|
||||
<a href="/admin/backups" class="{{ nav_active(request, '/admin/backups') }}">Back-ups</a>
|
||||
<a href="/admin/trash" class="{{ nav_active(request, '/admin/trash') }}">Prullenbak</a>
|
||||
<a href="/admin/backups" class="{{ nav_active(request, '/admin/backups') }}">Backups</a>
|
||||
<a href="/admin/trash" class="{{ nav_active(request, '/admin/trash') }}">Trash</a>
|
||||
<a href="/admin/users" class="{{ nav_active(request, '/admin/users') }}">Accounts</a>
|
||||
<a href="/admin/settings" class="{{ nav_active(request, '/admin/settings') }}">Instellingen</a>
|
||||
<a href="/admin/audit" class="{{ nav_active(request, '/admin/audit') }}">Auditlog</a>
|
||||
<a href="/admin/stats" class="{{ nav_active(request, '/admin/stats') }}">Statistieken</a>
|
||||
<a href="/profile/api-key">API-sleutel</a>
|
||||
<a href="/docs/beheer">Docs</a>
|
||||
<a href="/admin/settings" class="{{ nav_active(request, '/admin/settings') }}">Settings</a>
|
||||
<a href="/admin/audit" class="{{ nav_active(request, '/admin/audit') }}">Audit log</a>
|
||||
<a href="/admin/stats" class="{{ nav_active(request, '/admin/stats') }}">Statistics</a>
|
||||
<a href="/profile/api-key">API key</a>
|
||||
<a href="/docs/admin">Docs</a>
|
||||
</nav>
|
||||
<div class="admin-main">
|
||||
{% block admin_content %}{% endblock %}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<!-- retoor <retoor@molodetz.nl> -->
|
||||
{% extends "admin/base_admin.html" %}
|
||||
{% block admin_content %}
|
||||
<h1>Beheer</h1>
|
||||
<h1>Admin</h1>
|
||||
<div class="stat-cards">
|
||||
<div class="stat-card"><span class="value">{{ post_count }}</span><span class="name">Berichten</span></div>
|
||||
<div class="stat-card"><span class="value">{{ open_joins }}</span><span class="name">Open aanmeldingen</span></div>
|
||||
<div class="stat-card"><span class="value">{{ post_count }}</span><span class="name">Posts</span></div>
|
||||
<div class="stat-card"><span class="value">{{ open_joins }}</span><span class="name">Open join requests</span></div>
|
||||
<div class="stat-card"><span class="value">{{ user_count }}</span><span class="name">Accounts</span></div>
|
||||
<div class="stat-card"><span class="value">{{ deleted_count }}</span><span class="name">In prullenbak</span></div>
|
||||
<div class="stat-card"><span class="value">{{ deleted_count }}</span><span class="name">In trash</span></div>
|
||||
</div>
|
||||
<div class="actions"><a class="button" href="/admin/posts/new">Nieuw bericht</a><a class="button ghost" href="/admin/joins">Aanmeldingen bekijken</a></div>
|
||||
<div class="actions"><a class="button" href="/admin/posts/new">New post</a><a class="button ghost" href="/admin/joins">View join requests</a></div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
<!-- retoor <retoor@molodetz.nl> -->
|
||||
{% extends "admin/base_admin.html" %}
|
||||
{% block admin_content %}
|
||||
<h1>Aanmeldingen</h1>
|
||||
<h1>Join requests</h1>
|
||||
<div class="table-wrap">
|
||||
<table class="admin-table">
|
||||
<thead><tr><th>Wie</th><th>Contact</th><th>Werk</th><th>Bericht</th><th>Status</th><th>Ontvangen</th><th></th></tr></thead>
|
||||
<thead><tr><th>Who</th><th>Contact</th><th>Work</th><th>Message</th><th>Status</th><th>Received</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{% for item in requests %}
|
||||
<tr>
|
||||
<td>{{ item.name }}</td>
|
||||
<td>{{ item.contact }}</td>
|
||||
<td>{% if item.repo_url %}<a href="{{ item.repo_url }}" rel="nofollow noopener" target="_blank">{{ item.repo_url }}</a>{% if item.link_status %}<br><span class="muted">{{ item.link_status }}{% if item.link_title %}: {{ item.link_title }}{% endif %}</span>{% endif %}{% else %}<span class="muted">geen</span>{% endif %}</td>
|
||||
<td>{% if item.repo_url %}<a href="{{ item.repo_url }}" rel="nofollow noopener" target="_blank">{{ item.repo_url }}</a>{% if item.link_status %}<br><span class="muted">{{ item.link_status }}{% if item.link_title %}: {{ item.link_title }}{% endif %}</span>{% endif %}{% else %}<span class="muted">none</span>{% endif %}</td>
|
||||
<td>{{ item.message }}</td>
|
||||
<td><span class="status {{ item.status }}">{{ item.status }}</span></td>
|
||||
<td>{{ local_dt(item.created_at) }}</td>
|
||||
<td class="actions">
|
||||
<form method="post" action="/admin/joins/{{ item.uid }}/status">
|
||||
<select name="status" aria-label="Status">{% for value in ['open', 'contacted', 'accepted', 'declined'] %}<option value="{{ value }}" {% if item.status == value %}selected{% endif %}>{{ value }}</option>{% endfor %}</select>
|
||||
<button class="button ghost small" type="submit">Zet</button>
|
||||
<button class="button ghost small" type="submit">Set</button>
|
||||
</form>
|
||||
{% if item.repo_url %}<form method="post" action="/admin/joins/{{ item.uid }}/check"><button class="button ghost small" type="submit" data-loading>Link controleren</button></form>{% endif %}
|
||||
{% if item.repo_url %}<form method="post" action="/admin/joins/{{ item.uid }}/check"><button class="button ghost small" type="submit" data-loading>Check link</button></form>{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="7" class="muted">Nog geen aanmeldingen.</td></tr>
|
||||
<tr><td colspan="7" class="muted">No join requests yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
<!-- retoor <retoor@molodetz.nl> -->
|
||||
{% extends "admin/base_admin.html" %}
|
||||
{% block admin_content %}
|
||||
<h1>{{ 'Bericht bewerken' if post else 'Nieuw bericht' }}</h1>
|
||||
<h1>{{ 'Edit post' if post else 'New post' }}</h1>
|
||||
<form class="form wide" method="post" action="{{ '/admin/posts/' ~ post.uid if post else '/admin/posts' }}" data-form>
|
||||
<div class="field"><label for="title">Titel</label><input id="title" type="text" name="title" required minlength="3" maxlength="200" value="{{ post.title if post else '' }}"></div>
|
||||
{% set _selected = post.topic if post else 'rol' %}
|
||||
<div class="field"><label for="title">Title</label><input id="title" type="text" name="title" required minlength="3" maxlength="200" value="{{ post.title if post else '' }}"></div>
|
||||
{% set _selected = post.topic if post else 'roll' %}
|
||||
{% include "_topic_selector.html" %}
|
||||
<div class="editor">
|
||||
<div class="field">
|
||||
<label for="body">Tekst (markdown)</label>
|
||||
<label for="body">Text (markdown)</label>
|
||||
<textarea id="body" name="body" required minlength="10" data-preview-source>{{ post.body if post else '' }}</textarea>
|
||||
<span class="help">Markdown met GFM. Emoji via :shortcode:. Voorbeeld rechts is een live voorbeeld; de gepubliceerde versie wordt op de server gerenderd.</span>
|
||||
<span class="help">Markdown with GFM. Emoji via :shortcode:. The preview on the right is live; the published version is rendered on the server.</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Voorbeeld</label>
|
||||
<label>Preview</label>
|
||||
<dp-content class="preview prose" data-preview-target></dp-content>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="status">Status</label>
|
||||
<select id="status" name="status">
|
||||
<option value="draft" {% if not post or post.status == 'draft' %}selected{% endif %}>Concept</option>
|
||||
<option value="published" {% if post and post.status == 'published' %}selected{% endif %}>Gepubliceerd</option>
|
||||
<option value="draft" {% if not post or post.status == 'draft' %}selected{% endif %}>Draft</option>
|
||||
<option value="published" {% if post and post.status == 'published' %}selected{% endif %}>Published</option>
|
||||
</select>
|
||||
</div>
|
||||
<label class="check"><input type="checkbox" name="is_placeholder" value="true" {% if post and post.is_placeholder %}checked{% endif %}> Markeer als placeholder</label>
|
||||
<div class="actions"><button class="button" type="submit" data-loading>Opslaan</button><a class="button ghost" href="/admin/posts">Annuleren</a></div>
|
||||
<label class="check"><input type="checkbox" name="is_placeholder" value="true" {% if post and post.is_placeholder %}checked{% endif %}> Mark as placeholder</label>
|
||||
<div class="actions"><button class="button" type="submit" data-loading>Save</button><a class="button ghost" href="/admin/posts">Cancel</a></div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
{% block extra_js %}<script type="module" src="{{ static_url('/static/js/utils/EditorPreview.js') }}"></script>{% endblock %}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!-- retoor <retoor@molodetz.nl> -->
|
||||
{% extends "admin/base_admin.html" %}
|
||||
{% block admin_content %}
|
||||
<h1>Berichten</h1>
|
||||
<div class="actions"><a class="button" href="/admin/posts/new">Nieuw bericht</a></div>
|
||||
<h1>Posts</h1>
|
||||
<div class="actions"><a class="button" href="/admin/posts/new">New post</a></div>
|
||||
<div class="table-wrap">
|
||||
<table class="admin-table">
|
||||
<thead><tr><th>Titel</th><th>Onderwerp</th><th>Status</th><th>Bijgewerkt</th><th></th></tr></thead>
|
||||
<thead><tr><th>Title</th><th>Topic</th><th>Status</th><th>Updated</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{% for post in posts %}
|
||||
<tr>
|
||||
@@ -14,13 +14,13 @@
|
||||
<td><span class="status {{ post.status }}">{{ post.status }}</span></td>
|
||||
<td>{{ local_dt(post.updated_at) }}</td>
|
||||
<td class="actions">
|
||||
<a class="button ghost small" href="/admin/posts/{{ post.uid }}/edit">Bewerken</a>
|
||||
<form method="post" action="/admin/posts/{{ post.uid }}/publish"><button class="button ghost small" type="submit">{{ 'Depubliceren' if post.status == 'published' else 'Publiceren' }}</button></form>
|
||||
<form method="post" action="/admin/posts/{{ post.uid }}/delete" data-confirm="Bericht naar de prullenbak verplaatsen?"><button class="button danger small" type="submit">Verwijderen</button></form>
|
||||
<a class="button ghost small" href="/admin/posts/{{ post.uid }}/edit">Edit</a>
|
||||
<form method="post" action="/admin/posts/{{ post.uid }}/publish"><button class="button ghost small" type="submit">{{ 'Unpublish' if post.status == 'published' else 'Publish' }}</button></form>
|
||||
<form method="post" action="/admin/posts/{{ post.uid }}/delete" data-confirm="Move this post to the trash?"><button class="button danger small" type="submit">Delete</button></form>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="5" class="muted">Nog geen berichten.</td></tr>
|
||||
<tr><td colspan="5" class="muted">No posts yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<p class="muted">{{ service.description }}</p>
|
||||
<div data-service-monitor="detail" data-service="{{ service.name }}">
|
||||
<div class="tabs" role="tablist" data-tabs>
|
||||
<button type="button" class="active" data-tab="overview">Overzicht</button>
|
||||
<button type="button" data-tab="config">Configuratie</button>
|
||||
<button type="button" class="active" data-tab="overview">Overview</button>
|
||||
<button type="button" data-tab="config">Configuration</button>
|
||||
<button type="button" data-tab="logs">Logs</button>
|
||||
</div>
|
||||
<section data-panel="overview">
|
||||
@@ -16,8 +16,8 @@
|
||||
</div>
|
||||
<div class="actions">
|
||||
<form method="post" action="/admin/services/{{ service.name }}/command"><input type="hidden" name="verb" value="{{ 'stop' if service.enabled else 'start' }}"><button class="button ghost small" type="submit">{{ 'Stop' if service.enabled else 'Start' }}</button></form>
|
||||
<form method="post" action="/admin/services/{{ service.name }}/command"><input type="hidden" name="verb" value="run"><button class="button ghost small" type="submit">Nu draaien</button></form>
|
||||
<form method="post" action="/admin/services/{{ service.name }}/command"><input type="hidden" name="verb" value="clear"><button class="button ghost small" type="submit">Logs wissen</button></form>
|
||||
<form method="post" action="/admin/services/{{ service.name }}/command"><input type="hidden" name="verb" value="run"><button class="button ghost small" type="submit">Run now</button></form>
|
||||
<form method="post" action="/admin/services/{{ service.name }}/command"><input type="hidden" name="verb" value="clear"><button class="button ghost small" type="submit">Clear logs</button></form>
|
||||
</div>
|
||||
</section>
|
||||
<section data-panel="config" hidden>
|
||||
@@ -42,7 +42,7 @@
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
<div class="actions"><button class="button" type="submit">Opslaan</button></div>
|
||||
<div class="actions"><button class="button" type="submit">Save</button></div>
|
||||
</form>
|
||||
</section>
|
||||
<section data-panel="logs" hidden>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<h1>Services</h1>
|
||||
<div class="table-wrap">
|
||||
<table class="admin-table" data-service-monitor="index">
|
||||
<thead><tr><th>Service</th><th>Status</th><th>Laatste run</th><th>Volgende run</th><th></th></tr></thead>
|
||||
<thead><tr><th>Service</th><th>Status</th><th>Last run</th><th>Next run</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{% for service in services %}
|
||||
<tr data-service="{{ service.name }}">
|
||||
@@ -14,7 +14,7 @@
|
||||
<td data-field="next_run">{{ local_dt(service.next_run) }}</td>
|
||||
<td class="actions">
|
||||
<form method="post" action="/admin/services/{{ service.name }}/command"><input type="hidden" name="verb" value="{{ 'stop' if service.enabled else 'start' }}"><button class="button ghost small" type="submit">{{ 'Stop' if service.enabled else 'Start' }}</button></form>
|
||||
<form method="post" action="/admin/services/{{ service.name }}/command"><input type="hidden" name="verb" value="run"><button class="button ghost small" type="submit">Nu draaien</button></form>
|
||||
<form method="post" action="/admin/services/{{ service.name }}/command"><input type="hidden" name="verb" value="run"><button class="button ghost small" type="submit">Run now</button></form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<!-- retoor <retoor@molodetz.nl> -->
|
||||
{% extends "admin/base_admin.html" %}
|
||||
{% block admin_content %}
|
||||
<h1>Instellingen</h1>
|
||||
<h1>Settings</h1>
|
||||
<form class="form" method="post" action="/admin/settings">
|
||||
{% for key, value in settings.items() %}
|
||||
<div class="field">
|
||||
<label for="s-{{ key }}">{{ key }}</label>
|
||||
{% if key == 'site_intro' %}
|
||||
<textarea id="s-{{ key }}" name="{{ key }}" rows="24">{{ value }}</textarea>
|
||||
<span class="help">Intro bovenaan de homepage. Markdown; elke regelovergang blijft staan, een lege regel begint een nieuwe alinea. De eerste alinea wordt groot gezet.</span>
|
||||
<span class="help">Intro at the top of the home page. Markdown; every line break is kept, an empty line starts a new paragraph. The first paragraph is set large.</span>
|
||||
{% elif key in ('maintenance_mode', 'friendly_404') %}
|
||||
<select id="s-{{ key }}" name="{{ key }}"><option value="0" {% if value == '0' %}selected{% endif %}>0</option><option value="1" {% if value == '1' %}selected{% endif %}>1</option></select>
|
||||
{% else %}
|
||||
@@ -16,6 +16,6 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="actions"><button class="button" type="submit">Opslaan</button></div>
|
||||
<div class="actions"><button class="button" type="submit">Save</button></div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<!-- retoor <retoor@molodetz.nl> -->
|
||||
{% extends "admin/base_admin.html" %}
|
||||
{% block admin_content %}
|
||||
<h1>Statistieken</h1>
|
||||
<h1>Statistics</h1>
|
||||
<div class="tabs" role="tablist" data-tabs>
|
||||
<button type="button" class="active" data-tab="paths">Paden</button>
|
||||
<button type="button" data-tab="days">Per dag</button>
|
||||
<button type="button" class="active" data-tab="paths">Paths</button>
|
||||
<button type="button" data-tab="days">Per day</button>
|
||||
</div>
|
||||
<section data-panel="paths">
|
||||
<table class="admin-table"><thead><tr><th>Pad</th><th>Hits</th></tr></thead><tbody>
|
||||
{% for row in top_paths %}<tr><td>{{ row.path }}</td><td>{{ row.hits }}</td></tr>{% else %}<tr><td colspan="2" class="muted">Nog geen data.</td></tr>{% endfor %}
|
||||
<table class="admin-table"><thead><tr><th>Path</th><th>Hits</th></tr></thead><tbody>
|
||||
{% for row in top_paths %}<tr><td>{{ row.path }}</td><td>{{ row.hits }}</td></tr>{% else %}<tr><td colspan="2" class="muted">No data yet.</td></tr>{% endfor %}
|
||||
</tbody></table>
|
||||
</section>
|
||||
<section data-panel="days" hidden>
|
||||
<table class="admin-table"><thead><tr><th>Dag</th><th>Hits</th></tr></thead><tbody>
|
||||
{% for row in daily %}<tr><td>{{ format_date(row.day) }}</td><td>{{ row.hits }}</td></tr>{% else %}<tr><td colspan="2" class="muted">Nog geen data.</td></tr>{% endfor %}
|
||||
<table class="admin-table"><thead><tr><th>Day</th><th>Hits</th></tr></thead><tbody>
|
||||
{% for row in daily %}<tr><td>{{ format_date(row.day) }}</td><td>{{ row.hits }}</td></tr>{% else %}<tr><td colspan="2" class="muted">No data yet.</td></tr>{% endfor %}
|
||||
</tbody></table>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<!-- retoor <retoor@molodetz.nl> -->
|
||||
{% extends "admin/base_admin.html" %}
|
||||
{% block admin_content %}
|
||||
<h1>Prullenbak</h1>
|
||||
<h1>Trash</h1>
|
||||
<div class="table-wrap">
|
||||
<table class="admin-table">
|
||||
<thead><tr><th>Soort</th><th>Item</th><th>Verwijderd</th><th>Door</th><th></th></tr></thead>
|
||||
<thead><tr><th>Kind</th><th>Item</th><th>Deleted</th><th>By</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{% for item in items %}
|
||||
<tr>
|
||||
@@ -13,12 +13,12 @@
|
||||
<td>{{ local_dt(item.deleted_at) }}</td>
|
||||
<td>{{ item.deleted_by }}</td>
|
||||
<td class="actions">
|
||||
<form method="post" action="/admin/trash/restore"><input type="hidden" name="stamp" value="{{ item.deleted_at }}"><button class="button ghost small" type="submit">Herstellen</button></form>
|
||||
<form method="post" action="/admin/trash/purge" data-confirm="Definitief verwijderen? Dit kan niet ongedaan worden gemaakt."><input type="hidden" name="stamp" value="{{ item.deleted_at }}"><button class="button danger small" type="submit">Definitief</button></form>
|
||||
<form method="post" action="/admin/trash/restore"><input type="hidden" name="stamp" value="{{ item.deleted_at }}"><button class="button ghost small" type="submit">Restore</button></form>
|
||||
<form method="post" action="/admin/trash/purge" data-confirm="Delete permanently? This cannot be undone."><input type="hidden" name="stamp" value="{{ item.deleted_at }}"><button class="button danger small" type="submit">Purge</button></form>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="5" class="muted">De prullenbak is leeg.</td></tr>
|
||||
<tr><td colspan="5" class="muted">The trash is empty.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -4,17 +4,17 @@
|
||||
<h1>Accounts</h1>
|
||||
<div class="table-wrap">
|
||||
<table class="admin-table">
|
||||
<thead><tr><th>Naam</th><th>Rol</th><th>Sinds</th><th></th></tr></thead>
|
||||
<thead><tr><th>Name</th><th>Role</th><th>Since</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>{{ user.username }}{% if is_primary_admin(user) %} <span class="muted">(primair)</span>{% endif %}</td>
|
||||
<td>{{ user.username }}{% if is_primary_admin(user) %} <span class="muted">(primary)</span>{% endif %}</td>
|
||||
<td>{{ user.role }}</td>
|
||||
<td>{{ local_dt(user.created_at, 'date') }}</td>
|
||||
<td>
|
||||
<form class="actions" method="post" action="/admin/users/{{ user.uid }}/role">
|
||||
<select name="role" aria-label="Rol"><option value="Admin" {% if user.role == 'Admin' %}selected{% endif %}>Admin</option><option value="Member" {% if user.role == 'Member' %}selected{% endif %}>Member</option></select>
|
||||
<button class="button ghost small" type="submit">Zet</button>
|
||||
<select name="role" aria-label="Role"><option value="Admin" {% if user.role == 'Admin' %}selected{% endif %}>Admin</option><option value="Member" {% if user.role == 'Member' %}selected{% endif %}>Member</option></select>
|
||||
<button class="button ghost small" type="submit">Set</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<header class="article-header">
|
||||
<span class="label">Beheer</span>
|
||||
<h1>Inloggen</h1>
|
||||
<span class="label">Admin</span>
|
||||
<h1>Log in</h1>
|
||||
</header>
|
||||
{% if errors %}
|
||||
<div class="notice error" role="alert"><ul>{% for error in errors %}<li>{{ error }}</li>{% endfor %}</ul></div>
|
||||
{% endif %}
|
||||
<form class="form" method="post" action="/auth/login">
|
||||
<input type="hidden" name="next" value="{{ next or '/admin' }}">
|
||||
<div class="field"><label for="username">Naam of e-mail</label><input id="username" type="text" name="username" required value="{{ (form or {}).username or '' }}" autocomplete="username"></div>
|
||||
<div class="field"><label for="password">Wachtwoord</label><input id="password" type="password" name="password" required minlength="6" maxlength="128" autocomplete="current-password"></div>
|
||||
<label class="check"><input type="checkbox" name="remember" value="true"> Onthoud mij 30 dagen</label>
|
||||
<div class="actions"><button class="button" type="submit" data-loading>Inloggen</button></div>
|
||||
<div class="field"><label for="username">Name or email</label><input id="username" type="text" name="username" required value="{{ (form or {}).username or '' }}" autocomplete="username"></div>
|
||||
<div class="field"><label for="password">Password</label><input id="password" type="password" name="password" required minlength="6" maxlength="128" autocomplete="current-password"></div>
|
||||
<label class="check"><input type="checkbox" name="remember" value="true"> Remember me for 30 days</label>
|
||||
<div class="actions"><button class="button" type="submit" data-loading>Log in</button></div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{% set seo = seo or {} %}
|
||||
{% set viewer = current_user(request) %}
|
||||
<!doctype html>
|
||||
<html lang="nl">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
@@ -21,7 +21,7 @@
|
||||
<meta property="og:type" content="{{ seo.og_type or 'website' }}">
|
||||
{% if seo.canonical %}<meta property="og:url" content="{{ seo.canonical }}">{% endif %}
|
||||
{% if seo.og_image %}<meta property="og:image" content="{{ seo.og_image }}">{% endif %}
|
||||
<meta property="og:locale" content="nl_NL">
|
||||
<meta property="og:locale" content="en_US">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="{{ seo.title or app_title }}">
|
||||
<meta name="twitter:description" content="{{ seo.description or '' }}">
|
||||
@@ -40,23 +40,23 @@
|
||||
{% block extra_head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<a class="sr-only" href="#main">Naar inhoud</a>
|
||||
<a class="sr-only" href="#main">Skip to content</a>
|
||||
<header class="site-header">
|
||||
<nav class="site-nav" aria-label="Hoofdmenu">
|
||||
<nav class="site-nav" aria-label="Main menu">
|
||||
<a class="brand" href="/">MOLODETZ</a>
|
||||
<button class="nav-toggle" type="button" data-nav-toggle aria-expanded="false" aria-controls="nav-links">Menu</button>
|
||||
<div class="nav-links" id="nav-links">
|
||||
<a href="/rol" class="{{ nav_active(request, '/rol') }}">Rol</a>
|
||||
<a href="/standaard" class="{{ nav_active(request, '/standaard') }}">Standaard</a>
|
||||
<a href="/roll" class="{{ nav_active(request, '/roll') }}">Roll</a>
|
||||
<a href="/standard" class="{{ nav_active(request, '/standard') }}">Standard</a>
|
||||
<a href="/flyers" class="{{ nav_active(request, '/flyers') }}">Flyers</a>
|
||||
<a href="/memes" class="{{ nav_active(request, '/memes') }}">Memes</a>
|
||||
<a href="/mensen" class="{{ nav_active(request, '/mensen') }}">Mensen</a>
|
||||
<a href="/binnen" class="{{ nav_active(request, '/binnen') }}">Binnen</a>
|
||||
<a href="/people" class="{{ nav_active(request, '/people') }}">People</a>
|
||||
<a href="/join" class="{{ nav_active(request, '/join') }}">Join</a>
|
||||
{% if viewer %}
|
||||
<span class="nav-account">
|
||||
{% if is_admin(viewer) %}<a href="/admin" class="{{ nav_active(request, '/admin') }}">Beheer</a>{% endif %}
|
||||
<a href="/notifications" class="{{ nav_active(request, '/notifications') }}">Meldingen{% set unread = unread_count(request) %}{% if unread %}<span class="badge-count">{{ unread }}</span>{% endif %}</a>
|
||||
<form method="post" action="/auth/logout"><button type="submit">Uit</button></form>
|
||||
{% if is_admin(viewer) %}<a href="/admin" class="{{ nav_active(request, '/admin') }}">Admin</a>{% endif %}
|
||||
<a href="/notifications" class="{{ nav_active(request, '/notifications') }}">Notifications{% set unread = unread_count(request) %}{% if unread %}<span class="badge-count">{{ unread }}</span>{% endif %}</a>
|
||||
<form method="post" action="/auth/logout"><button type="submit">Log out</button></form>
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -72,15 +72,15 @@
|
||||
<div class="inner">
|
||||
<span class="tagline">Molodetz. {{ setting('site_tagline') }}</span>
|
||||
<a href="/docs">Docs</a>
|
||||
<a href="/voorwaarden">Voorwaarden</a>
|
||||
<a href="/terms">Terms</a>
|
||||
<a href="/privacy">Privacy</a>
|
||||
{% if not viewer %}<a href="/auth/login" rel="nofollow">Inloggen</a>{% endif %}
|
||||
{% if not viewer %}<a href="/auth/login" rel="nofollow">Log in</a>{% endif %}
|
||||
<span>© {{ current_year() }}</span>
|
||||
</div>
|
||||
</footer>
|
||||
<dp-toast></dp-toast>
|
||||
<dp-lightbox></dp-lightbox>
|
||||
<span class="response-time" title="Server-renderingstijd">{{ response_time(request) }}</span>
|
||||
<span class="response-time" title="Server render time">{{ response_time(request) }}</span>
|
||||
<script type="module" src="{{ static_url('/static/js/Application.js') }}"></script>
|
||||
{% block extra_js %}{% endblock %}
|
||||
</body>
|
||||
|
||||
@@ -3,22 +3,22 @@
|
||||
{% block docs_content %}
|
||||
<h1>API: {{ group.title }}</h1>
|
||||
<p>{{ group.description }}</p>
|
||||
<p class="muted">Elke HTML-route geeft JSON terug met <code>Accept: application/json</code>. Authenticatie: sessiecookie, <code>X-API-KEY</code>, <code>Authorization: Bearer</code> of <code>Basic</code>.</p>
|
||||
<p class="muted">Every HTML route returns JSON with <code>Accept: application/json</code>. Authentication: session cookie, <code>X-API-KEY</code>, <code>Authorization: Bearer</code> or <code>Basic</code>.</p>
|
||||
{% for item in endpoints %}
|
||||
<section class="endpoint" data-endpoint data-method="{{ item.method }}" data-path="{{ item.path }}" data-auth="{{ item.auth }}" data-negotiation="{{ item.negotiation }}" data-destructive="{{ '1' if item.destructive else '0' }}">
|
||||
<div class="signature"><span class="method">{{ item.method }}</span><code>{{ item.path }}</code><span class="auth">{{ item.min_role }}</span></div>
|
||||
<strong>{{ item.title }}</strong>
|
||||
{% if item.description %}<p>{{ item.description }}</p>{% endif %}
|
||||
{% if item.fields %}
|
||||
<table class="admin-table"><thead><tr><th>Veld</th><th>Type</th><th>Plek</th><th>Verplicht</th></tr></thead><tbody>
|
||||
{% for f in item.fields %}<tr><td><code>{{ f.name }}</code>{% if f.enum %}<div class="allowed">Toegestaan: {{ f.enum | join(', ') }}</div>{% endif %}</td><td>{{ f.type }}</td><td>{{ f.location }}</td><td>{{ 'ja' if f.required else 'nee' }}</td></tr>{% endfor %}
|
||||
<table class="admin-table"><thead><tr><th>Field</th><th>Type</th><th>Location</th><th>Required</th></tr></thead><tbody>
|
||||
{% for f in item.fields %}<tr><td><code>{{ f.name }}</code>{% if f.enum %}<div class="allowed">Allowed: {{ f.enum | join(', ') }}</div>{% endif %}</td><td>{{ f.type }}</td><td>{{ f.location }}</td><td>{{ 'yes' if f.required else 'no' }}</td></tr>{% endfor %}
|
||||
</tbody></table>
|
||||
{% endif %}
|
||||
<div class="tabs" data-tabs>
|
||||
<button type="button" class="active" data-tab="curl">cURL</button>
|
||||
<button type="button" data-tab="js">JS</button>
|
||||
<button type="button" data-tab="python">Python</button>
|
||||
<button type="button" data-tab="expected">Verwacht</button>
|
||||
<button type="button" data-tab="expected">Expected</button>
|
||||
{% if item.interactive %}<button type="button" data-tab="live">Live</button>{% endif %}
|
||||
</div>
|
||||
<pre data-panel="curl">{{ item.curl }}</pre>
|
||||
@@ -26,7 +26,7 @@
|
||||
console.log(await response.json());</pre>
|
||||
<pre data-panel="python" hidden>import httpx
|
||||
print(httpx.request("{{ item.method }}", BASE + "{{ item.path }}", headers={"Accept": "application/json", "X-API-KEY": API_KEY}).json())</pre>
|
||||
<pre data-panel="expected" hidden>{{ item.sample | tojson(indent=2) if item.sample is not none else 'Geen voorbeeld' }}</pre>
|
||||
<pre data-panel="expected" hidden>{{ item.sample | tojson(indent=2) if item.sample is not none else 'No example' }}</pre>
|
||||
{% if item.interactive %}
|
||||
<div data-panel="live" hidden class="tester">
|
||||
{% for f in item.fields %}
|
||||
@@ -35,8 +35,8 @@ print(httpx.request("{{ item.method }}", BASE + "{{ item.path }}", headers={"Acc
|
||||
{% else %}<input type="text" data-param="{{ f.name }}" data-location="{{ f.location }}" value="{{ f.example or '' }}">{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="actions"><button class="button small" type="button" data-send {% if item.auth != 'public' and not viewer %}disabled title="Log in om te versturen"{% endif %}>Verstuur</button></div>
|
||||
<pre data-response>Nog niet verstuurd.</pre>
|
||||
<div class="actions"><button class="button small" type="button" data-send {% if item.auth != 'public' and not viewer %}disabled title="Log in to send"{% endif %}>Send</button></div>
|
||||
<pre data-response>Not sent yet.</pre>
|
||||
</div>
|
||||
{% endif %}
|
||||
</section>
|
||||
@@ -45,7 +45,7 @@ print(httpx.request("{{ item.method }}", BASE + "{{ item.path }}", headers={"Acc
|
||||
<h2>Live services</h2>
|
||||
{% for service in services %}
|
||||
<section class="endpoint"><strong>{{ service.title }}</strong> <code>{{ service.name }}</code><p>{{ service.description }}</p>
|
||||
<p class="muted">Velden: {% for f in service.fields %}<code>{{ f.key }}</code> ({{ f.type }}){% if not loop.last %}, {% endif %}{% endfor %}</p></section>
|
||||
<p class="muted">Fields: {% for f in service.fields %}<code>{{ f.key }}</code> ({{ f.type }}){% if not loop.last %}, {% endif %}{% endfor %}</p></section>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -13,16 +13,16 @@
|
||||
<ul>{% for page in pages %}<li><a href="/docs/{{ page.slug }}" class="{{ nav_active(request, '/docs/' ~ page.slug, exact=True) }}">{{ page.title }}</a></li>{% endfor %}</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if section == 'Bouwen met de API' %}
|
||||
{% if section == 'Building with the API' %}
|
||||
<div>
|
||||
<h4>API-groepen</h4>
|
||||
<h4>API groups</h4>
|
||||
<ul>{% for group in api_groups %}<li><a href="/docs/api/{{ group.slug }}" class="{{ nav_active(request, '/docs/api/' ~ group.slug, exact=True) }}">{{ group.title }}</a></li>{% endfor %}</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<div>
|
||||
<h4>Downloads</h4>
|
||||
<ul><li><a href="/docs/download.md">download.md</a></li><li><a href="/docs/download.html">download.html</a></li><li><a href="/voorwaarden">Voorwaarden</a></li><li><a href="/privacy">Privacy</a></li><li><a href="/swagger">OpenAPI</a></li></ul>
|
||||
<ul><li><a href="/docs/download.md">download.md</a></li><li><a href="/docs/download.html">download.html</a></li><li><a href="/terms">Terms</a></li><li><a href="/privacy">Privacy</a></li><li><a href="/swagger">OpenAPI</a></li></ul>
|
||||
</div>
|
||||
</aside>
|
||||
<article class="docs-content prose">
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# Admin and services
|
||||
|
||||
*Admin, Services* lists every background service. Each service has an overview, configuration and logs. Start, stop, run now and clear logs are database writes the service picks up within a second.
|
||||
|
||||
| Service | Purpose |
|
||||
|---------|---------|
|
||||
| presence | publishes the online set |
|
||||
| housekeeping | clears expired sessions, prunes the audit log |
|
||||
| backup | works through the backup queue and schedules automatic backups |
|
||||
|
||||
## Intro
|
||||
|
||||
The intro at the top of the home page lives in the `site_intro` setting (markdown, line breaks are kept). Edit it under *Settings*; the first paragraph is set large.
|
||||
|
||||
## Maintenance mode
|
||||
|
||||
Set `maintenance_mode` to `1` under *Settings*. Administrators keep seeing everything.
|
||||
@@ -1,24 +1,28 @@
|
||||
# API en authenticatie
|
||||
# API and authentication
|
||||
|
||||
Elke route heeft vier gezichten: HTML, JSON, documentatie en (waar geselecteerd) een assistent-tool. Vraag JSON met `Accept: application/json`.
|
||||
Every route has four faces: HTML, JSON, documentation and (where selected) an assistant tool. Ask for JSON with `Accept: application/json`.
|
||||
|
||||
## Authenticatie
|
||||
## Authentication
|
||||
|
||||
Volgorde van resolutie:
|
||||
Resolution order:
|
||||
|
||||
1. `session` cookie (64 hex tekens) na inloggen op `/auth/login`.
|
||||
2. `X-API-KEY` header met je API-sleutel.
|
||||
3. `Authorization: Bearer <sleutel>`.
|
||||
4. `Authorization: Basic` met naam of e-mail en wachtwoord.
|
||||
1. `session` cookie (64 hex characters) after logging in at `/auth/login`.
|
||||
2. `X-API-KEY` header with your API key.
|
||||
3. `Authorization: Bearer <key>`.
|
||||
4. `Authorization: Basic` with name or email and password.
|
||||
|
||||
Er is geen JWT en geen OAuth.
|
||||
There is no JWT and no OAuth.
|
||||
|
||||
## Voorbeeld
|
||||
## Example
|
||||
|
||||
```bash
|
||||
curl -H 'Accept: application/json' https://molodetz.nl/rol
|
||||
curl -H 'Accept: application/json' https://molodetz.nl/roll
|
||||
```
|
||||
|
||||
## Fouten
|
||||
## Errors
|
||||
|
||||
Fouten hebben de vorm `{"error": {"status": 404, "message": "..."}}`. Validatiefouten geven `422` met `{"error": "validation", "fields": [...], "messages": [...]}`.
|
||||
Errors have the shape `{"error": {"status": 404, "message": "..."}}`. Validation errors return `422` with `{"error": "validation", "fields": [...], "messages": [...]}`.
|
||||
|
||||
## Old paths
|
||||
|
||||
The Dutch paths from before (`/rol`, `/standaard`, `/mensen`, `/binnen`, `/voorwaarden`) answer with a 301 to their English replacement. Query strings are kept.
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Architecture
|
||||
|
||||
FastAPI with Jinja2, rendered on the server. SQLite through `dataset`, synchronous and deliberately without a threadpool. Sessions with PBKDF2-SHA256 passwords.
|
||||
|
||||
## Layers
|
||||
|
||||
- `molodetz/database/`: one file per subject, always import from the package root.
|
||||
- `molodetz/routers/`: mirrors the URL path.
|
||||
- `molodetz/services/`: background services under one supervisor with a file lock.
|
||||
- `molodetz/templates/`: one Jinja environment in `templating.py`.
|
||||
|
||||
## Deleting
|
||||
|
||||
Everything is soft deleted with `deleted_at` and `deleted_by`. Only cleanup jobs delete for real.
|
||||
|
||||
## Static files
|
||||
|
||||
Assets go through `static_url()`, which versions them under `/static/v<version>/`. Those are immutably cacheable for a year.
|
||||
@@ -1,18 +0,0 @@
|
||||
# Architectuur
|
||||
|
||||
FastAPI met Jinja2, server-side gerenderd. SQLite via `dataset`, synchroon en bewust zonder threadpool. Sessies met PBKDF2-SHA256 wachtwoorden.
|
||||
|
||||
## Lagen
|
||||
|
||||
- `molodetz/database/`: één bestand per onderwerp, importeer altijd vanaf de package-root.
|
||||
- `molodetz/routers/`: spiegelt het URL-pad.
|
||||
- `molodetz/services/`: achtergrondservices onder één supervisor met een bestandsslot.
|
||||
- `molodetz/templates/`: één Jinja-omgeving in `templating.py`.
|
||||
|
||||
## Verwijderen
|
||||
|
||||
Alles wordt zacht verwijderd met `deleted_at` en `deleted_by`. Alleen opruimtaken verwijderen hard.
|
||||
|
||||
## Statische bestanden
|
||||
|
||||
Assets lopen via `static_url()`, wat ze versioneert onder `/static/v<versie>/`. Die zijn een jaar onveranderlijk cachebaar.
|
||||
@@ -1,6 +1,6 @@
|
||||
# Back-ups en onderhoud
|
||||
# Backups and maintenance
|
||||
|
||||
Doelen: `database`, `uploads`, `keys`, `full`. Archieven worden gecomprimeerd met zstd of brotli en staan onder `data/backups/`. Alleen de primaire beheerder kan downloaden.
|
||||
Targets: `database`, `uploads`, `keys`, `full`. Archives are compressed with zstd or brotli and live under `data/backups/`. Only the primary administrator can download them.
|
||||
|
||||
## CLI
|
||||
|
||||
@@ -11,4 +11,4 @@ molodetz backups prune --keep 14 --dry-run
|
||||
molodetz system prune --dry-run
|
||||
```
|
||||
|
||||
Het onderhoudsscript `bin/maintenance.sh` draait alle prunes en toont schijfgebruik voor en na.
|
||||
The maintenance script `bin/maintenance.sh` runs every prune and shows disk usage before and after.
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# Beheer en services
|
||||
|
||||
Onder *Beheer, Services* staan alle achtergrondservices. Elke service heeft een overzicht, configuratie en logs. Starten, stoppen, nu draaien en logs wissen zijn databaseschrijfacties waar de service binnen een seconde op reageert.
|
||||
|
||||
| Service | Doel |
|
||||
|---------|------|
|
||||
| presence | publiceert de online-set |
|
||||
| housekeeping | ruimt verlopen sessies op, snoeit het auditlog |
|
||||
| backup | verwerkt de back-upwachtrij en plant automatische back-ups |
|
||||
|
||||
## Onderhoudsmodus
|
||||
|
||||
De intro bovenaan de homepage staat in de instelling `site_intro` (markdown, regelovergangen blijven staan). Bewerk hem onder *Instellingen*; de eerste alinea wordt groot gezet.
|
||||
|
||||
Zet `maintenance_mode` op `1` onder *Instellingen*. Beheerders blijven alles zien.
|
||||
@@ -1,5 +0,0 @@
|
||||
# Binnenkomen
|
||||
|
||||
Het formulier op [/binnen](/binnen) slaat een aanmelding op: naam, contact, optioneel een link naar je werk en een bericht. Beheerders krijgen een melding en zien de aanmelding onder *Beheer, Aanmeldingen*.
|
||||
|
||||
Er is geen automatische toegang. retoor leest elke aanmelding en neemt zelf contact op.
|
||||
@@ -1,5 +1,5 @@
|
||||
# dp-avatar
|
||||
|
||||
Toont een deterministische SVG-avatar plus presence-stip. Attribuut `seed`, `size`, `uid`.
|
||||
Shows a deterministic SVG avatar plus a presence dot. Attributes `seed`, `size`, `uid`.
|
||||
|
||||
Light DOM, geen shadow DOM. Registreert zichzelf met `customElements.define`.
|
||||
Light DOM, no shadow DOM. Registers itself with `customElements.define`.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# dp-chat
|
||||
|
||||
Eenvoudige log met invoerregel. Geeft een `dp-chat-send` event.
|
||||
Simple log with an input line. Emits a `dp-chat-send` event.
|
||||
|
||||
Light DOM, geen shadow DOM. Registreert zichzelf met `customElements.define`.
|
||||
Light DOM, no shadow DOM. Registers itself with `customElements.define`.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# dp-code
|
||||
|
||||
Markeert een codeblok met highlight.js en voegt een Kopieer-knop toe.
|
||||
Highlights a code block with highlight.js and adds a Copy button.
|
||||
|
||||
Light DOM, geen shadow DOM. Registreert zichzelf met `customElements.define`.
|
||||
Light DOM, no shadow DOM. Registers itself with `customElements.define`.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# dp-content
|
||||
|
||||
Rendert live markdown aan de clientkant via marked, daarna DOMPurify. Alleen voor live inhoud, zoals het editorvoorbeeld.
|
||||
Renders live markdown on the client through marked, then DOMPurify. Only for live content, such as the editor preview.
|
||||
|
||||
Light DOM, geen shadow DOM. Registreert zichzelf met `customElements.define`.
|
||||
Light DOM, no shadow DOM. Registers itself with `customElements.define`.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# dp-context-menu
|
||||
|
||||
Klein contextmenu met knoppen, gepositioneerd bij de cursor.
|
||||
Small context menu with buttons, positioned at the cursor.
|
||||
|
||||
Light DOM, geen shadow DOM. Registreert zichzelf met `customElements.define`.
|
||||
Light DOM, no shadow DOM. Registers itself with `customElements.define`.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# dp-dialog
|
||||
|
||||
Bevestigingsdialoog. Formulieren met `data-confirm` lopen hierlangs.
|
||||
Confirmation dialog. Forms with `data-confirm` go through it.
|
||||
|
||||
Light DOM, geen shadow DOM. Registreert zichzelf met `customElements.define`.
|
||||
Light DOM, no shadow DOM. Registers itself with `customElements.define`.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# dp-lightbox
|
||||
|
||||
Volledig scherm voor afbeeldingen met `data-lightbox`. Escape of klik sluit.
|
||||
Full screen view for images with `data-lightbox`. Escape or a click closes it.
|
||||
|
||||
Light DOM, geen shadow DOM. Registreert zichzelf met `customElements.define`.
|
||||
Light DOM, no shadow DOM. Registers itself with `customElements.define`.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# dp-title
|
||||
|
||||
Inline-variant van dp-content voor titels.
|
||||
Inline variant of dp-content for titles.
|
||||
|
||||
Light DOM, geen shadow DOM. Registreert zichzelf met `customElements.define`.
|
||||
Light DOM, no shadow DOM. Registers itself with `customElements.define`.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# dp-toast
|
||||
|
||||
Host voor korte meldingen. Gebruik `Toast.flash(el, bericht)` of `app.toast(bericht)`.
|
||||
Host for short notices. Use `Toast.flash(el, message)` or `app.toast(message)`.
|
||||
|
||||
Light DOM, geen shadow DOM. Registreert zichzelf met `customElements.define`.
|
||||
Light DOM, no shadow DOM. Registers itself with `customElements.define`.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# dp-upload
|
||||
|
||||
Sleepvlak voor bestanden. Geeft een `dp-files` event met de gekozen bestanden.
|
||||
Drop zone for files. Emits a `dp-files` event with the chosen files.
|
||||
|
||||
Light DOM, geen shadow DOM. Registreert zichzelf met `customElements.define`.
|
||||
Light DOM, no shadow DOM. Registers itself with `customElements.define`.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Frontend en componenten
|
||||
# Frontend and components
|
||||
|
||||
ES6-modules, één klasse per bestand, geen framework en geen bundler. `Application.js` maakt alle singletons aan en zet `window.app`.
|
||||
ES6 modules, one class per file, no framework and no bundler. `Application.js` creates all singletons and sets `window.app`.
|
||||
|
||||
Componenten zijn custom elements met het voorvoegsel `dp-`, zonder shadow DOM. Zie de componentpagina's in de zijbalk.
|
||||
Components are custom elements with the `dp-` prefix, without shadow DOM. See the component pages in the sidebar.
|
||||
|
||||
Clientrendering met marked en DOMPurify is alleen voor live inhoud: het voorbeeld in de editor. Gepubliceerde berichten worden op de server gerenderd.
|
||||
Client rendering with marked and DOMPurify is only for live content: the preview in the editor. Published posts are rendered on the server.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Joining
|
||||
|
||||
The form at [/join](/join) stores a join request: name, contact, an optional link to your work and a message. Administrators get a notification and see the request under *Admin, Join requests*.
|
||||
|
||||
There is no automatic access. retoor reads every request and gets in touch himself.
|
||||
@@ -1,6 +0,0 @@
|
||||
# Voorwaarden en privacy
|
||||
|
||||
- [Voorwaarden](/voorwaarden)
|
||||
- [Privacy](/privacy)
|
||||
|
||||
Molodetz heeft geen advertenties, geen tracking over apps heen, geen betalingen en geen sociale login.
|
||||
@@ -0,0 +1,6 @@
|
||||
# Terms and privacy
|
||||
|
||||
- [Terms](/terms)
|
||||
- [Privacy](/privacy)
|
||||
|
||||
Molodetz has no ads, no cross-app tracking, no payments and no social login.
|
||||
@@ -1,17 +0,0 @@
|
||||
# Schrijven op de rol
|
||||
|
||||
Berichten zijn markdown en staan in SQLite. Alleen beheerders schrijven. Een bericht heeft een titel, een onderwerp (`rol` of `standaard`), een status (`draft` of `published`) en optioneel de markering *placeholder*.
|
||||
|
||||
## Markdown
|
||||
|
||||
De server rendert met GFM: tabellen, doorhalen, harde regeleinden. HTML in de tekst wordt ge-escaped. Links mogen alleen `http`, `https`, `mailto` en `tel` zijn. Emoji schrijf je als `:shortcode:`, bijvoorbeeld `:fire:`.
|
||||
|
||||
Een losse afbeeldings-URL wordt een afbeelding, `@naam` wordt een link naar het profiel.
|
||||
|
||||
## Placeholders
|
||||
|
||||
De eerste notities zijn overgenomen uit de oude Molodetz-pagina en dragen het label *Placeholder*. retoor vervangt ze door eigen verhalen. Haal het vinkje weg bij het bewerken zodra een bericht echt is.
|
||||
|
||||
## Slugs
|
||||
|
||||
Een slug is permanent: titel plus de laatste twaalf tekens van de uid. Bewerken verandert de slug niet.
|
||||
@@ -0,0 +1,15 @@
|
||||
# Welcome to Molodetz
|
||||
|
||||
Molodetz is a quiet blog roll. Notes about the work, finishing it, and judgement. This documentation describes how the site works, how to use the API and how admin keeps the whole thing running.
|
||||
|
||||
## What is here
|
||||
|
||||
- **Roll**: short notes, newest first.
|
||||
- **Standard**: what Molodetz stands for.
|
||||
- **Flyers** and **Memes**: two galleries.
|
||||
- **People**: who writes here.
|
||||
- **Join**: the form to write along.
|
||||
|
||||
## Where to start
|
||||
|
||||
Read [Writing on the roll](/docs/writing) for the content, [API and authentication](/docs/api) for integrations, and [Architecture](/docs/architecture) if you want to know how it is built.
|
||||
@@ -1,15 +0,0 @@
|
||||
# Welkom bij Molodetz
|
||||
|
||||
Molodetz is een rustige blogrol. Notities over het werk, het afmaken en het oordeel. Deze documentatie beschrijft hoe de site werkt, hoe je de API gebruikt en hoe beheer de boel draaiende houdt.
|
||||
|
||||
## Wat er is
|
||||
|
||||
- **Rol**: korte notities, nieuwste eerst.
|
||||
- **Standaard**: waar Molodetz op staat.
|
||||
- **Flyers** en **Memes**: twee galerijen.
|
||||
- **Mensen**: wie er schrijft.
|
||||
- **Binnen**: het formulier om mee te schrijven.
|
||||
|
||||
## Waar te beginnen
|
||||
|
||||
Lees [Schrijven op de rol](/docs/schrijven) voor de inhoud, [API en authenticatie](/docs/api) voor integraties, en [Architectuur](/docs/architectuur) als je wilt weten hoe het gebouwd is.
|
||||
@@ -0,0 +1,17 @@
|
||||
# Writing on the roll
|
||||
|
||||
Posts are markdown and live in SQLite. Only administrators write. A post has a title, a topic (`roll` or `standard`), a status (`draft` or `published`) and an optional *placeholder* marker.
|
||||
|
||||
## Markdown
|
||||
|
||||
The server renders GFM: tables, strikethrough, hard line breaks. HTML in the text is escaped. Links may only be `http`, `https`, `mailto` and `tel`. Write emoji as `:shortcode:`, for example `:fire:`.
|
||||
|
||||
A bare image URL becomes an image, `@name` becomes a link to the profile.
|
||||
|
||||
## Placeholders
|
||||
|
||||
The first notes were carried over from the old Molodetz page and carry the *Placeholder* label. retoor replaces them with his own stories. Untick the box while editing once a post is real.
|
||||
|
||||
## Slugs
|
||||
|
||||
A slug is the title plus the last twelve characters of the uid. Editing does not change the slug. If a title is ever rewritten in the database, the old slug still resolves through its uid tail and redirects with 301 to the canonical one.
|
||||
@@ -1,14 +1,14 @@
|
||||
<!-- retoor <retoor@molodetz.nl> -->
|
||||
{% extends "docs/base_docs.html" %}
|
||||
{% block docs_content %}
|
||||
<h1>Zoeken</h1>
|
||||
{% if query %}<p class="muted">{{ results | length }} resultaten voor <strong>{{ query }}</strong></p>{% endif %}
|
||||
<h1>Search</h1>
|
||||
{% if query %}<p class="muted">{{ results | length }} results for <strong>{{ query }}</strong></p>{% endif %}
|
||||
{% for hit in results %}
|
||||
<div class="search-hit">
|
||||
<a href="{{ hit.url }}">{{ hit.title }}</a> <span class="muted">{{ hit.score }}</span>
|
||||
<p>{{ hit.snippet | e | replace('[[', '<mark>') | replace(']]', '</mark>') | safe }}</p>
|
||||
</div>
|
||||
{% else %}
|
||||
{% if query %}<p class="empty">Niets gevonden.</p>{% endif %}
|
||||
{% if query %}<p class="empty">Nothing found.</p>{% endif %}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
<section class="error-page">
|
||||
<div class="code">{{ status_code }}</div>
|
||||
<p class="lead">{{ message }}</p>
|
||||
<div class="actions"><a class="button ghost" href="/">Naar de voorpagina</a><a class="button ghost" href="/rol">Naar de rol</a></div>
|
||||
<div class="actions"><a class="button ghost" href="/">To the home page</a><a class="button ghost" href="/roll">To the roll</a></div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
@@ -4,16 +4,16 @@
|
||||
{% block extra_head %}<link rel="stylesheet" href="{{ static_url('/static/css/gallery.css') }}">{% endblock %}
|
||||
{% block content %}
|
||||
<header class="article-header">
|
||||
<span class="label">{{ 'Gezet in serif' if kind == 'flyer' else 'Lichtelijk provocerend' }}</span>
|
||||
<span class="label">{{ 'Set in serif' if kind == 'flyer' else 'Slightly provocative' }}</span>
|
||||
<h1>{{ title }}</h1>
|
||||
</header>
|
||||
<p class="gallery-intro">{% if kind == 'flyer' %}Rustig gezet, scherp bedoeld. Klik voor het volle formaat.{% else %}Niet om te schreeuwen. Om even te blijven hangen.{% endif %}</p>
|
||||
<p class="gallery-intro">{% if kind == 'flyer' %}Set quietly, meant sharply. Click for full size.{% else %}Not to shout. To linger for a moment.{% endif %}</p>
|
||||
<div class="gallery {{ 'flyers' if kind == 'flyer' else 'memes' }}">
|
||||
{% for item in items %}
|
||||
{% set _item = item %}{% set _index = loop.index %}
|
||||
{% include "_gallery_item.html" %}
|
||||
{% else %}
|
||||
<p class="empty">Nog geen beelden.</p>
|
||||
<p class="empty">No images yet.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -3,21 +3,21 @@
|
||||
{% block content %}
|
||||
{% set viewer = current_user(request) %}
|
||||
<section class="hero">
|
||||
<span class="label">Een blogrol</span>
|
||||
<span class="label">A blog roll</span>
|
||||
<h1>Molodetz</h1>
|
||||
<div class="intro" data-intro>{{ intro_html | safe }}</div>
|
||||
{% if viewer and is_admin(viewer) %}<p class="intro-edit"><a href="/admin/settings#s-site_intro">Intro bewerken</a></p>{% endif %}
|
||||
{% if viewer and is_admin(viewer) %}<p class="intro-edit"><a href="/admin/settings#s-site_intro">Edit intro</a></p>{% endif %}
|
||||
</section>
|
||||
|
||||
<div class="section-head">
|
||||
<span class="label">Laatst op de rol</span>
|
||||
<a href="/rol">Alles</a>
|
||||
<span class="label">Latest on the roll</span>
|
||||
<a href="/roll">All</a>
|
||||
</div>
|
||||
{% for post in posts %}
|
||||
{% set _card = post %}
|
||||
{% include "_post_card.html" %}
|
||||
{% else %}
|
||||
<p class="empty">Nog niets gepubliceerd.</p>
|
||||
<p class="empty">Nothing published yet.</p>
|
||||
{% endfor %}
|
||||
|
||||
{% if featured_flyer %}
|
||||
@@ -33,8 +33,8 @@
|
||||
{% endif %}
|
||||
|
||||
<section class="closing">
|
||||
<span class="label">Binnen</span>
|
||||
<p>De rol is open voor mensen, niet voor een publiek. Wie mee wil schrijven, zegt het.</p>
|
||||
<div class="actions"><a class="button" href="/binnen">Ik schrijf mee</a><a class="button ghost" href="/memes">{{ meme_count }} memes</a></div>
|
||||
<span class="label">Join</span>
|
||||
<p>The roll is open to people, not to an audience. Want to write along? Say so.</p>
|
||||
<div class="actions"><a class="button" href="/join">I'm in</a><a class="button ghost" href="/memes">{{ meme_count }} memes</a></div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
@@ -2,36 +2,36 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<header class="article-header">
|
||||
<span class="label">Binnen</span>
|
||||
<h1>Ik schrijf mee</h1>
|
||||
<p class="muted">De rol is open voor mensen, niet voor een publiek. Zeg wie je bent en hoe we je bereiken. Een link naar je werk helpt, maar is niet verplicht.</p>
|
||||
<span class="label">Join</span>
|
||||
<h1>I'm in</h1>
|
||||
<p class="muted">The roll is open to people, not to an audience. Tell us who you are and how to reach you. A link to your work helps, but is not required.</p>
|
||||
</header>
|
||||
{% if submitted %}
|
||||
<div class="notice success" role="status">Genoteerd. retoor leest elke aanmelding zelf en neemt contact op.</div>
|
||||
<div class="notice success" role="status">Noted. retoor reads every request himself and will get in touch.</div>
|
||||
{% endif %}
|
||||
{% if errors %}
|
||||
<div class="notice error" role="alert"><ul>{% for error in errors %}<li>{{ error }}</li>{% endfor %}</ul></div>
|
||||
{% endif %}
|
||||
<form class="form" method="post" action="/binnen" data-form>
|
||||
<form class="form" method="post" action="/join" data-form>
|
||||
<div class="field">
|
||||
<label for="name">Naam</label>
|
||||
<label for="name">Name</label>
|
||||
<input id="name" type="text" name="name" required minlength="2" maxlength="80" value="{{ form.name or '' }}" autocomplete="name">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="contact">Contact</label>
|
||||
<input id="contact" type="text" name="contact" required minlength="3" maxlength="255" value="{{ form.contact or '' }}" placeholder="e-mail, Matrix of Signal">
|
||||
<span class="help">Alleen zichtbaar voor beheer.</span>
|
||||
<input id="contact" type="text" name="contact" required minlength="3" maxlength="255" value="{{ form.contact or '' }}" placeholder="email, Matrix or Signal">
|
||||
<span class="help">Only visible to admins.</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="repo_url">Repo of werk (optioneel)</label>
|
||||
<label for="repo_url">Repo or work (optional)</label>
|
||||
<input id="repo_url" type="text" name="repo_url" maxlength="500" value="{{ form.repo_url or '' }}" placeholder="https://">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="message">Waarover wil je schrijven? (optioneel)</label>
|
||||
<label for="message">What do you want to write about? (optional)</label>
|
||||
<textarea id="message" name="message" maxlength="2000" rows="4">{{ form.message or '' }}</textarea>
|
||||
</div>
|
||||
<div class="honeypot" aria-hidden="true"><label for="website">Website</label><input id="website" type="text" name="website" tabindex="-1" autocomplete="off"></div>
|
||||
<p class="muted">Door te versturen ga je akkoord met de <a href="/privacy">privacyverklaring</a>. We bewaren alleen wat je hier invult.</p>
|
||||
<div class="actions"><button class="button" type="submit" data-loading>Ik schrijf mee</button></div>
|
||||
<p class="muted">By sending this you agree to the <a href="/privacy">privacy statement</a>. We only keep what you enter here.</p>
|
||||
<div class="actions"><button class="button" type="submit" data-loading>I'm in</button></div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -2,15 +2,15 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<header class="article-header">
|
||||
<span class="label">{{ 'De rol' if topic == 'rol' else 'De standaard' }}</span>
|
||||
<span class="label">{{ 'The roll' if topic == 'roll' else 'The standard' }}</span>
|
||||
<h1>{{ title }}</h1>
|
||||
<p class="muted">{% if topic == 'rol' %}Korte notities. Eén gedachte per keer, scherp genoeg om op terug te komen.{% else %}Waar Molodetz op staat, en waarom de keuzes vastliggen.{% endif %}</p>
|
||||
<p class="muted">{% if topic == 'roll' %}Short notes. One thought at a time, sharp enough to come back to.{% else %}What Molodetz stands for, and why the choices are fixed.{% endif %}</p>
|
||||
</header>
|
||||
{% for post in posts %}
|
||||
{% set _card = post %}
|
||||
{% include "_post_card.html" %}
|
||||
{% else %}
|
||||
<p class="empty">Hier staat nog niets.</p>
|
||||
<p class="empty">Nothing here yet.</p>
|
||||
{% endfor %}
|
||||
{% set _next_url = next_url %}
|
||||
{% include "_load_more.html" %}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
<!-- retoor <retoor@molodetz.nl> -->
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<header class="article-header">
|
||||
<span class="label">Mensen</span>
|
||||
<h1>Wie schrijft</h1>
|
||||
<p class="muted">Een rol van mensen, geen ledenlijst. Wie binnenkomt, staat hier zodra er geschreven is.</p>
|
||||
</header>
|
||||
{% for person in people %}
|
||||
<div class="person-row">
|
||||
{% set _user = person %}{% set _size = 48 %}
|
||||
{% include "_avatar_link.html" %}
|
||||
<div class="who"><b>{% include "_user_link.html" %}</b><span>{{ person.bio or 'Schrijft op de rol.' }}</span></div>
|
||||
<span class="count">{{ person.post_count }} {{ 'bericht' if person.post_count == 1 else 'berichten' }}</span>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="empty">Nog niemand op de rol.</p>
|
||||
{% endfor %}
|
||||
<section class="closing">
|
||||
<p>De volgende schrijft hier zodra die binnen is.</p>
|
||||
<div class="actions"><a class="button" href="/binnen">Ik schrijf mee</a></div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -1,16 +1,16 @@
|
||||
<!-- retoor <retoor@molodetz.nl> -->
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<header class="article-header"><span class="label">Account</span><h1>Meldingen</h1></header>
|
||||
<header class="article-header"><span class="label">Account</span><h1>Notifications</h1></header>
|
||||
{% if unread %}
|
||||
<form method="post" action="/notifications/read"><button class="button ghost small" type="submit">Alles gelezen</button></form>
|
||||
<form method="post" action="/notifications/read"><button class="button ghost small" type="submit">Mark all read</button></form>
|
||||
{% endif %}
|
||||
{% for item in notifications %}
|
||||
<div class="post-card">
|
||||
<div class="post-meta">{{ dt_ago(item.created_at) }}{% if not item.read_at %}<span class="tag-placeholder tag-draft">Nieuw</span>{% endif %}</div>
|
||||
<div class="post-meta">{{ dt_ago(item.created_at) }}{% if not item.read_at %}<span class="tag-placeholder tag-draft">New</span>{% endif %}</div>
|
||||
<a href="{{ item.url }}">{{ item.message }}</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="empty">Geen meldingen.</p>
|
||||
<p class="empty">No notifications.</p>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<!-- retoor <retoor@molodetz.nl> -->
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<header class="article-header">
|
||||
<span class="label">People</span>
|
||||
<h1>Who writes</h1>
|
||||
<p class="muted">A roll of people, not a member list. Whoever joins shows up here once they have written something.</p>
|
||||
</header>
|
||||
{% for person in people %}
|
||||
<div class="person-row">
|
||||
{% set _user = person %}{% set _size = 48 %}
|
||||
{% include "_avatar_link.html" %}
|
||||
<div class="who"><b>{% include "_user_link.html" %}</b><span>{{ person.bio or 'Writes on the roll.' }}</span></div>
|
||||
<span class="count">{{ person.post_count }} {{ 'post' if person.post_count == 1 else 'posts' }}</span>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="empty">Nobody on the roll yet.</p>
|
||||
{% endfor %}
|
||||
<section class="closing">
|
||||
<p>The next one writes here as soon as they are in.</p>
|
||||
<div class="actions"><a class="button" href="/join">I'm in</a></div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -7,13 +7,13 @@
|
||||
{% include "_avatar_link.html" %}
|
||||
</div>
|
||||
<h1>{{ person.username }}</h1>
|
||||
<p class="muted">{{ person.bio or 'Schrijft op de rol.' }}</p>
|
||||
<p class="muted">{{ person.bio or 'Writes on the roll.' }}</p>
|
||||
</header>
|
||||
<div class="section-head"><span class="label">Berichten</span><span class="muted">{{ person.post_count }}</span></div>
|
||||
<div class="section-head"><span class="label">Posts</span><span class="muted">{{ person.post_count }}</span></div>
|
||||
{% for post in posts %}
|
||||
{% set _card = post %}
|
||||
{% include "_post_card.html" %}
|
||||
{% else %}
|
||||
<p class="empty">Nog niets gepubliceerd.</p>
|
||||
<p class="empty">Nothing published yet.</p>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<div class="byline">
|
||||
{% set _user = author %}{% set _size = 28 %}
|
||||
{% include "_avatar_link.html" %}
|
||||
<span>door {% include "_user_link.html" %}</span>
|
||||
<span>by {% include "_user_link.html" %}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</header>
|
||||
@@ -19,11 +19,11 @@
|
||||
{{ render_content(post.body) | safe }}
|
||||
</div>
|
||||
{% if viewer and is_admin(viewer) %}
|
||||
<div class="actions"><a class="button ghost small" href="/admin/posts/{{ post.uid }}/edit">Bewerken</a></div>
|
||||
<div class="actions"><a class="button ghost small" href="/admin/posts/{{ post.uid }}/edit">Edit</a></div>
|
||||
{% endif %}
|
||||
</article>
|
||||
<section class="closing">
|
||||
<span class="label">Verder lezen</span>
|
||||
<div class="actions"><a class="button ghost" href="/{{ post.topic }}">Terug naar {{ topic_title | lower }}</a></div>
|
||||
<span class="label">Keep reading</span>
|
||||
<div class="actions"><a class="button ghost" href="/{{ post.topic }}">Back to the {{ topic_title | lower }}</a></div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<!-- retoor <retoor@molodetz.nl> -->
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<header class="article-header"><span class="label">Juridisch</span><h1>Privacy</h1></header>
|
||||
<header class="article-header"><span class="label">Legal</span><h1>Privacy</h1></header>
|
||||
<div class="prose">
|
||||
<p>Molodetz verzamelt zo weinig mogelijk.</p>
|
||||
<h2>Aanmeldingen via Binnen</h2>
|
||||
<p>Als je het formulier <em>Ik schrijf mee</em> invult, bewaren we je naam, je contactgegeven, optioneel een link naar je werk en je bericht. Daarnaast bewaren we een niet-omkeerbare hash van je IP-adres om misbruik te herkennen. Alleen beheerders zien dit. We delen het met niemand.</p>
|
||||
<p>Als beheer je link controleert, haalt de server eenmalig de titel van die pagina op. Er gaat daarbij niets van jou naar die site behalve het verzoek zelf.</p>
|
||||
<h2>Bezoek</h2>
|
||||
<p>We tellen per dag hoe vaak een pad wordt opgevraagd, zonder IP-adres of identiteit. Er zijn geen cookies voor lezers. Er zijn geen externe scripts, lettertypen of trackers.</p>
|
||||
<p>Molodetz collects as little as possible.</p>
|
||||
<h2>Join requests</h2>
|
||||
<p>When you fill in the <em>I'm in</em> form, we keep your name, your contact detail, optionally a link to your work, and your message. We also keep a one-way hash of your IP address to recognise abuse. Only administrators see this. We share it with nobody.</p>
|
||||
<p>When an admin checks your link, the server fetches the title of that page once. Nothing about you goes to that site except the request itself.</p>
|
||||
<h2>Visits</h2>
|
||||
<p>We count per day how often a path is requested, without IP address or identity. There are no cookies for readers. There are no external scripts, fonts or trackers.</p>
|
||||
<h2>Accounts</h2>
|
||||
<p>Wie inlogt, krijgt een httponly sessiecookie. We bewaren je laatste activiteit voor de online-indicator en een auditlog van beheeracties.</p>
|
||||
<h2>Verwijderen</h2>
|
||||
<p>Wil je dat je aanmelding verdwijnt? Vraag het aan retoor via retoor@molodetz.nl. Verwijderen gebeurt eerst zacht en daarna definitief.</p>
|
||||
<p>Logging in sets an httponly session cookie. We keep your last activity for the online indicator, and an audit log of admin actions.</p>
|
||||
<h2>Deletion</h2>
|
||||
<p>Want your request gone? Ask retoor at retoor@molodetz.nl. Deletion is soft first and permanent after that.</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<!-- retoor <retoor@molodetz.nl> -->
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<header class="article-header"><span class="label">Account</span><h1>API-sleutel</h1></header>
|
||||
<p class="muted">Gebruik deze sleutel als <code>X-API-KEY</code> header of als <code>Authorization: Bearer</code>. Vernieuwen maakt de oude direct ongeldig.</p>
|
||||
<header class="article-header"><span class="label">Account</span><h1>API key</h1></header>
|
||||
<p class="muted">Use this key as an <code>X-API-KEY</code> header or as <code>Authorization: Bearer</code>. Renewing invalidates the old one immediately.</p>
|
||||
<pre class="log-tail" data-copy>{{ api_key }}</pre>
|
||||
<form method="post" action="/profile/api-key/regenerate" data-confirm="De huidige sleutel wordt ongeldig. Doorgaan?">
|
||||
<button class="button danger" type="submit">Vernieuwen</button>
|
||||
<form method="post" action="/profile/api-key/regenerate" data-confirm="The current key will stop working. Continue?">
|
||||
<button class="button danger" type="submit">Renew</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<!-- retoor <retoor@molodetz.nl> -->
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
{% set viewer = current_user(request) %}
|
||||
<header class="article-header"><span class="label">Legal</span><h1>Terms</h1><p class="muted">Version {{ terms_version }}</p></header>
|
||||
<div class="prose">
|
||||
<p>Molodetz is a small, closed blog roll. Reading is free. Writing is done by people retoor has put on the roll.</p>
|
||||
<p>What is on the roll belongs to its author. Reuse is fine with attribution and a link back.</p>
|
||||
<p>There are no ads, no cross-app tracking, no paywall and no social login. Accounts log in with a session on this site.</p>
|
||||
<p>Abuse of forms, automated sign-ups or attempts to break into admin are logged and refused.</p>
|
||||
</div>
|
||||
{% if viewer %}
|
||||
<form method="post" action="/terms/accept">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<button class="button" type="submit">I accept version {{ terms_version }}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -1,18 +0,0 @@
|
||||
<!-- retoor <retoor@molodetz.nl> -->
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
{% set viewer = current_user(request) %}
|
||||
<header class="article-header"><span class="label">Juridisch</span><h1>Voorwaarden</h1><p class="muted">Versie {{ terms_version }}</p></header>
|
||||
<div class="prose">
|
||||
<p>Molodetz is een kleine, besloten blogrol. Lezen is vrij. Schrijven gebeurt door mensen die retoor op de rol heeft gezet.</p>
|
||||
<p>Wat op de rol staat is van de auteur. Overnemen mag met bronvermelding en een link terug.</p>
|
||||
<p>Er is geen advertentie, geen tracking over apps heen, geen betaalmuur en geen sociale login. Accounts loggen in met een sessie op deze site.</p>
|
||||
<p>Misbruik van formulieren, automatische aanmeldingen of pogingen om beheer binnen te komen worden gelogd en geweigerd.</p>
|
||||
</div>
|
||||
{% if viewer %}
|
||||
<form method="post" action="/voorwaarden/accept">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<button class="button" type="submit">Ik accepteer versie {{ terms_version }}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user