Add kanban updates and static root

This commit is contained in:
rucki
2026-08-22 16:26:46 +02:00
parent b7b5e19a90
commit 40648d10a1
14 changed files with 220 additions and 59 deletions
+3
View File
@@ -0,0 +1,3 @@
<div class="card {% if item.is_done %}done{% endif %}" draggable="true" data-id="{{ item.id }}" @dragstart="drag($event)">
<div class="card-body py-2 small"><a href="{{ item.get_absolute_url }}">#{{ item.id }}</a> {{ item.content|truncatechars:120 }}</div>
</div>
+2 -1
View File
@@ -7,13 +7,14 @@
<title>my2dos</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://unpkg.com/htmx.org@1.9.12"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
<style>
body{background:#f7f7f4}.shell{max-width:960px}.item-card{border:0;border-radius:1rem}.content p:last-child{margin-bottom:0}.tag{font-size:.85rem}.slash-menu{position:absolute;z-index:20;top:100%;left:0;width:18rem}.done{opacity:.6}.done .content{text-decoration:line-through}.content img{max-width:100%;max-height:420px;border-radius:.5rem;border:1px solid #ddd;padding:.25rem;background:white}.overview-content{max-height:calc(var(--overview-lines) * 1.55em);overflow:hidden}.overview-content.expanded{max-height:none}.content{min-width:0;max-width:100%;overflow-wrap:anywhere}.content pre{position:relative;display:block;width:100%;max-width:100%;box-sizing:border-box;background:#1f2937;color:#f9fafb;border-radius:.6rem;padding:1rem;overflow-x:auto;overflow-y:auto;white-space:pre}.content pre code{display:block;color:inherit;white-space:pre;min-width:0}.copy-code{position:absolute;top:.4rem;right:.4rem;line-height:1}
</style>
</head>
<body>
<nav class="navbar bg-white border-bottom sticky-top"><div class="container shell"><a class="navbar-brand fw-bold" href="/">my2dos</a><div class="d-flex align-items-center gap-3"><span class="text-muted small d-none d-sm-inline">Notizen · Aufgaben · Links</span>{% if user.is_authenticated %}<a class="small" href="{% url 'journal' %}">Journal</a><a class="small" href="{% url 'settings' %}">{{ user.get_full_name|default:user.username }}</a><form method="post" action="{% url 'logout' %}" class="m-0">{% csrf_token %}<button class="btn btn-sm btn-outline-secondary">Logout</button></form>{% else %}<a class="btn btn-sm btn-dark" href="{% url 'login' %}">Login</a>{% endif %}</div></div></nav>
<nav class="navbar bg-white border-bottom sticky-top"><div class="container shell"><a class="navbar-brand fw-bold" href="/">my2dos</a><div class="d-flex align-items-center gap-3"><a class="text-muted small d-none d-sm-inline text-decoration-none" href="/">Notizen · Aufgaben · Links</a>{% if user.is_authenticated %}<a class="small" href="{% url 'journal' %}">Journal</a><a class="small" href="{% url 'kanban_list' %}">Kanban</a><div class="dropdown"><button class="btn btn-sm btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">{{ user.get_full_name|default:user.username }}</button><ul class="dropdown-menu dropdown-menu-end"><li><a class="dropdown-item" href="{% url 'settings' %}">Einstellungen</a></li>{% if user.is_staff %}<li><a class="dropdown-item" href="/admin/">Admin</a></li>{% endif %}<li><hr class="dropdown-divider"></li><li><form method="post" action="{% url 'logout' %}">{% csrf_token %}<button class="dropdown-item">Logout</button></form></li></ul></div>{% else %}<a class="btn btn-sm btn-dark" href="{% url 'login' %}">Login</a>{% endif %}</div></div></nav>
<main class="container shell py-4">{% for message in messages %}<div class="alert alert-{{ message.tags|default:'info' }}">{{ message }}</div>{% endfor %}{% block content %}{% endblock %}</main>
<script>
document.body.addEventListener('htmx:configRequest', e => {e.detail.headers['X-CSRFToken']='{{ csrf_token }}'});
-9
View File
@@ -9,7 +9,6 @@
<a class="btn btn-sm {% if active_kind == 'todo' %}btn-primary{% else %}btn-outline-primary{% endif %}" href="{% if active_kind == 'todo' %}{% query_replace kind=None %}{% else %}{% query_replace kind='todo' %}{% endif %}">Todos{% if active_kind == 'todo' %} ×{% endif %}</a>
<a class="btn btn-sm {% if active_kind == 'note' %}btn-secondary{% else %}btn-outline-secondary{% endif %}" href="{% if active_kind == 'note' %}{% query_replace kind=None %}{% else %}{% query_replace kind='note' %}{% endif %}">Notizen{% if active_kind == 'note' %} ×{% endif %}</a>
<a class="btn btn-sm {% if active_kind == 'link' %}btn-success{% else %}btn-outline-success{% endif %}" href="{% if active_kind == 'link' %}{% query_replace kind=None %}{% else %}{% query_replace kind='link' %}{% endif %}">Links{% if active_kind == 'link' %} ×{% endif %}</a>
<a class="btn btn-sm {% if active_kind == 'journal' %}btn-info{% else %}btn-outline-info{% endif %}" href="{% if active_kind == 'journal' %}{% query_replace kind=None day=None %}{% else %}{% query_replace kind='journal' %}{% endif %}">Journal{% if active_kind == 'journal' %} ×{% endif %}</a>
<a class="btn btn-sm {% if active_status == 'open' %}btn-warning{% else %}btn-outline-warning{% endif %}" href="{% if active_status == 'open' %}{% query_replace status=None %}{% else %}{% query_replace status='open' %}{% endif %}">Unerledigt{% if active_status == 'open' %} ×{% endif %}</a>
<a class="btn btn-sm {% if active_status == 'archive' %}btn-dark{% else %}btn-outline-dark{% endif %}" href="{% if active_status == 'archive' %}{% query_replace status=None %}{% else %}{% query_replace status='archive' %}{% endif %}">Archiv{% if active_status == 'archive' %} ×{% endif %}</a>
</div>
@@ -25,14 +24,6 @@
<template x-for="(tag, idx) in filtered" :key="tag"><button type="button" class="list-group-item list-group-item-action" :class="{'active': idx === active}" @mouseenter="active=idx" @mousedown.prevent="select(tag)"><strong x-text="'#'+tag"></strong></button></template>
</div>
</form>
{% if active_kind == 'journal' and journal_days %}
<div class="fw-semibold mb-2">Journal-Kalender</div>
<div class="d-flex flex-wrap gap-1 mb-3">
{% for d in journal_days %}
{% if d.has_entries %}<a class="btn btn-sm {% if active_day == d.date %}btn-info{% else %}btn-outline-info{% endif %}" href="{% query_replace day=d.date %}">{{ d.day }}</a>{% else %}<span class="btn btn-sm btn-light disabled">{{ d.day }}</span>{% endif %}
{% endfor %}
</div>
{% endif %}
<div class="fw-semibold mb-2">Tags</div>
<div hx-get="{% url 'tag_list' %}{% if request.GET.urlencode %}?{{ request.GET.urlencode }}{% endif %}" hx-trigger="tagsChanged from:body" hx-target="#tag-list" hx-swap="outerHTML">
{% include 'core/_tags.html' %}
+28 -19
View File
@@ -1,23 +1,32 @@
{% extends 'core/base.html' %}
{% load markdown_extras %}
{% load querystring %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-3">
<a class="btn btn-sm btn-outline-secondary" href="/">← zurück</a>
<h1 class="h4 m-0">Journal {{ month }}/{{ year }}</h1>
<div class="btn-group btn-group-sm">
<a class="btn btn-outline-dark" href="?year={{ prev_year }}&month={{ prev_month }}">←</a>
<a class="btn btn-outline-dark" href="?year={{ next_year }}&month={{ next_month }}">→</a>
</div>
<div class="row g-4">
<aside class="col-lg-3 order-lg-2">
<div class="card item-card shadow-sm"><div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-2">
<a class="btn btn-sm btn-outline-dark" href="?year={{ prev_year }}&month={{ prev_month }}">←</a>
<strong>{{ month }}/{{ year }}</strong>
<a class="btn btn-sm btn-outline-dark" href="?year={{ next_year }}&month={{ next_month }}">→</a>
</div>
<table class="table table-sm text-center mb-3"><thead><tr><th>Mo</th><th>Di</th><th>Mi</th><th>Do</th><th>Fr</th><th>Sa</th><th>So</th></tr></thead><tbody>
{% for week in weeks %}<tr>{% for d in week %}<td>{% if d.day %}{% if d.has_entries %}<a class="btn btn-sm {% if selected_day == d.date %}btn-info{% else %}btn-outline-info{% endif %}" href="?day={{ d.date }}&year={{ year }}&month={{ month }}">{{ d.day }}</a>{% else %}<span class="small text-muted">{{ d.day }}</span>{% endif %}{% endif %}</td>{% endfor %}</tr>{% endfor %}
</tbody></table>
<div class="fw-semibold mb-2">Tags</div>
{% include 'core/_tags.html' %}
</div></div>
</aside>
<section class="col-lg-9">
<h1 class="h4 mb-3">Journal {{ selected_day }}</h1>
<form class="position-relative mb-4" method="post" enctype="multipart/form-data" hx-post="{{ request.get_full_path }}" hx-target="#items" hx-swap="afterbegin" x-data="quickComposer([{% for tag in tags %}'{{ tag.name|escapejs }}'{% if not forloop.last %},{% endif %}{% endfor %}])" x-on:htmx:after-request="if($event.detail.successful){text='';open=false;$el.reset()}">
{% csrf_token %}
{{ form.content }}
<div class="slash-menu list-group shadow" x-show="open" @click.outside="open=false" x-cloak><template x-for="(cmd, idx) in filtered" :key="cmd.token"><button type="button" class="list-group-item list-group-item-action" :class="{'active': idx === active}" @mouseenter="active=idx" @mousedown.prevent="insert(cmd.token)"><strong x-text="cmd.token"></strong> <span class="text-muted" x-text="cmd.label"></span></button></template></div>
<div class="d-flex align-items-center gap-2 mt-2"><input class="form-control" type="file" name="files" multiple x-ref="fileInput" @change="previewFile($event)"><button class="btn btn-dark px-4">Speichern</button></div>
</form>
<div id="items" class="vstack gap-3">
{% for item in items %}{% include 'core/_item.html' %}{% empty %}<div class="text-center text-muted py-5">Keine Journal-Aktivität an diesem Tag.</div>{% endfor %}
</div>
</section>
</div>
<div class="card item-card shadow-sm"><div class="card-body table-responsive">
<table class="table table-bordered align-top mb-0">
<thead><tr><th>Mo</th><th>Di</th><th>Mi</th><th>Do</th><th>Fr</th><th>Sa</th><th>So</th></tr></thead>
<tbody>
{% for week in weeks %}<tr>{% for day, entries in week %}<td style="height:120px;width:14%" {% if not day %}class="bg-light"{% endif %}>
{% if day %}<div class="fw-semibold small mb-1">{{ day }}</div>{% endif %}
{% for item in entries %}<a class="d-block small text-decoration-none mb-1" href="{{ item.get_absolute_url }}">{{ item.content|truncatechars:55 }}</a>{% endfor %}
</td>{% endfor %}</tr>{% endfor %}
</tbody>
</table>
</div></div>
{% endblock %}
+14
View File
@@ -0,0 +1,14 @@
{% extends 'core/base.html' %}
{% block content %}
<a class="btn btn-sm btn-outline-secondary mb-3" href="{% url 'kanban_list' %}">← Kanbans</a>
<h1 class="h4 mb-3">{{ kanban.name }} <span class="badge text-bg-light">#{{ kanban.tag.name }}</span></h1>
<div class="row g-3" x-data="kanbanBoard({{ kanban.id }})">
<section class="col-lg-3 col-md-6"><div class="card item-card shadow-sm h-100"><div class="card-body"><h2 class="h6">Eingang</h2><div class="kanban-column vstack gap-2 p-2 rounded bg-light" data-status="inbox" @dragover.prevent @drop="drop($event)">{% for item in inbox_items %}{% include 'core/_kanban_card.html' %}{% empty %}<div class="text-muted small">Leer.</div>{% endfor %}</div></div></div></section>
<section class="col-lg-3 col-md-6"><div class="card item-card shadow-sm h-100"><div class="card-body"><h2 class="h6">Todo</h2><div class="kanban-column vstack gap-2 p-2 rounded bg-light" data-status="todo" @dragover.prevent @drop="drop($event)">{% for item in todo_items %}{% include 'core/_kanban_card.html' %}{% empty %}<div class="text-muted small">Leer.</div>{% endfor %}</div></div></div></section>
<section class="col-lg-3 col-md-6"><div class="card item-card shadow-sm h-100"><div class="card-body"><h2 class="h6">In Progress</h2><div class="kanban-column vstack gap-2 p-2 rounded bg-light" data-status="progress" @dragover.prevent @drop="drop($event)">{% for item in progress_items %}{% include 'core/_kanban_card.html' %}{% empty %}<div class="text-muted small">Leer.</div>{% endfor %}</div></div></div></section>
<section class="col-lg-3 col-md-6"><div class="card item-card shadow-sm h-100"><div class="card-body"><h2 class="h6">Erledigt</h2><div class="kanban-column vstack gap-2 p-2 rounded bg-light" data-status="done" @dragover.prevent @drop="drop($event)">{% for item in done_items %}{% include 'core/_kanban_card.html' %}{% empty %}<div class="text-muted small">Leer.</div>{% endfor %}</div></div></div></section>
</div>
<script>
function kanbanBoard(id){return{item:null,drag(e){this.item=e.target.closest('[data-id]')},drop(e){let col=e.target.closest('[data-status]');if(!this.item||!col)return;col.querySelectorAll('.text-muted.small').forEach(e=>e.remove());col.appendChild(this.item);this.item.classList.toggle('done', col.dataset.status==='done');fetch(`/kanban/${id}/move/${this.item.dataset.id}/`,{method:'POST',headers:{'X-CSRFToken':'{{ csrf_token }}','Content-Type':'application/x-www-form-urlencoded'},body:`status=${col.dataset.status}`});}}}
</script>
{% endblock %}
+8
View File
@@ -0,0 +1,8 @@
{% extends 'core/base.html' %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-3"><h1 class="h4 m-0">Kanbans</h1><a class="btn btn-sm btn-outline-secondary" href="/">← zurück</a></div>
<div class="row g-4">
<section class="col-lg-5"><div class="card item-card shadow-sm"><div class="card-body"><h2 class="h5">Neues Kanban</h2><form method="post" class="vstack gap-2">{% csrf_token %}{{ form.name }}{{ form.tag_name }}<button class="btn btn-dark">Anlegen</button></form></div></div></section>
<section class="col-lg-7"><div class="vstack gap-3">{% for s in stats %}<a class="card item-card shadow-sm text-decoration-none text-dark" href="{% url 'kanban_detail' s.kanban.id %}"><div class="card-body"><div class="d-flex justify-content-between"><strong>{{ s.kanban.name }}</strong><span class="badge text-bg-light">#{{ s.kanban.tag.name }}</span></div><div class="small text-muted mt-2">Offen: {{ s.open }} · Erledigt: {{ s.done }}</div></div></a>{% empty %}<div class="text-muted">Noch keine Kanbans.</div>{% endfor %}</div></section>
</div>
{% endblock %}