Forráskód Böngészése

Refactor the message form to make it more reactive/declarative

Make the message form a component
JC Brand 5 éve
szülő
commit
c5740b7876

+ 1 - 1
sass/_chatbox.scss

@@ -343,7 +343,7 @@
                 width: 100%;
                 border: none;
                 min-height: var(--chat-textarea-height);
-                margin-bottom: -4px; // Not clear why this is necessar :(
+                margin-bottom: -2px; // Not clear why this is necessar :(
                 resize: none;
                 &.spoiler {
                     height: 42px;

+ 24 - 26
sass/_toolbar.scss

@@ -18,33 +18,31 @@
         }
     }
 
-    .chat-toolbar {
 
-        converse-chat-toolbar {
-            background-color: white;
-            box-sizing: border-box;
+    converse-chat-toolbar {
+        background-color: white;
+        box-sizing: border-box;
+        color: var(--chat-head-color);
+        display: flex;
+        justify-content: space-between;
+        margin: 0;
+        width: 100%;
+
+        .fa, .fa:hover,
+        .far, .far:hover,
+        .fas, .fas:hover {
             color: var(--chat-head-color);
-            display: flex;
-            justify-content: space-between;
-            margin: 0;
-            width: 100%;
-
-            .fa, .fa:hover,
-            .far, .far:hover,
-            .fas, .fas:hover {
-                color: var(--chat-head-color);
-                font-size: var(--font-size-large);
-                svg {
-                    fill: var(--chat-head-color);
-                }
+            font-size: var(--font-size-large);
+            svg {
+                fill: var(--chat-head-color);
             }
-            .unencrypted a,
-            .unencrypted {
-                color: var(--text-color);
-                .toolbar-menu {
-                    a {
-                        color: var(--link-color);
-                    }
+        }
+        .unencrypted a,
+        .unencrypted {
+            color: var(--text-color);
+            .toolbar-menu {
+                a {
+                    color: var(--link-color);
                 }
             }
         }
@@ -182,7 +180,7 @@
 }
 
 #conversejs.converse-overlayed  {
-    .chat-toolbar {
+    converse-chat-toolbar {
         li {
             .toolbar-menu {
                 min-width: 235px;
@@ -190,7 +188,7 @@
         }
     }
     .chatroom {
-        .chat-toolbar {
+        converse-chat-toolbar {
             li {
                 .toolbar-menu {
                     min-width: 280px;

+ 1 - 1
spec/chatbox.js

@@ -1561,7 +1561,7 @@ describe("Chatboxes", function () {
             const view = _converse.chatboxviews.get(sender_jid);
             await u.waitUntil(() => view.model.messages.length);
             expect(select_msgs_indicator().textContent).toBe('1');
-            view.viewUnreadMessages();
+            view.scrollDown();
             _converse.rosterview.render();
             expect(select_msgs_indicator()).toBeUndefined();
             done();

+ 1 - 0
src/components/adhoc-commands.js

@@ -70,6 +70,7 @@ const tpl_adhoc = (o) => html`
                 ${i18n_choose_service}
                 <p class="form-help">${i18n_choose_service_instructions}</p>
                 <converse-autocomplete
+                    ?auto_evaluate=${true}
                     .getAutoCompleteList="${getAutoCompleteList}"
                     placeholder="${i18n_jid_placeholder}"
                     name="jid"/>

+ 3 - 2
src/components/autocomplete.js

@@ -21,7 +21,7 @@ export default class AutoCompleteComponent extends CustomElement {
 
     constructor () {
         super();
-        this.auto_evaluate = true; // Should evaluation happen automatically without any particular key as trigger?
+        this.auto_evaluate = false; // Should evaluation happen automatically without any particular key as trigger?
         this.auto_first = false; // Should the first element be automatically selected?
         this.filter = 'contains';
         this.include_triggers = ''; // Space separated chars which should be included in the returned value
@@ -35,7 +35,8 @@ export default class AutoCompleteComponent extends CustomElement {
         return html`
             <div class="suggestion-box suggestion-box__name">
                 <ul class="suggestion-box__results suggestion-box__results--above" hidden=""></ul>
-                <input type="text" name="${this.name}"
+                <input type="text"
+                       name="${this.name}"
                        autocomplete="off"
                        @keydown=${this.onKeyDown}
                        @keyup=${this.onKeyUp}

+ 165 - 0
src/components/message-form.js

@@ -0,0 +1,165 @@
+import AutoCompleteComponent from "./autocomplete.js";
+import { __ } from '@converse/headless/i18n';
+import { _converse, api, converse } from '@converse/headless/converse-core';
+import { html } from 'lit-element';
+
+const u = converse.env.utils;
+const i18n_hidden_message = __('Hidden message');
+const i18n_message = __('Message');
+const i18n_spoiler_hint = __('Spoiler hint');
+
+
+export class MessageForm extends AutoCompleteComponent {
+
+    static get properties () {
+        const props = super.properties;
+        return Object.assign({}, props, {
+            'chatview': { type: Object },
+            'composing_spoiler': { type: Boolean },
+            'message_value': { type: String },
+            'hint_value': { type: String },
+        });
+    }
+
+    render () {
+        const message_limit = api.settings.get('message_limit');
+        const show_call_button = api.settings.get('visible_toolbar_buttons').call;
+        const show_emoji_button = api.settings.get('visible_toolbar_buttons').emoji;
+        const show_send_button = api.settings.get('show_send_button');
+        const show_spoiler_button = api.settings.get('visible_toolbar_buttons').spoiler;
+        const show_toolbar = api.settings.get('show_toolbar');
+        const is_groupchat = this.chatview.model.get('type') === _converse.CHATROOMS_TYPE;
+        const show_occupants_toggle = is_groupchat && _converse.visible_toolbar_buttons.toggle_occupants;
+        const hidden_occupants = is_groupchat && this.chatview.model.get('hidden_occupants')
+
+        return html`
+            <form class="sendXMPPMessage">
+                <converse-chat-toolbar
+                    class="no-text-select"
+                    .chatview=${this.chatview}
+                    .model=${this.model}
+                    ?hidden_occupants="${hidden_occupants}"
+                    ?is_groupchat="${is_groupchat}"
+                    ?show_call_button="${show_call_button}"
+                    ?show_emoji_button="${show_emoji_button}"
+                    ?show_occupants_toggle="${show_occupants_toggle}"
+                    ?show_send_button="${show_send_button}"
+                    ?show_spoiler_button="${show_spoiler_button}"
+                    ?show_toolbar="${show_toolbar}"
+                    message_limit="${message_limit}"
+                ></converse-chat-toolbar>
+
+                <input type="text" placeholder="${i18n_spoiler_hint || ''}" value="${this.hint_value || ''}" class="${this.composing_spoiler ? '' : 'hidden'} spoiler-hint"/>
+                <div class="suggestion-box">
+                    <ul class="suggestion-box__results suggestion-box__results--above" hidden=""></ul>
+                    <textarea
+                        @dragover=${this.onDragOver}
+                        @drop=${this.onDrop}
+                        @input=${this.inputChanged}
+                        @keydown=${this.onKeyDown}
+                        @keyup=${this.onKeyUp}
+                        @paste=${this.onPaste}
+                        name="${this.name}"
+                        type="text"
+                        class="chat-textarea suggestion-box__input
+                            ${ this.show_send_button ? 'chat-textarea-send-button' : '' }
+                            ${ this.composing_spoiler ? 'spoiler' : '' }"
+                        placeholder="${this.composing_spoiler ? i18n_hidden_message : i18n_message}"
+                    >${ this.message_value || '' }</textarea>
+                    <span class="suggestion-box__additions visually-hidden" role="status" aria-live="assertive" aria-relevant="additions"></span>
+                </div>
+            </form>`;
+    }
+
+
+    onDragOver (ev) {  //eslint-disable-line class-methods-use-this
+        ev.preventDefault();
+    }
+
+
+    inputChanged (ev) {  //eslint-disable-line class-methods-use-this
+        const height = ev.target.scrollHeight + 'px';
+        if (ev.target.style.height != height) {
+            ev.target.style.height = 'auto';
+            ev.target.style.height = height;
+        }
+    }
+
+
+    onDrop (evt) {
+        if (evt.dataTransfer.files.length == 0) {
+            // There are no files to be dropped, so this isn’t a file
+            // transfer operation.
+            return;
+        }
+        evt.preventDefault();
+        this.chatview.model.sendFiles(evt.dataTransfer.files);
+    }
+
+
+    onKeyUp (ev) {
+        super.onKeyUp(ev);
+        this.chatview.updateCharCounter(ev.target.value);
+    }
+
+
+    onPaste (ev) {
+        if (ev.clipboardData.files.length !== 0) {
+            ev.preventDefault();
+            // Workaround for quirk in at least Firefox 60.7 ESR:
+            // It seems that pasted files disappear from the event payload after
+            // the event has finished, which apparently happens during async
+            // processing in sendFiles(). So we copy the array here.
+            this.chatview.model.sendFiles(Array.from(ev.clipboardData.files));
+            return;
+        }
+        this.chatview.updateCharCounter(ev.clipboardData.getData('text/plain'));
+    }
+
+    onKeyDown (ev) {
+        super.onKeyDown(ev);
+
+        if (ev.ctrlKey) {
+            // When ctrl is pressed, no chars are entered into the textarea.
+            return;
+        }
+        if (!ev.shiftKey && !ev.altKey && !ev.metaKey) {
+            if (ev.keyCode === converse.keycodes.FORWARD_SLASH) {
+                // Forward slash is used to run commands. Nothing to do here.
+                return;
+            } else if (ev.keyCode === converse.keycodes.ESCAPE) {
+                return this.onEscapePressed(ev);
+            } else if (ev.keyCode === converse.keycodes.ENTER) {
+                return this.onEnterPressed(ev);
+            } else if (ev.keyCode === converse.keycodes.UP_ARROW && !ev.target.selectionEnd) {
+                const textarea = this.el.querySelector('.chat-textarea');
+                if (!textarea.value || u.hasClass('correcting', textarea)) {
+                    return this.editEarlierMessage();
+                }
+            } else if (ev.keyCode === converse.keycodes.DOWN_ARROW &&
+                    ev.target.selectionEnd === ev.target.value.length &&
+                    u.hasClass('correcting', this.el.querySelector('.chat-textarea'))) {
+                return this.editLaterMessage();
+            }
+        }
+        if ([converse.keycodes.SHIFT,
+                converse.keycodes.META,
+                converse.keycodes.META_RIGHT,
+                converse.keycodes.ESCAPE,
+                converse.keycodes.ALT].includes(ev.keyCode)) {
+            return;
+        }
+        if (this.chatview.model.get('chat_state') !== _converse.COMPOSING) {
+            // Set chat state to composing if keyCode is not a forward-slash
+            // (which would imply an internal command and not a message).
+            this.chatview.model.setChatState(_converse.COMPOSING);
+        }
+    }
+
+    onEnterPressed (ev) {
+        return this.chatview.onFormSubmitted(ev);
+    }
+}
+
+
+api.elements.define('converse-message-form', MessageForm);

+ 5 - 6
src/components/toolbar.js

@@ -24,7 +24,6 @@ export class ChatToolbar extends CustomElement {
             hidden_occupants: { type: Boolean },
             is_groupchat: { type: Boolean },
             message_limit: { type: Number },
-            model: { type: Object },
             show_call_button: { type: Boolean },
             show_emoji_button: { type: Boolean },
             show_occupants_toggle: { type: Boolean },
@@ -101,7 +100,7 @@ export class ChatToolbar extends CustomElement {
     }
 
     getSpoilerButton () {
-        const model = this.model;
+        const model = this.chatview.model;
         if (!this.is_groupchat && model.presence.resources.length === 0) {
             return;
         }
@@ -140,19 +139,19 @@ export class ChatToolbar extends CustomElement {
     }
 
     onFileSelection (evt) {
-        this.model.sendFiles(evt.target.files);
+        this.chatview.model.sendFiles(evt.target.files);
     }
 
     toggleComposeSpoilerMessage (ev) {
         ev?.preventDefault?.();
         ev?.stopPropagation?.();
-        this.model.set('composing_spoiler', !this.model.get('composing_spoiler'));
+        this.chatview.model.set('composing_spoiler', !this.chatview.model.get('composing_spoiler'));
     }
 
     toggleOccupants (ev) {
         ev?.preventDefault?.();
         ev?.stopPropagation?.();
-        this.model.save({'hidden_occupants': !this.model.get('hidden_occupants')});
+        this.chatview.model.save({'hidden_occupants': !this.chatview.model.get('hidden_occupants')});
     }
 
     toggleCall (ev) {
@@ -168,7 +167,7 @@ export class ChatToolbar extends CustomElement {
          */
         api.trigger('callButtonClicked', {
             connection: _converse.connection,
-            model: this.model
+            model: this.chatview.model
         });
     }
 }

+ 22 - 191
src/converse-chatview.js

@@ -13,7 +13,6 @@ import tpl_chatbox from "templates/chatbox.js";
 import tpl_chatbox_head from "templates/chatbox_head.js";
 import tpl_chatbox_message_form from "templates/chatbox_message_form.js";
 import tpl_spinner from "templates/spinner.html";
-import tpl_toolbar from "templates/toolbar.js";
 import tpl_user_details_modal from "templates/user_details_modal.js";
 import { BootstrapModal } from "./converse-modal.js";
 import { View } from '@converse/skeletor/src/view.js';
@@ -173,18 +172,14 @@ converse.plugins.add('converse-chatview', {
             events: {
                 'click .chatbox-navback': 'showControlBox',
                 'click .chatbox-title': 'minimize',
-                'click .new-msgs-indicator': 'viewUnreadMessages',
                 'click .send-button': 'onFormSubmitted',
                 'click .toggle-clear': 'clearMessages',
-                'input .chat-textarea': 'inputChanged',
-                'keydown .chat-textarea': 'onKeyDown',
-                'keyup .chat-textarea': 'onKeyUp',
-                'paste .chat-textarea': 'onPaste',
             },
 
             async initialize () {
                 this.initDebounced();
-
+                this.listenTo(this.model, 'change:composing_spoiler', this.renderMessageForm);
+                this.listenTo(this.model, 'change:show_new_msgs_indicator', this.renderMessageForm);
                 this.listenTo(this.model, 'change:status', this.onStatusMessageChanged);
                 this.listenTo(this.model, 'destroy', this.remove);
                 this.listenTo(this.model, 'show', this.show);
@@ -239,21 +234,15 @@ converse.plugins.add('converse-chatview', {
             },
 
             render () {
-                const result = tpl_chatbox(
-                    Object.assign(
-                        this.model.toJSON(), {
-                            'markScrolled': () => this.markScrolled()
-                        }
-                    )
-                );
-                render(result, this.el);
+                const props = Object.assign(this.model.toJSON(), {markScrolled: () => this.markScrolled()});
+                render(tpl_chatbox(props), this.el);
                 this.content = this.el.querySelector('.chat-content');
                 this.notifications = this.el.querySelector('.chat-content__notifications');
                 this.msgs_container = this.el.querySelector('.chat-content__messages');
                 this.help_container = this.el.querySelector('.chat-content__help');
                 this.renderChatContent();
-                this.renderMessageForm();
                 this.renderHeading();
+                this.renderMessageForm();
                 return this;
             },
 
@@ -266,9 +255,9 @@ converse.plugins.add('converse-chatview', {
                         // gets scrolled down. We always want to scroll down
                         // when the user writes a message as opposed to when a
                         // message is received.
-                        this.model.set('scrolled', false);
+                        this.model.set({'scrolled': false, 'show_new_msgs_indicator': false});
                     } else if (this.model.get('scrolled', true)) {
-                        this.showNewMessagesIndicator();
+                        this.model.set('show_new_msgs_indicator', true);
                     }
                 }
             },
@@ -326,43 +315,18 @@ converse.plugins.add('converse-chatview', {
                 );
             },
 
-            renderToolbar () {
-                if (!api.settings.get('show_toolbar')) {
-                    return this;
-                }
-                const options = Object.assign({
-                        'model': this.model,
-                        'chatview': this
-                    },
-                    this.model.toJSON(),
-                    this.getToolbarOptions()
-                );
-                render(tpl_toolbar(options), this.el.querySelector('.chat-toolbar'));
-                /**
-                 * Triggered once the _converse.ChatBoxView's toolbar has been rendered
-                 * @event _converse#renderToolbar
-                 * @type { _converse.ChatBoxView }
-                 * @example _converse.api.listen.on('renderToolbar', view => { ... });
-                 */
-                api.trigger('renderToolbar', this);
-                return this;
-            },
-
             renderMessageForm () {
                 const form_container = this.el.querySelector('.message-form-container');
                 render(tpl_chatbox_message_form(
                     Object.assign(this.model.toJSON(), {
+                        'chatview': this,
                         'hint_value': this.el.querySelector('.spoiler-hint')?.value,
-                        'label_message': this.model.get('composing_spoiler') ? __('Hidden message') : __('Message'),
-                        'label_spoiler_hint': __('Optional hint'),
+                        'keydown': ev => this.onKeyDown(ev),
                         'message_value': this.el.querySelector('.chat-textarea')?.value,
-                        'show_send_button': api.settings.get('show_send_button'),
-                        'show_toolbar': api.settings.get('show_toolbar'),
-                        'unread_msgs': __('You have unread messages')
                     })), form_container);
                 this.el.addEventListener('focusin', ev => this.emitFocused(ev));
                 this.el.addEventListener('focusout', ev => this.emitBlurred(ev));
-                this.renderToolbar();
+                this.focus();
             },
 
             showControlBox () {
@@ -380,20 +344,6 @@ converse.plugins.add('converse-chatview', {
                 this.user_details_modal.show(ev);
             },
 
-            onDragOver (evt) {
-                evt.preventDefault();
-            },
-
-            onDrop (evt) {
-                if (evt.dataTransfer.files.length == 0) {
-                    // There are no files to be dropped, so this isn’t a file
-                    // transfer operation.
-                    return;
-                }
-                evt.preventDefault();
-                this.model.sendFiles(evt.dataTransfer.files);
-            },
-
             async renderHeading () {
                 const tpl = await this.generateHeadingTemplate();
                 render(tpl, this.el.querySelector('.chat-head-chatbox'));
@@ -469,11 +419,6 @@ converse.plugins.add('converse-chatview', {
                 return _converse.api.hook('getHeadingButtons', this, buttons);
             },
 
-            getToolbarOptions () {
-                //  FIXME: can this be removed?
-                return {};
-            },
-
             async updateAfterMessagesFetched () {
                 await this.model.messages.fetched;
                 this.renderChatContent();
@@ -499,9 +444,10 @@ converse.plugins.add('converse-chatview', {
                 ev?.preventDefault?.();
                 ev?.stopPropagation?.();
                 if (this.model.get('scrolled')) {
-                    u.safeSave(this.model, {
+                    this.model.save({
                         'scrolled': false,
                         'top_visible_message': null,
+                        'show_new_msgs_indicator': false
                     });
                 }
                 if (this.msgs_container.scrollTo) {
@@ -708,104 +654,10 @@ converse.plugins.add('converse-chatview', {
                 }
             },
 
-            onPaste (ev) {
-                if (ev.clipboardData.files.length !== 0) {
-                    ev.preventDefault();
-                    // Workaround for quirk in at least Firefox 60.7 ESR:
-                    // It seems that pasted files disappear from the event payload after
-                    // the event has finished, which apparently happens during async
-                    // processing in sendFiles(). So we copy the array here.
-                    this.model.sendFiles(Array.from(ev.clipboardData.files));
-                    return;
-                }
-                this.updateCharCounter(ev.clipboardData.getData('text/plain'));
-            },
-
-            autocompleteInPicker (input, value) {
-                const emoji_dropdown = this.el.querySelector('converse-emoji-dropdown');
-                const emoji_picker = this.el.querySelector('converse-emoji-picker');
-                if (emoji_picker && emoji_dropdown) {
-                    this.autocompleting = value;
-                    this.ac_position = input.selectionStart;
-                    emoji_picker.model.set({'query': value});
-                    emoji_dropdown.firstElementChild.click();
-                    return true;
-                }
-            },
-
-            onEmojiReceivedFromPicker (emoji) {
-                this.insertIntoTextArea(emoji, !!this.autocompleting, false, this.ac_position);
-                this.autocompleting = false;
-                this.ac_position = null;
-            },
-
-            /**
-             * Event handler for when a depressed key goes up
-             * @private
-             * @method _converse.ChatBoxView#onKeyUp
-             */
-            onKeyUp (ev) {
-                this.updateCharCounter(ev.target.value);
-            },
-
-            /**
-             * Event handler for when a key is pressed down in a chat box textarea.
-             * @private
-             * @method _converse.ChatBoxView#onKeyDown
-             * @param { Event } ev
-             */
-            onKeyDown (ev) {
-                if (ev.ctrlKey) {
-                    // When ctrl is pressed, no chars are entered into the textarea.
-                    return;
-                }
-                if (!ev.shiftKey && !ev.altKey && !ev.metaKey) {
-                    if (ev.keyCode === converse.keycodes.TAB) {
-                        const value = u.getCurrentWord(ev.target, null, /(:.*?:)/g);
-                        if (value.startsWith(':') && this.autocompleteInPicker(ev.target, value)) {
-                            ev.preventDefault();
-                            ev.stopPropagation();
-                        }
-                    } else if (ev.keyCode === converse.keycodes.FORWARD_SLASH) {
-                        // Forward slash is used to run commands. Nothing to do here.
-                        return;
-                    } else if (ev.keyCode === converse.keycodes.ESCAPE) {
-                        return this.onEscapePressed(ev);
-                    } else if (ev.keyCode === converse.keycodes.ENTER) {
-                        return this.onEnterPressed(ev);
-                    } else if (ev.keyCode === converse.keycodes.UP_ARROW && !ev.target.selectionEnd) {
-                        const textarea = this.el.querySelector('.chat-textarea');
-                        if (!textarea.value || u.hasClass('correcting', textarea)) {
-                            return this.editEarlierMessage();
-                        }
-                    } else if (ev.keyCode === converse.keycodes.DOWN_ARROW &&
-                            ev.target.selectionEnd === ev.target.value.length &&
-                            u.hasClass('correcting', this.el.querySelector('.chat-textarea'))) {
-                        return this.editLaterMessage();
-                    }
-                }
-                if ([converse.keycodes.SHIFT,
-                        converse.keycodes.META,
-                        converse.keycodes.META_RIGHT,
-                        converse.keycodes.ESCAPE,
-                        converse.keycodes.ALT].includes(ev.keyCode)) {
-                    return;
-                }
-                if (this.model.get('chat_state') !== _converse.COMPOSING) {
-                    // Set chat state to composing if keyCode is not a forward-slash
-                    // (which would imply an internal command and not a message).
-                    this.model.setChatState(_converse.COMPOSING);
-                }
-            },
-
             getOwnMessages () {
                 return this.model.messages.filter({'sender': 'me'});
             },
 
-            onEnterPressed (ev) {
-                return this.onFormSubmitted(ev);
-            },
-
             onEscapePressed (ev) {
                 ev.preventDefault();
                 const idx = this.model.messages.findLastIndex('correcting');
@@ -897,14 +749,6 @@ converse.plugins.add('converse-chatview', {
                 }
             },
 
-            inputChanged (ev) {
-                const height = ev.target.scrollHeight + 'px';
-                if (ev.target.style.height != height) {
-                    ev.target.style.height = 'auto';
-                    ev.target.style.height = height;
-                }
-            },
-
             async clearMessages (ev) {
                 if (ev && ev.preventDefault) { ev.preventDefault(); }
                 const result = confirm(__("Are you sure you want to clear the messages from this conversation?"));
@@ -1069,17 +913,6 @@ converse.plugins.add('converse-chatview', {
                 }
             },
 
-            showNewMessagesIndicator () {
-                u.showElement(this.el.querySelector('.new-msgs-indicator'));
-            },
-
-            hideNewMessagesIndicator () {
-                const new_msgs_indicator = this.el.querySelector('.new-msgs-indicator');
-                if (new_msgs_indicator !== null) {
-                    new_msgs_indicator.classList.add('hidden');
-                }
-            },
-
             /**
              * Called when the chat content is scrolled up or down.
              * We want to record when the user has scrolled away from
@@ -1091,28 +924,26 @@ converse.plugins.add('converse-chatview', {
              * @private
              */
             _markScrolled: function () {
-                let scrolled = true;
                 const is_at_bottom =
                     (this.msgs_container.scrollTop + this.msgs_container.clientHeight) >=
                         this.msgs_container.scrollHeight - 62; // sigh...
 
                 if (is_at_bottom) {
-                    scrolled = false;
+                    u.safeSave(this.model, {
+                        'scrolled': false,
+                        'top_visible_message': null,
+                        'show_new_msgs_indicator': false
+                    });
                     this.onScrolledDown();
+                } else {
+                    u.safeSave(this.model, {
+                        'scrolled': true,
+                        'top_visible_message': null
+                    });
                 }
-                u.safeSave(this.model, {
-                    'scrolled': scrolled,
-                    'top_visible_message': null
-                });
-            },
-
-            viewUnreadMessages () {
-                this.model.save({'scrolled': false, 'top_visible_message': null});
-                this.scrollDown();
             },
 
             onScrolledDown () {
-                this.hideNewMessagesIndicator();
                 if (_converse.windowState !== 'hidden') {
                     this.model.clearUnreadMsgCounter();
                 }

+ 1 - 49
src/converse-muc-views.js

@@ -438,17 +438,10 @@ converse.plugins.add('converse-muc-views', {
                 'click .chatbox-navback': 'showControlBox',
                 'click .chatbox-title': 'minimize',
                 'click .hide-occupants': 'hideOccupants',
-                'click .new-msgs-indicator': 'viewUnreadMessages',
                 // Arrow functions don't work here because you can't bind a different `this` param to them.
                 'click .occupant-nick': function (ev) {this.insertIntoTextArea(ev.target.textContent) },
                 'click .send-button': 'onFormSubmitted',
-                'dragover .chat-textarea': 'onDragOver',
-                'drop .chat-textarea': 'onDrop',
-                'input .chat-textarea': 'inputChanged',
-                'keydown .chat-textarea': 'onKeyDown',
-                'keyup .chat-textarea': 'onKeyUp',
                 'mousedown .dragresize-occupants-left': 'onStartResizeOccupants',
-                'paste .chat-textarea': 'onPaste',
                 'submit .muc-nickname-form': 'submitNickname',
             },
 
@@ -457,7 +450,7 @@ converse.plugins.add('converse-muc-views', {
 
                 this.listenTo(this.model, 'change', debounce(() => this.renderHeading(), 250));
                 this.listenTo(this.model, 'change:composing_spoiler', this.renderMessageForm);
-                this.listenTo(this.model, 'change:hidden_occupants', this.renderToolbar);
+                this.listenTo(this.model, 'change:hidden_occupants', this.renderMessageForm);
                 this.listenTo(this.model, 'configurationNeeded', this.getAndRenderConfigurationForm);
                 this.listenTo(this.model, 'destroy', this.hide);
                 this.listenTo(this.model, 'show', this.show);
@@ -635,7 +628,6 @@ converse.plugins.add('converse-muc-views', {
                 render(tpl, this.el.querySelector('.chat-head-chatroom'));
             },
 
-
             renderBottomPanel () {
                 const container = this.el.querySelector('.bottom-panel');
                 const entered = this.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED;
@@ -643,7 +635,6 @@ converse.plugins.add('converse-muc-views', {
                 container.innerHTML = tpl_chatroom_bottom_panel({__, can_edit, entered});
                 if (entered && can_edit) {
                     this.renderMessageForm();
-                    this.initMentionAutoComplete();
                 }
             },
 
@@ -765,23 +756,6 @@ converse.plugins.add('converse-muc-views', {
                 return element;
             },
 
-            initMentionAutoComplete () {
-                this.mention_auto_complete = new _converse.AutoComplete(this.el, {
-                    'auto_first': true,
-                    'auto_evaluate': false,
-                    'min_chars': api.settings.get('muc_mention_autocomplete_min_chars'),
-                    'match_current_word': true,
-                    'list': () => this.getAutoCompleteList(),
-                    'filter': api.settings.get('muc_mention_autocomplete_filter') == 'contains' ?
-                        _converse.FILTER_CONTAINS :
-                        _converse.FILTER_STARTSWITH,
-                    'ac_triggers': ["Tab", "@"],
-                    'include_triggers': [],
-                    'item': this.getAutoCompleteListItem
-                });
-                this.mention_auto_complete.on('suggestion-box-selectcomplete', () => (this.auto_completing = false));
-            },
-
             /**
              * Get the nickname value from the form and then join the groupchat with it.
              * @private
@@ -794,18 +768,6 @@ converse.plugins.add('converse-muc-views', {
                 nick && this.model.join(nick);
             },
 
-            onKeyDown (ev) {
-                if (this.mention_auto_complete.onKeyDown(ev)) {
-                    return;
-                }
-                return _converse.ChatBoxView.prototype.onKeyDown.call(this, ev);
-            },
-
-            onKeyUp (ev) {
-                this.mention_auto_complete.evaluate(ev);
-                return _converse.ChatBoxView.prototype.onKeyUp.call(this, ev);
-            },
-
             async onMessageRetractButtonClicked (message) {
                 const retraction_warning =
                     __("Be aware that other XMPP/Jabber clients (and servers) may "+
@@ -1074,16 +1036,6 @@ converse.plugins.add('converse-muc-views', {
                 }
             },
 
-            getToolbarOptions () {
-                return Object.assign(
-                    _converse.ChatBoxView.prototype.getToolbarOptions.apply(this, arguments), {
-                        'is_groupchat': true,
-                        'label_hide_occupants': __('Hide the list of participants'),
-                        'show_occupants_toggle': _converse.visible_toolbar_buttons.toggle_occupants
-                    }
-                );
-            },
-
             /**
              * Closes this chat box, which implies leaving the groupchat as well.
              * @private

+ 1 - 1
src/converse-omemo.js

@@ -323,7 +323,7 @@ function toggleOMEMO (ev) {
 
 
 function getOMEMOToolbarButton (toolbar_el, buttons) {
-    const model = toolbar_el.model;
+    const model = toolbar_el.chatview.model;
     const is_muc = model.get('type') === _converse.CHATROOMS_TYPE;
     let title;
     if (is_muc && model.get('omemo_supported')) {

+ 1 - 0
src/templates/chatbox.js

@@ -1,5 +1,6 @@
 import { html } from "lit-html";
 
+
 export default (o) => html`
     <div class="flyout box-flyout">
         <div class="chat-head chat-head-chatbox row no-gutters"></div>

+ 19 - 21
src/templates/chatbox_message_form.js

@@ -1,24 +1,22 @@
+import "../components/message-form.js";
+import { api } from "@converse/headless/converse-core";
 import { html } from "lit-html";
 
 
-export default (o) => html`
-    <div class="new-msgs-indicator hidden">▼ ${ o.unread_msgs } ▼</div>
-    <form class="setNicknameButtonForm hidden">
-        <input type="submit" class="btn btn-primary" name="join" value="Join"/>
-    </form>
-    <form class="sendXMPPMessage">
-        <span class="chat-toolbar no-text-select"></span>
-        <input type="text" placeholder="${o.label_spoiler_hint || ''}" value="${o.hint_value || ''}" class="${o.composing_spoiler ? '' : 'hidden'} spoiler-hint"/>
-
-        <div class="suggestion-box">
-            <ul class="suggestion-box__results suggestion-box__results--above" hidden=""></ul>
-            <textarea
-                type="text"
-                class="chat-textarea suggestion-box__input
-                    ${ o.show_send_button ? 'chat-textarea-send-button' : '' }
-                    ${ o.composing_spoile ? 'spoiler' : '' }"
-                placeholder="${o.label_message}">${ o.message_value || '' }</textarea>
-            <span class="suggestion-box__additions visually-hidden" role="status" aria-live="assertive" aria-relevant="additions"></span>
-        </div>
-    </form>
-`;
+export default (o) => {
+    return html`
+        <div class="new-msgs-indicator hidden">▼ ${ o.unread_msgs } ▼</div>
+        <form class="setNicknameButtonForm hidden">
+            <input type="submit" class="btn btn-primary" name="join" value="Join"/>
+        </form>
+        <converse-message-form
+            .chatview=${o.chatview}
+            .getAutoCompleteList=${o.getAutoCompleteList}}
+            ?composing_spoiler="${o.composing_spoiler}"
+            auto_first=${true}
+            filter=${api.settings.get('muc_mention_autocomplete_filter')}
+            message_value="${o.message_value || ''}"
+            min_chars=${api.settings.get('muc_mention_autocomplete_min_chars')}
+            name="chat_message"
+        ></converse-message-form>`;
+}

+ 0 - 27
src/templates/toolbar.js

@@ -1,27 +0,0 @@
-import { html } from "lit-html";
-import { api } from '@converse/headless/converse-core.js';
-
-export default (o) => {
-    const message_limit = api.settings.get('message_limit');
-    const show_call_button = api.settings.get('visible_toolbar_buttons').call;
-    const show_emoji_button = api.settings.get('visible_toolbar_buttons').emoji;
-    const show_send_button = api.settings.get('show_send_button');
-    const show_spoiler_button = api.settings.get('visible_toolbar_buttons').spoiler;
-    const show_toolbar = api.settings.get('show_toolbar');
-    return html`
-        <converse-chat-toolbar
-            .chatview=${o.chatview}
-            .model=${o.model}
-            ?composing_spoiler="${o.composing_spoiler}"
-            ?hidden_occupants="${o.hidden_occupants}"
-            ?is_groupchat="${o.is_groupchat}"
-            ?show_call_button="${show_call_button}"
-            ?show_emoji_button="${show_emoji_button}"
-            ?show_occupants_toggle="${o.show_occupants_toggle}"
-            ?show_send_button="${show_send_button}"
-            ?show_spoiler_button="${show_spoiler_button}"
-            ?show_toolbar="${show_toolbar}"
-            message_limit="${message_limit}"
-        ></converse-chat-toolbar>
-    `;
-}