diff --git a/.travis.yml b/.travis.yml index b18be5b..369834e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ +dist: trusty language: node_js node_js: - "10" - before_install: - export CHROME_BIN=chromium-browser - export DISPLAY=:99.0 @@ -10,7 +10,7 @@ before_script: - npm install - cd src - npm link - - npm install -g angular-cli + - npm install -g @angular/cli - npm install -g karma - npm install script: diff --git a/README.md b/README.md index 876c945..35f0cbb 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ __Required Settings__ __Additional Settings__ * [title]{string}: The title to be displayed on the friends list. Default is "Friends". +* [isDisabled]{boolean}: Indicates if ng-chat should be hidden. This stops poll requests to the friends list. Default is false. * [isCollapsed]{boolean}: If set to true the friends list will be rendered as collapsed by default. Default is false. * [pollFriendsList]{boolean}: If set to true the module will do a long poll on the "adapter.listFriends" method to keep the friends list updated. Default is false. * [pollingInterval]{number}: Configures the long poll interval in milliseconds. Default is 5000. @@ -96,6 +97,7 @@ __Additional Settings__ * [showMessageDate]{boolean}: Shows the date in which a message was sent. Default is true. * [messageDatePipeFormat]{string}: The format for the pipe that is used when rendering the date in which a message was sent. Default is "short". * [groupAdapter]{IChatGroupAdapter}: A group adapter implementation to enable group chat. +* [isViewportOnMobileEnabled]{boolean}: Allow ng-chat to render and be displayed on mobile devices. Default is false. __Localization__ * [messagePlaceholder]{string}: The placeholder that is displayed in the text input on each chat window. Default is "Type a message". diff --git a/angular.json b/angular.json index 25ae5f9..4e5fa81 100644 --- a/angular.json +++ b/angular.json @@ -68,7 +68,7 @@ "assets": [ "demo/offline_bot/src/assets" ], - "tsConfig": "demo/offline_bot/src/../../../tsconfig.json", + "tsConfig": "demo/offline_bot/src/../../../tsconfig.json" } }, "lint": { diff --git a/demo/offline_bot/package.json b/demo/offline_bot/package.json index 183e979..b7c1136 100644 --- a/demo/offline_bot/package.json +++ b/demo/offline_bot/package.json @@ -22,11 +22,12 @@ "@angular/platform-browser-dynamic": "^7.0.3", "@angular/router": "^7.0.3", "core-js": "^2.5.7", - "rxjs": "^6.3.3", - "zone.js": "^0.8.26", - "ng-chat": "^2.0.4" + "ng-chat": "^2.0.5", + "rxjs": "^6.5.2", + "zone.js": "^0.8.26" }, "devDependencies": { + "@angular-devkit/build-angular": "0.13.4", "@angular/cli": "^7.0.4", "@angular/compiler-cli": "^7.0.3", "@angular/language-service": "^7.0.3", @@ -45,6 +46,6 @@ "protractor": "~5.4.1", "ts-node": "~7.0.1", "tslint": "~5.11.0", - "typescript": "~3.1.6" + "typescript": "~3.2.4" } } diff --git a/package.json b/package.json index 28d5086..f738224 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ }, "dependencies": {}, "devDependencies": { - "@angular-devkit/build-angular": "~0.10.0", + "@angular-devkit/build-angular": "^0.13.4", "@angular/cli": "^7.0.5", "@angular/common": "^7.0.3", "@angular/compiler": "^7.0.3", @@ -82,7 +82,7 @@ "tslint": "^5.11.0", "tslint-config-valorsoft": "^2.2.1", "typedoc": "^0.13.0", - "typescript": "~3.1.6", + "typescript": "~3.2.4", "wallaby-webpack": "^3.9.13", "webdriver-manager": "^12.1.0", "zone.js": "^0.8.26" diff --git a/src/ng-chat/assets/ng-chat.component.default.css b/src/ng-chat/assets/ng-chat.component.default.css index 1de559b..11b8f9b 100644 --- a/src/ng-chat/assets/ng-chat.component.default.css +++ b/src/ng-chat/assets/ng-chat.component.default.css @@ -25,7 +25,7 @@ height:360px; border-width: 1px; border-style: solid; - margin-right:20px; + margin-right: 20px; box-shadow: 0 4px 8px rgba(0,0,0,.25); border-bottom:0; } @@ -212,15 +212,15 @@ } .ng-chat-window { - right:260px; - height:360px; - z-index:999; - bottom:0; + right: 260px; + height: 360px; + z-index: 999; + bottom: 0; + width: 300px; position: fixed; - width:300px; border-width: 1px; border-style: solid; - border-bottom:0; + border-bottom: 0; box-shadow: 0 4px 8px rgba(0,0,0,.25); } .ng-chat-window-collapsed @@ -417,3 +417,18 @@ float: right; margin-right: 5px; } + +/* Mobile viewport only */ +@media only screen and (max-width: 581px) { + #ng-chat-people + { + width: 300px; + height: 360px; + margin-right: 0; + } + + .ng-chat-window + { + position: initial; + } +} diff --git a/src/ng-chat/components/ng-chat-options/ng-chat-options.component.css b/src/ng-chat/components/ng-chat-options/ng-chat-options.component.css index 6a015e9..f2e574b 100644 --- a/src/ng-chat/components/ng-chat-options/ng-chat-options.component.css +++ b/src/ng-chat/components/ng-chat-options/ng-chat-options.component.css @@ -38,3 +38,9 @@ display: block; } +/* Mobile viewport only */ +@media only screen and (max-width: 581px) { + .ng-chat-options-content { + right: 0; + } +} diff --git a/src/ng-chat/ng-chat.component.html b/src/ng-chat/ng-chat.component.html index ddfbef7..696b1e8 100644 --- a/src/ng-chat/ng-chat.component.html +++ b/src/ng-chat/ng-chat.component.html @@ -1,6 +1,6 @@ - + -
+
@@ -28,7 +28,7 @@
- + {{user.displayName}} {{unreadMessagesTotalByParticipant(user)}} @@ -74,7 +74,7 @@
- + {{window.participant | groupMessageDisplayName:message}} diff --git a/src/ng-chat/ng-chat.component.ts b/src/ng-chat/ng-chat.component.ts index b47e913..28724a0 100644 --- a/src/ng-chat/ng-chat.component.ts +++ b/src/ng-chat/ng-chat.component.ts @@ -1,6 +1,5 @@ import { Component, Input, OnInit, ViewChildren, ViewChild, HostListener, Output, EventEmitter, ElementRef, ViewEncapsulation } from '@angular/core'; import { HttpClient } from '@angular/common/http'; -import { DomSanitizer } from '@angular/platform-browser'; import { ChatAdapter } from './core/chat-adapter'; import { IChatGroupAdapter } from './core/chat-group-adapter'; @@ -40,13 +39,34 @@ import { Observable } from 'rxjs'; }) export class NgChat implements OnInit, IChatController { - constructor(public sanitizer: DomSanitizer, private _httpClient: HttpClient) { } + constructor(private _httpClient: HttpClient) { } // Exposes enums for the ng-template public ChatParticipantType = ChatParticipantType; public ChatParticipantStatus = ChatParticipantStatus; public MessageType = MessageType; + private _isDisabled: boolean = false; + + get isDisabled(): boolean { + return this._isDisabled; + } + + @Input() + set isDisabled(value: boolean) { + this._isDisabled = value; + + if (value) + { + // To address issue https://github.com/rpaschoal/ng-chat/issues/120 + window.clearInterval(this.pollingIntervalWindowInstance) + } + else + { + this.activateFriendListFetch(); + } + } + @Input() public adapter: ChatAdapter; @@ -133,7 +153,10 @@ export class NgChat implements OnInit, IChatController { @Input() public showMessageDate: boolean = true; - + + @Input() + public isViewportOnMobileEnabled: boolean = false; + @Output() public onParticipantClicked: EventEmitter = new EventEmitter(); @@ -172,6 +195,8 @@ export class NgChat implements OnInit, IChatController { protected selectedUsersFromFriendsList: User[] = []; + private pollingIntervalWindowInstance: number; + public defaultWindowOptions(currentWindow: Window): IChatOption[] { if (this.groupAdapter && currentWindow.participant.participantType == ChatParticipantType.User) @@ -256,8 +281,8 @@ export class NgChat implements OnInit, IChatController { this.updateWindowsState(this.windows); - // Viewport should have space for at least one chat window. - this.unsupportedViewport = this.hideFriendsListOnUnsupportedViewport && maxSupportedOpenedWindows < 1; + // Viewport should have space for at least one chat window but should show in mobile if option is enabled. + this.unsupportedViewport = this.isViewportOnMobileEnabled? false : this.hideFriendsListOnUnsupportedViewport && maxSupportedOpenedWindows < 1; } // Initializes the chat plugin and the messaging adapter @@ -279,17 +304,7 @@ export class NgChat implements OnInit, IChatController { this.adapter.messageReceivedHandler = (participant, msg) => this.onMessageReceived(participant, msg); this.adapter.friendsListChangedHandler = (participantsResponse) => this.onFriendsListChanged(participantsResponse); - // Loading current users list - if (this.pollFriendsList){ - // Setting a long poll interval to update the friends list - this.fetchFriendsList(true); - setInterval(() => this.fetchFriendsList(false), this.pollingInterval); - } - else - { - // Since polling was disabled, a friends list update mechanism will have to be implemented in the ChatAdapter. - this.fetchFriendsList(true); - } + this.activateFriendListFetch(); this.bufferAudioFile(); @@ -300,6 +315,8 @@ export class NgChat implements OnInit, IChatController { this.fileUploadAdapter = new DefaultFileUploadAdapter(this.fileUploadUrl, this._httpClient); } + this.NormalizeWindows(); + this.isBootstrapped = true; } catch(ex) @@ -325,6 +342,23 @@ export class NgChat implements OnInit, IChatController { } } + private activateFriendListFetch(): void { + if (this.adapter) + { + // Loading current users list + if (this.pollFriendsList){ + // Setting a long poll interval to update the friends list + this.fetchFriendsList(true); + this.pollingIntervalWindowInstance = window.setInterval(() => this.fetchFriendsList(false), this.pollingInterval); + } + else + { + // Since polling was disabled, a friends list update mechanism will have to be implemented in the ChatAdapter. + this.fetchFriendsList(true); + } + } + } + // Initializes browser notifications private async initializeBrowserNotifications() { @@ -512,10 +546,11 @@ export class NgChat implements OnInit, IChatController { this.windows.unshift(newChatWindow); - // Is there enough space left in the view port ? - if (this.windows.length * this.windowSizeFactor >= this.viewPortTotalArea - (!this.hideFriendsList ? this.friendsListWidth : 0)) - { - this.windows.pop(); + // Is there enough space left in the view port ? but should be displayed in mobile if option is enabled + if (!this.isViewportOnMobileEnabled) { + if (this.windows.length * this.windowSizeFactor >= this.viewPortTotalArea - (!this.hideFriendsList ? this.friendsListWidth : 0)) { + this.windows.pop(); + } } this.updateWindowsState(this.windows); diff --git a/src/ng-chat/ng-chat.module.ts b/src/ng-chat/ng-chat.module.ts index 5c0da57..a3a422d 100644 --- a/src/ng-chat/ng-chat.module.ts +++ b/src/ng-chat/ng-chat.module.ts @@ -6,12 +6,13 @@ import { HttpClientModule } from '@angular/common/http'; import { NgChat } from './ng-chat.component'; import { EmojifyPipe } from './pipes/emojify.pipe'; import { LinkfyPipe } from './pipes/linkfy.pipe'; +import { SanitizePipe } from './pipes/sanitize.pipe'; import { GroupMessageDisplayNamePipe } from './pipes/group-message-display-name.pipe'; import { NgChatOptionsComponent } from './components/ng-chat-options/ng-chat-options.component'; @NgModule({ imports: [CommonModule, FormsModule, HttpClientModule], - declarations: [NgChat, EmojifyPipe, LinkfyPipe, GroupMessageDisplayNamePipe, NgChatOptionsComponent], + declarations: [NgChat, EmojifyPipe, LinkfyPipe, SanitizePipe, GroupMessageDisplayNamePipe, NgChatOptionsComponent], exports: [NgChat] }) export class NgChatModule { diff --git a/src/ng-chat/pipes/sanitize.pipe.ts b/src/ng-chat/pipes/sanitize.pipe.ts new file mode 100644 index 0000000..1187193 --- /dev/null +++ b/src/ng-chat/pipes/sanitize.pipe.ts @@ -0,0 +1,14 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; + +/* + * Sanitizes an URL resource +*/ +@Pipe({name: 'sanitize'}) +export class SanitizePipe implements PipeTransform { + constructor(protected sanitizer: DomSanitizer) {} + + transform(url: string): SafeResourceUrl { + return this.sanitizer.bypassSecurityTrustResourceUrl(url); + } +} diff --git a/src/package.json b/src/package.json index 0a196b4..e455b11 100644 --- a/src/package.json +++ b/src/package.json @@ -1,6 +1,6 @@ { "name": "ng-chat", - "version": "2.0.4", + "version": "2.0.5", "peerDependencies": { "@angular/common": "*", "@angular/core": "*", @@ -13,13 +13,13 @@ "@angular/forms": "^7.0.3", "@angular/platform-browser": "^7.0.3", "zone.js": "^0.8.26", - "rxjs": "^6.3.3", + "rxjs": "^6.5.2", "@angular/compiler": "^7.0.3", "@angular/compiler-cli": "^7.0.3", "gulp": "~4.0.0", "gulp-inline-ng2-template": "^5.0.1", "rollup": "^0.67.0", - "typescript": "~3.1.6", + "typescript": "~3.2.4", "uglify-js": "^3.4.9", "ng-packagr": "^4.4.0", "tsickle": "^0.33.1" diff --git a/src/spec/ng-chat.component.spec.ts b/src/spec/ng-chat.component.spec.ts index d498422..d3c5bc4 100644 --- a/src/spec/ng-chat.component.spec.ts +++ b/src/spec/ng-chat.component.spec.ts @@ -51,7 +51,7 @@ let subject: any = null; describe('NgChat', () => { beforeEach(() => { - subject = new NgChat(null, null); // HttpClient related methods are tested elsewhere + subject = new NgChat(null); // HttpClient related methods are tested elsewhere subject.userId = 123; subject.adapter = new MockableAdapter(); subject.groupAdapter = new MockableGroupAdapter(); @@ -97,7 +97,11 @@ describe('NgChat', () => { it('Persistent windows state must be enabled by default', () => { expect(subject.persistWindowsState).toBeTruthy(); }); - + + it('Is viewport mobile case state must be disabled by default', () => { + expect(subject.isViewportOnMobileEnabled).toBeFalsy(); + }); + it('isCollapsed must be disabled by default', () => { expect(subject.isCollapsed).toBeFalsy(); });