Skip to content

Commit

Permalink
feat(chat): add support for custom message template (#2750)
Browse files Browse the repository at this point in the history
BREAKING CHANGE:
`NbChatMessageComponent` constructor now has a second parameter (`NbCustomMessageService`).
  • Loading branch information
andreipadolin authored Jun 15, 2021
1 parent b9c77ce commit d84497c
Show file tree
Hide file tree
Showing 21 changed files with 887 additions and 23 deletions.
1 change: 1 addition & 0 deletions docs/structure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,7 @@ export const structure = [
'NbChatComponent',
'NbChatMessageComponent',
'NbChatFormComponent',
'NbChatCustomMessageDirective',
],
},
{
Expand Down
6 changes: 6 additions & 0 deletions src/app/playground-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,12 @@ export const PLAYGROUND_COMPONENTS: ComponentLink[] = [
component: 'ChatTestComponent',
name: 'Chat Test',
},
{
path: 'chat-custom-message.component',
link: '/chat/chat-custom-message.component',
component: 'ChatCustomMessageComponent',
name: 'Chat Custom Message',
},
],
},
{
Expand Down
44 changes: 44 additions & 0 deletions src/framework/theme/components/chat/_chat.component.theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
}

.avatar {
display: block;
border-radius: 50%;
flex-shrink: 0;
background: nb-theme(chat-message-avatar-background-color);
Expand All @@ -104,13 +105,15 @@
.sender {
font-size: 0.875rem;
color: nb-theme(chat-message-sender-text-color);
margin-top: 0;
margin-bottom: 0.5rem;
}

p {
word-break: break-word;
white-space: pre-wrap;
max-width: 100%;
margin-top: 0;
margin-bottom: 0;
}

Expand Down Expand Up @@ -179,6 +182,7 @@
color: nb-theme(chat-message-quote-text-color);
padding: 1rem;
border-radius: 0.5rem;
margin-top: 0;
margin-bottom: 0.5rem;
}

Expand Down Expand Up @@ -218,6 +222,9 @@
flex-direction: row-reverse;

.message {
display: flex;
flex-direction: column;
align-items: flex-end;
margin-left: 0;

@include nb-ltr(margin-right, 0.5rem);
Expand Down Expand Up @@ -322,5 +329,42 @@
}
}
}

.nb-custom-message {
display: inline-block;
padding: nb-theme(chat-padding);
margin-top: 0.5rem;
border-radius: 0.5rem;
}

.nb-custom-message-full-width {
width: 100%;
}

.nb-custom-message-no-space {
margin-top: 0;
}

.nb-custom-message-not-reply {
background: nb-theme(chat-message-background);
color: nb-theme(chat-message-text-color);

@include nb-ltr(border-top-left-radius, 0);
@include nb-rtl(border-top-right-radius, 0);

a,
a:hover,
a:focus, {
color: nb-theme(chat-message-text-color);
}
}

.nb-custom-message-reply {
background: nb-theme(chat-message-reply-background-color);
color: nb-theme(chat-message-reply-text-color);

@include nb-ltr(border-top-right-radius, 0);
@include nb-rtl(border-top-left-radius, 0);
}
}

23 changes: 23 additions & 0 deletions src/framework/theme/components/chat/chat-avatar.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ChangeDetectionStrategy, Component, HostBinding, Input } from '@angular/core';
import { SafeStyle } from '@angular/platform-browser';

@Component({
selector: 'nb-chat-avatar',
template: `
<ng-container *ngIf="!avatarStyle">
{{ initials }}
</ng-container>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NbChatAvatarComponent {

@Input() initials: string;

@Input()
@HostBinding('style.background-image')
avatarStyle: SafeStyle;

@HostBinding('class.avatar')
readonly avatarClass = true;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NbChatModule, NbThemeModule } from '@nebular/theme';
import { NbCustomMessageService } from './custom-message.service';

@Component({
selector: 'nb-custom-message-directive-test',
template: `
<div *nbCustomMessage="customMessageType">
<p>Hello world</p>
</div>
`,
})
export class NbCustomMessageTestComponent {
customMessageType: string = 'simpleMessageType';
}

describe('Directive chat-custom-message-directive: NbCustomMessageTestComponent', () => {
let fixture: ComponentFixture<NbCustomMessageTestComponent>;
let component: NbCustomMessageTestComponent;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [NbThemeModule.forRoot(), NbChatModule],
declarations: [NbCustomMessageTestComponent],
providers: [NbCustomMessageService],
});

fixture = TestBed.createComponent(NbCustomMessageTestComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should be created', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Directive, Input, OnDestroy, OnInit, TemplateRef } from '@angular/core';

import { convertToBoolProperty, NbBooleanInput } from '../helpers';
import { NbCustomMessageService } from './custom-message.service';

function throwCustomMessageTypeIsRequired(): void {
throw new Error('[nbCustomMessage]: custom message type is required.');
}

/**
* `[nbCustomMessage]` directive should be used as a structural directive or should be applied to the `ng-template`:
*
* ```html
* <div *nbCustomMessage="'my-custom-type'; let data">
* <!-- custom message -->
* </div>
* ```
* or
* ```html
* <ng-template nbCustomMessage='my-custom-type' let-data>
* <!-- custom message -->
* </ng-template>
* ```
*/
@Directive({
selector: `[nbCustomMessage]`,
})
export class NbChatCustomMessageDirective implements OnInit, OnDestroy {

/**
* Defines a message type which should rendered with the custom message template.
* @type {string}
*/
@Input()
get nbCustomMessage(): string {
return this._type;
}
set nbCustomMessage(value: string) {
this._type = value;
}
protected _type: string;

get type(): string {
return this._type
}

/**
* Disables generic message styles, such as round corners, text color, background, etc.,
* so a custom message could be styled from the ground up.
*
* @type {boolean}
*/
@Input()
set nbCustomMessageNoStyles(value: boolean) {
this._noStyles = convertToBoolProperty(value);
}
get nbCustomMessageNoStyles(): boolean {
return this._noStyles;
}
protected _noStyles: boolean = false;
static ngAcceptInputType_noStyles: NbBooleanInput;

get noStyles(): boolean {
return this.nbCustomMessageNoStyles;
}

constructor(public templateRef: TemplateRef<any>, protected customMessageService: NbCustomMessageService) { }

ngOnInit() {
if (!this._type) {
throwCustomMessageTypeIsRequired();
}
this.customMessageService.register(this.type, this);
}

ngOnDestroy() {
this.customMessageService.unregister(this.type);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NbChatModule, NbThemeModule } from '@nebular/theme';

@Component({
selector: 'nb-chat-message-quote-test',
template: `
<nb-chat-message-quote [sender]="sender"
[date]="date"
[dateFormat]="dateFormat"
[message]="message"
[quote]="quote">
</nb-chat-message-quote>
`,
})
export class NbChatMessageQuoteTestComponent {
sender: string;
dateFormat: string;
message: string;
date: Date;
quote: string;
}

describe('Chat-message-quote component: NbChatMessageQuoteTestComponent', () => {
let fixture: ComponentFixture<NbChatMessageQuoteTestComponent>;
let component: NbChatMessageQuoteTestComponent;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [NbThemeModule.forRoot(), NbChatModule],
declarations: [NbChatMessageQuoteTestComponent],
});

fixture = TestBed.createComponent(NbChatMessageQuoteTestComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should be created', () => {
expect(component).toBeTruthy();
});

it('quote should be created', () => {
component.message = 'some test message';
component.date = new Date();
component.dateFormat = 'shortTime';
component.quote = 'quote';
fixture.detectChanges();
const quoteElement = fixture.nativeElement.querySelector('.quote');
expect(quoteElement).toBeTruthy();
});

it('should be created inner chat message', () => {
component.message = 'some test message';
component.date = new Date();
component.dateFormat = 'shortTime';
component.quote = 'quote';
fixture.detectChanges();
const quoteElement = fixture.nativeElement.querySelector('nb-chat-message-text');
expect(quoteElement).toBeTruthy();
});

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NbChatModule, NbThemeModule } from '@nebular/theme';

@Component({
selector: 'nb-chat-message-text-test',
template: `
<nb-chat-message-text [sender]="sender"
[date]="date"
[dateFormat]="dateFormat"
[message]="message">
</nb-chat-message-text>
`,
})
export class NbChatMessageTextTestComponent {
sender: string;
dateFormat: string;
message: string;
date: Date;
}

describe('Chat-message-text component: NbChatMessageTextTestComponent', () => {
let fixture: ComponentFixture<NbChatMessageTextTestComponent>;
let component: NbChatMessageTextTestComponent;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [NbThemeModule.forRoot(), NbChatModule],
declarations: [NbChatMessageTextTestComponent],
});

fixture = TestBed.createComponent(NbChatMessageTextTestComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should be created', () => {
expect(component).toBeTruthy();
});

it('should set all inputs', () => {
component.sender = 'AB';
component.message = 'new text message';
component.date = new Date();
component.dateFormat = 'shortTime';
fixture.detectChanges();

const msgValue = fixture.nativeElement.querySelector('.text').textContent;
expect(msgValue).toContain('new text message');

const senderInitials = fixture.nativeElement.querySelector('.sender').textContent;
expect(senderInitials).toContain('AB');

const time = fixture.nativeElement.querySelector('time');
expect(time).toBeTruthy();
});

it('should not show sender if it is not provided', () => {
component.message = 'some test message';
fixture.detectChanges();
const sender = fixture.nativeElement.querySelector('.sender');
expect(sender).toBeFalsy();
});

it('should not show message if it is not provided', () => {
component.sender = 'JD';
fixture.detectChanges();
const sender = fixture.nativeElement.querySelector('.message');
expect(sender).toBeFalsy();
});
});
Loading

0 comments on commit d84497c

Please sign in to comment.