intent/templates/comments/_thread.html
Alec Perkins 5811e72a0c Add issues and comment threads (Phase 3)
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>
2026-10-27 09:18:45 -04:00

61 lines
3.1 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% comment %}
A feature or line thread. Needs `thread` (prepared), `project`, `access`, `focus`, `error`.
Resolved threads start collapsed. This whole element is what HTMX swaps.
{% endcomment %}
<div id="{{ thread.dom_id }}" class="thread" tabindex="-1"{% if focus == thread.dom_id %} data-autofocus{% endif %}>
{% if error %}<p role="alert"><strong>Error:</strong> {{ error }}</p>{% endif %}
<details{% if not thread.is_resolved or error or focus %} open{% endif %}>
<summary>
Thread by {{ thread.author.username|default:"deleted user" }},
{{ thread.live_count }} comment{{ thread.live_count|pluralize }}{% if thread.promoted_to %}, became issue {{ thread.promoted_to.key }}{% elif thread.is_resolved %}, resolved{% endif %}
</summary>
{% if thread.kind == "line" and thread.outdated %}
<p class="muted">
On lines {{ thread.line_start }}–{{ thread.line_end }} of
<a href="{% url 'features:revision' org_slug=project.org.slug project_slug=project.slug feature_key=thread.feature.key revision=thread.revision %}#L{{ thread.line_start }}-{{ thread.line_end }}">revision {{ thread.revision }}</a>,
which have changed since:
</p>
<pre class="quote">{{ thread.quoted }}</pre>
{% endif %}
{% if thread.promoted_to %}
<p>Continued in issue <a href="{% url 'issues:detail' org_slug=project.org.slug project_slug=project.slug number=thread.promoted_to.number %}">{{ thread.promoted_to.key }} {{ thread.promoted_to.title }}</a>.</p>
{% endif %}
{% for c in thread.comment_list %}
{% include "comments/_comment.html" %}
{% endfor %}
{% if access.can_contribute %}
{% url 'comments:reply' org_slug=project.org.slug project_slug=project.slug thread_id=thread.pk as reply_url %}
<form method="post" action="{{ reply_url }}" hx-post="{{ reply_url }}" hx-target="#{{ thread.dom_id }}" hx-swap="outerHTML">
{% csrf_token %}
<label for="reply-{{ thread.pk }}">Reply</label>
<textarea id="reply-{{ thread.pk }}" name="body" rows="3" required>{{ thread.draft }}</textarea>
<button type="submit">Reply</button>
</form>
{% endif %}
{% if thread.can_resolve or thread.can_promote %}
<div class="actions">
{% if thread.can_resolve %}
{% url 'comments:resolve' org_slug=project.org.slug project_slug=project.slug thread_id=thread.pk as resolve_url %}
<form method="post" action="{{ resolve_url }}" hx-post="{{ resolve_url }}" hx-target="#{{ thread.dom_id }}" hx-swap="outerHTML">
{% csrf_token %}
{% if thread.is_resolved %}
<input type="hidden" name="resolved" value="0">
<button type="submit">Reopen thread</button>
{% else %}
<input type="hidden" name="resolved" value="1">
<button type="submit">Resolve thread</button>
{% endif %}
</form>
{% endif %}
{% if thread.can_promote %}
<a href="{% url 'comments:promote' org_slug=project.org.slug project_slug=project.slug thread_id=thread.pk %}">Turn into an issue</a>
{% endif %}
</div>
{% endif %}
</details>
</div>