mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-05-19 17:14:57 +00:00
[ADF-497] Improvement to task comment (activiti-comments) component -1829 Github (#2054)
* [ADF-497] Improvement to task comment (activiti-comments) component - 1829 Github * Added new design * Removed dialog * Migrated from mdl to md * Created a new component to list comments by using data-table * [ADF-497] Improvement to task comment (activiti-comments) component - 1829 Github * Added documentation * [ADF-497] Improvement to task comment (activiti-comments) component -1829 Github * Improved css class naming * Changed component name AdfCommentList to AdfCommentListComponent * Code refactoring * [ADF-497] Improvement to task comment (activiti-comments) component -1829 Github * Removed extra space at the end * [ADF-497] Improvement to task comment (activiti-comments) component -1829 Github * Import reordering
This commit is contained in:
parent
58eb56a966
commit
403d5cea85
@ -39,6 +39,9 @@
|
||||
- [Task People Component](#task-people-component)
|
||||
* [Properties](#properties-8)
|
||||
+ [Events](#events-7)
|
||||
- [ADF Comments Component](#adf-comments-component)
|
||||
* [Properties](#properties-9)
|
||||
+ [Events](#events-8)
|
||||
- [Build from sources](#build-from-sources)
|
||||
- [NPM scripts](#npm-scripts)
|
||||
- [Demo](#demo)
|
||||
@ -460,6 +463,32 @@ This component displays involved users to a specified task
|
||||
|
||||
No Events
|
||||
|
||||
## ADF Comments Component
|
||||
|
||||
This component displays comments entered by involved users to a specified task. It also allows an involved user to add his/her comment to the task.
|
||||
|
||||
```html
|
||||
<adf-comments
|
||||
[taskId]="YOUR_TASK_ID"
|
||||
[readOnly]="YOUR_READ_ONLY_FLAG">
|
||||
</adf-comments>
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| taskId | string | The numeric ID of the task |
|
||||
| readOnly | boolean | The boolean flag |
|
||||
|
||||
#### Events
|
||||
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| error | Raised when an error occurs while displaying/adding a comment |
|
||||
|
||||
## Build from sources
|
||||
|
||||
You can build component from sources with the following commands:
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
@ -15,8 +15,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { ModuleWithProviders, NgModule } from '@angular/core';
|
||||
import { MdButtonModule, MdIconModule } from '@angular/material';
|
||||
import { MdButtonModule, MdIconModule, MdInputModule } from '@angular/material';
|
||||
import { ActivitiFormModule } from 'ng2-activiti-form';
|
||||
import { CoreModule } from 'ng2-alfresco-core';
|
||||
import { DataTableModule } from 'ng2-alfresco-datatable';
|
||||
@ -35,6 +36,7 @@ import {
|
||||
ActivitiTaskDetails,
|
||||
ActivitiTaskHeader,
|
||||
ActivitiTaskList,
|
||||
AdfCommentListComponent,
|
||||
NoTaskDetailsTemplateComponent,
|
||||
PeopleList,
|
||||
TaskAttachmentListComponent
|
||||
@ -59,7 +61,8 @@ export const ACTIVITI_TASKLIST_DIRECTIVES: any[] = [
|
||||
ActivitiPeopleSearch,
|
||||
TaskAttachmentListComponent,
|
||||
ActivitiCreateTaskAttachmentComponent,
|
||||
PeopleList
|
||||
PeopleList,
|
||||
AdfCommentListComponent
|
||||
];
|
||||
|
||||
export const ACTIVITI_TASKLIST_PROVIDERS: any[] = [
|
||||
@ -73,13 +76,15 @@ export const ACTIVITI_TASKLIST_PROVIDERS: any[] = [
|
||||
DataTableModule,
|
||||
ActivitiFormModule,
|
||||
MdIconModule,
|
||||
MdButtonModule
|
||||
MdButtonModule,
|
||||
MdInputModule
|
||||
],
|
||||
declarations: [
|
||||
...ACTIVITI_TASKLIST_DIRECTIVES
|
||||
],
|
||||
providers: [
|
||||
...ACTIVITI_TASKLIST_PROVIDERS
|
||||
...ACTIVITI_TASKLIST_PROVIDERS,
|
||||
DatePipe
|
||||
],
|
||||
exports: [
|
||||
...ACTIVITI_TASKLIST_DIRECTIVES,
|
||||
|
@ -1,32 +1,25 @@
|
||||
:host {
|
||||
.adf-comments-container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.activiti-label {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.material-icons:hover {
|
||||
color: rgb(255, 152, 0);
|
||||
}
|
||||
|
||||
.mdl-tooltip {
|
||||
will-change: unset;
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.list-wrap {
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
-moz-hyphens:auto;
|
||||
-webkit-hyphens:auto;
|
||||
-o-hyphens:auto;
|
||||
hyphens:auto;
|
||||
}
|
||||
|
||||
.hide-long-names {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.adf-comments-header {
|
||||
padding: 10px 20px;
|
||||
font-size: 14px;
|
||||
font-family: Muli;
|
||||
font-weight: 600;
|
||||
border-bottom: 1px solid #e1e1e1;
|
||||
color: #a1a1a1;
|
||||
}
|
||||
|
||||
.adf-comments-input-container {
|
||||
padding: 0 15px;
|
||||
width: calc(100% - 30px);
|
||||
padding-top: 8px;
|
||||
border-bottom: 1px solid #e1e1e1;
|
||||
}
|
||||
|
||||
.adf-full-width {
|
||||
width: 100%;
|
||||
}
|
@ -1,32 +1,13 @@
|
||||
<span class="activiti-label mdl-badge"
|
||||
[attr.data-badge]="comments?.length">{{ 'TASK_DETAILS.LABELS.COMMENTS' |translate }}</span>
|
||||
<div *ngIf="!readOnly" id="addComment" (click)="showDialog()" class="icon material-icons">add</div>
|
||||
<div *ngIf="!readOnly" class="mdl-tooltip" for="addComment">
|
||||
{{ 'TASK_DETAILS.COMMENTS.ADD' | translate }}
|
||||
</div>
|
||||
|
||||
<div class="menu-container" *ngIf="comments?.length > 0">
|
||||
<ul class='mdl-list'>
|
||||
<li class="mdl-list__item list-wrap" *ngFor="let comment of comments">
|
||||
<span class="mdl-list__item-primary-content hide-long-names"><i class="material-icons mdl-list__item-icon">comment</i>{{comment.message}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div *ngIf="comments?.length === 0" data-automation-id="comments-none">
|
||||
{{ 'TASK_DETAILS.COMMENTS.NONE' | translate }}
|
||||
</div>
|
||||
|
||||
|
||||
<dialog class="mdl-dialog" #dialog>
|
||||
<h4 class="mdl-dialog__title">{{ 'TASK_DETAILS.COMMENTS.DIALOG.TITLE' | translate }}</h4>
|
||||
<div class="mdl-dialog__content">
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
|
||||
<textarea class="mdl-textfield__input" type="text" [(ngModel)]="message" rows="1" id="commentText"></textarea>
|
||||
<label class="mdl-textfield__label" for="commentText">{{ 'TASK_DETAILS.COMMENTS.DIALOG.LABELS.MESSAGE' | translate }}</label>
|
||||
<div class="adf-comments-container">
|
||||
<div id="comment-header" class="adf-comments-header">
|
||||
{{'TASK_DETAILS.COMMENTS.HEADER' | translate}}({{comments?.length}})
|
||||
</div>
|
||||
<div class="adf-comments-input-container" *ngIf="!isReadOnly()">
|
||||
<md-input-container class="adf-full-width">
|
||||
<input mdInput id="comment-input" placeholder="{{'TASK_DETAILS.COMMENTS.ADD' | translate}}" [(ngModel)]="message" (keyup.enter)="add()" (keyup.esc)="clear()">
|
||||
</md-input-container>
|
||||
</div>
|
||||
<div class="mdl-dialog__actions">
|
||||
<button type="button" (click)="add()" class="mdl-button">{{ 'TASK_DETAILS.COMMENTS.DIALOG.BUTTON.ADD' | translate }}</button>
|
||||
<button type="button" (click)="cancel()" class="mdl-button close">{{ 'TASK_DETAILS.COMMENTS.DIALOG.BUTTON.CANCEL' | translate }}</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<adf-comment-list [comments]="comments">
|
||||
</adf-comment-list>
|
||||
</div>
|
||||
|
@ -17,14 +17,17 @@
|
||||
|
||||
import { SimpleChange } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
|
||||
import { ActivitiFormModule } from 'ng2-activiti-form';
|
||||
import { AlfrescoTranslationService, CoreModule } from 'ng2-alfresco-core';
|
||||
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { MdInputModule } from '@angular/material';
|
||||
import { DataTableModule } from 'ng2-alfresco-datatable';
|
||||
import { ActivitiTaskListService } from './../services/activiti-tasklist.service';
|
||||
import { ActivitiComments } from './activiti-comments.component';
|
||||
import { AdfCommentListComponent } from './adf-comment-list.component';
|
||||
|
||||
describe('ActivitiComments', () => {
|
||||
|
||||
@ -39,13 +42,17 @@ describe('ActivitiComments', () => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CoreModule.forRoot(),
|
||||
ActivitiFormModule.forRoot()
|
||||
ActivitiFormModule.forRoot(),
|
||||
DataTableModule,
|
||||
MdInputModule
|
||||
],
|
||||
declarations: [
|
||||
ActivitiComments
|
||||
ActivitiComments,
|
||||
AdfCommentListComponent
|
||||
],
|
||||
providers: [
|
||||
ActivitiTaskListService
|
||||
ActivitiTaskListService,
|
||||
DatePipe
|
||||
]
|
||||
}).compileComponents();
|
||||
|
||||
@ -60,11 +67,11 @@ describe('ActivitiComments', () => {
|
||||
service = fixture.debugElement.injector.get(ActivitiTaskListService);
|
||||
|
||||
getCommentsSpy = spyOn(service, 'getTaskComments').and.returnValue(Observable.of([
|
||||
{ message: 'Test1' },
|
||||
{ message: 'Test2' },
|
||||
{ message: 'Test3'}
|
||||
{ message: 'Test1', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'} },
|
||||
{ message: 'Test2', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'} },
|
||||
{ message: 'Test3', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'} }
|
||||
]));
|
||||
addCommentSpy = spyOn(service, 'addTaskComment').and.returnValue(Observable.of({id: 123, message: 'Test'}));
|
||||
addCommentSpy = spyOn(service, 'addTaskComment').and.returnValue(Observable.of({id: 123, message: 'Test Comment'}));
|
||||
|
||||
componentHandler = jasmine.createSpyObj('componentHandler', [
|
||||
'upgradeAllRegistered',
|
||||
@ -90,7 +97,7 @@ describe('ActivitiComments', () => {
|
||||
expect(emitSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not comments when no taskId is specified', () => {
|
||||
it('should not load comments when no taskId is specified', () => {
|
||||
fixture.detectChanges();
|
||||
expect(getCommentsSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
@ -101,17 +108,45 @@ describe('ActivitiComments', () => {
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.queryAll(By.css('ul.mdl-list li')).length).toBe(3);
|
||||
expect(fixture.nativeElement.querySelectorAll('#comment-message').length).toBe(3);
|
||||
expect(fixture.nativeElement.querySelector('#comment-message:empty')).toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should display comments count when the task has comments', () => {
|
||||
let change = new SimpleChange(null, '123', true);
|
||||
component.ngOnChanges({ 'taskId': change });
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let element = fixture.nativeElement.querySelector('#comment-header');
|
||||
expect(element.innerText).toContain('(3)');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not display comments when the task has no comments', async(() => {
|
||||
component.taskId = '123';
|
||||
getCommentsSpy.and.returnValue(Observable.of([]));
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.querySelector('#comment-container')).toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should display comments input by default', async(() => {
|
||||
let change = new SimpleChange(null, '123', true);
|
||||
component.ngOnChanges({ 'taskId': change });
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.querySelector('#comment-input')).not.toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should not display comments input when the task is readonly', async(() => {
|
||||
component.readOnly = true;
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.queryAll(By.css('ul.mdl-list li')).length).toBe(0);
|
||||
expect(fixture.nativeElement.querySelector('#comment-input')).toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
@ -133,21 +168,15 @@ describe('ActivitiComments', () => {
|
||||
expect(getCommentsSpy).toHaveBeenCalledWith('456');
|
||||
});
|
||||
|
||||
it('should NOT fetch new comments when empty changeset made', () => {
|
||||
it('should not fetch new comments when empty changeset made', () => {
|
||||
component.ngOnChanges({});
|
||||
expect(getCommentsSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should NOT fetch new comments when taskId changed to null', () => {
|
||||
it('should not fetch new comments when taskId changed to null', () => {
|
||||
component.ngOnChanges({ 'taskId': nullChange });
|
||||
expect(getCommentsSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should set a placeholder message when taskId changed to null', () => {
|
||||
component.ngOnChanges({ 'taskId': nullChange });
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.query(By.css('[data-automation-id="comments-none"]'))).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Add comment', () => {
|
||||
@ -158,37 +187,54 @@ describe('ActivitiComments', () => {
|
||||
fixture.whenStable();
|
||||
}));
|
||||
|
||||
it('should display a dialog to the user when the Add button clicked', () => {
|
||||
let dialogEl = fixture.debugElement.query(By.css('.mdl-dialog')).nativeElement;
|
||||
let showSpy: jasmine.Spy = spyOn(dialogEl, 'showModal');
|
||||
component.showDialog();
|
||||
expect(showSpy).toHaveBeenCalled();
|
||||
it('should call service to add a comment when enter key is pressed', async(() => {
|
||||
let event = new KeyboardEvent('keyup', {'key': 'Enter'});
|
||||
let element = fixture.nativeElement.querySelector('#comment-input');
|
||||
component.message = 'Test Comment';
|
||||
element.dispatchEvent(event);
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(addCommentSpy).toHaveBeenCalled();
|
||||
let elements = fixture.nativeElement.querySelectorAll('#comment-message');
|
||||
expect(elements.length).toBe(1);
|
||||
expect(elements[0].innerText).toBe('Test Comment');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should call service to add a comment', () => {
|
||||
component.showDialog();
|
||||
component.message = 'Test comment';
|
||||
component.add();
|
||||
expect(addCommentSpy).toHaveBeenCalledWith('123', 'Test comment');
|
||||
it('should not call service to add a comment when comment is empty', async(() => {
|
||||
let event = new KeyboardEvent('keyup', {'key': 'Enter'});
|
||||
let element = fixture.nativeElement.querySelector('#comment-input');
|
||||
component.message = '';
|
||||
element.dispatchEvent(event);
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(addCommentSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should clear comment when escape key is pressed', async(() => {
|
||||
let event = new KeyboardEvent('keyup', {'key': 'Escape'});
|
||||
let element = fixture.nativeElement.querySelector('#comment-input');
|
||||
component.message = 'Test comment';
|
||||
element.dispatchEvent(event);
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
element = fixture.nativeElement.querySelector('#comment-input');
|
||||
expect(element.value).toBe('');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should emit an error when an error occurs adding the comment', () => {
|
||||
let emitSpy = spyOn(component.error, 'emit');
|
||||
addCommentSpy.and.returnValue(Observable.throw({}));
|
||||
component.showDialog();
|
||||
component.message = 'Test comment';
|
||||
component.add();
|
||||
expect(emitSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should close add dialog when close button clicked', () => {
|
||||
let dialogEl = fixture.debugElement.query(By.css('.mdl-dialog')).nativeElement;
|
||||
let closeSpy: jasmine.Spy = spyOn(dialogEl, 'close');
|
||||
component.showDialog();
|
||||
component.cancel();
|
||||
expect(closeSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -15,13 +15,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core';
|
||||
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
|
||||
import { AlfrescoTranslationService } from 'ng2-alfresco-core';
|
||||
import { Observable, Observer } from 'rxjs/Rx';
|
||||
import { Comment } from '../models/comment.model';
|
||||
import { ActivitiTaskListService } from './../services/activiti-tasklist.service';
|
||||
|
||||
declare let dialogPolyfill: any;
|
||||
import { Comment } from '../models/comment.model';
|
||||
import { ActivitiTaskListService } from '../services/activiti-tasklist.service';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-comments, activiti-comments',
|
||||
@ -40,9 +39,6 @@ export class ActivitiComments implements OnChanges {
|
||||
@Output()
|
||||
error: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
@ViewChild('dialog')
|
||||
dialog: any;
|
||||
|
||||
comments: Comment [] = [];
|
||||
|
||||
private commentObserver: Observer<Comment>;
|
||||
@ -50,6 +46,8 @@ export class ActivitiComments implements OnChanges {
|
||||
|
||||
message: string;
|
||||
|
||||
beingAdded: boolean = false;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param translate Translation service
|
||||
@ -79,11 +77,17 @@ export class ActivitiComments implements OnChanges {
|
||||
}
|
||||
}
|
||||
|
||||
private getTaskComments(taskId: string) {
|
||||
private getTaskComments(taskId: string): void {
|
||||
this.resetComments();
|
||||
if (taskId) {
|
||||
this.activitiTaskList.getTaskComments(taskId).subscribe(
|
||||
(res: Comment[]) => {
|
||||
res = res.sort((comment1: Comment, comment2: Comment) => {
|
||||
let date1 = new Date(comment1.created);
|
||||
let date2 = new Date(comment2.created);
|
||||
return date1 > date2 ? -1 : date1 < date2 ? 1 : 0;
|
||||
});
|
||||
|
||||
res.forEach((comment) => {
|
||||
this.commentObserver.next(comment);
|
||||
});
|
||||
@ -92,38 +96,35 @@ export class ActivitiComments implements OnChanges {
|
||||
this.error.emit(err);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.resetComments();
|
||||
}
|
||||
}
|
||||
|
||||
private resetComments() {
|
||||
private resetComments(): void {
|
||||
this.comments = [];
|
||||
}
|
||||
|
||||
public showDialog() {
|
||||
if (!this.dialog.nativeElement.showModal) {
|
||||
dialogPolyfill.registerDialog(this.dialog.nativeElement);
|
||||
}
|
||||
this.dialog.nativeElement.showModal();
|
||||
}
|
||||
|
||||
public add() {
|
||||
add(): void {
|
||||
if (this.message && this.message.trim() && !this.beingAdded) {
|
||||
this.beingAdded = true;
|
||||
this.activitiTaskList.addTaskComment(this.taskId, this.message).subscribe(
|
||||
(res: Comment) => {
|
||||
this.comments.push(res);
|
||||
this.comments.unshift(res);
|
||||
this.message = '';
|
||||
this.beingAdded = false;
|
||||
},
|
||||
(err) => {
|
||||
this.error.emit(err);
|
||||
this.beingAdded = false;
|
||||
}
|
||||
);
|
||||
this.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public cancel() {
|
||||
if (this.dialog) {
|
||||
this.dialog.nativeElement.close();
|
||||
clear(): void {
|
||||
this.message = '';
|
||||
}
|
||||
|
||||
isReadOnly(): boolean {
|
||||
return this.readOnly;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,64 @@
|
||||
.adf-comment-img-container {
|
||||
float: left;
|
||||
width: 40px;
|
||||
padding: 5px 10px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.adf-comment-user-icon {
|
||||
padding: 10px 5px;
|
||||
width: 30px;
|
||||
background-color: #01bcd4;
|
||||
border-radius: 50%;
|
||||
font-family: Muli;
|
||||
font-size: 16px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.adf-comment-user-name {
|
||||
float: left;
|
||||
width: calc(100% - 120px);
|
||||
padding: 2px 10px;
|
||||
font-family: Muli;
|
||||
font-weight: 600;
|
||||
color: #595959;
|
||||
}
|
||||
|
||||
.adf-comment-message {
|
||||
float: left;
|
||||
width: calc(100% - 20px);
|
||||
padding: 2px 10px;
|
||||
font-family: Muli;
|
||||
font-style: italic;
|
||||
color: #595959;
|
||||
white-space: initial;
|
||||
}
|
||||
|
||||
.adf-comment-message-time {
|
||||
float: left;
|
||||
width: calc(100% - 120px);
|
||||
padding: 2px 10px;
|
||||
font-family: Muli;
|
||||
font-size: 12px;
|
||||
color: #595959;
|
||||
}
|
||||
|
||||
.adf-comment-contents {
|
||||
float: left;
|
||||
width: calc(100% - 60px);
|
||||
}
|
||||
|
||||
adf-datatable >>> table thead {
|
||||
display: none;
|
||||
}
|
||||
|
||||
adf-datatable >>> table {
|
||||
border: none;
|
||||
}
|
||||
|
||||
adf-datatable >>> table tbody td {
|
||||
padding: 0px!important;
|
||||
border-top: none;
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
<adf-datatable
|
||||
[rows]="comments"
|
||||
(rowClick)="selectComment($event)" *ngIf="hasComments()">
|
||||
|
||||
<data-columns>
|
||||
<data-column key="createdBy">
|
||||
<ng-template let-entry="$implicit">
|
||||
<div id="comment-user-icon" class="adf-comment-img-container">
|
||||
<div class="adf-comment-user-icon">{{getUserShortName(entry.row.obj.createdBy)}}</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</data-column>
|
||||
<data-column key="message">
|
||||
<ng-template let-entry="$implicit">
|
||||
<div class="adf-comment-contents">
|
||||
<div id="comment-user" class="adf-comment-user-name">
|
||||
{{entry.row.obj.createdBy?.firstName}} {{entry.row.obj.createdBy?.lastName}}
|
||||
</div>
|
||||
<div id="comment-message" class="adf-comment-message">
|
||||
{{entry.row.obj.message}}
|
||||
</div>
|
||||
<div id="comment-time" class="adf-comment-message-time">
|
||||
{{transformDate(entry.row.obj.created)}}
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</data-column>
|
||||
</data-columns>
|
||||
|
||||
</adf-datatable>
|
@ -0,0 +1,148 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { CoreModule } from 'ng2-alfresco-core';
|
||||
import { DataRowEvent, DataTableModule, ObjectDataRow } from 'ng2-alfresco-datatable';
|
||||
import { Comment, User } from '../models/index';
|
||||
import { AdfCommentListComponent } from './adf-comment-list.component';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
const testUser: User = new User({
|
||||
id: '1',
|
||||
firstName: 'Test',
|
||||
lastName: 'User',
|
||||
email: 'tu@domain.com'
|
||||
});
|
||||
const testDate = new Date();
|
||||
const testComment: Comment = new Comment(1, 'Test Comment', testDate.toDateString(), testUser);
|
||||
|
||||
describe('AdfCommentListComponent', () => {
|
||||
|
||||
let commentList: AdfCommentListComponent;
|
||||
let fixture: ComponentFixture<AdfCommentListComponent>;
|
||||
let element: HTMLElement;
|
||||
let componentHandler;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CoreModule.forRoot(),
|
||||
DataTableModule
|
||||
],
|
||||
declarations: [
|
||||
AdfCommentListComponent
|
||||
],
|
||||
providers: [
|
||||
DatePipe
|
||||
]
|
||||
}).compileComponents().then(() => {
|
||||
|
||||
fixture = TestBed.createComponent(AdfCommentListComponent);
|
||||
commentList = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
componentHandler = jasmine.createSpyObj('componentHandler', [
|
||||
'upgradeAllRegistered'
|
||||
]);
|
||||
|
||||
window['componentHandler'] = componentHandler;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should emit row click event', (done) => {
|
||||
let row = new ObjectDataRow(testComment);
|
||||
let rowEvent = new DataRowEvent(row, null);
|
||||
|
||||
commentList.clickRow.subscribe(selectedComment => {
|
||||
expect(selectedComment.id).toEqual(1);
|
||||
expect(selectedComment.message).toEqual('Test Comment');
|
||||
expect(selectedComment.createdBy).toEqual(testUser);
|
||||
expect(selectedComment.created).toEqual(testDate.toDateString());
|
||||
done();
|
||||
});
|
||||
|
||||
commentList.selectComment(rowEvent);
|
||||
});
|
||||
|
||||
it('should not show comment list if no input is given', () => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.querySelector('adf-datatable')).toBeNull();
|
||||
});
|
||||
|
||||
it('should show comment message when input is given', () => {
|
||||
commentList.comments = [testComment];
|
||||
fixture.detectChanges();
|
||||
let elements = fixture.nativeElement.querySelectorAll('#comment-message');
|
||||
expect(elements.length).toBe(1);
|
||||
expect(elements[0].innerText).toBe(testComment.message);
|
||||
expect(fixture.nativeElement.querySelector('#comment-message:empty')).toBeNull();
|
||||
});
|
||||
|
||||
it('should show comment user when input is given', () => {
|
||||
commentList.comments = [testComment];
|
||||
fixture.detectChanges();
|
||||
let elements = fixture.nativeElement.querySelectorAll('#comment-user');
|
||||
expect(elements.length).toBe(1);
|
||||
expect(elements[0].innerText).toBe(testComment.createdBy.firstName + ' ' + testComment.createdBy.lastName);
|
||||
expect(fixture.nativeElement.querySelector('#comment-user:empty')).toBeNull();
|
||||
});
|
||||
|
||||
it('should show comment date time when input is given', () => {
|
||||
commentList.comments = [testComment];
|
||||
fixture.detectChanges();
|
||||
let elements = fixture.nativeElement.querySelectorAll('#comment-time');
|
||||
expect(elements.length).toBe(1);
|
||||
expect(elements[0].innerText).toBe(commentList.transformDate(testDate.toDateString()));
|
||||
expect(fixture.nativeElement.querySelector('#comment-time:empty')).toBeNull();
|
||||
});
|
||||
|
||||
it('comment date time should start with Today when comment date is today', () => {
|
||||
commentList.comments = [testComment];
|
||||
fixture.detectChanges();
|
||||
element = fixture.nativeElement.querySelector('#comment-time');
|
||||
expect(element.innerText).toContain('Today');
|
||||
});
|
||||
|
||||
it('comment date time should start with Yesterday when comment date is yesterday', () => {
|
||||
testComment.created = (Date.now() - 24 * 3600 * 1000).toString();
|
||||
commentList.comments = [testComment];
|
||||
fixture.detectChanges();
|
||||
element = fixture.nativeElement.querySelector('#comment-time');
|
||||
expect(element.innerText).toContain('Yesterday');
|
||||
});
|
||||
|
||||
it('comment date time should not start with Today/Yesterday when comment date is before yesterday', () => {
|
||||
testComment.created = (Date.now() - 24 * 3600 * 1000 * 2).toString();
|
||||
commentList.comments = [testComment];
|
||||
fixture.detectChanges();
|
||||
element = fixture.nativeElement.querySelector('#comment-time');
|
||||
expect(element.innerText).not.toContain('Today');
|
||||
expect(element.innerText).not.toContain('Yesterday');
|
||||
});
|
||||
|
||||
it('should show user icon when input is given', () => {
|
||||
commentList.comments = [testComment];
|
||||
fixture.detectChanges();
|
||||
let elements = fixture.nativeElement.querySelectorAll('#comment-user-icon');
|
||||
expect(elements.length).toBe(1);
|
||||
expect(elements[0].innerText).toContain(commentList.getUserShortName(testComment.createdBy));
|
||||
expect(fixture.nativeElement.querySelector('#comment-user-icon:empty')).toBeNull();
|
||||
});
|
||||
});
|
@ -0,0 +1,80 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { Comment, User } from '../models/index';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-comment-list',
|
||||
templateUrl: './adf-comment-list.component.html',
|
||||
styleUrls: ['./adf-comment-list.component.css']
|
||||
})
|
||||
|
||||
export class AdfCommentListComponent {
|
||||
|
||||
@Input()
|
||||
comments: Comment[];
|
||||
|
||||
@Output()
|
||||
clickRow: EventEmitter<Comment> = new EventEmitter<Comment>();
|
||||
|
||||
selectedComment: Comment;
|
||||
|
||||
constructor(private datePipe: DatePipe) {
|
||||
}
|
||||
|
||||
selectComment(event: any): void {
|
||||
this.selectedComment = event.value.obj;
|
||||
this.clickRow.emit(this.selectedComment);
|
||||
}
|
||||
|
||||
getUserShortName(user: User): string {
|
||||
let shortName = '';
|
||||
if (user) {
|
||||
if (user.firstName) {
|
||||
shortName = user.firstName[0].toUpperCase();
|
||||
}
|
||||
if (user.lastName) {
|
||||
shortName += user.lastName[0].toUpperCase();
|
||||
}
|
||||
}
|
||||
return shortName;
|
||||
}
|
||||
|
||||
transformDate(aDate: string): string {
|
||||
let formattedDate: string;
|
||||
let givenDate = Number.parseInt(this.datePipe.transform(aDate, 'yMMdd'));
|
||||
let today = Number.parseInt(this.datePipe.transform(Date.now(), 'yMMdd'));
|
||||
if (givenDate === today) {
|
||||
formattedDate = 'Today, ' + this.datePipe.transform(aDate, 'hh:mm a');
|
||||
}else {
|
||||
let yesterday = Number.parseInt(this.datePipe.transform(Date.now() - 24 * 3600 * 1000, 'yMMdd'));
|
||||
if (givenDate === yesterday) {
|
||||
formattedDate = 'Yesterday, ' + this.datePipe.transform(aDate, 'hh:mm a');
|
||||
}else {
|
||||
formattedDate = this.datePipe.transform(aDate, 'MMM dd y, hh:mm a');
|
||||
}
|
||||
}
|
||||
return formattedDate;
|
||||
}
|
||||
|
||||
hasComments(): boolean {
|
||||
return this.comments && this.comments.length && true;
|
||||
}
|
||||
|
||||
}
|
@ -29,3 +29,4 @@ export * from './activiti-start-task.component';
|
||||
export * from './activiti-people-search.component';
|
||||
export * from './adf-create-task-attachment.component';
|
||||
export * from './adf-people-list.component';
|
||||
export * from './adf-comment-list.component';
|
||||
|
@ -43,6 +43,7 @@
|
||||
"COMMENTS": {
|
||||
"NONE": "No comments.",
|
||||
"ADD": "Add a comment",
|
||||
"HEADER": "Comments",
|
||||
"DIALOG": {
|
||||
"TITLE": "New comment",
|
||||
"LABELS": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user