diff --git a/chatto/src/main/javascript/package.json b/chatto/src/main/javascript/package.json index 65a5d62..d23ab30 100644 --- a/chatto/src/main/javascript/package.json +++ b/chatto/src/main/javascript/package.json @@ -1,11 +1,13 @@ { "dependencies": { + "@types/bootbox": "^5.2.0", "@types/dompurify": "^2.0.0", "@types/jquery": "^3.3.31", "@types/markdown-it": "^0.0.9", "@types/sjcl": "^1.0.28", "@types/sprintf-js": "^1.1.2", "alertifyjs": "^1.12.0", + "bootbox": "^5.4.0", "builder-pattern": "^1.2.3", "chart.js": "^2.9.3", "dompurify": "^2.0.7", @@ -43,7 +45,13 @@ "fuse.js": "global:Fuse", "sjcl": "global:sjcl", "sprintf-js": "global:sprintf", - "alertifyjs": "global:alertify" + "alertifyjs": "global:alertify", + "bootbox": { + "exports": "global:bootbox", + "depends": [ + "@types/jquery" + ] + } }, "scripts": { "watch": "watchify ts/src/main.ts -p [ tsify --target ES6 --noImplicitAny ] --debug -o ../resources/static/js/bundle.js" diff --git a/chatto/src/main/javascript/ts/src/main.ts b/chatto/src/main/javascript/ts/src/main.ts index d9b0469..5fbd71a 100644 --- a/chatto/src/main/javascript/ts/src/main.ts +++ b/chatto/src/main/javascript/ts/src/main.ts @@ -66,7 +66,8 @@ const uvDeps: UserViewDeps = { userSearchCancelButton: userSearchCancelButton, searchService: activeUserSearchService, userContactOnlineTemplate: TemplateFactory.getTemplate('user-contact-online-template'), - userContactOfflineTemplate: TemplateFactory.getTemplate('user-contact-offline-template') + userContactOfflineTemplate: TemplateFactory.getTemplate('user-contact-offline-template'), + notificationService: ns } const userView = new UserView(uvDeps); userModel.attach(userView); @@ -79,14 +80,22 @@ Handlebars.registerHelper('avatar', function () { return '
'; }); Handlebars.registerHelper('fromNow', function (date: string) { - if(date == null) - return ": Never" + if (date == null) + return ": Never" return moment(date).fromNow(); }) Handlebars.registerHelper('msgDateFormat', function (date: string) { return moment(date).calendar(moment.now(), { lastWeek: "DD/MM/YY hh:mm A", sameElse: "DD/MM/YY hh:mm A" }) }) +Handlebars.registerHelper('lockIcon', function (unlocked: boolean) { + switch (unlocked) { + case true: { return ''; } + default: { return ''; } + } +}) + + ns.success("Welcome"); diff --git a/chatto/src/main/javascript/ts/src/model/ChatModel.ts b/chatto/src/main/javascript/ts/src/model/ChatModel.ts index 085b267..300047e 100644 --- a/chatto/src/main/javascript/ts/src/model/ChatModel.ts +++ b/chatto/src/main/javascript/ts/src/model/ChatModel.ts @@ -94,8 +94,9 @@ export class ChatModel implements Subject { } public clear(): void { + log.info("Clearing model") this._messagePageMap.set(JsonAPI.contactName!, 0); - this.storeUserMessages(JsonAPI.contactName!, [], "clear"); + this.storeUserMessages(JsonAPI.contactName!, Array(), "clear"); this.notify({ userName: "", data: [], op: "clear" }) } diff --git a/chatto/src/main/javascript/ts/src/service/AlertifyNotificationService.ts b/chatto/src/main/javascript/ts/src/service/AlertifyNotificationService.ts index fbc2d00..e81a62b 100644 --- a/chatto/src/main/javascript/ts/src/service/AlertifyNotificationService.ts +++ b/chatto/src/main/javascript/ts/src/service/AlertifyNotificationService.ts @@ -1,7 +1,11 @@ import { NotificationService } from "./NotificationService"; // @ts-ignore import * as alertify from "alertifyjs"; +import { ActiveUserViewModel } from "../viewmodel/ActiveUserViewModel"; +import log = require("loglevel"); +import bootbox = require("bootbox") export class AlertifyNotificationService implements NotificationService { + private readonly _alertify = alertify; constructor() { this._alertify.set('notifier', 'position', 'top-center'); @@ -24,4 +28,84 @@ export class AlertifyNotificationService implements NotificationService { message(message: string): void { this._alertify.message(message); } + passphrasePrompt(vm: ActiveUserViewModel, vms: ActiveUserViewModel[], cb1: (contactName: string, passphrase: string, + lastMessageTime: string | null, op: string) => void, + cb2: () => any): void { + + // alertify.myprompt || alertify.dialog('myprompt', function () { + + // // document.getElementById('passphraseFormNew')!.addEventListener('submit', function (e: Event) { + // // e.preventDefault(); + // // log.debug(this.querySelectorAll('input')) + // // Array.from(this.querySelectorAll('input')).map((el) => { + // // const el2 = el as HTMLInputElement; + // // vm.passphrase = el2.value; + // // vm.unlocked = true; + // // cb1(vm.userName!, vm.passphrase, null, "new"); + // // cb2(); + // // alertify.myprompt().close(); + // // }) + // // log.debug(vm) + + // // }) + + // return { + // main: function (content: HTMLElement) { + // log.debug(vm) + + // // @ts-ignore + // this.setContent(content); + // }, + + // setup: function () { + // return { + // focus: { + // // @ts-ignore + // element: function () { + // // @ts-ignore + // return this.elements.body.querySelector(this.get('selector')); + // }, + // select: true + // }, + // options: { + // title: 'Enter Passphrase', + // basic: true, + // maximizable: false, + // resizable: false, + // padding: false + // } + // }; + // }, + // settings: { + // // @ts-ignore + // selector: undefined + // }, + // } + + + // }) + + // alertify.myprompt(document.getElementById('passphraseFormNew')).set('selector', 'input[type="password"]'); + + if (vm.passphrase == null) { + bootbox.prompt({ + title: "Please enter the passphrase", + inputType: 'password', + callback: function (result) { + if (result) { + log.debug(result); + cb1(vm.userName!, result, null, "new"); + vm.unlocked = true + vms.filter(v => v.userName == vm.userName).map(v => { v.passphrase = result; v.unlocked = true }) + cb2(); + log.debug(vm) + log.debug(vms) + } + } + }); + } + else { + cb1(vm.userName!, vm.passphrase, null, "new"); + } + } } \ No newline at end of file diff --git a/chatto/src/main/javascript/ts/src/service/NotificationService.ts b/chatto/src/main/javascript/ts/src/service/NotificationService.ts index 9d9b208..5c97f86 100644 --- a/chatto/src/main/javascript/ts/src/service/NotificationService.ts +++ b/chatto/src/main/javascript/ts/src/service/NotificationService.ts @@ -1,7 +1,10 @@ +import { ActiveUserViewModel } from "../viewmodel/ActiveUserViewModel"; + export interface NotificationService { success(message: string): void; error(message: string): void; errorWithDelay(message: string, delay: number): void; warning(message: string): void; message(message: string): void; + passphrasePrompt(vm: ActiveUserViewModel, vms: ActiveUserViewModel[], cb1: (...x: any) => any, cb2: (...x: any) => any): void; } \ No newline at end of file diff --git a/chatto/src/main/javascript/ts/src/view/UserView.ts b/chatto/src/main/javascript/ts/src/view/UserView.ts index c2ef9ba..9744a6b 100644 --- a/chatto/src/main/javascript/ts/src/view/UserView.ts +++ b/chatto/src/main/javascript/ts/src/view/UserView.ts @@ -8,10 +8,11 @@ import { UserModel } from "../model/UserModel"; import { UserViewDeps } from "./UserViewDeps"; import { ObserverData } from "../observe/ObserverData"; import { JsonAPI } from "../singleton/JsonAPI"; +import { NotificationService } from "../service/NotificationService"; export class UserView implements Observer { - + private readonly _model: UserModel; private readonly _chatModel: ChatModel; private readonly _usersListElement: HTMLElement; @@ -21,6 +22,7 @@ export class UserView implements Observer { private readonly _searchService: SearchService; private readonly _userContactOnlineTemplate: Handlebars.TemplateDelegate; private readonly _userContactOfflineTemplate: Handlebars.TemplateDelegate; + private readonly _notificationService: NotificationService; constructor(deps: UserViewDeps) { this._model = deps.model; @@ -32,6 +34,7 @@ export class UserView implements Observer { this._searchService = deps.searchService; this._userContactOnlineTemplate = deps.userContactOnlineTemplate; this._userContactOfflineTemplate = deps.userContactOfflineTemplate; + this._notificationService = deps.notificationService; this._addSearchEventListeners(); } @@ -39,7 +42,8 @@ export class UserView implements Observer { update(d: ObserverData): void { let html: string = ""; d.data.forEach((element: ActiveUserViewModel) => { - element.online ? html += this._userContactOnlineTemplate(element) : html += this._userContactOfflineTemplate(element); + element.online ? html += this._userContactOnlineTemplate(element) : + html += this._userContactOfflineTemplate(element); }); $(this._usersListElement).html(DOMPurify.sanitize(html)); this._addUserCallBacks(); @@ -53,10 +57,9 @@ export class UserView implements Observer { private _addUserCallBacks(): void { let userBoxes = document.getElementsByClassName('user-box'); - for (let i = 0; i < userBoxes.length; i++) { - let userBox = userBoxes[i]; - userBoxes[i].addEventListener('click', this._userCallBack.bind(this, userBox)); - } + + Array.from(userBoxes).forEach((ub: Element) => + ub.addEventListener('click', this._userCallBack.bind(this, ub))) } @@ -109,10 +112,24 @@ export class UserView implements Observer { JsonAPI.contactName = userName; // @ts-ignore: Object is possibly 'null'. document.getElementById('user-name-span').innerText = userName; - this._chatModel.getMessages(userName, passphrase, null, "new"); + let vm = this._model.activeUsersList.find(vm => vm.userName === userName); + if (!vm) { + vm = new ActiveUserViewModel(); + vm.userName = userName; + } + this._notificationService.passphrasePrompt(vm, this._model.activeUsersList, + this._chatModel.getMessages.bind(this._chatModel), + this._promptHandler.bind(this)); + // this._chatModel.getMessages(userName, vm.passphrase, null, "new"); el.className += " active"; } + private _promptHandler(vm: ActiveUserViewModel, vms: ActiveUserViewModel[]) { + // vms.filter(v => v.userName == vm.userName).map(v => v.userName = vm.userName) + log.debug(vms) + this._model.notify(); + } + private _addSearchButtonEL() { this._userSearchButton.addEventListener('submit', (e) => { e.preventDefault(); @@ -127,7 +144,7 @@ export class UserView implements Observer { return; } let searchResult = this._searchService.search(list, searchTerm); - this.update({data: searchResult, op: ""}); + this.update({ data: searchResult, op: "" }); log.debug(searchResult); }) } @@ -148,7 +165,7 @@ export class UserView implements Observer { this._userSearchInputElement.value = ""; this._userSearchCancelButton.hidden = true; let list = this._model.activeUsersList; - this.update({data: list, op: ""}) + this.update({ data: list, op: "" }) }) } diff --git a/chatto/src/main/javascript/ts/src/view/UserViewDeps.ts b/chatto/src/main/javascript/ts/src/view/UserViewDeps.ts index 6b29880..491d07c 100644 --- a/chatto/src/main/javascript/ts/src/view/UserViewDeps.ts +++ b/chatto/src/main/javascript/ts/src/view/UserViewDeps.ts @@ -5,6 +5,7 @@ import { ChatModel } from "../model/ChatModel"; import { SearchService } from "../service/SearchService"; import { ActiveUserViewModel } from "../viewmodel/ActiveUserViewModel"; +import { NotificationService } from "../service/NotificationService"; export interface UserViewDeps { model: UserModel; @@ -16,4 +17,5 @@ export interface UserViewDeps { searchService: SearchService; userContactOnlineTemplate: Handlebars.TemplateDelegate; userContactOfflineTemplate: Handlebars.TemplateDelegate; + notificationService: NotificationService; } \ No newline at end of file diff --git a/chatto/src/main/javascript/ts/src/viewmodel/ActiveUserViewModel.ts b/chatto/src/main/javascript/ts/src/viewmodel/ActiveUserViewModel.ts index 3ffdde5..555a62e 100644 --- a/chatto/src/main/javascript/ts/src/viewmodel/ActiveUserViewModel.ts +++ b/chatto/src/main/javascript/ts/src/viewmodel/ActiveUserViewModel.ts @@ -1,5 +1,7 @@ export class ActiveUserViewModel { userName: string | undefined; - online: boolean | undefined; + online: boolean = false; + unlocked: boolean = false; + passphrase: string = ""; lastActive: Date | undefined; } \ No newline at end of file diff --git a/chatto/src/main/javascript/yarn.lock b/chatto/src/main/javascript/yarn.lock index c65bd9f..1e5a5d0 100644 --- a/chatto/src/main/javascript/yarn.lock +++ b/chatto/src/main/javascript/yarn.lock @@ -2,6 +2,13 @@ # yarn lockfile v1 +"@types/bootbox@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@types/bootbox/-/bootbox-5.2.0.tgz#0e51344914dbe2fbb5c720b3bc2797c467d2115a" + integrity sha512-aHKihTnKaYUqWvii5DlJDQqVud4Bp2fmfGKIaLRfsb/DzuLyvdFLnI9RKiXaNnVLQ0g6sorQZMwiV102r6UASw== + dependencies: + "@types/jquery" "*" + "@types/dompurify@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-2.0.0.tgz#9616caa5bf2569aea2e4889d4f929d968c081b40" @@ -9,6 +16,13 @@ dependencies: "@types/trusted-types" "*" +"@types/jquery@*": + version "3.3.32" + resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.3.32.tgz#93e27fdc45dd38ee07f2f0acf34b59c1ccee036f" + integrity sha512-UKoof2mnV/X1/Ix2g+V2Ny5sgHjV8nK/UJbiYxuo4zPwzGyFlZ/mp4KaePb2VqQrqJctmcDQNA57buU84/2uIw== + dependencies: + "@types/sizzle" "*" + "@types/jquery@^3.3.31": version "3.3.31" resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.3.31.tgz#27c706e4bf488474e1cb54a71d8303f37c93451b" @@ -258,6 +272,20 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== +bootbox@^5.4.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/bootbox/-/bootbox-5.4.0.tgz#2857a63c270b1b797d62e4c5597e74b497267655" + integrity sha512-GCPrDwZpJsUnqzrto3ZURVafypl13+DAyE3YZx5jR5EIoTDSyREPhr77hlCuPKvM6VvXR5Mh/34W3DYMC7JE9g== + dependencies: + bootstrap "^4.4.0" + jquery "^3.4.1" + popper.js "^1.16.0" + +bootstrap@^4.4.0: + version "4.4.1" + resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.4.1.tgz#8582960eea0c5cd2bede84d8b0baf3789c3e8b01" + integrity sha512-tbx5cHubwE6e2ZG7nqM3g/FZ5PQEDMWmMGNrCUBVRPHXTJaH7CBDdsLeu3eCh3B1tzAxTnAbtmrzvWEvT2NNEA== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -1689,6 +1717,11 @@ isobject@^3.0.0, isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= +jquery@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2" + integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw== + js-yaml@~3.13.0: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" @@ -2215,6 +2248,11 @@ pinkie@^2.0.0: resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= +popper.js@^1.16.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b" + integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ== + posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" diff --git a/chatto/src/main/resources/templates/chat.html b/chatto/src/main/resources/templates/chat.html index 054b907..509de84 100644 --- a/chatto/src/main/resources/templates/chat.html +++ b/chatto/src/main/resources/templates/chat.html @@ -256,7 +256,7 @@
@@ -273,7 +273,7 @@
diff --git a/chatto/src/main/resources/templates/fragments/head.html b/chatto/src/main/resources/templates/fragments/head.html index 768e113..430488d 100644 --- a/chatto/src/main/resources/templates/fragments/head.html +++ b/chatto/src/main/resources/templates/fragments/head.html @@ -14,6 +14,8 @@ + +