OXYGENHUB
# 4da8b828a822 oxygenhub / templates / issue.html
14.3 KiB · HTML · last changed Issue page: GitHub-style sidebar menus and a full-width comment box 1 month ago source blame history raw
1
{% extends "base.html" %}
2
{% import "repo_tabs.html" as m %}
3
{% block title %}Issue #{{ i.number }} {{ i.title }} · {{ repo.owner }}/{{ repo.name }}{% endblock %}
4
{% block bodyattrs %} data-repo="{{ repo.url }}"{% endblock %}
5
{% block tabs %}{% call m::tabs(repo, "issues") %}{% endblock %}
6
{% block content %}
7
<div class="head"><h1>{{ i.title }} <small class="px kind-issue">issue #{{ i.number }}</small>
8
  <small><span class="tag{% if i.state == "closed" %} bad{% else %} head{% endif %}">{{ i.state }}{% if !i.reason.is_empty() %}: {{ i.reason.replace("_", " ") }}{% endif %}</span> {{ i.author }} opened this <time title="{{ i.created_ms|when_i }}">{{ i.created_ms|ago_i }}</time> · {{ comments.len()|plural("comment", "comments") }}{% if i.locked %} · <span class="tag">locked</span>{% endif %}</small>
9
</h1></div>
10
{% if !closed_by.is_empty() %}<p class="chain" aria-label="The chain of work"><span class="chain-link kind-issue st-{{ i.state }}"><span class="chain-kind">issue #{{ i.number }}</span> <span class="chain-state">{{ i.state }}</span></span>
11
<span class="chain-arrow">←</span> closed by {% for c in closed_by %}<a class="chain-link kind-proposal st-{{ c.state }}" href="{{ c.url }}" title="{{ c.title }}"><span class="chain-kind">proposal #{{ c.number }}</span> {{ c.title }} <span class="chain-state">{{ c.state }}</span></a>{% if !loop.last %} <span class="chain-and">·</span> {% endif %}{% endfor %}</p>{% endif %}
12
{% if let Some(n) = notice %}<p class="note">{{ n }}</p>{% endif %}
13
{% if let Some(e) = error %}<p class="error">{{ e }}</p>{% endif %}
14
<div class="side-layout">
15
<div>
16
  <div class="comment"><span class="who">{{ i.author }}</span> <time class="mute" title="{{ i.created_ms|when_i }}">{{ i.created_ms|ago_i }}</time>
17
    {% if can_edit %}<details class="inline-edit"><summary class="small">edit</summary><form method="post" action="{{ repo.url }}/issue/{{ i.number }}/edit"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="text" name="title" value="{{ i.title }}"><textarea name="body" rows="6">{{ i.body }}</textarea><button type="submit">SAVE</button></form></details>{% endif %}
18
    <div class="body">{% if i.body.is_empty() %}<p class="mute">No description.</p>{% else %}{{ body_html|safe }}{% endif %}</div></div>
19
  {% for item in timeline %}
20
    {% match item %}
21
    {% when crate::web::issues::Item::Comment with (c, html) %}
22
    <div class="comment" id="c{{ c.id }}"><span class="who">{{ c.author }}</span> <time class="mute" title="{{ c.created_ms|when_i }}">{{ c.created_ms|ago_i }}</time>{% if c.edited_ms.is_some() %} <span class="mute small">edited</span>{% endif %}
23
      {% if let Some(u) = shell.user %}{% if *u == c.author || can_triage %}<form class="tiny" method="post" action="{{ repo.url }}/issue/{{ i.number }}/comment/{{ c.id }}/delete"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><button>delete</button></form>{% endif %}{% endif %}
24
      <div class="body">{{ html|safe }}</div></div>
25
    {% when crate::web::issues::Item::Event with (e) %}
26
    <p class="small mute timeline">{{ e.actor }} {{ e.event }}{% if e.event.ends_with("by change") && !e.detail.is_empty() %}{% let hash = e.detail.split(' ').next().unwrap_or("") %} <a href="{{ repo.url }}/change/{{ hash }}"><code>{{ hash }}</code></a>{{ e.detail[hash.len()..] }}{% else if !e.detail.is_empty() %} {{ e.detail }}{% endif %} · <time title="{{ e.at_ms|when_i }}">{{ e.at_ms|ago_i }}</time></p>
27
    {% endmatch %}
28
  {% endfor %}
29
  {% if shell.user.is_some() && (!i.locked || can_triage) %}
30
  <form class="card comment-form" method="post" action="{{ repo.url }}/issue/{{ i.number }}/comment">
31
    <input type="hidden" name="_csrf" value="{{ shell.csrf }}">
32
    <label for="body">Comment (Markdown)</label><textarea id="body" name="body" class="md" rows="5" required></textarea>
33
    <div class="choice">
34
      <button type="submit">COMMENT</button>
35
      {% if i.state == "open" %}<button type="submit" name="close" value="completed">COMMENT AND CLOSE</button>{% else %}<button type="submit" name="reopen" value="1">COMMENT AND REOPEN</button>{% endif %}
36
    </div>
37
  </form>
38
  {% else if i.locked %}<p class="empty">This issue is locked.</p>{% endif %}
39
</div>
40
<aside class="side side-sections">
41
  {% if shell.user.is_some() %}
42
  <section class="side-sec">
43
    <div class="side-head"><h3>Notifications</h3></div>
44
    <form method="post" action="{{ repo.url }}/issue/{{ i.number }}/subscribe"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><button type="submit" class="small side-wide">{% if subscribed %}Unsubscribe{% else %}Subscribe{% endif %}</button></form>
45
    <p class="side-note">{% if subscribed %}You're receiving notifications because you're subscribed.{% else %}You're not receiving notifications from this issue.{% endif %}</p>
46
  </section>
47
  {% endif %}
48
  <section class="side-sec">
49
    <div class="side-head"><h3>Assignees</h3>
50
    {% if can_triage %}<details class="menu"><summary class="gear" title="Edit assignees" aria-label="Edit assignees"><svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path fill="currentColor" d="M8 0a8.2 8.2 0 0 1 .7.03c.4.04.7.34.76.73l.2 1.37c.33.11.65.24.96.4l1.12-.83a.85.85 0 0 1 1.07.06c.34.3.65.63.94.97.25.3.27.73.06 1.06l-.83 1.12c.16.3.29.63.4.96l1.37.2c.39.06.69.37.73.76a8 8 0 0 1 0 1.4.85.85 0 0 1-.73.76l-1.37.2c-.11.33-.24.65-.4.96l.83 1.12c.21.33.19.76-.06 1.06-.29.34-.6.67-.94.97a.85.85 0 0 1-1.07.06l-1.12-.83c-.3.16-.63.29-.96.4l-.2 1.37a.85.85 0 0 1-.76.73 8 8 0 0 1-1.4 0 .85.85 0 0 1-.76-.73l-.2-1.37a5.6 5.6 0 0 1-.96-.4l-1.12.83a.85.85 0 0 1-1.07-.06 8 8 0 0 1-.94-.97.85.85 0 0 1-.06-1.06l.83-1.12a5.6 5.6 0 0 1-.4-.96l-1.37-.2A.85.85 0 0 1 0 8.7a8 8 0 0 1 0-1.4c.04-.39.34-.7.73-.76l1.37-.2c.11-.33.24-.65.4-.96l-.83-1.12a.85.85 0 0 1 .06-1.06c.29-.34.6-.67.94-.97a.85.85 0 0 1 1.07-.06l1.12.83c.3-.16.63-.29.96-.4l.2-1.37A.85.85 0 0 1 7.3.03 8 8 0 0 1 8 0Zm0 5a3 3 0 1 0 0 6 3 3 0 0 0 0-6Z"/></svg></summary>
51
      <form class="menu-pop" method="post" action="{{ repo.url }}/issue/{{ i.number }}/assign"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="action" value="set">
52
        <p class="menu-title">Assign people to this issue</p>
53
        <input class="menu-filter" type="search" placeholder="Filter people" aria-label="Filter people" data-menu-filter>
54
        <div class="menu-list">{% for u in assignable %}<label class="menu-item"><input type="checkbox" name="username" value="{{ u }}"{% if assignees.contains(u) %} checked{% endif %}> <img class="avatar xs" src="/avatar/{{ u }}" alt="" loading="lazy"> <span class="menu-name">{{ u }}</span></label>{% endfor %}
55
        {% if assignable.is_empty() %}<p class="menu-empty">Nobody with access to this repository yet.</p>{% endif %}</div>
56
        <div class="menu-foot"><button type="submit" class="small primary-btn">Apply</button></div>
57
      </form></details>{% endif %}</div>
58
    {% if assignees.is_empty() %}<p class="side-empty">No one{% if can_triage %}{% if let Some(me) = shell.user %}: <form class="inline" method="post" action="{{ repo.url }}/issue/{{ i.number }}/assign"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="username" value="{{ me }}"><button name="action" value="add" class="linkish">assign yourself</button></form>{% endif %}{% endif %}</p>
59
    {% else %}<ul class="side-people">{% for a in assignees %}<li><a href="/{{ a }}"><img class="avatar xs" src="/avatar/{{ a }}" alt="" loading="lazy"> {{ a }}</a></li>{% endfor %}</ul>{% endif %}
60
  </section>
61
  <section class="side-sec">
62
    <div class="side-head"><h3>Labels</h3>
63
    {% if can_triage %}<details class="menu"><summary class="gear" title="Edit labels" aria-label="Edit labels"><svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path fill="currentColor" d="M8 0a8.2 8.2 0 0 1 .7.03c.4.04.7.34.76.73l.2 1.37c.33.11.65.24.96.4l1.12-.83a.85.85 0 0 1 1.07.06c.34.3.65.63.94.97.25.3.27.73.06 1.06l-.83 1.12c.16.3.29.63.4.96l1.37.2c.39.06.69.37.73.76a8 8 0 0 1 0 1.4.85.85 0 0 1-.73.76l-1.37.2c-.11.33-.24.65-.4.96l.83 1.12c.21.33.19.76-.06 1.06-.29.34-.6.67-.94.97a.85.85 0 0 1-1.07.06l-1.12-.83c-.3.16-.63.29-.96.4l-.2 1.37a.85.85 0 0 1-.76.73 8 8 0 0 1-1.4 0 .85.85 0 0 1-.76-.73l-.2-1.37a5.6 5.6 0 0 1-.96-.4l-1.12.83a.85.85 0 0 1-1.07-.06 8 8 0 0 1-.94-.97.85.85 0 0 1-.06-1.06l.83-1.12a5.6 5.6 0 0 1-.4-.96l-1.37-.2A.85.85 0 0 1 0 8.7a8 8 0 0 1 0-1.4c.04-.39.34-.7.73-.76l1.37-.2c.11-.33.24-.65.4-.96l-.83-1.12a.85.85 0 0 1 .06-1.06c.29-.34.6-.67.94-.97a.85.85 0 0 1 1.07-.06l1.12.83c.3-.16.63-.29.96-.4l.2-1.37A.85.85 0 0 1 7.3.03 8 8 0 0 1 8 0Zm0 5a3 3 0 1 0 0 6 3 3 0 0 0 0-6Z"/></svg></summary>
64
      <form class="menu-pop" method="post" action="{{ repo.url }}/issue/{{ i.number }}/labels"><input type="hidden" name="_csrf" value="{{ shell.csrf }}">
65
        <p class="menu-title">Apply labels to this issue</p>
66
        {% if !all_labels.is_empty() %}<input class="menu-filter" type="search" placeholder="Filter labels" aria-label="Filter labels" data-menu-filter>{% endif %}
67
        <div class="menu-list">{% for l in all_labels %}<label class="menu-item"><input type="checkbox" name="label" value="{{ l.id }}"{% if labels.contains(l) %} checked{% endif %}> <span class="swatch" style="--c: {{ l.colour }}"></span> <span class="menu-name">{{ l.name }}{% if !l.description.is_empty() %}<small>{{ l.description }}</small>{% endif %}</span></label>{% endfor %}
68
        {% if all_labels.is_empty() %}<p class="menu-empty">This repository has no labels yet.</p>{% endif %}</div>
69
        <div class="menu-foot"><a class="small" href="{{ repo.url }}/labels">Edit labels</a> <button type="submit" class="small primary-btn">Apply</button></div>
70
      </form></details>{% endif %}</div>
71
    {% if labels.is_empty() %}<p class="side-empty">None yet</p>{% else %}<p class="side-labels">{% for l in labels %}<span class="label" style="--c: {{ l.colour }}" title="{{ l.description }}">{{ l.name }}</span> {% endfor %}</p>{% endif %}
72
  </section>
73
  <section class="side-sec">
74
    <div class="side-head"><h3>Milestone</h3>
75
    {% if can_triage && !milestones.is_empty() %}<details class="menu"><summary class="gear" title="Set milestone" aria-label="Set milestone"><svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path fill="currentColor" d="M8 0a8.2 8.2 0 0 1 .7.03c.4.04.7.34.76.73l.2 1.37c.33.11.65.24.96.4l1.12-.83a.85.85 0 0 1 1.07.06c.34.3.65.63.94.97.25.3.27.73.06 1.06l-.83 1.12c.16.3.29.63.4.96l1.37.2c.39.06.69.37.73.76a8 8 0 0 1 0 1.4.85.85 0 0 1-.73.76l-1.37.2c-.11.33-.24.65-.4.96l.83 1.12c.21.33.19.76-.06 1.06-.29.34-.6.67-.94.97a.85.85 0 0 1-1.07.06l-1.12-.83c-.3.16-.63.29-.96.4l-.2 1.37a.85.85 0 0 1-.76.73 8 8 0 0 1-1.4 0 .85.85 0 0 1-.76-.73l-.2-1.37a5.6 5.6 0 0 1-.96-.4l-1.12.83a.85.85 0 0 1-1.07-.06 8 8 0 0 1-.94-.97.85.85 0 0 1-.06-1.06l.83-1.12a5.6 5.6 0 0 1-.4-.96l-1.37-.2A.85.85 0 0 1 0 8.7a8 8 0 0 1 0-1.4c.04-.39.34-.7.73-.76l1.37-.2c.11-.33.24-.65.4-.96l-.83-1.12a.85.85 0 0 1 .06-1.06c.29-.34.6-.67.94-.97a.85.85 0 0 1 1.07-.06l1.12.83c.3-.16.63-.29.96-.4l.2-1.37A.85.85 0 0 1 7.3.03 8 8 0 0 1 8 0Zm0 5a3 3 0 1 0 0 6 3 3 0 0 0 0-6Z"/></svg></summary>
76
      <form class="menu-pop" method="post" action="{{ repo.url }}/issue/{{ i.number }}/milestone"><input type="hidden" name="_csrf" value="{{ shell.csrf }}">
77
        <p class="menu-title">Set milestone</p>
78
        <div class="menu-list"><label class="menu-item"><input type="radio" name="milestone" value=""{% if milestone.is_none() %} checked{% endif %}> <span class="menu-name">No milestone</span></label>
79
        {% for (ms, sel) in milestones %}<label class="menu-item"><input type="radio" name="milestone" value="{{ ms.id }}"{% if *sel %} checked{% endif %}> <span class="menu-name">{{ ms.title }}</span></label>{% endfor %}</div>
80
        <div class="menu-foot"><button type="submit" class="small primary-btn">Apply</button></div>
81
      </form></details>{% endif %}</div>
82
    {% if let Some(ms) = milestone %}<p class="side-value"><a href="{{ repo.url }}/milestones">{{ ms.title }}</a></p>{% else %}<p class="side-empty">No milestone</p>{% endif %}
83
  </section>
84
  <section class="side-sec">
85
    <div class="side-head"><h3>Projects</h3></div>
86
    {% if projects.is_empty() %}<p class="side-empty">None yet</p>{% else %}<ul class="plain small side-list">{% for (pr, col) in projects %}<li><a href="/projects/{{ pr.owner }}/{{ pr.number }}">{{ pr.title }}</a> <span class="mute">· {{ col }}</span>{% if project_choices.is_some() %}<form class="inline" method="post" action="{{ repo.url }}/issue/{{ i.number }}/project"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="project" value="{{ pr.number }}"><button name="action" value="remove" class="linkish" title="Take it off this project">remove</button></form>{% endif %}</li>{% endfor %}</ul>{% endif %}
87
    {% if let Some(choices) = project_choices %}{% if choices.is_empty() %}{% if projects.is_empty() %}<p class="side-note"><a href="/projects/{{ repo.owner }}">{{ repo.owner }}'s projects</a></p>{% endif %}{% else %}<form class="side-picker-inline" method="post" action="{{ repo.url }}/issue/{{ i.number }}/project"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><select name="project" aria-label="Project">{% for pr in choices %}<option value="{{ pr.number }}">{{ pr.title }}</option>{% endfor %}</select> <button name="action" value="add" class="small">Add</button></form>{% endif %}{% endif %}
88
  </section>
89
  {% if can_triage %}
90
  <section class="side-sec side-actions">
91
    <form method="post" action="{{ repo.url }}/issue/{{ i.number }}/state"><input type="hidden" name="_csrf" value="{{ shell.csrf }}">
92
    {% if i.state == "open" %}<button type="submit" name="action" value="close" class="side-action"><span aria-hidden="true">✓</span> Close as completed</button>
93
    <button type="submit" name="action" value="not_planned" class="side-action"><span aria-hidden="true">⊘</span> Close as not planned</button>{% else %}<button type="submit" name="action" value="reopen" class="side-action"><span aria-hidden="true">↺</span> Reopen issue</button>{% endif %}
94
    <button type="submit" name="action" value="{% if i.locked %}unlock{% else %}lock{% endif %}" class="side-action"><span aria-hidden="true">🔒</span> {% if i.locked %}Unlock conversation{% else %}Lock conversation{% endif %}</button>
95
    <button type="submit" name="action" value="{% if i.pinned %}unpin{% else %}pin{% endif %}" class="side-action"><span aria-hidden="true">📌</span> {% if i.pinned %}Unpin issue{% else %}Pin issue{% endif %}</button>
96
    </form>
97
  </section>
98
  {% endif %}
99
</aside>
100
</div>
101
{% endblock %}