Bläddra i källkod

Some more refactoring now that we use a vdom for the login panel

We can now simply call `render` whenever we want to show an error or
notification message.

The flip side is that the template is now larger and contains more login.
JC Brand 7 år sedan
förälder
incheckning
92af05d510

+ 7 - 6
css/converse.css

@@ -1977,13 +1977,14 @@
       font-size: 85%; }
       #conversejs #controlbox #converse-register .instructions:hover {
         color: #777; }
-  #conversejs #controlbox .conn-feedback p {
-    color: #578EA9;
-    padding-bottom: 0.5em; }
-    #conversejs #controlbox .conn-feedback p.feedback-subject.error {
-      font-weight: bold; }
-    #conversejs #controlbox .conn-feedback p.error {
+  #conversejs #controlbox .conn-feedback {
+    color: #578EA9; }
+    #conversejs #controlbox .conn-feedback.error {
       color: #A53214; }
+    #conversejs #controlbox .conn-feedback p {
+      padding-bottom: 0.5em; }
+      #conversejs #controlbox .conn-feedback p.feedback-subject.error {
+        font-weight: bold; }
   #conversejs #controlbox .brand-heading-container .brand-heading {
     text-align: left;
     font-size: 150%; }

+ 7 - 8
css/inverse.css

@@ -2065,13 +2065,14 @@ body {
       font-size: 85%; }
       #conversejs #controlbox #converse-register .instructions:hover {
         color: #777; }
-  #conversejs #controlbox .conn-feedback p {
-    color: #578EA9;
-    padding-bottom: 0.5em; }
-    #conversejs #controlbox .conn-feedback p.feedback-subject.error {
-      font-weight: bold; }
-    #conversejs #controlbox .conn-feedback p.error {
+  #conversejs #controlbox .conn-feedback {
+    color: #578EA9; }
+    #conversejs #controlbox .conn-feedback.error {
       color: #A53214; }
+    #conversejs #controlbox .conn-feedback p {
+      padding-bottom: 0.5em; }
+      #conversejs #controlbox .conn-feedback p.feedback-subject.error {
+        font-weight: bold; }
   #conversejs #controlbox .brand-heading-container .brand-heading {
     text-align: left;
     font-size: 150%; }
@@ -2387,8 +2388,6 @@ body {
   width: 200px;
   float: left;
   margin: 0; }
-  #conversejs #controlbox .conn-feedback {
-    padding-top: 2em; }
   #conversejs #controlbox .toggle-register-login {
     line-height: 30px; }
   #conversejs #controlbox .brand-heading-container {

+ 4 - 4
sass/_controlbox.scss

@@ -115,15 +115,15 @@
         }
 
         .conn-feedback {
+            color: $controlbox-head-color;
+            &.error {
+                color: $error-color;
+            }
             p {
-                color: $controlbox-head-color;
                 padding-bottom: 0.5em;
                 &.feedback-subject.error {
                     font-weight: bold;
                 }
-                &.error {
-                    color: $error-color;
-                }
             }
         }
 

+ 0 - 4
sass/inverse/_controlbox.scss

@@ -5,10 +5,6 @@
         float: left;
         margin: 0;
 
-        .conn-feedback {
-            padding-top: 2em;
-        }
-
         .toggle-register-login {
             line-height: $line-height-huge;
         }

+ 93 - 62
src/converse-controlbox.js

@@ -58,6 +58,43 @@
     const CHATBOX_TYPE = 'chatbox';
     const { Strophe, Backbone, Promise, utils, _, moment } = converse.env;
 
+    const CONNECTION_STATUS_CSS_CLASS = {
+       'Error': 'error',
+       'Connecting': 'info',
+       'Connection failure': 'error',
+       'Authenticating': 'info',
+       'Authentication failure': 'error',
+       'Connected': 'info',
+       'Disconnected': 'error',
+       'Disconnecting': 'warn',
+       'Attached': 'info',
+       'Redirect': 'info',
+       'Reconnecting': 'warn'
+    };
+
+    const PRETTY_CONNECTION_STATUS = {
+        0: 'Error',
+        1: 'Connecting',
+        2: 'Connection failure',
+        3: 'Authenticating',
+        4: 'Authentication failure',
+        5: 'Connected',
+        6: 'Disconnected',
+        7: 'Disconnecting',
+        8: 'Attached',
+        9: 'Redirect',
+       10: 'Reconnecting'
+    };
+
+    const REPORTABLE_STATUSES = [
+        0, // ERROR'
+        1, // CONNECTING
+        2, // CONNFAIL
+        3, // AUTHENTICATING
+        4, // AUTHFAIL
+        7, // DISCONNECTING
+       10  // RECONNECTING
+    ];
 
     converse.plugins.add('converse-controlbox', {
 
@@ -299,7 +336,9 @@
                 renderLoginPanel () {
                     this.el.classList.add("logged-out");
                     if (_.isNil(this.loginpanel)) {
-                        this.loginpanel = new _converse.LoginPanel({'model': this});
+                        this.loginpanel = new _converse.LoginPanel({
+                            'model': new _converse.LoginPanelModel()
+                        });
                         const panes = this.el.querySelector('.controlbox-panes');
                         panes.innerHTML = '';
                         panes.appendChild(this.loginpanel.render().el);
@@ -413,31 +452,51 @@
                 }
             });
 
+            _converse.LoginPanelModel = Backbone.Model.extend({
+                defaults: {
+                    'errors': [],
+                }
+            });
 
             _converse.LoginPanel = Backbone.View.extend({
                 tagName: 'div',
                 id: "converse-login-panel",
                 className: 'controlbox-pane fade-in',
                 events: {
-                    'submit form#converse-login': 'authenticate'
+                    'submit form#converse-login': 'authenticate',
+                    'blur   input': 'validate'
                 },
 
                 initialize (cfg) {
-                    _converse.connfeedback.on('change', this.renderConnectionFeedback, this);
+                    this.model.on('change', this.render, this);
+                    this.listenTo(_converse.connfeedback, 'change', this.render);
                 },
 
                 render () {
-                    const html = tpl_login_panel({
-                        '__': __,
-                        'ANONYMOUS': _converse.ANONYMOUS,
-                        'EXTERNAL': _converse.EXTERNAL,
-                        'LOGIN': _converse.LOGIN,
-                        'PREBIND': _converse.PREBIND,
-                        'auto_login': _converse.auto_login,
-                        'authentication': _converse.authentication,
-                        'label_anon_login': __('Click here to log in anonymously'),
-                        'placeholder_username': (_converse.locked_domain || _converse.default_domain) && __('Username') || __('user@domain'),
-                    });
+                    const connection_status = _converse.connfeedback.get('connection_status');
+                    let feedback_class, pretty_status;
+                    if (_.includes(REPORTABLE_STATUSES, connection_status)) {
+                        pretty_status = PRETTY_CONNECTION_STATUS[connection_status];
+                        feedback_class = CONNECTION_STATUS_CSS_CLASS[pretty_status];
+                    }
+                    const html = tpl_login_panel(
+                        _.extend(this.model.toJSON(), {
+                            '__': __,
+                            '_converse': _converse,
+                            'ANONYMOUS': _converse.ANONYMOUS,
+                            'EXTERNAL': _converse.EXTERNAL,
+                            'LOGIN': _converse.LOGIN,
+                            'PREBIND': _converse.PREBIND,
+                            'auto_login': _converse.auto_login,
+                            'authentication': _converse.authentication,
+                            'connection_status': connection_status,
+                            'conn_feedback_class': feedback_class,
+                            'conn_feedback_subject': pretty_status,
+                            'conn_feedback_message': _converse.connfeedback.get('message'),
+                            'placeholder_username': (_converse.locked_domain || _converse.default_domain) &&
+                                                    __('Username') || __('user@domain'),
+                        })
+                    );
                     const form = this.el.querySelector('form');
                     if (_.isNull(form)) {
                         this.el.innerHTML = html;
@@ -445,75 +504,47 @@
                         const patches = vdom.diff(vdom_parser(form), vdom_parser(html));
                         vdom.patch(form, patches);
                     }
-                    this.renderConnectionFeedback();
                     return this;
                 },
 
-                renderConnectionFeedback () {
-                    const feedback_html = tpl_login_feedback({
-                        'conn_feedback_class': _converse.connfeedback.get('klass'),
-                        'conn_feedback_subject': _converse.connfeedback.get('subject'),
-                        'conn_feedback_message': _converse.connfeedback.get('message'),
-                    });
-                    const feedback_el = this.el.querySelector('.conn-feedback');
-                    if (_.isNull(feedback_el)) {
-                        this.el.insertAdjacentHTML('afterbegin', feedback_html);
-                    } else {
-                        feedback_el.outerHTML = feedback_html;
-                    }
-                },
-
-                showSpinner (only_submit_button=false) {
+                validate () {
                     const form = this.el.querySelector('form');
-                    if (only_submit_button) {
-                        const button = form.querySelector('input[type=submit]');
-                        button.classList.add('hidden');
-                        button.insertAdjacentHTML('afterend', '<span class="spinner login-submit"/>');
-                    } else {
-                        form.innerHTML = tpl_spinner();
+                    const jid = form.querySelector('input[name=jid]').value;
+                    const password = _.get(form.querySelector('input[name=password]'), 'value');
+                    const errors = [];
+                    if (!jid || (
+                            !_converse.locked_domain &&
+                            !_converse.default_domain &&
+                            _.filter(jid.split('@')).length < 2)) {
+                        errors.push(errors, 'invalid_jid');
                     }
-                    return this;
+                    if (!password && _converse.authentication !== _converse.EXTERNAL)  {
+                        errors.push(errors, 'password_required');
+                    }
+                    this.model.set('errors', errors);
+                    return errors.length == 0;
                 },
 
                 authenticate (ev) {
                     /* Authenticate the user based on a form submission event.
                      */
                     if (ev && ev.preventDefault) { ev.preventDefault(); }
-                    const $form = $(ev.target);
                     if (_converse.authentication === _converse.ANONYMOUS) {
-                        this.showSpinner().connect(_converse.jid, null);
+                        this.connect(_converse.jid, null);
                         return;
                     }
-                    const $jid_input = $form.find('input[name=jid]');
-                    const $jid_error_msg = $form.find('.invalid-jid-msg');
-                    const $pw_input = $form.find('input[name=password]');
-                    const password = $pw_input.val();
-
-                    let jid = $jid_input.val(),
-                        errors = false;
-
-                    if (!jid || (
-                            !_converse.locked_domain &&
-                            !_converse.default_domain &&
-                            _.filter(jid.split('@')).length < 2)) {
-                        errors = true;
-                        $jid_input.addClass('error');
-                        $jid_error_msg.removeClass('hidden');
-                    } else {
-                        $jid_error_msg.addClass('hidden');
+                    if (!this.validate()) {
+                        return;
                     }
+                    let jid = ev.target.querySelector('input[name=jid]').value;
+                    const password = _.get(ev.target.querySelector('input[name=password]'), 'value');
 
-                    if (!password && _converse.authentication !== _converse.EXTERNAL)  {
-                        errors = true;
-                        $pw_input.addClass('error');
-                    }
-                    if (errors) { return; }
                     if (_converse.locked_domain) {
                         jid = Strophe.escapeNode(jid) + '@' + _converse.locked_domain;
                     } else if (_converse.default_domain && !_.includes(jid, '@')) {
                         jid = jid + '@' + _converse.default_domain;
                     }
-                    this.showSpinner(true).connect(jid, password);
+                    this.connect(jid, password);
                     return false;
                 },
 

+ 22 - 29
src/converse-core.js

@@ -100,7 +100,7 @@
     _converse.OPENED = 'opened';
     _converse.PREBIND = "prebind";
 
-    _converse.PRETTY_CONNECTION_STATUS = {
+    _converse.CONNECTION_STATUS = {
         0: 'ERROR',
         1: 'CONNECTING',
         2: 'CONNFAIL',
@@ -110,7 +110,8 @@
         6: 'DISCONNECTED',
         7: 'DISCONNECTING',
         8: 'ATTACHED',
-        9: 'REDIRECT'
+        9: 'REDIRECT',
+       10: 'RECONNECTING'
     };
 
     _converse.DEFAULT_IMAGE_TYPE = 'image/png';
@@ -407,10 +408,9 @@
             _converse.everySecondTrigger = window.setInterval(_converse.onEverySecond, 1000);
         };
 
-        this.giveFeedback = function (subject, klass, message) {
+        this.setConnectionStatus = function (connection_status, message) {
             _converse.connfeedback.set({
-                'subject': subject,
-                'klass': klass,
+                'connection_status': connection_status,
                 'message': message
             });
         };
@@ -431,9 +431,8 @@
         this.reconnect = _.debounce(function () {
             _converse.log('RECONNECTING');
             _converse.log('The connection has dropped, attempting to reconnect.');
-            _converse.giveFeedback(
-                __("Reconnecting"),
-                'warn',
+            _converse.setConnectionStatus(
+                Strophe.Status.RECONNECTING,
                 __('The connection has dropped, attempting to reconnect.')
             );
             _converse.connection.reconnecting = true;
@@ -495,9 +494,9 @@
              * through various states while establishing or tearing down a
              * connection.
              */
-            _converse.log(`Status changed to: ${_converse.PRETTY_CONNECTION_STATUS[status]}`);
+            _converse.log(`Status changed to: ${_converse.CONNECTION_STATUS[status]}`);
             if (status === Strophe.Status.CONNECTED || status === Strophe.Status.ATTACHED) {
-                _converse.giveFeedback();
+                _converse.setConnectionStatus(status);
                 // By default we always want to send out an initial presence stanza.
                 _converse.send_initial_presence = true;
                 _converse.setDisconnectionCause();
@@ -517,20 +516,19 @@
                 _converse.setDisconnectionCause(status, message);
                 _converse.onDisconnected();
             } else if (status === Strophe.Status.ERROR) {
-                _converse.giveFeedback(
-                    __('Connection error'),
-                    'error',
+                _converse.setConnectionStatus(
+                    status,
                     __('An error occurred while connecting to the chat server.')
                 );
             } else if (status === Strophe.Status.CONNECTING) {
-                _converse.giveFeedback(__('Connecting…'));
+                _converse.setConnectionStatus(status);
             } else if (status === Strophe.Status.AUTHENTICATING) {
-                _converse.giveFeedback(__('Authenticating…'));
+                _converse.setConnectionStatus(status);
             } else if (status === Strophe.Status.AUTHFAIL) {
                 if (!message) {
                     message = __('Your Jabber ID and/or password is incorrect. Please try again.');
                 }
-                _converse.giveFeedback(__('Authentication failed'), 'error', message);
+                _converse.setConnectionStatus(status, message);
                 _converse.setDisconnectionCause(status, message, true);
                 _converse.onDisconnected();
             } else if (status === Strophe.Status.CONNFAIL) {
@@ -541,11 +539,7 @@
                 } else if (!_.isUndefined(message) && message === _.get(Strophe, 'ErrorCondition.NO_AUTH_MECH')) {
                     feedback = __("The XMPP server did not offer a supported authentication mechanism");
                 }
-                _converse.giveFeedback(
-                    __('Connection failed'),
-                    'error',
-                    feedback
-                );
+                _converse.setConnectionStatus(status, feedback);
                 _converse.setDisconnectionCause(status, message);
             } else if (status === Strophe.Status.DISCONNECTING) {
                 _converse.setDisconnectionCause(status, message);
@@ -571,7 +565,7 @@
             }
         };
 
-        this.initStatus = () => 
+        this.initStatus = () =>
             new Promise((resolve, reject) => {
                 const promise = new utils.getWrappedPromise();
                 this.xmppstatus = new this.XMPPStatus();
@@ -1521,15 +1515,14 @@
 
         this.ConnectionFeedback = Backbone.Model.extend({
 
-            initialize () {
-                this.on('change', this.emitConnectionFeedbackChange);
+            defaults: {
+                'connection_status': undefined,
+                'message': ''
             },
 
-            emitConnectionFeedbackChange () {
-                _converse.emit('connfeedback', {
-                    'klass': _converse.connfeedback.get('klass'),
-                    'message': _converse.connfeedback.get('message'),
-                    'subject': _converse.connfeedback.get('subject')
+            initialize () {
+                this.on('change', () => {
+                    _converse.emit('connfeedback', _converse.connfeedback);
                 });
             }
         });

+ 5 - 5
src/converse-register.js

@@ -148,10 +148,10 @@
             const { _converse } = this,
                 { __ } = _converse;
 
-            _converse.PRETTY_CONNECTION_STATUS[Strophe.Status.REGIFAIL] = 'REGIFAIL';
-            _converse.PRETTY_CONNECTION_STATUS[Strophe.Status.REGISTERED] = 'REGISTERED';
-            _converse.PRETTY_CONNECTION_STATUS[Strophe.Status.CONFLICT] = 'CONFLICT';
-            _converse.PRETTY_CONNECTION_STATUS[Strophe.Status.NOTACCEPTABLE] = 'NOTACCEPTABLE';
+            _converse.CONNECTION_STATUS[Strophe.Status.REGIFAIL] = 'REGIFAIL';
+            _converse.CONNECTION_STATUS[Strophe.Status.REGISTERED] = 'REGISTERED';
+            _converse.CONNECTION_STATUS[Strophe.Status.CONFLICT] = 'CONFLICT';
+            _converse.CONNECTION_STATUS[Strophe.Status.NOTACCEPTABLE] = 'NOTACCEPTABLE';
 
             _converse.api.settings.update({
                 allow_registration: true,
@@ -433,7 +433,7 @@
                             ], status_code)) {
 
                         _converse.log(
-                            `Problem during registration: Strophe.Status is ${_converse.PRETTY_CONNECTION_STATUS[status_code]}`,
+                            `Problem during registration: Strophe.Status is ${_converse.CONNECTION_STATUS[status_code]}`,
                             Strophe.LogLevel.ERROR
                         );
                         this.abortRegistration();

+ 0 - 8
src/templates/login_feedback.html

@@ -1,8 +0,0 @@
-<div class="conn-feedback {[ if (!conn_feedback_subject && !conn_feedback_message) { ]} hidden {[ } ]}">
-    <p class="feedback-subject {[ if (!conn_feedback_subject) { ]} hidden {[ } ]} {{{ conn_feedback_class }}}">
-        {{{ conn_feedback_subject }}}
-    </p>
-    <p class="feedback-message {[ if (!conn_feedback_message) { ]} hidden {[ } ]} {{{ conn_feedback_class }}}">
-        {{{ conn_feedback_message }}}
-    </p>
-</div>

+ 25 - 9
src/templates/login_panel.html

@@ -1,22 +1,38 @@
 <form class="pure-form pure-form-stacked converse-form" id="converse-login" method="post">
-    {[ if (auto_login) { ]}
-        <span class="spinner login-submit"/>
-    {[ } ]}
-    {[ if (!auto_login) { ]}
-        <legend>{{{__("Login")}}}</legend>
+    <legend>{{{__("Login")}}}</legend>
+
+    <div class="conn-feedback fade-in {[ if (!conn_feedback_subject) { ]} hidden {[ } ]} {{{conn_feedback_class}}}">
+        <p class="feedback-subject">{{{ conn_feedback_subject }}}</p>
+        <p class="feedback-message {[ if (!conn_feedback_message) { ]} hidden {[ } ]}">{{{conn_feedback_message}}}</p>
+    </div>
 
+    {[ if (auto_login || _converse.CONNECTION_STATUS[connection_status] === 'CONNECTING') { ]}
+        <span class="spinner centered"/>
+    {[ } else { ]}
         {[ if (authentication == LOGIN || authentication == EXTERNAL) { ]}
             <label>{{{__("Jabber ID:")}}}</label>
-            <p class="form-help fade-in invalid-jid-msg error hidden">{{{_('Please enter a valid XMPP address')}}}</p>
-            <input autofocus type="text" name="jid" placeholder="{{{placeholder_username}}}">
+            <p class="form-help fade-in error {[ if (!_.includes(errors, 'invalid_jid')) { ]} hidden {[ } ]}">
+                {{{_('Please enter a valid XMPP address')}}}
+            </p>
+            <input autofocus
+                   type="text"
+                   name="jid"
+                   class="{[ if (_.includes(errors, 'invalid_jid')) { ]} error {[ } ]}"
+                   placeholder="{{{placeholder_username}}}">
             {[ if (authentication !== EXTERNAL) { ]}
                 <label>{{{__("Password:")}}}</label>
-                <input type="password" name="password" placeholder="{{{__('password')}}}">
+                <p class="form-help fade-in error {[ if (!_.includes(errors, 'password_required')) { ]} hidden {[ } ]}">
+                    {{{_('Please enter your password')}}}
+                </p>
+                <input type="password"
+                       name="password"
+                       class="{[ if (_.includes(errors, 'password_required')) { ]} error {[ } ]}"
+                       placeholder="{{{__('password')}}}">
             {[ } ]}
             <input class="pure-button button-primary" type="submit" value="{{{__('Submit')}}}">
         {[ } ]}
         {[ if (authentication == ANONYMOUS) { ]}
-            <input class="pure-button button-primary login-anon" type="submit" value="{{{label_anon_login}}}"/>
+            <input class="pure-button button-primary login-anon" type="submit" value="{{{__('Click here to log in anonymously')}}}"/>
         {[ } ]}
         {[ if (authentication == PREBIND) { ]}
             <p>Disconnected.</p>