Issues: per-project numbers, statuses (open, accepted, rejected, withdrawn, duplicate), feature links, edits, and a timeline of comments and events. From an issue, collaborators create or update a feature with the revision tied to the issue and an option to accept it. Comments: general and line-anchored threads on features, one discussion thread per issue, edit history, tombstone deletes, resolve, and promoting a feature thread to an issue. Line threads attach to top-level Markdown blocks and go outdated only when the text changes. HTMX swaps threads in place with focus management; every action also works as a plain form post. Markdown headings are re-levelled so user text never skips heading levels on the page. The example flow from concept.md (steps 1-7) is covered end to end in tests/test_example_flow.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
48 lines
2.5 KiB
HTML
48 lines
2.5 KiB
HTML
{% comment %}
|
||
One comment. Needs `c` (prepared by intent.comments.presenters), `thread`, `project`, `focus`.
|
||
Forms post normally; with HTMX they swap the whole thread (`thread.dom_id`) in place.
|
||
{% endcomment %}
|
||
{% with by=c.author.username|default:"deleted user" comment_id="comment-"|add:c.pk %}
|
||
<article id="{{ comment_id }}" class="comment" tabindex="-1"{% if focus == comment_id %} data-autofocus{% endif %}>
|
||
<header>
|
||
<strong>{{ by }}</strong>
|
||
<a href="#{{ comment_id }}"><time datetime="{{ c.created_at|date:'c' }}">{{ c.created_at|date:"j M Y, H:i" }}</time></a>
|
||
{% if c.edited_at and not c.is_deleted %}
|
||
· <a href="{% url 'comments:history' org_slug=project.org.slug project_slug=project.slug comment_id=c.pk %}">edited<span class="visually-hidden"> (see earlier versions of {{ by }}’s comment)</span></a>
|
||
{% endif %}
|
||
</header>
|
||
{% if c.is_deleted %}
|
||
<p class="muted"><em>Comment deleted.</em></p>
|
||
{% else %}
|
||
<div class="prose">{{ c.html }}</div>
|
||
{% endif %}
|
||
{% if c.can_edit or c.can_delete %}
|
||
<div class="actions">
|
||
{% if c.can_edit %}
|
||
{% url 'comments:edit' org_slug=project.org.slug project_slug=project.slug comment_id=c.pk as edit_url %}
|
||
<details>
|
||
<summary>Edit<span class="visually-hidden"> {{ by }}’s comment</span></summary>
|
||
<form method="post" action="{{ edit_url }}" hx-post="{{ edit_url }}" hx-target="#{{ thread.dom_id }}" hx-swap="outerHTML">
|
||
{% csrf_token %}
|
||
<label for="edit-{{ c.pk }}" class="visually-hidden">Comment text</label>
|
||
<textarea id="edit-{{ c.pk }}" name="body" rows="4" required>{{ c.body }}</textarea>
|
||
<button type="submit">Save edit</button>
|
||
</form>
|
||
</details>
|
||
{% endif %}
|
||
{% if c.can_delete %}
|
||
{% url 'comments:delete' org_slug=project.org.slug project_slug=project.slug comment_id=c.pk as delete_url %}
|
||
{# A disclosure rather than a script confirm, so the second step works without JS. #}
|
||
<details>
|
||
<summary>Delete<span class="visually-hidden"> {{ by }}’s comment</span></summary>
|
||
<form method="post" action="{{ delete_url }}" hx-post="{{ delete_url }}" hx-target="#{{ thread.dom_id }}" hx-swap="outerHTML">
|
||
{% csrf_token %}
|
||
<p>The text and its edit history will be removed. This can't be undone.</p>
|
||
<button type="submit">Delete comment</button>
|
||
</form>
|
||
</details>
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
</article>
|
||
{% endwith %}
|