Procházet zdrojové kódy

Update the login form

- Add navbar
- Add footer
JC Brand před 3 měsíci
rodič
revize
8142bbd3c8

+ 1 - 1
README.md

@@ -16,7 +16,7 @@ You can either use it as a webchat app, or you can integrate it into your own we
 It's 100% client-side JavaScript, HTML and CSS and the only backend required
 is a modern XMPP server.
 
-Please support this project via [Patreon](https://www.patreon.com/jcbrand) or [Liberapay](https://liberapay.com/jcbrand)
+Please consider supporting this project via [Github](https://github.com/sponsors/jcbrand), [Patreon](https://www.patreon.com/jcbrand) or [Liberapay](https://liberapay.com/jcbrand)
 
 ## Demo
 

+ 1 - 0
fullscreen.html

@@ -50,6 +50,7 @@
     @licend
     */
     converse.initialize({
+        theme: 'dracula',
         authentication: 'login',
         auto_away: 300,
         auto_reconnect: true,

+ 1 - 42
index.html

@@ -10,17 +10,11 @@
     <meta name="author" content="JC Brand" />
     <meta name="keywords" content="xmpp chat webchat converse.js Converse" />
 
-    <!-- These files are NOT needed when using converse.js in your own project. -->
     <link rel="shortcut icon" type="image/ico" href="/dist/favicon.ico"/>
+    <link type="text/css" rel="stylesheet" media="screen" href="/dist/converse.min.css" />
     <link type="text/css" rel="stylesheet" media="screen" href="/dist/website.min.css" />
     <noscript><p><img src="//stats.opkode.com/piwik.php?idsite=1" style="border:0;" alt="" /></p></noscript>
     <script type="text/javascript" src="analytics.js"></script>
-    <!-- *********************************************************************** -->
-
-    <link rel="manifest" href="./manifest.json">
-    <link type="text/css" rel="stylesheet" media="screen" href="/dist/converse.min.css" />
-    <script src="https://cdn.conversejs.org/3rdparty/libsignal-protocol.min.js"></script>
-    <script src="/dist/converse.min.js"></script>
 </head>
 
 <body id="page-top" data-spy="scroll" class="converse-website">
@@ -347,39 +341,4 @@
     </section>
 </section>
 </body>
-
-<script>
-    /*
-    @licstart
-    This is free and unencumbered software released into the public domain.
-
-    Anyone is free to copy, modify, publish, use, compile, sell, or
-    distribute this software, either in source code form or as a compiled
-    binary, for any purpose, commercial or non-commercial, and by any
-    means.
-
-    In jurisdictions that recognize copyright laws, the author or authors
-    of this software dedicate any and all copyright interest in the
-    software to the public domain. We make this dedication for the benefit
-    of the public at large and to the detriment of our heirs and
-    successors. We intend this dedication to be an overt act of
-    relinquishment in perpetuity of all present and future rights to this
-    software under copyright law.
-
-    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 NONINFRINGEMENT.
-    IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
-    OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
-    ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-    OTHER DEALINGS IN THE SOFTWARE.
-
-    For more information, please refer to <https://unlicense.org/>
-    @licend
-    */
-    converse.initialize({
-        // Please use this connection manager only for testing purposes
-        bosh_service_url: 'https://conversejs.org/http-bind/'
-    });
-</script>
 </html>

+ 1 - 0
src/plugins/controlbox/controlbox.js

@@ -1,6 +1,7 @@
 import { _converse, api, constants, u } from '@converse/headless';
 import { CustomElement } from 'shared/components/element.js';
 import tplControlbox from './templates/controlbox.js';
+import './navbar.js';
 
 const { LOGOUT } = constants;
 

+ 22 - 0
src/plugins/controlbox/navbar.js

@@ -0,0 +1,22 @@
+import { api } from '@converse/headless';
+import { CustomElement } from 'shared/components/element.js';
+import tplNavbar from './templates/navbar.js';
+import './modals/about.js';
+
+class NavBar extends CustomElement {
+    render() {
+        return tplNavbar(this);
+    }
+
+    /**
+     * @param {Event} ev
+     */
+    openAboutDialog(ev) {
+        ev.preventDefault();
+        api.modal.show('converse-about-modal');
+    }
+}
+
+api.elements.define('converse-controlbox-navbar', NavBar);
+
+export default NavBar;

+ 6 - 2
src/plugins/controlbox/styles/_controlbox.scss

@@ -254,9 +254,13 @@
                 }
 
                 converse-brand-logo {
+                    margin-top: 2em;
+                    margin-bottom: 0;
                     @include make-col(12);
-                    margin-top: 5em;
-                    margin-bottom: 1em;
+                    @include media-breakpoint-up(xl) {
+                        margin-top: 5em;
+                        margin-bottom: 1em;
+                    }
                     .brand-heading {
                         width: 100%;
                         font-size: 500%;

+ 62 - 10
src/plugins/controlbox/styles/loginform.scss

@@ -1,6 +1,6 @@
-@import "bootstrap/scss/functions";
-@import "bootstrap/scss/variables";
-@import "bootstrap/scss/mixins";
+@import 'bootstrap/scss/functions';
+@import 'bootstrap/scss/variables';
+@import 'bootstrap/scss/mixins';
 
 .conversejs {
     #controlbox {
@@ -16,8 +16,15 @@
 
         .controlbox-pane {
             .switch-form {
+                color: var(--subdued-color);
                 text-align: center;
-                padding: 0.5em 0;
+                padding: 2em 0;
+            }
+
+            .login-form-container {
+                display: flex;
+                flex-direction: column;
+                height: 100%;
             }
         }
     }
@@ -33,6 +40,10 @@
                     }
                 }
 
+                converse-brand-logo {
+                    padding-bottom: 1em;
+                }
+
                 .login-trusted {
                     white-space: nowrap;
                     font-size: 90%;
@@ -47,8 +58,10 @@
                     margin-top: 0.5em;
                 }
 
-                #converse-register, #converse-login {
+                #converse-register,
+                #converse-login {
                     @include make-col(12);
+                    padding-top: 0;
                     padding-bottom: 0;
                 }
 
@@ -67,8 +80,43 @@
                     line-height: var(--line-height-huge);
                 }
 
-                #converse-register, #converse-login {
+                converse-brand-logo {
+                    padding-bottom: 1em;
+                    @include media-breakpoint-up(md) {
+                        padding-bottom: 2em;
+                    }
+                    @include media-breakpoint-up(xl) {
+                        padding-bottom: 5em;
+                    }
+                }
+
+                converse-footer {
+                    display: none;
+                    @include media-breakpoint-up(sm) {
+                        display: block;
+                        margin-bottom: 2em;
+                    }
+                    font-size: 100%;
+                    text-align: center;
+                    font-size: 90%;
+                }
+
+                #converse-login-panel {
+                    display: block;
+                    @include media-breakpoint-up(sm) {
+                        padding-top: 5vh;
+                    }
+                }
+
+                #converse-register,
+                #converse-login {
                     margin: auto;
+                    @include media-breakpoint-up(xs) {
+                        padding: 1em 1em;
+                    }
+                    @include media-breakpoint-up(sm) {
+                        padding: 3em 2em 3em;
+                    }
                     @include make-col-ready();
                     @include make-col(8);
 
@@ -79,13 +127,17 @@
                         @include make-col(8);
                     }
                     @include media-breakpoint-up(lg) {
-                        @include make-col(6);
+                        @include make-col(4);
+                    }
+                    @include media-breakpoint-up(xl) {
+                        @include make-col(4);
                     }
-                    .title, .instructions {
+                    .title,
+                    .instructions {
                         margin: 0.5em 0;
                     }
-                    input[type=submit],
-                    input[type=button] {
+                    input[type='submit'],
+                    input[type='button'] {
                         width: auto;
                     }
                 }

+ 1 - 1
src/plugins/controlbox/templates/buttons.js

@@ -39,7 +39,7 @@ function tplCloseButton(el) {
 export default (el) => {
     const is_connected = el.model.get('connected');
     const show_settings_button = api.settings.get('show_client_info') || api.settings.get('allow_adhoc_commands');
-    return html` <div class="btn-toolbar g-0">
+    return html`<div class="btn-toolbar g-0">
         ${is_connected && show_settings_button ? tplUserSettingsButton(el) : ''}
         ${is_connected && api.settings.get('allow_logout') ? tplSignout() : ''}
         ${api.settings.get('sticky_controlbox') ? '' : tplCloseButton(el)}

+ 3 - 1
src/plugins/controlbox/templates/controlbox.js

@@ -1,6 +1,6 @@
-import tplSpinner from 'templates/spinner.js';
 import { _converse, api, converse, constants } from '@converse/headless';
 import { html } from 'lit';
+import tplSpinner from 'templates/spinner.js';
 
 const { Strophe } = converse.env;
 const { ANONYMOUS } = constants;
@@ -26,6 +26,7 @@ function whenNotConnected(el) {
  * @param {import('../controlbox').default} el
  */
 export default (el) => {
+    const is_fullscreen = api.settings.get('view_mode') === 'fullscreen';
     return html` <div class="flyout box-flyout">
         <converse-dragresize></converse-dragresize>
         ${
@@ -33,6 +34,7 @@ export default (el) => {
                 html`<converse-user-profile></converse-user-profile>` :
                 html`<converse-controlbox-buttons class="controlbox-padded"></converse-controlbox-buttons>`
         }
+        ${ is_fullscreen ? html`<converse-controlbox-navbar></converse-controlbox-navbar>` : '' }
         <div class="controlbox-pane">
             ${el.model.get('connected')
                 ? html`<converse-headlines-feeds-list class="controlbox-section"></converse-headlines-feeds-list>

+ 76 - 73
src/plugins/controlbox/templates/loginform.js

@@ -1,9 +1,10 @@
-import { html } from "lit";
-import { _converse, api, constants } from "@converse/headless";
-import "shared/components/brand-heading.js";
-import tplSpinner from "templates/spinner.js";
-import { CONNECTION_STATUS_CSS_CLASS } from "../constants.js";
-import { __ } from "i18n";
+import { html } from 'lit';
+import { _converse, api, constants } from '@converse/headless';
+import tplSpinner from 'templates/spinner.js';
+import { CONNECTION_STATUS_CSS_CLASS } from '../constants.js';
+import { __ } from 'i18n';
+import 'shared/components/brand-heading.js';
+import 'shared/components/footer.js';
 
 const { ANONYMOUS, EXTERNAL, LOGIN, PREBIND, CONNECTION_STATUS } = constants;
 
@@ -12,31 +13,31 @@ const { ANONYMOUS, EXTERNAL, LOGIN, PREBIND, CONNECTION_STATUS } = constants;
  */
 function tplTrustCheckbox(checked) {
     const i18n_hint_trusted = __(
-        "To improve performance, we cache your data in this browser. " +
-            "Uncheck this box if this is a public computer or if you want your data to be deleted when you log out. " +
+        'To improve performance, we cache your data in this browser. ' +
+            'Uncheck this box if this is a public computer or if you want your data to be deleted when you log out. ' +
             "It's important that you explicitly log out, otherwise not all cached data might be deleted. " +
-            "Please note, when using an untrusted device, OMEMO encryption is NOT available."
+            'Please note, when using an untrusted device, OMEMO encryption is NOT available.'
     );
-    const i18n_trusted = __("This is a trusted device");
+    const i18n_trusted = __('This is a trusted device');
     return html`
-        <div class="form-check login-trusted">
+        <div class="form-check mb-2 login-trusted">
             <input
                 id="converse-login-trusted"
                 type="checkbox"
-                class="form-check-input p-1 me-1"
+                class="form-check-input"
                 name="trusted"
                 ?checked=${checked}
             />
             <label for="converse-login-trusted" class="form-check-label login-trusted__desc">${i18n_trusted}</label>
-            <converse-popover title="${__("Info")}" text="${i18n_hint_trusted}"></converse-popover>
+            <converse-popover title="${__('Info')}" text="${i18n_hint_trusted}"></converse-popover>
         </div>
     `;
 }
 
 export function tplConnectionURLInput() {
-    const i18n_connection_url = __("Connection URL");
-    const i18n_form_help = __("HTTP or websocket URL that is used to connect to your XMPP server");
-    const i18n_placeholder = __("e.g. wss://example.org/xmpp-websocket");
+    const i18n_connection_url = __('Connection URL');
+    const i18n_form_help = __('HTTP or websocket URL that is used to connect to your XMPP server');
+    const i18n_placeholder = __('e.g. wss://example.org/xmpp-websocket');
     return html`
         <div class="mb-3 fade-in">
             <label for="converse-conn-url" class="form-label">${i18n_connection_url}</label>
@@ -54,7 +55,7 @@ export function tplConnectionURLInput() {
 }
 
 function tplPasswordInput() {
-    const i18n_password = __("Password");
+    const i18n_password = __('Password');
     return html`
         <div class="mb-3">
             <label for="converse-login-password" class="form-label">${i18n_password}</label>
@@ -62,7 +63,7 @@ function tplPasswordInput() {
                 id="converse-login-password"
                 class="form-control"
                 required="required"
-                value="${api.settings.get("password") ?? ""}"
+                value="${api.settings.get('password') ?? ''}"
                 type="password"
                 name="password"
                 placeholder="${i18n_password}"
@@ -72,71 +73,69 @@ function tplPasswordInput() {
 }
 
 function tplRegisterLink() {
-    const i18n_create_account = __("Create an account");
+    const i18n_create_account = __('Create an account');
     const i18n_hint_no_account = __("Don't have a chat account?");
     return html`
-        <fieldset class="switch-form">
-            <p>${i18n_hint_no_account}</p>
-            <p>
-                <a class="register-account toggle-register-login" href="#converse/register">${i18n_create_account}</a>
-            </p>
-        </fieldset>
+        <div class="mt-3 text-center switch-form">
+            <p class="mb-1">${i18n_hint_no_account}</p>
+            <a class="register-account toggle-register-login" href="#converse/register">${i18n_create_account}</a>
+        </div>
     `;
 }
 
 function tplShowRegisterLink() {
     return (
-        api.settings.get("allow_registration") &&
-        !api.settings.get("auto_login") &&
-        _converse.pluggable.plugins["converse-register"].enabled(_converse)
+        api.settings.get('allow_registration') &&
+        !api.settings.get('auto_login') &&
+        _converse.pluggable.plugins['converse-register'].enabled(_converse)
     );
 }
 
 function tplAuthFields() {
-    const authentication = api.settings.get("authentication");
-    const i18n_login = __("Log in");
-    const i18n_xmpp_address = __("XMPP Address");
-    const locked_domain = api.settings.get("locked_domain");
-    const default_domain = api.settings.get("default_domain");
-    const placeholder_username = ((locked_domain || default_domain) && __("Username")) || __("user@domain");
-    const show_trust_checkbox = api.settings.get("allow_user_trust_override");
+    const authentication = api.settings.get('authentication');
+    const i18n_login = __('Log in');
+    const i18n_xmpp_address = __('XMPP Address');
+    const locked_domain = api.settings.get('locked_domain');
+    const default_domain = api.settings.get('default_domain');
+    const placeholder_username = ((locked_domain || default_domain) && __('Username')) || __('user@domain');
+    const show_trust_checkbox = api.settings.get('allow_user_trust_override');
 
     return html`
-        <fieldset class="form-group">
-            <div class="mb-3">
-                <label for="converse-login-jid" class="form-label">${i18n_xmpp_address}:</label>
-                <input
-                    id="converse-login-jid"
-                    ?autofocus=${api.settings.get("auto_focus") ? true : false}
-                    value="${api.settings.get("jid") ?? ""}"
-                    required
-                    class="form-control"
-                    type="text"
-                    name="jid"
-                    placeholder="${placeholder_username}"
-                />
-            </div>
-            ${authentication !== EXTERNAL ? tplPasswordInput() : ""}
-            ${api.settings.get("show_connection_url_input") ? tplConnectionURLInput() : ""}
-            ${show_trust_checkbox ? tplTrustCheckbox(show_trust_checkbox === "off" ? false : true) : ""}
-        </fieldset>
-        <fieldset class="form-group buttons">
-            <input class="btn btn-primary" type="submit" value="${i18n_login}" />
-        </fieldset>
-        ${tplShowRegisterLink() ? tplRegisterLink() : ""}
+        <div class="mb-3">
+            <label for="converse-login-jid" class="form-label">${i18n_xmpp_address}:</label>
+            <input
+                id="converse-login-jid"
+                ?autofocus=${api.settings.get('auto_focus') ? true : false}
+                value="${api.settings.get('jid') ?? ''}"
+                required
+                class="form-control"
+                type="text"
+                name="jid"
+                placeholder="${placeholder_username}"
+            />
+        </div>
+        ${authentication !== EXTERNAL ? tplPasswordInput() : ''}
+        ${api.settings.get('show_connection_url_input') ? tplConnectionURLInput() : ''}
+        ${show_trust_checkbox ? tplTrustCheckbox(show_trust_checkbox === 'off' ? false : true) : ''}
+        <div class="text-center mb-3">
+            <button class="btn btn-primary px-5 mx-auto" type="submit">${i18n_login}</button>
+        </div>
+        ${tplShowRegisterLink() ? tplRegisterLink() : ''}
     `;
 }
 
 function tplFormFields() {
-    const authentication = api.settings.get("authentication");
-    const i18n_disconnected = __("Disconnected");
-    const i18n_anon_login = __("Click here to log in anonymously");
+    const authentication = api.settings.get('authentication');
+    const i18n_disconnected = __('Disconnected');
+    const i18n_anon_login = __('Click here to log in anonymously');
     return html`
-        ${authentication == LOGIN || authentication == EXTERNAL ? tplAuthFields() : ""}
+        ${authentication == LOGIN || authentication == EXTERNAL ? tplAuthFields() : ''}
         ${authentication == ANONYMOUS
-            ? html`<input class="btn btn-primary login-anon" type="submit" value="${i18n_anon_login}" />`
-            : ""}
-        ${authentication == PREBIND ? html`<p>${i18n_disconnected}</p>` : ""}
+            ? html`<div class="text-center mb-3">
+                  <button class="btn btn-primary login-anon px-5 mx-auto" type="submit">${i18n_anon_login}</button>
+              </div>`
+            : ''}
+        ${authentication == PREBIND ? html`<p class="alert alert-warning">${i18n_disconnected}</p>` : ''}
     `;
 }
 
@@ -145,19 +144,23 @@ function tplFormFields() {
  */
 export default (el) => {
     const { connfeedback } = _converse.state;
-    const connection_status = connfeedback.get("connection_status");
-    const feedback_class = CONNECTION_STATUS_CSS_CLASS?.[connection_status] ?? "none";
-    const conn_feedback_message = connfeedback.get("message");
-    return html` <converse-brand-heading></converse-brand-heading>
+    const connection_status = connfeedback.get('connection_status');
+    const feedback_class = CONNECTION_STATUS_CSS_CLASS?.[connection_status] ?? 'none';
+    const conn_feedback_message = connfeedback.get('message');
+    const is_fullscreen = api.settings.get('view_mode') === 'fullscreen';
+    return html` <div class="login-form-container">
+        <converse-brand-logo></converse-brand-logo>
         <form id="converse-login" class="converse-form" method="post" @submit=${el.onLoginFormSubmitted}>
             <div
-                class="alert ${`alert-${feedback_class}`} fade-in conn-feedback ${!conn_feedback_message
-                    ? "d-none"
-                    : ""}"
+                class="alert ${`alert-${feedback_class}`} conn-feedback mb-3 ${!conn_feedback_message ? 'd-none' : ''}"
                 role="alert"
             >
                 <span class="feedback-message">${conn_feedback_message}</span>
             </div>
-            ${CONNECTION_STATUS[connection_status] === "CONNECTING" ? tplSpinner() : tplFormFields()}
-        </form>`;
+            ${CONNECTION_STATUS[connection_status] === 'CONNECTING'
+                ? html`<div class="text-center my-3">${tplSpinner()}</div>`
+                : tplFormFields()}
+        </form>
+        ${is_fullscreen ? html`<converse-footer></converse-footer>` : ''}
+    </div>`;
 };

+ 39 - 0
src/plugins/controlbox/templates/navbar.js

@@ -0,0 +1,39 @@
+import { html } from 'lit';
+
+/**
+ * @param {import('../navbar.js').default} el
+ */
+export default (el) => {
+    return html`
+        <nav class="navbar navbar-expand-lg">
+            <div class="container-fluid">
+                <div class="collapse navbar-collapse" id="navbarSupportedContent">
+                    <ul class="navbar-nav ms-auto mb-2 mb-lg-0">
+                        <li class="nav-item">
+                            <a class="nav-link" href="#" @click=${el.openAboutDialog}>About</a>
+                        </li>
+                        <li class="nav-item">
+                            <a
+                                class="nav-link"
+                                target="_blank"
+                                rel="noopener"
+                                href="https://github.com/conversejs/converse.js"
+                                >Github</a
+                            >
+                        </li>
+                        <li class="nav-item">
+                            <a class="nav-link" target="_blank" rel="noopener" href="https://conversejs.org/docs/html"
+                                >Documentation</a
+                            >
+                        </li>
+                        <li class="nav-item">
+                            <a class="nav-link" target="_blank" rel="noopener" href="https://opkode.com/contact.html"
+                                >Contact</a
+                            >
+                        </li>
+                    </ul>
+                </div>
+            </div>
+        </nav>
+    `;
+};

+ 4 - 4
src/plugins/controlbox/tests/login.js

@@ -28,12 +28,12 @@ describe("The Login Form", function () {
 
         expect(_converse.config.get('trusted')).toBe(true);
         expect(u.getDefaultStore()).toBe('persistent');
-        cbview.querySelector('input[type="submit"]').click();
+        cbview.querySelector('button[type="submit"]').click();
         expect(_converse.config.get('trusted')).toBe(true);
         expect(u.getDefaultStore()).toBe('persistent');
 
         checkbox.click();
-        cbview.querySelector('input[type="submit"]').click();
+        cbview.querySelector('button[type="submit"]').click();
         expect(_converse.config.get('trusted')).toBe(false);
         expect(u.getDefaultStore()).toBe('session');
     }));
@@ -61,12 +61,12 @@ describe("The Login Form", function () {
         cbview.querySelector('input[name="jid"]').value = 'romeo@montague.lit';
         cbview.querySelector('input[name="password"]').value = 'secret';
 
-        cbview.querySelector('input[type="submit"]').click();
+        cbview.querySelector('button[type="submit"]').click();
         expect(_converse.config.get('trusted')).toBe(false);
         expect(u.getDefaultStore()).toBe('session');
 
         checkbox.click();
-        cbview.querySelector('input[type="submit"]').click();
+        cbview.querySelector('button[type="submit"]').click();
         expect(_converse.config.get('trusted')).toBe(true);
         expect(u.getDefaultStore()).toBe('persistent');
     }));

+ 1 - 1
src/plugins/muc-views/modals/add-muc.js

@@ -1,6 +1,6 @@
 import { _converse, api, converse } from '@converse/headless';
-import tplAddMuc from './templates/add-muc.js';
 import BaseModal from 'plugins/modal/modal.js';
+import tplAddMuc from './templates/add-muc.js';
 import { __ } from 'i18n';
 
 import '../styles/add-muc-modal.scss';

+ 2 - 2
src/plugins/muc-views/tests/commands.js

@@ -845,7 +845,7 @@ describe("Groupchats", function () {
             textarea.value = '/destroy';
             let message_form = view.querySelector('converse-muc-message-form');
             message_form.onFormSubmitted(new Event('submit'));
-            let modal = await u.waitUntil(() => document.querySelector('.modal-dialog'));
+            let modal = await u.waitUntil(() => document.querySelector('converse-confirm-modal .modal-dialog'));
             await u.waitUntil(() => u.isVisible(modal));
 
             let challenge_el = modal.querySelector('[name="challenge"]');
@@ -894,7 +894,7 @@ describe("Groupchats", function () {
             textarea.value = '/destroy';
             message_form = view.querySelector('converse-muc-message-form');
             message_form.onFormSubmitted(new Event('submit'));
-            modal = await u.waitUntil(() => document.querySelector('.modal-dialog'));
+            modal = await u.waitUntil(() => document.querySelector('converse-confirm-modal .modal-dialog'));
             await u.waitUntil(() => u.isVisible(modal));
 
             challenge_el = modal.querySelector('[name="challenge"]');

+ 1 - 1
src/plugins/muc-views/tests/mep.js

@@ -211,7 +211,7 @@ describe("A XEP-0316 MEP notification", function () {
         const action = view.querySelector('converse-message-actions converse-dropdown .chat-msg__action');
         expect(action.textContent.trim()).toBe('Retract');
         action.click();
-        await u.waitUntil(() => u.isVisible(document.querySelector('#converse-modals .modal')));
+        await u.waitUntil(() => u.isVisible(document.querySelector('converse-confirm-modal.modal')));
         const submit_button = document.querySelector('#converse-modals .modal button[type="submit"]');
         submit_button.click();
 

+ 4 - 4
src/plugins/muc-views/tests/retractions.js

@@ -21,7 +21,7 @@ async function sendAndThenRetractMessage (_converse, view) {
 
     const retract_button = await u.waitUntil(() => view.querySelector('.chat-msg__content .chat-msg__action-retract'));
     retract_button.click();
-    await u.waitUntil(() => u.isVisible(document.querySelector('#converse-modals .modal')));
+    await u.waitUntil(() => u.isVisible(document.querySelector('converse-confirm-modal.modal')));
     const submit_button = document.querySelector('#converse-modals .modal button[type="submit"]');
     submit_button.click();
     const sent_stanzas = _converse.api.connection.get().sent_stanzas;
@@ -275,7 +275,7 @@ describe("Message Retractions", function () {
             const retract_button = await u.waitUntil(() => view.querySelector('.chat-msg__content .chat-msg__action-retract'));
             retract_button.click();
 
-            await u.waitUntil(() => u.isVisible(document.querySelector('#converse-modals .modal')));
+            await u.waitUntil(() => u.isVisible(document.querySelector('converse-confirm-modal.modal')));
 
             const reason_input = document.querySelector('#converse-modals .modal input[name="reason"]');
             reason_input.value = 'This content is inappropriate for this forum!';
@@ -389,7 +389,7 @@ describe("Message Retractions", function () {
 
             const retract_button = await u.waitUntil(() => view.querySelector('.chat-msg__content .chat-msg__action-retract'));
             retract_button.click();
-            await u.waitUntil(() => u.isVisible(document.querySelector('#converse-modals .modal')));
+            await u.waitUntil(() => u.isVisible(document.querySelector('converse-confirm-modal.modal')));
 
             const reason_input = document.querySelector('#converse-modals .modal input[name="reason"]');
             const reason = "This content is inappropriate for this forum!"
@@ -746,7 +746,7 @@ describe("Message Retractions", function () {
 
             const retract_button = await u.waitUntil(() => view.querySelector('.chat-msg__content .chat-msg__action-retract'));
             retract_button.click();
-            await u.waitUntil(() => u.isVisible(document.querySelector('#converse-modals .modal')));
+            await u.waitUntil(() => u.isVisible(document.querySelector('converse-confirm-modal.modal')));
             const submit_button = document.querySelector('#converse-modals .modal button[type="submit"]');
             submit_button.click();
 

+ 2 - 2
src/plugins/profile/modals/user-settings.js

@@ -1,7 +1,7 @@
+import { api } from "@converse/headless";
+import { __ } from 'i18n';
 import BaseModal from "plugins/modal/modal.js";
 import tplUserSettingsModal from "./templates/user-settings.js";
-import { __ } from 'i18n';
-import { api } from "@converse/headless";
 
 export default class UserSettingsModal extends BaseModal {
 

+ 3 - 3
src/plugins/register/templates/choose_provider.js

@@ -51,14 +51,14 @@ function tplDomainInput(el) {
 function tplFetchFormButtons() {
     const i18n_register = __('Fetch registration form');
     const i18n_existing_account = __('Already have a chat account?');
-    const i18n_login = __('Log in here');
+    const i18n_login = __('Go back to login');
     return html`
         <fieldset class="form-group buttons">
             <input class="btn btn-primary" type="submit" value="${i18n_register}" />
         </fieldset>
         <div class="switch-form">
-            <p>${i18n_existing_account}</p>
-            <p><a class="login-here toggle-register-login" href="#converse/login">${i18n_login}</a></p>
+            <p class="mb-1">${i18n_existing_account}</p>
+            <a class="login-here toggle-register-login" href="#converse/login">${i18n_login}</a>
         </div>
     `;
 };

+ 3 - 3
src/plugins/register/templates/registration_form.js

@@ -1,7 +1,7 @@
-import tplSwitchForm from './switch_form.js';
-import { __ } from 'i18n';
-import { api } from '@converse/headless';
 import { html } from 'lit';
+import { api } from '@converse/headless';
+import { __ } from 'i18n';
+import tplSwitchForm from './switch_form.js';
 
 export default (el) => {
     const i18n_choose_provider = __('Choose a different provider');

+ 3 - 3
src/plugins/register/templates/switch_form.js

@@ -3,10 +3,10 @@ import { html } from 'lit';
 
 export default () => {
     const i18n_has_account = __('Already have a chat account?');
-    const i18n_login = __('Log in here');
+    const i18n_login = __('Go back to login');
     return html`
         <div class="switch-form">
-            <p>${i18n_has_account}</p>
-            <p><a class="login-here toggle-register-login" href="#converse/login">${i18n_login}</a></p>
+            <p class="mb-1">${i18n_has_account}</p>
+            <a class="login-here toggle-register-login" href="#converse/login">${i18n_login}</a>
         </div>`;
 }

+ 0 - 30
src/shared/components/brand-byline.js

@@ -1,30 +0,0 @@
-import { CustomElement } from './element.js';
-import { _converse, api } from '@converse/headless';
-import { html } from 'lit';
-
-
-export class ConverseBrandByline extends CustomElement {
-
-    render () { // eslint-disable-line class-methods-use-this
-        const is_fullscreen = api.settings.get('view_mode') === 'fullscreen';
-        return html`
-            ${is_fullscreen
-                ? html`
-                    <p class="brand-subtitle">${_converse.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-byline', ConverseBrandByline);

+ 4 - 6
src/shared/components/brand-heading.js

@@ -1,16 +1,14 @@
-import './brand-byline.js';
-import './brand-logo.js';
-import { CustomElement } from './element.js';
-import { api } from '@converse/headless';
 import { html } from 'lit/html.js';
+import { api } from '@converse/headless';
+import { CustomElement } from './element.js';
+import './brand-logo.js';
 
 
 export class ConverseBrandHeading extends CustomElement {
 
-    render () { // eslint-disable-line class-methods-use-this
+    render () {
         return html`
             <converse-brand-logo></converse-brand-logo>
-            <converse-brand-byline></converse-brand-byline>
         `;
     }
 }

+ 103 - 0
src/shared/components/footer.js

@@ -0,0 +1,103 @@
+import { _converse, api } from '@converse/headless';
+import { html } from 'lit';
+import { CustomElement } from './element.js';
+import { __ } from 'i18n';
+
+export class ConverseFooter extends CustomElement {
+    render() {
+        const is_fullscreen = api.settings.get('view_mode') === 'fullscreen';
+        const theme = api.settings.get('theme');
+        const is_dark_theme = ['dracula', 'cyberpunk'].includes(theme);
+        const i18n_sponsors = __('A big thank you to our sponsors 🙏');
+        return html`
+            ${is_fullscreen
+                ? html`
+                      <footer class="footer mt-auto py-3 mb-2">
+                          <div class="container">
+                              <div class="row">
+                                  <div class="col-12 text-center">
+                                      <p class="brand-subtitle mb-2 subdued">Version: ${_converse.VERSION_NAME}</p>
+                                      <p class="brand-subtitle mb-2">
+                                          <a
+                                              target="_blank"
+                                              rel="nofollow"
+                                              href="https://github.com/conversejs/converse.js"
+                                              >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 mb-4">
+                                          You can
+                                          <a target="_blank" rel="nofollow" href="https://opkode.com/contact.html"
+                                              >hire me</a
+                                          >
+                                          for customizations, support or to build your next project.
+                                      </p>
+                                      <div class="sponsors text-center">
+                                          <p class="byline mb-1">${i18n_sponsors}</p>
+                                          <div
+                                              class="fs-6 d-flex flex-wrap justify-content-center align-items-center gap-4 mb-2"
+                                          >
+                                              <div>
+                                                  <a
+                                                      href="https://bairesdev.com/sponsoring-open-source-projects/?utm_source=conversejs"
+                                                      target="_blank"
+                                                      rel="noopener"
+                                                      ><img
+                                                          style="width: 13em"
+                                                          src="/media/logos/bairesdev-primary.png"
+                                                          alt="BairesDev"
+                                                  /></a>
+                                              </div>
+                                              <div>
+                                                  <a
+                                                      href="https://blokt.com?utm_source=conversejs"
+                                                      target="_blank"
+                                                      rel="noopener"
+                                                      ><img
+                                                          style="width: 12em"
+                                                          src="${is_dark_theme
+                                                              ? '/media/logos/blokt-invert.png'
+                                                              : '/media/logos/blokt.png'}"
+                                                          alt="Blokt Crypto & Privacy"
+                                                  /></a>
+                                              </div>
+                                              <div>
+                                                  <a
+                                                      href="https://www.keycdn.com?utm_source=conversejs"
+                                                      target="_blank"
+                                                      rel="noopener"
+                                                      ><img style="height: 3em" src="/logo/keycdn.svg" alt="KeyCDN"
+                                                  /></a>
+                                              </div>
+                                          </div>
+                                      </div>
+
+                                      <p class="brand-subtitle mb-3">
+                                          If you'd like to sponsor this project, please visit:
+                                          <a href="https://github.com/sponsors/jcbrand" target="_blank" rel="noopener"
+                                              >Github</a
+                                          >,
+                                          <a href="https://www.patreon.com/jcbrand" target="_blank" rel="noopener"
+                                              >Patreon</a
+                                          >,
+                                          <a href="https://liberapay.com/jcbrand" target="_blank" rel="noopener"
+                                              >Liberapay</a
+                                          >
+                                          or
+                                          <a href="https://opkode.com/contact.html" target="_blank" rel="noopener"
+                                              >contact us</a
+                                          >.
+                                      </p>
+                                  </div>
+                              </div>
+                          </div>
+                      </footer>
+                  `
+                : ''}
+        `;
+    }
+}
+
+api.elements.define('converse-footer', ConverseFooter);

+ 1 - 7
src/shared/styles/forms.scss

@@ -7,13 +7,7 @@
     form {
         .form-check {
             .form-check-input {
-                margin-top: 0.05em;
-            }
-        }
-
-        .form-check {
-            .form-check-input {
-                margin-top: 0.05em;
+                margin-top: 0.35em;
             }
         }
 

+ 3 - 0
src/shared/styles/themes/classic.scss

@@ -31,6 +31,9 @@
     --converse-body-bg: var(--background-color);
     --converse-body-color: var(--foreground-color) !important;
     --converse-highlight-color: var(--dark-red);
+    .navbar-nav {
+        --converse-nav-link-color: var(--link-color) !important;
+    }
     .popover {
         --converse-popover-header-color: var(--background-color) !important;
         --converse-popover-header-bg: var(--info-color) !important;

+ 6 - 2
src/shared/styles/themes/cyberpunk.scss

@@ -25,8 +25,8 @@
     --foreground-color: var(--white);
 
     // Bootstrap variables
-    --primary-color: var(--purple) !important;
-    --secondary-color: var(--pink) !important;
+    --primary-color: var(--pink) !important;
+    --secondary-color: var(--purple) !important;
     --success-color: var(--green);
     --danger-color: var(--red);
     --warning-color: var(--orange);
@@ -35,6 +35,10 @@
     --converse-body-bg: var(--background-color);
     --converse-body-color: var(--foreground-color) !important;
     --converse-highlight-color: var(--yellow);
+    .navbar-nav {
+        --converse-nav-link-color: var(--link-color) !important;
+        --converse-nav-link-hover-color: var(--link-color-hover) !important;
+    }
     .popover {
         --converse-popover-header-color: var(--background-color) !important;
         --converse-popover-header-bg: var(--info-color) !important;

+ 6 - 2
src/shared/styles/themes/dracula.scss

@@ -20,8 +20,8 @@
     --foreground-color: #f8f8f2;
 
     // Bootstrap variables
-    --primary-color: var(--purple) !important;
-    --secondary-color: var(--pink) !important;
+    --primary-color: var(--pink) !important;
+    --secondary-color: var(--purple) !important;
     --success-color: var(--green);
     --danger-color: var(--red);
     --warning-color: var(--orange);
@@ -30,6 +30,10 @@
     --converse-body-bg: var(--background-color);
     --converse-body-color: var(--foreground-color) !important;
     --converse-highlight-color: var(--yellow) !important;
+    .navbar-nav {
+        --converse-nav-link-color: var(--link-color) !important;
+        --converse-nav-link-hover-color: var(--link-color-hover) !important;
+    }
     .popover {
         --converse-popover-header-color: var(--background-color) !important;
         --converse-popover-header-bg: var(--info-color) !important;

+ 3 - 0
src/shared/styles/themes/nordic.scss

@@ -43,6 +43,9 @@
     --converse-body-bg: var(--background-color);
     --converse-body-color: var(--foreground-color) !important;
     --converse-highlight-color: var(--red);
+    .navbar-nav {
+        --converse-nav-link-color: var(--link-color) !important;
+    }
     .popover {
         --converse-popover-header-color: var(--background-color) !important;
         --converse-popover-header-bg: var(--info-color) !important;