Add automatic tag collections

This commit is contained in:
rucki
2026-08-27 16:48:56 +02:00
parent 2bcd9d901a
commit 682f5f7442
11 changed files with 197 additions and 56 deletions
+10 -11
View File
@@ -9,30 +9,29 @@
<a class="btn btn-sm btn-outline-secondary mb-3" href="{% if user.is_authenticated %}{% url 'collection_list' %}{% else %}/{% endif %}">← {% if user.is_authenticated %}{% trans "Collections" %}{% else %}Start{% endif %}</a>
<h1 class="display-5 mb-3">{{ collection.title }}</h1>
<div class="d-flex flex-wrap gap-2 align-items-center">
{% if collection.mode == 'tags' %}<span class="badge text-bg-info">{% trans "Tag collection" %}</span>{% endif %}
{% if collection.team %}<span class="badge text-bg-warning">{{ collection.team.name }}</span>{% else %}<span class="badge text-bg-light">{{ collection.get_visibility_display }}</span>{% endif %}
{% for tag in collection.tags.all %}<span class="badge rounded-pill text-bg-light">#{{ tag.name }}</span>{% endfor %}
{% for tag in collection.filter_tags.all %}<span class="badge rounded-pill text-bg-light">#{{ tag.name }}</span>{% endfor %}
</div>
</div>
{% if can_edit %}<a class="btn btn-dark" href="{% url 'collection_manage' collection.id %}">{% trans "Edit collection" %}</a>{% endif %}
{% if can_edit %}<a class="btn btn-dark" href="{% if collection.mode == 'tags' %}{% url 'collection_edit' collection.id %}{% else %}{% url 'collection_manage' collection.id %}{% endif %}">{% trans "Edit collection" %}</a>{% endif %}
</div>
{% if collection.description %}<div class="content lead mt-4">{{ collection.description|markdown }}</div>{% endif %}
</header>
{% if sections %}
{% if sections or tag_items %}
<nav class="collection-toc mb-5" aria-label="{% trans 'Contents' %}">
<div class="small fw-semibold text-uppercase text-muted mb-2">{% trans "Contents" %}</div>
<ol class="mb-0">{% for section in sections %}<li><a href="#section-{{ section.id }}">{{ section.title|default:section.item.content|truncatechars:90 }}</a></li>{% endfor %}</ol>
<ol class="mb-0">{% if collection.mode == 'tags' %}{% for item in tag_items %}<li><a href="#item-{{ item.id }}">{{ item.content|truncatechars:90 }}</a></li>{% endfor %}{% else %}{% for section in sections %}<li><a href="#section-{{ section.id }}">{{ section.title|default:section.item.content|truncatechars:90 }}</a></li>{% endfor %}{% endif %}</ol>
</nav>
{% endif %}
<div class="collection-flow content">
{% for section in sections %}
<section id="section-{{ section.id }}" class="collection-flow-section">
{% if section.title %}<h2>{{ section.title }}</h2>{% endif %}
{{ section.item|item_markdown }}
{% if section.item.comment %}<div class="text-muted border-start ps-3 mt-3">{{ section.item.comment|markdown }}</div>{% endif %}
</section>
{% empty %}<div class="text-center text-muted py-5">{% trans "This collection has no sections yet." %}</div>{% endfor %}
{% if collection.mode == 'tags' %}
{% for item in tag_items %}<section id="item-{{ item.id }}" class="collection-flow-section">{{ item|item_markdown }}{% if item.comment %}<div class="text-muted border-start ps-3 mt-3">{{ item.comment|markdown }}</div>{% endif %}</section>{% empty %}<div class="text-center text-muted py-5">{% trans "No notes match the selected tags yet." %}</div>{% endfor %}
{% else %}
{% for section in sections %}<section id="section-{{ section.id }}" class="collection-flow-section">{% if section.title %}<h2>{{ section.title }}</h2>{% endif %}{{ section.item|item_markdown }}{% if section.item.comment %}<div class="text-muted border-start ps-3 mt-3">{{ section.item.comment|markdown }}</div>{% endif %}</section>{% empty %}<div class="text-center text-muted py-5">{% trans "This collection has no sections yet." %}</div>{% endfor %}
{% endif %}
</div>
</article>
{% endblock %}
+5 -3
View File
@@ -4,14 +4,16 @@
<div class="d-md-none alert alert-info">{% trans "Collections are currently available on desktop only." %}</div>
<div class="d-none d-md-block">
<a class="btn btn-sm btn-outline-secondary mb-3" href="{% if collection %}{{ collection.get_absolute_url }}{% else %}{% url 'collection_list' %}{% endif %}">← {% trans "back" %}</a>
<form method="post" class="card item-card"><div class="card-body vstack gap-3">
<form method="post" class="card item-card" x-data="{mode:'{{ form.mode.value|default:'manual'|escapejs }}'}"><div class="card-body vstack gap-3">
<h1 class="h3 mb-0">{{ heading }}</h1>
{% csrf_token %}
{% if form.non_field_errors %}<div class="alert alert-danger mb-0">{{ form.non_field_errors }}</div>{% endif %}
<div><label class="form-label">{{ form.title.label }}</label>{{ form.title }}{{ form.title.errors }}</div>
<div><label class="form-label">{{ form.description.label }}</label>{{ form.description }}{{ form.description.errors }}</div>
<div><label class="form-label">{{ form.visibility.label }}</label>{{ form.visibility }}{{ form.visibility.errors }}<div class="form-text">{% trans "Optional. Selecting a team makes the collection visible and editable for that team." %}</div></div>
<div><label class="form-label">{{ form.tags.label }}</label><div class="border rounded p-3 collection-tags">{% if form.tags.field.queryset.exists %}{{ form.tags }}{% else %}<span class="text-muted small">{% trans "You do not belong to a team." %}</span>{% endif %}</div>{{ form.tags.errors }}</div>
<div><label class="form-label">{{ form.mode.label }}</label>{{ form.mode }}{{ form.mode.errors }}<div class="form-text">{% trans "Tag collections update automatically and are sorted by note creation time." %}</div></div>
<div><label class="form-label">{{ form.visibility.label }}</label>{{ form.visibility }}{{ form.visibility.errors }}</div>
<div><label class="form-label">{{ form.team.label }}</label>{{ form.team }}{{ form.team.errors }}<div class="form-text">{% trans "Optional. Selecting a team makes the collection visible and editable for that team." %}</div></div>
<div x-show="mode === 'tags'"><label class="form-label">{{ form.filter_tags.label }}</label><div class="border rounded p-3 collection-tags">{% if form.filter_tags.field.queryset.exists %}{{ form.filter_tags }}{% else %}<span class="text-muted small">{% trans "No content tags are available yet." %}</span>{% endif %}</div>{{ form.filter_tags.errors }}<div class="form-text">{% trans "Notes must contain all selected tags. Team tags are not content filters." %}</div></div>
<div class="d-flex gap-2"><button class="btn btn-dark">{% trans "Save" %}</button><a class="btn btn-outline-secondary" href="{% if collection %}{{ collection.get_absolute_url }}{% else %}{% url 'collection_list' %}{% endif %}">{% trans "Cancel" %}</a></div>
</div></form>
</div>
+5 -4
View File
@@ -8,13 +8,14 @@
<a class="btn btn-dark" href="{% url 'collection_create' %}">{% trans "New collection" %}</a>
</div>
<div class="row g-3">
{% for collection in collections %}
{% for entry in entries %}{% with collection=entry.collection %}
<div class="col-md-6"><a class="card item-card h-100 text-decoration-none text-dark" href="{{ collection.get_absolute_url }}"><div class="card-body">
<div class="d-flex justify-content-between gap-2"><h2 class="h5 mb-1">{{ collection.title }}</h2>{% if collection.team %}<span class="badge text-bg-warning">{{ collection.team.name }}</span>{% else %}<span class="badge text-bg-light">{{ collection.get_visibility_display }}</span>{% endif %}</div>
<div class="d-flex justify-content-between gap-2"><h2 class="h5 mb-1">{{ collection.title }}</h2><div class="d-flex gap-1">{% if collection.mode == 'tags' %}<span class="badge text-bg-info">{% trans "Tag collection" %}</span>{% endif %}{% if collection.team %}<span class="badge text-bg-warning">{{ collection.team.name }}</span>{% else %}<span class="badge text-bg-light">{{ collection.get_visibility_display }}</span>{% endif %}</div></div>
{% if collection.description %}<p class="text-muted mt-2 mb-2">{{ collection.description|truncatechars:140 }}</p>{% endif %}
<div class="small text-muted">{{ collection.sections.count }} {% trans "sections" %} · {{ collection.updated_at|date:'d.m.Y H:i' }}</div>
{% if collection.mode == 'tags' %}<div class="mb-2">{% for tag in collection.filter_tags.all %}<span class="badge rounded-pill text-bg-light">#{{ tag.name }}</span> {% endfor %}</div>{% endif %}
<div class="small text-muted">{{ entry.section_count }} {% trans "sections" %} · {{ collection.updated_at|date:'d.m.Y H:i' }}</div>
</div></a></div>
{% empty %}<div class="col-12 text-center text-muted py-5">{% trans "No collections yet." %}</div>{% endfor %}
{% endwith %}{% empty %}<div class="col-12 text-center text-muted py-5">{% trans "No collections yet." %}</div>{% endfor %}
</div>
</div>
{% endblock %}
+1 -1
View File
@@ -9,7 +9,7 @@
<h1 class="display-6 mb-2">{{ collection.title }}</h1>
<div class="d-flex flex-wrap gap-2 align-items-center">
{% if collection.team %}<span class="badge text-bg-warning">{{ collection.team.name }}</span>{% else %}<span class="badge text-bg-light">{{ collection.get_visibility_display }}</span>{% endif %}
{% for tag in collection.tags.all %}<span class="badge rounded-pill text-bg-light">#{{ tag.name }}</span>{% endfor %}
{% for tag in collection.filter_tags.all %}<span class="badge rounded-pill text-bg-light">#{{ tag.name }}</span>{% endfor %}
</div>
</div>
{% if can_edit %}<div class="d-flex gap-2"><a class="btn btn-outline-secondary" href="{% url 'collection_edit' collection.id %}">{% trans "Collection settings" %}</a>{% if can_delete %}<form method="post" action="{% url 'collection_delete' collection.id %}" onsubmit="return confirm('{% trans "Delete collection? The notes remain available." %}')">{% csrf_token %}<button class="btn btn-outline-danger">{% trans "Delete" %}</button></form>{% endif %}</div>{% endif %}