diff --git a/public/config.json b/public/config.json index fad29ef06331f1bdeef1daaea5f44ea4d5e39465..6645f28f2a7be89a88953d9d7d6b0afd998b26d3 100644 --- a/public/config.json +++ b/public/config.json @@ -6,6 +6,10 @@ "title": "Chatterbox Test Room", "avatar": "https://i.imgur.com/KkSYCKB.png" }, + "footer": { + "chatterbox_link": "https://element.io/solutions/embedded-live-chat-for-customer-service", + "matrix_link": "https://matrix.org" + }, "token": "k9SL1R~GcdbjiFjC", "invite_user": "@botuser:matrix.midhun.dev", "encrypt_room": true, diff --git a/src/types/IChatterboxConfig.ts b/src/types/IChatterboxConfig.ts index c555e79db1c01bf8e01ca78a12ea543a183ed3f2..7883b127050e139e7116f959a8cb4984591ab475 100644 --- a/src/types/IChatterboxConfig.ts +++ b/src/types/IChatterboxConfig.ts @@ -11,6 +11,8 @@ export interface IChatterboxConfig { encrypt_room: boolean; // Configurations for header on chatterbox (containing title, avatar, minimize button) header: IHeader; + // Configurations for footer on chatterbox (containing what links to use) + footer: IFooter; // Token needed for token-authenticated registration token: string; // URL of the image that should be used as the users avatar @@ -23,3 +25,10 @@ interface IHeader { // An optional link to static avatar. If this is not given, the room avatar is used instead avatar?: string; } + +interface IFooter { + // Specifies the link which must be opened when chatterbox logo in the footer is clicked. + chatterbox_link: string; + // Specifies the link which must be opened when matrix branding in the footer is clicked. + matrix_link: string; +} diff --git a/src/ui/styles/style.css b/src/ui/styles/style.css index a04ea0ebf5fb34e8478f7d218f8b7238e256251d..2f318745d1caff92fd2213ea35a9e3d68f369c4e 100644 --- a/src/ui/styles/style.css +++ b/src/ui/styles/style.css @@ -99,11 +99,15 @@ body { margin: 10.5px 0; } -.FooterView>div { +.FooterView>div, .FooterView>a { display: flex; align-items: center; } +.FooterView a { + text-decoration: none; +} + .FooterView_matrix-branding { font-size: 12px; line-height: 15px; diff --git a/src/ui/views/AccountSetupView.ts b/src/ui/views/AccountSetupView.ts index 4057fd92f839d90e51b79f8de7f2fef73156d275..4d9d7b255d353f987c9e287d357ee66dcfaea095 100644 --- a/src/ui/views/AccountSetupView.ts +++ b/src/ui/views/AccountSetupView.ts @@ -13,7 +13,7 @@ export class AccountSetupView extends TemplateView<AccountSetupViewModel> { { className: "AccountSetupView" }, [ t.mapView((vm) => vm.privacyPolicyLink, (link) => link ? new PolicyAgreementView(vm) : new LoadingView()), - t.view(new FooterView()), + t.view(new FooterView(vm.footerViewModel)), ] ); } diff --git a/src/ui/views/ChatterboxView.ts b/src/ui/views/ChatterboxView.ts index c7e12bb280096e13529642ea206207208ae82a50..7c521829efc0c500981e6a47488505074259e208 100644 --- a/src/ui/views/ChatterboxView.ts +++ b/src/ui/views/ChatterboxView.ts @@ -9,7 +9,7 @@ export class ChatterboxView extends TemplateView<ChatterboxViewModel> { super(value); } - render(t) { + render(t, vm) { return t.div({ className: "ChatterboxView", }, [ t.mapView( @@ -24,7 +24,7 @@ export class ChatterboxView extends TemplateView<ChatterboxViewModel> { (vm) => vm.messageComposerViewModel, (vm) => (vm ? new MessageComposer(vm) : null) ), - t.view(new FooterView()), + t.view(new FooterView(vm.footerViewModel)), ]); } } diff --git a/src/ui/views/FooterView.ts b/src/ui/views/FooterView.ts index 3ddee056617fd57f2babf551829c59bd6d454454..f645b39289715122bae3ee594da6e81fecd61a80 100644 --- a/src/ui/views/FooterView.ts +++ b/src/ui/views/FooterView.ts @@ -1,15 +1,35 @@ import { TemplateView } from "hydrogen-view-sdk"; import chatterboxLogo from "../res/chat-bubbles.svg"; +import { FooterViewModel } from "../../viewmodels/FooterViewModel"; -export class FooterView extends TemplateView { - render(t) { - return t.div({ className: "FooterView" }, - [ - t.div([ - t.img({ src: chatterboxLogo, className: "FooterView_logo" }), - t.span({ className: "FooterView_chatterbox-branding" }, "Chatterbox"), - ]), - t.div({ className: "FooterView_matrix-branding" }, ["Powered by Matrix"]) - ]); +export class FooterView extends TemplateView<FooterViewModel> { + constructor(value) { + super(value); + } + + render(t, vm) { + return t.div({ className: "FooterView" }, [ + t.div([ + t.img({ src: chatterboxLogo, className: "FooterView_logo" }), + t.a( + { + className: "FooterView_chatterbox-branding", + href: vm.chatterboxLink, + target: "_top", + rel: "noopener" + }, + "Chatterbox" + ), + ]), + t.a( + { + className: "FooterView_matrix-branding", + href: vm.matrixLink, + target: "_top", + rel: "noopener" + }, + "Powered by Matrix" + ), + ]); } } diff --git a/src/viewmodels/AccountSetupViewModel.ts b/src/viewmodels/AccountSetupViewModel.ts index aa76de7c559ec0b3f8e324cd4b4b49a4b99541e1..3e83fa81dbc212e099b00b4c94e0439108417ea7 100644 --- a/src/viewmodels/AccountSetupViewModel.ts +++ b/src/viewmodels/AccountSetupViewModel.ts @@ -109,4 +109,8 @@ export class AccountSetupViewModel extends ViewModel { get privacyPolicyLink() { return this._privacyPolicyLink; } + + get footerViewModel() { + return this.options.footerVM; + } } diff --git a/src/viewmodels/ChatterboxViewModel.ts b/src/viewmodels/ChatterboxViewModel.ts index 05844c7e74671146bac4ac4b946455bcf031bd6c..2698e74e9a6bc5a145f61ce25990b06ce98fc58b 100644 --- a/src/viewmodels/ChatterboxViewModel.ts +++ b/src/viewmodels/ChatterboxViewModel.ts @@ -119,4 +119,8 @@ export class ChatterboxViewModel extends ViewModel { private get _session() { return this._client.session; } + + get footerViewModel() { + return this.options.footerVM; + } } diff --git a/src/viewmodels/FooterViewModel.ts b/src/viewmodels/FooterViewModel.ts new file mode 100644 index 0000000000000000000000000000000000000000..a3ec8d06de91a452af5ac0efdd0fc1ec6f39e899 --- /dev/null +++ b/src/viewmodels/FooterViewModel.ts @@ -0,0 +1,19 @@ +import { ViewModel } from "hydrogen-view-sdk"; +import { IChatterboxConfig } from "../types/IChatterboxConfig"; + +export class FooterViewModel extends ViewModel { + private _config: IChatterboxConfig; + + constructor(options) { + super(options); + this._config = options.config; + } + + get chatterboxLink(): string { + return this._config.footer?.chatterbox_link ?? null; + } + + get matrixLink(): string { + return this._config.footer?.matrix_link ?? null; + } +} diff --git a/src/viewmodels/RootViewModel.ts b/src/viewmodels/RootViewModel.ts index 82e515ccbde432136c005c35f9135fc601bb8cae..a904c333f2329438edcbb07ea6ec92df9b5e1d7b 100644 --- a/src/viewmodels/RootViewModel.ts +++ b/src/viewmodels/RootViewModel.ts @@ -3,6 +3,7 @@ import { IChatterboxConfig } from "../types/IChatterboxConfig"; import { ChatterboxViewModel } from "./ChatterboxViewModel"; import "hydrogen-view-sdk/style.css"; import { AccountSetupViewModel } from "./AccountSetupViewModel"; +import { FooterViewModel } from "./FooterViewModel"; import { MessageFromParent } from "../observables/MessageFromParent"; type Options = { platform: typeof Platform, navigation: typeof Navigation, urlCreator: ReturnType<typeof createRouter>, startMinimized: boolean }; @@ -16,12 +17,14 @@ export class RootViewModel extends ViewModel { private _messageFromParent: MessageFromParent = new MessageFromParent(); private _startMinimized: boolean; private _isWatchingNotificationCount: boolean; + private _footerViewModel: FooterViewModel; constructor(config: IChatterboxConfig, options: Options) { super(options); this._startMinimized = options.startMinimized; this._config = config; this._client = new Client(this.platform); + this._footerViewModel = new FooterViewModel(this.childOptions({ config: this._config })); this._setupNavigation(); this._messageFromParent.on("maximize", () => this.start()); // Chatterbox can be minimized via the start button on the parent page! @@ -60,6 +63,7 @@ export class RootViewModel extends ViewModel { client: this._client, config: this._config, state: this._state, + footerVM: this._footerViewModel, loginPromise, }) )); @@ -79,6 +83,7 @@ export class RootViewModel extends ViewModel { client: this._client, config: this._config, state: this._state, + footerVM: this._footerViewModel, }) )); this.emitChange("activeSection");