todo.sr.ht/todosrht/templates/ticket.html

463 lines
14 KiB
HTML

{% extends "todo.html" %}
{% block title %}
<title>
{{tracker.name}}/#{{ticket.scoped_id}}:
{{ticket.title}}
&mdash;
{{ cfg("sr.ht", "site-name") }} todo
</title>
{% endblock %}
{% block body %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<h2 style="margin: 0;">
<span class="d-block d-md-inline">
<a href="{{ tracker.owner|user_url }}"
>{{ tracker.owner }}</a>/<a href="{{ tracker|tracker_url }}"
>{{ tracker.name }}</a>/#{{ ticket.scoped_id }}<span
class="d-none d-md-inline">:</span>
</span>
<span class="d-block d-md-inline">
{{ticket.title}}
</span>
</h2>
</div>
</div>
</div>
<div class="header-tabbed">
<div class="container">
<div class="row">
<div class="col-md-12 header-tabbed" style="border: none; margin: 0;">
<ul class="nav nav-tabs">
<li class="nav-item">
<a href="{{ ticket|ticket_url }}"
class="nav-link active">view</a>
</li>
{% if TicketAccess.edit in access %}
<li class="nav-item">
<a href="{{ ticket|ticket_edit_url }}"
class="nav-link">edit</a>
</li>
{% endif %}
{% if current_user %}
<li class="flex-grow-1 d-none d-sm-block"></li>
<li class="nav-item d-none d-sm-block">
{% if not tracker_sub %}
<form method="POST" action="{{url_for("ticket." +
("disable_notifications" if ticket_sub else "enable_notifications"),
owner=tracker.owner.canonical_name(),
name=tracker.name,
ticket_id=ticket.scoped_id)}}">
{{csrf_token()}}
{% else %}
<div>
{% endif %}
<button
class="nav-link active"
{% if tracker_sub %}
title="You are subscribed to all activity on this tracker"
disabled
{% else %}
type="submit"
{% endif %}
>
{{icon("envelope-o")}}
{% if ticket_sub or tracker_sub %}
Disable notifications
{% else %}
Enable notifications
{% endif %}
{{icon("caret-right")}}
</button>
{% if not tracker_sub %}
</form>
{% else %}
</div>
{% endif %}
</li>
{% endif %}
</ul>
</div>
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-md-6">
{% if ticket.description %}
{{ ticket.description | extended_md(baselevel=4) }}
{% endif %}
<dl class="row" style="margin-top: 1rem">
<dt class="col-md-3">Status</dt>
<dd class="col-md-9">
<strong class="text-success">
{{ ticket.status.name.upper() }}
{% if ticket.status == TicketStatus.resolved %}
{{ ticket.resolution.name.upper() }}
{% endif %}
</strong>
</dd>
<dt class="col-md-3">Submitter</dt>
<dd class="col-md-9">
<a href="{{ ticket.submitter|user_url }}">
{{ ticket.submitter }}
</a>
</dd>
<dt class="col-md-3">Assigned to</dt>
<dd class="col-md-9">
{% for assignee in ticket.assigned_users %}
<div class="row">
<div class="col">
<a href="{{ assignee|user_url }}">{{ assignee }}</a>
</div>
<div class="col">
{% if TicketAccess.edit in access %}
<form
method="POST"
action="{{ ticket|ticket_unassign_url }}"
style="margin-bottom: .2rem"
>
{{ csrf_token() }}
<input
type="hidden"
name="username"
value="~{{ assignee.username }}" />
<button
type="submit"
class="btn btn-link btn-block"
style="text-align: right"
>(unassign)</button>
</form>
{% endif %}
</div>
</div>
{% endfor %}
{% if TicketAccess.triage in access %}
<details class="assign" {{"open" if not valid.ok else ""}}>
<summary>
Assign someone
</summary>
<form
method="POST"
action="{{ ticket|ticket_assign_url }}"
style="margin-bottom: 0"
>
{{ csrf_token() }}
<div class="form-group">
<input
id="assignee-input"
type="text"
name="username"
autocomplete="off"
list="assignee-list"
class="form-control {{valid.cls("username")}}"
value="{{username}}" />
<datalist id="assignee-list">
{% for u in recent_users %}
<option value="~{{ u }}" />
{% endfor %}
</datalist>
{{valid.summary("username")}}
</div>
<div class="pull-right">
<button
name="myself"
class="btn btn-default"
>Assign myself {{ icon('caret-right') }}</button>
<button class="btn btn-primary">
Assign {{ icon('caret-right') }}
</button>
</div>
<div class="clearfix"></div>
</form>
</details>
{% endif %}
</dd>
<dt class="col-md-3">Submitted</dt>
<dd class="col-md-9">{{ ticket.created | date }}</dd>
<dt class="col-md-3">Updated</dt>
<dd class="col-md-9">{{ ticket.updated | date }}</dd>
<dt class="col-md-3">Labels</dt>
<dd class="col-md-9">
{% for label in ticket.labels %}
{% if TicketAccess.edit in access %}
{{ label|label_badge(remove_from_ticket=ticket) }}
{% else %}
{{ label|label_badge }}
{% endif %}
{% else %}
No labels applied.
{% endfor %}
</dd>
{% if TicketAccess.edit in access
and tracker.labels|count > ticket.labels|count %}
<dd class="col-md-9 offset-md-3">
<form
method="POST"
action="{{
url_for(".ticket_add_label",
owner=tracker.owner.canonical_name(),
name=tracker.name,
ticket_id=ticket.scoped_id,
)
}}">
{{csrf_token()}}
<select
id="label_id"
name="label_id"
class="form-control {{ valid.cls("label_id") }}"
>
<option value="">-- Pick one --</option>
{% for label in tracker.labels if label not in ticket.labels %}
<option
value="{{ label.id }}"
style="color: {{ label.text_color }};
background-color: {{ label.color }}">
{{ label.name }}
</option>
{% endfor %}
</select>
<button type="submit" class="btn btn-default">
Add label {{icon('caret-right')}}
</button>
{{ valid.summary('label_id') }}
</form>
</dd>
{% endif %}
</dl>
</div>
<div class="col-md-6">
{% for event in ticket.events %}
{% if event.event_type not in [
EventType.created,
EventType.assigned_user,
EventType.unassigned_user,
] %}
<h4>
<a href="{{ event.user|user_url }}">
{{ event.user }}
</a>
<span class="pull-right">
<small>{{ event.created | date }}</small>
</span>
</h4>
{% endif %}
{% if EventType.comment in event.event_type %}
{% set comment = event.comment %}
{{ comment.text | md }}
{% endif %}
{% if EventType.status_change in event.event_type %}
<p>
<strong class="text-success">
{{ event.old_status.name.upper() }}
{% if event.old_status == TicketStatus.resolved %}
{{ event.old_resolution.name.upper() }}
{% endif %}
</strong>
{{icon("arrow-right", cls="sm")}}
<strong class="text-success">
{{ event.new_status.name.upper() }}
{% if event.new_status == TicketStatus.resolved %}
{{ event.new_resolution.name.upper() }}
{% endif %}
</strong>
</p>
{% endif %}
{% if EventType.label_added in event.event_type %}
<p>
{{ event.label|label_badge() }}
<span class="text-muted">added</span>
</p>
{% endif %}
{% if EventType.label_removed in event.event_type %}
<p>
{{ event.label|label_badge() }}
<span class="text-muted">removed</span>
</p>
{% endif %}
{% if EventType.assigned_user in event.event_type %}
<h4 style="font-size: 1rem">
<a href="{{event.user|user_url}}">
{{event.user}}
</a>
assigned
<a href="{{event.assigned_user|user_url}}">
{{event.assigned_user}}
</a>
<span class="pull-right">
<small>{{ event.created | date }}</small>
</span>
</h4>
{% endif %}
{% if EventType.unassigned_user in event.event_type %}
<h4 style="font-size: 1rem">
<a href="{{event.user|user_url}}">
{{event.user}}
</a>
unassigned
<a href="{{event.assigned_user|user_url}}">
{{event.assigned_user}}
</a>
<span class="pull-right">
<small>{{ event.created | date }}</small>
</span>
</h4>
{% endif %}
{% endfor %}
{% if TicketAccess.comment in access %}
<form
{% if any(ticket.comments) %}
style="margin-top: 1rem"
{% endif %}
method="POST"
action="{{
url_for(".ticket_comment_POST",
owner=tracker.owner.canonical_name(),
name=tracker.name,
ticket_id=ticket.scoped_id
)
}}">
{{csrf_token()}}
<div class="form-group" style="margin-bottom: 0.25rem">
<textarea
class="form-control {{ valid.cls("comment") }}"
id="comment"
name="comment"
placeholder="Markdown supported"
maxlength="16384"
rows="5">{{ comment or "" }}</textarea>
{{valid.summary("comment")}}
</div>
<button
type="submit"
class="btn btn-primary"
>
Comment {{icon("caret-right")}}
</button>
{% if TicketAccess.edit in access %}
{% if ticket.status != TicketStatus.resolved %}
<button
type="submit"
class="btn btn-success"
name="resolve"
value="resolve"
>
Resolve {{icon("check")}}
</button>
<select class="form-control" name="resolution">
{% for r in TicketResolution %}
{% if r.name != "unresolved" %}
<option value="{{ r.value }}">{{ r.name.upper() }}</option>
{% endif %}
{% endfor %}
</select>
{% else %}
<button
type="submit"
class="btn btn-info"
name="reopen"
value="reopen"
>
Re-open {{icon("caret-right")}}
</button>
{% endif %}
{% endif %}
</form>
{% else %}
{% if not ticket.comments %}
<p>It's a bit quiet in here.</p>
{% endif %}
{% endif %}
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script type="text/javascript">
function UserAutoComplete(input, list) {
this.input = input;
this.list = list;
this.lastQuery = input.value;
// Settings
this.delay = 250;
this.minQueryLength = 3;
this.onLoad = function(event) {
const response = event.target;
if (response.status == 200) {
const data = JSON.parse(response.responseText);
this.list.innerHTML = '';
data.results.forEach(function (username) {
const option = document.createElement('option');
option.value = username;
this.list.appendChild(option);
}.bind(this));
}
}.bind(this)
this.sendRequest = function(query) {
const search = encodeURIComponent(query);
const request = new XMLHttpRequest();
request.onload = this.onLoad;
request.open("GET", "/usernames/?q=" + search);
request.send();
}
this.search = function() {
const query = this.input.value
if (query == "" || query == "~") {
this.list.innerHTML = "";
this.lastQuery = "";
return;
}
const notRepeated = query !== this.lastQuery;
const notTooShort = query.length >= this.minQueryLength;
if (notRepeated && notTooShort) {
this.sendRequest(query);
this.lastQuery = query;
}
}.bind(this)
this.debounce = function(fn, delay) {
let timeout = null;
return function() {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(fn, delay);
}
}
this.register = function() {
this.input.addEventListener("input",
this.debounce(this.search, this.delay));
// Prevent search being triggered when an user is selected from the datalist
// 'select' works in Firefox, 'change' works in Chrome
this.input.addEventListener("select", function(e) {
this.lastQuery = e.target.value;
}.bind(this));
this.input.addEventListener("change", function(e) {
this.lastQuery = e.target.value;
}.bind(this));
}
}
(function() {
const input = document.getElementById("assignee-input");
const list = document.getElementById("assignee-list");
autocomplete = new UserAutoComplete(input, list);
autocomplete.register();
})();
</script>
{% endblock %}