storyboard-webclient/src/app/stories/template/detail.html

856 lines
36 KiB
HTML

<!--
~ Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
~ Copyright (c) 2016 Codethink Ltd.
~
~ Licensed under the Apache License, Version 2.0 (the "License"); you may
~ not use this file except in compliance with the License. You may obtain
~ a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
~ License for the specific language governing permissions and limitations
~ under the License.
-->
<div class="container-fluid">
<div class="row">
<div class="col-xs-12">
<div class="alert alert-danger" ng-show="story.private">
<i class="fa fa-eye-slash"></i>
<strong>This story is private</strong>.
Edit this story to change the privacy settings.
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="hidden-xs hidden-sm col-sm-3 pull-right"
ng-show="worklists.length > 0 && !showEditForm">
<div ng-include src="'/inline/worklists.html'"></div>
</div>
<div ng-include
src="'/inline/story_detail.html'"
ng-hide="showEditForm">
</div>
<div ng-include
src="'/inline/story_detail_form.html'"
ng-show="showEditForm">
</div>
<hr class="visible-sm" />
<div class="col-xs-12 visible-sm"
ng-show="worklists.length > 0 && !showEditForm">
<div ng-include src="'/inline/worklists.html'"></div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<hr/>
<div ng-include src="'/inline/task_list.html'"></div>
<hr/>
<div ng-include src="'/inline/tags.html'"></div>
<hr/>
<div ng-include src="'/inline/discussion.html'"></div>
</div>
</div>
</div>
<!-- Template for the header and description -->
<script type="text/ng-template" id="/inline/story_detail.html">
<h1>
<span ng-show="story.title" view-title>
{{story.title}}
</span>
<em ng-hide="story.title" class="text-muted">
No title
</em>
<small ng-show="isLoggedIn">
<a href="" ng-click="toggleEditMode()" >
<i class="fa fa-pencil-alt"></i>
</a>
<subscribe resource="story"
resource-id="story.id">
</subscribe>
<button type="button"
class="btn btn-link"
ng-click="remove()" permission="is_superuser">
Remove this story
</button>
</small>
</h1>
<p><strong>Author:</strong>
<em ng-show="!story.creator_id" class="text-muted">
None
</em>
<span ng-show="story.creator_id && !creator.$resolved">
<i class="fa fa-sync fa-spin"></i>
</span>
<span ng-show="creator.full_name">
{{creator.full_name}}
</span>
<br/>
<strong>Last updated:</strong>
<span time-moment eventdate="story.created_at" ng-if="!story.updated_at"></span>
<span time-moment eventdate="story.updated_at" ng-if="story.updated_at"></span>
<em ng-hide="story.updated_at || story.created_at" class="text-muted">
Never
</em>
</p>
<p>
<insert-markdown ng-show="story.description"
content="story.description">
</insert-markdown>
<em ng-hide="story.description" class="text-muted">
No description provided
</em>
</p>
</script>
<!-- Template for the header and description -->
<script type="text/ng-template" id="/inline/story_detail_form.html">
<hr>
<form name="storyForm" role="form" class="form-horizontal">
<div class="form-group has-feedback"
ng-class="{'has-error': storyForm.title.$invalid,
'has-success': !storyForm.title.$invalid}">
<label for="title" class="col-sm-2 control-label">
Title
</label>
<div class="col-sm-10">
<input id="title"
name="title"
type="text"
class="form-control"
ng-model="story.title"
required
focus
maxlength="255"
placeholder="Story title"
ng-disabled="isUpdating">
<span class="form-control-feedback"
ng-show="storyForm.title.$invalid">
<i class="fa fa-times fa-lg"></i>
</span>
<span class="form-control-feedback"
ng-show="!storyForm.title.$invalid">
<i class="fa fa-check fa-lg"></i>
</span>
<div class="help-block text-danger"
ng-show="storyForm.title.$invalid">
<span ng-show="storyForm.title.$error.required">
A story title is required.
</span>
</div>
</div>
</div>
<hr ng-show="preview">
<div class="form-group" ng-show="preview">
<div class="col-sm-offset-1 col-sm-10">
<insert-markdown content="story.description">
</insert-markdown>
</div>
</div>
<hr ng-show="preview">
<div class="form-group">
<label for="description"
class="col-sm-2 control-label">
Description
</label>
<div class="col-sm-10">
<textarea id="description"
class="form-control"
ng-model="story.description"
msd-elastic
placeholder="Enter a story description here."
ng-disabled="isUpdating">
</textarea>
</div>
</div>
<div class="form-group">
<label for="private" class="col-sm-2 control-label">
Private
</label>
<div class="col-sm-10 checkbox">
<input id="private"
type="checkbox"
class="modal-checkbox"
ng-model="story.private"
ng-disabled="isUpdating"
/>
</div>
</div>
<div class="row">
<div class="col-md-6 col-md-offset-3"
ng-show="story.private">
<table class="table table-striped">
<thead>
<tr>
<th>Teams and Users that can see this story</th>
<th class="text-right">
<small>
<a href
ng-click="showAddUser = !showAddUser">
<i class="fa fa-plus" ng-if="!showAddUser"></i>
<i class="fa fa-minus" ng-if="showAddUser"></i>
Add Team or User
</a>
</small>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="team in story.teams">
<td colspan="2">
<i class="fa fa-sb-team"></i>
{{ team.name }}
<a class="close"
ng-click="removeTeam(team)"
ng-show="story.teams.length > 1 || story.users.length > 0">
&times;
</a>
</td>
</tr>
<tr ng-repeat="user in story.users">
<td colspan="2">
<i class="fa fa-sb-user"></i>
{{user.full_name}}
<a class="close"
ng-click="removeUser(user)"
ng-show="story.users.length > 1 || story.teams.length > 0">
&times;
</a>
</td>
</tr>
<tr ng-show="showAddUser">
<td colspan="2">
<input id="user"
type="text"
autocomplete="off"
placeholder="Click to add a team or user"
ng-model="asyncUser"
typeahead-wait-ms="200"
typeahead-editable="false"
typeahead="actor as actor.name for actor in
searchActors($viewValue, story.users, story.teams)"
typeahead-loading="loadingUsers"
typeahead-on-select="addActor($model)"
typeahead-template-url="/inline/actor-typeahead-item.html"
class="form-control input-sm"
/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="clearfix">
<div class="pull-right">
<div class="btn" ng-show="isUpdating">
<i class="fa fa-spinner fa-lg fa-spin"></i>
</div>
<button type="button"
class="btn btn-primary"
ng-click="update()"
ng-disabled="!storyForm.$valid">
Save
</button>
<button type="button"
class="btn btn-default"
ng-click="cancel()">
Cancel
</button>
</div>
<button type="button"
class="btn btn-link"
ng-click="remove()" permission="is_superuser">
Remove this story
</button>
<button type="button"
class="btn btn-primary"
ng-click="preview = !preview">
Toggle Preview
</button>
<a class="btn btn-link" target="_blank"
href="http://daringfireball.net/projects/markdown/basics">
Markdown formatting is supported.
</a>
</div>
</div>
</form>
</script>
<script type="text/ng-template" id="/inline/actor-typeahead-item.html">
<a tabindex="-1">
<i ng-class="'fa fa-sb-' + match.model.type"></i>&nbsp;
<span ng-bind-html="match.model.name | typeaheadHighlight:query"></span>
</a>
</script>
<!-- Template for the list of relevant worklists -->
<script type="text/ng-template" id="/inline/worklists.html">
<table class="table table-striped table-condensed">
<thead>
<tr>
<th>In worklists you subscribed to</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="worklist in worklists.slice(0, 5)">
<td>
<a href="#!/worklist/{{worklist.id}}">{{worklist.title}}</a>
<span class="text-muted pull-right" ng-show="worklist.automatic">
(automatic)
</span>
<div ng-click="showItems = !showItems">
<a href>
<i class="fa"
ng-class="{'fa-caret-right': !showItems,
'fa-caret-down': showItems}"></i>
<small>
{{showItems ? 'Hide' : 'Show'}} items...
</small>
</a>
</div>
<table class="table table-striped table-condensed subtable" ng-show="showItems">
<tr ng-repeat="item in worklist.relatedItems | orderBy: 'list_position'">
<td>
{{item.item_type | capitalize}} {{item.item_id}}
<span class="pull-right">
{{item.list_position + 1}} of {{worklist.items.length}}
</span>
</td>
</tr>
</table>
</td>
</tr>
<tr ng-if="worklists.length > 5">
<td class="text-center">
<a href ng-click="showWorklistsModal()">More...</a>
</td>
</tr>
</tbody>
<tbody ng-if="worklists.length < 1">
<tr>
<td colspan="2">No worklists related to this story</td>
</tr>
</tbody>
</table>
</script>
<!-- Template for the tags list and controls -->
<script type="text/ng-template" id="/inline/tags.html">
<strong>Tags</strong>
<ul class="list-inline">
<li ng-repeat="tag in story.tags">
<h4>
<span class="label label-warning">
{{tag}}
<i class="fa fa-times" ng-show="isLoggedIn"
ng-click="removeTag(tag)"></i>
</span>
</h4>
</li>
</ul>
<br>
<span class="input-group" ng-show="showAddTag">
<input type="text" class="form-control" ng-model="newTag.name"
typeahead="tag as tag.name for tag in searchTags($viewValue)"
typeahead-on-select="updateViewValue($model.name);"/>
<span class="input-group-btn">
<button class="btn btn-primary" type="button"
ng-click="addTag(newTag.name)">Add</button>
<button class="btn btn-default" type="button"
ng-click="toggleAddTag()">Cancel</button>
</span>
</span>
<h4>
<span class="label label-default" ng-click="toggleAddTag()"
ng-show="!showAddTag && isLoggedIn">
Add
<i class="fa fa-plus"></i>
</span>
<span class="label label-default" ng-click="showLoginRequiredModal()"
ng-show="!isLoggedIn">
Add
<i class="fa fa-plus"></i>
</span>
</h4>
</script>
<!-- Template for the task list -->
<script type="text/ng-template" id="/inline/task_list.html">
<div class="pull-right">
Sort by:
<span class="dropdown form-control-static" dropdown="">
<button class="btn btn-xs btn-default" dropdown-toggle="" aria-haspopup="true" aria-expanded="true">
<li class="dropdown-header">{{tasks_sort_field.label}}</li>
</button>
<ul class="dropdown-menu dropdown-menu-right" role="menu">
<li ng-click="changeOrder('Id')">
<a>Id</a>
</li>
<li ng-click="changeOrder('Title')">
<a>Title</a>
</li>
<li ng-click="changeOrder('Status')">
<a>Status</a>
</li>
</ul>
</span>
</div>
<h3>Tasks</h3>
<div ng-repeat="name in projectNames | orderBy">
<h4>
<i class="fa fa-sb-project text-muted"></i>
<a href="#!/project/{{ name }}">{{ name }}</a>
</h4>
<div ng-repeat="branchName in projects[name].branchNames">
<div class="row" ng-if="branchName != 'master'">
<div class="col-xs-3">
<strong>
&emsp;&emsp;{{ branchName }}
</strong>
</div>
</div>
<div class="row" ng-if="branchName == 'master'">
<div class="col-xs-12" ng-include src="'/inline/task_table.html'"></div>
</div>
<div class="row" ng-if="branchName != 'master'">
<div class="col-xs-11 col-xs-offset-1"
ng-include src="'/inline/task_table.html'">
</div>
</div>
</div>
</div>
<div ng-show="newTask.show" ng-include src="'/inline/new_task.html'"></div>
<button class="btn btn-default"
ng-show="isLoggedIn && !newTask.show"
ng-click="newTask.show = !newTask.show">
<i class="fa fa-plus-circle"></i>
Affects other project
</button>
</script>
<!-- Template for a table of tasks -->
<script type="text/ng-template" id="/inline/task_table.html">
<table class="table table-striped table-supercondensed">
<tr ng-repeat="task in projects[name].branches[branchName].tasks | orderBy: tasks_sort_field.value"
ng-include src="'/inline/task_list_item.html'">
</tr>
<tr ng-show="isLoggedIn && !projects[name].branches[branchName].showAddTask">
<td class="text-center">
<a href
ng-click="projects[name].branches[branchName].showAddTask =
!projects[name].branches[branchName].showAddTask">
<i class="fa fa-plus-circle"></i>
Add task affecting this project
</a>
</td>
</tr>
<tr ng-show="projects[name].branches[branchName].showAddTask"
ng-include src="'/inline/task_edit_form.html'">
</tr>
</table>
</script>
<!-- Template for an item in the task list -->
<script type="text/ng-template" id="/inline/task_list_item.html">
<td>
<div class="row">
<div class="col-xs-4 task-title">
<div>
<a href ng-click="showDetail = !showDetail">
<i class="fa fa-caret-down" ng-show="showDetail"></i>
<i class="fa fa-caret-right" ng-show="!showDetail && (task.link || isLoggedIn)"></i>
</a>
<strong>{{ task.id }}</strong>
&nbsp;
</div>
<div class="title">
<input-inline
ng-model="task.title"
enabled="isLoggedIn"
on-change="updateTask(task, 'title', task.title)"
empty-prompt="Click to set title."
empty-disabled-prompt="No title."
maxlength="255"
as-inline="true"
/>
</div>
</div>
<div class="col-xs-3">
<user-typeahead
ng-model="task.assignee_id"
enabled="isLoggedIn"
on-change="updateTask(task, 'assignee_id', task.assignee_id)"
empty-prompt="Click to assign."
empty-disabled-prompt="Not assigned."
as-inline="true"
/>
</div>
<div class="col-xs-2">
<task-status-dropdown
editable="{{isLoggedIn}}"
on-change="updateTask(task, 'status', status)"
status="{{ task.status }}"
/>
</div>
<!-- Review link should go here once implemented in the API -->
</div>
<div class="padded-row" ng-show="showDetail">
<div class="col-xs-10 col-xs-offset-1">
<project-typeahead
auto-focus="false"
ng-model="task.project_id"
ng-show="changingProject"
enabled="isLoggedIn"
on-change="updateTask(task, 'project_id', task.project_id, name, branchName);
changingProject = false"
empty-prompt="Click to set a project"
empty-disabled-prompt="No project set."
as-inline="false"
placeholder="Enter project name">
</project-typeahead>
<button class="btn btn-xs btn-default"
ng-show="isLoggedIn"
ng-click="changingProject = !changingProject">
{{ changingProject ? 'Cancel' : 'Change project' }}
</button>
<button class="btn btn-xs btn-default"
ng-click="showAddWorklist(task)"
ng-show="isLoggedIn">
Add to worklist
</button>
<button class="btn btn-xs btn-default"
ng-show="tasks.length > 1 && isLoggedIn"
ng-click="removeTask(task, name, branchName)">
Delete
</button>
</div>
</div>
<div class="padded-row" ng-show="showDetail">
<div class="col-xs-10 col-xs-offset-1 task-notes"
ng-show="isLoggedIn">
<button class="btn btn-primary btn-xs pull-right"
ng-show="!task.editing && task.link && isLoggedIn"
ng-click="editNotes(task)">
<i class="fa fa-pencil-alt"></i> Edit
</button>
<insert-markdown content="task.link" ng-show="!task.editing"></insert-markdown>
<insert-markdown content="task.tempNotes" ng-show="task.editing && notesPreview"></insert-markdown>
<form name="taskNotesForm" ng-show="task.editing">
<div class="form-group">
<textarea placeholder="Enter task notes here"
class="form-control context-edit"
msd-elastic
rows="3"
ng-disabled="isUpdating"
ng-model="task.tempNotes">
</textarea>
</div>
<div class="clearfix">
<button type="button"
class="btn btn-default" ng-click="notesPreview = !notesPreview">
Toggle Preview
</button>
<div class="pull-right">
<div class="btn" ng-show="isUpdating">
<i class="fa fa-spinner fa-lg fa-spin"></i>
</div>
<button type="button"
class="btn btn-primary"
ng-click="updateTask(task, 'link', task.tempNotes);
cancelEditNotes(task)"
ng-disabled="!taskNotesForm.$valid">
Save
</button>
<button type="button" ng-show="isLoggedIn"
class="btn btn-default"
ng-click="cancelEditNotes(task)">
Cancel
</button>
</div>
</div>
</form>
<div class="padded-row pull-right" ng-show="!task.editing && !task.link">
<button class="btn btn-primary btn-xs"
ng-click="editNotes(task)">
<i class="fa fa-pencil-alt"></i> Add notes
</button>
</div>
</div>
<div class="col-xs-10 col-xs-offset-1 task-notes"
ng-show="!isLoggedIn && task.link">
<insert-markdown content="task.link" ng-show="!task.editing"></insert-markdown>
<insert-markdown content="task.tempNotes" ng-show="task.editing && notesPreview"></insert-markdown>
</div>
</div>
</td>
</script>
<!-- Template for the task add form. -->
<script type="text/ng-template" id="/inline/task_edit_form.html">
<td>
<div class="row">
<div class="col-xs-4 task-title">
<div>
<a href ng-click="showDetail = !showDetail">
<i class="fa fa-caret-down" ng-show="showDetail"></i>
<i class="fa fa-caret-right" ng-show="!showDetail"></i>
</a>
&nbsp;
</div>
<input-inline
auto-focus="true"
ng-model="projects[name].branches[branchName].newTask.title"
enabled="isLoggedIn"
on-change="updateTaskInline()"
empty-prompt="Click to assign."
empty-disabled-prompt="Not assigned."
maxlength="255"
as-inline="false"
placeholder="Enter task name"
/>
</div>
<div class="col-xs-3">
<user-typeahead
auto-focus="false"
ng-model="projects[name].branches[branchName].newTask.assignee_id"
enabled="isLoggedIn"
on-change="updateTaskInline()"
empty-prompt="Click to assign."
empty-disabled-prompt="Not assigned."
as-inline="false"
placeholder="Assign user to task"
/>
</div>
<div class="col-xs-2">
<task-status-dropdown
editable="{{isLoggedIn}}"
on-change="updateTask(projects[name].branches[branchName].newTask, 'status', status)"
status="{{projects[name].branches[branchName].newTask.status}}"
/>
</div>
<!-- Review link should go here once implemented in the API -->
<div class="col-xs-2 text-right col-xs-offset-1">
<button ng-click="createTask(projects[name].branches[branchName].newTask,
projects[name].branches[branchName])"
class="btn btn-primary">
Save
</button>
<button ng-click="projects[name].branches[branchName].showAddTask =
!projects[name].branches[branchName].showAddTask"
class="btn btn-default">
Cancel
</button>
</div>
</div>
<div class="row" ng-show="showDetail">
<div class="col-xs-10 col-xs-offset-1">
<textarea ng-model="projects[name].branches[branchName].newTask.link"
class="form-control context-edit"
msd-elastic
rows="3"
ng-show="isLoggedIn"
placeholder="Task notes">
</textarea>
</div>
</div>
</td>
</script>
<!-- Template for creating a task in an as-yet unrelated project -->
<script type="text/ng-template" id="/inline/new_task.html">
<div>
<h4>
<i class="fa fa-sb-project text-muted"></i>
<project-typeahead
auto-focus="false"
ng-model="newTask.project_id"
enabled="isLoggedIn"
on-change="updateTaskInline()"
empty-prompt="Click to set a project"
empty-disabled-prompt="No project set."
as-inline="false"
placeholder="Enter project name">
</project-typeahead>
</h4>
<div class="row">
<div class="col-xs-12">
<table class="table table-supercondensed">
<tr>
<td>
<div class="row">
<div class="col-xs-4 task-title">
<div>
<a href ng-click="showDetail = !showDetail">
<i class="fa fa-caret-down" ng-show="showDetail"></i>
<i class="fa fa-caret-right" ng-show="!showDetail"></i>
</a>
&nbsp;
</div>
<input-inline
auto-focus="true"
ng-model="newTask.title"
enabled="isLoggedIn"
on-change="updateTaskInline()"
empty-prompt="Click to assign."
empty-disabled-prompt="Not assigned."
maxlength="255"
as-inline="false"
placeholder="Enter task name"
/>
</div>
<div class="col-xs-3">
<user-typeahead
auto-focus="false"
ng-model="newTask.assignee_id"
enabled="isLoggedIn"
on-change="updateTaskInline()"
empty-prompt="Click to assign."
empty-disabled-prompt="Not assigned."
as-inline="false"
placeholder="Assign user to task"
/>
</div>
<div class="col-xs-2">
<task-status-dropdown
editable="{{isLoggedIn}}"
on-change="updateTask(newTask, 'status', status)"
status="{{newTask.status}}"
/>
</div>
<!-- Review link should go here once implemented in the API -->
<div class="col-xs-2 text-right col-xs-offset-1">
<button ng-click="createTask(newTask)"
class="btn btn-primary">
Save
</button>
<button ng-click="newTask.show = !newTask.show"
class="btn btn-default">
Cancel
</button>
</div>
</div>
<div class="row" ng-show="showDetail">
<div class="col-xs-10 col-xs-offset-1">
<textarea ng-model="newTask.link"
class="form-control context-edit"
msd-elastic
rows="3"
ng-show="isLoggedIn"
placeholder="Task notes">
</textarea>
</div>
</div>
</td>
</tr>
</table>
</div>
</div>
</div>
</script>
<!-- Template for the events stream -->
<script type="text/ng-template" id="/inline/discussion.html">
<div>
<div class="clearfix">
<span class="btn-group pull-right">
<button class="btn btn-default" ng-click="filterComments()"
type="button" ng-show="isLoggedIn"
ng-class="{'btn-primary': filterMode == 'comments'}">
Comments
</button>
<button class="btn btn-default" ng-click="filterAll()"
type="button" ng-show="isLoggedIn"
ng-class="{'btn-primary': filterMode == 'all'}">
All Events
</button>
<button class="btn btn-default" ng-click="updateFilter()"
type="button" ng-show="isLoggedIn"
ng-class="{'btn-primary': filterMode == 'advanced'}">
Advanced...
</button>
</span>
<h4>Events Timeline and Comments</h4>
</div>
<div class="discussion-overlay text-center" ng-show="isSearching">
<i class="fa fa-lg fa-sync fa-spin text-muted"></i>
</div>
<div class="discussion" ng-class="{'discussion-faded': isSearching}">
<div class="alert alert-warning"
ng-show="comments.length == 0">
The discussion hasn't started yet
</div>
<div ng-repeat="event in events">
<timeline-event tl_event={{event}} />
</div>
<div class="discussion-comment-important" ng-if="previewingComment">
<p class="discussion-comment-icon-warning">
<i class="fa fa-comment"></i>
</p>
<div class="discussion-event-complex">
<div class="discussion-event-header">
Comment Preview
</div>
<insert-markdown ng-show="newComment.content"
content="newComment.content">
</insert-markdown>
</div>
</div>
</div>
<form class="discussion-comment-form comment"
id="commentForm"
name="commentForm" ng-show="isLoggedIn">
<div class="form-group">
<textarea id="comment"
placeholder="Enter your comment here"
class="form-control"
msd-elastic
rows="3"
required
ng-disabled="isSavingComment"
ng-shift-enter="addComment()"
ng-model="newComment.content">
</textarea>
</div>
<div class="form-group clearfix">
<button type="button"
class="btn btn-primary pull-right"
ng-click="addComment()"
ng-disabled="!commentForm.$valid || isSavingComment">
Comment
</button>
<button type="button"
class="btn btn-primary"
ng-click="togglePreview()"
ng-disabled="!commentForm.$valid || isSavingComment">
Toggle Preview
</button>
<a class="btn btn-link" target="_blank"
href="http://daringfireball.net/projects/markdown/basics">
Markdown formatting is supported
</a>
</div>
</form>
<hr />
</div>
</script>