| {% extends "repo_master.html" %} |
| {% from "_formhelper.html" |
| import render_bootstrap_field, |
| show_comment, show_initial_comment, show_attachments %} |
| |
| {% block title %}Issue |
| {% set tag = "home"%} |
| |
| {% block header %} |
| <link rel="stylesheet" nonce="{{ g.nonce }}" href="{{ |
| url_for('static', filename='vendor/emojione/emojione.sprites.css') }}?version={{ g.version}}"/> |
| <link rel="stylesheet" nonce="{{ g.nonce }}" href="{{ |
| url_for('static', filename='vendor/selectize/selectize.bootstrap3.css') }}?version={{ g.version}}"/> |
| <link rel="stylesheet" nonce="{{ g.nonce }}" href="{{ |
| url_for('static', filename='vendor/jquery.atwho/jquery.atwho.css') }}?version={{ g.version}}"/> |
| {% endblock %} |
| |
| {% block repo %} |
| <div class="d-flex align-items-start"> |
| <h4 class="ml-1"> |
| {% if g.authenticated and (g.repo_user or open_access or g.fas_user.username == issue.user.user) %} |
| <form action="{{ url_for('ui_ns.update_issue', username=username, |
| namespace=repo.namespace, repo=repo.name, issueid=issueid) |
| }}" method="post" class="hidden" id="changestatusform"> |
| {{form.csrf_token}} |
| <input type="hidden" id="statusform_status" name="status" value=""/> |
| <input type="hidden" id="statusform_close_status" name="close_status" value=""/> |
| {% endif %} |
| {% if g.authenticated and (g.repo_user or open_access) %} |
| <input type="hidden" id="statusform_tag" name="tag" value=""/> |
| <input type="hidden" id="statusform_depending" name="depending" value=""/> |
| <input type="hidden" id="statusform_blocking" name="blocking" value=""/> |
| <input type="hidden" id="statusform_assignee" name="assignee" value=""/> |
| <input type="hidden" id="statusform_milestone" name="milestone" value=""/> |
| <input type="hidden" id="statusform_priority" name="priority" value=""/> |
| {{form.private}} |
| |
| {% if repo.issue_keys %} |
| {% for field in repo.issue_keys %} |
| <input type="hidden" id="statusform_{{ field.name | replace(' ', '_') }}" name="{{ field.name }}" value=""/> |
| {% endfor %} |
| {% endif %} |
| {% endif %} |
| {% if g.authenticated and (g.repo_user or open_access or g.fas_user.username == issue.user.user) %} |
| </form> |
| {% endif %} |
| <div> |
| {% if issue.private %} |
| <span title="Private ticket" class="text-danger fa fa-fw fa-lock"></span> |
| {% endif %} |
| {% if issue.status == 'Open' %} |
| <span class="fa fa-fw text-success fa-exclamation-circle pt-1"></span> |
| <span class="text-success font-weight-bold"> |
| {% elif issue.status == 'Closed' %} |
| <span class="fa fa-fw text-danger fa-exclamation-circle pt-1"></span> |
| <span class="text-danger font-weight-bold"> |
| {% endif %} |
| <span class="font-weight-bold"> |
| {{ issue.title | noJS(ignore="img") | safe}} |
| </span> |
| {% if g.repo_committer or ( |
| g.fas_user and g.fas_user.username == issue.user.username) %} |
| <a class="btn btn-outline-secondary btn-sm border-0" href="{{ |
| url_for('ui_ns.edit_issue', |
| repo=repo.name, |
| username=username, |
| namespace=repo.namespace, |
| issueid=issueid) |
| }}" title="Edit this issue"> |
| <i class="fa fa-pencil"></i></a> |
| {% endif %} |
| </div> |
| <div> |
| <small> |
| {% if issue.status == 'Open' %} |
| <span data-toggle="tooltip" title="{{issue.date_created | format_datetime}}"> |
| <span class="text-success font-weight-bold">Opened</span> {{ issue.date_created |humanize }} |
| </span> |
| <span title="{{ issue.user.html_title }}"> by {{ issue.user.user }}.</span> |
| <span class="text-muted" data-toggle="tooltip" title="{{issue.last_updated | format_datetime}}"> |
| Modified {{ issue.last_updated |humanize }} |
| </span> |
| {% elif issue.status == 'Closed' %} |
| <span data-toggle="tooltip" title="{{issue.closed_at | format_datetime}}"> |
| <span class="text-danger font-weight-bold"> |
| {% if issue.close_status %} |
| Closed: {{issue.close_status}} |
| {% else %} |
| Closed |
| {% endif %} |
| </span> {{ issue.closed_at |humanize }} |
| </span> |
| {% if issue.closed_by %} |
| by |
| <span title="{{ issue.closed_by.html_title }}">{{ issue.closed_by.user }}.</span> |
| {% endif %} |
| <span class="text-muted" data-toggle="tooltip" title="{{issue.date_created | format_datetime}}"> |
| <span class="font-weight-bold">Opened</span> {{ issue.date_created |humanize }} |
| </span> |
| <span class="text-muted" title="{{ issue.user.html_title }}">by {{ issue.user.user }}.</span> |
| {% endif %} |
| </small> |
| </div> |
| </h4> |
| <div class="ml-auto"> |
| <div class="btn-group"> |
| <div class="dropdown"> |
| {% if g.authenticated and (g.repo_user or open_access or g.fas_user.username == issue.user.user) %} |
| <a class="font-weight-bold btn btn-sm {{'btn-success' if issue.status=='Open' else 'btn-danger'}} dropdown-toggle pointer" |
| id="dropdownMenuButton" data-toggle='dropdown' aria-haspopup="true" aria-expanded="false"> |
| {% else %} |
| <a class="font-weight-bold opacity-100 disabled btn btn-sm {{'btn-success' if issue.status=='Open' else 'btn-danger'}} pointer"> |
| {% endif %} |
| {% if issue.status == 'Open' %} |
| Open |
| {% else %} |
| {% if issue.close_status %} |
| Closed: {{issue.close_status}} |
| {% else %} |
| Closed |
| {% endif %} |
| {% endif %} |
| </a> |
| <div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton"> |
| {% if issue.status == 'Open' %} |
| {% if repo.close_status %} |
| <h6 class="dropdown-header">Close issue as:</h6> |
| {% for close_status in repo.close_status %} |
| <a class="dropdown-item close_status_dropdown_action pointer" data-value="{{close_status}}">{{close_status}}</a> |
| {% endfor %} |
| {% else %} |
| <a class="dropdown-item close_status_dropdown_action pointer" data-value="">Close Issue</a> |
| {% endif %} |
| {% else %} |
| <a class="dropdown-item close_status_dropdown_action pointer">Reopen Issue</a> |
| {% endif %} |
| {% if g.repo_committer %} |
| <div class="dropdown-divider"></div> |
| <a class="dropdown-item text-danger pointer" id="closeticket" |
| title="Delete this ticket"> |
| <i class="fa fa-fw fa-trash"></i> Delete Issue |
| </a> |
| {% endif %} |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <form action="{{ url_for('ui_ns.update_issue', username=username, |
| namespace=repo.namespace, repo=repo.name, issueid=issueid) |
| }}" method="post" class="mainform"> |
| {{ form.csrf_token }} |
| <div class="row mt-4"> |
| <div class="col-md-8 mt-2"> |
| |
| {{ show_initial_comment(issue, username, repo,issueid, form) }} |
| |
| <hr class="mb-1"/> |
| |
| <section id="comments" class="pt-1"> |
| {% if issue.comments %} |
| {% for comment in issue.comments %} |
| {% if comment.notification %} |
| <div class="d-flex align-items-center px-3 py-2 mb-3"> |
| <div class=""> |
| {{ comment.user.default_email | avatar(16) | safe }} |
| </div> |
| <span class="font-size-09 autogenerated-comment pl-4">{{ comment.comment | markdown | noJS | safe }}</span> |
| <div class="text-muted ml-auto"> |
| <span title="{{ comment.date_created | format_datetime }}">{{ |
| comment.date_created | humanize }}</span> |
| </div> |
| </div> |
| {% else %} |
| {{ show_comment(comment, comment.id, repo, username, issueid, form) }} |
| {% endif %} |
| {% endfor %} |
| {% endif %} |
| </section> |
| |
| {% if g.authenticated and form and not repo.settings.get('issue_tracker_read_only', False) %} |
| |
| <div class="card mt-5"> |
| <div class="card-header pb-0 pt-1 bg-light"> |
| <div class="row"> |
| <div class="col align-self-center"> |
| <span><strong>Add new comment</strong></span> |
| </div> |
| <div class="col"> |
| <ul class="nav nav-tabs float-right border-bottom-0"> |
| <li class="nav-item"> |
| <a class="nav-link pointer" id="previewinmarkdown">Preview</a> |
| </li> |
| <li class="nav-item"> |
| <a class="nav-link active pointer" id="editinmarkdown" >Edit</a> |
| </li> |
| </ul> |
| {% if repo.quick_replies %} |
| {% include "quick_reply.html" %} |
| {% endif %} |
| </div> |
| </div> |
| </div> |
| <div class="card-body"> |
| <textarea class="form-control" rows=8 id="comment" name="comment" |
| placeholder="Enter your comment here" tabindex=1></textarea> |
| <div id="preview" class="p-1"> |
| </div> |
| <div class="mt-2"> |
| <label class="custom-file font-size-09"> |
| <input type="file" id="file-picker" class="custom-file-input" name="file" accept="image/*" multiple tabindex=3> |
| <label class="custom-file-label" for="file-picker"> |
| Browse to attach images or drag them into the comment field |
| </label> |
| </label> |
| <div id="progress" class="progress hidden height-22p"> |
| <div id="progress-bar" class="progress-bar height-22p">0%</div> |
| </div> |
| </div> |
| </div> |
| <div class="card-footer bg-light"> |
| <div class="d-flex align-items-center"> |
| <small>Comments use <a href="https://docs.pagure.org/pagure/usage/markdown.html" |
| target="_blank" rel="noopener noreferrer" class="notblue">Markdown Syntax</a></small> |
| <div class="ml-auto"> |
| <div class="btn-group"> |
| {% if g.authenticated and (g.repo_user or open_access or g.fas_user.username == issue.user.user) %} |
| {% if issue.status == 'Open' %} |
| {% if repo.close_status %} |
| <div class="btn-group"> |
| <a class="btn btn-outline-primary dropdown-toggle pointer" |
| id="dropdownMenuButton" data-toggle='dropdown' aria-haspopup="true" aria-expanded="false" tabindex=3> |
| Comment & Close |
| </a> |
| <div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton"> |
| <h6 class="dropdown-header">Close issue as:</h6> |
| {% for close_status in repo.close_status %} |
| <a class="dropdown-item comment_and_close_action pointer" data-value="{{close_status}}">{{close_status}}</a> |
| {% endfor %} |
| </div> |
| </div> |
| {% else %} |
| <a class="btn btn-outline-primary comment_and_close_action pointer" data-value=""> |
| Comment & Close |
| </a> |
| {% endif %} |
| {% else %} |
| <a class="btn btn-outline-primary comment_and_close_action pointer" data-value=""> |
| Comment & Reopen |
| </a> |
| {% endif %} |
| {% endif %} |
| <input type="submit" class="btn btn-primary" |
| value="Comment" tabindex=2 /> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| {% elif g.authenticated and form and repo.settings.get('issue_tracker_read_only', False) %} |
| <p> |
| This issue tracker is read-only. |
| </p> |
| {% else %} |
| <p> |
| <a href="{{ url_for('auth_login', next=request.url) }}">Login</a> |
| to comment on this ticket. |
| </p> |
| {% endif %} |
| |
| </div> |
| |
| <div class="col-md-4"> |
| <div> |
| <div class="mb-4"> |
| <h5 class="d-flex align-items-center font-weight-bold border-bottom"> |
| <div class="py-2 text-uppercase font-size-09">Metadata</div> |
| {% if g.authenticated and (g.repo_user or g.fas_user.username == issue.user.user or open_access) |
| and not repo.settings.get('issue_tracker_read_only', False) %} |
| <div class="ml-auto"> |
| <a class="btn btn-outline-primary border-0 btn-sm issue-metadata-display editmetadatatoggle pointer inline-block"><i class="fa fa-fw fa-pencil"></i></a> |
| <a class="btn btn-outline-secondary border-0 btn-sm issue-metadata-form hidden editmetadatatoggle pointer hidden"><i class="fa fa-fw fa-times"></i></a> |
| </div> |
| {% endif %} |
| </h5> |
| |
| {% if g.authenticated and (g.repo_user or g.fas_user.username == issue.user.user) %} |
| <div class="hidden"> |
| {{form.status}} |
| {{form.close_status}} |
| </div> |
| {% endif%} |
| |
| <fieldset class="form-group issue-metadata-display mt-4"> |
| <label class="mb-1 pl-1"> <i class="fa fa-fw fa-user-plus"></i> <strong>Assignee</strong></label> |
| <div id="assignee_plain"> |
| <div class="ml-2" title="{{ issue.assignee.html_title if issue.assignee else '' }}"> |
| {% if issue.assignee %} |
| <div class="mt-1">{{issue.assignee.username| avatar(size=24) | safe}} |
| <a href="{{ url_for( |
| 'ui_ns.view_issues', |
| repo=repo.name, |
| username=username, |
| namespace=repo.namespace, |
| assignee=issue.assignee.username) |
| }}" title="{{ issue.assignee.html_title }}"> |
| {{ issue.assignee.username }} |
| </a> |
| {% if g.authenticated and (issue.assignee.username == g.fas_user.username) %} |
| — <a class="pointer" id="drop-btn" |
| title="drop the assignment of this issue"> |
| Drop |
| </a> |
| {% endif %} |
| </div> |
| {% else %} |
| <div class="text-muted"> |
| None |
| {% if g.authenticated and (g.repo_user or g.fas_user.username == issue.user.user or open_access) and issue.status|lower == 'open' |
| and (not issue.assignee or issue.assignee.username != g.fas_user.username) |
| and not repo.settings.get('issue_tracker_read_only', False) %} |
| — <a class="pointer" id="take-btn" |
| title="assign this issue to you"> |
| Take |
| </a> |
| {% endif %} |
| </div> |
| {% endif %} |
| </div> |
| </div> |
| </fieldset> |
| |
| {% if g.authenticated and (g.repo_user or open_access) %} |
| <fieldset class="form-group issue-metadata-form hidden"> |
| <label for="assignee"><strong>Assignee</strong></label> |
| <input class="form-control" name="assignee" id="assignee" |
| placeholder="username" |
| value="{{ issue.assignee.username or '' }}" /> |
| </fieldset> |
| {% endif%} |
| |
| <fieldset class="form-group issue-metadata-display mt-4"> |
| <label class="mb-1"><i class="fa fa-fw fa-tag"></i> <strong>Tags</strong></label> |
| {% if issue.tags %} |
| <h4 class="ml-2" id="taglist"> |
| {% for tag in issue.tags %} |
| <a id="tag-{{ tag.tag }}" title="{{ tag.tag_description }}" |
| data-bg-color="{{ tag.tag_color }}" |
| class="badge badge-secondary text-left my-1 p-2 badge-tag" |
| href="{{ url_for('ui_ns.view_issues', |
| repo=repo.name, |
| username=username, |
| namespace=repo.namespace, |
| tags=tag.tag) }}"> |
| {{ tag.tag }} |
| </a> |
| {% endfor %} |
| </h4> |
| {% else %} |
| <div class="text-muted">None</div> |
| {% endif%} |
| </fieldset> |
| |
| {% if g.authenticated and (g.repo_user or open_access) %} |
| <fieldset class="form-group issue-metadata-form hidden"> |
| <label for="tag"><strong>Tags</strong></label> |
| <input id="tag" type="text" placeholder="tag1, tag2" name="tag" |
| title="comma separated list of tags" |
| value="{{ issue.tags_text | join(',') }}" /> |
| </fieldset> |
| {% endif%} |
| |
| {%macro blocks_item(ticket, itemtype="block") %} |
| {% if ticket.status|lower == 'open' %} |
| {% set status_color = "success" %} |
| {% elif ticket.status|lower == 'merged' %} |
| {% set status_color = "info" %} |
| {% else %} |
| {% set status_color = "danger" %} |
| {% endif %} |
| <div class="d-flex align-items-center"> |
| <div class="nowrap"> |
| <span class="fa fa-fw text-{{status_color}} fa-exclamation-circle pt-1"></span> |
| <span class="text-{{status_color}} font-weight-bold"> |
| </div> |
| <div class="ellipsis pl-2 font-size-09"> |
| <a id="{{itemtype}}-{{ ticket.id }}" |
| href="{{ url_for('ui_ns.view_issue', |
| repo=repo.name, |
| username=username, |
| namespace=repo.namespace, |
| issueid=ticket.id) |
| }}" class="notblue">{{ticket.title}}</a> |
| </div> |
| </div> |
| {% endmacro %} |
| |
| <fieldset class="form-group issue-metadata-display mt-4"> |
| <label class="mb-1 pl-1"> <i class="fa fa-fw fa-ban"></i> <strong>Blocking</strong></label> |
| <div class="ml-2" id="blocklist"> |
| {% if issue.children %} |
| {% for ticket in issue.children %} |
| {{blocks_item(ticket, itemtype="block")}} |
| {% endfor %} |
| {% else %} |
| <div class="text-muted">None</div> |
| {% endif%} |
| </div> |
| </fieldset> |
| |
| {% if g.authenticated and (g.repo_user or open_access) %} |
| <fieldset class="form-group issue-metadata-form hidden"> |
| <label for="blocking"><strong>Blocking</strong></label> |
| <input class="form-control" id="blocking" type="text" |
| placeholder="issue blocking" name="blocking" |
| value="{{ issue.blocking_text | join(',') }}" /> |
| </fieldset> |
| {% endif%} |
| |
| <fieldset class="form-group issue-metadata-display mt-4"> |
| <label class="mb-1 pl-1"> <i class="fa fa-fw fa-check-circle-o"></i> <strong>Depending on</strong></label> |
| <div class="ml-2" id="dependlist"> |
| {% if issue.parents %} |
| {% for ticket in issue.parents %} |
| {{blocks_item(ticket, itemtype="depend")}} |
| {% endfor %} |
| {% else %} |
| <div class="text-muted">None</div> |
| {% endif %} |
| </div> |
| </fieldset> |
| |
| {% if g.authenticated and (g.repo_user or open_access) %} |
| <fieldset class="form-group issue-metadata-form hidden"> |
| <label for="depending"><strong>Depending on</strong></label> |
| <input class="form-control" id="depending" type="text" |
| placeholder="issue depending" name="depending" |
| value="{{ issue.depending_text | join(',') }}" /> |
| </fieldset> |
| {% endif%} |
| |
| {% if repo.priorities %} |
| <fieldset class="form-group issue-metadata-display mt-4"> |
| <label class="mb-1 pl-1"> <i class="fa fa-bolt"></i> <strong>Priority</strong></label> |
| <div class="ml-2" id="priority_plain"> |
| {% if issue.priority is not none %} |
| <span >{{ repo.priorities[issue.priority | string] }}</span> |
| {% else %} |
| <div class="text-muted">None</div> |
| {% endif %} |
| </div> |
| </fieldset> |
| {% if g.authenticated and (g.repo_user or open_access) %} |
| {{ render_bootstrap_field(form.priority, |
| formclass="issue-metadata-form hidden") }} |
| {% endif%} |
| |
| {% endif %} |
| |
| {% if repo.milestones %} |
| <fieldset class="form-group issue-metadata-display mt-4"> |
| <label class="mb-1 pl-1"> <i class="fa fa-fw fa-map-signs"></i> <strong>Milestone</strong></label> |
| <div class="ml-2" id="milestone_plain"> |
| {% if issue.milestone %} |
| <span> |
| <a href="{{ url_for( |
| 'ui_ns.view_milestone', |
| repo=repo.name, |
| username=username, |
| namespace=repo.namespace, |
| milestone=issue.milestone) }}"> |
| {{ issue.milestone }} |
| </a> |
| </span> |
| {% else %} |
| <span class="text-muted">None</span> |
| {% endif %} |
| </div> |
| </fieldset> |
| {% if g.authenticated and (g.repo_user or open_access) %} |
| {{ render_bootstrap_field(form.milestone, |
| formclass="issue-metadata-form hidden") }} |
| {% endif%} |
| |
| {% endif %} |
| |
| {% if g.authenticated and (g.repo_user or open_access) %} |
| {{ render_bootstrap_field(form.private, |
| formclass="issue-metadata-form hidden") }} |
| {% endif%} |
| |
| {% if repo.issue_keys %} |
| {% for field in repo.issue_keys %} |
| <fieldset class="form-group issue-metadata-display mt-4"> |
| <label class="mb-0"> <i class="fa fa-fw fa-circle-o"></i> <strong>{{ field.name }}</strong></label> |
| <div class="pl-2" id="{{ field.name | replace(' ', '_') }}_plain"> |
| {% if field.name in knowns_keys %} |
| {% if field.key_type == 'link' %} |
| {% for link in knowns_keys[field.name].value.split(',') %} |
| <a target="_blank" rel="noopener noreferrer" href="{{ link }}">{{ link }}</a> |
| <br> |
| {% endfor %} |
| {% else %} |
| {{ knowns_keys[field.name].value }} |
| {% endif %} |
| {% else %} |
| <div class="text-muted">None</div> |
| {% endif %} |
| </div> |
| </fieldset> |
| {% if g.authenticated and (g.repo_user or open_access) %} |
| <fieldset class="form-group issue-metadata-form hidden"> |
| <label for="field"><strong> <i class="fa fa-fw fa-circle-o"></i>{{ field.name }}</strong></label> |
| {% if field.key_type == 'list' %} |
| <select class="form-control" |
| name="{{ field.name }}" |
| id="{{ field.name | replace(' ', '_') }}"> |
| <option value="None">None</option> |
| {% for item in field.data or [] %} |
| <option value="{{item}}" {% if field.name in knowns_keys and item == knowns_keys[field.name].value %} selected {% endif %}> |
| {{ item }} |
| </option> |
| {% endfor %} |
| </select> |
| {% else %} |
| <input |
| {%- if field.key_type == 'boolean' %} type="checkbox" {% endif %} |
| {%- if field.key_type == 'date'%} type="date" {% endif %} |
| class="form-control" name="{{ field.name }}" id="{{ field.name }}" |
| {%- if field.name in knowns_keys %} |
| {% if field.key_type == 'boolean'%} |
| {% if knowns_keys[field.name].value in ['true', 'on', '1'] %}checked{% endif %} |
| {% else %} value="{{ knowns_keys[field.name].value }}" |
| {% endif %} |
| {%- endif -%} /> |
| {% endif %} |
| </fieldset> |
| {% endif %} |
| {% endfor %} |
| |
| {% endif %} |
| |
| <input type="submit" class="btn btn-primary issue-metadata-form hidden" value="Update"> |
| </div> |
| </div> |
| |
| {% if attachments %} |
| <div class="mt-3"> |
| <h5 class="d-flex align-items-center font-weight-bold border-bottom"> |
| <div class="py-2 text-uppercase font-size-09"> |
| Attachments |
| <span class="badge badge-secondary badge-pill font-size-09 ml-1" id="attachments-count">{{attachments|count}}</span> |
| </div> |
| { |
| <a href="#" class="btn btn-sm btn-link" id="subcribe-btn" |
| {% if g.fas_user.username in subscribers -%} |
| title="Unsubscribe from this issue">Unsubscribe |
| {%- else -%} |
| title="Subscribe to this issue">Subscribe |
| {%- endif -%} |
| </a> |
| </div> |
| </h5> |
| {{ show_attachments(attachments) }} |
| </div> |
| {% endif %} |
| |
| {% if g.authenticated %} |
| |
| <div class="mt-3"> |
| <h5 class="d-flex align-items-center font-weight-bold border-bottom"> |
| <div class="py-2 text-uppercase font-size-09"> |
| Subscribers |
| <span class="badge badge-secondary badge-pill font-size-09 ml-1" id="subscribers-count">{{subscribers|count}}</span> |
| </div> |
| <div class="ml-auto"> |
| <a href="#" class="btn btn-sm btn-link" id="subcribe-btn" |
| {% if g.fas_user.username in subscribers -%} |
| title="Unsubscribe from this issue">Unsubscribe |
| {%- else -%} |
| title="Subscribe to this issue">Subscribe |
| {%- endif -%} |
| </a> |
| </div> |
| </h5> |
| |
| {% if subscribers %} |
| <div id="subscribers_list" class="p-2"> |
| {% for subscriber in subscribers %} |
| <a href="{{ url_for('ui_ns.view_user', username=subscriber) |
| }}" title="{{ subscriber }}" id="sub-avatar-{{subscriber}}">{{ |
| subscriber |avatar(size=30, css_class="pb-1") | safe |
| }}</a> |
| {% endfor %} |
| </div> |
| {% else %} |
| <div class="text-center text-muted pt-2">No Subscribers</div> |
| {% endif %} |
| |
| </div> |
| {% endif %} |
| |
| |
| {% if issue.related_prs %} |
| <div class="mt-3"> |
| <h5 class="d-flex align-items-center font-weight-bold border-bottom"> |
| <div class="py-2 text-uppercase font-size-09"> |
| Related Pull Requests |
| </div> |
| </h5> |
| <div id="pr_list"> |
| <ul class="list-unstyled"> |
| {% for pr in issue.related_prs %} |
| <li> |
| <a class="badge badge-secondary" href="{{ url_for( |
| 'ui_ns.request_pull', |
| repo=pr.project.name, |
| username=pr.project.user.user if pr.project.is_fork else none, |
| namespace=pr.project.namespace, |
| requestid=pr.id) }}"> |
| {{ pr.status if pr.status != 'Open' else 'Last updated' |
| }} {{ pr.last_updated | humanize }} |
| </li> |
| {% endfor %} |
| </ul> |
| </div> |
| </div> |
| {% endif %} |
| </div> |
| |
| </div> |
| </form> |
| |
| |
| {% endblock %} |
| |
| {% block jscripts %} |
| {{ super() }} |
| <script type="text/javascript" nonce="{{ g.nonce }}"> |
| var UPLOAD_URL = "{{ url_for('ui_ns.upload_issue', repo=repo.name, username=username, namespace=repo.namespace, issueid=issue.id) }}"; |
| </script> |
| |
| <script type="text/javascript" nonce="{{ g.nonce }}"src="{{ |
| url_for('static', filename='vendor/jquery.textcomplete/jquery.textcomplete.min.js') }}?version={{ g.version}}"></script> |
| <script type="text/javascript" nonce="{{ g.nonce }}" src="{{ |
| url_for('static', filename='vendor/emojione/emojione.min.js') }}?version={{ g.version}}"></script> |
| <script type="text/javascript" nonce="{{ g.nonce }}" src="{{ |
| url_for('static', filename='emoji/emojicomplete.js') }}?version={{ g.version}}"></script> |
| <script type="text/javascript" nonce="{{ g.nonce }}" src="{{ |
| url_for('static', filename='upload.js') }}?version={{ g.version}}"></script> |
| |
| <script type="text/javascript" nonce="{{ g.nonce }}" src="{{ |
| url_for('static', filename='vendor/selectize/selectize.min.js') }}?version={{ g.version}}"></script> |
| <script type="text/javascript" nonce="{{ g.nonce }}" src="{{ |
| url_for('static', filename='vendor/jquery.caret/jquery.caret.min.js') }}?version={{ g.version}}"></script> |
| <script type="text/javascript" nonce="{{ g.nonce }}" src="{{ |
| url_for('static', filename='vendor/jquery.atwho/jquery.atwho.min.js') }}?version={{ g.version}}"></script> |
| <script type="text/javascript" nonce="{{ g.nonce }}" src="{{ |
| url_for('static', filename='tags.js') }}?version={{ g.version}}"></script> |
| |
| <script type="text/javascript" nonce="{{ g.nonce }}"> |
| |
| {% if g.authenticated and form %} |
| $(document).ready(function() { |
| // Set up the drag/drop zone. |
| initDropbox("{{ form.csrf_token.current_token }}", "#comment"); |
| |
| // Set up the handler for the file input box. |
| $("#file-picker").on("change", function() { |
| doUpload("{{ form.csrf_token.current_token }}", this.files); |
| }); |
| |
| $('.delete_comment_btn').click(function() { |
| return confirm('Do you really want to remove this comment?'); |
| }); |
| |
| $('.mainform').submit(function() { |
| return try_async_comment($(this)); |
| }); |
| |
| $('.mainform #assignee').selectize({ |
| valueField: 'user', |
| labelField: 'user', |
| searchField: 'user', |
| maxItems: 1, |
| create: false, |
| load: function(query, callback) { |
| if (!query.length) return callback(); |
| $.getJSON( |
| "{{ url_for('api_ns.api_users') }}", { |
| pattern: "*"+query+"*" |
| }, |
| function( data ) { |
| callback( data.users.map(function(x) { return { user: x }; }) ); |
| } |
| ); |
| } |
| }); |
| |
| $.get("{{ url_for('api_ns.api_users') }}", { |
| pattern: '*' |
| }).done(function(resp) { |
| var userConfig = { |
| at: '@', |
| data: resp['mention'], |
| insertTpl: '@${username}', |
| displayTpl: "<li><img src=\"${image}\"> ${username} <small>${name}</small></li>", |
| searchKey: "username" |
| } |
| $("#comment").atwho(userConfig); |
| |
| }); |
| $.when($.get("{{ url_for('api_ns.api_view_issues', |
| repo=repo.name, |
| username=username, |
| namespace=repo.namespace, |
| status='all') }}"), |
| $.get("{{ url_for('api_ns.api_pull_request_views', |
| repo=repo.name, |
| username=username, |
| namespace=repo.namespace, |
| status='all') }}") |
| ).done(function(issuesResp, prResp) { |
| // 0 is the api response |
| var issuesAndPrs = issuesResp[0]['issues'].concat(prResp[0]['requests']); |
| var data = $.map(issuesAndPrs, function(ticket, idx) { |
| return { |
| name: ticket.id.toString(), |
| title: $('<div>').text(ticket.title).html() |
| } |
| }); |
| var issueAndPrConfig = { |
| at: '#', |
| data: data, |
| insertTpl: '#${name}', |
| displayTpl: "<li>#${name}<small> ${title}</small></li>", |
| } |
| $("#comment").atwho(issueAndPrConfig); |
| }) |
| }); |
| {% endif %} |
| |
| function setup_edit_btns() { |
| $(".edit_btn").unbind(); |
| $(".edit_btn").click(function() { |
| var commentid = $( this ).attr('data-comment'); |
| var _url = '{{ request.base_url }}' + '/comment/' + commentid + '/edit'; |
| $.ajax({ |
| url: _url + '?js=1', |
| type: 'GET', |
| dataType: 'html', |
| success: function(res) { |
| var el = $('#comment-' + commentid); |
| var sec = el.parent().find('.issue_comment'); |
| $(sec).hide(); |
| el.parent().find('.issue_actions').hide(); |
| $(sec).after(res); |
| cancel_edit_btn(); |
| }, |
| error: function() { |
| alert('Could not make edit work'); |
| } |
| }); |
| return false; |
| }); |
| }; |
| |
| function cancel_edit_btn() { |
| $("#comment_update_cancel").unbind(); |
| $("#comment_update_cancel").click( |
| function() { |
| $(this).closest('#comments').find('.issue_comment').show(); |
| $(this).closest('#comments').find('.issue_actions').show(); |
| $(this).closest('.edit_comment').remove(); |
| return false; |
| }); |
| }; |
| |
| function setup_reply_btns() { |
| $(".reply").unbind(); |
| $( ".reply" ).click( |
| function() { |
| var _section = $(this).closest('.card'); |
| if (!_section.length) { |
| var _section = $(this).closest('#original_comment_box'); |
| } |
| var _comment = _section.find('.comment_body'); |
| var _text = _comment.text().split("\n"); |
| var _output = new Array(); |
| for (cnt = 0; cnt < _text.length ; cnt ++) { |
| _output[cnt] = '> ' + $.trim(_text[cnt]); |
| } |
| var _prev = $.trim($( "#comment" ).val()); |
| if (_prev.length > 0){ |
| _prev += "\n\n"; |
| } |
| $( "#comment" ).val(_prev + _output.join("\n")); |
| } |
| ).click(function(){ |
| $('html, body').animate({ |
| scrollTop: $("#comment").offset().top |
| }, 2000); |
| }); |
| }; |
| |
| $(document).ready(function() { |
| var cur_hash = null; |
| |
| highlight_comment = function() { |
| var _hash = window.location.hash; |
| if (_hash != cur_hash) { |
| $( cur_hash ).css( |
| "background", "linear-gradient(to bottom, #ededed 0%, #fff 100%)" |
| ); |
| }; |
| cur_hash = _hash; |
| if ( _hash ) { |
| $( _hash ).css( |
| "background", "linear-gradient(to bottom, #eded98 0%, #fff 100%)" |
| ); |
| }; |
| return false; |
| }; |
| |
| {% if g.repo_user %} |
| $('#closeticket').click(function(event){ |
| event.preventDefault(); |
| var closeForm = $('<form>', { |
| 'method': 'POST', |
| 'action': '{{ |
| url_for('ui_ns.delete_issue', |
| repo=repo.name, |
| username=username, |
| namespace=repo.namespace, |
| issueid=issueid) }}', |
| }).append($('<input>', { |
| 'name': 'csrf_token', |
| 'value': '{{ form.csrf_token.current_token }}', |
| 'type': 'hidden' |
| })).appendTo('body'); |
| if (confirm('Are you sure to delete this ticket? \nThis is final and cannot be un-done.')){ |
| closeForm.submit(); |
| } |
| return false; |
| }); |
| {% endif %} |
| |
| $(window.onload=highlight_comment()); |
| $(window).on('hashchange', highlight_comment); |
| cancel_edit_btn(); |
| setup_edit_btns(); |
| setup_reply_btns(); |
| |
| }); |
| </script> |
| |
| {% if config['EVENTSOURCE_SOURCE'] and not issue.private %} |
| <script type="text/javascript" nonce="{{ g.nonce }}" src="{{ |
| url_for('static', filename='issue_ev.js') }}?version={{ g.version}}"></script> |
| {% endif %} |
| |
| |
| <script type="text/javascript" nonce="{{ g.nonce }}"> |
| var source = null; |
| var sse = true; |
| |
| {% if config['EVENTSOURCE_SOURCE'] and not issue.private %} |
| if (!!window.EventSource) { |
| source = new EventSource('{{ config["EVENTSOURCE_SOURCE"] |
| + request.script_root + request.path }}'); |
| source.addEventListener('error', function(e) { |
| sse = false; |
| }, false); |
| } |
| |
| window.onbeforeunload = function() { |
| source.close() |
| }; |
| |
| source.addEventListener('message', function(e) { |
| console.log(e.data); |
| var data = $.parseJSON(e.data); |
| var _issues_url ='{{ |
| url_for('ui_ns.view_issues', |
| repo=repo.name, |
| username=username, |
| namespace=repo.namespace)}}'; |
| var _api_issues_url ='{{ |
| url_for('api_ns.api_view_issue', |
| repo=repo.name, |
| username=username, |
| namespace=repo.namespace, |
| issueid='-123456789')}}'; |
| var _issue_url ='{{ |
| url_for('ui_ns.view_issue', |
| repo=repo.name, |
| username=username, |
| namespace=repo.namespace, |
| issueid='-123456789')}}'; |
| var _roadmap_url ='{{ |
| url_for('ui_ns.view_roadmap', |
| repo=repo.name, |
| username=username, |
| namespace=repo.namespace, |
| milestone='-123456789')}}'; |
| process_event(data, "{{ issue.uid }}", _issue_url, |
| _issues_url, _api_issues_url, _roadmap_url, |
| "{{ g.fas_user.username if g.authenticated or '' }}"); |
| setup_edit_btns(); |
| setup_reply_btns(); |
| |
| }, false); |
| |
| {% else %} |
| sse = false; |
| {% endif %} |
| |
| {% if g.authenticated and form %} |
| function set_ui_for_comment(setting){ |
| if (setting == false) { |
| $(document.body).find('input[type="submit"]').removeAttr("disabled"); |
| document.body.style.cursor = 'default'; |
| } else { |
| $(document.body).find('input[type="submit"]').attr("disabled", "disabled"); |
| document.body.style.cursor = 'wait'; |
| } |
| } |
| function try_async_comment(form) { |
| console.log('Submitting form:'); |
| console.log(form); |
| set_ui_for_comment(true); |
| var _data = $(form).serialize(); |
| var btn = $(document.activeElement); |
| if (btn[0].name == 'drop_comment'){ |
| _data += '&drop_comment=' + btn[0].value; |
| set_ui_for_comment(false); |
| return true; |
| } |
| if (!sse || source.readyState != 1) { |
| $(form).off('submit'); |
| form.submit(); |
| return false; |
| } |
| var _url = form.attr("action") + "?js=1"; |
| $.post( _url, _data ) |
| .done(function(data) { |
| if(data == 'ok') { |
| { |
| $('#comment').val(''); |
| $('#preview').html(''); |
| $('#previewinmarkdown').addClass('inactive'); |
| $('#previewinmarkdown').removeClass('active'); |
| $('#preview').hide(); |
| $('#comment').show(); |
| $('#comments').find('.comment_body').show(); |
| $('#comments').find('.edit_comment').remove(); |
| $( ".issue-metadata-form" ).hide(); |
| $( ".issue-metadata-display" ).show(); |
| set_ui_for_comment(false); |
| } else { |
| // Make the browser submit the form sync |
| $(form).off('submit'); |
| form.submit(); |
| } |
| }) |
| .fail(function() { |
| // Make the browser submit the form sync |
| $(form).off('submit'); |
| form.submit(); |
| }) |
| return false; |
| }; |
| {% endif %} |
| |
| </script> |
| |
| |
| <script type="text/javascript" nonce="{{ g.nonce }}"> |
| {% if g.authenticated and (g.repo_user or issue.user.user == g.fas_user.username or open_access) %} |
| function take_issue(){ |
| var _url = "{{ url_for('api_ns.api_assign_issue', |
| repo=repo.name, namespace=repo.namespace, username=username, |
| issueid=issueid) }}"; |
| var _data = {assignee: "{{ g.fas_user.username }}"}; |
| $.post (_url, _data ).done( |
| function(data) { |
| var _user_url = '\n<div class="ml-2"><div class="mt-1">{{g.fas_user.username| avatar(size=24) | safe}} ' |
| + '<a href="{{ url_for("ui_ns.view_issues", repo=repo.name, username=username, namespace=repo.namespace) }}' |
| + '?assignee={{ g.fas_user.username }}">' |
| + '{{ g.fas_user.username }}</a>' |
| + ' — <a id="drop-btn" title="drop the assignment of this issue" ' |
| + 'class="pointer">Drop</a></div></div>'; |
| $('#assignee_plain').html(_user_url); |
| $('#assignee').val("{{ g.fas_user.username }}"); |
| setup_btn_take_drop(); |
| } |
| ).fail(function() { |
| alert( "An error occured, could not assign this ticket to you." ); |
| }) |
| return false; |
| } |
| {% endif %} |
| |
| {% if g.authenticated and ( |
| g.repo_user |
| or issue.user.user == g.fas_user.username |
| or issue.assignee.user == g.fas_user.username) %} |
| function drop_issue(){ |
| var _url = "{{ url_for('api_ns.api_assign_issue', |
| repo=repo.name, namespace=repo.namespace, username=username, |
| issueid=issueid) }}"; |
| var _data = {assignee: ""}; |
| $.post( _url, _data ).done( |
| function(data) { |
| var _user_url = '<div class="ml-2">\n<span class="text-muted">None</span>' |
| + ' — <a id="take-btn" title="assign this issue to you" ' |
| + 'class="pointer">Take</a></div>'; |
| $('#assignee_plain').html(_user_url); |
| $('#assignee').val(""); |
| setup_btn_take_drop(); |
| } |
| ).fail(function() { |
| alert( "An error occured, could not drop the current assignee." ); |
| }) |
| return false; |
| } |
| {% endif %} |
| |
| function setup_btn_take_drop(){ |
| {% if g.authenticated and (g.repo_user or open_access) %} |
| $("#take-btn").click(take_issue) |
| {% endif %} |
| {% if g.authenticated and ( |
| g.repo_user |
| or issue.user.user == g.fas_user.username |
| or issue.assignee.user == g.fas_user.username) %} |
| $("#drop-btn").click(drop_issue); |
| {% endif %} |
| } |
| |
| $( document ).ready(function() { |
| |
| $(".close_status_dropdown_action").click(function(event){ |
| var status = "{{issue.status}}"; |
| if (status == "Open") { |
| $("#changestatusform #statusform_status").val("Closed"); |
| $("#changestatusform #statusform_close_status").val($(this).attr("data-value")); |
| } else { |
| $("#changestatusform #statusform_status").val("Open"); |
| } |
| $("#changestatusform #statusform_assignee").val($("#assignee").val()); |
| $("#changestatusform #statusform_tag").val($("#tag").val()); |
| $("#changestatusform #statusform_priority").val($("#priority").val()); |
| $("#changestatusform #statusform_milestone").val($("#milestone").val()); |
| $("#changestatusform #statusform_blocking").val($("#blocking").val()); |
| $("#changestatusform #statusform_depending").val($("#depending").val()); |
| |
| {% if repo.issue_keys %} |
| {% for field in repo.issue_keys %} |
| $("#changestatusform #statusform_{{ field.name | replace(' ', '_') }}").val($("#{{ field.name | replace(' ', '_') }}").val()); |
| {% endfor %} |
| {% endif %} |
| $("#changestatusform").submit(); |
| |
| }); |
| |
| $(".comment_and_close_action").click(function(event){ |
| var status = "{{issue.status}}"; |
| if (status == "Open") { |
| $(".mainform #status").val("Closed"); |
| $(".mainform #close_status").val($(this).attr("data-value")); |
| } else { |
| $(".mainform #status").val("Open"); |
| } |
| $(".mainform").submit(); |
| |
| }); |
| |
| |
| var emojiStrategy; |
| $.getJSON( |
| '{{ url_for("static", filename="vendor/emojione/emoji_strategy.json") }}', |
| function( data ) { |
| emojiStrategy = data; |
| } |
| ); |
| |
| var folder = '{{url_for("static", filename="emoji/png/") }}?version={{ g.version}}'; |
| var json_url = '{{ url_for("static", filename="vendor/emojione/emoji_strategy.json") }}?version={{ g.version}}'; |
| emoji_complete(json_url, folder); |
| |
| $(".comment_body").each(function(ind, obj) { |
| var source = $(obj).html(); |
| var preview = emojione.toImage(source); |
| $(obj).html(preview); |
| }); |
| |
| $( ".editmetadatatoggle" ).click( |
| function() { |
| $( ".issue-metadata-form" ).toggle(); |
| $( ".issue-metadata-display" ).toggle(); |
| } |
| ); |
| |
| function _get_issues(url, callback){ |
| $.getJSON( |
| url, |
| function( data ) { |
| issues = data.issues.filter(function(el) { |
| return el.id !== {{issue.id}}; |
| }); |
| callback(issues); |
| if (data.pagination.next){ |
| _get_issues(data.pagination.next, callback) |
| } |
| } |
| ); |
| } |
| |
| $('.mainform #blocking').selectize({ |
| plugins: ['remove_button'], |
| valueField: 'id', |
| labelField: 'id', |
| searchField: ['id', 'title'], |
| preload: 'focus', |
| render: { |
| option: function(item, escape) { |
| return '<div><span>'+escape(item.id)+'</span> <span>'+escape(item.title)+'</span></div>'; |
| }, |
| item: function(item, escape) { |
| return '<div><span>#'+escape(item.id)+'</span></div>'; |
| }, |
| }, |
| create: false, |
| load: function(query, callback) { |
| if (!query){ |
| callback(); |
| return; |
| }; |
| var _url = "{{ url_for('api_ns.api_view_issues', |
| repo=repo.name, |
| username=username, |
| namespace=repo.namespace) }}" + "?query_id=" + query; |
| _get_issues(_url, callback); |
| } |
| }); |
| |
| $('.mainform #depending').selectize({ |
| plugins: ['remove_button'], |
| valueField: 'id', |
| labelField: 'id', |
| searchField: ['id', 'title'], |
| preload: 'focus', |
| render: { |
| option: function(item, escape) { |
| return '<div><span>'+escape(item.id)+'</span> <span>'+escape(item.title)+'</span></div>'; |
| }, |
| item: function(item, escape) { |
| return '<div><span>#'+escape(item.id)+'</span></div>'; |
| }, |
| }, |
| create: false, |
| load: function(query, callback) { |
| if (!query){ |
| callback(); |
| return; |
| }; |
| var _url = "{{ url_for('api_ns.api_view_issues', |
| repo=repo.name, |
| username=username, |
| namespace=repo.namespace) }}" + "?query_id=" + query; |
| _get_issues(_url, callback); |
| } |
| }); |
| |
| var available_tags = []; |
| {%for tog in tag_list %} |
| available_tags.push("{{tog.tag}}"); |
| {%endfor%} |
| var items = available_tags.map(function(x) { return { item: x }; }); |
| |
| $('.mainform #tag').selectize({ |
| delimiter: ',', |
| options: items, |
| persist: false, |
| create: false, |
| labelField: "item", |
| valueField: "item", |
| searchField: ["item"], |
| }); |
| |
| $( "#preview" ).hide(); |
| |
| $( "#previewinmarkdown" ).click( |
| function(event, ui) { |
| var _text = $( "#comment" ).val(); |
| var _url = "{{ url_for('ui_ns.markdown_preview', |
| repo=repo.name, |
| user=repo.user.user if repo.is_fork, |
| namespace=repo.namespace) | safe}}"; |
| $.ajax({ |
| url: _url , |
| type: 'POST', |
| data: { |
| content: _text, |
| csrf_token: "{{ g.confirmationform.csrf_token.current_token }}", |
| }, |
| dataType: 'html', |
| success: function(res) { |
| var preview = emojione.toImage(res); |
| $( "#preview" ).html(preview); |
| $( "#previewinmarkdown" ).addClass("active"); |
| $( "#editinmarkdown" ).removeClass("active"); |
| $( "#comment" ).hide(); |
| $( "#preview" ).show(); |
| }, |
| error: function() { |
| alert('Unable to generate preview!'+error); |
| } |
| }); |
| return false; |
| } |
| ); |
| |
| $( "#editinmarkdown" ).click( |
| function(event, ui) { |
| $( "#editinmarkdown" ).addClass("active"); |
| $( "#previewinmarkdown" ).removeClass("active"); |
| $( "#comment" ).show(); |
| $( "#preview" ).hide(); |
| } |
| ); |
| |
| function submitFormOnCtrlKey(event) { |
| if (event.ctrlKey && event.keyCode == 13) { |
| var form = event.target.form; |
| form.submit(); |
| event.preventDefault(); |
| } |
| } |
| |
| $('#comment').keydown(function(e) { |
| submitFormOnCtrlKey(e); |
| }); |
| |
| |
| {% if g.authenticated and ( |
| g.repo_user |
| or open_access |
| or issue.user.user == g.fas_user.username |
| or issue.assignee.user == g.fas_user.username) %} |
| setup_btn_take_drop(); |
| {% endif %} |
| |
| {% if g.authenticated %} |
| function set_up_subcribed() { |
| $("#subcribe-btn").click(function(){ |
| var _url = "{{ url_for( |
| 'api_ns.api_subscribe_issue', |
| repo=repo.name, |
| username=username, |
| namespace=repo.namespace, |
| issueid=issueid |
| ) }}"; |
| var _btn = $("#subcribe-btn"); |
| var _data = {}; |
| if (_btn.text() == 'Unsubscribe'){ |
| _data.status = false; |
| } else { |
| _data.status = true; |
| } |
| $.post( _url, _data ).done( |
| function(data) { |
| var _btn = $("#subcribe-btn"); |
| var _countlabel = $("#subscribers-count") |
| var _count = parseInt(_countlabel.text()) |
| if (_btn.text() == 'Subscribe'){ |
| _btn.text('Unsubscribe'); |
| _countlabel.text(_count+1) |
| var _html = '<a href="/user/' + data.user + '"' |
| + 'title="'+data.user+'" id="sub-avatar-'+data.user+'">' |
| + '<img src="'+data.avatar_url+'" class="pb-1"></a>'; |
| $('#subscribers_list').prepend(_html); |
| } else { |
| _btn.text('Subscribe'); |
| _countlabel.text(_count-1); |
| $('#sub-avatar-'+data.user).remove(); |
| } |
| return false; |
| } |
| ) |
| return false; |
| }); |
| }; |
| set_up_subcribed(); |
| {% endif %} |
| |
| }); |
| </script> |
| |
| {% if repo.quick_replies %} |
| <script type="text/javascript" nonce="{{ g.nonce }}" src="{{ |
| url_for('static', filename='quick_reply.js') }}?version={{ g.version}}"></script> |
| {% endif %} |
| <script type="text/javascript" nonce="{{ g.nonce }}" src="{{ |
| url_for('static', filename='reactions.js') }}?version={{ g.version}}"></script> |
| |
| {% endblock %} |