WIP - Various updates

This commit is contained in:
Matthew Ross 2018-06-01 06:43:09 -04:00
parent 3774d2ba71
commit fe080b9817
39 changed files with 2777 additions and 1562 deletions

1751
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,7 @@
},
"scripts": {
"build": "ng b --output-hashing=none",
"build:prod": "npm-run-all -s \"build -- --prod\" api-changed",
"build:prod": "npm-run-all -s \"build -- --prod --aot=false --build-optimizer=false\" api-changed",
"build:dev": "npm-run-all -s build api-changed",
"watch": "npm-run-all -l -p \"build -- --watch\" dist-watch",
"dist-watch": "nodemon -w dist/api/ -x \"npm run api-changed\"",
@ -52,7 +52,7 @@
"chartist": "^0.11.0",
"chartist-plugin-tooltips": "^0.0.17",
"classlist.js": "^1.1.20150312",
"core-js": "^2.5.6",
"core-js": "^2.5.7",
"dragula": "^3.7.2",
"highlight.js": "^9.12.0",
"marked": "^0.4.0",
@ -63,13 +63,13 @@
"zone.js": "^0.8.26"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.6.3",
"@angular/cli": "^6.0.3",
"@angular-devkit/build-angular": "~0.6.5",
"@angular/cli": "^6.0.7",
"@angular/compiler-cli": "^6.0.3",
"@angular/language-service": "^6.0.3",
"@types/jasmine": "~2.8.7",
"@types/jasminewd2": "~2.0.3",
"@types/node": "~10.1.2",
"@types/node": "~10.1.4",
"bourbon": "5.0.0",
"bourbon-neat": "1.9.0",
"codelyzer": "^4.3.0",
@ -85,7 +85,7 @@
"nodemon": "^1.17.5",
"npm-run-all": "^4.1.3",
"protractor": "~5.3.2",
"ts-node": "~6.0.3",
"ts-node": "~6.0.5",
"tslint": "~5.10.0",
"typescript": "^2.7.2"
}

27
src/api/composer.lock generated
View File

@ -258,25 +258,28 @@
},
{
"name": "myclabs/deep-copy",
"version": "1.7.0",
"version": "1.8.0",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
"reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e"
"reference": "478465659fd987669df0bd8a9bf22a8710e5f1b6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e",
"reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/478465659fd987669df0bd8a9bf22a8710e5f1b6",
"reference": "478465659fd987669df0bd8a9bf22a8710e5f1b6",
"shasum": ""
},
"require": {
"php": "^5.6 || ^7.0"
"php": "^7.1"
},
"replace": {
"myclabs/deep-copy": "self.version"
},
"require-dev": {
"doctrine/collections": "^1.0",
"doctrine/common": "^2.6",
"phpunit/phpunit": "^4.1"
"phpunit/phpunit": "^7.1"
},
"type": "library",
"autoload": {
@ -299,7 +302,7 @@
"object",
"object graph"
],
"time": "2017-10-19T19:58:43+00:00"
"time": "2018-05-29T17:25:09+00:00"
},
{
"name": "myclabs/php-enum",
@ -1109,16 +1112,16 @@
},
{
"name": "phpunit/phpunit-mock-objects",
"version": "5.0.6",
"version": "5.0.7",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
"reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf"
"reference": "3eaf040f20154d27d6da59ca2c6e28ac8fd56dce"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/33fd41a76e746b8fa96d00b49a23dadfa8334cdf",
"reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/3eaf040f20154d27d6da59ca2c6e28ac8fd56dce",
"reference": "3eaf040f20154d27d6da59ca2c6e28ac8fd56dce",
"shasum": ""
},
"require": {
@ -1164,7 +1167,7 @@
"mock",
"xunit"
],
"time": "2018-01-06T05:45:45+00:00"
"time": "2018-05-29T13:50:43+00:00"
},
{
"name": "pimple/pimple",

View File

@ -1,6 +1,8 @@
import { Injectable } from '@angular/core';
import {
HttpErrorResponse,
HttpHeaderResponse,
HttpResponseBase,
HttpEvent,
HttpHandler,
HttpInterceptor,
@ -36,7 +38,15 @@ export class ApiInterceptor implements HttpInterceptor {
return next.handle(request).pipe(
tap((evt: HttpEvent<any>) => {
if (!(evt instanceof HttpResponse)) {
if (evt instanceof HttpHeaderResponse ||
!(evt instanceof HttpResponseBase)) {
return;
}
if ((evt.status === 401 || evt.status === 400) &&
(evt.url + '').indexOf('login') === -1) {
localStorage.removeItem(this.JWT_KEY);
this.router.navigate(['']);
return;
}
@ -44,13 +54,6 @@ export class ApiInterceptor implements HttpInterceptor {
if (response.data) {
localStorage.setItem(this.JWT_KEY, response.data[0]);
}
}, (err: any) => {
if ((err instanceof HttpErrorResponse) &&
(err.status === 401 || err.status === 400) &&
(err.url + '').indexOf('login') === -1) {
this.router.navigate(['']);
localStorage.removeItem(this.JWT_KEY);
}
})
);
}

View File

@ -31,7 +31,7 @@ import { SharedModule } from './shared/shared.module';
provide: HTTP_INTERCEPTORS,
useClass: ApiInterceptor,
multi: true
},
}
],
declarations: [
AppComponent,

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import {
@ -30,80 +30,80 @@ export class BoardService {
getBoards(): Observable<ApiResponse> {
return this.http.get('api/boards')
.pipe(
catchError((err, caught) => { return caught; }),
map((response: ApiResponse) => { return response; })
map((response: ApiResponse) => { return response; }),
catchError((err) => { return of(<ApiResponse>err.error); })
);
}
toggleCollapsed(userId: number, columnId: number): Observable<ApiResponse> {
return this.http.post('api/users/' + userId + '/cols', { id: columnId })
.pipe(
catchError((err, caught) => { return caught; }),
map((response: ApiResponse) => { return response; })
map((response: ApiResponse) => { return response; }),
catchError((err) => { return of(<ApiResponse>err.error); })
);
}
updateBoard(board: Board): Observable<ApiResponse> {
return this.http.post('api/boards/' + board.id, board)
.pipe(
catchError((err, caught) => { return caught; }),
map((response: ApiResponse) => { return response; })
map((response: ApiResponse) => { return response; }),
catchError((err) => { return of(<ApiResponse>err.error); })
);
}
updateColumn(column: Column): Observable<ApiResponse> {
return this.http.post('api/columns/' + column.id, column)
.pipe(
catchError((err, caught) => { return caught; }),
map((response: ApiResponse) => { return response; })
map((response: ApiResponse) => { return response; }),
catchError((err) => { return of(<ApiResponse>err.error); })
);
}
addTask(task: Task): Observable<ApiResponse> {
return this.http.post('api/tasks', task)
.pipe(
catchError((err, caught) => { return caught; }),
map((response: ApiResponse) => { return response; })
map((response: ApiResponse) => { return response; }),
catchError((err) => { return of(<ApiResponse>err.error); })
);
}
updateTask(task: Task): Observable<ApiResponse> {
return this.http.post('api/tasks/' + task.id, task)
.pipe(
catchError((err, caught) => { return caught; }),
map((response: ApiResponse) => { return response; })
map((response: ApiResponse) => { return response; }),
catchError((err) => { return of(<ApiResponse>err.error); })
);
}
removeTask(taskId: number): Observable<ApiResponse> {
return this.http.delete('api/tasks/' + taskId)
.pipe(
catchError((err, caught) => { return caught; }),
map((response: ApiResponse) => { return response; })
map((response: ApiResponse) => { return response; }),
catchError((err) => { return of(<ApiResponse>err.error); })
);
}
getTaskActivity(taskId: number): Observable<ApiResponse> {
return this.http.get('api/activity/task/' + taskId)
.pipe(
catchError((err, caught) => { return caught; }),
map((response: ApiResponse) => { return response; })
map((response: ApiResponse) => { return response; }),
catchError((err) => { return of(<ApiResponse>err.error); })
);
}
updateComment(comment: Comment): Observable<ApiResponse> {
return this.http.post('api/comments/' + comment.id, comment)
.pipe(
catchError((err, caught) => { return caught; }),
map((response: ApiResponse) => { return response; })
map((response: ApiResponse) => { return response; }),
catchError((err) => { return of(<ApiResponse>err.error); })
);
}
removeComment(commentId: number): Observable<ApiResponse> {
return this.http.delete('api/comments/' + commentId)
.pipe(
catchError((err, caught) => { return caught; }),
map((response: ApiResponse) => { return response; })
map((response: ApiResponse) => { return response; }),
catchError((err) => { return of(<ApiResponse>err.error); })
);
}

View File

@ -105,9 +105,8 @@
{{ strings['boards_taskDue'] + ' ' + viewModalProps.due_date }}
</div>
<div class="description" *ngIf="viewModalProps.description.length">
<ng-template ngNonBindable
*compile="getTaskDescription()"></ng-template>
<div class="description" *ngIf="viewModalProps.description.length"
[innerHtml]="getTaskDescription()">
</div>
<div class="stats">

View File

@ -540,15 +540,11 @@ export class ColumnDisplay implements OnInit, OnDestroy {
private getTaskDescription() {
let html = marked(this.viewModalProps.description, this.markedCallback);
// Escape curly braces for dynamic component.
html = html.replace(/(\{)([^}]+)(\})/g, '{{ "{" }}$2{{ "}" }}');
return html;
return html.replace(/(\{)([^}]+)(\})/g, '{{ "{" }}$2{{ "}" }}');
}
private getComment(text: string) {
let html = marked(text, this.markedCallback);
return this.sanitizer.bypassSecurityTrustHtml(html);
}

View File

@ -108,7 +108,8 @@ export class TaskDisplay implements OnInit {
}
getTaskDescription(): string {
return marked(this.taskData.description, this.markedCallback);
let html = marked(this.taskData.description, this.markedCallback);
return html.replace(/(\{)([^}]+)(\})/g, '{{ "{" }}$2{{ "}" }}');
}
getPercentStyle() {

View File

@ -47,11 +47,7 @@ export class Login implements OnInit {
this.isSubmitted = true;
this.authService.login(this.username, this.password, this.remember)
.subscribe((response: any) => {
response = response.error
? <ApiResponse>response.error
: <ApiResponse>response;
.subscribe((response: ApiResponse) => {
response.alerts.forEach(msg => {
this.notes.add(new Notification(msg.type, msg.text));
});

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import {
@ -18,7 +18,7 @@ export class AutoActionsService {
return this.http.post('api/autoactions', action)
.pipe(
map((response: ApiResponse) => { return response; }),
catchError((err, caught) => { return caught; })
catchError((err) => { return of(<ApiResponse>err.error); })
);
}
@ -26,7 +26,7 @@ export class AutoActionsService {
return this.http.delete('api/autoactions/' + action.id)
.pipe(
map((response: ApiResponse) => { return response; }),
catchError((err, caught) => { return caught; })
catchError((err) => { return of(<ApiResponse>err.error); })
);
}
}

View File

@ -124,6 +124,10 @@ export class BoardAdmin implements OnDestroy {
}
addEditBoard(): void {
if (!this.modal.isOpen(this.MODAL_ID)) {
return;
}
let isAdd = this.modalProps.title === 'Add';
this.saving = true;

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import {
@ -22,24 +22,24 @@ export class BoardAdminService {
addBoard(board: BoardData): Observable<ApiResponse> {
return this.http.post('api/boards', board)
.pipe(
catchError((err, caught) => { return caught; }),
map((response: ApiResponse) => { return response; })
map((response: ApiResponse) => { return response; }),
catchError((err) => { return of(<ApiResponse>err.error); })
);
}
editBoard(board: BoardData): Observable<ApiResponse> {
return this.http.post('api/boards/' + board.id, board)
.pipe(
catchError((err, caught) => { return caught; }),
map((response: ApiResponse) => { return response; })
map((response: ApiResponse) => { return response; }),
catchError((err) => { return of(<ApiResponse>err.error); })
);
}
removeBoard(boardId: number): Observable<ApiResponse> {
return this.http.delete('api/boards/' + boardId)
.pipe(
catchError((err, caught) => { return caught; }),
map((response: ApiResponse) => { return response; })
map((response: ApiResponse) => { return response; }),
catchError((err) => { return of(<ApiResponse>err.error); })
);
}

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import {
@ -31,8 +31,8 @@ export class SettingsService {
getUsers(): Observable<ApiResponse> {
return this.http.get('api/users')
.pipe(
catchError((err, caught) => { return caught; }),
map((response: ApiResponse) => { return response; })
map((response: ApiResponse) => { return response; }),
catchError((err) => { return of(<ApiResponse>err.error); })
);
}
@ -46,8 +46,8 @@ export class SettingsService {
getBoards(): Observable<ApiResponse> {
return this.http.get('api/boards')
.pipe(
catchError((err, caught) => { return caught; }),
map((response: ApiResponse) => { return response; })
map((response: ApiResponse) => { return response; }),
catchError((err) => { return of(<ApiResponse>err.error); })
);
}
@ -58,8 +58,8 @@ export class SettingsService {
getActions(): Observable<ApiResponse> {
return this.http.get('api/autoactions')
.pipe(
catchError((err, caught) => { return caught; }),
map((response: ApiResponse) => { return response; })
map((response: ApiResponse) => { return response; }),
catchError((err) => { return of(<ApiResponse>err.error); })
);
}
}

View File

@ -93,6 +93,10 @@ export class UserAdmin {
}
addEditUser(): void {
if (!this.modal.isOpen(this.MODAL_ID)) {
return;
}
let isAdd = this.modalProps.prefix;
this.saving = true;

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { ApiResponse } from '../../shared/models';
@ -15,24 +15,24 @@ export class UserAdminService {
addUser(user: ModalUser): Observable<ApiResponse> {
return this.http.post('api/users', user)
.pipe(
catchError((err, caught) => { return caught; }),
map((response: ApiResponse) => { return response; })
map((response: ApiResponse) => { return response; }),
catchError((err) => { return of(<ApiResponse>err.error); })
);
}
editUser(user: ModalUser): Observable<ApiResponse> {
return this.http.post('api/users/' + user.id, user)
.pipe(
catchError((err, caught) => { return caught; }),
map((response: ApiResponse) => { return response; })
map((response: ApiResponse) => { return response; }),
catchError((err) => { return of(<ApiResponse>err.error); })
);
}
removeUser(userId: number): Observable<ApiResponse> {
return this.http.delete('api/users/' + userId)
.pipe(
catchError((err, caught) => { return caught; }),
map((response: ApiResponse) => { return response; })
map((response: ApiResponse) => { return response; }),
catchError((err) => { return of(<ApiResponse>err.error); })
);
}
}

View File

@ -120,7 +120,9 @@ export class UserSettings implements OnInit {
this.resetPasswordForm();
this.changePassword.submitted = false;
this.auth.updateUser(JSON.parse(response.data[1]));
if (response.status === 'success') {
this.auth.updateUser(JSON.parse(response.data[1]));
}
});
}
@ -129,7 +131,7 @@ export class UserSettings implements OnInit {
if (this.changeUsername.newName === '') {
this.notes.add(new Notification('error',
'New Username cannot be blank.'));
this.strings['settings_usernameRequired']));
this.changeUsername.submitted = false;
return;

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import {
@ -29,8 +29,8 @@ export class UserSettingsService {
return this.http.post('api/users/' + this.activeUser.id, json)
.pipe(
catchError((err, caught) => { return caught; }),
map((response: ApiResponse) => { return response; })
map((response: ApiResponse) => { return response; }),
catchError((err) => { return of(<ApiResponse>err.error); })
);
}
@ -43,8 +43,8 @@ export class UserSettingsService {
return this.http.post('api/users/' + this.activeUser.id, json)
.pipe(
catchError((err, caught) => { return caught; }),
map((response: ApiResponse) => { return response; })
map((response: ApiResponse) => { return response; }),
catchError((err) => { return of(<ApiResponse>err.error); })
);
}
@ -56,11 +56,11 @@ export class UserSettingsService {
return this.http.post('api/users/' + this.activeUser.id, json)
.pipe(
catchError((err, caught) => { return caught; }),
map((response: ApiResponse) => {
this.auth.updateUser(JSON.parse(response.data[1]));
return response;
})
}),
catchError((err) => { return of(<ApiResponse>err.error); })
);
}
@ -72,11 +72,11 @@ export class UserSettingsService {
return this.http.post('api/users/' + this.activeUser.id, json)
.pipe(
catchError((err, caught) => { return caught; }),
map((response: ApiResponse) => {
this.auth.updateUser(JSON.parse(response.data[1]));
return response;
})
}),
catchError((err) => { return of(<ApiResponse>err.error); })
);
}
@ -85,11 +85,12 @@ export class UserSettingsService {
return this.http.post('api/users/' + this.activeUser.id + '/opts', json)
.pipe(
catchError((err, caught) => { return caught; }),
map((response: ApiResponse) => {
this.auth.updateUser(JSON.parse(response.data[1]));
this.auth.updateUser(JSON.parse(response.data[2]),
JSON.parse(response.data[1]));
return response;
})
}),
catchError((err) => { return of(<ApiResponse>err.error); })
);
}
}

View File

@ -36,11 +36,11 @@ export class AuthService {
authenticate(): Observable<boolean> {
return this.http.post('api/authenticate', null)
.pipe(
catchError((err, caught) => { return of(false); }),
map((response: ApiResponse) => {
this.updateUser(response.data[1], response.data[2]);
return true;
})
}),
catchError((err, caught) => { return of(false); })
);
}
@ -54,13 +54,13 @@ export class AuthService {
return this.http.post('api/login', json)
.pipe(
catchError((err, caught) => {
this.updateUser(null, null);
return caught;
}),
map((response: ApiResponse) => {
this.updateUser(response.data[1], response.data[2]);
return response;
}),
catchError((err, caught) => {
this.updateUser(null, null);
return of(<ApiResponse>err.error);
})
);
}

View File

@ -0,0 +1,8 @@
import { Component } from '@angular/core';
@Component({
selector: 'tb-context-menu-item',
template:`<hr *ngIf="isSeparator;else inner_content">
<ng-content #inner_content></ng-content>
`
})

View File

@ -1,18 +0,0 @@
export class ContextMenuItem {
constructor(public text: string = '',
public action: Function = null,
public isSeparator: boolean = false,
public canHighlight: boolean = true,
public isCustom: boolean = false) {
if (isSeparator) {
this.text = '<hr>';
this.canHighlight = false;
}
if (!isCustom) {
this.text = '<span (click)="action($event)">' + this.text +
'</span>';
}
}
}

View File

@ -1,12 +1,11 @@
<div class="context-menu-container"
[ngClass]="{ 'menu-open': isOpen }"
(click)="$event.stopPropagation()">
[ngClass]="{ 'menu-open': isOpen }"
(click)="$event.stopPropagation()">
<div class="menu-item" *ngFor="let item of menuItems"
(click)="menuService.closeAllMenus()"
[ngClass]="{ 'no-highlight': item.isSeparator || !item.canHighlight }">
<ng-template
*compile="item.text; context: { action: item.action }"></ng-template>
</div>
(click)="menuService.closeAllMenus()"
[ngClass]="{ 'no-highlight': item.isSeparator || !item.canHighlight }">
<ng-content></ng-content>
</div>
</div>

View File

@ -25,6 +25,16 @@ export class ModalService {
this.modals.push(newModal);
}
isOpen(modalId: string): boolean {
let modal = this.modals.find(modal => modal.modalId === modalId);
if (modal) {
return modal.isOpen;
}
return false;
}
open(modalId: string): void {
let modal = this.modals.find(modal => modal.modalId === modalId);

View File

@ -1,447 +1,447 @@
.board-nav {
margin: 5px;
width: 99%;
margin: 5px;
width: 99%;
label {
display: inline-block;
label {
display: inline-block;
select {
width: auto;
}
select {
width: auto;
}
}
}
.no-boards {
@include shadow-low;
@include shadow-low;
background-color: $white;
margin: 1em auto;
padding: 1em;
padding-bottom: .1em;
width: 45%;
background-color: $white;
margin: 1em auto;
padding: 1em;
padding-bottom: .1em;
width: 45%;
}
.board {
display: flex;
height: calc(100% - 96px);
overflow-x: auto;
white-space: nowrap;
width: 100%;
.icon-minus-squared-alt,
.icon-plus-squared-alt {
cursor: pointer;
margin-right: 5px;
}
.column {
@include shadow-low();
@include clearfix();
background-color: #fff;
display: flex;
height: calc(100% - 96px);
overflow-x: auto;
white-space: nowrap;
width: 100%;
flex: 1 0 300px;
flex-direction: column;
height: calc(100% - 7px);
margin-left: 7px;
overflow: auto;
position: relative;
white-space: normal;
.icon-minus-squared-alt,
.icon-plus-squared-alt {
h3 {
background-color: $color-heading-bg;
border-bottom: 1px solid lighten($color-border, 18%);
font-size: 24px;
padding: 5px;
&.near-limit {
box-shadow: 0 2px 2px 0 rgba(255, 150, 0, .4),
0 1px 5px 0 rgba(255, 150, 0, .2),
0 3px 1px -2px rgba(255, 150, 0, .2);
.count {
color: rgb(255, 150, 0);
}
}
&.limit-reached {
box-shadow: 0 2px 2px 0 rgba(255, 20, 0, .4),
0 1px 5px 0 rgba(255, 20, 0, .2),
0 3px 1px -2px rgba(255, 20, 0, .2);
.count {
color: rgb(255, 20, 0);
}
}
span {
cursor: pointer;
margin-right: 5px;
}
.count {
cursor: default;
}
.count-editor {
float: right;
font-size: .5em;
margin-top: -4px;
}
.limit-editor {
@include shadow-float;
background-color: $white;
border-radius: 3px;
padding: 1em;
position: absolute;
right: 1.5em;
width: 70px;
z-index: 100;
}
.sort-by {
cursor: default;
float: right;
font-size: .6em;
}
select {
height: 1.7em;
padding: 0;
width: auto;
}
.icon-cancel,
.icon-floppy {
margin: 5px;
margin-bottom: 0;
}
.icon-cancel {
color: $color-text;
}
.icon-angle-double-up,
.badge {
display: none;
}
}
.column {
@include shadow-low();
@include clearfix();
.quick-add {
padding: 6px;
padding-right: 5px;
}
background-color: #fff;
&:last-of-type {
margin-right: 7px;
}
&.double {
.tasks {
align-content: flex-start;
align-items: flex-start;
display: flex;
flex: 1 0 300px;
flex-direction: column;
height: calc(100% - 7px);
margin-left: 7px;
overflow: auto;
position: relative;
white-space: normal;
flex-wrap: wrap;
justify-content: space-between;
}
h3 {
background-color: $color-heading-bg;
border-bottom: 1px solid lighten($color-border, 18%);
font-size: 24px;
padding: 5px;
&.near-limit {
box-shadow: 0 2px 2px 0 rgba(255, 150, 0, .4),
0 1px 5px 0 rgba(255, 150, 0, .2),
0 3px 1px -2px rgba(255, 150, 0, .2);
.count {
color: rgb(255, 150, 0);
}
}
&.limit-reached {
box-shadow: 0 2px 2px 0 rgba(255, 20, 0, .4),
0 1px 5px 0 rgba(255, 20, 0, .2),
0 3px 1px -2px rgba(255, 20, 0, .2);
.count {
color: rgb(255, 20, 0);
}
}
span {
cursor: pointer;
}
.count {
cursor: default;
}
.count-editor {
float: right;
font-size: .5em;
margin-top: -4px;
}
.limit-editor {
@include shadow-float;
background-color: $white;
border-radius: 3px;
padding: 1em;
position: absolute;
right: 1.5em;
width: 70px;
z-index: 100;
}
.sort-by {
cursor: default;
float: right;
font-size: .6em;
}
select {
height: 1.7em;
padding: 0;
width: auto;
}
.icon-cancel,
.icon-floppy {
margin: 5px;
margin-bottom: 0;
}
.icon-cancel {
color: $color-text;
}
.icon-angle-double-up,
.badge {
display: none;
}
}
.quick-add {
padding: 6px;
padding-right: 5px;
}
&:last-of-type {
margin-right: 7px;
}
&.double {
.tasks {
align-content: flex-start;
align-items: flex-start;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.task-container {
width: calc(50% - 3.5px);
}
}
&.collapsed {
background-color: $color-heading-bg;
display: block;
flex: 0 0 35px;
h3 {
border: 0;
overflow: unset;
padding: 0;
transform: rotate(90deg)
translateX(-25px)
translateY(2px);
transform-origin: left bottom;
white-space: nowrap;
}
.badge {
cursor: default;
display: inline-block;
font-size: .6em;
margin-left: .9em;
transform: rotate(-90deg) translateX(4px);
}
.icon-angle-double-down,
.icon-minus-squared-alt,
.icon-plus-squared-alt,
.tasks,
.sort-by,
.quick-add {
display: none;
}
.icon-angle-double-up {
display: inline-block;
}
}
.task-container {
width: calc(50% - 3.5px);
}
}
.tasks {
flex: 1 0;
flex-direction: column;
overflow-y: auto;
padding: 7px;
padding-top: 0;
&.collapsed {
background-color: $color-heading-bg;
display: block;
flex: 0 0 35px;
div:last-of-type {
margin-bottom: 0;
}
}
.task-container {
display: block;
margin-bottom: 7px;
}
code {
background-color: #1d1f21;
h3 {
border: 0;
color: #c5c8c6;
overflow: auto;
overflow: unset;
padding: 0;
transform: rotate(90deg)
translateX(-25px)
translateY(2px);
transform-origin: left bottom;
white-space: nowrap;
}
.badge {
cursor: default;
display: inline-block;
font-size: .6em;
margin-left: .9em;
transform: rotate(-90deg) translateX(4px);
}
.icon-angle-double-down,
.icon-minus-squared-alt,
.icon-plus-squared-alt,
.tasks,
.sort-by,
.quick-add {
display: none;
}
.icon-angle-double-up {
display: inline-block;
}
}
}
.tasks {
flex: 1 0;
flex-direction: column;
overflow-y: auto;
padding: 7px;
padding-top: 0;
div:last-of-type {
margin-bottom: 0;
}
}
.task-container {
display: block;
margin-bottom: 7px;
}
code {
background-color: #1d1f21;
border: 0;
color: #c5c8c6;
overflow: auto;
}
a:link,
a:visited {
background-image: none;
color: inherit;
font-weight: bold;
text-decoration: underline;
text-shadow: none;
}
a:hover,
a:active {
text-decoration: none;
}
.overdue {
text-shadow: 0 0 2px rgba(255, 0, 0, 1);
}
.near-due {
text-shadow: 0 0 2px rgba(255, 180, 0, 1);
}
.task {
@include shadow-low();
h4 {
border-bottom: 1px solid lighten($color-border, 25%);
cursor: move;
font-size: 18px;
line-height: 1.5em;
padding: 0 5px 2px;
.badge {
font-size: .6em;
margin-right: -2px;
margin-top: 2px;
}
}
a:link,
a:visited {
background-image: none;
color: inherit;
.description {
max-height: 12em;
overflow: auto;
padding: 5px;
h3,
h4 {
background: transparent;
border: 0;
cursor: default;
}
p {
margin: 0 0 10px;
}
p:last-of-type {
margin: 0;
}
}
.checklist {
list-style: none;
margin-left: -27px;
}
.stats {
font-size: .9em;
overflow: hidden;
padding: 0 5px;
}
.category {
font-weight: 700;
}
&.compact {
.description {
display: none;
}
}
&.filtered {
opacity: .4;
&.hide {
display: none;
}
}
}
.view-modal {
.badge {
float: right;
margin-right: 3em;
margin-top: -4.2em;
}
.icon {
cursor: pointer;
}
[type="file"] {
width: 80%;
}
.details {
@include shadow-low;
.date {
padding-left: 1em;
padding-top: 1em;
}
.stats {
display: flex;
justify-content: space-around;
div {
display: flex;
flex-direction: column;
}
}
}
custom-dynamic-component > :first-child {
margin-top: 0;
}
custom-dynamic-component > :last-child {
margin-bottom: 0;
}
.description {
max-height: 20em;
overflow: auto;
padding: 1em;
.checklist {
list-style: none;
margin-left: -27px;
}
}
.quick-actions {
display: flex;
justify-content: space-around;
margin-top: 12px;
button {
width: 45%;
}
}
.comment {
@include shadow-low;
padding: 1em;
p:first-of-type {
margin-top: 0;
}
p:last-of-type {
margin-bottom: 0;
}
.byline {
font-size: .8em;
font-weight: bold;
text-decoration: underline;
text-shadow: none;
}
.actions {
float: right;
margin-top: -1.5em;
}
.icon {
cursor: pointer;
}
}
a:hover,
a:active {
text-decoration: none;
h3 {
background: transparent;
border: 0;
margin: 1em 0 0;
}
.overdue {
text-shadow: 0 0 2px rgba(255, 0, 0, 1);
}
.near-due {
text-shadow: 0 0 2px rgba(255, 180, 0, 1);
}
.task {
@include shadow-low();
h4 {
border-bottom: 1px solid lighten($color-border, 25%);
cursor: move;
font-size: 18px;
line-height: 1.5em;
padding: 0 5px 2px;
.badge {
font-size: .6em;
margin-right: -2px;
margin-top: 2px;
}
}
.description {
max-height: 12em;
overflow: auto;
padding: 5px;
h3,
h4 {
background: transparent;
border: 0;
cursor: default;
}
p {
margin: 0 0 10px;
}
p:last-of-type {
margin: 0;
}
}
.checklist {
list-style: none;
margin-left: -27px;
}
.stats {
font-size: .9em;
overflow: hidden;
padding: 0 5px;
}
.category {
font-weight: 700;
}
&.compact {
.description {
display: none;
}
}
&.filtered {
opacity: .4;
&.hide {
display: none;
}
}
}
.view-modal {
.badge {
float: right;
margin-right: 3em;
margin-top: -4.2em;
}
.icon {
cursor: pointer;
}
[type="file"] {
width: 80%;
}
.details {
@include shadow-low;
.date {
padding-left: 1em;
padding-top: 1em;
}
.stats {
display: flex;
justify-content: space-around;
div {
display: flex;
flex-direction: column;
}
}
}
custom-dynamic-component > :first-child {
margin-top: 0;
}
custom-dynamic-component > :last-child {
margin-bottom: 0;
}
.description {
max-height: 20em;
overflow: auto;
padding: 1em;
.checklist {
list-style: none;
margin-left: -27px;
}
}
.quick-actions {
display: flex;
justify-content: space-around;
margin-top: 12px;
button {
width: 45%;
}
}
.comment {
@include shadow-low;
padding: 1em;
p:first-of-type {
margin-top: 0;
}
p:last-of-type {
margin-bottom: 0;
}
.byline {
font-size: .8em;
font-weight: bold;
}
.actions {
float: right;
margin-top: -1.5em;
}
.icon {
cursor: pointer;
}
}
h3 {
background: transparent;
border: 0;
margin: 1em 0 0;
}
.activity {
background-color: #fff;
box-shadow: 0 12px 15px 0 rgba(0, 0, 0, .22),
0 17px 20px 0 rgba(0, 0, 0, .12);
height: 85vh;
left: 0;
overflow: hidden;
position: fixed;
top: 6em;
width: 300px;
&.collapsed {
background-color: transparent;
box-shadow: none;
.title {
transform: rotate(90deg) translateX(-48px) translateY(-1px);
transform-origin: left bottom;
}
}
.log-items {
height: calc(100% - 48px);
overflow: auto;
}
.log-item {
border-bottom: 1px solid #1b4e5c;
padding: .5em 1em;
}
.log-item:last-of-type {
border: 0;
}
span {
display: block;
font-size: .8em;
font-weight: bold;
}
}
.activity {
background-color: #fff;
box-shadow: 0 12px 15px 0 rgba(0, 0, 0, .22),
0 17px 20px 0 rgba(0, 0, 0, .12);
height: 85vh;
left: 0;
overflow: hidden;
position: fixed;
top: 6em;
width: 300px;
&.collapsed {
background-color: transparent;
box-shadow: none;
.title {
transform: rotate(90deg) translateX(-48px) translateY(-1px);
transform-origin: left bottom;
}
}
.log-items {
height: calc(100% - 48px);
overflow: auto;
}
.log-item {
border-bottom: 1px solid #1b4e5c;
padding: .5em 1em;
}
.log-item:last-of-type {
border: 0;
}
span {
display: block;
font-size: .8em;
font-weight: bold;
}
}
}
}

View File

@ -1,53 +1,54 @@
$ct-text-color: $color-text;
.white-labels {
.ct-label {
color: $white;
fill: $white;
}
.ct-label {
color: $white;
fill: $white;
}
}
.chartist-tooltip {
background: $color-text;
border-radius: 3px;
color: $white;
opacity: 0;
padding: .5em;
pointer-events: none;
background: $color-text;
border-radius: 3px;
color: $white;
opacity: 0;
padding: .5em;
pointer-events: none;
position: absolute;
transition: opacity .2s;
&::before {
border: 10px solid transparent;
border-top-color: $color-text;
content: '';
height: 0;
left: 50%;
margin-left: -10px;
position: absolute;
transition: opacity .2s;
top: 100%;
width: 0;
}
&::before {
border: 10px solid transparent;
border-top-color: $color-text;
content: '';
height: 0;
left: 50%;
margin-left: -10px;
position: absolute;
top: 100%;
width: 0;
}
&.tooltip-show {
opacity: 1;
}
&.tooltip-show {
opacity: 1;
}
}
$ct-series-colors: (
#2291c9,
#135170,
#2088bc,
#104763,
#1d7faf,
#0d3e56,
#1b76a3,
#0b3449,
#196c96,
#092b3d,
#176389,
#072230,
#155a7c,
#051923,
#031016
#2291c9,
#135170,
#2088bc,
#104763,
#1d7faf,
#0d3e56,
#1b76a3,
#0b3449,
#196c96,
#092b3d,
#176389,
#072230,
#155a7c,
#051923,
#031016
);

View File

@ -1,43 +1,43 @@
.collapsed {
.context-menu-container {
&.menu-open {
display: none;
}
.context-menu-container {
&.menu-open {
display: none;
}
}
}
.context-menu-container {
@include shadow-high;
@include shadow-high;
background-color: $white;
border-radius: 3px;
color: $color-text;
display: none;
padding: 5px 0;
position: fixed;
width: 200px;
z-index: 100;
background-color: $white;
border-radius: 3px;
color: $color-text;
display: none;
padding: 5px 0;
position: fixed;
width: 200px;
z-index: 100;
&.menu-open {
display: block;
&.menu-open {
display: block;
}
.menu-item {
cursor: pointer;
padding: 4px 10px;
user-select: none;
&:hover {
background-color: lighten($color-background, 5%);
}
.menu-item {
cursor: pointer;
padding: 4px 10px;
user-select: none;
&.no-highlight {
cursor: default;
&:hover {
background-color: lighten($color-background, 5%);
}
&.no-highlight {
cursor: default;
&:hover {
background-color: $white;
}
}
&:hover {
background-color: $white;
}
}
}
}

View File

@ -1,15 +1,15 @@
html {
background-image: url('../images/bg.png');
background-image: url('../images/bg.png');
}
html,
body {
height: 100%;
margin: 0;
height: 100%;
margin: 0;
}
::selection {
color: $color-background;
color: $color-background;
}
h1,
@ -18,42 +18,42 @@ h3,
h4,
h5,
h6 {
font-family: Raleway;
font-weight: 500;
margin: 0;
font-family: Raleway;
font-weight: 500;
margin: 0;
}
form {
box-shadow: none;
padding: 0;
box-shadow: none;
padding: 0;
}
section {
@include clearfix();
@include shadow-low();
@include clearfix();
@include shadow-low();
background-color: $white;
margin-bottom: 1em;
position: relative;
background-color: $white;
margin-bottom: 1em;
position: relative;
h2 {
background-color: $color-heading-bg;
padding: 7px;
}
h2 {
background-color: $color-heading-bg;
padding: 7px;
}
h3 {
padding: 7px;
padding-left: 0;
}
h3 {
padding: 7px;
padding-left: 0;
}
}
a {
background-position: left 1.05em;
background-position: left 1.05em;
}
table {
border-collapse: collapse;
margin-bottom: 1em;
border-collapse: collapse;
margin-bottom: 1em;
}
// Override scss-base flat styles
@ -63,8 +63,9 @@ table {
[type="submit"].flat,
button.flat,
.btn.flat {
background-color: $white;
color: $color-primary;
background-color: $white;
color: $color-primary;
margin-right: 7px;
}
// scss-lint:enable QualifyingElement
@ -74,136 +75,138 @@ input,
optgroup,
select,
textarea {
font-family: "Pontano Sans", sans-serif;
font-family: "Pontano Sans", sans-serif;
}
button {
transition: background .3s;
margin-right: 7px;
transition: background .3s;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
100% {
transform: rotate(360deg);
}
}
.spinner {
animation: spin 1s infinite;
border: 3px solid $color-heading-bg;
border-radius: 50%;
border-right-color: $color-text;
display: inline-block;
height: 20px;
margin-bottom: -.3em;
width: 20px;
animation: spin 1s infinite;
border: 3px solid $color-heading-bg;
border-radius: 50%;
border-right-color: $color-text;
display: inline-block;
height: 20px;
margin-bottom: -.3em;
width: 20px;
}
.icon-help-circled {
position: relative;
position: relative;
&:hover::after {
@include shadow-high;
&:hover::after {
@include shadow-high;
background-color: $white;
border: 1px solid $color-border;
content: attr(data-help);
left: 20px;
min-width: 250px;
padding: 7px;
position: absolute;
text-align: left;
top: -1em;
z-index: 100;
}
background-color: $white;
border: 1px solid $color-border;
content: attr(data-help);
left: 20px;
min-width: 250px;
padding: 7px;
position: absolute;
text-align: left;
top: -1em;
z-index: 100;
}
}
.color-primary {
color: $color-primary;
color: $color-primary;
}
.color-secondary {
color: $color-secondary;
color: $color-secondary;
}
.loading {
padding-top: 2em;
text-align: center;
padding-top: 2em;
text-align: center;
}
.small {
font-size: .8em;
font-size: .8em;
}
.clearfix {
@include clearfix;
@include clearfix;
}
.row {
@include row();
@include row();
padding: 7px;
padding: 7px;
}
.right {
float: right;
float: right;
}
.center {
text-align: center;
text-align: center;
}
.inline {
margin-right: 1em;
margin-right: 1em;
input {
width: auto;
}
input {
width: auto;
}
}
.badge {
background-color: $color-tertiary;
border: 1px solid $color-border;
border-radius: 33%;
box-shadow: inset 1px 1px rgba(0, 0, 0, .1);
font-family: 'Fira Mono';
font-size: .75em;
font-weight: 700;
line-height: 1.7em;
margin: 2px;
padding: 3px 5px 1px;
background-color: $color-tertiary;
border: 1px solid $color-border;
border-radius: 33%;
box-shadow: inset 1px 1px rgba(0, 0, 0, .1);
font-family: 'Fira Mono';
font-size: .75em;
font-weight: 700;
line-height: 1.7em;
margin: 2px;
padding: 3px 5px 1px;
}
.quick-add {
padding: 7px;
padding: 7px;
input {
width: calc(100% - 40px);
}
input {
width: calc(100% - 40px);
}
button {
margin-left: 2px;
padding: 8px;
}
button {
margin-left: 5px;
margin-right: 0;
padding: 8px;
}
}
.toggle {
vertical-align: sub;
vertical-align: sub;
&::before {
height: 16px;
width: 30px;
}
&::before {
height: 16px;
width: 30px;
}
&::after {
top: -2px;
}
&::after {
top: -2px;
}
}
.hidden:checked + .toggle::after {
transform: translate(15px);
transform: translate(15px);
}

View File

@ -1,78 +1,78 @@
.dashboard {
@include clearfix();
@include clearfix();
margin: 7px 1em;
margin: 7px 1em;
.details {
margin-right: 1em;
.details {
margin-right: 1em;
}
.half-page {
@include span-columns(9 of 18);
}
.calendar {
td {
width: 100px;
}
.half-page {
@include span-columns(9 of 18);
th {
text-align: center;
.icon {
cursor: pointer;
}
}
.calendar {
td {
width: 100px;
}
th {
text-align: center;
.icon {
cursor: pointer;
}
}
.days {
background-color: $color-table-row;
border-left: 1px solid lighten($color-border, 15%);
border-right: 1px solid lighten($color-border, 15%);
}
.today {
background-color: $color-table-row;
}
.day {
border-left: 1px solid lighten($color-border, 15%);
height: 9em;
padding-top: 2em;
position: relative;
&:last-of-type {
border-right: 1px solid lighten($color-border, 15%);
}
}
.date {
left: 5px;
position: absolute;
top: 2px;
}
.tasks-wrapper {
display: inline-block;
height: 6em;
overflow: auto;
width: 100%;
}
.task {
border: 1px solid lighten($color-border, 20%);
cursor: pointer;
display: inline-block;
margin-bottom: .3em;
padding-left: 5px;
padding-right: 1.2em;
width: 100%;
.points {
float: right;
font-weight: 700;
margin-right: -1em;
}
}
.days {
background-color: $color-table-row;
border-left: 1px solid lighten($color-border, 15%);
border-right: 1px solid lighten($color-border, 15%);
}
.today {
background-color: $color-table-row;
}
.day {
border-left: 1px solid lighten($color-border, 15%);
height: 9em;
padding-top: 2em;
position: relative;
&:last-of-type {
border-right: 1px solid lighten($color-border, 15%);
}
}
.date {
left: 5px;
position: absolute;
top: 2px;
}
.tasks-wrapper {
display: inline-block;
height: 6em;
overflow: auto;
width: 100%;
}
.task {
border: 1px solid lighten($color-border, 20%);
cursor: pointer;
display: inline-block;
margin-bottom: .3em;
padding-left: 5px;
padding-right: 1.2em;
width: 100%;
.points {
float: right;
font-weight: 700;
margin-right: -1em;
}
}
}
}

View File

@ -1,25 +1,25 @@
@font-face {
font-family: 'fontello';
src: url('../fonts/fontello.eot');
src: url('../fonts/fontello.woff') format('woff'),
url('../fonts/fontello.ttf') format('truetype'),
url('../fonts/fontello.svg') format('svg');
font-weight: normal;
font-style: normal;
font-family: 'fontello';
src: url('../fonts/fontello.eot');
src: url('../fonts/fontello.woff') format('woff'),
url('../fonts/fontello.ttf') format('truetype'),
url('../fonts/fontello.svg') format('svg');
font-weight: normal;
font-style: normal;
}
.icon {
display: inline-block;
font-family: 'fontello';
font-style: normal;
font-variant: normal;
font-weight: normal;
line-height: 1em;
speak: none;
text-align: center;
text-decoration: inherit;
text-transform: none;
width: 1em;
display: inline-block;
font-family: 'fontello';
font-style: normal;
font-variant: normal;
font-weight: normal;
line-height: 1em;
speak: none;
text-align: center;
text-decoration: inherit;
text-transform: none;
width: 1em;
}
.icon-trash-empty::before { content: '\e800'; }
@ -45,16 +45,16 @@
.icon-hashtag::before { content: '\f292'; }
.icon-check::before {
content: '\e812';
margin-left: -6px;
content: '\e812';
margin-left: -6px;
}
.icon-check-empty::before {
content: '\f096';
margin-left: -8px;
content: '\f096';
margin-left: -8px;
}
.icon-help-circled {
cursor: help;
cursor: help;
}

View File

@ -1,66 +1,66 @@
.login {
@include shadow-low();
background-color: $white;
display: inline-block;
margin: 3em calc(50% - 175px);
padding: 1em;
position: relative;
text-align: center;
width: 350px;
h1 {
margin: 1em;
margin-top: .5em;
}
[type='text'] {
border-bottom: 0;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
text-align: center;
}
[type='password'] {
border-top-left-radius: 0;
border-top-right-radius: 0;
text-align: center;
}
label {
display: inline-block;
margin-top: .5em;
}
button {
margin-top: .5em;
width: 100%;
}
p {
font-size: .8em;
margin-bottom: -1em;
}
&::before,
&::after {
@include shadow-low();
background-color: $white;
display: inline-block;
margin: 3em calc(50% - 175px);
padding: 1em;
position: relative;
text-align: center;
width: 350px;
background: $color-table-row;
content: '';
height: 100%;
left: 0;
position: absolute;
top: 0;
transform: rotate(2deg);
width: 100%;
z-index: -1;
}
h1 {
margin: 1em;
margin-top: .5em;
}
[type='text'] {
border-bottom: 0;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
text-align: center;
}
[type='password'] {
border-top-left-radius: 0;
border-top-right-radius: 0;
text-align: center;
}
label {
display: inline-block;
margin-top: .5em;
}
button {
margin-top: .5em;
width: 100%;
}
p {
font-size: .8em;
margin-bottom: -1em;
}
&::before,
&::after {
@include shadow-low();
background: $color-table-row;
content: '';
height: 100%;
left: 0;
position: absolute;
top: 0;
transform: rotate(2deg);
width: 100%;
z-index: -1;
}
&::after {
background-color: darken($color-table-row, 5%);
transform: rotate(-3deg);
z-index: -2;
}
&::after {
background-color: darken($color-table-row, 5%);
transform: rotate(-3deg);
z-index: -2;
}
}

View File

@ -1,102 +1,102 @@
.no-scroll {
overflow: hidden;
overflow: hidden;
}
.modal-container {
background-color: rgba(0, 0, 0, .4);
bottom: 0;
display: flex;
left: 0;
overflow-y: auto;
padding: 10vh 0;
position: fixed;
right: 0;
top: 0;
visibility: hidden;
z-index: 1000;
background-color: rgba(0, 0, 0, .4);
bottom: 0;
display: flex;
left: 0;
overflow-y: auto;
padding: 10vh 0;
position: fixed;
right: 0;
top: 0;
visibility: hidden;
z-index: 1000;
&.animated {
transition: all .3s;
}
&.animated {
transition: all .3s;
}
&.modal-closed {
opacity: 0;
}
&.modal-closed {
opacity: 0;
}
&.modal-open {
opacity: 1;
visibility: visible;
}
&.modal-open {
opacity: 1;
visibility: visible;
}
}
.modal {
@include shadow-float();
@include shadow-float();
background-color: $white;
margin: auto;
max-width: 100%;
width: 450px;
background-color: $white;
margin: auto;
max-width: 100%;
width: 450px;
&.animated {
transition: all .3s;
&.animated {
transition: all .3s;
}
&.wide {
width: 650px;
}
&.modal-closed {
opacity: 0;
transform: translateY(-100%);
}
.title {
background-color: $color-heading-bg;
.right {
color: $color-text;
cursor: pointer;
}
&.wide {
width: 650px;
.right:hover {
color: lighten($color-text, 10%);
}
}
&.modal-closed {
opacity: 0;
transform: translateY(-100%);
.title,
.body {
padding: .75em;
}
input,
select {
margin-bottom: 7px;
}
[multiple] {
height: 4em;
}
.half {
display: flex;
justify-content: space-between;
label {
display: block;
width: 49%;
}
}
.title {
background-color: $color-heading-bg;
.buttons {
float: right;
margin: 1em;
margin-right: 0;
.right {
color: $color-text;
cursor: pointer;
}
.right:hover {
color: lighten($color-text, 10%);
}
}
.title,
.body {
padding: .75em;
}
input,
select {
margin-bottom: 7px;
}
[multiple] {
height: 4em;
}
.half {
display: flex;
justify-content: space-between;
label {
display: block;
width: 49%;
}
}
.buttons {
float: right;
margin: 1em;
margin-right: 0;
.flat {
background-color: $white;
color: $color-text;
line-height: 1.1em;
}
.flat {
background-color: $white;
color: $color-text;
line-height: 1.1em;
}
}
}

View File

@ -1,28 +1,28 @@
.nav-top {
border-bottom: 1px solid lighten($color-border, 10%);
margin-bottom: 5px;
padding: .5em;
border-bottom: 1px solid lighten($color-border, 10%);
margin-bottom: 5px;
padding: .5em;
h1 {
margin-top: 3px;
h1 {
margin-top: 3px;
span {
padding: 0;
}
span {
padding: 0;
}
}
.dark {
color: $color-text;
}
.dark {
color: $color-text;
}
.small {
font-size: .655555em;
font-weight: 500;
}
.small {
font-size: .655555em;
font-weight: 500;
}
.buttons {
float: right;
margin-top: -2.2em;
}
.buttons {
float: right;
margin-top: -2.2em;
}
}

View File

@ -4,52 +4,52 @@ $error: #ffbaba;
$info: #bde5f8;
.notifications {
bottom: 1%;
left: calc(50% - 200px);
position: absolute;
z-index: 10000000;
bottom: 1%;
left: calc(50% - 200px);
position: absolute;
z-index: 10000000;
div {
@include shadow-float;
div {
@include shadow-float;
border: 1px solid;
border-radius: 2px;
cursor: pointer;
margin-bottom: .5em;
min-height: 3em;
opacity: 1;
padding: 1em;
text-align: center;
transition: opacity .5s linear;
width: 400px;
border: 1px solid;
border-radius: 2px;
cursor: pointer;
margin-bottom: .5em;
min-height: 3em;
opacity: 1;
padding: 1em;
text-align: center;
transition: opacity .5s linear;
width: 400px;
&.clicked {
opacity: 0;
}
&.success {
background-color: $success;
border-color: darken($success, 30%);
color: darken($success, 60%);
}
&.error {
background-color: $error;
border-color: darken($error, 10%);
color: darken($error, 60%);
}
&.warn {
background-color: $warn;
border-color: darken($warn, 30%);
color: darken($warn, 57%);
}
&.info {
background-color: $info;
border-color: darken($info, 30%);
color: darken($info, 50%);
}
&.clicked {
opacity: 0;
}
&.success {
background-color: $success;
border-color: darken($success, 30%);
color: darken($success, 60%);
}
&.error {
background-color: $error;
border-color: darken($error, 10%);
color: darken($error, 60%);
}
&.warn {
background-color: $warn;
border-color: darken($warn, 30%);
color: darken($warn, 57%);
}
&.info {
background-color: $info;
border-color: darken($info, 30%);
color: darken($info, 50%);
}
}
}

View File

@ -1,293 +1,293 @@
.settings {
@include clearfix();
@include clearfix();
margin: 7px 1em;
margin: 7px 1em;
.quick-add {
padding: 0;
.quick-add {
padding: 0;
}
.half-page {
@include span-columns(9 of 18);
}
.half-modal {
float: left;
margin-right: 2%;
width: 49%;
&:nth-of-type(2) {
margin: 0;
}
.half-page {
@include span-columns(9 of 18);
label {
display: inline-block;
margin-top: 1em;
}
}
.no-bottom-margin {
margin-bottom: 0;
}
.borderless {
border: 0;
}
.padded {
margin-right: 3px;
}
.alternating {
td:last-child {
a {
background-image: none;
}
}
}
.categories {
[type="text"] {
width: calc(100% - 81px);
}
.half-modal {
float: left;
margin-right: 2%;
width: 49%;
[type="color"] {
cursor: pointer;
height: 35px;
margin-left: 2px;
vertical-align: bottom;
width: 35px;
}
}
&:nth-of-type(2) {
margin: 0;
}
label {
display: inline-block;
margin-top: 1em;
}
.issue-trackers {
label {
display: inline-block;
margin-top: 1em;
}
.no-bottom-margin {
margin-bottom: 0;
[type="text"] {
&:first-of-type {
width: 430px;
}
&:last-of-type {
margin-left: 2px;
width: 148px;
}
}
.borderless {
border: 0;
}
.padded {
margin-right: 3px;
}
.alternating {
td:last-child {
a {
background-image: none;
}
}
}
.categories {
[type="text"] {
width: calc(100% - 81px);
}
[type="color"] {
cursor: pointer;
height: 35px;
margin-left: 2px;
vertical-align: bottom;
width: 35px;
}
}
.issue-trackers {
label {
display: inline-block;
margin-top: 1em;
}
[type="text"] {
&:first-of-type {
width: 430px;
}
&:last-of-type {
margin-left: 2px;
width: 148px;
}
}
button {
height: 36px;
margin-left: 2px;
padding: 9px;
vertical-align: top;
}
}
.double-edit .first {
.inline-edit {
margin-right: 1%;
width: 70%;
}
[type="text"] {
width: 95%;
}
}
.double-edit .last {
.inline-edit {
width: 25%;
}
[type="text"] {
width: 86%;
}
}
.users {
.user-select {
display: inline-block;
margin-bottom: .3em;
margin-top: 0;
width: 33%;
}
label {
display: inline-block;
margin-top: 1em;
}
}
.gu-mirror {
@include shadow-high;
background: $white;
display: block;
// To override default opacity settings
filter: alpha(opacity = 100);
opacity: 1;
.actions {
float: right;
}
}
.modal-list {
border: 1px solid $color-border;
border-radius: 3px;
list-style: none;
margin: 0;
margin-bottom: 1em;
padding: 0;
&.category-list {
[type="color"] {
border: 0;
cursor: pointer;
height: 24px;
margin: 0;
padding: 0;
width: 20px;
}
.inline-edit {
vertical-align: top;
width: 85%;
}
}
li {
border-bottom: 1px solid $color-border;
padding: 0 7px;
&:last-of-type {
border: 0;
}
}
.actions {
float: right;
}
.badge {
margin-left: 5px;
margin-right: 13px;
}
.icon-trash-empty,
.icon-edit {
cursor: pointer;
}
button {
height: 36px;
margin-left: 2px;
padding: 9px;
vertical-align: top;
}
}
.double-edit .first {
.inline-edit {
margin-right: 1%;
width: 70%;
}
[type="text"] {
width: 95%;
}
}
.double-edit .last {
.inline-edit {
width: 25%;
}
[type="text"] {
width: 86%;
}
}
.users {
.user-select {
display: inline-block;
margin-bottom: .3em;
margin-top: 0;
width: 33%;
}
label {
display: inline-block;
margin-top: 1em;
}
}
.gu-mirror {
@include shadow-high;
background: $white;
display: block;
// To override default opacity settings
filter: alpha(opacity = 100);
opacity: 1;
.actions {
float: right;
}
}
.modal-list {
border: 1px solid $color-border;
border-radius: 3px;
list-style: none;
margin: 0;
margin-bottom: 1em;
padding: 0;
&.category-list {
[type="color"] {
border: 0;
cursor: pointer;
height: 24px;
margin: 0;
padding: 0;
width: 20px;
}
.inline-edit {
vertical-align: top;
width: 85%;
}
}
li {
border-bottom: 1px solid $color-border;
padding: 0 7px;
&:last-of-type {
border: 0;
}
}
.actions {
float: right;
}
.badge {
margin-left: 5px;
margin-right: 13px;
}
.icon-trash-empty,
.icon-edit {
cursor: pointer;
}
}
.inline-edit {
display: inline-block;
width: 82%;
.icon {
cursor: pointer;
float: right;
margin-top: 4px;
}
[type="text"] {
border: 0;
border-bottom: 1px solid $color-border;
border-radius: 0;
height: 1em;
margin: 0;
padding-left: 0;
width: 90%;
}
}
.icon-resize-vertical {
border-right: 1px solid $color-border;
cursor: move;
margin-left: -5px;
margin-right: .5em;
width: 1.5em;
}
section {
ul {
border: 1px solid lighten($color-border, 10%);
border-radius: 4px;
list-style: none;
margin: 0;
padding: 0;
li {
@include clearfix();
border-bottom: 1px solid lighten($color-border, 10%);
line-height: 2em;
padding-left: 5px;
}
li:last-of-type {
border-bottom: 0;
}
.badge {
float: right;
margin-top: 3px;
}
}
label {
display: block;
line-height: 2em;
&.inline {
display: inline-block;
width: 82%;
margin-right: 1em;
}
.icon {
cursor: pointer;
float: right;
margin-top: 4px;
}
[type="text"] {
border: 0;
border-bottom: 1px solid $color-border;
border-radius: 0;
height: 1em;
margin: 0;
padding-left: 0;
width: 90%;
}
&.hidden {
display: none;
}
}
.icon-resize-vertical {
border-right: 1px solid $color-border;
cursor: move;
margin-left: -5px;
margin-right: .5em;
width: 1.5em;
tbody label {
display: inline;
}
section {
ul {
border: 1px solid lighten($color-border, 10%);
border-radius: 4px;
list-style: none;
margin: 0;
padding: 0;
li {
@include clearfix();
border-bottom: 1px solid lighten($color-border, 10%);
line-height: 2em;
padding-left: 5px;
}
li:last-of-type {
border-bottom: 0;
}
.badge {
float: right;
margin-top: 3px;
}
}
label {
display: block;
line-height: 2em;
&.inline {
display: inline-block;
margin-right: 1em;
}
&.hidden {
display: none;
}
}
tbody label {
display: inline;
}
.filters {
margin-top: -2.5em;
max-width: 80%;
}
.hold-bottom {
bottom: 7px;
position: absolute;
}
.autosize {
margin-bottom: .5em;
width: auto;
}
.tall {
margin-top: 2.5em;
}
.half {
@include span-columns(4.5 of 9);
padding: 7px;
input,
button {
margin-top: 7px;
}
}
.flat {
background-color: lighten($color-background, 10%);
color: $color-primary;
}
.filters {
margin-top: -2.5em;
max-width: 80%;
}
.hold-bottom {
bottom: 7px;
position: absolute;
}
.autosize {
margin-bottom: .5em;
width: auto;
}
.tall {
margin-top: 2.5em;
}
.half {
@include span-columns(4.5 of 9);
padding: 7px;
input,
button {
margin-top: 7px;
}
}
.flat {
background-color: lighten($color-background, 10%);
color: $color-primary;
}
}
}

View File

@ -50,7 +50,8 @@ describe('ApiInterceptor', () => {
});
const req = httpMock.expectOne(req =>
req.headers.has('Content-Type') && req.headers.get('Content-Type') === 'application/json'
req.headers.has('Content-Type') &&
req.headers.get('Content-Type') === 'application/json'
);
expect(req.request.method).toEqual('GET');
@ -69,7 +70,8 @@ describe('ApiInterceptor', () => {
});
const req = httpMock.expectOne(req =>
req.headers.has('Authorization') && req.headers.get('Authorization') === 'fake'
req.headers.has('Authorization') &&
req.headers.get('Authorization') === 'fake'
);
expect(req.request.method).toEqual('POST');
@ -91,12 +93,13 @@ describe('ApiInterceptor', () => {
});
const req = httpMock.expectOne(req =>
req.headers.has('Content-Type') && req.headers.get('Content-Type') === 'application/json'
req.headers.has('Content-Type') &&
req.headers.get('Content-Type') === 'application/json'
);
expect(req.request.method).toEqual('GET');
const error = new HttpErrorResponse({ status: 401 });
req.flush({}, error);
req.flush(error);
expect(localStorage.getItem('Authorization')).toEqual(null);
}
)

View File

@ -85,7 +85,7 @@ describe('TaskDisplay', () => {
const actual = component.getTaskDescription();
expect(actual).toEqual('<h1 id="make-this-html">Make this HTML</h1>\n ');
expect(actual).toEqual('<h1 id="make-this-html">Make this HTML</h1>\n');
});
it('handles checklists in markdown', () => {

View File

@ -96,6 +96,7 @@ describe('BoardAdmin', () => {
let called = false;
(<any>component.modal).isOpen = () => true;
(<any>component.boardService).addBoard = () => {
return { subscribe: fn => {
const board = new Board();
@ -118,6 +119,7 @@ describe('BoardAdmin', () => {
let called = false;
(<any>component.modal).isOpen = () => true;
(<any>component.boardService).editBoard = () => {
return { subscribe: fn => {
const board = new Board();

View File

@ -66,6 +66,7 @@ describe('UserAdmin', () => {
it('calls a service to add a user', () => {
let called = false;
(<any>component.modal).isOpen = () => true;
(<any>component.userService).addUser = () => {
return { subscribe: fn => {
const user = new User();
@ -91,6 +92,7 @@ describe('UserAdmin', () => {
it('calls a service to edit a user', () => {
let called = false;
(<any>component.modal).isOpen = () => true;
(<any>component.userService).editUser = () => {
return { subscribe: fn => {
const user = new User();

View File

@ -75,9 +75,8 @@ describe('AuthService', () => {
testCall('api/login', 'POST');
});
fit('handles errors on user login', () => {
service.login('test', 'test', true).subscribe(response => {
console.log(response);
it('handles errors on user login', () => {
service.login('test', 'test', true).subscribe(() => {}, response => {
expect(response.alerts.length).toEqual(1);
});
@ -93,7 +92,7 @@ describe('AuthService', () => {
});
const testCall = (url, method, isError = false) => {
const req = httpMock.expectOne(url);
const req = httpMock.expectOne({ method, url });
expect(req.request.method).toEqual(method);
if (isError) {