├── deploy ├── deploy_pre.sh ├── deploy_prod.sh ├── publish_pre.sh └── publish_prod.sh ├── src ├── assets │ ├── .gitkeep │ ├── sounds │ │ ├── Carme.mp3 │ │ ├── pling.mp3 │ │ └── justsaying.mp3 │ ├── images │ │ ├── file-alt-solid.png │ │ ├── website_mockup.jpg │ │ ├── f21ico-done.svg │ │ ├── f21ico-done_all.svg │ │ ├── f21ico-schedule.svg │ │ ├── icons │ │ │ └── no-image.svg │ │ ├── chat_human_avatar.svg │ │ └── avatar_bot_tiledesk.svg │ ├── twp │ │ └── tiledesk_widget_files │ │ │ ├── logo.png │ │ │ ├── twitter.png │ │ │ ├── facebook.png │ │ │ ├── linkedin.png │ │ │ ├── telegram.png │ │ │ ├── whatsapp.png │ │ │ ├── logo-black.png │ │ │ ├── logo-mobile.png │ │ │ ├── logo-short.png │ │ │ ├── logo@2x-black.png │ │ │ └── icon │ └── styles │ │ └── tiledesk_v1.scss ├── app │ ├── sass │ │ ├── _mixins.scss │ │ ├── styles.scss │ │ └── _variables.scss │ ├── component │ │ ├── form │ │ │ ├── inputs │ │ │ │ ├── form-select │ │ │ │ │ ├── form-select.component.scss │ │ │ │ │ ├── form-select.component.html │ │ │ │ │ ├── form-select.component.ts │ │ │ │ │ └── form-select.component.spec.ts │ │ │ │ ├── form-radio-button │ │ │ │ │ ├── form-radio-button.component.scss │ │ │ │ │ ├── form-radio-button.component.ts │ │ │ │ │ ├── form-radio-button.component.html │ │ │ │ │ └── form-radio-button.component.spec.ts │ │ │ │ ├── form-label │ │ │ │ │ ├── form-label.component.html │ │ │ │ │ ├── form-label.component.scss │ │ │ │ │ ├── form-label.component.ts │ │ │ │ │ └── form-label.component.spec.ts │ │ │ │ ├── form-checkbox │ │ │ │ │ ├── form-checkbox.component.html │ │ │ │ │ ├── form-checkbox.component.spec.ts │ │ │ │ │ └── form-checkbox.component.ts │ │ │ │ ├── form-textarea │ │ │ │ │ ├── form-textarea.component.spec.ts │ │ │ │ │ └── form-textarea.component.html │ │ │ │ └── form-text │ │ │ │ │ ├── form-text.component.html │ │ │ │ │ └── form-text.component.spec.ts │ │ │ ├── prechat-form │ │ │ │ └── prechat-form.component.spec.ts │ │ │ └── form-builder │ │ │ │ └── form-builder.component.spec.ts │ │ ├── message │ │ │ ├── html │ │ │ │ ├── html.component.html │ │ │ │ ├── html.component.spec.ts │ │ │ │ └── html.component.ts │ │ │ ├── text │ │ │ │ ├── text.component.html │ │ │ │ ├── text.component.spec.ts │ │ │ │ ├── text.component.scss │ │ │ │ └── text.component.ts │ │ │ ├── buttons │ │ │ │ ├── text-button │ │ │ │ │ ├── text-button.component.html │ │ │ │ │ ├── text-button.component.spec.ts │ │ │ │ │ └── text-button.component.scss │ │ │ │ ├── action-button │ │ │ │ │ ├── action-button.component.html │ │ │ │ │ └── action-button.component.spec.ts │ │ │ │ └── link-button │ │ │ │ │ ├── link-button.component.spec.ts │ │ │ │ │ └── link-button.component.html │ │ │ ├── frame │ │ │ │ ├── frame.component.html │ │ │ │ ├── frame.component.scss │ │ │ │ ├── frame.component.ts │ │ │ │ └── frame.component.spec.ts │ │ │ ├── like-unlike │ │ │ │ ├── like-unlike.component.scss │ │ │ │ ├── like-unlike.component.ts │ │ │ │ └── like-unlike.component.spec.ts │ │ │ ├── image │ │ │ │ ├── image.component.html │ │ │ │ ├── image.component.spec.ts │ │ │ │ └── image.component.scss │ │ │ ├── audio │ │ │ │ ├── audio.component.spec.ts │ │ │ │ └── audio.component.html │ │ │ ├── carousel │ │ │ │ └── carousel.component.spec.ts │ │ │ ├── return-receipt │ │ │ │ ├── return-receipt.component.scss │ │ │ │ ├── return-receipt.component.ts │ │ │ │ └── return-receipt.component.spec.ts │ │ │ ├── avatar │ │ │ │ ├── avatar.component.spec.ts │ │ │ │ ├── avatar.component.html │ │ │ │ └── avatar.component.ts │ │ │ ├── info-message │ │ │ │ ├── info-message.component.html │ │ │ │ ├── info-message.component.ts │ │ │ │ ├── info-message.component.spec.ts │ │ │ │ └── info-message.component.scss │ │ │ └── bubble-message │ │ │ │ └── bubble-message.component.scss │ │ ├── conversation-detail │ │ │ ├── conversation-emojii │ │ │ │ ├── conversation-emojii.component.scss │ │ │ │ ├── conversation-emojii.component.html │ │ │ │ ├── conversation-emojii.component.spec.ts │ │ │ │ └── conversation-emojii.component.ts │ │ │ ├── conversation-audio-recorder │ │ │ │ ├── conversation-audio-recorder.component.spec.ts │ │ │ │ └── conversation-audio-recorder.component.html │ │ │ ├── conversation-internal-frame │ │ │ │ └── conversation-internal-frame.component.spec.ts │ │ │ ├── conversation-preview │ │ │ │ └── conversation-preview.component.spec.ts │ │ │ └── conversation-header │ │ │ │ └── conversation-header.component.spec.ts │ │ ├── send-button │ │ │ ├── send-button.component.ts │ │ │ ├── send-button.component.spec.ts │ │ │ ├── send-button.component.scss │ │ │ └── send-button.component.html │ │ ├── error-alert │ │ │ ├── error-alert.component.html │ │ │ ├── error-alert.component.spec.ts │ │ │ ├── error-alert.component.scss │ │ │ └── error-alert.component.ts │ │ ├── menu-options │ │ │ ├── menu-options.component.spec.ts │ │ │ └── menu-options.component.ts │ │ ├── message-attachment │ │ │ ├── message-attachment.component.scss │ │ │ ├── message-attachment.component.spec.ts │ │ │ └── message-attachment.component.html │ │ ├── launcher-button │ │ │ ├── launcher-button.component.spec.ts │ │ │ └── launcher-button.component.scss │ │ ├── eyeeye-catcher-card │ │ │ └── eyeeye-catcher-card.component.spec.ts │ │ ├── selection-department │ │ │ └── selection-department.component.spec.ts │ │ ├── star-rating-widget │ │ │ └── star-rating-widget.component.spec.ts │ │ ├── last-message │ │ │ └── last-message.component.spec.ts │ │ ├── home │ │ │ └── home.component.spec.ts │ │ ├── list-conversations │ │ │ └── list-conversations.component.spec.ts │ │ └── list-all-conversations │ │ │ └── list-all-conversations.component.spec.ts │ ├── pipe │ │ ├── marked.pipe.spec.ts │ │ ├── date-ago.pipe.spec.ts │ │ ├── html-entites-encode.pipe.spec.ts │ │ ├── html-entities-encode.pipe.ts │ │ ├── safe-html.pipe.ts │ │ ├── safe-html.pipe.spec.ts │ │ └── date-ago.pipe.ts │ ├── directives │ │ └── tooltip.directive.spec.ts │ ├── app-routing.module.ts │ ├── providers │ │ ├── brand.service.spec.ts │ │ ├── events.service.spec.ts │ │ ├── app-config.service.spec.ts │ │ ├── waiting.service.spec.ts │ │ ├── star-rating-widget.service.spec.ts │ │ ├── translator.service.spec.ts │ │ ├── global-settings.service.spec.ts │ │ ├── waiting.service.ts │ │ └── app-config.service.ts │ ├── modals │ │ └── confirm-close │ │ │ ├── confirm-close.component.spec.ts │ │ │ ├── confirm-close.component.scss │ │ │ └── confirm-close.component.ts │ └── utils │ │ ├── utils-resources.ts │ │ └── BrandResources.ts ├── favicon.ico ├── chat21-core │ ├── models │ │ ├── upload.ts │ │ ├── group.ts │ │ ├── formArray.ts │ │ ├── user.ts │ │ ├── message.ts │ │ └── conversation.ts │ ├── providers │ │ ├── abstract │ │ │ ├── conversation-handler-builder.service.ts │ │ │ ├── logger.service.spec.ts │ │ │ ├── typing.service.spec.ts │ │ │ ├── upload.service.spec.ts │ │ │ ├── presence.service.spec.ts │ │ │ ├── app-storage.service.spec.ts │ │ │ ├── image-repo.service.spec.ts │ │ │ ├── groups-handler.service.spec.ts │ │ │ ├── messagingAuth.service.spec.ts │ │ │ ├── notifications.service.spec.ts │ │ │ ├── conversation-handler.service.spec.ts │ │ │ ├── conversations-handler.service.spec.ts │ │ │ ├── logger.service.ts │ │ │ ├── conversation-handler-builder.service.spec.ts │ │ │ ├── archivedconversations-handler.service.spec.ts │ │ │ ├── app-storage.service.ts │ │ │ ├── image-repo.service.ts │ │ │ ├── notifications.service.ts │ │ │ ├── typing.service.ts │ │ │ ├── presence.service.ts │ │ │ ├── upload.service.ts │ │ │ ├── groups-handler.service.ts │ │ │ ├── messagingAuth.service.ts │ │ │ ├── conversation-handler.service.ts │ │ │ ├── archivedconversations-handler.service.ts │ │ │ └── conversations-handler.service.ts │ │ ├── scripts │ │ │ └── script.service.spec.ts │ │ ├── logger │ │ │ └── loggerInstance.ts │ │ ├── custom-translate.service.spec.ts │ │ ├── firebase │ │ │ ├── firebase-conversation-handler-builder.service.ts │ │ │ ├── firebase-init-service.ts │ │ │ └── firebase-image-repo.ts │ │ ├── mqtt │ │ │ ├── mqtt-conversation-handler-builder.service.ts │ │ │ └── chat-service.ts │ │ ├── tiledesk │ │ │ └── tiledesk-requests.service.spec.ts │ │ ├── native │ │ │ └── native-image-repo.ts │ │ └── custom-translate.service.ts │ └── utils │ │ ├── user-typing │ │ ├── user-typing.component.html │ │ ├── user-typing.component.spec.ts │ │ └── user-typing.component.ts │ │ └── utils-user.ts ├── zone-flag.ts ├── models │ ├── upload.ts │ ├── contact.ts │ ├── userAgent.ts │ ├── department.ts │ ├── rule.ts │ └── conversation.ts ├── styles.scss ├── main.ts ├── index.html ├── test.ts ├── widget-config.json ├── widget-config-template.json └── environments │ ├── environment.ts │ ├── environment.prod.ts │ └── environment.pre.ts ├── .vscode └── settings.json ├── bin └── chat21-web-widget ├── docs ├── authuser.png ├── tiledesk-project-settings.png ├── tiledesk-dashboard-widget-screenshots.png └── autoinstall.md ├── current_version.ts ├── current_version.ts-e ├── firebase.json ├── .github ├── ISSUE_TEMPLATE │ ├── custom.md │ ├── feature_request.md │ └── bug_report.md └── workflows │ ├── docker-community-push-latest.yml │ ├── docker-image-tag-community-tag-push.yml │ └── build.yml ├── e2e ├── tsconfig.json └── src │ ├── app.po.ts │ └── app.e2e-spec.ts ├── .editorconfig ├── tsconfig.spec.json ├── tsconfig.app.json ├── sonar-project.properties ├── server.js ├── env.sample ├── nginx.conf ├── tsconfig.json ├── protractor.conf.js ├── .gitignore ├── .README.md.swp ├── deploy_prod.sh ├── .npmignore ├── deploy_beta.sh ├── LICENSE ├── karma.conf.js ├── Dockerfile ├── deploy_amazon_prod.sh └── 404.html /deploy/deploy_pre.sh: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /deploy/deploy_prod.sh: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /deploy/publish_pre.sh: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /deploy/publish_prod.sh: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/sass/_mixins.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true 3 | } -------------------------------------------------------------------------------- /src/app/component/form/inputs/form-select/form-select.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bin/chat21-web-widget: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('../server'); 3 | -------------------------------------------------------------------------------- /src/app/component/form/inputs/form-radio-button/form-radio-button.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/sass/styles.scss: -------------------------------------------------------------------------------- 1 | @import './variables'; 2 | @import './mixins'; 3 | 4 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tiledesk/chat21-web-widget/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /docs/authuser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tiledesk/chat21-web-widget/HEAD/docs/authuser.png -------------------------------------------------------------------------------- /current_version.ts: -------------------------------------------------------------------------------- 1 | export const CURR_VER_DEV = 'test.002'; 2 | export const CURR_VER_PROD = '1.009'; 3 | -------------------------------------------------------------------------------- /current_version.ts-e: -------------------------------------------------------------------------------- 1 | export const CURR_VER_DEV = 'test.001'; 2 | export const CURR_VER_PROD = '1.009'; 3 | -------------------------------------------------------------------------------- /src/app/component/form/inputs/form-select/form-select.component.html: -------------------------------------------------------------------------------- 1 |

2 | select works! 3 |

4 | -------------------------------------------------------------------------------- /src/assets/sounds/Carme.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tiledesk/chat21-web-widget/HEAD/src/assets/sounds/Carme.mp3 -------------------------------------------------------------------------------- /src/assets/sounds/pling.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tiledesk/chat21-web-widget/HEAD/src/assets/sounds/pling.mp3 -------------------------------------------------------------------------------- /src/app/component/message/html/html.component.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /src/assets/sounds/justsaying.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tiledesk/chat21-web-widget/HEAD/src/assets/sounds/justsaying.mp3 -------------------------------------------------------------------------------- /docs/tiledesk-project-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tiledesk/chat21-web-widget/HEAD/docs/tiledesk-project-settings.png -------------------------------------------------------------------------------- /src/assets/images/file-alt-solid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tiledesk/chat21-web-widget/HEAD/src/assets/images/file-alt-solid.png -------------------------------------------------------------------------------- /src/assets/images/website_mockup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tiledesk/chat21-web-widget/HEAD/src/assets/images/website_mockup.jpg -------------------------------------------------------------------------------- /src/assets/twp/tiledesk_widget_files/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tiledesk/chat21-web-widget/HEAD/src/assets/twp/tiledesk_widget_files/logo.png -------------------------------------------------------------------------------- /docs/tiledesk-dashboard-widget-screenshots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tiledesk/chat21-web-widget/HEAD/docs/tiledesk-dashboard-widget-screenshots.png -------------------------------------------------------------------------------- /src/assets/twp/tiledesk_widget_files/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tiledesk/chat21-web-widget/HEAD/src/assets/twp/tiledesk_widget_files/twitter.png -------------------------------------------------------------------------------- /src/assets/twp/tiledesk_widget_files/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tiledesk/chat21-web-widget/HEAD/src/assets/twp/tiledesk_widget_files/facebook.png -------------------------------------------------------------------------------- /src/assets/twp/tiledesk_widget_files/linkedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tiledesk/chat21-web-widget/HEAD/src/assets/twp/tiledesk_widget_files/linkedin.png -------------------------------------------------------------------------------- /src/assets/twp/tiledesk_widget_files/telegram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tiledesk/chat21-web-widget/HEAD/src/assets/twp/tiledesk_widget_files/telegram.png -------------------------------------------------------------------------------- /src/assets/twp/tiledesk_widget_files/whatsapp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tiledesk/chat21-web-widget/HEAD/src/assets/twp/tiledesk_widget_files/whatsapp.png -------------------------------------------------------------------------------- /src/assets/twp/tiledesk_widget_files/logo-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tiledesk/chat21-web-widget/HEAD/src/assets/twp/tiledesk_widget_files/logo-black.png -------------------------------------------------------------------------------- /src/assets/twp/tiledesk_widget_files/logo-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tiledesk/chat21-web-widget/HEAD/src/assets/twp/tiledesk_widget_files/logo-mobile.png -------------------------------------------------------------------------------- /src/assets/twp/tiledesk_widget_files/logo-short.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tiledesk/chat21-web-widget/HEAD/src/assets/twp/tiledesk_widget_files/logo-short.png -------------------------------------------------------------------------------- /src/assets/twp/tiledesk_widget_files/logo@2x-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tiledesk/chat21-web-widget/HEAD/src/assets/twp/tiledesk_widget_files/logo@2x-black.png -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "dist", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/app/component/message/text/text.component.html: -------------------------------------------------------------------------------- 1 |

4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/autoinstall.md: -------------------------------------------------------------------------------- 1 | https://medium.com/@svsh227/add-node-server-with-angular-app-run-angularjs-app-with-node-server-a3c472ef8997 2 | 3 | 4 | https://blog.devget.net/development/how-to-deploy-angular-6-project-to-heroku/ -------------------------------------------------------------------------------- /src/app/component/form/inputs/form-label/form-label.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /src/app/component/conversation-detail/conversation-emojii/conversation-emojii.component.scss: -------------------------------------------------------------------------------- 1 | .emoji-mart { 2 | position: absolute; 3 | // bottom: 40px; 4 | // left: 10px; 5 | border: none; 6 | margin: -2px -2px 0px; 7 | 8 | } -------------------------------------------------------------------------------- /src/app/pipe/marked.pipe.spec.ts: -------------------------------------------------------------------------------- 1 | import { MarkedPipe } from './marked.pipe'; 2 | 3 | describe('MarkedPipe', () => { 4 | it('create an instance', () => { 5 | const pipe = new MarkedPipe(); 6 | expect(pipe).toBeTruthy(); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /src/app/pipe/date-ago.pipe.spec.ts: -------------------------------------------------------------------------------- 1 | import { DateAgoPipe } from './date-ago.pipe'; 2 | 3 | describe('DateAgoPipe', () => { 4 | it('create an instance', () => { 5 | const pipe = new DateAgoPipe(); 6 | expect(pipe).toBeTruthy(); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "target": "es2018", 6 | "types": [ 7 | "jasmine", 8 | "jasminewd2", 9 | "node" 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/chat21-core/models/upload.ts: -------------------------------------------------------------------------------- 1 | export class UploadModel { 2 | $key: string; 3 | file: File; 4 | name: string; 5 | url: string; 6 | progress: number; 7 | createdAt: Date = new Date(); 8 | constructor(file: File) { 9 | this.file = file; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/zone-flag.ts: -------------------------------------------------------------------------------- 1 | (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['message', 'scroll','readystatechange', 'visibilitychange', 'click', 'keypress', 'keydown', 'blur']; 2 | 3 | // disable patching specified eventNames 4 | // (window as any).__zone_symbol__UNPATCHED_EVENTS = ['Timeout']; -------------------------------------------------------------------------------- /src/models/upload.ts: -------------------------------------------------------------------------------- 1 | export class UploadModel { 2 | $key: string; 3 | file: File; 4 | name: string; 5 | url: string; 6 | progress: number; 7 | createdAt: Date = new Date(); 8 | 9 | constructor(file: File) { 10 | this.file = file; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | // /* You can add global styles to this file, and also import other style files */ 2 | // /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ 3 | 4 | 5 | html, body { height: 100%; } 6 | body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } 7 | -------------------------------------------------------------------------------- /src/app/directives/tooltip.directive.spec.ts: -------------------------------------------------------------------------------- 1 | import { TooltipDirective } from './tooltip.directive'; 2 | 3 | describe('TooltipDirective', () => { 4 | // it('should create an instance', () => { 5 | // const directive = new TooltipDirective(); 6 | // expect(directive).toBeTruthy(); 7 | // }); 8 | }); 9 | -------------------------------------------------------------------------------- /src/app/pipe/html-entites-encode.pipe.spec.ts: -------------------------------------------------------------------------------- 1 | import { HtmlEntitiesEncodePipe } from './html-entities-encode.pipe'; 2 | 3 | describe('HtmlEntitiesEncodePipe', () => { 4 | it('create an instance', () => { 5 | const pipe = new HtmlEntitiesEncodePipe(); 6 | expect(pipe).toBeTruthy(); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /src/models/contact.ts: -------------------------------------------------------------------------------- 1 | export class ContactModel { 2 | constructor( 3 | public uid: string, 4 | public email: string, 5 | public firstname: string, 6 | public imageurl: string, 7 | public lastname: string, 8 | public timestamp: string, 9 | ) { } 10 | } 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css('app-root h1')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | 5 | const routes: Routes = []; 6 | 7 | @NgModule({ 8 | imports: [RouterModule.forRoot(routes)], 9 | exports: [RouterModule] 10 | }) 11 | export class AppRoutingModule { } 12 | -------------------------------------------------------------------------------- /src/chat21-core/providers/abstract/conversation-handler-builder.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | // @Injectable({ 4 | // providedIn: 'root' 5 | // }) 6 | @Injectable() 7 | export abstract class ConversationHandlerBuilderService { 8 | 9 | constructor() { } 10 | 11 | abstract build(): any; 12 | } 13 | -------------------------------------------------------------------------------- /src/assets/images/f21ico-done.svg: -------------------------------------------------------------------------------- 1 | ic_done -------------------------------------------------------------------------------- /src/app/component/message/buttons/text-button/text-button.component.html: -------------------------------------------------------------------------------- 1 | 2 |
7 | {{button?.value}} 8 |
9 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/models/userAgent.ts: -------------------------------------------------------------------------------- 1 | export interface UserAgent { 2 | id: string; 3 | firstname: string; 4 | updatedAt: any; 5 | createdAt: any; 6 | id_project: string; 7 | id_user: string; 8 | user_available?: boolean; 9 | role: string; 10 | createdBy: string; 11 | is_group_member: boolean; 12 | __v: any; 13 | imageurl: string; 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": ["node"], 6 | "resolveJsonModule": true, 7 | "allowSyntheticDefaultImports": true, 8 | }, 9 | "files": [ 10 | "src/main.ts", 11 | "src/polyfills.ts" 12 | ], 13 | "include": [ 14 | "src/**/*.d.ts" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/app/component/message/buttons/action-button/action-button.component.html: -------------------------------------------------------------------------------- 1 |
6 | {{button?.value}} 7 |
8 | -------------------------------------------------------------------------------- /src/models/department.ts: -------------------------------------------------------------------------------- 1 | export class DepartmentModel { 2 | constructor( 3 | public appId: string, 4 | public createdAt: string, 5 | public createdBy: string, 6 | public name: string, 7 | public updatedAt: string, 8 | public _id: string, 9 | public offline_msg: string, 10 | public online_msg: string 11 | ) { } 12 | } 13 | -------------------------------------------------------------------------------- /src/app/providers/brand.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { BrandService } from './brand.service'; 4 | 5 | describe('BrandService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: BrandService = TestBed.get(BrandService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/chat21-core/models/group.ts: -------------------------------------------------------------------------------- 1 | export class GroupModel { 2 | constructor( 3 | public uid: string, 4 | public createdOn: any, 5 | public iconURL: string, 6 | public members: any[], 7 | public membersinfo: any[], 8 | public name: string, 9 | public owner: string, 10 | public color: string, 11 | public avatar: string 12 | ) { } 13 | } 14 | -------------------------------------------------------------------------------- /src/app/component/form/inputs/form-select/form-select.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'chat-form-select', 5 | templateUrl: './form-select.component.html', 6 | styleUrls: ['./form-select.component.scss'] 7 | }) 8 | export class FormSelectComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/app/component/message/frame/frame.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 8 |
9 | -------------------------------------------------------------------------------- /src/models/rule.ts: -------------------------------------------------------------------------------- 1 | export interface IRules { 2 | [id: string]:Rule[] 3 | } 4 | 5 | export interface Rule { 6 | uid: string, 7 | name: string, 8 | description?: string, 9 | when: { 10 | regexOption: string, 11 | text: string, 12 | urlMatches: string, 13 | triggerEvery: number 14 | }, 15 | do: [ 16 | // {wait: number}, 17 | {message: any} 18 | ] 19 | } -------------------------------------------------------------------------------- /src/assets/images/f21ico-done_all.svg: -------------------------------------------------------------------------------- 1 | ic_done_all -------------------------------------------------------------------------------- /src/chat21-core/utils/user-typing/user-typing.component.html: -------------------------------------------------------------------------------- 1 |
2 | {{nameUserTypingNow}} 3 | {{ translationMap?.get('LABEL_WRITING') }} 4 |
5 |
6 |
7 |
8 |
9 |
10 | -------------------------------------------------------------------------------- /src/chat21-core/providers/scripts/script.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { ScriptService } from './script.service'; 4 | 5 | describe('ScriptService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: ScriptService = TestBed.get(ScriptService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/assets/images/f21ico-schedule.svg: -------------------------------------------------------------------------------- 1 | ic_schedule -------------------------------------------------------------------------------- /src/app/component/message/like-unlike/like-unlike.component.scss: -------------------------------------------------------------------------------- 1 | :host{ 2 | // --borderRadius: #{$border-radius-bubble-message}; 3 | --borderRadius: 25px; 4 | } 5 | 6 | .like-container{ 7 | background-color: #f6f7fb; 8 | border-radius: var( --borderRadius); 9 | } 10 | 11 | .menu{ 12 | display: flex; 13 | padding: 8px 12px; 14 | border-radius: 16px; 15 | } 16 | 17 | .c21-like, .c21-unlike{ 18 | padding: 2px; 19 | } -------------------------------------------------------------------------------- /src/app/component/form/inputs/form-radio-button/form-radio-button.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'chat-form-radio-button', 5 | templateUrl: './form-radio-button.component.html', 6 | styleUrls: ['./form-radio-button.component.scss'] 7 | }) 8 | export class FormRadioButtonComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/chat21-core/models/formArray.ts: -------------------------------------------------------------------------------- 1 | export class FormArray { 2 | constructor( 3 | public label?: string | {}, 4 | public errorLabel?: string | {}, 5 | public text?: string, 6 | public name?: string, 7 | public type?: string, 8 | public mandatory?: boolean, 9 | public regex?: string, 10 | public value?: any, 11 | public options?: Array, 12 | public tabIndex?: number 13 | ) { } 14 | } -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | import 'zone.js' 7 | 8 | if (environment.production) { 9 | enableProdMode(); 10 | } 11 | 12 | platformBrowserDynamic().bootstrapModule(AppModule) 13 | .catch(err => console.error(err)); 14 | -------------------------------------------------------------------------------- /src/app/component/form/inputs/form-label/form-label.component.scss: -------------------------------------------------------------------------------- 1 | .c21-header-label{ 2 | padding: 10px 0px; 3 | // margin: 0px 12px; 4 | position: relative; 5 | left: 0; 6 | right: 0; 7 | .c21-form-message-field { 8 | padding: 0; 9 | margin: 0; 10 | line-height: 1.2em; 11 | color: #616161; 12 | font-size: 1.5em; 13 | text-align: center; 14 | word-wrap: break-word; 15 | } 16 | } -------------------------------------------------------------------------------- /src/app/providers/events.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { EventsService } from './events.service'; 4 | 5 | describe('EventsService', () => { 6 | let service: EventsService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(EventsService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/assets/images/icons/no-image.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=Tiledesk_chat21-web-widget 2 | sonar.organization=tiledesk 3 | 4 | # This is the name and version displayed in the SonarCloud UI. 5 | #sonar.projectName=chat21-web-widget 6 | #sonar.projectVersion=1.0 7 | 8 | # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. 9 | #sonar.sources=. 10 | 11 | # Encoding of the source code. Default is default system encoding 12 | #sonar.sourceEncoding=UTF-8 13 | -------------------------------------------------------------------------------- /src/chat21-core/models/user.ts: -------------------------------------------------------------------------------- 1 | export class UserModel { 2 | constructor( 3 | public uid: string, 4 | public email?: string, 5 | public firstname?: string, 6 | public lastname?: string, 7 | public fullname?: string, 8 | public imageurl?: string, 9 | public avatar?: string, 10 | public color?: string, 11 | public checked?: boolean, 12 | public online?: boolean, 13 | public lastConnection?: string, 14 | public decoded?: any 15 | ) { } 16 | } 17 | -------------------------------------------------------------------------------- /src/chat21-core/providers/abstract/logger.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { LoggerService } from './logger.service'; 3 | 4 | 5 | describe('LoggerService', () => { 6 | beforeEach(() => 7 | TestBed.configureTestingModule({ 8 | providers: [LoggerService] 9 | }) 10 | ); 11 | 12 | it('should be created', () => { 13 | const service: LoggerService = TestBed.get(LoggerService); 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/chat21-core/providers/abstract/typing.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { TypingService } from './typing.service'; 4 | 5 | describe('TypingService', () => { 6 | beforeEach(() => 7 | TestBed.configureTestingModule({ 8 | providers: [TypingService] 9 | }) 10 | ); 11 | 12 | it('should be created', () => { 13 | const service: TypingService = TestBed.get(TypingService); 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/app/pipe/html-entities-encode.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | import { htmlEntities, replaceEndOfLine } from 'src/chat21-core/utils/utils'; 3 | 4 | @Pipe({ 5 | name: 'htmlEntitiesEncode' 6 | }) 7 | 8 | export class HtmlEntitiesEncodePipe implements PipeTransform { 9 | 10 | transform(text: any, args?: any): any { 11 | text = htmlEntities(text); 12 | text = replaceEndOfLine(text); 13 | text = text.trim(); 14 | return text; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/chat21-core/providers/abstract/upload.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { UploadService } from './upload.service'; 4 | 5 | describe('UploadService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [UploadService] 9 | }); 10 | }); 11 | 12 | it('should be created', () => { 13 | const service: UploadService = TestBed.get(UploadService); 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/chat21-core/providers/abstract/presence.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { PresenceService } from './presence.service'; 4 | 5 | describe('PresenceService', () => { 6 | beforeEach(() => 7 | TestBed.configureTestingModule({ 8 | providers: [PresenceService] 9 | }) 10 | ); 11 | 12 | it('should be created', () => { 13 | const service: PresenceService = TestBed.get(PresenceService); 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/chat21-core/providers/abstract/app-storage.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { AppStorageService } from './app-storage.service'; 4 | 5 | describe('AppStorageService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [AppStorageService] 9 | }); 10 | }); 11 | 12 | it('should be created', inject([AppStorageService], (service: AppStorageService) => { 13 | expect(service).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /src/chat21-core/providers/abstract/image-repo.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { ImageRepoService } from './image-repo.service'; 4 | 5 | describe('ImageRepoService', () => { 6 | beforeEach(() => 7 | TestBed.configureTestingModule({ 8 | providers: [ImageRepoService] 9 | }) 10 | ); 11 | 12 | it('should be created', () => { 13 | const service: ImageRepoService = TestBed.get(ImageRepoService); 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/chat21-core/providers/abstract/groups-handler.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { GroupsHandlerService } from './groups-handler.service'; 4 | 5 | describe('GroupsHandlerService', () => { 6 | beforeEach(() => 7 | TestBed.configureTestingModule({ 8 | providers: [GroupsHandlerService] 9 | }) 10 | ); 11 | 12 | it('should be created', () => { 13 | const service: GroupsHandlerService = TestBed.get(GroupsHandlerService); 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/chat21-core/providers/abstract/messagingAuth.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { MessagingAuthService } from './messagingAuth.service'; 4 | 5 | describe('MessagingAuthService', () => { 6 | beforeEach(() => 7 | TestBed.configureTestingModule({ 8 | providers: [MessagingAuthService] 9 | }) 10 | ); 11 | 12 | it('should be created', () => { 13 | const service: MessagingAuthService = TestBed.get(MessagingAuthService); 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/chat21-core/providers/abstract/notifications.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { NotificationsService } from './notifications.service'; 4 | 5 | describe('NotificationsService', () => { 6 | beforeEach(() => 7 | TestBed.configureTestingModule({ 8 | providers: [NotificationsService] 9 | }) 10 | ); 11 | 12 | it('should be created', () => { 13 | const service: NotificationsService = TestBed.get(NotificationsService); 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | //Install express server 2 | const express = require('express'); 3 | const path = require('path'); 4 | 5 | const app = express(); 6 | 7 | // Serve only the static files form the dist directory 8 | app.use(express.static(__dirname + '/dist')); 9 | 10 | app.get('/*', function(req,res) { 11 | console.log("other res"); 12 | res.sendFile(path.join(__dirname,'/dist/index.html')); 13 | }); 14 | 15 | // Start the app by listening on the default Heroku port 16 | let listener = app.listen(process.env.PORT || 4200, function(){ 17 | }); 18 | -------------------------------------------------------------------------------- /src/app/component/conversation-detail/conversation-emojii/conversation-emojii.component.html: -------------------------------------------------------------------------------- 1 | 11 | 12 | -------------------------------------------------------------------------------- /src/app/component/form/inputs/form-radio-button/form-radio-button.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 6 |
7 |
8 | 9 | 12 |
13 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Tilechat Widget 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/app/pipe/safe-html.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | import { DomSanitizer } from '@angular/platform-browser'; 3 | 4 | @Pipe({ 5 | name: 'safeHtml' 6 | }) 7 | export class SafeHtmlPipe implements PipeTransform { 8 | 9 | constructor(protected sanitizer: DomSanitizer) {} 10 | 11 | transform(value: any, args?: any): any { 12 | // console.log('SafeHtmlPipe html ', html) 13 | // return this.sanitizer.bypassSecurityTrustResourceUrl(html); 14 | return this.sanitizer.bypassSecurityTrustHtml(value);; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/chat21-core/providers/abstract/conversation-handler.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { ConversationHandlerService } from './conversation-handler.service'; 4 | 5 | describe('ConversationHandlerService', () => { 6 | beforeEach(() => 7 | TestBed.configureTestingModule({ 8 | providers: [ConversationHandlerService] 9 | }) 10 | ); 11 | 12 | it('should be created', () => { 13 | const service: ConversationHandlerService = TestBed.get(ConversationHandlerService); 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/chat21-core/providers/logger/loggerInstance.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core' 2 | import { LoggerService } from '../abstract/logger.service'; 3 | import { CustomLogger } from './customLogger'; 4 | 5 | @Injectable() 6 | export class LoggerInstance { 7 | 8 | 9 | //private variables 10 | private static instance: LoggerService; 11 | 12 | static getInstance() { 13 | return LoggerInstance.instance; 14 | } 15 | 16 | static setInstance(loggerService: LoggerService) { 17 | LoggerInstance.instance = loggerService 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/chat21-core/providers/abstract/conversations-handler.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { ConversationsHandlerService } from './conversations-handler.service'; 4 | 5 | describe('ConversationsHandlerService', () => { 6 | beforeEach(() => 7 | TestBed.configureTestingModule({ 8 | providers: [ConversationsHandlerService] 9 | }) 10 | ); 11 | 12 | it('should be created', () => { 13 | const service: ConversationsHandlerService = TestBed.get(ConversationsHandlerService); 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/chat21-core/providers/abstract/logger.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | // @Injectable({ 4 | // providedIn: 'root' 5 | // }) 6 | @Injectable() 7 | export abstract class LoggerService { 8 | 9 | constructor() { } 10 | 11 | abstract setLoggerConfig(isLogEnabled: boolean, logLevel: string); 12 | abstract getLoggerConfig(): {isLogEnabled: boolean, logLevel: number}; 13 | abstract debug(...message: any[]) 14 | abstract log(...message: any[]) 15 | abstract warn(...message: any[]) 16 | abstract info(...message: any[]) 17 | abstract error(...message: any[]) 18 | } 19 | -------------------------------------------------------------------------------- /src/app/component/message/image/image.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 11 | 12 |
13 | -------------------------------------------------------------------------------- /env.sample: -------------------------------------------------------------------------------- 1 | CHAT21_ENGINE=mqtt 2 | UPLOAD_ENGINE=native 3 | PUSH_ENGINE=none 4 | TENANT=tilechat 5 | FILE_UPLOAD_ACCEPT=*/* 6 | LOG_LEVEL=INFO 7 | TRANSLATIONS_URL=CHANGEIT 8 | FIREBASE_APIKEY=CHANGEIT 9 | FIREBASE_AUTHDOMAIN=CHANGEIT.firebaseapp.com 10 | FIREBASE_DATABASEURL=https://CHANGEIT.firebaseio.com 11 | FIREBASE_PROJECT_ID=CHANGEIT 12 | FIREBASE_STORAGEBUCKET=CHANGEIT.appspot.com 13 | FIREBASE_APP_ID=CHANGEIT 14 | FIREBASE_MESSAGINGSENDERID=******** 15 | MQTT_APPID=tilechat 16 | MQTT_ENDPOINT=CHANGEIT 17 | MQTT_APIENDPOINT=CHANGEIT 18 | API_URL=CHANGEIT 19 | API_BASEIMAGE_URL=CHANGEIT 20 | ENBED_JS=true 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/app/component/send-button/send-button.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, OnInit, Output } from '@angular/core'; 2 | import { Globals } from '../../utils/globals'; 3 | 4 | @Component({ 5 | selector: 'chat-send-button', 6 | templateUrl: './send-button.component.html', 7 | styleUrls: ['./send-button.component.scss'] 8 | }) 9 | export class SendButtonComponent implements OnInit { 10 | 11 | @Output() onSendButtonClicked = new EventEmitter() 12 | constructor(public g: Globals) { } 13 | 14 | ngOnInit() { 15 | } 16 | 17 | onSendPressed(event){ 18 | this.onSendButtonClicked.emit(true) 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/app/pipe/safe-html.pipe.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { BrowserModule, DomSanitizer } from '@angular/platform-browser'; 3 | import { SafeHtmlPipe } from './safe-html.pipe'; 4 | 5 | describe('SafeHtmlPipe', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | imports: [ 9 | BrowserModule 10 | ], 11 | providers: [] 12 | }); 13 | }); 14 | 15 | it('create an instance', () => { 16 | const domSanitizer = TestBed.inject(DomSanitizer); 17 | const pipe = new SafeHtmlPipe(domSanitizer); 18 | expect(pipe).toBeTruthy(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/chat21-core/providers/abstract/conversation-handler-builder.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { ConversationHandlerBuilderService } from './conversation-handler-builder.service'; 4 | 5 | describe('ConversationHandlerBuilderService', () => { 6 | beforeEach(() => 7 | TestBed.configureTestingModule({ 8 | providers: [ConversationHandlerBuilderService] 9 | }) 10 | ); 11 | 12 | it('should be created', () => { 13 | const service: ConversationHandlerBuilderService = TestBed.get(ConversationHandlerBuilderService); 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/chat21-core/models/message.ts: -------------------------------------------------------------------------------- 1 | export class MessageModel { 2 | constructor( 3 | public uid: string, 4 | public language: string, 5 | public recipient: string, 6 | public recipient_fullname: string, 7 | public sender: string, 8 | public sender_fullname: string, 9 | public status: number, 10 | public metadata: any, 11 | public text: string, 12 | public timestamp: any, 13 | public type: string, 14 | public attributes: any, 15 | public channel_type: string, 16 | public isSender: boolean, 17 | public emoticon?: boolean 18 | ) { } 19 | } 20 | -------------------------------------------------------------------------------- /src/chat21-core/providers/custom-translate.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TranslateModule } from '@ngx-translate/core'; 2 | import { TestBed } from '@angular/core/testing'; 3 | 4 | import { CustomTranslateService } from './custom-translate.service'; 5 | 6 | describe('CustomTranslateService', () => { 7 | beforeEach(() => 8 | TestBed.configureTestingModule({ 9 | imports: [ TranslateModule.forRoot()], 10 | providers: [CustomTranslateService] 11 | }) 12 | ); 13 | 14 | it('should be created', () => { 15 | const service: CustomTranslateService = TestBed.get(CustomTranslateService); 16 | expect(service).toBeTruthy(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/chat21-core/providers/abstract/archivedconversations-handler.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { ArchivedConversationsHandlerService } from './archivedconversations-handler.service'; 4 | 5 | describe('ArchivedconversationsHandlerService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [ArchivedConversationsHandlerService] 9 | }); 10 | }); 11 | 12 | it('should be created', () => { 13 | const service: ArchivedConversationsHandlerService = TestBed.get(ArchivedConversationsHandlerService); 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/chat21-core/providers/abstract/app-storage.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable() 4 | export abstract class AppStorageService { 5 | 6 | 7 | constructor() { } 8 | 9 | abstract initialize(storagePrefix: string, persistence: string, projectID?: string): void; 10 | abstract getItem(key: string): any; 11 | abstract setItem(key: string, value: any): void; 12 | /** @deprecated */ 13 | abstract getItemWithoutProjectID(key: string): any; 14 | /** @deprecated */ 15 | abstract setItemWithoutProjectID(key: string, value: any): void; 16 | abstract removeItem(key: string): void; 17 | abstract clear(): void; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/docker-community-push-latest.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image Community latest CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | push_to_registry: 11 | name: Push Docker image to Docker Hub 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | name: Check out the repo 17 | - uses: docker/build-push-action@v1 18 | with: 19 | username: ${{ secrets.DOCKERHUB_USERNAME }} 20 | password: ${{ secrets.DOCKERHUB_TOKEN }} 21 | repository: chat21/chat21-web-widget 22 | push: true 23 | tags: latest 24 | -------------------------------------------------------------------------------- /src/app/providers/app-config.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; 2 | import { TestBed } from '@angular/core/testing'; 3 | import { Globals } from '../utils/globals'; 4 | 5 | import { AppConfigService } from './app-config.service'; 6 | 7 | describe('AppConfigService', () => { 8 | beforeEach(() => TestBed.configureTestingModule({ 9 | imports: [], 10 | providers: [ 11 | Globals, 12 | provideHttpClient(withInterceptorsFromDi()) 13 | ] 14 | })); 15 | 16 | it('should be created', () => { 17 | const service: AppConfigService = TestBed.get(AppConfigService); 18 | expect(service).toBeTruthy(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes 1; 2 | 3 | events { 4 | worker_connections 1024; 5 | } 6 | 7 | http { 8 | server { 9 | listen 80; 10 | server_name localhost; 11 | 12 | root /usr/share/nginx/html; 13 | index index.html index.htm; 14 | include /etc/nginx/mime.types; 15 | 16 | gzip on; 17 | gzip_min_length 1000; 18 | gzip_proxied expired no-cache no-store private auth; 19 | gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; 20 | 21 | location / { 22 | try_files $uri $uri/ /index.html; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/docker-image-tag-community-tag-push.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docker Community image tags 2 | 3 | on: 4 | push: 5 | tags: 6 | - '**' # Push events to every tag including hierarchical tags like 7 | jobs: 8 | 9 | push_to_registry: 10 | name: Push Docker image to Docker Hub 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Check out the repo 14 | uses: actions/checkout@v2 15 | - name: Push to Docker Hub 16 | uses: docker/build-push-action@v1 17 | with: 18 | username: ${{ secrets.DOCKERHUB_USERNAME }} 19 | password: ${{ secrets.DOCKERHUB_TOKEN }} 20 | repository: chat21/chat21-web-widget 21 | push: true 22 | tag_with_ref: true 23 | -------------------------------------------------------------------------------- /src/app/component/error-alert/error-alert.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 | {{ errorMessage }} 7 |
8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /src/app/component/message/audio/audio.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AudioComponent } from './audio.component'; 4 | 5 | describe('AudioTrackComponent', () => { 6 | let component: AudioComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ AudioComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(AudioComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/assets/styles/tiledesk_v1.scss: -------------------------------------------------------------------------------- 1 | // chat-root #chat21-home-component .c21-header{ 2 | // color: rgb(0, 0, 0) !important; 3 | // background-image: linear-gradient(rgb(255, 255, 255), rgba(255, 255, 255, 0.5)) !important; 4 | // height: 252px; 5 | // overflow: hidden; 6 | // margin: 0; 7 | // padding: 0 8 | // } 9 | 10 | // chat-root #tiledesk-container .c21-close-button{ 11 | // width: 50px; 12 | // height: 50px 13 | // } 14 | 15 | // chat-root #tiledesk-container .c21-close-button .c21-close-button-body{ 16 | // width: 51px; 17 | // height: 51px; 18 | // } 19 | 20 | // chat-root #tiledesk-container .c21-close-button .c21-close-button-body svg{ 21 | // fill: rgb(0,0,0) !important 22 | // } 23 | -------------------------------------------------------------------------------- /src/chat21-core/providers/firebase/firebase-conversation-handler-builder.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | // services 3 | import { ConversationHandlerBuilderService } from '../abstract/conversation-handler-builder.service'; 4 | import { FirebaseConversationHandler } from './firebase-conversation-handler'; 5 | 6 | // @Injectable({ 7 | // providedIn: 'root' 8 | // }) 9 | @Injectable() 10 | export class FirebaseConversationHandlerBuilderService extends ConversationHandlerBuilderService { 11 | 12 | constructor() { 13 | super(); 14 | } 15 | 16 | public build(): any { 17 | const conversationHandlerService = new FirebaseConversationHandler(false); 18 | return conversationHandlerService; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('Welcome to widget!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/assets/twp/tiledesk_widget_files/icon: -------------------------------------------------------------------------------- 1 | /* fallback */ 2 | @font-face { 3 | font-family: 'Material Icons Extended'; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: url(https://fonts.gstatic.com/s/materialiconsextended/v53/kJEjBvgX7BgnkSrUwT8UnLVc38YydejYY-oE_LvJHMXBBA.woff2) format('woff2'); 7 | } 8 | 9 | .material-icons-extended { 10 | font-family: 'Material Icons Extended'; 11 | font-weight: normal; 12 | font-style: normal; 13 | font-size: 24px; 14 | line-height: 1; 15 | letter-spacing: normal; 16 | text-transform: none; 17 | display: inline-block; 18 | white-space: nowrap; 19 | word-wrap: normal; 20 | direction: ltr; 21 | -webkit-font-feature-settings: 'liga'; 22 | -webkit-font-smoothing: antialiased; 23 | } 24 | -------------------------------------------------------------------------------- /src/app/component/message/carousel/carousel.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CarouselComponent } from './carousel.component'; 4 | 5 | describe('CarouselComponent', () => { 6 | let component: CarouselComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ CarouselComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(CarouselComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/app/component/message/like-unlike/like-unlike.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { LoggerService } from 'src/chat21-core/providers/abstract/logger.service'; 3 | import { LoggerInstance } from './../../../../chat21-core/providers/logger/loggerInstance'; 4 | 5 | @Component({ 6 | selector: 'chat-like-unlike', 7 | templateUrl: './like-unlike.component.html', 8 | styleUrls: ['./like-unlike.component.scss'] 9 | }) 10 | export class LikeUnlikeComponent implements OnInit { 11 | 12 | private logger: LoggerService = LoggerInstance.getInstance(); 13 | constructor() { } 14 | 15 | ngOnInit(): void { 16 | } 17 | 18 | onClick(icon: string){ 19 | this.logger.debug('[LIKE-UNLIKE] onClick-->', icon) 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | import 'zone.js/testing'; 3 | import { getTestBed } from '@angular/core/testing'; 4 | import { 5 | BrowserDynamicTestingModule, 6 | platformBrowserDynamicTesting 7 | } from '@angular/platform-browser-dynamic/testing'; 8 | 9 | declare const require: any; 10 | 11 | // First, initialize the Angular testing environment. 12 | getTestBed().initTestEnvironment( 13 | BrowserDynamicTestingModule, 14 | platformBrowserDynamicTesting(), { 15 | teardown: { destroyAfterEach: false } 16 | } 17 | ); 18 | // Then we find all the tests. 19 | const context = require.context('./', true, /\.spec\.ts$/); 20 | // And load the modules. 21 | context.keys().map(context); 22 | -------------------------------------------------------------------------------- /src/app/component/form/inputs/form-checkbox/form-checkbox.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 10 | 11 |
12 | {{translationErrorLabelMap.get('LABEL_ERROR_FIELD_REQUIRED')}} 13 |
14 |
15 |
-------------------------------------------------------------------------------- /src/app/component/message/return-receipt/return-receipt.component.scss: -------------------------------------------------------------------------------- 1 | .status-message { 2 | width: 16px; 3 | height: 16px; 4 | margin-left: 4px; 5 | .icon { 6 | width: 13px; 7 | height: 13px; 8 | background-repeat: no-repeat; 9 | background-position: center; 10 | background-size: cover; 11 | overflow-x: hidden; 12 | overflow-y: hidden; 13 | } 14 | .c21-ico-schedule { 15 | background-image: url("../../../../assets/images/f21ico-schedule.svg"); 16 | } 17 | .c21-ico-done { 18 | background-image: url("../../../../assets/images/f21ico-done.svg"); 19 | } 20 | .c21-ico-done_all { 21 | background-image: url("../../../../assets/images/f21ico-done_all.svg"); 22 | } 23 | } -------------------------------------------------------------------------------- /src/models/conversation.ts: -------------------------------------------------------------------------------- 1 | export class ConversationModel { 2 | constructor( 3 | public uid: string, 4 | public attributes: any, 5 | public channel_type: string, 6 | public is_new: boolean, 7 | public last_message_text: string, 8 | public recipient: string, 9 | public recipient_fullname: string, 10 | public sender: string, 11 | public sender_fullname: string, 12 | public status: string, 13 | public timestamp: number, 14 | public type: string, 15 | public time_last_message: string, 16 | public color: string, 17 | public avatar: string, 18 | public image: string, 19 | public badge: number, 20 | public archived: boolean 21 | ) {} 22 | } 23 | -------------------------------------------------------------------------------- /src/app/component/error-alert/error-alert.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ErrorAlertComponent } from './error-alert.component'; 4 | 5 | describe('ErrorAlertComponent', () => { 6 | let component: ErrorAlertComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ErrorAlertComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(ErrorAlertComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/app/component/form/inputs/form-label/form-label.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { FormGroup, FormGroupDirective } from '@angular/forms'; 3 | import { FormArray } from '../../../../../chat21-core/models/formArray'; 4 | 5 | @Component({ 6 | selector: 'chat-form-label', 7 | templateUrl: './form-label.component.html', 8 | styleUrls: ['./form-label.component.scss'] 9 | }) 10 | export class FormLabelComponent implements OnInit { 11 | 12 | @Input() element: FormArray; 13 | @Input() controlName: string; 14 | @Input() hasSubmitted: boolean; 15 | 16 | form: FormGroup; 17 | constructor(private rootFormGroup: FormGroupDirective) { } 18 | 19 | ngOnInit() { 20 | this.form = this.rootFormGroup.control; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/app/modals/confirm-close/confirm-close.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ConfirmCloseComponent } from './confirm-close.component'; 4 | 5 | describe('ConfirmCloseComponent', () => { 6 | let component: ConfirmCloseComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ConfirmCloseComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(ConfirmCloseComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "esModuleInterop": true, 9 | "experimentalDecorators": true, 10 | "module": "es2020", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "ES2022", 14 | "types": [ 15 | "node", 16 | "jasmine" 17 | ], 18 | "typeRoots": [ 19 | "node_modules/@types" 20 | ], 21 | "lib": [ 22 | "es2020", 23 | "dom" 24 | ], 25 | "resolveJsonModule": true, 26 | }, 27 | "skipLibCheck": true, 28 | "angularCompilerOptions": { 29 | "fullTemplateTypeCheck": true, 30 | "strictInjectionParameters": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/chat21-core/utils/user-typing/user-typing.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | import { UserTypingComponent } from './user-typing.component'; 3 | 4 | describe('UserTypingComponent', () => { 5 | let component: UserTypingComponent; 6 | let fixture: ComponentFixture; 7 | 8 | beforeEach(waitForAsync(() => { 9 | TestBed.configureTestingModule({ 10 | declarations: [ UserTypingComponent ], 11 | imports: [] 12 | }).compileComponents(); 13 | 14 | fixture = TestBed.createComponent(UserTypingComponent); 15 | component = fixture.componentInstance; 16 | fixture.detectChanges(); 17 | })); 18 | 19 | it('should create', () => { 20 | expect(component).toBeTruthy(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: 5 | - master # The default branch 6 | - (branch|release)-.* # The other branches to be analyzed 7 | - (features|release)/.* 8 | pull_request: 9 | types: [opened, synchronize, reopened] 10 | jobs: 11 | sonarcloud: 12 | name: SonarCloud 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | with: 17 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis 18 | - name: SonarCloud Scan 19 | uses: SonarSource/sonarcloud-github-action@master 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any 22 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 23 | -------------------------------------------------------------------------------- /src/app/component/send-button/send-button.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SendButtonComponent } from './send-button.component'; 4 | 5 | describe('SendButtonComponent', () => { 6 | let component: SendButtonComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ SendButtonComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SendButtonComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/component/message/like-unlike/like-unlike.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LikeUnlikeComponent } from './like-unlike.component'; 4 | 5 | describe('LikeUnlikeComponent', () => { 6 | let component: LikeUnlikeComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ LikeUnlikeComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(LikeUnlikeComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/providers/waiting.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; 2 | import { AppConfigService } from './app-config.service'; 3 | import { Globals } from './../utils/globals'; 4 | import { TestBed, inject } from '@angular/core/testing'; 5 | 6 | import { WaitingService } from './waiting.service'; 7 | 8 | describe('WaitingService', () => { 9 | beforeEach(() => { 10 | TestBed.configureTestingModule({ 11 | imports: [], 12 | providers: [ 13 | WaitingService, 14 | Globals, 15 | AppConfigService, 16 | provideHttpClient(withInterceptorsFromDi()) 17 | ] 18 | }); 19 | }); 20 | 21 | it('should be created', inject([WaitingService], (service: WaitingService) => { 22 | expect(service).toBeTruthy(); 23 | })); 24 | }); 25 | -------------------------------------------------------------------------------- /src/app/component/form/inputs/form-select/form-select.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | import { FormSelectComponent } from './form-select.component'; 3 | 4 | describe('SelectComponent', () => { 5 | let component: FormSelectComponent; 6 | let fixture: ComponentFixture; 7 | 8 | beforeEach(waitForAsync(() => { 9 | TestBed.configureTestingModule({ 10 | declarations: [ FormSelectComponent ] 11 | }) 12 | .compileComponents(); 13 | })); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(FormSelectComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/app/component/message/buttons/link-button/link-button.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LinkButtonComponent } from './link-button.component'; 4 | 5 | describe('LinkButtonComponent', () => { 6 | let component: LinkButtonComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ LinkButtonComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(LinkButtonComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/component/message/buttons/text-button/text-button.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { TextButtonComponent } from './text-button.component'; 4 | 5 | describe('TextButtonComponent', () => { 6 | let component: TextButtonComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ TextButtonComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(TextButtonComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/assets/images/chat_human_avatar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/app/component/message/return-receipt/return-receipt.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { MSG_STATUS_RETURN_RECEIPT, MSG_STATUS_SENT, MSG_STATUS_SENT_SERVER } from '../../../utils/constants'; 3 | 4 | @Component({ 5 | selector: 'chat-return-receipt', 6 | templateUrl: './return-receipt.component.html', 7 | styleUrls: ['./return-receipt.component.scss'] 8 | }) 9 | export class ReturnReceiptComponent implements OnInit { 10 | 11 | @Input() status: number; 12 | 13 | // ========== begin:: set icon status message 14 | MSG_STATUS_SENT = MSG_STATUS_SENT; 15 | MSG_STATUS_SENT_SERVER = MSG_STATUS_SENT_SERVER; 16 | MSG_STATUS_RETURN_RECEIPT = MSG_STATUS_RETURN_RECEIPT; 17 | // ========== end:: icon status message 18 | 19 | constructor() { } 20 | 21 | ngOnInit() { 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/app/component/error-alert/error-alert.component.scss: -------------------------------------------------------------------------------- 1 | #alert-container{ 2 | /* // z-index: 1051; 3 | // position: fixed; */ 4 | position: relative; 5 | display: flex; 6 | align-items: center; 7 | justify-content: center; 8 | gap: 5px; 9 | border-radius: 3px; 10 | box-shadow: rgba(0, 27, 71, .24) 0px 1px 3px; 11 | background: rgb(14 14 14); 12 | padding: 7px 10px; 13 | bottom: 10px; 14 | width: 90%; 15 | } 16 | 17 | span.material-icons, 18 | span.material-icons-outlined{ 19 | color: rgb(242, 247, 247); 20 | font-size: 20px; 21 | } 22 | 23 | 24 | .alert-content{ 25 | color: rgb(242, 247, 247); 26 | text-align: center; 27 | transition: all 0.15s ease-in-out 0s; 28 | font-size: 11px; 29 | line-height: 18px; 30 | /* // letter-spacing: -0.01em; */ 31 | } 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/app/component/message/buttons/action-button/action-button.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ActionButtonComponent } from './action-button.component'; 4 | 5 | describe('ActionButtonComponent', () => { 6 | let component: ActionButtonComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ActionButtonComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ActionButtonComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/chat21-core/providers/abstract/image-repo.service.ts: -------------------------------------------------------------------------------- 1 | import { environment } from '../../../environments/environment'; 2 | import { Injectable } from '@angular/core'; 3 | 4 | // @Injectable({providedIn: 'root'}) 5 | @Injectable() 6 | export abstract class ImageRepoService { 7 | 8 | 9 | 10 | //params 11 | private DEFAULT_URL: string = environment.baseImageUrl; 12 | private baseImageUrl; 13 | 14 | public setImageBaseUrl(baseUrl): void { 15 | this.baseImageUrl = baseUrl; 16 | } 17 | public getImageBaseUrl(): string { 18 | if (this.baseImageUrl) { 19 | return this.baseImageUrl; 20 | } else { 21 | return this.DEFAULT_URL; 22 | } 23 | } 24 | 25 | // functions 26 | abstract getImagePhotoUrl(uid: string): string; 27 | abstract checkImageExists(uid: string, callback:(exist: boolean)=>void): void; 28 | } 29 | -------------------------------------------------------------------------------- /src/chat21-core/providers/mqtt/mqtt-conversation-handler-builder.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | // services 3 | import { ConversationHandlerBuilderService } from '../abstract/conversation-handler-builder.service'; 4 | import { MQTTConversationHandler } from './mqtt-conversation-handler'; 5 | import { Chat21Service } from './chat-service'; 6 | 7 | // @Injectable({ providedIn: 'root' }) 8 | @Injectable() 9 | export class MQTTConversationHandlerBuilderService extends ConversationHandlerBuilderService { 10 | 11 | constructor( 12 | public chat21Service: Chat21Service 13 | ) { 14 | super(); 15 | } 16 | 17 | public build(): any { 18 | const conversationHandlerService = new MQTTConversationHandler( 19 | this.chat21Service, 20 | true 21 | ); 22 | return conversationHandlerService; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './e2e/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | // print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: 'e2e/tsconfig.e2e.json' 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/app/component/conversation-detail/conversation-emojii/conversation-emojii.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ConversationEmojiiComponent } from './conversation-emojii.component'; 4 | 5 | describe('ConversationEmojiiComponent', () => { 6 | let component: ConversationEmojiiComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ConversationEmojiiComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(ConversationEmojiiComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/app/component/form/inputs/form-radio-button/form-radio-button.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { FormRadioButtonComponent } from './form-radio-button.component'; 4 | 5 | describe('RadioButtonComponent', () => { 6 | let component: FormRadioButtonComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ FormRadioButtonComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(FormRadioButtonComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/chat21-core/models/conversation.ts: -------------------------------------------------------------------------------- 1 | export class ConversationModel { 2 | constructor( 3 | public uid: string, 4 | public attributes: any, 5 | public channel_type: string, 6 | public conversation_with_fullname: string, 7 | public conversation_with: string, 8 | public recipient: string, 9 | public recipient_fullname: string, 10 | public image: string, 11 | public is_new: boolean, 12 | public last_message_text: string, 13 | public text: string, 14 | public sender: string, 15 | public senderAuthInfo: any, 16 | public sender_fullname: string, 17 | public status: string, 18 | public timestamp: string, 19 | public selected: boolean, 20 | public color: string, 21 | public avatar: string, 22 | public archived: boolean, 23 | public type: string, 24 | public sound: boolean 25 | ) { } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/providers/star-rating-widget.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { AppConfigService } from './app-config.service'; 2 | import { Globals } from '../utils/globals'; 3 | import { TestBed, inject } from '@angular/core/testing'; 4 | 5 | import { StarRatingWidgetService } from './star-rating-widget.service'; 6 | import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; 7 | 8 | describe('StarRatingWidgetService', () => { 9 | beforeEach(() => { 10 | TestBed.configureTestingModule({ 11 | imports: [], 12 | providers: [ 13 | StarRatingWidgetService, 14 | Globals, 15 | AppConfigService, 16 | provideHttpClient(withInterceptorsFromDi()) 17 | ] 18 | }); 19 | }); 20 | 21 | it('should be created', inject([StarRatingWidgetService], (service: StarRatingWidgetService) => { 22 | expect(service).toBeTruthy(); 23 | })); 24 | }); 25 | -------------------------------------------------------------------------------- /src/chat21-core/providers/abstract/notifications.service.ts: -------------------------------------------------------------------------------- 1 | import { environment } from './../../../environments/environment'; 2 | import { Injectable } from '@angular/core'; 3 | 4 | // @Injectable({ providedIn: 'root' }) 5 | @Injectable() 6 | export abstract class NotificationsService { 7 | 8 | private _tenant: string; 9 | public BUILD_VERSION = environment.version 10 | 11 | public setTenant(tenant): void { 12 | this._tenant = tenant; 13 | } 14 | public getTenant(): string { 15 | if (this._tenant) { 16 | return this._tenant; 17 | } 18 | } 19 | 20 | abstract initialize(tenant: string, vapidKey: string, platform: string): void; 21 | abstract getNotificationPermissionAndSaveToken(currentUserUid: string): void; 22 | abstract removeNotificationsInstance(callback: (string) => void): void; 23 | 24 | constructor( ) { 25 | 26 | } 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/app/component/message/frame/frame.component.scss: -------------------------------------------------------------------------------- 1 | :host{ 2 | // --borderRadius: #{$border-radius-bubble-message}; 3 | --borderRadius: 8px; 4 | } 5 | 6 | .loader { 7 | float: left; 8 | // position: absolute; 9 | z-index: 1000; 10 | // background-color: #ccc; 11 | border-radius: var(--borderRadius); 12 | background-image: linear-gradient(90deg, transparent 0px, #e8e8e8 40px, transparent 80px); 13 | background-size: 600px; 14 | animation: shine-loader 1.6s infinite linear; 15 | } 16 | 17 | .isLoadingImage { 18 | // position: relative; 19 | // top: 6px; 20 | display: none; 21 | } 22 | 23 | iframe { 24 | border:none; 25 | border-radius: var(--borderRadius) 26 | } 27 | 28 | @keyframes shine-loader { 29 | 0% { 30 | background-position: -32px; 31 | } 32 | 40%, 100% { 33 | background-position: 208px; 34 | } 35 | } -------------------------------------------------------------------------------- /src/app/component/message/html/html.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { SafeHtmlPipe } from './../../../pipe/safe-html.pipe'; 2 | import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | 4 | import { HtmlComponent } from './html.component'; 5 | 6 | describe('HtmlComponent', () => { 7 | let component: HtmlComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(waitForAsync(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [ 13 | HtmlComponent, 14 | SafeHtmlPipe 15 | ] 16 | }) 17 | .compileComponents(); 18 | })); 19 | 20 | beforeEach(() => { 21 | fixture = TestBed.createComponent(HtmlComponent); 22 | component = fixture.componentInstance; 23 | fixture.detectChanges(); 24 | }); 25 | 26 | it('should create', () => { 27 | expect(component).toBeTruthy(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/app/component/conversation-detail/conversation-emojii/conversation-emojii.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'chat-conversation-emojii', 5 | templateUrl: './conversation-emojii.component.html', 6 | styleUrls: ['./conversation-emojii.component.scss'] 7 | }) 8 | export class ConversationEmojiiComponent implements OnInit { 9 | 10 | @Input() var: string; 11 | @Output() addEmoji = new EventEmitter(); 12 | 13 | emojiiOptions = { 14 | emojiPerLine : 9, 15 | totalFrequentLines: 1, 16 | showPreview: false, 17 | darkMode: false, 18 | enableSearch: false, 19 | include: [ 'recent', 'people', 'nature', 'activity', 'flags'] 20 | } 21 | 22 | constructor(){} 23 | 24 | ngOnInit(): void { 25 | } 26 | 27 | addEmojiFN(event){ 28 | this.addEmoji.emit(event) 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/app/component/menu-options/menu-options.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { Globals } from '../../utils/globals'; 3 | 4 | import { MenuOptionsComponent } from './menu-options.component'; 5 | 6 | describe('MenuOptionsComponent', () => { 7 | let component: MenuOptionsComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [ MenuOptionsComponent ], 13 | providers: [ Globals ] 14 | }) 15 | .compileComponents(); 16 | })); 17 | 18 | beforeEach(() => { 19 | fixture = TestBed.createComponent(MenuOptionsComponent); 20 | component = fixture.componentInstance; 21 | fixture.detectChanges(); 22 | }); 23 | 24 | it('should create', () => { 25 | expect(component).toBeTruthy(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ConversationAudioRecorderComponent } from './conversation-audio-recorder.component'; 4 | 5 | describe('AudioRecorderComponent', () => { 6 | let component: ConversationAudioRecorderComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ConversationAudioRecorderComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(ConversationAudioRecorderComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | dist 5 | /tmp 6 | /out-tsc 7 | 8 | 9 | # dependencies 10 | /node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # IDE - VSCode 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | 28 | # misc 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | testem.log 35 | /typings 36 | 37 | # e2e 38 | /e2e/*.js 39 | /e2e/*.map 40 | 41 | # System Files 42 | .DS_Store 43 | Thumbs.db 44 | test_widget.php 45 | 46 | src/environments 47 | src/environments/real_data 48 | src/environments_dati_reali 49 | src/widget-config-docker.json 50 | src/widget-config-native-mqtt.json 51 | 52 | .angular/cache/ 53 | -------------------------------------------------------------------------------- /src/app/component/message/avatar/avatar.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { ImageRepoService } from '../../../../chat21-core/providers/abstract/image-repo.service'; 3 | 4 | import { AvatarComponent } from './avatar.component'; 5 | 6 | describe('AvatarComponent', () => { 7 | let component: AvatarComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [ AvatarComponent ], 13 | providers: [ ImageRepoService ] 14 | }) 15 | .compileComponents(); 16 | })); 17 | 18 | beforeEach(() => { 19 | fixture = TestBed.createComponent(AvatarComponent); 20 | component = fixture.componentInstance; 21 | fixture.detectChanges(); 22 | }); 23 | 24 | it('should create', () => { 25 | expect(component).toBeTruthy(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/app/component/form/inputs/form-label/form-label.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | import { FormGroupDirective } from '@angular/forms'; 3 | 4 | import { FormLabelComponent } from './form-label.component'; 5 | 6 | describe('FormLabelComponent', () => { 7 | let component: FormLabelComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(waitForAsync(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [ FormLabelComponent ], 13 | providers: [ FormGroupDirective ] 14 | }) 15 | .compileComponents(); 16 | })); 17 | 18 | beforeEach(() => { 19 | fixture = TestBed.createComponent(FormLabelComponent); 20 | component = fixture.componentInstance; 21 | fixture.detectChanges(); 22 | }); 23 | 24 | it('should create', () => { 25 | expect(component).toBeTruthy(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/app/providers/translator.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; 2 | import { AppConfigService } from './app-config.service'; 3 | import { Globals } from './../utils/globals'; 4 | import { TranslateModule, TranslateService } from '@ngx-translate/core'; 5 | import { TestBed, inject } from '@angular/core/testing'; 6 | 7 | import { TranslatorService } from './translator.service'; 8 | 9 | describe('TranslatorService', () => { 10 | beforeEach(() => { 11 | TestBed.configureTestingModule({ 12 | imports: [TranslateModule.forRoot()], 13 | providers: [ 14 | TranslatorService, 15 | Globals, 16 | AppConfigService, 17 | provideHttpClient(withInterceptorsFromDi()) 18 | ] 19 | }); 20 | }); 21 | 22 | it('should be created', inject([TranslatorService], (service: TranslatorService) => { 23 | expect(service).toBeTruthy(); 24 | })); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/component/message/info-message/info-message.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 | 8 |
9 | 10 | 11 | 14 |
15 | 16 |
17 |
18 |
19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /src/chat21-core/providers/tiledesk/tiledesk-requests.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { provideHttpClientTesting } from '@angular/common/http/testing'; 2 | import { TestBed, inject } from '@angular/core/testing'; 3 | import { AppStorageService } from '../abstract/app-storage.service'; 4 | 5 | import { TiledeskRequestsService } from './tiledesk-requests.service'; 6 | import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; 7 | 8 | describe('TiledeskRequestsService', () => { 9 | beforeEach(() => { 10 | TestBed.configureTestingModule({ 11 | imports: [], 12 | providers: [ 13 | TiledeskRequestsService, 14 | AppStorageService, 15 | provideHttpClient(withInterceptorsFromDi()), 16 | provideHttpClientTesting() 17 | ] 18 | }); 19 | }); 20 | 21 | it('should be created', inject([TiledeskRequestsService], (service: TiledeskRequestsService) => { 22 | expect(service).toBeTruthy(); 23 | })); 24 | }); 25 | -------------------------------------------------------------------------------- /src/app/component/form/inputs/form-textarea/form-textarea.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { FormGroup, FormGroupDirective } from '@angular/forms'; 3 | 4 | import { FormTextareaComponent } from './form-textarea.component'; 5 | 6 | describe('FormTextareaComponent', () => { 7 | let component: FormTextareaComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(waitForAsync(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [ FormTextareaComponent ], 13 | providers: [ FormGroupDirective ] 14 | }) 15 | .compileComponents(); 16 | })); 17 | 18 | beforeEach(() => { 19 | fixture = TestBed.createComponent(FormTextareaComponent); 20 | component = fixture.componentInstance; 21 | fixture.detectChanges(); 22 | }); 23 | 24 | it('should create', () => { 25 | expect(component).toBeTruthy(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/app/component/message/avatar/avatar.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 |
8 |
9 | 10 | 16 | 17 | -------------------------------------------------------------------------------- /src/app/component/message/bubble-message/bubble-message.component.scss: -------------------------------------------------------------------------------- 1 | /* ====== SET MESSAGES ====== */ 2 | 3 | .messages { 4 | border-radius: var(--border-radius-bubble-message); 5 | padding: 0; 6 | word-wrap: break-word; 7 | 8 | background: transparent; 9 | 10 | // padding: 14px; 11 | // padding: 6px 6px 6px 6px; 12 | // box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); 13 | // -webkit-animation: heartbeat 1.5s ease-in-out both; 14 | // animation: heartbeat 1.5s ease-in-out both; 15 | 16 | 17 | .message_sender_fullname{ 18 | font-size: 12px; 19 | font-weight: 400; 20 | } 21 | 22 | img { 23 | border-radius: var(--border-radius-bubble-message); 24 | padding: 3px; 25 | margin-bottom: 0px; 26 | max-width: calc(100% - 6px); 27 | width: auto; 28 | height: auto; 29 | object-fit: cover; 30 | } 31 | 32 | chat-audio { 33 | display: flex; 34 | } 35 | } -------------------------------------------------------------------------------- /src/widget-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "chatEngine": "mqtt", 3 | "uploadEngine": "native", 4 | "pushEngine":"none", 5 | "logLevel":"INFO", 6 | "remoteTranslationsUrl": "http://localhost:3000/", 7 | "firebaseConfig": { 8 | "apiKey": "CHANGEIT", 9 | "authDomain": "CHANGEIT", 10 | "databaseURL": "CHANGEIT", 11 | "projectId": "CHANGEIT", 12 | "storageBucket": "CHANGEIT", 13 | "messagingSenderId": "CHANGEIT", 14 | "appId": "CHANGEIT", 15 | "tenant": "CHANGEIT" 16 | }, 17 | "chat21Config": { 18 | "appId": "tilechat", 19 | "MQTTendpoint": "ws://localhost:15675/ws", 20 | "APIendpoint": "http://localhost:8004/api" 21 | }, 22 | "apiUrl": "http://localhost:3000/", 23 | "baseImageUrl": "http://localhost:3000/", 24 | "dashboardUrl": "http://localhost:4500/", 25 | "authPersistence": "LOCAL", 26 | "enbedJs": true, 27 | "brandSrc": "${BRAND_SRC}" 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/app/providers/global-settings.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Globals } from './../utils/globals'; 2 | import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; 3 | import { TestBed, inject } from '@angular/core/testing'; 4 | import { AppStorageService } from '../../chat21-core/providers/abstract/app-storage.service'; 5 | import { AppConfigService } from './app-config.service'; 6 | 7 | import { GlobalSettingsService } from './global-settings.service'; 8 | 9 | describe('GlobalSettingsService', () => { 10 | beforeEach(() => { 11 | TestBed.configureTestingModule({ 12 | imports: [], 13 | providers: [ 14 | GlobalSettingsService, 15 | AppStorageService, 16 | AppConfigService, 17 | Globals, 18 | provideHttpClient(withInterceptorsFromDi()) 19 | ] 20 | }); 21 | }); 22 | 23 | it('should be created', inject([GlobalSettingsService], (service: GlobalSettingsService) => { 24 | expect(service).toBeTruthy(); 25 | })); 26 | }); 27 | -------------------------------------------------------------------------------- /.README.md.swp: -------------------------------------------------------------------------------- 1 | b0nano 2.5.3Rparallelsubuntu/home/parallels/dev/chat21-web-widget/README.mdU -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /deploy_prod.sh: -------------------------------------------------------------------------------- 1 | # npm version prerelease --preid=beta 2 | version=`node -e 'console.log(require("./package.json").version)'` 3 | echo "version $version" 4 | echo "____________WIDGET-V5______________" 5 | echo "CREATING TAG ON GIT FOR version: $version" 6 | 7 | # Get curent branch name 8 | current_branch=$(git rev-parse --abbrev-ref HEAD) 9 | remote_name=$(git config --get branch.$current_branch.remote) 10 | 11 | ## Push commit to git 12 | git add . 13 | git commit -m "version added: ### $version" 14 | git push "$remote_name" "$current_branch" 15 | 16 | # Get curent branch name 17 | current_branch=$(git rev-parse --abbrev-ref HEAD) 18 | remote_name=$(git config --get branch.$current_branch.remote) 19 | 20 | ## Push commit to git 21 | git add . 22 | git commit -m "version added: ### $version" 23 | git push "$remote_name" "$current_branch" 24 | 25 | if [ "$version" != "" ]; then 26 | git tag -a "$version" -m "`git log -1 --format=%s`" 27 | echo "Created a new tag, $version" 28 | git push --tags 29 | npm publish 30 | fi -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | #f you want to include something that is excluded by your .gitignore file, you can create an empty .npmignore file to override it. 2 | # See http://help.github.com/ignore-files/ for more about ignoring files. 3 | 4 | # compiled output 5 | /dist 6 | /tmp 7 | /out-tsc 8 | .angular/ 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # IDEs and editors 14 | /.idea 15 | .project 16 | .classpath 17 | .c9/ 18 | *.launch 19 | .settings/ 20 | *.sublime-workspace 21 | 22 | # IDE - VSCode 23 | .vscode/* 24 | !.vscode/settings.json 25 | !.vscode/tasks.json 26 | !.vscode/launch.json 27 | !.vscode/extensions.json 28 | 29 | # misc 30 | /.sass-cache 31 | /connect.lock 32 | /coverage 33 | /libpeerconnection.log 34 | npm-debug.log 35 | testem.log 36 | /typings 37 | 38 | # e2e 39 | /e2e/*.js 40 | /e2e/*.map 41 | 42 | # System Files 43 | .DS_Store 44 | Thumbs.db 45 | test_widget.php 46 | 47 | src/environments 48 | src/environments/real_data 49 | src/widget-config-docker.json 50 | src/widget-config-native-mqtt.json 51 | 52 | ./deploy*.sh 53 | -------------------------------------------------------------------------------- /deploy_beta.sh: -------------------------------------------------------------------------------- 1 | # npm version prerelease --preid=beta 2 | version=`node -e 'console.log(require("./package.json").version)'` 3 | echo "version $version" 4 | echo "____________WIDGET-V5______________" 5 | echo "CREATING TAG ON GIT FOR version: $version" 6 | 7 | # Get curent branch name 8 | current_branch=$(git rev-parse --abbrev-ref HEAD) 9 | remote_name=$(git config --get branch.$current_branch.remote) 10 | 11 | ## Push commit to git 12 | git add . 13 | git commit -m "version added: ### $version" 14 | git push "$remote_name" "$current_branch" 15 | 16 | 17 | # Get curent branch name 18 | current_branch=$(git rev-parse --abbrev-ref HEAD) 19 | remote_name=$(git config --get branch.$current_branch.remote) 20 | 21 | ## Push commit to git 22 | git add . 23 | git commit -m "version added: ### $version" 24 | git push "$remote_name" "$current_branch" 25 | 26 | if [ "$version" != "" ]; then 27 | git tag -a "$version" -m "`git log -1 --format=%s`" 28 | echo "Created a new tag, $version" 29 | git push --tags 30 | npm publish 31 | fi -------------------------------------------------------------------------------- /src/app/component/message-attachment/message-attachment.component.scss: -------------------------------------------------------------------------------- 1 | #buttons-in-message { 2 | text-align: right; 3 | display: block; 4 | // margin-right: 16px; //align attachment-buttons to bubble sent message 5 | height: auto; 6 | // width: 85%; 7 | max-width: 95%; 8 | float: left; 9 | padding-left: calc(var(--avatar-width) + 10px); 10 | 11 | .buttons-wrapper { 12 | margin-top: 0px; 13 | display: flex; 14 | flex-wrap: wrap; 15 | -webkit-box-pack: end; 16 | // justify-content: flex-end; 17 | justify-content: flex-start; 18 | width: 100%; 19 | border: none; 20 | } 21 | 22 | .div-button { 23 | // display: inline-block; 24 | // overflow: hidden; 25 | // text-overflow: ellipsis; 26 | // min-width: inherit; 27 | .button-in-msg { 28 | padding: 8px 16px!important; 29 | } 30 | } 31 | 32 | &.align-left{ 33 | text-align: left; 34 | margin: 4px 50px; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/app/component/launcher-button/launcher-button.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | import { AppStorageService } from '../../../chat21-core/providers/abstract/app-storage.service'; 3 | import { Globals } from '../../utils/globals'; 4 | 5 | import { LauncherButtonComponent } from './launcher-button.component'; 6 | 7 | describe('LauncherButtonComponent', () => { 8 | let component: LauncherButtonComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(waitForAsync(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [ LauncherButtonComponent ], 14 | providers: [ Globals, AppStorageService] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(LauncherButtonComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/chat21-core/utils/user-typing/user-typing.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy, Input, ElementRef } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'user-typing', 5 | templateUrl: './user-typing.component.html', 6 | styleUrls: ['./user-typing.component.scss'], 7 | }) 8 | export class UserTypingComponent implements OnInit, OnDestroy { 9 | 10 | // @Input() idConversation: string; 11 | // @Input() idCurrentUser: string; 12 | // @Input() isDirect: boolean; 13 | @Input() typingLocation: string = 'content' 14 | @Input() translationMap: Map; 15 | @Input() color: string; 16 | @Input() idUserTypingNow: string; 17 | @Input() nameUserTypingNow: string; 18 | // @Input() membersConversation: [string]; 19 | 20 | constructor(private elementRef: ElementRef ) { } 21 | 22 | /** */ 23 | ngOnInit() { 24 | this.elementRef.nativeElement.style.setProperty('--bubbleReceivedBackgroundColor', this.color); 25 | } 26 | 27 | /** */ 28 | ngOnDestroy() { 29 | // this.unsubescribeAll(); 30 | } 31 | 32 | 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Tiledesk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 10 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/app/component/message/text/text.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { HtmlEntitiesEncodePipe } from './../../../pipe/html-entities-encode.pipe'; 2 | import { MarkedPipe } from './../../../pipe/marked.pipe'; 3 | import { async, ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 4 | 5 | import { TextComponent } from './text.component'; 6 | 7 | describe('TextComponent', () => { 8 | let component: TextComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(waitForAsync(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [ 14 | TextComponent, 15 | MarkedPipe, 16 | HtmlEntitiesEncodePipe 17 | ] 18 | }) 19 | .compileComponents(); 20 | })); 21 | 22 | beforeEach(() => { 23 | fixture = TestBed.createComponent(TextComponent); 24 | component = fixture.componentInstance; 25 | component.text = 'Msg text' 26 | component.color= 'black' 27 | fixture.detectChanges(); 28 | }); 29 | 30 | it('should create', () => { 31 | expect(component).toBeTruthy(); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/app/providers/waiting.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, HttpHeaders } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { Observable } from 'rxjs'; 4 | import { map } from 'rxjs/operators'; 5 | import { AppConfigService } from '../providers/app-config.service'; 6 | import { Globals } from '../utils/globals'; 7 | 8 | @Injectable() 9 | export class WaitingService { 10 | API_URL: string; 11 | 12 | constructor( 13 | public http: HttpClient, 14 | public g: Globals, 15 | public appConfigService: AppConfigService 16 | ) { 17 | 18 | this.API_URL = appConfigService.getConfig().apiUrl; 19 | } 20 | 21 | public getCurrent(projectId): Observable { 22 | const url = this.API_URL + projectId + '/publicanalytics/waiting/current'; 23 | const headers = new HttpHeaders(); 24 | headers.append('Content-Type', 'application/json'); 25 | // headers.append('Authorization', TOKEN); 26 | return this.http.get(url, { headers }) 27 | .pipe(map((response: any) => { return response})) 28 | 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/app/component/message/text/text.component.scss: -------------------------------------------------------------------------------- 1 | .message_innerhtml { 2 | margin: 0px; 3 | // padding: 0px 14px; 4 | &.marked{ 5 | padding: 12px 16px; 6 | margin-block-start: 0em!important; 7 | margin-block-end: 0em!important; 8 | } 9 | 10 | .text-message { 11 | padding-top: 14px; 12 | } 13 | } 14 | 15 | p { 16 | font-size: var(--font-size-bubble-message); 17 | margin: 0; 18 | padding: 14px; 19 | line-height: 1.4em; 20 | font-style: normal; 21 | letter-spacing: normal; 22 | font-stretch: normal; 23 | font-variant: normal; 24 | font-weight: 300; 25 | overflow: hidden; 26 | 27 | } 28 | 29 | p ::ng-deep a { 30 | word-break: break-word; 31 | } 32 | 33 | p ::ng-deep p{ 34 | margin-block-end: 0em; 35 | margin-block-start: 0em 36 | } 37 | 38 | p ::ng-deep ol{ 39 | margin-block-end: 0em; 40 | margin-block-start: 0em; 41 | padding-inline-start: 15px; 42 | 43 | li:before { 44 | content:""; 45 | font-size: 1.4em; 46 | vertical-align:middle; 47 | } 48 | } -------------------------------------------------------------------------------- /src/chat21-core/providers/abstract/typing.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { BehaviorSubject } from 'rxjs'; 3 | 4 | 5 | // @Injectable({ 6 | // providedIn: 'root' 7 | // }) 8 | @Injectable() 9 | export abstract class TypingService { 10 | 11 | // BehaviorSubject 12 | abstract BSIsTyping: BehaviorSubject; 13 | abstract BSSetTyping: BehaviorSubject; 14 | 15 | // params 16 | // private DEFAULT_TENANT: string = environment.firebaseConfig.tenant; 17 | // private _tenant: string; 18 | 19 | // public setTenant(tenant): void { 20 | // this._tenant = tenant; 21 | // } 22 | // public getTenant(): string { 23 | // if (this._tenant) { 24 | // return this._tenant; 25 | // } else { 26 | // return this.DEFAULT_TENANT 27 | // } 28 | // } 29 | 30 | // functions 31 | abstract initialize(tenant: string): void; 32 | abstract isTyping(idConversation: string, idCurrentUser: string, isDirect: boolean): void; 33 | abstract setTyping(idConversation: string, message: string, idUser: string, userFullname: string): void; 34 | } 35 | -------------------------------------------------------------------------------- /src/chat21-core/providers/firebase/firebase-init-service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | // firebase 4 | // import firebase from 'firebase/app'; 5 | /* 6 | Generated class for the AuthService provider. 7 | See https://angular.io/docs/ts/latest/guide/dependency-injection.html 8 | for more info on providers and Angular 2 DI. 9 | */ 10 | @Injectable() 11 | /** 12 | * DESC PROVIDER 13 | */ 14 | export class FirebaseInitService { 15 | 16 | public static firebaseInit: any; 17 | 18 | constructor() { 19 | } 20 | 21 | public static async initFirebase(firebaseConfig: any) { 22 | const { default: firebase} = await import("firebase/app"); 23 | if(!FirebaseInitService.firebaseInit){ 24 | if (!firebaseConfig || firebaseConfig.apiKey === 'CHANGEIT') { 25 | throw new Error('Firebase config is not defined. Please create your widget-config.json. See the Chat21-Web_widget Installation Page'); 26 | } 27 | FirebaseInitService.firebaseInit = firebase.initializeApp(firebaseConfig); 28 | } 29 | return FirebaseInitService.firebaseInit 30 | } 31 | } -------------------------------------------------------------------------------- /src/app/component/message/text/text.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'chat-text', 5 | templateUrl: './text.component.html', 6 | styleUrls: ['./text.component.scss'] 7 | }) 8 | export class TextComponent implements OnInit { 9 | 10 | @Input() text: string; 11 | @Input() htmlEnabled: boolean = false; 12 | @Input() color: string; 13 | @Output() onBeforeMessageRender = new EventEmitter(); 14 | @Output() onAfterMessageRender = new EventEmitter(); 15 | 16 | constructor() { } 17 | 18 | ngOnInit() { 19 | } 20 | 21 | 22 | printMessage(text, messageEl, component) { 23 | const messageOBJ = { messageEl: messageEl, component: component} 24 | this.onBeforeMessageRender.emit(messageOBJ) 25 | const messageText = text; 26 | this.onAfterMessageRender.emit(messageOBJ) 27 | // this.triggerBeforeMessageRender(message, messageEl, component); 28 | // const messageText = message.text; 29 | // this.triggerAfterMessageRender(message, messageEl, component); 30 | return messageText; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/app/component/conversation-detail/conversation-internal-frame/conversation-internal-frame.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 2 | import { async, ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 3 | 4 | import { ConversationInternalFrameComponent } from './conversation-internal-frame.component'; 5 | 6 | describe('InterlalFrameComponent', () => { 7 | let component: ConversationInternalFrameComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(waitForAsync(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [ ConversationInternalFrameComponent ], 13 | imports: [ 14 | BrowserAnimationsModule 15 | ] 16 | }) 17 | .compileComponents(); 18 | })); 19 | 20 | beforeEach(() => { 21 | fixture = TestBed.createComponent(ConversationInternalFrameComponent); 22 | component = fixture.componentInstance; 23 | fixture.detectChanges(); 24 | }); 25 | 26 | it('should create', () => { 27 | expect(component).toBeTruthy(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 2 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 3 | import { Globals } from '../../utils/globals'; 4 | 5 | import { EyeeyeCatcherCardComponent } from './eyeeye-catcher-card.component'; 6 | 7 | describe('EyeeyeCatcherCardComponent', () => { 8 | let component: EyeeyeCatcherCardComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(waitForAsync(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [ EyeeyeCatcherCardComponent ], 14 | imports: [ 15 | BrowserAnimationsModule 16 | ], 17 | providers: [ Globals ] 18 | }) 19 | .compileComponents(); 20 | })); 21 | 22 | beforeEach(() => { 23 | fixture = TestBed.createComponent(EyeeyeCatcherCardComponent); 24 | component = fixture.componentInstance; 25 | fixture.detectChanges(); 26 | }); 27 | 28 | it('should create', () => { 29 | expect(component).toBeTruthy(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/widget-config-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "chatEngine": "${CHAT21_ENGINE}", 3 | "uploadEngine": "${UPLOAD_ENGINE}", 4 | "pushEngine": "${PUSH_ENGINE}", 5 | "logLevel": "${LOG_LEVEL}" , 6 | "remoteTranslationsUrl": "${TRANSLATIONS_URL}", 7 | "firebaseConfig": { 8 | "apiKey": "${FIREBASE_APIKEY}", 9 | "authDomain": "${FIREBASE_AUTHDOMAIN}", 10 | "databaseURL": "${FIREBASE_DATABASEURL}", 11 | "projectId": "${FIREBASE_PROJECT_ID}", 12 | "storageBucket": "${FIREBASE_STORAGEBUCKET}", 13 | "messagingSenderId": "${FIREBASE_MESSAGINGSENDERID}", 14 | "appId": "${FIREBASE_APP_ID}", 15 | "tenant": "${FIREBASE_TENANT}" 16 | }, 17 | "chat21Config": { 18 | "appId": "${MQTT_APPID}", 19 | "MQTTendpoint": "${MQTT_ENDPOINT}", 20 | "APIendpoint": "${MQTT_APIENDPOINT}" 21 | }, 22 | "apiUrl": "${SERVER_BASE_URL}", 23 | "baseImageUrl": "${API_BASEIMAGE_URL}", 24 | "dashboardUrl": "${DASHBOARD_URL}", 25 | "authPersistence": "${AUTH_PERSISTENCE}", 26 | "enbedJs": "${ENBED_JS}", 27 | "brandSrc": "${BRAND_SRC}" 28 | } 29 | -------------------------------------------------------------------------------- /src/app/component/message/html/html.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ElementRef, Input, OnInit, SimpleChanges, ViewChild } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'chat-html', 5 | templateUrl: './html.component.html', 6 | styleUrls: ['./html.component.scss'] 7 | }) 8 | export class HtmlComponent implements OnInit { 9 | 10 | @Input() htmlText: string; 11 | @Input() fontSize: string; 12 | @Input() themeColor: string; 13 | @Input() foregroundColor: string; 14 | 15 | @ViewChild('htmlCode') container; 16 | 17 | constructor(private elementRef: ElementRef) { } 18 | 19 | ngOnInit(){ 20 | 21 | } 22 | 23 | ngOnChanges(changes: SimpleChanges){ 24 | //decomment if element should have same color of themeColor and fregroundColor 25 | if(this.fontSize) this.elementRef.nativeElement.style.setProperty('--buttonFontSize', this.fontSize); 26 | if(this.themeColor) this.elementRef.nativeElement.style.setProperty('--themeColor', this.themeColor); 27 | if(this.foregroundColor) this.elementRef.nativeElement.style.setProperty('--foregroundColor', this.foregroundColor); 28 | } 29 | 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/app/component/form/inputs/form-text/form-text.component.html: -------------------------------------------------------------------------------- 1 |
2 |
5 | 8 | 16 |
17 |
18 | {{translationErrorLabelMap?.get('LABEL_ERROR_FIELD_REQUIRED')}} 19 |
20 |
21 | {{element?.errorLabel}} 22 |
23 |
24 | -------------------------------------------------------------------------------- /src/app/component/form/inputs/form-textarea/form-textarea.component.html: -------------------------------------------------------------------------------- 1 |
2 |
4 | 7 | 18 |
19 |
20 | {{translationErrorLabelMap.get('LABEL_ERROR_FIELD_REQUIRED')}} 21 |
22 |
23 | {{element.errorLabel}} 24 |
25 |
26 | -------------------------------------------------------------------------------- /src/chat21-core/providers/abstract/presence.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { BehaviorSubject, Observable } from 'rxjs'; 3 | 4 | // @Injectable({ 5 | // providedIn: 'root' 6 | // }) 7 | @Injectable() 8 | export abstract class PresenceService { 9 | 10 | // BehaviorSubject 11 | abstract BSIsOnline: BehaviorSubject; 12 | abstract BSLastOnline: BehaviorSubject; 13 | 14 | // params 15 | // private DEFAULT_TENANT: string = environment.firebaseConfig.tenant; 16 | // private _tenant: string; 17 | 18 | // public setTenant(tenant): void { 19 | // this._tenant = tenant; 20 | // } 21 | // public getTenant(): string { 22 | // if (this._tenant) { 23 | // return this._tenant; 24 | // } else { 25 | // return this.DEFAULT_TENANT 26 | // } 27 | // } 28 | 29 | // functions 30 | abstract initialize(tenant: string): void; 31 | abstract userIsOnline(userid: string): Observable 32 | abstract lastOnlineForUser(userid: string): void; 33 | abstract setPresence(userid: string): void; 34 | abstract imHere():void; 35 | abstract removePresence(): void; 36 | } 37 | -------------------------------------------------------------------------------- /src/app/component/form/prechat-form/prechat-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 3 | 4 | import { PrechatFormComponent } from './prechat-form.component'; 5 | import { AppStorageService } from 'src/chat21-core/providers/abstract/app-storage.service'; 6 | import { Globals } from 'src/app/utils/globals'; 7 | 8 | describe('PrechatFormComponent', () => { 9 | let component: PrechatFormComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ PrechatFormComponent ], 15 | imports: [ FormsModule, ReactiveFormsModule ], 16 | providers: [ Globals, AppStorageService] 17 | }) 18 | .compileComponents(); 19 | })); 20 | 21 | beforeEach(() => { 22 | fixture = TestBed.createComponent(PrechatFormComponent); 23 | component = fixture.componentInstance; 24 | fixture.detectChanges(); 25 | }); 26 | 27 | it('should create', () => { 28 | expect(component).toBeTruthy(); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/chat21-core/providers/abstract/upload.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { BehaviorSubject } from 'rxjs'; 3 | import { environment } from '../../../environments/environment'; 4 | 5 | // models 6 | import { UploadModel } from '../../models/upload'; 7 | 8 | @Injectable() 9 | export abstract class UploadService { 10 | 11 | //params 12 | private DEFAULT_URL: string = environment.apiUrl; 13 | private baseUrl; 14 | 15 | public setBaseUrl(baseUrl): void { 16 | this.baseUrl = baseUrl; 17 | } 18 | public getBaseUrl(): string { 19 | if (this.baseUrl) { 20 | return this.baseUrl; 21 | } else { 22 | return this.DEFAULT_URL; 23 | } 24 | } 25 | 26 | //BehaviorSubject 27 | abstract BSStateUpload: BehaviorSubject; 28 | 29 | // functions 30 | abstract initialize(): void; 31 | abstract upload(userId: string, upload: UploadModel): Promise<{downloadURL: string, src: string}>; 32 | abstract uploadProfile(userId: string, upload: UploadModel): Promise; 33 | abstract delete(userId: string, path: string): Promise; 34 | abstract deleteProfile(userId: string, path: string): Promise 35 | } -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-coverage'), 11 | require('karma-chrome-launcher'), 12 | require('karma-jasmine-html-reporter'), 13 | require('karma-coverage-istanbul-reporter'), 14 | require('@angular-devkit/build-angular/plugins/karma') 15 | ], 16 | client: { 17 | clearContext: false // leave Jasmine Spec Runner output visible in browser 18 | }, 19 | coverageIstanbulReporter: { 20 | dir: require('path').join(__dirname, './coverage/widget'), 21 | reports: ['html', 'lcovonly', 'text-summary'], 22 | fixWebpackSourcePaths: true 23 | }, 24 | reporters: ['progress', 'kjhtml'], 25 | port: 9876, 26 | colors: true, 27 | logLevel: config.LOG_INFO, 28 | autoWatch: true, 29 | browsers: ['Chrome'], 30 | singleRun: false, 31 | restartOnFileChange: true 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /src/app/component/form/inputs/form-checkbox/form-checkbox.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { style } from '@angular/animations'; 2 | import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { FormGroupDirective } from '@angular/forms'; 4 | 5 | import { FormCheckboxComponent } from './form-checkbox.component'; 6 | 7 | describe('FormCheckboxComponent', () => { 8 | let component: FormCheckboxComponent; 9 | let fixture: ComponentFixture; 10 | let stylesMap = new Map(); 11 | beforeEach(waitForAsync(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [ FormCheckboxComponent ], 14 | providers: [ FormGroupDirective ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(FormCheckboxComponent); 21 | component = fixture.componentInstance; 22 | component.stylesMap = stylesMap.set('themeColor', "#2a6ac1") 23 | .set('foregroundColor', "#ffffff") 24 | fixture.detectChanges(); 25 | }); 26 | 27 | it('should create', () => { 28 | expect(component).toBeTruthy(); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/assets/images/avatar_bot_tiledesk.svg: -------------------------------------------------------------------------------- 1 | ic_widget_bot_gray_03 -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ### STAGE 1: Build ### 2 | 3 | # We label our stage as ‘builder’ 4 | FROM node:20.12.2-alpine3.19 as builder 5 | 6 | COPY package.json package-lock.json ./ 7 | 8 | ## Storing node modules on a separate layer will prevent unnecessary npm installs at each build 9 | 10 | RUN npm ci && mkdir /ng-app && mv ./node_modules ./ng-app 11 | 12 | WORKDIR /ng-app 13 | 14 | COPY . . 15 | 16 | ## Build the angular app in production mode and store the artifacts in dist folder 17 | 18 | RUN npx ng build --configuration="prod" --output-path=dist --base-href=./ --output-hashing=none 19 | 20 | 21 | ### STAGE 2: Setup ### 22 | 23 | FROM nginx:1.14.1-alpine 24 | 25 | ## Copy our default nginx config 26 | COPY nginx.conf /etc/nginx/nginx.conf 27 | 28 | ## Remove default nginx website 29 | RUN rm -rf /usr/share/nginx/html/* 30 | 31 | ## From ‘builder’ stage copy over the artifacts in dist folder to default nginx public folder 32 | COPY --from=builder /ng-app/dist/browser /usr/share/nginx/html 33 | 34 | RUN echo "Chat21 Web Widget Started!!" 35 | 36 | CMD ["/bin/sh", "-c", "envsubst < /usr/share/nginx/html/widget-config-template.json > /usr/share/nginx/html/widget-config.json && exec nginx -g 'daemon off;'"] 37 | -------------------------------------------------------------------------------- /src/app/component/message/frame/frame.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; 2 | import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; 3 | 4 | @Component({ 5 | selector: 'chat-frame', 6 | templateUrl: './frame.component.html', 7 | styleUrls: ['./frame.component.scss'] 8 | }) 9 | export class FrameComponent implements OnInit { 10 | 11 | @Input() metadata: any; 12 | @Input() width: number; 13 | @Input() height: number; 14 | @Output() onElementRendered = new EventEmitter<{element: string, status: boolean}>(); 15 | 16 | url: SafeResourceUrl = null 17 | loading: boolean = true 18 | constructor(private sanitizer: DomSanitizer) { } 19 | 20 | ngOnInit() { 21 | if(this.metadata && this.metadata.src){ 22 | this.url = this.sanitizer.bypassSecurityTrustResourceUrl(this.metadata.src); 23 | } 24 | // this.width = this.getSizeImg(this.metadata).width; 25 | // this.height = this.getSizeImg(this.metadata).height; 26 | } 27 | 28 | ngOnDestroy(){ 29 | this.url = null; 30 | } 31 | 32 | onLoaded(event){ 33 | this.loading = false 34 | this.onElementRendered.emit({element: "frame", status:true}) 35 | } 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/app/component/message/frame/frame.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | import { DomSanitizer } from '@angular/platform-browser'; 3 | 4 | import { FrameComponent } from './frame.component'; 5 | 6 | describe('FrameComponent', () => { 7 | let component: FrameComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(waitForAsync(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [ FrameComponent ], 13 | providers: [ 14 | { 15 | provide: DomSanitizer, 16 | useValue: { 17 | sanitize: () => 'safeString', 18 | bypassSecurityTrustResourceUrl: () => 'safeString' 19 | } 20 | } 21 | ,] 22 | }) 23 | .compileComponents(); 24 | })); 25 | 26 | beforeEach(() => { 27 | fixture = TestBed.createComponent(FrameComponent); 28 | component = fixture.componentInstance; 29 | fixture.detectChanges(); 30 | }); 31 | 32 | it('should create', () => { 33 | component.url = component['sanitizer'].bypassSecurityTrustResourceUrl('http://www.tiledesk.com'); 34 | fixture.detectChanges(); 35 | expect(component).toBeTruthy(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/app/component/message/image/image.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ImageComponent } from './image.component'; 4 | 5 | describe('ImageComponent', () => { 6 | let component: ImageComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ImageComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ImageComponent); 18 | component = fixture.componentInstance; 19 | component.metadata = { 20 | height: 650, 21 | name: "logo_fb.png", 22 | src: "https://firebasestorage.googleapis.com/v0/b/chat21-pre-01.appspot.com/o/public%2Fimages%2F42083ab3-3aa6-4507-831c-41c84b09dd83%2F0e8e8562-360e-4407-aa20-a0ccbe99b62d%2Flogo_fb.png?alt=media&token=8125435a-76ac-42f0-9017-cf3d8fb4e8a1", 23 | type: "image/png", 24 | uid: "ksg7sxnt", 25 | width: 650 26 | } 27 | component.width = 650 28 | component.height = 650 29 | fixture.detectChanges(); 30 | }); 31 | 32 | it('should create', () => { 33 | expect(component).toBeTruthy(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/app/component/selection-department/selection-department.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ElementRef } from '@angular/core'; 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { AppStorageService } from '../../../chat21-core/providers/abstract/app-storage.service'; 4 | import { Globals } from '../../utils/globals'; 5 | 6 | import { SelectionDepartmentComponent } from './selection-department.component'; 7 | 8 | describe('SelectionDepartmentComponent', () => { 9 | let component: SelectionDepartmentComponent; 10 | let fixture: ComponentFixture; 11 | class MockElementRef {} 12 | 13 | beforeEach(async(() => { 14 | TestBed.configureTestingModule({ 15 | declarations: [ SelectionDepartmentComponent ], 16 | providers: [ 17 | Globals, 18 | { provide: ElementRef, useClass: MockElementRef }, 19 | AppStorageService 20 | ] 21 | }) 22 | .compileComponents(); 23 | })); 24 | 25 | beforeEach(() => { 26 | fixture = TestBed.createComponent(SelectionDepartmentComponent); 27 | component = fixture.componentInstance; 28 | fixture.detectChanges(); 29 | }); 30 | 31 | it('should create', () => { 32 | expect(component).toBeTruthy(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /src/app/component/form/inputs/form-text/form-text.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | import { FormGroupDirective } from '@angular/forms'; 3 | 4 | import { FormTextComponent } from './form-text.component'; 5 | 6 | describe('FormTextComponent', () => { 7 | let component: FormTextComponent; 8 | let fixture: ComponentFixture; 9 | let stylesMap = new Map(); 10 | let translationErrorLabelMap = new Map(); 11 | beforeEach(waitForAsync(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [ FormTextComponent ], 14 | providers: [ FormGroupDirective ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(FormTextComponent); 21 | component = fixture.componentInstance; 22 | component.stylesMap = stylesMap.set('themeColor', "#2a6ac1") 23 | .set('foregroundColor', "#ffffff") 24 | component.translationErrorLabelMap = translationErrorLabelMap.set('LABEL_ERROR_FIELD_REQUIRED', "LABEL_ERROR_FIELD_REQUIRED") 25 | fixture.detectChanges(); 26 | }); 27 | 28 | it('should create', () => { 29 | expect(component).toBeTruthy(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/chat21-core/providers/abstract/groups-handler.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { BehaviorSubject, Observable, Subject } from 'rxjs'; 4 | import { GroupModel } from '../../models/group'; 5 | 6 | // @Injectable({ providedIn: 'root' }) 7 | @Injectable() 8 | export abstract class GroupsHandlerService { 9 | 10 | // BehaviorSubject 11 | abstract BSgroupDetail: BehaviorSubject; 12 | abstract SgroupDetail: Subject; 13 | abstract groupAdded: BehaviorSubject; 14 | abstract groupChanged: BehaviorSubject; 15 | abstract groupRemoved: BehaviorSubject; 16 | 17 | abstract initialize(tenant: string, loggedUserId: string): void; 18 | abstract connect(): void; 19 | abstract getDetail(groupId: string, callback?:(group: GroupModel)=>void): Promise; 20 | abstract onGroupChange(groupId: string): Observable; 21 | abstract create(groupName: string, members: [string], callback?:(res: any, error: any)=>void): Promise; 22 | abstract leave(groupId: string, callback?:(res: any, error: any)=>void): Promise; 23 | abstract join(groupId: string, member: string, callback?:(res: any, error: any)=>void) 24 | abstract dispose(): void; 25 | } 26 | -------------------------------------------------------------------------------- /src/app/component/message-attachment/message-attachment.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ActionButtonComponent } from './../message/buttons/action-button/action-button.component'; 2 | import { TextButtonComponent } from './../message/buttons/text-button/text-button.component'; 3 | import { LinkButtonComponent } from './../message/buttons/link-button/link-button.component'; 4 | import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; 5 | 6 | import { MessageAttachmentComponent } from './message-attachment.component'; 7 | 8 | describe('MessageAttachmentComponent', () => { 9 | let component: MessageAttachmentComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(waitForAsync(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ 15 | MessageAttachmentComponent, 16 | LinkButtonComponent, 17 | TextButtonComponent, 18 | ActionButtonComponent 19 | ] 20 | }) 21 | .compileComponents(); 22 | })); 23 | 24 | beforeEach(() => { 25 | fixture = TestBed.createComponent(MessageAttachmentComponent); 26 | component = fixture.componentInstance; 27 | fixture.detectChanges(); 28 | }); 29 | 30 | it('should create', () => { 31 | expect(component).toBeTruthy(); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/app/component/message/info-message/info-message.component.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Component, Input, OnInit } from '@angular/core'; 3 | import { MessageModel } from '../../../../chat21-core/models/message'; 4 | import { LoggerService } from '../../../../chat21-core/providers/abstract/logger.service'; 5 | import { LoggerInstance } from '../../../../chat21-core/providers/logger/loggerInstance'; 6 | 7 | @Component({ 8 | selector: 'chat-info-message', 9 | templateUrl: './info-message.component.html', 10 | styleUrls: ['./info-message.component.scss'] 11 | }) 12 | export class InfoMessageComponent implements OnInit { 13 | 14 | @Input() message: MessageModel 15 | 16 | public message_text: string 17 | private logger: LoggerService = LoggerInstance.getInstance() 18 | 19 | constructor() { } 20 | 21 | ngOnInit() { 22 | this.logger.debug('[INFO-COMP] message ', this.message) 23 | // Fixes the bug: if a snippet of code is pasted and sent it is not displayed correctly info message 24 | if(this.message && this.message.text) { 25 | var regex = //gi; 26 | this.message.text = this.message.text.replace(regex, "\n") 27 | // this.message.text = replaceEndOfLine(this.message.text); 28 | this.logger.debug('[INFO-COMP] message .text ', this.message.text ) 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/app/component/send-button/send-button.component.scss: -------------------------------------------------------------------------------- 1 | #c21-launcher-button { 2 | // border-radius: 50%; //--> now retrived from settings 3 | position: fixed; 4 | bottom: 0px; 5 | // width: 60px; //--> now retrived from settings 6 | // height: 60px; //--> now retrived from settings 7 | cursor: pointer; 8 | display: block; 9 | margin: 20px; 10 | z-index: 1; 11 | box-shadow: rgba(0, 18, 46, 0.16) 0px 0px 16px 0px; /* NEW GAB */ 12 | 13 | .launcher-button{ 14 | width:100%; 15 | height:100%; 16 | padding:0px; 17 | margin: 0px; 18 | display: flex; 19 | } 20 | } 21 | .c21-divBudge { 22 | position: absolute; 23 | top: 0; 24 | width: 16px; 25 | height: 16px; 26 | min-width: 12px; 27 | font-size: 1.2em; 28 | line-height: 16px; 29 | border-radius: 50%; 30 | background-color: red; 31 | color: white; 32 | font-weight: bold; 33 | padding: 5px 5px; 34 | text-align: center; 35 | margin-top: -10px; 36 | display: inline-block; 37 | } 38 | 39 | @media (max-width: 451px) { 40 | #c21-launcher-button { 41 | //display: none; 42 | &.c21-align-right { 43 | right: 0; 44 | } 45 | &.c21-align-left { 46 | left: 0; 47 | } 48 | 49 | } 50 | } -------------------------------------------------------------------------------- /src/app/component/launcher-button/launcher-button.component.scss: -------------------------------------------------------------------------------- 1 | #c21-launcher-button { 2 | // border-radius: 50%; //--> now retrived from settings 3 | position: fixed; 4 | bottom: 0px; 5 | // width: 60px; //--> now retrived from settings 6 | // height: 60px; //--> now retrived from settings 7 | cursor: pointer; 8 | display: block; 9 | margin: 20px; 10 | z-index: 1; 11 | box-shadow: rgba(0, 18, 46, 0.16) 0px 0px 16px 0px; /* NEW GAB */ 12 | 13 | .launcher-button{ 14 | width:100%; 15 | height:100%; 16 | padding:0px; 17 | margin: 0px; 18 | display: flex; 19 | } 20 | } 21 | .c21-divBudge { 22 | position: absolute; 23 | top: 0; 24 | width: 16px; 25 | height: 16px; 26 | min-width: 12px; 27 | font-size: 1.2em; 28 | line-height: 16px; 29 | border-radius: 50%; 30 | background-color: red; 31 | color: white; 32 | font-weight: bold; 33 | padding: 5px 5px; 34 | text-align: center; 35 | margin-top: -10px; 36 | display: inline-block; 37 | } 38 | 39 | @media (max-width: 451px) { 40 | #c21-launcher-button { 41 | //display: none; 42 | &.c21-align-right { 43 | right: 0; 44 | } 45 | &.c21-align-left { 46 | left: 0; 47 | } 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/app/component/star-rating-widget/star-rating-widget.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { AppConfigService } from './../../providers/app-config.service'; 2 | import { StarRatingWidgetService } from './../../providers/star-rating-widget.service'; 3 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 4 | 5 | import { StarRatingWidgetComponent } from './star-rating-widget.component'; 6 | import { Globals } from '../../utils/globals'; 7 | import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; 8 | 9 | describe('StarRatingWidgetComponent', () => { 10 | let component: StarRatingWidgetComponent; 11 | let fixture: ComponentFixture; 12 | 13 | beforeEach((() => { 14 | TestBed.configureTestingModule({ 15 | declarations: [StarRatingWidgetComponent], 16 | imports: [], 17 | providers: [ 18 | Globals, 19 | AppConfigService, 20 | StarRatingWidgetService, 21 | provideHttpClient(withInterceptorsFromDi()) 22 | ] 23 | }) 24 | .compileComponents(); 25 | })); 26 | 27 | beforeEach(() => { 28 | fixture = TestBed.createComponent(StarRatingWidgetComponent); 29 | component = fixture.componentInstance; 30 | fixture.detectChanges(); 31 | }); 32 | 33 | it('should create', () => { 34 | expect(component).toBeTruthy(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/chat21-core/providers/abstract/messagingAuth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { BehaviorSubject } from 'rxjs'; 3 | 4 | 5 | // @Injectable({ 6 | // providedIn: 'root' 7 | // }) 8 | @Injectable() 9 | export abstract class MessagingAuthService { 10 | 11 | // BehaviorSubject 12 | abstract BSAuthStateChanged: BehaviorSubject; 13 | abstract BSSignOut: BehaviorSubject; 14 | 15 | // params 16 | public DEFAULT_PERSISTENCE: string = 'NONE'; 17 | public DEFAULT_URL: string = 'https://api.tiledesk.com/v2/auth/'; 18 | 19 | private persistence; 20 | private baseUrl; 21 | 22 | public setPersistence(persistence): void { 23 | this.persistence = persistence; 24 | } 25 | 26 | public getPersistence(): string { 27 | if (this.persistence) { 28 | return this.persistence; 29 | } else { 30 | return this.DEFAULT_PERSISTENCE; 31 | } 32 | } 33 | 34 | public setBaseUrl(baseUrl): void { 35 | this.baseUrl = baseUrl; 36 | } 37 | public getBaseUrl(): string { 38 | if (this.baseUrl) { 39 | return this.baseUrl; 40 | } else { 41 | return this.DEFAULT_URL; 42 | } 43 | } 44 | 45 | // functions 46 | abstract initialize(): void; 47 | abstract getToken(): string; 48 | abstract createCustomToken(tiledeskToken): void; 49 | abstract logout(): Promise; 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/chat21-core/providers/native/native-image-repo.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { ImageRepoService } from '../abstract/image-repo.service'; 4 | 5 | // @Injectable({ providedIn: 'root' }) 6 | @Injectable() 7 | export class NativeImageRepoService extends ImageRepoService { 8 | 9 | private baseImageURL: string; 10 | 11 | constructor(public http: HttpClient) { 12 | super(); 13 | } 14 | 15 | /** 16 | * @param uid 17 | */ 18 | getImagePhotoUrl(uid: string): string { 19 | this.baseImageURL = this.getImageBaseUrl() + 'images' 20 | let sender_id = ''; 21 | if (uid.includes('bot_')) { 22 | sender_id = uid.slice(4) 23 | } else { 24 | sender_id = uid 25 | } 26 | const filename_photo = '?path=uploads/users/'+ sender_id + '/images/photo.jpg' 27 | const filename_thumbnail = '?path=uploads/users/'+ sender_id + '/images/thumbnails_200_200-photo.jpg' 28 | return this.baseImageURL + filename_photo 29 | } 30 | 31 | checkImageExists(url: string, callback: (exist: boolean) => void): void { 32 | this.http.get(url).subscribe( res => { 33 | callback(true) 34 | },(error) => { console.log('[NATIVE-IMAGE-REPO-SERVICE] checkImageExists error:', url, error);callback(false)}) 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /src/app/component/last-message/last-message.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { MarkedPipe } from './../../pipe/marked.pipe'; 2 | import { NO_ERRORS_SCHEMA } from '@angular/core'; 3 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 4 | import { ImageRepoService } from '../../../chat21-core/providers/abstract/image-repo.service'; 5 | import { Globals } from '../../utils/globals'; 6 | 7 | import { LastMessageComponent } from './last-message.component'; 8 | import { HtmlEntitiesEncodePipe } from '../../pipe/html-entities-encode.pipe'; 9 | 10 | describe('LastMessageComponent', () => { 11 | let component: LastMessageComponent; 12 | let fixture: ComponentFixture; 13 | 14 | beforeEach(async(() => { 15 | TestBed.configureTestingModule({ 16 | declarations: [ 17 | LastMessageComponent, 18 | MarkedPipe, 19 | HtmlEntitiesEncodePipe 20 | ], 21 | imports: [ 22 | ], 23 | providers: [ 24 | Globals, 25 | ImageRepoService, 26 | ], 27 | schemas: [NO_ERRORS_SCHEMA] 28 | }) 29 | .compileComponents(); 30 | })); 31 | 32 | beforeEach(() => { 33 | fixture = TestBed.createComponent(LastMessageComponent); 34 | component = fixture.componentInstance; 35 | fixture.detectChanges(); 36 | }); 37 | 38 | it('should create', () => { 39 | expect(component).toBeTruthy(); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /src/app/modals/confirm-close/confirm-close.component.scss: -------------------------------------------------------------------------------- 1 | .modal-container{ 2 | display: flex; 3 | flex-direction: column; 4 | gap: 5px; 5 | } 6 | 7 | .header{ 8 | display: flex; 9 | justify-content: flex-end; 10 | } 11 | 12 | .content{ 13 | font-size: 1.2em; 14 | display: flex; 15 | flex-direction: column; 16 | gap: 15px; 17 | .text{ 18 | max-width: calc(100% - 30px); 19 | } 20 | .options{ 21 | display: flex; 22 | justify-content: flex-end; 23 | align-items: center; 24 | gap: 15px; 25 | 26 | &:hover{ 27 | cursor: pointer; 28 | } 29 | 30 | .back-button{ 31 | color: #777777; 32 | 33 | &.disabled{ 34 | cursor: not-allowed; 35 | pointer-events: none; 36 | opacity: 0.6 37 | } 38 | } 39 | 40 | .spinner-container{ 41 | width: inherit; 42 | height: inherit; 43 | display: flex; 44 | align-items: center; 45 | } 46 | 47 | } 48 | } 49 | 50 | .c21-header-button{ 51 | width: 30px; 52 | height: 30px; 53 | } 54 | 55 | button.c21-button-primary{ 56 | margin: 0 !important; 57 | height: 30px !important; 58 | display: flex; 59 | align-items: center; 60 | 61 | .c21-label-button{ 62 | margin: 0 !important; 63 | } 64 | } -------------------------------------------------------------------------------- /src/chat21-core/providers/mqtt/chat-service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import { Chat21Client } from '../../../assets/js/chat21client'; 4 | // declare var Chat21Client: any; 5 | 6 | /* 7 | Generated class for the AuthService provider. 8 | See https://angular.io/docs/ts/latest/guide/dependency-injection.html 9 | for more info on providers and Angular 2 DI. 10 | */ 11 | @Injectable() 12 | /** 13 | * DESC PROVIDER 14 | */ 15 | export class Chat21Service { 16 | 17 | public _chatClient: any; 18 | private _config: any; 19 | 20 | constructor() { 21 | } 22 | 23 | public set config(config: any) { 24 | this._config = config; 25 | } 26 | 27 | public get config() : any { 28 | return this._config; 29 | } 30 | 31 | public get chatClient(){ 32 | return this._chatClient 33 | } 34 | 35 | public set chatClient(chatClient){ 36 | this._chatClient =chatClient 37 | } 38 | 39 | public initChat() { 40 | 41 | if (!this._config || this._config.appId === 'CHANGEIT') { 42 | throw new Error('chat21Config is not defined. Please setup your environment'); 43 | } 44 | if (!this.chatClient) { 45 | // const { Chat21Client} = await import("../../../assets/js/chat21client"); 46 | 47 | this.chatClient = new Chat21Client(this._config); 48 | } else { 49 | console.log("Did you try again to create a Chat21Client istance?"); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /src/app/component/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { NO_ERRORS_SCHEMA } from '@angular/core'; 2 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 3 | import { NGXLogger } from 'ngx-logger'; 4 | import { CustomLogger } from 'src/chat21-core/providers/logger/customLogger'; 5 | import { LoggerInstance } from 'src/chat21-core/providers/logger/loggerInstance'; 6 | import { Globals } from '../../utils/globals'; 7 | 8 | import { HomeComponent } from './home.component'; 9 | 10 | describe('HomeComponent', () => { 11 | let component: HomeComponent; 12 | let fixture: ComponentFixture; 13 | let ngxlogger: NGXLogger; 14 | let customLogger = new CustomLogger(ngxlogger) 15 | 16 | beforeEach(waitForAsync(() => { 17 | TestBed.configureTestingModule({ 18 | declarations: [ HomeComponent ], 19 | providers: [ 20 | Globals, 21 | NGXLogger 22 | ], 23 | schemas: [NO_ERRORS_SCHEMA] 24 | }) 25 | .compileComponents(); 26 | })); 27 | 28 | beforeEach(() => { 29 | fixture = TestBed.createComponent(HomeComponent); 30 | component = fixture.componentInstance; 31 | let loggerInstance = LoggerInstance.setInstance(customLogger) 32 | let logger = LoggerInstance.getInstance() 33 | component['logger']= logger 34 | fixture.detectChanges(); 35 | }); 36 | 37 | it('should create', () => { 38 | expect(component).toBeTruthy(); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/app/component/message/return-receipt/return-receipt.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | import { By } from '@angular/platform-browser'; 3 | import { MSG_STATUS_SENT } from 'src/chat21-core/utils/constants'; 4 | 5 | import { ReturnReceiptComponent } from './return-receipt.component'; 6 | 7 | describe('ReturnReceiptComponent', () => { 8 | let component: ReturnReceiptComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(waitForAsync(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [ ReturnReceiptComponent ] 14 | }) 15 | .compileComponents(); 16 | })); 17 | 18 | beforeEach(() => { 19 | fixture = TestBed.createComponent(ReturnReceiptComponent); 20 | component = fixture.componentInstance; 21 | fixture.detectChanges(); 22 | }); 23 | 24 | it('should create', () => { 25 | expect(component).toBeTruthy(); 26 | }); 27 | 28 | it('shold render MSG_STATUS_SENT icon', ()=> { 29 | component.status= MSG_STATUS_SENT 30 | fixture.detectChanges(); 31 | expect(component.status).toBe(MSG_STATUS_SENT) 32 | }) 33 | 34 | // it('shold render MSG_STATUS_SENT icon', ()=> { 35 | // component.status= MSG_STATUS_SENT 36 | // fixture.detectChanges(); 37 | // let element = fixture.debugElement.query(By.css('icon')) 38 | // expect(element.classes).toBeE('icon') 39 | // }) 40 | }); 41 | -------------------------------------------------------------------------------- /src/app/pipe/date-ago.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | import dayjs from 'dayjs'; 4 | import relativeTime from 'dayjs/plugin/relativeTime'; 5 | dayjs.extend(relativeTime) 6 | 7 | @Pipe({ 8 | name: 'dateAgo', 9 | pure: true 10 | }) 11 | export class DateAgoPipe implements PipeTransform { 12 | 13 | transform(value: any, args?: any): any { 14 | if (value) { 15 | // const seconds = Math.floor((+new Date() - +new Date(value)) / 1000); 16 | // if (seconds < 29) // less than 30 seconds ago will show as 'Just now' 17 | // return 'Just now'; 18 | // const intervals: { [key: string]: number } = { 19 | // 'year': 31536000, 20 | // 'month': 2592000, 21 | // 'week': 604800, 22 | // 'day': 86400, 23 | // 'hour': 3600, 24 | // 'minute': 60, 25 | // 'second': 1 26 | // }; 27 | // let counter; 28 | // for (const i in intervals) { 29 | // counter = Math.floor(seconds / intervals[i]); 30 | // if (counter > 0) 31 | // if (counter === 1) { 32 | // return counter + ' ' + i + ' ago'; // singular (1 day ago) 33 | // } else { 34 | // return counter + ' ' + i + 's ago'; // plural (2 days ago) 35 | // } 36 | // } 37 | value = dayjs(value).fromNow() 38 | } 39 | return value; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/app/component/message/audio/audio.component.html: -------------------------------------------------------------------------------- 1 | 2 | 5 |
6 | 7 |
8 | 14 | 20 |
21 | {{ audioDuration ? formatTime(audioDuration) : '00:00' }} 22 | {{ formatTime(currentTime) }} 23 |
24 | 25 |
26 | 27 |
28 | 29 | 30 |
31 | 32 |
-------------------------------------------------------------------------------- /src/app/component/send-button/send-button.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25 | -------------------------------------------------------------------------------- /src/app/component/menu-options/menu-options.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; 2 | import { Globals } from 'src/app/utils/globals'; 3 | import { convertColorToRGBA } from 'src/chat21-core/utils/utils'; 4 | 5 | 6 | 7 | @Component({ 8 | selector: 'chat-menu-options', 9 | templateUrl: './menu-options.component.html', 10 | styleUrls: ['./menu-options.component.scss'] 11 | }) 12 | export class MenuOptionsComponent implements OnInit { 13 | 14 | @Input() isHover: boolean = false; 15 | @Output() onSignOut = new EventEmitter(); 16 | 17 | themeColor50: string; 18 | 19 | constructor( 20 | public g: Globals 21 | ) { } 22 | 23 | ngOnInit() { 24 | this.themeColor50 = convertColorToRGBA(this.g.themeColor, 50); 25 | } 26 | 27 | f21_toggle_options() { 28 | this.g.setParameter('isOpenMenuOptions', !this.g.isOpenMenuOptions, true); 29 | } 30 | 31 | toggleSound() { 32 | this.g.setParameter('soundEnabled', !this.g.soundEnabled, true); 33 | this.g.setParameter('isOpenMenuOptions', false, true); 34 | // this.g.soundEnabled = !this.g.soundEnabled; 35 | // if ( this.g.soundEnabled === false ) { 36 | // this.storageService.setItem('soundEnabled', false); 37 | // } else { 38 | // this.storageService.setItem('soundEnabled', true); 39 | // } 40 | } 41 | 42 | signOut() { 43 | this.g.setParameter('isOpenMenuOptions', false, true); 44 | this.onSignOut.emit(); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/app/component/message/info-message/info-message.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { CustomLogger } from './../../../../chat21-core/providers/logger/customLogger'; 2 | import { LoggerInstance } from './../../../../chat21-core/providers/logger/loggerInstance'; 3 | import { LoggerService } from 'src/chat21-core/providers/abstract/logger.service'; 4 | import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; 5 | import { MarkedPipe } from '../../../pipe/marked.pipe'; 6 | 7 | import { InfoMessageComponent } from './info-message.component'; 8 | import { NGXLogger } from 'ngx-logger'; 9 | 10 | describe('InfoMessageComponent', () => { 11 | let component: InfoMessageComponent; 12 | let fixture: ComponentFixture; 13 | let ngxlogger: NGXLogger; 14 | let customLogger = new CustomLogger(ngxlogger) 15 | 16 | beforeEach(waitForAsync(() => { 17 | TestBed.configureTestingModule({ 18 | declarations: [ InfoMessageComponent, MarkedPipe ], 19 | providers: [LoggerService, NGXLogger] 20 | }) 21 | .compileComponents(); 22 | })); 23 | 24 | beforeEach(() => { 25 | fixture = TestBed.createComponent(InfoMessageComponent); 26 | component = fixture.componentInstance; 27 | LoggerInstance.setInstance(customLogger) 28 | let logger = LoggerInstance.getInstance() 29 | component['logger']= logger 30 | fixture.detectChanges(); 31 | }); 32 | 33 | it('should create', () => { 34 | expect(component).toBeTruthy(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/app/component/message/buttons/link-button/link-button.component.html: -------------------------------------------------------------------------------- 1 | 2 |
4 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | {{button?.value}} 25 |
26 |
27 | 28 | -------------------------------------------------------------------------------- /src/chat21-core/providers/custom-translate.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | // https://enappd.com/blog/how-to-translate-in-ionic-internationalization-and-localization/143/ 3 | // https://phrase.com/blog/posts/localizing-ionic-applications-with-ngx-translate/ 4 | import { TranslateService } from '@ngx-translate/core'; 5 | import LabelENG from 'src/assets/i18n/en.json'; 6 | // @Injectable({ 7 | // providedIn: 'root' 8 | // }) 9 | @Injectable() 10 | export class CustomTranslateService { 11 | 12 | 13 | public language: string; 14 | 15 | constructor( 16 | private translateService: TranslateService 17 | ) { 18 | //this.translateService.setDefaultLang('en'); 19 | } 20 | 21 | /** */ 22 | public translateLanguage(keys: any, lang?: string): Map { 23 | // if (!lang || lang === '') { 24 | // this.language = this.translateService.currentLang; 25 | // } else { 26 | // this.language = lang; 27 | // } 28 | // this.translateService.use(this.language); 29 | return this.initialiseTranslation(keys); 30 | } 31 | 32 | /** */ 33 | private initialiseTranslation(keys): Map { 34 | const mapTranslate = new Map(); 35 | keys.forEach((key: string) => { 36 | this.translateService.get(key).subscribe((res: string) => { 37 | if(key === res && LabelENG[key]){ 38 | res= LabelENG[key] 39 | } 40 | mapTranslate.set(key, res); 41 | }); 42 | }); 43 | return mapTranslate; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/app/component/list-conversations/list-conversations.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TranslatorService } from './../../providers/translator.service'; 2 | import { NO_ERRORS_SCHEMA } from '@angular/core'; 3 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 4 | 5 | import { ListConversationsComponent } from './list-conversations.component'; 6 | import { NGXLogger } from 'ngx-logger'; 7 | import { CustomLogger } from 'src/chat21-core/providers/logger/customLogger'; 8 | import { LoggerInstance } from 'src/chat21-core/providers/logger/loggerInstance'; 9 | 10 | describe('ListConversationsComponent', () => { 11 | let component: ListConversationsComponent; 12 | let fixture: ComponentFixture; 13 | let ngxlogger: NGXLogger; 14 | let customLogger = new CustomLogger(ngxlogger) 15 | 16 | beforeEach(waitForAsync(() => { 17 | TestBed.configureTestingModule({ 18 | declarations: [ ListConversationsComponent ], 19 | providers:[ 20 | TranslatorService 21 | ], 22 | schemas: [NO_ERRORS_SCHEMA] 23 | }) 24 | .compileComponents(); 25 | })); 26 | 27 | beforeEach(() => { 28 | fixture = TestBed.createComponent(ListConversationsComponent); 29 | component = fixture.componentInstance; 30 | LoggerInstance.setInstance(customLogger) 31 | let logger = LoggerInstance.getInstance() 32 | component['logger']= logger 33 | fixture.detectChanges(); 34 | }); 35 | 36 | it('should create', () => { 37 | expect(component).toBeTruthy(); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /src/chat21-core/utils/utils-user.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * getColorBck 3 | * @param str 4 | */ 5 | export function getColorBck(str: string): string { 6 | const arrayBckColor = ['#E17076', '#7BC862', '#65aadd', '#a695e7', '#ee7aae', '#6ec9cb', '#faa774']; 7 | let num = 0; 8 | if (str) { 9 | const code = str.charCodeAt((str.length - 1)); 10 | num = Math.round(code % arrayBckColor.length); 11 | } 12 | return arrayBckColor[num]; 13 | } 14 | 15 | /** 16 | * avatarPlaceholder 17 | * @param name 18 | */ 19 | export function avatarPlaceholder(name: string): string { 20 | let initials = ''; 21 | if (name) { 22 | const arrayName = name.split(' '); 23 | arrayName.forEach(member => { 24 | if (member.trim().length > 1 && initials.length < 3) { 25 | initials += member.substring(0, 1).toUpperCase(); 26 | } 27 | if(member.trim().length == 1){ 28 | initials+= member.substring(0, 1).toUpperCase(); 29 | } 30 | }); 31 | } 32 | return initials; 33 | } 34 | 35 | /** 36 | * getImageUrlThumbFromFirebasestorage 37 | * @param uid 38 | * @param FIREBASESTORAGE_BASE_URL_IMAGE 39 | * @param urlStorageBucket 40 | */ 41 | export function getImageUrlThumbFromFirebasestorage( 42 | uid: string, 43 | FIREBASESTORAGE_BASE_URL_IMAGE: string, 44 | urlStorageBucket: string 45 | ): string { 46 | const imageurl = FIREBASESTORAGE_BASE_URL_IMAGE + urlStorageBucket + uid + '%2Fthumb_photo.jpg?alt=media'; 47 | return imageurl; 48 | } 49 | -------------------------------------------------------------------------------- /src/app/component/message/info-message/info-message.component.scss: -------------------------------------------------------------------------------- 1 | .info-container{ 2 | display: flex; 3 | align-content: center; 4 | justify-content: center; 5 | padding: 4px 0px 2px 0px; 6 | width: 100%; 7 | } 8 | .base_info { 9 | border-radius: 14px; 10 | // border: 1px solid rgba(24, 119, 242, 0.1); 11 | padding-left: 4px; 12 | padding-right: 4px; 13 | padding: 6px 10px; 14 | display: inline-block; 15 | background: var(--bkg-color-info-message); 16 | font-size: 10px; 17 | color: var(--base-gray); 18 | margin-left: 5px; //32px; 19 | margin-right: 5px; //32px; 20 | } 21 | 22 | .base_info ::ng-deep > p { 23 | margin-top: 0 !important; 24 | margin-bottom: 0 !important; 25 | 26 | a { 27 | word-break: break-all; 28 | color: #3880ff !important; 29 | } 30 | } 31 | 32 | .spinner { 33 | // margin: 15px 10px; //remove it if activate avatar image 34 | width: 45px; 35 | // text-align: center; 36 | min-height: 100%; 37 | margin: auto 0px; 38 | } 39 | 40 | .spinner > div { 41 | width: 10px; 42 | height: 10px; 43 | background-color: #333; 44 | 45 | border-radius: 100%; 46 | display: inline-block; 47 | margin: 0px 2px; 48 | } 49 | 50 | .spinner .bounce1 { 51 | background-color: var(--bkg-color-info-message); 52 | } 53 | 54 | .spinner .bounce2 { 55 | background-color:var(--bkg-color-info-message); 56 | opacity: 0.6 57 | } 58 | 59 | .spinner .bounce3 { 60 | background-color:var(--bkg-color-info-message); 61 | opacity: 0.4 62 | } 63 | -------------------------------------------------------------------------------- /src/chat21-core/providers/abstract/conversation-handler.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { BehaviorSubject } from 'rxjs'; 3 | 4 | // models 5 | import { MessageModel } from '../../models/message'; 6 | import { UserModel } from 'src/chat21-core/models/user'; 7 | 8 | // @Injectable({ 9 | // providedIn: 'root' 10 | // }) 11 | @Injectable() 12 | export abstract class ConversationHandlerService { 13 | 14 | // BehaviorSubject 15 | abstract messageAdded: BehaviorSubject; 16 | abstract messageChanged: BehaviorSubject; 17 | abstract messageRemoved: BehaviorSubject; 18 | abstract messageWait: BehaviorSubject; 19 | abstract messageInfo: BehaviorSubject; 20 | 21 | // params 22 | abstract attributes: any; 23 | abstract messages: Array; 24 | abstract conversationWith: string; 25 | 26 | constructor() {} 27 | 28 | // functions 29 | abstract initialize( 30 | recipientId: string, 31 | recipientFullName: string, 32 | loggedUser: UserModel, 33 | tenant: string, 34 | translationMap: Map, 35 | showBubbleInfoMessage: Array): void; 36 | abstract connect(): void; 37 | abstract sendMessage( 38 | msg: string, 39 | type: string, 40 | metadata: string, 41 | conversationWith: string, 42 | conversationWithFullname: string, 43 | sender: string, 44 | senderFullname: string, 45 | channelType: string, 46 | attributes: any 47 | ): MessageModel; 48 | abstract dispose(): void; 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build2. 2 | // The build2 system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build2 --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false, 8 | version: require('../../package.json').version, // https://stackoverflow.com/questions/34907682/how-to-display-app-version-in-angular2 9 | remoteConfig: true, 10 | remoteConfigUrl: '/widget-config.json', 11 | loadRemoteTranslations: true, 12 | remoteTranslationsUrl: 'http://localhost:3000/', 13 | chatEngine: 'mqtt', 14 | uploadEngine: 'native', 15 | tenant: 'tilechat', 16 | logLevel: 'INFO', 17 | firebaseConfig: { 18 | apiKey: 'CHANGEIT', 19 | authDomain: 'CHANGEIT', 20 | databaseURL: 'CHANGEIT', 21 | projectId: 'CHANGEIT', 22 | storageBucket: 'CHANGEIT', 23 | messagingSenderId: 'CHANGEIT', 24 | appId: 'CHANGEIT', 25 | tenant: 'tilechat' 26 | }, 27 | chat21Config: { 28 | appId: 'tilechat', 29 | MQTTendpoint: 'mqtt://localhost:15675/ws', // MQTT endpoint 30 | APIendpoint: 'http://localhost:8004/api' 31 | }, 32 | apiUrl: 'http://localhost:3000/', 33 | baseImageUrl: 'https://firebasestorage.googleapis.com/v0/b/', 34 | dashboardUrl: 'http://localhost:4500/', 35 | defaultLang : 'en', 36 | storage_prefix : 'widget_sv5', 37 | authPersistence: 'LOCAL', 38 | supportMode: true, 39 | enbedJs: true 40 | }; -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build2. 2 | // The build2 system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build2 --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: true, 8 | version: require('../../package.json').version, // https://stackoverflow.com/questions/34907682/how-to-display-app-version-in-angular2 9 | remoteConfig: true, 10 | remoteConfigUrl: '/widget-config.json', 11 | loadRemoteTranslations: true, 12 | remoteTranslationsUrl: 'http://localhost:3000/', 13 | chatEngine: 'CHANGEIT', 14 | uploadEngine: 'CHANGEIT', 15 | logLevel: 'INFO', 16 | firebaseConfig: { 17 | apiKey: 'CHANGEIT', 18 | authDomain: 'CHANGEIT', 19 | databaseURL: 'CHANGEIT', 20 | projectId: 'CHANGEIT', 21 | storageBucket: 'CHANGEIT', 22 | messagingSenderId: 'CHANGEIT', 23 | appId: 'CHANGEIT', 24 | tenant: 'CHANGEIT', 25 | }, 26 | chat21Config: { 27 | appId: 'tilechat', 28 | MQTTendpoint: 'mqtt://localhost:15675/ws', // MQTT endpoint 29 | APIendpoint: 'http://localhost:8004/api' 30 | }, 31 | apiUrl: 'http://localhost:3000/', 32 | baseImageUrl: 'https://firebasestorage.googleapis.com/v0/b/', 33 | dashboardUrl: 'https://console.tiledesk.com/v2/dashboard/', 34 | defaultLang : 'en', 35 | storage_prefix : 'widget_sv5', 36 | authPersistence: 'LOCAL', 37 | supportMode: true, 38 | enbedJs: true 39 | }; -------------------------------------------------------------------------------- /src/environments/environment.pre.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build2. 2 | // The build2 system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build2 --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: true, 8 | version: require('../../package.json').version, // https://stackoverflow.com/questions/34907682/how-to-display-app-version-in-angular2 9 | remoteConfig: true, 10 | remoteConfigUrl: '/widget-config.json', 11 | remoteTranslationsUrl: 'http://localhost:3000/', 12 | loadRemoteTranslations: true, 13 | chatEngine: 'mqtt', 14 | uploadEngine: 'native', 15 | logLevel: 'INFO', 16 | firebaseConfig: { 17 | apiKey: 'CHANGEIT', 18 | authDomain: 'CHANGEIT', 19 | databaseURL: 'CHANGEIT', 20 | projectId: 'CHANGEIT', 21 | storageBucket: 'CHANGEIT', 22 | messagingSenderId: 'CHANGEIT', 23 | appId: 'CHANGEIT', 24 | tenant: 'tilechat', 25 | }, 26 | chat21Config: { 27 | appId: 'tilechat', 28 | MQTTendpoint: 'mqtt://localhost:15675/ws', // MQTT endpoint 29 | APIendpoint: 'http://localhost:8004/api' 30 | }, 31 | apiUrl: 'https://tiledesk-server-pre.herokuapp.com/', 32 | baseImageUrl: 'https://firebasestorage.googleapis.com/v0/b/', 33 | dashboardUrl: "https://support-pre.tiledesk.com/dashboard/", 34 | defaultLang : 'en', 35 | storage_prefix : 'widget_sv5', 36 | authPersistence: 'LOCAL', 37 | supportMode: true, 38 | enbedJs: true 39 | }; 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/app/component/form/inputs/form-checkbox/form-checkbox.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ElementRef, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; 2 | import { FormGroup, FormGroupDirective } from '@angular/forms'; 3 | import { FormArray } from '../../../../../chat21-core/models/formArray'; 4 | 5 | @Component({ 6 | selector: 'chat-form-checkbox', 7 | templateUrl: './form-checkbox.component.html', 8 | styleUrls: ['./form-checkbox.component.scss'] 9 | }) 10 | export class FormCheckboxComponent implements OnInit { 11 | 12 | @Input() element: FormArray; 13 | @Input() controlName: string; 14 | @Input() translationErrorLabelMap: Map; 15 | @Input() stylesMap: Map; 16 | @Input() hasSubmitted: boolean; 17 | @Output() onKeyEnterPressed = new EventEmitter(); 18 | 19 | form: FormGroup; 20 | constructor(private rootFormGroup: FormGroupDirective, 21 | private elementRef: ElementRef) { } 22 | 23 | ngOnInit() { 24 | this.form = this.rootFormGroup.control; 25 | } 26 | 27 | ngOnChanges(changes: SimpleChanges){ 28 | if(this.stylesMap && this.stylesMap.get('themeColor')) this.elementRef.nativeElement.style.setProperty('--themeColor', this.stylesMap.get('themeColor')); 29 | if(this.stylesMap && this.stylesMap.get('foregroundColor')) this.elementRef.nativeElement.style.setProperty('--foregroundColor', this.stylesMap.get('foregroundColor')); 30 | } 31 | 32 | /** 33 | * FIRED when user press ENTER button on keyboard 34 | * @param event 35 | */ 36 | onEnterPressed(event){ 37 | this.onKeyEnterPressed.emit(event) 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/app/utils/utils-resources.ts: -------------------------------------------------------------------------------- 1 | export const BRAND_BASE_INFO: { [key: string] : string} ={ 2 | COMPANY_NAME: "Tiledesk", 3 | BRAND_NAME: "Tiledesk", 4 | COMPANY_SITE_NAME:"tiledesk.com", 5 | COMPANY_SITE_URL:"https://www.tiledesk.com", 6 | CONTACT_US_EMAIL: "support@tiledesk.com", 7 | FAVICON: "https://tiledesk.com/wp-content/uploads/2022/07/tiledesk_v13-300x300.png", 8 | META_TITLE:"Design Studio", 9 | LOGO_CHAT:"https://widget.tiledesk.com/v2/assets/images/tiledesk_logo_white_small.svg", 10 | POWERED_BY:"Powered by Tiledesk", 11 | } 12 | 13 | export const LOGOS_ITEMS: { [key: string] : { label: string, icon: string }} ={ 14 | COMPANY_LOGO: {label: BRAND_BASE_INFO.COMPANY_NAME, icon: 'assets/logos/tiledesk_logo.svg'}, 15 | COMPANY_LOGO_NO_TEXT: {label: BRAND_BASE_INFO.COMPANY_NAME, icon: 'assets/logos/tiledesk_logo_no_text.svg'}, 16 | BASE_LOGO: {label: BRAND_BASE_INFO.BRAND_NAME, icon: 'assets/logos/tiledesk_logo.svg'}, 17 | BASE_LOGO_NO_TEXT: {label: BRAND_BASE_INFO.BRAND_NAME, icon: 'assets/logos/tiledesk_logo_no_text.svg'}, 18 | BASE_LOGO_WHITE: { label: BRAND_BASE_INFO.BRAND_NAME, icon: '"assets/logos/tiledesk-logo_new_white.svg'}, 19 | BASE_LOGO_WHITE_NO_TEXT: { label: BRAND_BASE_INFO.BRAND_NAME, icon: '"assets/logos/tiledesk-logo_new_white.svg'}, 20 | BASE_LOGO_GRAY: { label: BRAND_BASE_INFO.BRAND_NAME, icon: 'https://support-pre.tiledesk.com/dashboard/assets/img/logos/tiledesk-logo_new_gray.svg'} 21 | } -------------------------------------------------------------------------------- /src/chat21-core/providers/abstract/archivedconversations-handler.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { BehaviorSubject } from 'rxjs'; 3 | import { ConversationModel } from '../../models/conversation'; 4 | import { ImageRepoService } from './image-repo.service'; 5 | 6 | @Injectable() 7 | export abstract class ArchivedConversationsHandlerService { 8 | 9 | // BehaviorSubject 10 | abstract BSConversationDetail: BehaviorSubject; 11 | abstract archivedConversationAdded: BehaviorSubject; 12 | abstract archivedConversationChanged: BehaviorSubject; 13 | abstract archivedConversationRemoved: BehaviorSubject; 14 | // abstract readAllMessages: BehaviorSubject = new BehaviorSubject(null); 15 | 16 | // params 17 | abstract archivedConversations: Array; 18 | abstract uidConvSelected: string; 19 | //abstract imageRepo: ImageRepoService; 20 | 21 | // functions 22 | abstract initialize(tenant: string, userId: string, translationMap: Map): void; 23 | // abstract connect(): void; 24 | abstract subscribeToConversations(callback: any): void; 25 | abstract countIsNew(): number; 26 | abstract setConversationRead(conversationId: string) 27 | abstract dispose(): void; 28 | abstract getConversationDetail(conversationId: string, callback:(conv: ConversationModel)=>void): void; 29 | abstract getClosingConversation(conversationId: string): boolean; 30 | abstract setClosingConversation(conversationId: string, status: boolean): void; 31 | abstract deleteClosingConversation(conversationId: string): void; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/app/component/form/form-builder/form-builder.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TranslateModule, TranslateService } from '@ngx-translate/core'; 2 | import { CustomTranslateService } from './../../../../chat21-core/providers/custom-translate.service'; 3 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 4 | import { ReactiveFormsModule } from '@angular/forms'; 5 | 6 | import { FormBuilderComponent } from './form-builder.component'; 7 | import { LoggerInstance } from 'src/chat21-core/providers/logger/loggerInstance'; 8 | import { NGXLogger } from 'ngx-logger'; 9 | import { CustomLogger } from 'src/chat21-core/providers/logger/customLogger'; 10 | 11 | describe('FormBuilderComponent', () => { 12 | let component: FormBuilderComponent; 13 | let fixture: ComponentFixture; 14 | let ngxlogger: NGXLogger; 15 | let customLogger = new CustomLogger(ngxlogger) 16 | 17 | beforeEach(waitForAsync(() => { 18 | TestBed.configureTestingModule({ 19 | declarations: [ FormBuilderComponent ], 20 | imports: [ 21 | ReactiveFormsModule, 22 | TranslateModule.forRoot(), 23 | ], 24 | providers: [ 25 | CustomTranslateService, 26 | ] 27 | }) 28 | .compileComponents(); 29 | })); 30 | 31 | beforeEach(() => { 32 | fixture = TestBed.createComponent(FormBuilderComponent); 33 | component = fixture.componentInstance; 34 | LoggerInstance.setInstance(customLogger) 35 | let logger = LoggerInstance.getInstance() 36 | component['logger']= logger 37 | fixture.detectChanges(); 38 | }); 39 | 40 | it('should create', () => { 41 | expect(component).toBeTruthy(); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/app/utils/BrandResources.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { BrandService } from "../providers/brand.service"; 3 | import { BRAND_BASE_INFO, LOGOS_ITEMS } from "./utils-resources"; 4 | 5 | @Injectable({ 6 | providedIn: 'root' 7 | }) 8 | export class BrandResources { 9 | 10 | brand: {} 11 | 12 | constructor( 13 | brandService: BrandService 14 | ) { 15 | this.brand = brandService.getBrand() 16 | } 17 | 18 | loadResources(){ 19 | if(!this.brand){ 20 | return; 21 | } 22 | 23 | /** META TITLE and FAVICON */ 24 | document.title = this.brand['BRAND_NAME'] + ' ' + this.brand['META_TITLE'] 25 | // var icon = document.querySelector("link[rel~='icon']") as HTMLElement; 26 | // icon.setAttribute('href', this.brand['FAVICON_URL']) 27 | 28 | /** META sharing ELEMENTS */ 29 | if(this.brand['META_SHARE_INFO'] && this.brand['META_SHARE_INFO'].length > 0){ 30 | Object.keys(this.brand['META_SHARE_INFO']).forEach(key => { 31 | var meta = document.querySelector("meta[property^='og:"+key.toLowerCase()+"']") as HTMLElement; 32 | meta.setAttribute('content', this.brand['META_SHARE_INFO'][key]) 33 | }) 34 | } 35 | 36 | /** CSS */ 37 | document.documentElement.style.setProperty('--base-brand-color', this.brand['BRAND_PRIMARY_COLOR']); 38 | 39 | /** BRAND_BASE_INFO */ 40 | Object.keys(BRAND_BASE_INFO).forEach(key => BRAND_BASE_INFO[key] = this.brand[key]) 41 | 42 | /** LOGOS_ITEMS */ 43 | Object.keys(LOGOS_ITEMS).forEach(key => { LOGOS_ITEMS[key].icon = this.brand[key] }) 44 | 45 | } 46 | } -------------------------------------------------------------------------------- /src/app/component/error-alert/error-alert.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { CustomTranslateService } from 'src/chat21-core/providers/custom-translate.service'; 3 | import * as CONSTANTS from 'src/app/utils/constants'; 4 | 5 | @Component({ 6 | selector: 'chat-error-alert', 7 | templateUrl: './error-alert.component.html', 8 | styleUrls: ['./error-alert.component.scss'] 9 | }) 10 | export class ErrorAlertComponent implements OnInit { 11 | 12 | @Input() errorMessage: string = ''; 13 | @Input() errorKeyMessage: string = ''; 14 | @Input() errorParams: Record = {}; 15 | 16 | translationMap: Map; 17 | 18 | constructor( 19 | private customTranslateService: CustomTranslateService, 20 | ){} 21 | 22 | ngOnInit(): void { 23 | let rawMessage: string = ''; 24 | // Combina costanti globali + parametri passati come input 25 | const replacements = { ...CONSTANTS, ...this.errorParams }; 26 | if (this.errorKeyMessage) { 27 | // Traduci il messaggio e sostituisci i placeholder 28 | rawMessage = this.customTranslateService 29 | .translateLanguage([this.errorKeyMessage]) 30 | .get(this.errorKeyMessage); 31 | } else if (this.errorMessage) { 32 | rawMessage = this.errorMessage; 33 | } 34 | this.errorMessage = this.interpolate(rawMessage, replacements); 35 | } 36 | 37 | /** Sostituisce {{placeholders}} con i valori corrispondenti */ 38 | private interpolate(template: string, variables: Record): string { 39 | return template.replace(/\{\{(.*?)\}\}/g, (_, key) => { 40 | const trimmedKey = key.trim(); 41 | return variables[trimmedKey] ?? `{{${trimmedKey}}}`; 42 | }); 43 | } 44 | 45 | } 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/app/component/conversation-detail/conversation-preview/conversation-preview.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { DomSanitizer } from '@angular/platform-browser'; 2 | import { LoggerInstance } from './../../../../chat21-core/providers/logger/loggerInstance'; 3 | import { LoggerService } from 'src/chat21-core/providers/abstract/logger.service'; 4 | import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; 5 | 6 | import { ConversationPreviewComponent } from './conversation-preview.component'; 7 | import { LogLevel } from 'src/chat21-core/utils/constants'; 8 | import { NGXLogger } from 'ngx-logger'; 9 | import { CustomLogger } from 'src/chat21-core/providers/logger/customLogger'; 10 | const mockService = jasmine.createSpyObj('LoggerService', ['setLoggerConfig', "debug", "log", "warn", "info", "error" ]); 11 | 12 | describe('ConversationPreviewComponent', () => { 13 | let component: ConversationPreviewComponent; 14 | let fixture: ComponentFixture; 15 | let ngxlogger: NGXLogger; 16 | let customLogger = new CustomLogger(ngxlogger) 17 | 18 | beforeEach(waitForAsync(() => { 19 | TestBed.configureTestingModule({ 20 | declarations: [ ConversationPreviewComponent ], 21 | providers: [LoggerService] 22 | }).compileComponents(); 23 | })); 24 | 25 | beforeEach(() => { 26 | fixture = TestBed.createComponent(ConversationPreviewComponent); 27 | component = fixture.componentInstance; 28 | LoggerInstance.setInstance(customLogger) 29 | let logger = LoggerInstance.getInstance() 30 | component['logger']= logger 31 | fixture.detectChanges(); 32 | }); 33 | 34 | it('should create', () => { 35 | console.log('ConversationPreviewComponent --->', component) 36 | expect(component).toBeTruthy(); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /src/app/modals/confirm-close/confirm-close.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ElementRef, EventEmitter, Input, OnInit, Output, SimpleChange, SimpleChanges, ViewChild, OnDestroy } from '@angular/core'; 2 | import { LoggerService } from 'src/chat21-core/providers/abstract/logger.service'; 3 | import { LoggerInstance } from 'src/chat21-core/providers/logger/loggerInstance'; 4 | 5 | 6 | @Component({ 7 | selector: 'chat-confirm-close', 8 | templateUrl: './confirm-close.component.html', 9 | styleUrls: ['./confirm-close.component.scss'] 10 | }) 11 | export class ConfirmCloseComponent implements OnInit{ 12 | 13 | @Input() isLoadingActive: boolean; 14 | @Input() conversationId: string; 15 | 16 | @Input() translationMap: Map< string, string>; 17 | @Input() stylesMap: Map; 18 | @Output() onDiaglogClosed = new EventEmitter<{type: string, data: any}>(); 19 | 20 | private logger: LoggerService = LoggerInstance.getInstance(); 21 | constructor() { } 22 | 23 | ngOnInit(): void { 24 | this.logger.log('[CONFIRM CLOSE MODAL] onInit', this.isLoadingActive, this.stylesMap); 25 | // this.dialog.showModal(); 26 | } 27 | 28 | ngOnChanges(changes: SimpleChanges){ 29 | if(changes && 30 | changes['conversationId'] && 31 | changes['conversationId'].previousValue !== undefined && 32 | (changes['conversationId'].previousValue !== changes['conversationId'].currentValue) && 33 | changes['conversationId'].currentValue 34 | ){ 35 | this.isLoadingActive = false; 36 | } 37 | } 38 | 39 | ngAfterViewInit(){ 40 | } 41 | 42 | onBack(){ 43 | this.onDiaglogClosed.emit({type: 'back', data: null}); 44 | } 45 | 46 | onConfirm(){ 47 | this.isLoadingActive = true; 48 | this.onDiaglogClosed.emit({type: 'confirm', data: null}); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /deploy_amazon_prod.sh: -------------------------------------------------------------------------------- 1 | # npm version patch 2 | version=`node -e 'console.log(require("./package.json").version)'` 3 | echo "version $version" 4 | 5 | npm i 6 | 7 | cp src/environments/real_data/environment.prod.ts src/environments/environment.prod.ts 8 | 9 | # --build-optimizer=false if localstorage is disabled (webview) appears https://github.com/firebase/angularfire/issues/970 10 | ng build --configuration="prod" --aot=true 11 | ##--base-href='./v5/' --output-hashing none 12 | 13 | ### SET HASHING : START ### 14 | cp ./src/launch_template.js ./dist/browser/launch.js 15 | node ./src/build_launch.js 16 | ### SET HASHING : END ### 17 | 18 | #### FIREBASE ##### 19 | # cd dist 20 | # # aws s3 sync . s3://tiledesk-widget/v5/latest/ 21 | # aws s3 sync . s3://tiledesk-widget/v5/$version/ --cache-control max-age=300 22 | # aws s3 sync . s3://tiledesk-widget/v5/ --cache-control max-age=300 23 | # cd .. 24 | 25 | # #### MQTT ##### 26 | cd dist/browser 27 | # aws s3 sync . s3://tiledesk-widget/v5/latest/ 28 | aws s3 sync . s3://tiledesk-widget/v6/$version/ --cache-control max-age=86400 --exclude='launch.js' #8days 29 | aws s3 sync . s3://tiledesk-widget/v6/$version/ --cache-control "no-store,no-cache,private" --exclude='*' --include='launch.js' 30 | aws s3 sync . s3://tiledesk-widget/v6/ --cache-control max-age=86400 --exclude='launch.js' #8days 31 | aws s3 sync . s3://tiledesk-widget/v6/ --cache-control "no-store,no-cache,private" --exclude='*' --include='launch.js' 32 | cd ../.. 33 | 34 | aws cloudfront create-invalidation --distribution-id E3EJDWEHY08CZZ --paths "/*" 35 | 36 | git restore src/environments/environment.prod.ts 37 | 38 | echo new version deployed $version on s3://tiledesk-widget/v6 39 | echo available on https://s3.eu-west-1.amazonaws.com/tiledesk-widget/v6/index.html 40 | echo https://widget.tiledesk.com/v6/index.html 41 | echo https://widget.tiledesk.com/v6/$version/index.html 42 | -------------------------------------------------------------------------------- /src/app/component/conversation-detail/conversation-header/conversation-header.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; 2 | import { AppConfigService } from './../../../providers/app-config.service'; 3 | import { Globals } from './../../../utils/globals'; 4 | import { NO_ERRORS_SCHEMA } from '@angular/core'; 5 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 6 | 7 | import { ConversationHeaderComponent } from './conversation-header.component'; 8 | import { TypingService } from '../../../../chat21-core/providers/abstract/typing.service'; 9 | import { LoggerInstance } from 'src/chat21-core/providers/logger/loggerInstance'; 10 | import { NGXLogger } from 'ngx-logger'; 11 | import { CustomLogger } from 'src/chat21-core/providers/logger/customLogger'; 12 | 13 | describe('ConversationHeaderComponent', () => { 14 | let component: ConversationHeaderComponent; 15 | let fixture: ComponentFixture; 16 | let ngxlogger: NGXLogger; 17 | let customLogger = new CustomLogger(ngxlogger) 18 | 19 | beforeEach(waitForAsync(() => { 20 | TestBed.configureTestingModule({ 21 | declarations: [ConversationHeaderComponent], 22 | schemas: [NO_ERRORS_SCHEMA], 23 | imports: [], 24 | providers: [ 25 | Globals, 26 | TypingService, 27 | AppConfigService, 28 | provideHttpClient(withInterceptorsFromDi()) 29 | ] 30 | }) 31 | .compileComponents(); 32 | })); 33 | 34 | beforeEach(() => { 35 | fixture = TestBed.createComponent(ConversationHeaderComponent); 36 | component = fixture.componentInstance; 37 | LoggerInstance.setInstance(customLogger) 38 | let logger = LoggerInstance.getInstance() 39 | component['logger']= logger 40 | }); 41 | 42 | it('should create', () => { 43 | expect(component).toBeTruthy(); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Page Not Found 7 | 8 | 23 | 24 | 25 |
26 |

404

27 |

Page Not Found

28 |

The specified file was not found on this website. Please check the URL for mistakes and try again.

29 |

Why am I seeing this?

30 |

This page was generated by the Firebase Command-Line Interface. To modify it, edit the 404.html file in your project's configured public directory.

31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /src/app/component/list-all-conversations/list-all-conversations.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { CustomTranslateService } from './../../../chat21-core/providers/custom-translate.service'; 2 | import { NO_ERRORS_SCHEMA, IterableDiffers, IterableDifferFactory } from '@angular/core'; 3 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 4 | import { Globals } from '../../utils/globals'; 5 | 6 | import { ListAllConversationsComponent } from './list-all-conversations.component'; 7 | import { TranslateModule, TranslateService } from '@ngx-translate/core'; 8 | import { NGXLogger } from 'ngx-logger'; 9 | import { CustomLogger } from 'src/chat21-core/providers/logger/customLogger'; 10 | import { LoggerInstance } from 'src/chat21-core/providers/logger/loggerInstance'; 11 | 12 | describe('ListAllConversationsComponent', () => { 13 | let component: ListAllConversationsComponent; 14 | let fixture: ComponentFixture; 15 | let ngxlogger: NGXLogger; 16 | let customLogger = new CustomLogger(ngxlogger) 17 | 18 | beforeEach(waitForAsync(() => { 19 | TestBed.configureTestingModule({ 20 | declarations: [ ListAllConversationsComponent ], 21 | imports: [ 22 | TranslateModule.forRoot(), 23 | ], 24 | providers: [ 25 | Globals, 26 | IterableDiffers, 27 | CustomTranslateService, 28 | TranslateService 29 | ], 30 | schemas: [NO_ERRORS_SCHEMA] 31 | }) 32 | .compileComponents(); 33 | })); 34 | 35 | beforeEach(() => { 36 | fixture = TestBed.createComponent(ListAllConversationsComponent); 37 | component = fixture.componentInstance; 38 | LoggerInstance.setInstance(customLogger) 39 | let logger = LoggerInstance.getInstance() 40 | component['logger']= logger 41 | fixture.detectChanges(); 42 | }); 43 | 44 | it('should create', () => { 45 | expect(component).toBeTruthy(); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /src/chat21-core/providers/abstract/conversations-handler.service.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Injectable } from '@angular/core'; 3 | import { BehaviorSubject } from 'rxjs'; 4 | 5 | // models 6 | import { ImageRepoService } from './image-repo.service'; 7 | import { ConversationModel } from './../../models/conversation'; 8 | // import { ImageRepoService } from './image-repo.service'; 9 | 10 | // @Injectable({ 11 | // providedIn: 'root' 12 | // }) 13 | @Injectable() 14 | export abstract class ConversationsHandlerService { 15 | 16 | // BehaviorSubject 17 | abstract BSConversationDetail: BehaviorSubject; 18 | abstract conversationAdded: BehaviorSubject; 19 | abstract conversationChanged: BehaviorSubject; 20 | abstract conversationChangedDetailed: BehaviorSubject<{value: ConversationModel, previousValue: ConversationModel}>; 21 | abstract conversationRemoved: BehaviorSubject; 22 | // abstract readAllMessages: BehaviorSubject = new BehaviorSubject(null); 23 | 24 | // params 25 | abstract conversations: Array; 26 | abstract uidConvSelected: string; 27 | 28 | // functions 29 | abstract initialize(tenant: string, userId: string, translationMap: Map): void; 30 | abstract subscribeToConversations(callback: any): void; 31 | abstract countIsNew(): number; 32 | abstract setConversationRead(conversationId: string): void; 33 | abstract dispose(): void; 34 | abstract archiveConversation(conversationId: string): void; 35 | abstract getLastConversation(callback:(conv: ConversationModel, error: string)=>void): void 36 | abstract getConversationDetail(conversationId: string, callback:(conv: ConversationModel)=>void): void; 37 | abstract getClosingConversation(conversationId: string): boolean; 38 | abstract setClosingConversation(conversationId: string, status: boolean): void; 39 | abstract deleteClosingConversation(conversationId: string): void; 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/app/component/message/image/image.component.scss: -------------------------------------------------------------------------------- 1 | :host{ 2 | // --borderRadius: #{$border-radius-bubble-message}; 3 | --borderRadius: 8px; 4 | } 5 | 6 | img { 7 | border-radius: var(--borderRadius); 8 | //padding: 3px; 9 | margin-bottom: 0px; 10 | max-width: calc(100% - 6px); 11 | width: auto; 12 | height: auto; 13 | object-fit: cover; 14 | 15 | transition: transform 0.3s ease-in-out 0s; 16 | 17 | } 18 | 19 | .c21-img-container img:hover{ 20 | cursor: pointer; 21 | transform: scale(1.05); 22 | } 23 | 24 | 25 | .c21-img-container { 26 | text-align: center; 27 | width: 100%; 28 | overflow: hidden; 29 | // background-image: linear-gradient(rgba(255,255,255,.5), rgba(255,255,255,.5)), url("https://firebasestorage.googleapis.com/v0/b/chat21-pre-01.appspot.com/o/public%2Fimages%2Fb4361ea5-5e37-433c-b727-9034eb5586fe%2F6d74f795-5873-49a8-9165-38f48642df51%2Ffacebook_like%20(3).png?alt=media&token=79afcfe5-ba0c-4573-9263-0877e0225c84"); 30 | // background-repeat: no-repeat; 31 | // background-position: 50% 0; 32 | // background-size: cover; 33 | // border-top-left-radius: $border-radius-bubble-message; 34 | // border-top-right-radius: $border-radius-bubble-message; 35 | border-radius: var(--borderRadius); 36 | 37 | } 38 | 39 | .demo-content { 40 | position: relative; 41 | } 42 | 43 | .isLoadingImage { 44 | // position: relative; 45 | // top: 6px; 46 | display: none; 47 | } 48 | 49 | .loader { 50 | float: left; 51 | // position: absolute; 52 | z-index: 1000; 53 | // background-color: #ccc; 54 | border-radius: var(--borderRadius); 55 | background-image: linear-gradient(90deg, transparent 0px, #e8e8e8 40px, transparent 80px); 56 | background-size: 600px; 57 | animation: shine-loader 1.6s infinite linear; 58 | } 59 | 60 | @keyframes shine-loader { 61 | 0% { 62 | background-position: -32px; 63 | } 64 | 40%, 100% { 65 | background-position: 208px; 66 | } 67 | } -------------------------------------------------------------------------------- /src/app/providers/app-config.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { getParameterByName } from 'src/chat21-core/utils/utils'; 4 | import { environment } from 'src/environments/environment'; 5 | import { Globals } from '../utils/globals'; 6 | 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | export class AppConfigService { 11 | 12 | private appConfig; 13 | 14 | constructor(private http: HttpClient, public g: Globals) { 15 | this.appConfig = environment; 16 | } 17 | 18 | loadAppConfig() { 19 | // START GET BASE URL and create absolute url of remoteConfigUrl // 20 | let urlConfigFile = this.appConfig.remoteConfigUrl; 21 | if (!this.appConfig.remoteConfigUrl.startsWith('http')) { 22 | let wContext: any = window; 23 | if (window.frameElement && window.frameElement.getAttribute('tiledesk_context') === 'parent') { 24 | wContext = window.parent; 25 | } 26 | const windowcontextFromWindow = getParameterByName(window, 'windowcontext'); 27 | if (windowcontextFromWindow !== null && windowcontextFromWindow === 'window.parent') { 28 | wContext = window.parent; 29 | } 30 | if (!wContext['tiledesk']) { 31 | return; 32 | } else { 33 | const baseLocation = wContext['tiledesk'].getBaseLocation(); 34 | if (baseLocation !== undefined) { 35 | // globals.setParameter('baseLocation', baseLocation); 36 | this.g.baseLocation = baseLocation; 37 | urlConfigFile = this.g.baseLocation + this.appConfig.remoteConfigUrl; 38 | } 39 | } 40 | } 41 | // END GET BASE URL and create absolute url of remoteConfigUrl // 42 | const that = this; 43 | return this.http.get(urlConfigFile).toPromise().then(data => { 44 | that.appConfig = data; 45 | }).catch(err => { 46 | console.error('error loadAppConfig', err); 47 | }); 48 | } 49 | 50 | getConfig() { 51 | return this.appConfig; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/chat21-core/providers/firebase/firebase-image-repo.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { environment } from '../../../environments/environment'; 4 | 5 | // services 6 | import { ImageRepoService } from '../abstract/image-repo.service'; 7 | 8 | // firebase 9 | // import firebase from 'firebase/app'; 10 | // import 'firebase/storage'; 11 | 12 | // @Injectable({ providedIn: 'root' }) 13 | @Injectable() 14 | export class FirebaseImageRepoService extends ImageRepoService { 15 | 16 | // private params 17 | private urlStorageBucket = environment.firebaseConfig.storageBucket + '/o/profiles%2F'; 18 | private baseImageURL: string; 19 | 20 | private firebase: any 21 | 22 | constructor(public http: HttpClient) { 23 | super(); 24 | this.initialize() 25 | } 26 | 27 | /** 28 | * @param uid 29 | */ 30 | getImagePhotoUrl(uid: string): string { 31 | this.baseImageURL = this.getImageBaseUrl() 32 | let sender_id = ''; 33 | if (uid && uid.includes('bot_')) { 34 | sender_id = uid.slice(4) 35 | } else { 36 | sender_id = uid 37 | } 38 | const firebase_photo = '/o/profiles%2F'+ sender_id + '%2Fphoto.jpg?alt=media' 39 | const firebase_thumbnail = '/o/profiles%2F'+ sender_id + '%2Fthumb_photo.jpg?alt=media' 40 | const imageurl = this.baseImageURL + this.firebase.storage().ref().bucket + firebase_thumbnail 41 | return imageurl; 42 | } 43 | 44 | 45 | checkImageExists(url: string, callback: (exist: boolean) => void): void { 46 | this.http.get(url, { responseType: 'blob' }).subscribe( res => { 47 | callback(true) 48 | },(error) => { 49 | callback(false) 50 | }) 51 | } 52 | 53 | 54 | private async initialize(){ 55 | const { default: firebase} = await import("firebase/app"); 56 | await Promise.all([import("firebase/storage")]); 57 | this.firebase = firebase 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.html: -------------------------------------------------------------------------------- 1 |
2 | 10 | 11 | 15 | 16 | 17 | 22 | 23 | 24 | 31 | 32 |
-------------------------------------------------------------------------------- /src/app/component/message/avatar/avatar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { ImageRepoService } from '../../../../chat21-core/providers/abstract/image-repo.service'; 3 | @Component({ 4 | selector: 'chat-avatar-image', 5 | templateUrl: './avatar.component.html', 6 | styleUrls: ['./avatar.component.scss'] 7 | }) 8 | export class AvatarComponent implements OnInit { 9 | 10 | @Input() senderID: string; 11 | @Input() senderFullname: string; 12 | @Input() baseLocation: string; 13 | url: string; 14 | constructor(private imageRepoService: ImageRepoService) { } 15 | 16 | ngOnInit() { 17 | if(this.senderID){ 18 | if(this.senderID.indexOf('bot_') !== -1 || this.senderFullname.toLowerCase().includes('bot')){ 19 | this.url = this.baseLocation +'/assets/images/tommy_bot_tiledesk.svg' 20 | }else if( this.senderID.indexOf('bot_') == -1){ 21 | this.url = this.baseLocation +'/assets/images/chat_human_avatar.svg' 22 | } 23 | let url = this.imageRepoService.getImagePhotoUrl(this.senderID) 24 | // this.imageRepoService.checkImageExists(url, (existImage)=> { 25 | // existImage? this.url = url: null; 26 | // }) 27 | this.checkImageExists(url, (existImage)=> { 28 | existImage? this.url = url: null; 29 | }) 30 | } 31 | 32 | } 33 | 34 | onBotImgError(event){ 35 | event.target.src = this.baseLocation +'/assets/images/tommy_bot_tiledesk.svg' 36 | } 37 | onHumanImgError(event) { 38 | event.target.src = this.baseLocation + "/assets/images/chat_human_avatar.svg" 39 | } 40 | 41 | onLoadedBot(event){ 42 | // console.log('LOADED Bot avatar image...') 43 | } 44 | 45 | onLoadedHuman(event){ 46 | // console.log('LOADED Bot human image...') 47 | } 48 | 49 | 50 | checkImageExists(imageUrl, callBack) { 51 | var imageData = new Image(); 52 | imageData.onload = function () { 53 | callBack(true); 54 | }; 55 | imageData.onerror = function () { 56 | callBack(false); 57 | }; 58 | imageData.src = imageUrl; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/app/component/message/buttons/text-button/text-button.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | --backgroundColor: #{var(--blue)}; 3 | --buttonTextColor: #{var(--bck-msg-sent)}; 4 | --buttonHoverBackgroundColor: #{var(--bck-msg-sent)}; 5 | --buttonHoverTextColor: #{var(--blue)}; 6 | --buttonFontSize: #{var(--button-font-size)}; 7 | --max-width: #{var(--button-in-msg-max-width)}; 8 | --padding: #{var(--button-in-msg-padding)}; 9 | --font-family: #{var(--button-in-msg-font-family)}; 10 | --buttonBorderColor: #{var(--button-border-color)}; 11 | --buttonBackgroundColor: #{var(--button-background-color)}; 12 | --buttonColor: #{var(--button-color)}; 13 | } 14 | 15 | 16 | .button-in-msg { 17 | padding: var(--padding); 18 | position: relative; 19 | min-width: inherit; 20 | cursor: pointer; 21 | border: 1px solid var(--buttonBorderColor); 22 | background: var(--buttonBackgroundColor); 23 | font-family: var(--font-family); 24 | font-size: var(--buttonFontSize); 25 | color: var(--buttonTextColor); 26 | overflow: hidden; 27 | -o-text-overflow: ellipsis; 28 | text-overflow: ellipsis; 29 | // white-space: nowrap; 30 | word-wrap: break-word; 31 | // letter-spacing: -0.24px; 32 | -webkit-font-smoothing: antialiased; 33 | line-height: 16px; 34 | font-weight: 500; 35 | border-radius: 12px; 36 | box-shadow: 0 1px 2px rgba(0, 0, 0, .05); 37 | padding: 10px 16px; 38 | margin: 3px; 39 | transition: background-color .2s ease; 40 | } 41 | 42 | .text { 43 | // color: rgb(42, 106, 193); 44 | transition: background-color .6s ease; 45 | &:focus, 46 | &:hover { 47 | color: var(--buttonHoverTextColor); 48 | background: var(--buttonHoverBackgroundColor); 49 | // transform: scale(1.05); 50 | .icon-button-action { 51 | svg { 52 | fill: var(--black); 53 | } 54 | } 55 | } 56 | } 57 | 58 | .disabled { 59 | // border: 1px solid #999999; 60 | // background-color: #cccccc; 61 | // color: #666666; 62 | pointer-events: none; 63 | } 64 | -------------------------------------------------------------------------------- /src/app/component/message-attachment/message-attachment.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 15 | 16 | 17 | 25 | 26 | 27 | 36 | 37 | 38 | 39 | 40 |
41 |
-------------------------------------------------------------------------------- /src/app/sass/_variables.scss: -------------------------------------------------------------------------------- 1 | // =========== COLORS =============== // 2 | :root{ 3 | --black: #1a1a1a; 4 | --light-white: #f7f7f7; 5 | --gray: #aaaaaa; 6 | --dark-gray: #5f6368; 7 | --light-gray: #eeeeee; 8 | --base-gray: #666666; 9 | --danger: #a94442; 10 | --blue: rgb(42, 106, 193); 11 | 12 | --trasp-light-gray: rgba(191, 191, 191, 0.2); 13 | $trasp-black:rgba(0,0,0,0.8); 14 | --trasp-light-black:rgba(0,0,0,0.2); 15 | 16 | // $theme-color: rgb(42, 106, 193); 17 | /** conversation **/ 18 | --bck-msg-sent: rgb(98, 168, 234); 19 | --col-msg-sent: #ffffff; 20 | 21 | --border-radius-bubble-message: 20px; 22 | --button-in-msg-font-size: 13px; 23 | --button-in-msg-font-family: 'Roboto','Google Sans', Helvetica, Arial, sans-serif; 24 | --button-in-msg-max-width: 280px; 25 | --button-in-msg-padding: 8px 16px; 26 | --button-color: #5c6c73; 27 | --button-background-color: #ffffff;//#f5f5f5; // #f9fcff; 28 | --button-border-color: #0000000f; //#e4ebf2; 29 | --button-font-size: 13px; 30 | 31 | --max-width-images: 230px; //change also MAX_WIDTH_IMAGES in constants.ts 32 | 33 | --bkg-color-info-message: rgba(24, 119, 242, 0.1); 34 | 35 | --chat-header-height: 52px; 36 | 37 | --chat-footer-height: 64px; 38 | --chat-footer-logo-height: 30px; 39 | --chat-footer-border-radius: 16px; 40 | --chat-footer-background-color: #f6f7fb; 41 | --chat-footer-color: #1a1a1a; 42 | --chat-footer-border-color-error: #aa0404; 43 | 44 | --icon-fill-color: #5f6368; 45 | 46 | --content-background-color: #fff; 47 | --content-text-color: var(--black); 48 | 49 | --avatar-height: 20px; 50 | --avatar-width: 20px; 51 | 52 | 53 | --font-family: 'Roboto','Google Sans', Helvetica, Arial, sans-serif; //Mulish, sans-serif; 54 | --font-family-bubble-message: 'Roboto','Google Sans', Helvetica, Arial, sans-serif; 55 | --font-family-callout: 'Helvetica Neue', 'Apple Color Emoji', Helvetica, Arial, sans-serif; 56 | --font-family-powered-by: Mulish, sans-serif; 57 | 58 | --font-size-bubble-message: 1.4em; 59 | } 60 | 61 | $trasp-black:rgba(0,0,0,0.8); 62 | 63 | 64 | 65 | --------------------------------------------------------------------------------