Refactoring/cleanup and other minor changes
This commit is contained in:
parent
66c06c01e8
commit
ad17f51d71
14
README.md
14
README.md
@ -195,11 +195,11 @@ Because I like seeing the numbers.
|
||||
|
||||
Language | Files | Blank | Comment | Code
|
||||
-----------|--------:|---------:|---------:|---------:
|
||||
TypeScript | 66 | 949 | 119 | 4058
|
||||
PHP | 18 | 660 | 24 | 2034
|
||||
HTML | 21 | 255 | 0 | 1533
|
||||
SASS | 14 | 295 | 10 | 1333
|
||||
**SUM:** | **119** | **2159** | **153** | **8958**
|
||||
TypeScript | 66 | 959 | 120 | 4060
|
||||
PHP | 18 | 665 | 24 | 2043
|
||||
HTML | 21 | 257 | 0 | 1538
|
||||
SASS | 14 | 296 | 10 | 1338
|
||||
**SUM:** | **119** | **2177** | **154** | **8978**
|
||||
|
||||
Command: `cloc --exclude-dir=vendor --exclude-ext=json,svg,ini src/`
|
||||
|
||||
@ -207,8 +207,8 @@ Command: `cloc --exclude-dir=vendor --exclude-ext=json,svg,ini src/`
|
||||
|
||||
Language | Files | Blank | Comment | Code
|
||||
-----------|-------:|---------:|---------:|---------:
|
||||
TypeScript | 38 | 995 | 7 | 3442
|
||||
TypeScript | 38 | 1002 | 8 | 3481
|
||||
PHP | 11 | 793 | 16 | 2338
|
||||
**SUM:** | **49** | **1788** | **23** | **5780**
|
||||
**SUM:** | **49** | **1795** | **24** | **5819**
|
||||
|
||||
Command: `cloc --exclude-ext=xml test/`
|
||||
|
@ -40,6 +40,7 @@
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"aot": true,
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
|
79
src/api/composer.lock
generated
79
src/api/composer.lock
generated
@ -166,16 +166,16 @@
|
||||
},
|
||||
{
|
||||
"name": "gabordemooij/redbean",
|
||||
"version": "v5.4.2",
|
||||
"version": "v5.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/gabordemooij/redbean.git",
|
||||
"reference": "58d366ac135cf14c7b952cda075a7723dfb425a7"
|
||||
"reference": "8b8326f7dbe8023675b0d1936df4800fe0cce0b3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/gabordemooij/redbean/zipball/58d366ac135cf14c7b952cda075a7723dfb425a7",
|
||||
"reference": "58d366ac135cf14c7b952cda075a7723dfb425a7",
|
||||
"url": "https://api.github.com/repos/gabordemooij/redbean/zipball/8b8326f7dbe8023675b0d1936df4800fe0cce0b3",
|
||||
"reference": "8b8326f7dbe8023675b0d1936df4800fe0cce0b3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -203,7 +203,7 @@
|
||||
"keywords": [
|
||||
"orm"
|
||||
],
|
||||
"time": "2019-12-26T17:14:58+00:00"
|
||||
"time": "2020-04-26T13:31:18+00:00"
|
||||
},
|
||||
{
|
||||
"name": "jeremeamia/superclosure",
|
||||
@ -794,24 +794,21 @@
|
||||
},
|
||||
{
|
||||
"name": "phpdocumentor/reflection-common",
|
||||
"version": "2.0.0",
|
||||
"version": "2.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpDocumentor/ReflectionCommon.git",
|
||||
"reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a"
|
||||
"reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a",
|
||||
"reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a",
|
||||
"url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/6568f4687e5b41b054365f9ae03fcb1ed5f2069b",
|
||||
"reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~6"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
@ -842,7 +839,7 @@
|
||||
"reflection",
|
||||
"static analysis"
|
||||
],
|
||||
"time": "2018-08-07T13:53:10+00:00"
|
||||
"time": "2020-04-27T09:25:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpdocumentor/reflection-docblock",
|
||||
@ -1336,16 +1333,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "8.5.3",
|
||||
"version": "8.5.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "67750516bc02f300e2742fed2f50177f8f37bedf"
|
||||
"reference": "8474e22d7d642f665084ba5ec780626cbd1efd23"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/67750516bc02f300e2742fed2f50177f8f37bedf",
|
||||
"reference": "67750516bc02f300e2742fed2f50177f8f37bedf",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8474e22d7d642f665084ba5ec780626cbd1efd23",
|
||||
"reference": "8474e22d7d642f665084ba5ec780626cbd1efd23",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1425,7 +1422,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2020-03-31T08:52:04+00:00"
|
||||
"time": "2020-04-23T04:39:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
@ -2433,16 +2430,16 @@
|
||||
},
|
||||
{
|
||||
"name": "slim/psr7",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/slimphp/Slim-Psr7.git",
|
||||
"reference": "7ca5b5d96687b7c563238715cc80b12675d8b895"
|
||||
"reference": "3c76899e707910779f13d7af95fde12310b0a5ae"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/slimphp/Slim-Psr7/zipball/7ca5b5d96687b7c563238715cc80b12675d8b895",
|
||||
"reference": "7ca5b5d96687b7c563238715cc80b12675d8b895",
|
||||
"url": "https://api.github.com/repos/slimphp/Slim-Psr7/zipball/3c76899e707910779f13d7af95fde12310b0a5ae",
|
||||
"reference": "3c76899e707910779f13d7af95fde12310b0a5ae",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2503,7 +2500,7 @@
|
||||
"psr-7",
|
||||
"psr7"
|
||||
],
|
||||
"time": "2020-01-04T23:05:43+00:00"
|
||||
"time": "2020-05-01T14:24:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "slim/slim",
|
||||
@ -2600,16 +2597,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.15.0",
|
||||
"version": "v1.16.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14"
|
||||
"reference": "1aab00e39cebaef4d8652497f46c15c1b7e45294"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/4719fa9c18b0464d399f1a63bf624b42b6fa8d14",
|
||||
"reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1aab00e39cebaef4d8652497f46c15c1b7e45294",
|
||||
"reference": "1aab00e39cebaef4d8652497f46c15c1b7e45294",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2621,7 +2618,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.15-dev"
|
||||
"dev-master": "1.16-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@ -2668,20 +2665,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-02-27T09:26:54+00:00"
|
||||
"time": "2020-05-08T16:50:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php56",
|
||||
"version": "v1.15.0",
|
||||
"version": "v1.16.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php56.git",
|
||||
"reference": "d51ec491c8ddceae7dca8dd6c7e30428f543f37d"
|
||||
"reference": "14b55b0e88c67c6a14526799abaea197dde78911"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/d51ec491c8ddceae7dca8dd6c7e30428f543f37d",
|
||||
"reference": "d51ec491c8ddceae7dca8dd6c7e30428f543f37d",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/14b55b0e88c67c6a14526799abaea197dde78911",
|
||||
"reference": "14b55b0e88c67c6a14526799abaea197dde78911",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2691,7 +2688,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.15-dev"
|
||||
"dev-master": "1.16-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@ -2738,20 +2735,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-03-09T19:04:49+00:00"
|
||||
"time": "2020-05-08T16:50:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-util",
|
||||
"version": "v1.15.0",
|
||||
"version": "v1.16.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-util.git",
|
||||
"reference": "d8e76c104127675d0ea3df3be0f2ae24a8619027"
|
||||
"reference": "fa1fdaf94e8a60932d8821692eb1ed07efc52db2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-util/zipball/d8e76c104127675d0ea3df3be0f2ae24a8619027",
|
||||
"reference": "d8e76c104127675d0ea3df3be0f2ae24a8619027",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-util/zipball/fa1fdaf94e8a60932d8821692eb1ed07efc52db2",
|
||||
"reference": "fa1fdaf94e8a60932d8821692eb1ed07efc52db2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2760,7 +2757,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.15-dev"
|
||||
"dev-master": "1.16-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@ -2804,7 +2801,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-03-02T11:55:35+00:00"
|
||||
"time": "2020-05-02T14:56:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "theseer/tokenizer",
|
||||
|
@ -1,4 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Slim\Factory\AppFactory;
|
||||
use Slim\Exception\HttpNotFoundException;
|
||||
use Slim\Psr7\Response;
|
||||
@ -35,12 +37,24 @@ $container->set('logger', function() {
|
||||
return $logger;
|
||||
});
|
||||
|
||||
$container->set('strings', function() {
|
||||
$json = file_get_contents('../json/en.json');
|
||||
|
||||
return json_decode($json);
|
||||
});
|
||||
|
||||
$errorMiddleware->setErrorHandler(HttpNotFoundException::class,
|
||||
function () {
|
||||
function (ServerRequestInterface $request) {
|
||||
$response = new Response();
|
||||
|
||||
$message = 'Matching API call not found.';
|
||||
|
||||
if (strpos($request->getUri()->getPath(), 'uploads')) {
|
||||
$message = 'File not found.';
|
||||
}
|
||||
|
||||
$response->withHeader('Content-Type', 'application/json')
|
||||
->getBody()->write('{ message: "Matching API call not found." }');
|
||||
->getBody()->write('{ message: "' . $message . '" }');
|
||||
|
||||
return $response->withStatus(404);
|
||||
}
|
||||
|
@ -20,10 +20,12 @@ export class ApiInterceptor implements HttpInterceptor {
|
||||
|
||||
constructor(private router: Router) {}
|
||||
|
||||
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
intercept(request: HttpRequest<any>, next: HttpHandler):
|
||||
Observable<HttpEvent<any>> {
|
||||
|
||||
const headers = (request.body instanceof FormData)
|
||||
? { }
|
||||
: { 'Content-Type': 'application/json' };
|
||||
? { }
|
||||
: { 'Content-Type': 'application/json' };
|
||||
const token = sessionStorage.getItem(this.JWT_KEY);
|
||||
|
||||
if (token !== null) {
|
||||
|
@ -84,6 +84,8 @@
|
||||
</div>
|
||||
|
||||
<div class="board" cdkDropListGroup *ngIf="activeBoard">
|
||||
<div class="loading" *ngIf="loading">{{ strings['loading'] }}...</div>
|
||||
|
||||
<tb-column class="column" [id]="column.id" [column]="column"
|
||||
*ngFor="let column of activeBoard.columns"
|
||||
(on-update-boards)="updateBoards()"></tb-column>
|
||||
|
@ -34,14 +34,14 @@ export class BoardService {
|
||||
this.initMarked();
|
||||
}
|
||||
|
||||
convertMarkdown(markdown: string, callback = this.defaultCallback, doCount = false): MarkedReturn {
|
||||
async convertMarkdown(markdown: string, callback = this.defaultCallback,
|
||||
doCount = false): Promise<MarkedReturn> {
|
||||
this.checkCounts.total = 0;
|
||||
this.checkCounts.complete = 0;
|
||||
|
||||
const retVal: MarkedReturn = { html: '', counts: {} };
|
||||
|
||||
retVal.html = marked(markdown);
|
||||
callback(false, retVal.html);
|
||||
retVal.html = callback(false, marked(markdown));
|
||||
|
||||
if (doCount) {
|
||||
retVal.counts = this.checkCounts;
|
||||
|
@ -77,6 +77,7 @@
|
||||
<div class="tasks" *ngIf="columnData" cdkDropList
|
||||
[cdkDropListData]="columnData.tasks"
|
||||
(cdkDropListDropped)="drop($event, +columnData.id - 1)">
|
||||
|
||||
<tb-task class="task-container" [id]="task.id" cdkDrag
|
||||
*ngFor="let task of columnData.tasks"
|
||||
[task]="task" [add-task]="getShowModalFunction()"
|
||||
@ -212,7 +213,7 @@
|
||||
<h3>{{ strings['boards_taskComments'] }}</h3>
|
||||
|
||||
<div class="comment" *ngFor="let comment of viewModalProps.comments">
|
||||
<div [innerHTML]="getComment(comment.text)" *ngIf="!comment.isEdit"></div>
|
||||
<div [innerHTML]="comment.html" *ngIf="!comment.isEdit"></div>
|
||||
|
||||
<textarea *ngIf="comment.isEdit" [(ngModel)]="commentEdit.text"
|
||||
(keyup.control.enter)="comment.isEdit = false;editComment()"
|
||||
@ -298,7 +299,10 @@
|
||||
{{ strings['boards_taskTitle'] }}
|
||||
|
||||
<input type="text" name="title" #focusMe [(ngModel)]="modalProps.title"
|
||||
[placeholder]="strings['boards_taskTitlePlaceholder']">
|
||||
[placeholder]="strings['boards_taskTitlePlaceholder']"
|
||||
(keyup.control.enter)="modalProps.id === 0 ? addTask()
|
||||
: updateTask()"
|
||||
(keyup.enter)="preventEnter($event)">
|
||||
</label>
|
||||
|
||||
<label>
|
||||
@ -321,7 +325,7 @@
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<label *ngIf="modalProps.categories.length">
|
||||
{{ strings['boards_taskCategories'] }}
|
||||
|
||||
<select name="categories" multiple [ngModel]="modalProps.categories"
|
||||
|
@ -110,6 +110,7 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
|
||||
this.modalProps = new Task();
|
||||
this.viewModalProps = new Task();
|
||||
|
||||
// These are kept locally to allow override in tests.
|
||||
this.moveItemInArray = moveItemInArray;
|
||||
this.transferArrayItem = transferArrayItem;
|
||||
|
||||
@ -175,7 +176,9 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
userName(id: number) {
|
||||
return this.activeBoard.users.find(u => u.id === id).username;
|
||||
const user = this.activeBoard.users.find(u => u.id === id);
|
||||
|
||||
return user ? user.username : this.strings.none;
|
||||
}
|
||||
|
||||
sortTasks() {
|
||||
@ -582,11 +585,6 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
|
||||
this.modal.open(this.MODAL_ID + this.columnData.id);
|
||||
}
|
||||
|
||||
getComment(text: string) {
|
||||
const data = this.boardService.convertMarkdown(text, this.markedCallback);
|
||||
return this.sanitizer.bypassSecurityTrustHtml(data.html);
|
||||
}
|
||||
|
||||
getUserName(userId: number) {
|
||||
const user = this.activeBoard.users
|
||||
.find((test: User) => test.id === +userId);
|
||||
@ -603,6 +601,22 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.updateTaskActivity(taskId);
|
||||
|
||||
this.boardService
|
||||
.convertMarkdown(viewTask.description, this.markedCallback)
|
||||
.then(data => {
|
||||
data.html.replace(/(\{)([^}]+)(\})/g, '{{ "{" }}$2{{ "}" }}');
|
||||
|
||||
this.viewModalProps.html =
|
||||
this.sanitizer.bypassSecurityTrustHtml(data.html);
|
||||
});
|
||||
|
||||
viewTask.comments.forEach(comment => {
|
||||
this.boardService.convertMarkdown(comment.text, this.markedCallback)
|
||||
.then(data => {
|
||||
comment.html = this.sanitizer.bypassSecurityTrustHtml(data.html);
|
||||
});
|
||||
});
|
||||
|
||||
this.newComment = '';
|
||||
this.viewModalProps = Object.assign({}, viewTask);
|
||||
this.checkDueDate();
|
||||
@ -663,22 +677,17 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
|
||||
|
||||
private convertToTask(updatedTask: any) {
|
||||
const task = new Task(updatedTask.id,
|
||||
updatedTask.title,
|
||||
updatedTask.description,
|
||||
updatedTask.color,
|
||||
updatedTask.due,
|
||||
updatedTask.points,
|
||||
updatedTask.position,
|
||||
updatedTask.column_id,
|
||||
updatedTask.ownComment,
|
||||
updatedTask.ownAttachment,
|
||||
updatedTask.sharedUser,
|
||||
updatedTask.sharedCategory);
|
||||
const data = this.boardService.convertMarkdown(task.description,
|
||||
(_, text) => text, true);
|
||||
|
||||
task.html = data.html;
|
||||
|
||||
updatedTask.title,
|
||||
updatedTask.description,
|
||||
updatedTask.color,
|
||||
updatedTask.due,
|
||||
updatedTask.points,
|
||||
updatedTask.position,
|
||||
updatedTask.column_id,
|
||||
updatedTask.ownComment,
|
||||
updatedTask.ownAttachment,
|
||||
updatedTask.sharedUser,
|
||||
updatedTask.sharedCategory);
|
||||
return task;
|
||||
}
|
||||
|
||||
@ -718,24 +727,19 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
|
||||
this.activeBoard.issue_trackers.forEach(tracker => {
|
||||
const re = new RegExp(tracker.regex, 'ig');
|
||||
const replacements = new Array<any>();
|
||||
|
||||
let result = re.exec(text);
|
||||
|
||||
while (result !== null) {
|
||||
const link = '<a href="' +
|
||||
tracker.url.replace(/%BUGID%/g, result[1]) +
|
||||
'" target="tb_external" rel="noreferrer">' +
|
||||
result[0] + '</a>';
|
||||
const link = '<a href="' + tracker.url.replace(/%BUGID%/g, result[1]) +
|
||||
'" target="tb_external" rel="noreferrer">' + result[0] + '</a>';
|
||||
|
||||
replacements.push({
|
||||
str: result[0],
|
||||
link
|
||||
});
|
||||
replacements.push({ str: result[0], link });
|
||||
result = re.exec(text);
|
||||
}
|
||||
|
||||
for (let i = replacements.length - 1; i >= 0; --i) {
|
||||
text = text.replace(replacements[i].str,
|
||||
replacements[i].link);
|
||||
text = text.replace(replacements[i].str, replacements[i].link);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -141,7 +141,7 @@ export class TaskDisplayComponent implements OnInit {
|
||||
|
||||
taskData.column_id = boardData.columns[0].id;
|
||||
|
||||
this.boardService.addTask(taskData)
|
||||
this.boardService.addTask(taskData as any)
|
||||
.subscribe((response: ApiResponse) => {
|
||||
if (response.status === 'success') {
|
||||
this.notes.add(
|
||||
@ -201,16 +201,17 @@ export class TaskDisplayComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = this.boardService.convertMarkdown(
|
||||
this.taskData.description, this.markedCallback, true
|
||||
);
|
||||
this.boardService
|
||||
.convertMarkdown(this.taskData.description, this.markedCallback, true)
|
||||
.then(data => {
|
||||
data.html.replace(/(\{)([^}]+)(\})/g, '{{ "{" }}$2{{ "}" }}');
|
||||
|
||||
data.html.replace(/(\{)([^}]+)(\})/g, '{{ "{" }}$2{{ "}" }}');
|
||||
if (data.counts.total) {
|
||||
this.percentComplete = data.counts.complete / data.counts.total;
|
||||
}
|
||||
if (data.counts.total) {
|
||||
this.percentComplete = data.counts.complete / data.counts.total;
|
||||
}
|
||||
|
||||
this.taskData.html = this.sanitizer.bypassSecurityTrustHtml(data.html);
|
||||
this.taskData.html = this.sanitizer.bypassSecurityTrustHtml(data.html);
|
||||
});
|
||||
}
|
||||
|
||||
private checkDueDate() {
|
||||
@ -241,31 +242,22 @@ export class TaskDisplayComponent implements OnInit {
|
||||
|
||||
// Needs anonymous function for proper `this` context.
|
||||
private markedCallback = (_: any, text: string) => {
|
||||
if (!this.activeBoard.issue_trackers) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.activeBoard.issue_trackers.forEach(tracker => {
|
||||
const re = new RegExp(tracker.regex, 'ig');
|
||||
const replacements = new Array<any>();
|
||||
|
||||
let result = re.exec(text);
|
||||
|
||||
while (result !== null) {
|
||||
const link = '<a href="' +
|
||||
tracker.url.replace(/%BUGID%/g, result[1]) +
|
||||
'" target="tb_external" rel="noreferrer">' +
|
||||
result[0] + '</a>';
|
||||
const link = '<a href="' + tracker.url.replace(/%BUGID%/g, result[1]) +
|
||||
'" target="tb_external" rel="noreferrer">' + result[0] + '</a>';
|
||||
|
||||
replacements.push({
|
||||
str: result[0],
|
||||
link
|
||||
});
|
||||
replacements.push({ str: result[0], link });
|
||||
result = re.exec(text);
|
||||
}
|
||||
|
||||
for (let i = replacements.length - 1; i >= 0; --i) {
|
||||
text = text.replace(replacements[i].str,
|
||||
replacements[i].link);
|
||||
text = text.replace(replacements[i].str, replacements[i].link);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -28,12 +28,12 @@ export class LoginComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.authService.authenticate()
|
||||
.subscribe(isAuth => {
|
||||
if (isAuth) {
|
||||
this.router.navigate(['/boards']);
|
||||
}
|
||||
});
|
||||
this.authService.authenticate(undefined, true)
|
||||
.subscribe(isAuth => {
|
||||
if (isAuth) {
|
||||
this.router.navigate(['/boards']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
login(): void {
|
||||
@ -53,6 +53,15 @@ export class LoginComponent implements OnInit {
|
||||
});
|
||||
|
||||
if (response.status === 'success') {
|
||||
if (this.authService.attemptedRoute?.length) {
|
||||
this.router.navigate([this.authService.attemptedRoute]);
|
||||
|
||||
this.authService.attemptedRoute = undefined;
|
||||
this.isSubmitted = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.router.navigate(['/boards']);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CanActivate } from '@angular/router';
|
||||
import { CanActivate, RouterStateSnapshot } from '@angular/router';
|
||||
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
@ -8,8 +8,8 @@ export class AuthGuard implements CanActivate {
|
||||
constructor(private authService: AuthService) {
|
||||
}
|
||||
|
||||
canActivate() {
|
||||
return this.authService.authenticate();
|
||||
canActivate(_: any, state: RouterStateSnapshot) {
|
||||
return this.authService.authenticate(state.url);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,8 @@ export class AuthService {
|
||||
public userOptions: UserOptions = null;
|
||||
public userChanged = this.activeUser.asObservable();
|
||||
|
||||
public attemptedRoute: string;
|
||||
|
||||
constructor(public constants: Constants, private http: HttpClient,
|
||||
public router: Router, private strings: StringsService) {
|
||||
}
|
||||
@ -33,7 +35,11 @@ export class AuthService {
|
||||
this.activeUser.next(user);
|
||||
}
|
||||
|
||||
authenticate(): Observable<boolean> {
|
||||
authenticate(url: string, isLogin = false): Observable<boolean> {
|
||||
if (!isLogin) {
|
||||
this.attemptedRoute = url;
|
||||
}
|
||||
|
||||
return this.http.post('api/authenticate', null)
|
||||
.pipe(
|
||||
map((response: ApiResponse) => {
|
||||
@ -46,24 +52,20 @@ export class AuthService {
|
||||
|
||||
login(username: string, password: string,
|
||||
remember: boolean): Observable<ApiResponse> {
|
||||
const json = JSON.stringify({
|
||||
username,
|
||||
password,
|
||||
remember
|
||||
});
|
||||
const json = JSON.stringify({ username, password, remember });
|
||||
|
||||
return this.http.post('api/login', json)
|
||||
.pipe(
|
||||
map((response: ApiResponse) => {
|
||||
this.updateUser(response.data[1], response.data[2]);
|
||||
return response;
|
||||
}),
|
||||
catchError((err, _) => {
|
||||
this.updateUser(null, null);
|
||||
return of(err.error as ApiResponse);
|
||||
})
|
||||
);
|
||||
}
|
||||
return this.http.post('api/login', json)
|
||||
.pipe(
|
||||
map((response: ApiResponse) => {
|
||||
this.updateUser(response.data[1], response.data[2]);
|
||||
return response;
|
||||
}),
|
||||
catchError((err, _) => {
|
||||
this.updateUser(null, null);
|
||||
return of(err.error as ApiResponse);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
logout(): Observable<ApiResponse> {
|
||||
return this.http.post('api/logout', null)
|
||||
|
@ -1,4 +1,8 @@
|
||||
import { SafeHtml } from '@angular/platform-browser';
|
||||
|
||||
export class Comment {
|
||||
public html: SafeHtml;
|
||||
|
||||
constructor(public id: number = 0,
|
||||
public text: string = '',
|
||||
public user_id: number = 0, // tslint:disable-line
|
||||
|
@ -6,6 +6,7 @@
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"sortBy": "Sort By",
|
||||
"loading": "Loading",
|
||||
|
||||
"dashboard": "Dashboard",
|
||||
"boards": "Boards",
|
||||
|
@ -6,6 +6,7 @@
|
||||
"save": "Guardar",
|
||||
"cancel": "Cancelar",
|
||||
"sortBy": "Ordenar por",
|
||||
"loading": "Cargando",
|
||||
|
||||
"dashboard": "Salpicadero",
|
||||
"boards": "Tableros",
|
||||
|
@ -6,6 +6,7 @@
|
||||
"save": "Enregistrer",
|
||||
"cancel": "Annuler",
|
||||
"sortBy": "Trier par ",
|
||||
"loading": "Chargement",
|
||||
|
||||
"dashboard": "Tableau de bord",
|
||||
"boards": "Tableaux",
|
||||
|
@ -29,6 +29,11 @@
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
|
||||
.loading {
|
||||
font-size: 24px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.icon-minus-squared-alt,
|
||||
.icon-plus-squared-alt {
|
||||
cursor: pointer;
|
||||
@ -366,7 +371,7 @@
|
||||
.description {
|
||||
max-height: 20em;
|
||||
overflow: auto;
|
||||
padding: 1em;
|
||||
padding: 0 1em;
|
||||
|
||||
.checklist {
|
||||
list-style: none;
|
||||
@ -380,6 +385,7 @@
|
||||
margin-top: 12px;
|
||||
|
||||
button {
|
||||
margin: 0;
|
||||
width: 45%;
|
||||
}
|
||||
}
|
||||
|
@ -60,9 +60,10 @@ describe('BoardService', () => {
|
||||
});
|
||||
|
||||
it('converts markdown', () => {
|
||||
const actual = service.convertMarkdown('# Test');
|
||||
|
||||
expect(actual.html).toEqual('<h1 id="test">Test</h1>\n');
|
||||
service.convertMarkdown('# Test')
|
||||
.then(actual => {
|
||||
expect(actual.html).toEqual('<h1 id="test">Test</h1>\n');
|
||||
});
|
||||
});
|
||||
|
||||
it('handles errors when getting all boards', () => {
|
||||
|
@ -558,14 +558,6 @@ describe('ColumnDisplay', () => {
|
||||
expect(component.viewModalProps.column_id).toEqual(1);
|
||||
});
|
||||
|
||||
it('gets a comment converted from markdown', () => {
|
||||
component.activeBoard = { issue_trackers: [] } as any;
|
||||
|
||||
const comment = component.getComment('# Testing');
|
||||
|
||||
expect(comment).toEqual('<h1 id="testing">Testing</h1>\n');
|
||||
});
|
||||
|
||||
it('gets a username by user id', () => {
|
||||
component.activeBoard = {
|
||||
users: [{ id: 1, username: 'test' } as any]
|
||||
|
@ -78,48 +78,77 @@ describe('TaskDisplay', () => {
|
||||
expect(component.taskData.id).toEqual(1);
|
||||
});
|
||||
|
||||
it('parses task description markdown into text', () => {
|
||||
it('parses task description markdown into text', done => {
|
||||
setupBoard();
|
||||
component.taskData.description = '# Make this HTML';
|
||||
component.activeBoard.issue_trackers = [
|
||||
{ regex: 'test', url: '%BUGID%' }
|
||||
] as any;
|
||||
{ id: 1, regex: 'test', url: '%BUGID%' }
|
||||
];
|
||||
component.ngOnInit();
|
||||
|
||||
// tslint:disable-next-line
|
||||
expect(component.taskData.html['changingThisBreaksApplicationSecurity'])
|
||||
.toEqual('<h1 id="make-this-html">Make this HTML</h1>\n');
|
||||
setTimeout(() => {
|
||||
// tslint:disable-next-line
|
||||
expect(component.taskData.html['changingThisBreaksApplicationSecurity'])
|
||||
.toEqual('<h1 id="make-this-html">Make this HTML</h1>\n');
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('handles checklists in markdown', () => {
|
||||
it('handles checklists in markdown', done => {
|
||||
setupBoard();
|
||||
component.taskData.description = ' - [x] One\n - [ ] Two';
|
||||
component.ngOnInit();
|
||||
|
||||
// tslint:disable-next-line
|
||||
expect(component.taskData.html['changingThisBreaksApplicationSecurity'])
|
||||
.toEqual('<ul>\n<li><i class="icon icon-check"></i>One</li>\n' +
|
||||
'<li><i class="icon icon-check-empty"></i>Two</li>\n</ul>\n');
|
||||
setTimeout(() => {
|
||||
// tslint:disable-next-line
|
||||
expect(component.taskData.html['changingThisBreaksApplicationSecurity'])
|
||||
.toEqual('<ul>\n<li><i class="icon icon-check"></i>One</li>\n' +
|
||||
'<li><i class="icon icon-check-empty"></i>Two</li>\n</ul>\n');
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('adds attributes to links in markdown', () => {
|
||||
it('adds attributes to links in markdown', done => {
|
||||
setupBoard();
|
||||
component.taskData.description = '[link](google.com)';
|
||||
component.ngOnInit();
|
||||
|
||||
// tslint:disable-next-line
|
||||
expect(component.taskData.html['changingThisBreaksApplicationSecurity'])
|
||||
.toContain('target="tb_external" rel="noreferrer"');
|
||||
setTimeout(() => {
|
||||
// tslint:disable-next-line
|
||||
expect(component.taskData.html['changingThisBreaksApplicationSecurity'])
|
||||
.toContain('target="tb_external" rel="noreferrer"');
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('provides a custom style for percentage of task completed', () => {
|
||||
it('handles issue trackers in markdown', done => {
|
||||
setupBoard();
|
||||
component.activeBoard.issue_trackers = [
|
||||
{ id: 1, url: 'TaskBoard/issues/%BUGID%', regex: '(?:Issue)?#(\\d+)' }
|
||||
];
|
||||
|
||||
component.taskData.description = '#123';
|
||||
component.ngOnInit();
|
||||
|
||||
setTimeout(() => {
|
||||
// tslint:disable-next-line
|
||||
expect(component.taskData.html['changingThisBreaksApplicationSecurity'])
|
||||
.toContain('href="TaskBoard/issues/123"');
|
||||
done();
|
||||
}, 100);
|
||||
});;
|
||||
|
||||
it('provides a custom style for percentage of task completed', done => {
|
||||
component.percentComplete = .5;
|
||||
|
||||
const actual = component.getPercentStyle();
|
||||
|
||||
// tslint:disable-next-line
|
||||
expect((actual as any)['changingThisBreaksApplicationSecurity'])
|
||||
.toContain('width: 50%;');
|
||||
setTimeout(() => {
|
||||
// tslint:disable-next-line
|
||||
expect((actual as any)['changingThisBreaksApplicationSecurity'])
|
||||
.toContain('width: 50%;');
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('provides a custom title for percentage of task completed', () => {
|
||||
|
@ -82,6 +82,11 @@ describe('Login', () => {
|
||||
component.login();
|
||||
|
||||
expect(spy).toHaveBeenCalledWith(['/boards']);
|
||||
|
||||
component.authService.attemptedRoute = '/boards/2';
|
||||
component.login();
|
||||
|
||||
expect(spy).toHaveBeenCalledWith(['/boards/2']);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -132,6 +132,25 @@ describe('UserAdmin', () => {
|
||||
expect(called).toEqual(true);
|
||||
});
|
||||
|
||||
it('shows a modal', () => {
|
||||
component.showModal();
|
||||
expect(component.modalProps.prefix).toEqual(true);
|
||||
|
||||
const user = new UserDisplay(1);
|
||||
user.board_access = [1, 2];
|
||||
|
||||
component.showModal(false, user);
|
||||
expect(component.modalProps.prefix).toEqual(false);
|
||||
});
|
||||
|
||||
it('shows a confirmation modal', () => {
|
||||
const user = new UserDisplay(1);
|
||||
|
||||
component.showConfirmModal(user);
|
||||
|
||||
expect(component.userToRemove).toEqual(user);
|
||||
});
|
||||
|
||||
it('validates modal user data', () => {
|
||||
let called = false;
|
||||
|
||||
|
@ -28,7 +28,8 @@ describe('AuthGuard', () => {
|
||||
it('implements CanActivate', () => {
|
||||
let actual = false;
|
||||
|
||||
service.canActivate().subscribe(value => actual = value);
|
||||
service.canActivate(null, { url: '' } as any)
|
||||
.subscribe(value => actual = value);
|
||||
|
||||
expect(actual).toEqual(true);
|
||||
});
|
||||
|
@ -52,7 +52,7 @@ describe('AuthService', () => {
|
||||
});
|
||||
|
||||
it('authenticates a user', () => {
|
||||
service.authenticate().subscribe(response => {
|
||||
service.authenticate('').subscribe(response => {
|
||||
expect(response).toEqual(true);
|
||||
});
|
||||
|
||||
@ -60,7 +60,7 @@ describe('AuthService', () => {
|
||||
});
|
||||
|
||||
it('handles errors on authenticate', () => {
|
||||
service.authenticate().subscribe(response => {
|
||||
service.authenticate('').subscribe(response => {
|
||||
expect(response).toEqual(false);
|
||||
});
|
||||
|
||||
@ -91,7 +91,7 @@ describe('AuthService', () => {
|
||||
testCall('api/logout', 'POST');
|
||||
});
|
||||
|
||||
const testCall = (url, method, isError = false) => {
|
||||
const testCall = (url: string, method: string, isError = false) => {
|
||||
const req = httpMock.expectOne({ method, url });
|
||||
expect(req.request.method).toEqual(method);
|
||||
|
||||
|
Reference in New Issue
Block a user