{% extends "base.html" %} {% block media %} {{ form.media.css }} <style> #content > h2:first-child small { color: #999; font-size: 0.9em; margin-left: 0.3em; } #content > h2:first-child .status, #content > h2:first-child .title { display: inline; } #content > h2:first-child .fa-check-circle-o { color: #00a900; } #content > h2:first-child .fa-exclamation-circle { color: darkred; } .container { width: 100%; margin: 0 auto; display: flex; flex-direction: row; flex-wrap: wrap-reverse; max-width: 1000px; } .ticket-sidebar { flex: 1; padding: 10px 0 0 10px; min-width: 150px; max-width: 200px; } .ticket-info { position: sticky; top: 60px; } .ticket-messages { flex: 1; } .info-box { margin: 5px 0 10px; border: 1px #999 solid; border-radius: 5px; } .info-title { padding: 2px 5px; font-weight: 600; border-bottom: 1px #999 solid; background: #eee; border-radius: 5px 5px 0 0; } .info-data { padding: 2px 5px; } .info-empty { color: #999; font-style: italic; } .close-ticket { display: block; width: 100%; background: linear-gradient(to bottom, #4bad00 0%, #278811 100%); border-color: #24710e; font-weight: 600; } .close-ticket:hover { background: #24710e; } .open-ticket { display: block; width: 100%; background: linear-gradient(to bottom, #ff130f, #b03d17); border-color: #853011; font-weight: 600; } .open-ticket:hover { background: #853011; } a.edit-notes { float: right; } #ticket-notes .info-real :first-child { margin-top: 0; } #ticket-notes .info-real :last-child { margin-bottom: 0; } .message { margin-top: -40px; padding-top: 55px; } .message .info { } .message .username { text-align: center; } .message .gravatar { width: 40px; border-radius: 4px; display: block; margin: 0 auto; } .message .message-date, .message .message-time { display: inline-block; } .message .detail { border: 1px #999 solid; border-radius: 5px; flex: 1; min-width: 200px; } .message .header { background: #eee; color: #777; border-bottom: 1px solid #999; border-radius: 5px 5px 0 0; padding: 2px 7px; display: inline-flex; width: -webkit-fill-available; } .message .send-time { text-align: right; } .message .content { padding: 7px; } .message .content :first-child { margin-top: 0; } .message .content :last-child { margin-bottom: 0; } .new-message .detail { padding: 8px 10px; } .new-message .submit, #edit-notes .submit { margin: 10px 0 0 auto; } .user-container { display: inline-flex; } .user-container .username { padding-left: 0.5em; padding-top: 1.65em; } </style> {% endblock %} {% block js_media %} {{ form.media.js }} <script src="{{ static('libs/featherlight/featherlight.min.js') }}" type="text/javascript"></script> <script type="text/javascript" src="{{ static('event.js') }}"></script> <script type="text/javascript"> $(function () { var $h2 = $('#content').find('> h2:first-child'); var $status = $h2.find('.status i'), $title = $h2.find('.title'); function update_ticket_state(open) { if (open) $status.removeClass('fa-check-circle-o').addClass('fa-exclamation-circle'); else $status.removeClass('fa-exclamation-circle').addClass('fa-check-circle-o'); $('.close-ticket').toggle(open); $('.open-ticket').toggle(!open); } $('.close-ticket, .open-ticket').click(function () { var open = $(this).attr('data-open') == '1'; $.ajax({ url: $(this).attr('data-ajax'), type: 'POST', success: function () { update_ticket_state(open); }, error: function (data) { alert('Could not change ticket: ' + data.responseText); } }); }); $('.edit-notes').featherlight({ afterOpen: function () { var $form = $('#edit-notes'); $form.find('.submit').click(function () { $.post($form.attr('action'), $form.serialize()).done(function (data) { $('#ticket-notes').find('.info-empty').toggle(!data).end().find('.info-real').html(data); $.featherlight.current().close(); }); return false; }); } }); var page_ref_key = 'ticket:open:{{ ticket.id }}', page_close_key = page_ref_key + ':close'; var page_ref; function increase_page_ref() { if (page_ref_key in localStorage) localStorage[page_ref_key] = page_ref = +localStorage[page_ref_key] + 1; else localStorage[page_ref_key] = page_ref = 1; } function decrease_page_ref() { if (page_ref_key in localStorage) { localStorage[page_close_key] = page_ref; delete localStorage[page_close_key]; localStorage[page_ref_key] = +localStorage[page_ref_key] - 1; } page_ref = null; } function is_highest_ref() { console.log(localStorage[page_ref_key], page_ref); if (page_ref_key in localStorage) return +localStorage[page_ref_key] == page_ref; return true; } $(window).on('storage', function (e) { e = e.originalEvent; if (e.key == page_close_key && e.newValue !== null) if (page_ref != null && page_ref > +e.newValue) --page_ref; }); register_notify('ticket', { change: function (enabled) { if (enabled) increase_page_ref(); } }); $(window).on('beforeunload', function () { decrease_page_ref(); }); function ticket_status(ticket) { update_ticket_state(ticket.open); if (is_highest_ref()) notify('ticket', (ticket.open ? '{{ _('Reopened: ') }}' : '{{ _('Closed: ') }}') + $title.text()); } function ticket_message(ticket) { $.ajax({ url: '{{ url('ticket_message_ajax', ticket.id) }}', data: {message: ticket.message}, success: function (data) { $('#messages').append($(data.message)); }, error: function (data) { if (data.status === 403) console.log('No right to see: ' + ticket.message); else { console.log('Could not load ticket message:'); console.log(data.responseText); } } }); } window.load_dynamic_update = function (last_msg) { return new EventReceiver( "{{ EVENT_DAEMON_LOCATION }}", "{{ EVENT_DAEMON_POLL_LOCATION }}", ['ticket-{{ ticket.id }}'], last_msg, function (message) { switch (message.type) { case 'ticket-status': ticket_status(message); break; case 'ticket-message': ticket_message(message); break; } } ); } }); </script> {% if last_msg %} <script type="text/javascript"> $(function () { load_dynamic_update({{last_msg}}); }); </script> {% endif %} {% endblock %} {% block content_title %} <div class="status"> <i class="fa {% if ticket.is_open %}fa-exclamation-circle{% else %}fa-check-circle-o{% endif %}"></i> </div> <div class="title">{{ ticket.title }}</div><small>#{{ ticket.id }}</small> {% endblock %} {% block body %} <div class="container"> <div class="ticket-messages"> <main id="messages" class="messages"> {% for message in ticket_messages %} {% include "ticket/message.html" %} {% endfor %} </main> <hr> <section class="message new-message"> <div class="info"> <a href="{{ url('user_page', request.user.username) }}" class="user"> <img src="{{ gravatar(request.user, 135) }}" class="gravatar"> <div class="username {{ request.profile.css_class }}">{{ request.user.username }}</div> </a> </div> <div class="detail"> <form action="" method="post"> {% csrf_token %} {% if form.non_field_errors() or form.body.errors %} <div class="form-errors"> {{ form.non_field_errors() }} {{ form.body.errors }} </div> {% endif %} <div class="body-block">{{ form.body }}</div> <button type="submit" class="submit">{{ _('Post') }}</button> </form> </div> </section> </div> <aside class="ticket-sidebar"> <div class="ticket-info"> <div class="info-box"> <div class="info-title">{{ _('Associated object') }}</div> <div class="info-data"> <a href="{{ ticket.linked_item.get_absolute_url() }}">{{ ticket.linked_item }}</a> </div> </div> <div class="info-box"> <div class="info-title">{{ _('Assignees') }}</div> <div class="info-data"> {% if assignees %} {{ link_users(assignees) }} {% else %} <div class="info-empty">{{ _('No one is assigned.') }}</div> {% endif %} </div> </div> <button data-ajax="{{ url('ticket_close', ticket.id) }}" data-open="0" class="close-ticket" {% if not ticket.is_open %}style="display: none"{% endif %}>{{ _('Close ticket') }}</button> <button data-ajax="{{ url('ticket_open', ticket.id) }}" data-open="1" class="open-ticket" {% if ticket.is_open %}style="display: none"{% endif %}>{{ _('Reopen ticket') }}</button> {% if perms.judge.change_ticket or request.profile in assignees %} <div class="info-box"> <div class="info-title">{{ _('Assignee notes') }} <a href="#" data-featherlight="{{ url('ticket_notes', ticket.id) }}" class="edit-notes"> <i class="fa fa-pencil"></i> </a> </div> <div id="ticket-notes" class="info-data"> <div{% if ticket.notes %} style="display: none"{% endif %} class="info-empty"> {{ _('Nothing here.') }} </div> <div class="info-real">{{ ticket.notes|linebreaks }}</div> </div> </div> {% endif %} </div> </aside> </div> {% endblock %} {% block bodyend %} {{ super() }} {% if REQUIRE_JAX %} {% include "mathjax-load.html" %} {% endif %} {% include "comments/math.html" %} {% endblock %}