Przeglądaj źródła

Render the brand heading as a component

JC Brand 4 lat temu
rodzic
commit
383b6a27c3

+ 9 - 0
package-lock.json

@@ -11874,6 +11874,15 @@
 				"minimalistic-assert": "^1.0.1"
 				"minimalistic-assert": "^1.0.1"
 			}
 			}
 		},
 		},
+		"haunted": {
+			"version": "4.7.0",
+			"resolved": "https://registry.npmjs.org/haunted/-/haunted-4.7.0.tgz",
+			"integrity": "sha512-ajsUengbSL2ELljJ2VLU2o5BfG/drzZV+BvSLALvIBx+24owmxC5iW7+SK/Y667JaVmratEPR010bYpGKAt7ow==",
+			"dev": true,
+			"requires": {
+				"lit-html": "^1.0.0"
+			}
+		},
 		"he": {
 		"he": {
 			"version": "1.2.0",
 			"version": "1.2.0",
 			"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
 			"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",

+ 1 - 0
package.json

@@ -79,6 +79,7 @@
     "exports-loader": "^0.7.0",
     "exports-loader": "^0.7.0",
     "fast-text-encoding": "^1.0.2",
     "fast-text-encoding": "^1.0.2",
     "file-loader": "^6.0.0",
     "file-loader": "^6.0.0",
+    "haunted": "^4.7.0",
     "html-webpack-plugin": "^4.3.0",
     "html-webpack-plugin": "^4.3.0",
     "http-server": "^0.12.3",
     "http-server": "^0.12.3",
     "imports-loader": "^0.8.0",
     "imports-loader": "^0.8.0",

+ 16 - 12
sass/_controlbox.scss

@@ -67,9 +67,21 @@
 
 
     #controlbox {
     #controlbox {
         order: -1;
         order: -1;
-
         color: var(--controlbox-text-color);
         color: var(--controlbox-text-color);
 
 
+        converse-brand-heading {
+            width: 100%;
+            display: block;
+        }
+
+        .brand-name-wrapper {
+            font-size: 200%;
+        }
+
+        .brand-name-wrapper--fullscreen {
+            font-size: 100%;
+        }
+
         .open-rooms-toggle, .open-rooms-toggle .fa {
         .open-rooms-toggle, .open-rooms-toggle .fa {
             color: var(--groupchats-header-color) !important;
             color: var(--groupchats-header-color) !important;
             &:hover {
             &:hover {
@@ -78,7 +90,7 @@
         }
         }
 
 
         .box-flyout {
         .box-flyout {
-            background-color: white;
+            background-color: var(--controlbox-pane-background-color);
         }
         }
 
 
         margin-right: calc(3 * var(--chat-gutter));
         margin-right: calc(3 * var(--chat-gutter));
@@ -178,11 +190,7 @@
         }
         }
 
 
         #converse-login-panel {
         #converse-login-panel {
-            flex-direction: column;
-
-            .brand-heading {
-                color: var(--global-background-color);
-            }
+            flex-direction: row;
         }
         }
 
 
         .toggle-register-login {
         .toggle-register-login {
@@ -411,10 +419,6 @@
             }
             }
         }
         }
 
 
-        .brand-heading-container {
-            width: 100%;
-        }
-
         .controlbox-head {
         .controlbox-head {
             display: flex;
             display: flex;
             flex-direction: row-reverse;
             flex-direction: row-reverse;
@@ -497,7 +501,7 @@
             line-height: var(--line-height-huge);
             line-height: var(--line-height-huge);
         }
         }
 
 
-        .brand-heading-container {
+        converse-brand-heading {
             @include make-col(12);
             @include make-col(12);
             margin-top: 5em;
             margin-top: 5em;
             margin-bottom: 1em;
             margin-bottom: 1em;

+ 53 - 34
sass/_core.scss

@@ -137,7 +137,7 @@ body.converse-fullscreen {
         width: 100%;
         width: 100%;
     }
     }
 
 
-    .brand-heading-container {
+    converse-brand-heading {
         text-align: center;
         text-align: center;
     }
     }
 
 
@@ -147,43 +147,62 @@ body.converse-fullscreen {
         align-items: flex-start;
         align-items: flex-start;
         font-family: var(--branding-font);
         font-family: var(--branding-font);
         color: var(--link-color);
         color: var(--link-color);
-        margin-bottom: 1em;
+        margin-bottom: 0.75em;
 
 
-        .brand-name {
-            color: var(--link-color);
-            display: flex;
-            flex-direction: column;
-            align-items: center;
-            margin-top: -0.5em;
-        }
+      .brand-name-wrapper {
+        display: flex;
+        white-space: nowrap;
+        margin: auto;
+      }
 
 
-        .brand-name__text {
-            font-size: 120%;
-            vertical-align: text-bottom;
-        }
+      .brand-name {
+          color: var(--link-color);
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          margin-top: -0.25em;
+
+          .byline {
+              font-family: var(--heading-font);
+              font-size: 0.3em;
+              margin-bottom: 0.75em;
+              margin-left: -2.7em;
+              opacity: 0.55;
+              word-spacing: 5px;
+          }
+      }
 
 
-        .converse-svg-logo {
-            color: var(--link-color);
-            height: 1.5em;
-            margin-right: 0.25em;
-            margin-bottom: -0.25em;
-            .cls-1 {
-                isolation: isolate;
-            }
-            .cls-2 {
-                opacity: 0.5;
-                mix-blend-mode: multiply;
-            }
-            .cls-3 {
-                fill: var(--link-color);
-            }
-            .cls-4 {
-                fill: var(--link-color);
-            }
-        }
-    }
+      .brand-subtitle {
+          color: var(--text-color);
+      }
+
+      .brand-name__text {
+          font-size: 120%;
+          vertical-align: text-bottom;
+      }
+
+      .converse-svg-logo {
+          color: var(--link-color);
+          height: 1.5em;
+          margin-right: 0.25em;
+          margin-bottom: -0.25em;
+          .cls-1 {
+              isolation: isolate;
+          }
+          .cls-2 {
+              opacity: 0.5;
+              mix-blend-mode: multiply;
+          }
+          .cls-3 {
+              fill: var(--link-color);
+          }
+          .cls-4 {
+              fill: var(--link-color);
+          }
+      }
+  }
 
 
-    .brand-heading--inverse {
+  .brand-heading--inverse {
         .converse-svg-logo {
         .converse-svg-logo {
             margin-bottom: 0em;
             margin-bottom: 0em;
             margin-top: -0.2em;
             margin-top: -0.2em;

+ 1 - 14
spec/chatbox.js

@@ -260,27 +260,14 @@ describe("Chatboxes", function () {
                 async function (done, _converse) {
                 async function (done, _converse) {
 
 
             await mock.waitForRoster(_converse, 'current');
             await mock.waitForRoster(_converse, 'current');
-            await mock.openControlBox(_converse);
-
             const contact_jid = mock.cur_names[7].replace(/ /g,'.').toLowerCase() + '@montague.lit';
             const contact_jid = mock.cur_names[7].replace(/ /g,'.').toLowerCase() + '@montague.lit';
             await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
             await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length);
             await mock.openChatBoxFor(_converse, contact_jid);
             await mock.openChatBoxFor(_converse, contact_jid);
-            const controlview = _converse.chatboxviews.get('controlbox'), // The controlbox is currently open
-                  chatview = _converse.chatboxviews.get(contact_jid);
-
+            const chatview = _converse.chatboxviews.get(contact_jid);
             spyOn(chatview, 'close').and.callThrough();
             spyOn(chatview, 'close').and.callThrough();
-            spyOn(controlview, 'close').and.callThrough();
             spyOn(_converse.api, "trigger").and.callThrough();
             spyOn(_converse.api, "trigger").and.callThrough();
-
             // We need to rebind all events otherwise our spy won't be called
             // We need to rebind all events otherwise our spy won't be called
-            controlview.delegateEvents();
             chatview.delegateEvents();
             chatview.delegateEvents();
-
-            controlview.el.querySelector('.close-chatbox-button').click();
-            expect(controlview.close).toHaveBeenCalled();
-            await new Promise(resolve => _converse.api.listen.once('chatBoxClosed', resolve));
-            expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
-
             chatview.el.querySelector('.close-chatbox-button').click();
             chatview.el.querySelector('.close-chatbox-button').click();
             expect(chatview.close).toHaveBeenCalled();
             expect(chatview.close).toHaveBeenCalled();
             await new Promise(resolve => _converse.api.listen.once('chatBoxClosed', resolve));
             await new Promise(resolve => _converse.api.listen.once('chatBoxClosed', resolve));

+ 21 - 0
spec/controlbox.js

@@ -33,6 +33,27 @@ describe("The Controlbox", function () {
         done();
         done();
     }));
     }));
 
 
+
+    it("can be closed by clicking a DOM element with class 'close-chatbox-button'",
+            mock.initConverse(['chatBoxesFetched'], {}, async function (done, _converse) {
+
+        await mock.openControlBox(_converse);
+        const controlview = _converse.chatboxviews.get('controlbox');
+
+        spyOn(controlview, 'close').and.callThrough();
+        spyOn(_converse.api, "trigger").and.callThrough();
+
+        // We need to rebind all events otherwise our spy won't be called
+        controlview.delegateEvents();
+
+        controlview.el.querySelector('.close-chatbox-button').click();
+        expect(controlview.close).toHaveBeenCalled();
+        await new Promise(resolve => _converse.api.listen.once('chatBoxClosed', resolve));
+        expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
+        done();
+    }));
+
+
     describe("The \"Contacts\" section", function () {
     describe("The \"Contacts\" section", function () {
 
 
         it("can be used to add contact and it checks for case-sensivity",
         it("can be used to add contact and it checks for case-sensivity",

+ 42 - 0
src/components/brand-heading.js

@@ -0,0 +1,42 @@
+import { api } from "@converse/headless/converse-core";
+import { component } from 'haunted';
+import { html } from 'lit-html';
+
+export const ConverseBrandHeading = (o) => {
+    const is_fullscreen = api.settings.get('view_mode') === 'fullscreen';
+    return html`
+        <a class="brand-heading" href="https://conversejs.org" target="_blank" rel="noopener">
+            <span class="brand-name-wrapper ${is_fullscreen ? 'brand-name-wrapper--fullscreen' : '' }">
+                <svg class="converse-svg-logo"
+                    xmlns:svg="http://www.w3.org/2000/svg"
+                    xmlns="http://www.w3.org/2000/svg"
+                    xmlns:xlink="http://www.w3.org/1999/xlink"
+                    viewBox="0 0 364 364">
+                    <title>Converse</title>
+                    <g class="cls-1" id="g904">
+                        <g data-name="Layer 2">
+                            <g data-name="Layer 7">
+                                <path
+                                    class="cls-3"
+                                    d="M221.46,103.71c0,18.83-29.36,18.83-29.12,0C192.1,84.88,221.46,84.88,221.46,103.71Z" />
+                                <path
+                                    class="cls-4"
+                                    d="M179.9,4.15A175.48,175.48,0,1,0,355.38,179.63,175.48,175.48,0,0,0,179.9,4.15Zm-40.79,264.5c-.23-17.82,27.58-17.82,27.58,0S138.88,286.48,139.11,268.65ZM218.6,168.24A79.65,79.65,0,0,1,205.15,174a12.76,12.76,0,0,0-6.29,4.65L167.54,222a1.36,1.36,0,0,1-2.46-.8v-35.8a2.58,2.58,0,0,0-3.06-2.53c-15.43,3-30.23,7.7-42.73,19.94-38.8,38-29.42,105.69,16.09,133.16a162.25,162.25,0,0,1-91.47-67.27C-3.86,182.26,34.5,47.25,138.37,25.66c46.89-9.75,118.25,5.16,123.73,62.83C265.15,120.64,246.56,152.89,218.6,168.24Z" />
+                            </g>
+                        </g>
+                    </g>
+                </svg>
+                <span class="brand-name">
+                    <span class="brand-name__text">converse<span class="subdued">.js</span></span>
+                    ${ is_fullscreen ? html`<p class="byline">messaging freedom</p>` : '' }
+                </span>
+            </span>
+        </a>
+        ${ is_fullscreen ? html`
+            <p class="brand-subtitle">${o.version_name}</p>
+            <p class="brand-subtitle"><a target="_blank" rel="nofollow" href="https://conversejs.org">Open Source</a> XMPP chat client brought to you by <a target="_blank" rel="nofollow" href="https://opkode.com">Opkode</a> </p>
+            <p class="brand-subtitle"><a target="_blank" rel="nofollow" href="https://hosted.weblate.org/projects/conversejs/#languages">Translate</a> it into your own language</p>` : '' }
+        `;
+}
+
+api.elements.define('converse-brand-heading', component(ConverseBrandHeading, {'useShadowDOM': false}));

+ 9 - 20
src/converse-controlbox.js

@@ -4,16 +4,17 @@
  * @license Mozilla Public License (MPLv2)
  * @license Mozilla Public License (MPLv2)
  */
  */
 import "converse-chatview";
 import "converse-chatview";
+import "./components/brand-heading";
 import bootstrap from "bootstrap.native";
 import bootstrap from "bootstrap.native";
 import log from "@converse/headless/log";
 import log from "@converse/headless/log";
-import tpl_brand_heading from "templates/converse_brand_heading.html";
-import tpl_controlbox from "templates/controlbox.html";
+import tpl_controlbox from "templates/controlbox.js";
 import tpl_controlbox_toggle from "templates/controlbox_toggle.html";
 import tpl_controlbox_toggle from "templates/controlbox_toggle.html";
 import tpl_login_panel from "templates/login_panel.js";
 import tpl_login_panel from "templates/login_panel.js";
 import { Model } from '@converse/skeletor/src/model.js';
 import { Model } from '@converse/skeletor/src/model.js';
 import { View } from "@converse/skeletor/src/view";
 import { View } from "@converse/skeletor/src/view";
 import { __ } from './i18n';
 import { __ } from './i18n';
 import { _converse, api, converse } from "@converse/headless/converse-core";
 import { _converse, api, converse } from "@converse/headless/converse-core";
+import { render } from 'lit-html';
 
 
 const { Strophe, dayjs } = converse.env;
 const { Strophe, dayjs } = converse.env;
 const u = converse.env.utils;
 const u = converse.env.utils;
@@ -198,7 +199,12 @@ converse.plugins.add('converse-controlbox', {
                         this.model.set('closed', !api.settings.get('show_controlbox_by_default'));
                         this.model.set('closed', !api.settings.get('show_controlbox_by_default'));
                     }
                     }
                 }
                 }
-                this.el.innerHTML = tpl_controlbox(Object.assign(this.model.toJSON()));
+
+               const tpl_result = tpl_controlbox({
+                    'sticky_controlbox': api.settings.get('sticky_controlbox'),
+                     ...this.model.toJSON()
+                });
+                render(tpl_result, this.el);
 
 
                 if (!this.model.get('closed')) {
                 if (!this.model.get('closed')) {
                     this.show();
                     this.show();
@@ -221,22 +227,6 @@ converse.plugins.add('converse-controlbox', {
                 }
                 }
             },
             },
 
 
-             createBrandHeadingHTML () {
-                return tpl_brand_heading({
-                    'sticky_controlbox': api.settings.get('sticky_controlbox')
-                });
-            },
-
-            insertBrandHeading () {
-                const heading_el = this.el.querySelector('.brand-heading-container');
-                if (heading_el === null) {
-                    const el = this.el.querySelector('.controlbox-head');
-                    el.insertAdjacentHTML('beforeend', this.createBrandHeadingHTML());
-                } else {
-                    heading_el.outerHTML = this.createBrandHeadingHTML();
-                }
-            },
-
             renderLoginPanel () {
             renderLoginPanel () {
                 this.el.classList.add("logged-out");
                 this.el.classList.add("logged-out");
                 if (this.loginpanel) {
                 if (this.loginpanel) {
@@ -248,7 +238,6 @@ converse.plugins.add('converse-controlbox', {
                     const panes = this.el.querySelector('.controlbox-panes');
                     const panes = this.el.querySelector('.controlbox-panes');
                     panes.innerHTML = '';
                     panes.innerHTML = '';
                     panes.appendChild(this.loginpanel.render().el);
                     panes.appendChild(this.loginpanel.render().el);
-                    this.insertBrandHeading();
                 }
                 }
                 this.loginpanel.initPopovers();
                 this.loginpanel.initPopovers();
                 return this;
                 return this;

+ 1 - 26
src/converse-fullscreen.js

@@ -7,8 +7,7 @@ import "@converse/headless/converse-muc";
 import "converse-chatview";
 import "converse-chatview";
 import "converse-controlbox";
 import "converse-controlbox";
 import "converse-singleton";
 import "converse-singleton";
-import { _converse, api, converse } from "@converse/headless/converse-core";
-import tpl_brand_heading from "templates/inverse_brand_heading.html";
+import { api, converse } from "@converse/headless/converse-core";
 
 
 
 
 converse.plugins.add('converse-fullscreen', {
 converse.plugins.add('converse-fullscreen', {
@@ -17,30 +16,6 @@ converse.plugins.add('converse-fullscreen', {
         return _converse.isUniView();
         return _converse.isUniView();
     },
     },
 
 
-    overrides: {
-        // overrides mentioned here will be picked up by converse.js's
-        // plugin architecture they will replace existing methods on the
-        // relevant objects or classes.
-        //
-        // new functions which don't exist yet can also be added.
-
-        ControlBoxView: {
-            createBrandHeadingHTML() {
-                return tpl_brand_heading({
-                    'version_name': _converse.VERSION_NAME
-                });
-            },
-
-            insertBrandHeading () {
-                const el = _converse.root.getElementById('converse-login-panel');
-                el.parentNode.insertAdjacentHTML(
-                    'afterbegin',
-                    this.createBrandHeadingHTML()
-                );
-            }
-        }
-    },
-
     initialize () {
     initialize () {
         api.settings.extend({
         api.settings.extend({
             chatview_avatar_height: 50,
             chatview_avatar_height: 50,

+ 0 - 8
src/templates/controlbox.html

@@ -1,8 +0,0 @@
-<div class="flyout box-flyout">
-    <div class="chat-head controlbox-head">
-        {[ if (!o.sticky_controlbox) { ]}
-            <a class="chatbox-btn close-chatbox-button fa fa-times"></a>
-        {[ } ]}
-    </div>
-    <div class="controlbox-panes"></div>
-</div>

+ 9 - 0
src/templates/controlbox.js

@@ -0,0 +1,9 @@
+import { html } from 'lit-html';
+
+export default (o) => html`
+    <div class="flyout box-flyout">
+        <div class="chat-head controlbox-head">
+            ${o.sticky_controlbox ? '' : html`<a class="chatbox-btn close-chatbox-button fa fa-times"></a>` }
+        </div>
+        <div class="controlbox-panes"></div>
+    </div>`;

+ 0 - 26
src/templates/converse_brand_heading.html

@@ -1,26 +0,0 @@
-<span class="brand-heading-container">
-    <a class="brand-heading" href="https://conversejs.org" target="_blank" rel="noopener">
-        <svg class="converse-svg-logo"
-            xmlns:svg="http://www.w3.org/2000/svg"
-            xmlns="http://www.w3.org/2000/svg"
-            xmlns:xlink="http://www.w3.org/1999/xlink"
-            viewBox="0 0 364 364">
-            <title>Converse</title>
-            <g class="cls-1" id="g904">
-                <g data-name="Layer 2">
-                    <g data-name="Layer 7">
-                        <path
-                            class="cls-3"
-                            d="M221.46,103.71c0,18.83-29.36,18.83-29.12,0C192.1,84.88,221.46,84.88,221.46,103.71Z" />
-                        <path
-                            class="cls-4"
-                            d="M179.9,4.15A175.48,175.48,0,1,0,355.38,179.63,175.48,175.48,0,0,0,179.9,4.15Zm-40.79,264.5c-.23-17.82,27.58-17.82,27.58,0S138.88,286.48,139.11,268.65ZM218.6,168.24A79.65,79.65,0,0,1,205.15,174a12.76,12.76,0,0,0-6.29,4.65L167.54,222a1.36,1.36,0,0,1-2.46-.8v-35.8a2.58,2.58,0,0,0-3.06-2.53c-15.43,3-30.23,7.7-42.73,19.94-38.8,38-29.42,105.69,16.09,133.16a162.25,162.25,0,0,1-91.47-67.27C-3.86,182.26,34.5,47.25,138.37,25.66c46.89-9.75,118.25,5.16,123.73,62.83C265.15,120.64,246.56,152.89,218.6,168.24Z" />
-                    </g>
-                </g>
-            </g>
-        </svg>
-        <span class="brand-name">
-            <span class="brand-name__text">converse<span class="subdued">.js</span></span>
-        </span>
-    </a>
-</span>

+ 0 - 32
src/templates/inverse_brand_heading.html

@@ -1,32 +0,0 @@
-<div>
-    <div class="container brand-heading-container">
-        <h1 class="brand-heading brand-heading--inverse">
-            <svg class="converse-svg-logo"
-                xmlns:svg="http://www.w3.org/2000/svg"
-                xmlns="http://www.w3.org/2000/svg"
-                xmlns:xlink="http://www.w3.org/1999/xlink"
-                viewBox="0 0 364 364">
-                <title>Converse</title>
-                <g class="cls-1" id="g904">
-                    <g data-name="Layer 2">
-                        <g data-name="Layer 7">
-                            <path
-                                class="cls-3"
-                                d="M221.46,103.71c0,18.83-29.36,18.83-29.12,0C192.1,84.88,221.46,84.88,221.46,103.71Z" />
-                            <path
-                                class="cls-4"
-                                d="M179.9,4.15A175.48,175.48,0,1,0,355.38,179.63,175.48,175.48,0,0,0,179.9,4.15Zm-40.79,264.5c-.23-17.82,27.58-17.82,27.58,0S138.88,286.48,139.11,268.65ZM218.6,168.24A79.65,79.65,0,0,1,205.15,174a12.76,12.76,0,0,0-6.29,4.65L167.54,222a1.36,1.36,0,0,1-2.46-.8v-35.8a2.58,2.58,0,0,0-3.06-2.53c-15.43,3-30.23,7.7-42.73,19.94-38.8,38-29.42,105.69,16.09,133.16a162.25,162.25,0,0,1-91.47-67.27C-3.86,182.26,34.5,47.25,138.37,25.66c46.89-9.75,118.25,5.16,123.73,62.83C265.15,120.64,246.56,152.89,218.6,168.24Z" />
-                        </g>
-                    </g>
-                </g>
-            </svg>
-            <span class="brand-name">
-                <span class="brand-name__text">converse<span class="subdued">.js</span></span>
-                <p class="byline">messaging freedom</p>
-            </span>
-        </h1>
-        <p class="brand-subtitle">{{{o.version_name}}}</p>
-        <p class="brand-subtitle"><a target="_blank" rel="nofollow" href="https://conversejs.org">Open Source</a> XMPP chat client brought to you by <a target="_blank" rel="nofollow" href="https://opkode.com">Opkode</a> </p>
-        <p class="brand-subtitle"><a target="_blank" rel="nofollow" href="https://hosted.weblate.org/projects/conversejs/#languages">Translate</a> it into your own language</p>
-    </div>
-</div>

+ 1 - 0
src/templates/login_panel.js

@@ -87,6 +87,7 @@ const form_fields = (o) => {
 
 
 
 
 export default (o) => html`
 export default (o) => html`
+    <converse-brand-heading></converse-brand-heading>
     <form id="converse-login" class="converse-form" method="post">
     <form id="converse-login" class="converse-form" method="post">
         <div class="conn-feedback fade-in ${ !o.conn_feedback_subject ? 'hidden' : o.conn_feedback_class }">
         <div class="conn-feedback fade-in ${ !o.conn_feedback_subject ? 'hidden' : o.conn_feedback_class }">
             <p class="feedback-subject">${ o.conn_feedback_subject }</p>
             <p class="feedback-subject">${ o.conn_feedback_subject }</p>

+ 1 - 1
src/templates/user_settings_modal.js

@@ -56,7 +56,7 @@ export default (o) => {
 
 
                         <span class="modal-alert"></span>
                         <span class="modal-alert"></span>
                         <br/>
                         <br/>
-                        <div class="container brand-heading-container">
+                        <div class="container">
                             <h6 class="brand-heading">Converse</h6>
                             <h6 class="brand-heading">Converse</h6>
                             <p class="brand-subtitle">${o.version_name}</p>
                             <p class="brand-subtitle">${o.version_name}</p>
                             <p class="brand-subtitle">${unsafeHTML(xss.filterXSS(first_subtitle, {'whiteList': {'a': []}}))}</p>
                             <p class="brand-subtitle">${unsafeHTML(xss.filterXSS(first_subtitle, {'whiteList': {'a': []}}))}</p>