Add and remove tasks from context menus
This commit is contained in:
parent
783b868220
commit
2243eebda9
@ -126,7 +126,7 @@ class Tasks extends BaseController {
|
|||||||
$id = (int)$args['id'];
|
$id = (int)$args['id'];
|
||||||
$task = R::load('task', $id);
|
$task = R::load('task', $id);
|
||||||
|
|
||||||
if ((int)$task->id !== $id) {
|
if ((int)$task->id !== $id || (int)$task->id === 0) {
|
||||||
$this->logger->addError('Remove Task: ', [$task]);
|
$this->logger->addError('Remove Task: ', [$task]);
|
||||||
$this->apiJson->addAlert('error', 'Error removing task. ' .
|
$this->apiJson->addAlert('error', 'Error removing task. ' .
|
||||||
'No task found for ID ' . $id . '.');
|
'No task found for ID ' . $id . '.');
|
||||||
@ -134,8 +134,9 @@ class Tasks extends BaseController {
|
|||||||
return $this->jsonResponse($response);
|
return $this->jsonResponse($response);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->checkBoardAccess(
|
$boardId = $this->getBoardId($task->column_id);
|
||||||
$this->getBoardId($task->column_id), $request)) {
|
|
||||||
|
if (!$this->checkBoardAccess($boardId, $request)) {
|
||||||
return $this->jsonResponse($response, 403);
|
return $this->jsonResponse($response, 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,6 +152,9 @@ class Tasks extends BaseController {
|
|||||||
$this->apiJson->addAlert('success',
|
$this->apiJson->addAlert('success',
|
||||||
'Task ' . $before->title . ' removed.');
|
'Task ' . $before->title . ' removed.');
|
||||||
|
|
||||||
|
$board = R::load('board', $boardId);
|
||||||
|
$this->apiJson->addData(R::exportAll($board));
|
||||||
|
|
||||||
return $this->jsonResponse($response);
|
return $this->jsonResponse($response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +64,18 @@ export class BoardService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeTask(taskId: number): Observable<ApiResponse> {
|
||||||
|
return this.http.delete('api/tasks/' + taskId)
|
||||||
|
.map(res => {
|
||||||
|
let response: ApiResponse = res.json();
|
||||||
|
return response;
|
||||||
|
})
|
||||||
|
.catch((res, caught) => {
|
||||||
|
let response: ApiResponse = res.json();
|
||||||
|
return Observable.of(response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Determine when to use this
|
// TODO: Determine when to use this
|
||||||
refreshToken(): void {
|
refreshToken(): void {
|
||||||
this.http.post('api/refresh', {}).subscribe();
|
this.http.post('api/refresh', {}).subscribe();
|
||||||
|
@ -29,27 +29,29 @@
|
|||||||
|
|
||||||
<div class="tasks">
|
<div class="tasks">
|
||||||
<tb-task class="task-container" *ngFor="let task of columnData.ownTask"
|
<tb-task class="task-container" *ngFor="let task of columnData.ownTask"
|
||||||
[task]="task" [add-task]="getShowModalFunction()"></tb-task>
|
[task]="task" [add-task]="getShowModalFunction(task.id)"
|
||||||
|
[remove-task]="getRemoveTaskFunction(task.id)"></tb-task>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<tb-context-menu [menu-items]="contextMenuItems"></tb-context-menu>
|
<tb-context-menu [menu-items]="contextMenuItems"></tb-context-menu>
|
||||||
|
|
||||||
<!--<tb-modal modal-title="Confirm Task Removal" blocking="true">-->
|
<tb-modal modal-title="Confirm Task Removal" blocking="true"
|
||||||
<!-- <div class="center">Removing a task cannot be undone.<br>Continue?</div>-->
|
modal-id="{{ MODAL_CONFIRM_ID + columnData.id }}">
|
||||||
<!-- <div class="buttons">-->
|
<div class="center">Removing a task cannot be undone.<br>Continue?</div>
|
||||||
<!-- <button class="flat"-->
|
<div class="buttons">
|
||||||
<!-- (click)="removeUser()">Yes</button>-->
|
<button class="flat"
|
||||||
<!-- <button #defaultAction-->
|
(click)="removeTask()">Yes</button>
|
||||||
<!-- (click)="modal.close(MODAL_CONFIRM_ID)">No</button>-->
|
<button #defaultAction
|
||||||
<!-- </div>-->
|
(click)="modal.close(MODAL_CONFIRM_ID + columnData.id)">No</button>
|
||||||
<!--</tb-modal>-->
|
</div>
|
||||||
|
</tb-modal>
|
||||||
|
|
||||||
<tb-modal *ngIf="activeBoard" modal-title="Add Task"
|
<tb-modal *ngIf="activeBoard" modal-title="Add Task"
|
||||||
modal-id="{{ MODAL_ID + columnData.id }}">
|
modal-id="{{ MODAL_ID + columnData.id }}">
|
||||||
<label>
|
<label>
|
||||||
Title
|
Title
|
||||||
<input #focusMe type="text" name="title" placeholder="Task Title"
|
<input #focusMe type="text" name="title" placeholder="Task Title"
|
||||||
[(ngModel)]="modalProps.title">
|
[(ngModel)]="modalProps.title">
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
|
@ -40,7 +40,10 @@ export class ColumnDisplay implements OnInit {
|
|||||||
private contextMenuItems: Array<ContextMenuItem>;
|
private contextMenuItems: Array<ContextMenuItem>;
|
||||||
|
|
||||||
private MODAL_ID: string;
|
private MODAL_ID: string;
|
||||||
|
private MODAL_CONFIRM_ID: string;
|
||||||
|
|
||||||
private modalProps: Task;
|
private modalProps: Task;
|
||||||
|
private taskToRemove: number;
|
||||||
|
|
||||||
@Input('column') columnData: Column;
|
@Input('column') columnData: Column;
|
||||||
|
|
||||||
@ -55,11 +58,13 @@ export class ColumnDisplay implements OnInit {
|
|||||||
this.collapseTasks = false;
|
this.collapseTasks = false;
|
||||||
|
|
||||||
this.contextMenuItems = [
|
this.contextMenuItems = [
|
||||||
new ContextMenuItem('Add New Task',
|
new ContextMenuItem('Add Task',
|
||||||
this.getShowModalFunction())
|
this.getShowModalFunction())
|
||||||
];
|
];
|
||||||
|
|
||||||
this.MODAL_ID = 'add-task-form-';
|
this.MODAL_ID = 'add-task-form-';
|
||||||
|
this.MODAL_CONFIRM_ID = 'task-remove-confirm';
|
||||||
|
|
||||||
this.modalProps = new Task();
|
this.modalProps = new Task();
|
||||||
|
|
||||||
boardService.activeBoardChanged.subscribe((board: Board) => {
|
boardService.activeBoardChanged.subscribe((board: Board) => {
|
||||||
@ -145,6 +150,36 @@ export class ColumnDisplay implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeTask() {
|
||||||
|
this.boardService.removeTask(this.taskToRemove)
|
||||||
|
.subscribe((response: ApiResponse) => {
|
||||||
|
response.alerts.forEach(note => this.notes.add(note));
|
||||||
|
|
||||||
|
if (response.status !== 'success') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let boardData = response.data[1][0];
|
||||||
|
|
||||||
|
let newBoard = new Board(+boardData.id, boardData.name,
|
||||||
|
boardData.is_active === '1',
|
||||||
|
boardData.ownColumn,
|
||||||
|
boardData.ownCategory,
|
||||||
|
boardData.ownAutoAction,
|
||||||
|
boardData.ownIssuetracker,
|
||||||
|
boardData.sharedUser);
|
||||||
|
|
||||||
|
this.boardService.updateActiveBoard(newBoard);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getRemoveTaskFunction(taskId: number): Function {
|
||||||
|
return () => {
|
||||||
|
this.taskToRemove = taskId;
|
||||||
|
this.modal.open(this.MODAL_CONFIRM_ID + this.columnData.id);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private getShowModalFunction(): Function {
|
private getShowModalFunction(): Function {
|
||||||
return () => { this.showModal(); };
|
return () => { this.showModal(); };
|
||||||
}
|
}
|
||||||
|
@ -11,10 +11,9 @@
|
|||||||
<span *ngIf="taskData.points > 0" class="badge right" title="Points">
|
<span *ngIf="taskData.points > 0" class="badge right" title="Points">
|
||||||
{{ taskData.points }}</span>
|
{{ taskData.points }}</span>
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<div class="description" [innerHTML]="getTaskDescription()"></div>
|
<div class="description" [innerHTML]="getTaskDescription()"></div>
|
||||||
<div class="hidden">
|
|
||||||
<pre>{{ taskData | json }}</pre>
|
|
||||||
</div>
|
|
||||||
<div class="stats">
|
<div class="stats">
|
||||||
<span *ngIf="userOptions.show_assignee">
|
<span *ngIf="userOptions.show_assignee">
|
||||||
Assigned To:
|
Assigned To:
|
||||||
@ -25,6 +24,7 @@
|
|||||||
Unassigned
|
Unassigned
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="right">
|
<span class="right">
|
||||||
<span *ngIf="taskData.due_date">
|
<span *ngIf="taskData.due_date">
|
||||||
Due: {{ taskData.due_date }}
|
Due: {{ taskData.due_date }}
|
||||||
@ -39,6 +39,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<tb-context-menu [menu-items]="contextMenuItems"></tb-context-menu>
|
<tb-context-menu [menu-items]="contextMenuItems"></tb-context-menu>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -5,16 +5,23 @@ import {
|
|||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||||
|
|
||||||
import * as Marked from 'marked';
|
import * as marked from 'marked';
|
||||||
import * as hljs from 'highlight.js';
|
import * as hljs from 'highlight.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
ApiResponse,
|
||||||
|
Board,
|
||||||
|
Column,
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
ContextMenuItem,
|
ContextMenuItem,
|
||||||
|
Notification,
|
||||||
Task,
|
Task,
|
||||||
UserOptions,
|
UserOptions,
|
||||||
AuthService
|
AuthService,
|
||||||
|
ModalService,
|
||||||
|
NotificationsService
|
||||||
} from '../../shared/index';
|
} from '../../shared/index';
|
||||||
|
import { BoardService } from '../board.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'tb-task',
|
selector: 'tb-task',
|
||||||
@ -23,16 +30,35 @@ import {
|
|||||||
export class TaskDisplay implements OnInit {
|
export class TaskDisplay implements OnInit {
|
||||||
private userOptions: UserOptions;
|
private userOptions: UserOptions;
|
||||||
private contextMenuItems: Array<ContextMenuItem>;
|
private contextMenuItems: Array<ContextMenuItem>;
|
||||||
|
private selectMenuItem: ContextMenuItem;
|
||||||
|
|
||||||
|
private activeBoard: Board;
|
||||||
|
|
||||||
@Input('task') taskData: Task;
|
@Input('task') taskData: Task;
|
||||||
@Input('add-task') addTask: Function;
|
@Input('add-task') addTask: Function;
|
||||||
|
@Input('remove-task') removeTask: Function;
|
||||||
|
|
||||||
constructor(private auth: AuthService,
|
constructor(private auth: AuthService,
|
||||||
private sanitizer: DomSanitizer) {
|
private sanitizer: DomSanitizer,
|
||||||
|
private boardService: BoardService,
|
||||||
|
private modal: ModalService,
|
||||||
|
private notes: NotificationsService) {
|
||||||
auth.userChanged.subscribe(() => {
|
auth.userChanged.subscribe(() => {
|
||||||
this.userOptions = auth.userOptions;
|
this.userOptions = auth.userOptions;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
boardService.activeBoardChanged.subscribe((board: Board) => {
|
||||||
|
let menuText = 'Move to Column: <select>';
|
||||||
|
|
||||||
|
board.columns.forEach((column: Column) => {
|
||||||
|
menuText += '<option>' + column.name + '</option>';
|
||||||
|
});
|
||||||
|
|
||||||
|
menuText += '</select>';
|
||||||
|
|
||||||
|
this.selectMenuItem = new ContextMenuItem(menuText, null, false, false);
|
||||||
|
});
|
||||||
|
|
||||||
this.initMarked();
|
this.initMarked();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,17 +66,20 @@ export class TaskDisplay implements OnInit {
|
|||||||
this.contextMenuItems = [
|
this.contextMenuItems = [
|
||||||
new ContextMenuItem('View Task'),
|
new ContextMenuItem('View Task'),
|
||||||
new ContextMenuItem('Edit Task'),
|
new ContextMenuItem('Edit Task'),
|
||||||
new ContextMenuItem('Delete Task'),
|
new ContextMenuItem('Remove Task', this.removeTask),
|
||||||
new ContextMenuItem('', null, true),
|
new ContextMenuItem('', null, true),
|
||||||
new ContextMenuItem('Move to Column:', null, false, false),
|
new ContextMenuItem('Copy To Board'),
|
||||||
|
new ContextMenuItem('Move To Board'),
|
||||||
new ContextMenuItem('', null, true),
|
new ContextMenuItem('', null, true),
|
||||||
new ContextMenuItem('Add New Task', this.addTask)
|
this.selectMenuItem,
|
||||||
|
new ContextMenuItem('', null, true),
|
||||||
|
new ContextMenuItem('Add Task', this.addTask)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
getTaskDescription(): SafeHtml {
|
getTaskDescription(): SafeHtml {
|
||||||
return this.sanitizer.bypassSecurityTrustHtml(
|
return this.sanitizer.bypassSecurityTrustHtml(
|
||||||
Marked(this.taskData.description));
|
marked(this.taskData.description));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expects a color in full HEX with leading #, e.g. #ffffe0
|
// Expects a color in full HEX with leading #, e.g. #ffffe0
|
||||||
@ -64,7 +93,7 @@ export class TaskDisplay implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private initMarked() {
|
private initMarked() {
|
||||||
let renderer = new Marked.Renderer();
|
let renderer = new marked.Renderer();
|
||||||
|
|
||||||
renderer.listitem = text => {
|
renderer.listitem = text => {
|
||||||
if (/^\s*\[[x ]\]\s*/.test(text)) {
|
if (/^\s*\[[x ]\]\s*/.test(text)) {
|
||||||
@ -91,7 +120,7 @@ export class TaskDisplay implements OnInit {
|
|||||||
return out;
|
return out;
|
||||||
};
|
};
|
||||||
|
|
||||||
Marked.setOptions({
|
marked.setOptions({
|
||||||
renderer,
|
renderer,
|
||||||
smartypants: true,
|
smartypants: true,
|
||||||
highlight: code => {
|
highlight: code => {
|
||||||
|
@ -6,9 +6,7 @@
|
|||||||
(click)="callAction(item.action)">
|
(click)="callAction(item.action)">
|
||||||
<hr *ngIf="item.isSeparator">
|
<hr *ngIf="item.isSeparator">
|
||||||
|
|
||||||
<div *ngIf="!item.isSeparator">
|
<div *ngIf="!item.isSeparator"[innerHTML]="getText(item)"></div>
|
||||||
{{ item.text }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,6 +3,7 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
ElementRef
|
ElementRef
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||||
|
|
||||||
import { ContextMenuItem } from './context-menu-item.model';
|
import { ContextMenuItem } from './context-menu-item.model';
|
||||||
import { ContextMenuService } from './context-menu.service';
|
import { ContextMenuService } from './context-menu.service';
|
||||||
@ -18,7 +19,8 @@ export class ContextMenu {
|
|||||||
animate = true;
|
animate = true;
|
||||||
|
|
||||||
constructor(private el: ElementRef,
|
constructor(private el: ElementRef,
|
||||||
private menuService: ContextMenuService) {
|
private menuService: ContextMenuService,
|
||||||
|
private sanitizer: DomSanitizer) {
|
||||||
menuService.registerMenu(this);
|
menuService.registerMenu(this);
|
||||||
|
|
||||||
let parentElement = el.nativeElement.parentElement;
|
let parentElement = el.nativeElement.parentElement;
|
||||||
@ -31,6 +33,10 @@ export class ContextMenu {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getText(item: ContextMenuItem): SafeHtml {
|
||||||
|
return this.sanitizer.bypassSecurityTrustHtml(item.text);
|
||||||
|
}
|
||||||
|
|
||||||
callAction(action: Function) {
|
callAction(action: Function) {
|
||||||
if (action) {
|
if (action) {
|
||||||
action();
|
action();
|
||||||
@ -49,7 +55,7 @@ export class ContextMenu {
|
|||||||
target.style.top = event.pageY + 'px';
|
target.style.top = event.pageY + 'px';
|
||||||
|
|
||||||
// Adjust position if near an edge
|
// Adjust position if near an edge
|
||||||
setTimeout(() => {
|
let adjustPosition = () => {
|
||||||
let rect = target.getBoundingClientRect();
|
let rect = target.getBoundingClientRect();
|
||||||
|
|
||||||
let offsetX = (event.pageX + rect.width + edgeBuffer) > window.innerWidth;
|
let offsetX = (event.pageX + rect.width + edgeBuffer) > window.innerWidth;
|
||||||
@ -57,8 +63,9 @@ export class ContextMenu {
|
|||||||
|
|
||||||
target.style.left = event.pageX - (offsetX ? rect.width : 0) + 'px';
|
target.style.left = event.pageX - (offsetX ? rect.width : 0) + 'px';
|
||||||
target.style.top = event.pageY - (offsetY ? rect.height : 0) + 'px';
|
target.style.top = event.pageY - (offsetY ? rect.height : 0) + 'px';
|
||||||
},
|
};
|
||||||
0);
|
|
||||||
|
setTimeout(adjustPosition, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user