浏览代码

Create a component which renders message actions in a dropdown

JC Brand 5 年之前
父节点
当前提交
c2c1db587f

+ 34 - 18
sass/_messages.scss

@@ -90,7 +90,6 @@
             display: inline-flex;
             width: 100%;
             flex-direction: row;
-            overflow: auto; // Ensures that content stays inside
             padding: 0.125rem 1rem;
 
             &.onload {
@@ -98,12 +97,7 @@
                 -webkit-animation: colorchange-chatmessage 1s;
             }
             &:hover {
-                background-color: rgba(0, 0, 0, 0.035);
-                .chat-msg__actions {
-                    .chat-msg__action {
-                        opacity: 1;
-                    }
-                }
+                background-color: var(--list-item-hover-color);
             }
             &.correcting {
                 &.groupchat  {
@@ -162,9 +156,15 @@
                 display: flex;
                 flex-direction: row;
                 justify-content: space-between;
+                &:hover {
+                    .btn--standalone {
+                        opacity: 1;
+                    }
+                }
             }
 
             .chat-msg__message {
+                line-height: 1.5em;
                 display: inline-flex;
                 flex-direction: column;
                 width: 100%;
@@ -235,20 +235,36 @@
                 }
             }
 
+            converse-message-actions {
+                margin-left: 0.5em;
+            }
+
             .chat-msg__actions {
-                display: flex;
-                flex-wrap: nowrap;
-                .chat-msg__action {
-                    height: var(--message-font-size);
-                    font-size: var(--message-font-size);
-                    padding: 0;
-                    margin-left: 0.75em;
+                .dropdown-menu {
+                    min-width: 5rem;
+                }
+                i {
+                    color: var(--text-color-lighten-15-percent);
+                    font-size: 70%;
+                }
+                button {
                     border: none;
-                    opacity: 0;
                     background: transparent;
-                    cursor: pointer;
-                    &:focus {
-                        opacity: 1;
+                    color: var(--text-color-lighten-15-percent);
+                    padding: 0 0.25em;
+                }
+                .btn--standalone {
+                    opacity: 0;
+                    margin-top: -0.2em;
+                }
+                .chat-msg__action {
+                    width: 100%;
+                    padding: 0.5em 1em;
+                    text-align: left;
+                    white-space: nowrap;
+                    &:hover {
+                        color: var(--text-color);
+                        background-color: var(--list-item-hover-color);
                     }
                 }
             }

+ 1 - 0
sass/_variables.scss

@@ -178,6 +178,7 @@ $mobile_portrait_length: 480px !default;
     --list-toggle-color: #818479; // $gray-color
     --list-toggle-hover-color: #585B51; // $dark-gray-color
     --list-toggle-font-weight: normal;
+    --list-item-hover-color: rgba(0, 0, 0, 0.035);
     --list-item-action-color: #e3eef3; // lighten($lightest-blue, 25%)
     --list-item-link-color: inherit;
     --list-item-link-hover-color: var(--dark-link-color);

+ 1 - 1
spec/chatbox.js

@@ -107,7 +107,7 @@ describe("Chatboxes", function () {
             // get the 'chat-msg--followup' class.
             message = 'This a normal message';
             await mock.sendMessage(view, message);
-            const msg_txt_sel = 'converse-chat-message:last-child .chat-msg__body';
+            const msg_txt_sel = 'converse-chat-message:last-child .chat-msg__text';
             await u.waitUntil(() => view.el.querySelector(msg_txt_sel).textContent.trim() === message);
             let el = view.el.querySelector('converse-chat-message:last-child .chat-msg__body');
             expect(u.hasClass('chat-msg--followup', el)).toBeFalsy();

+ 1 - 1
spec/messages.js

@@ -106,7 +106,7 @@ describe("A Chat Message", function () {
         const first_msg = view.model.messages.findWhere({'message': 'But soft, what light through yonder airlock breaks?'});
         expect(view.el.querySelectorAll('.chat-msg .chat-msg__action').length).toBe(2);
         let action = view.el.querySelector('.chat-msg .chat-msg__action');
-        expect(action.getAttribute('title')).toBe('Edit this message');
+        expect(action.textContent.trim()).toBe('Edit');
 
         action.style.opacity = 1;
         action.click();

+ 1 - 1
spec/muc_messages.js

@@ -1215,7 +1215,7 @@ describe("A Groupchat Message", function () {
                             `<origin-id id="${msg.nodeTree.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
                         `</message>`);
 
-            const action = view.el.querySelector('.chat-msg .chat-msg__action');
+            const action = await u.waitUntil(() => view.el.querySelector('.chat-msg .chat-msg__action'));
             action.style.opacity = 1;
             action.click();
 

+ 2 - 1
spec/omemo.js

@@ -437,7 +437,8 @@ describe("The OMEMO module", function() {
         _converse.connection._dataRecv(mock.createRequest(carbon));
         await new Promise(resolve => view.model.messages.once('rendered', resolve));
         expect(view.model.messages.length).toBe(1);
-        expect(view.el.querySelector('.chat-msg__body').textContent.trim())
+
+        expect(view.el.querySelector('.chat-msg__text').textContent.trim())
             .toBe('This is an encrypted carbon message from another device of mine');
 
         expect(devicelist.devices.length).toBe(2);

+ 1 - 2
spec/retractions.js

@@ -119,8 +119,7 @@ describe("Message Retractions", function () {
             `);
             _converse.connection._dataRecv(mock.createRequest(received_stanza));
             await u.waitUntil(() => view.model.handleRetraction.calls.count() === 2);
-
-            expect(view.el.querySelectorAll('.chat-msg').length).toBe(0);
+            await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 1, 1000);
             expect(view.model.messages.length).toBe(1);
 
             const message = view.model.messages.at(0)

+ 3 - 1
src/components/dropdown.js

@@ -52,15 +52,17 @@ export class DropdownList extends BaseDropdown {
 
     static get properties () {
         return {
+            'icon_classes': { type: String },
             'items': { type: Array }
         }
     }
 
     render () {
+        const icon_classes = this.icon_classes || "fa fa-bars";
         return html`
             <div class="dropleft">
                 <button type="button" class="btn btn--transparent btn--standalone" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                    <i class="fa fa-bars only-icon"></i>
+                    <i class="${icon_classes} only-icon"></i>
                 </button>
                 <div class="dropdown-menu">
                     ${ this.items.map(b => until(b, '')) }

+ 74 - 0
src/components/message-actions.js

@@ -0,0 +1,74 @@
+import { CustomElement } from './element.js';
+import { __ } from '@converse/headless/i18n';
+import { html } from 'lit-element';
+import { until } from 'lit-html/directives/until.js';
+
+
+class MessageActions extends CustomElement {
+
+    static get properties () {
+        return {
+            chatview: { type: Object },
+            model: { type: Object },
+            editable: { type: Boolean },
+            correcting: { type: Boolean },
+            message_type: { type: String },
+            is_retracted: { type: Boolean },
+        }
+    }
+
+    render () {
+        return html`${ until(this.renderActions(), '') }`;
+    }
+
+    static getActionsDropdownItem (o) {
+        return html`
+            <button class="chat-msg__action ${o.button_class}" @click=${o.handler}>
+                <fa-icon class="${o.icon_class}" path-prefix="/dist" color="var(--text-color-lighten-15-percent)" size="1em"></fa-icon>
+                ${o.i18n_text}
+            </button>
+        `;
+    }
+
+    onMessageEditButtonClicked (ev) {
+        ev.preventDefault();
+        this.chatview.onMessageEditButtonClicked(this.model);
+    }
+
+    onMessageRetractButtonClicked (ev) {
+        ev.preventDefault();
+        this.chatview.onMessageRetractButtonClicked(this.model);
+    }
+
+    async renderActions () {
+        const buttons = [];
+        if (this.editable) {
+            buttons.push({
+                'i18n_text': this.correcting ? __('Cancel Editing') : __('Edit'),
+                'handler': ev => this.onMessageEditButtonClicked(ev),
+                'button_class': 'chat-msg__action-edit',
+                'icon_class': 'fa fa-pencil-alt',
+                'name': 'edit'
+            });
+        }
+        const may_be_moderated = this.model.get('type') === 'groupchat' && await this.model.mayBeModerated();
+        const retractable = !this.is_retracted && (this.model.mayBeRetracted() || may_be_moderated);
+        if (retractable) {
+            buttons.push({
+                'i18n_text': __('Retract'),
+                'handler': ev => this.onMessageRetractButtonClicked(ev),
+                'button_class': 'chat-msg__action-retract',
+                'icon_class': 'fas fa-trash-alt',
+                'name': 'retract'
+            });
+        }
+        const items = buttons.map(b => MessageActions.getActionsDropdownItem(b));
+        if (items.length) {
+            return html`<converse-dropdown class="chat-msg__actions" .items=${ items }></converse-dropdown>`;
+        } else {
+            return '';
+        }
+    }
+}
+
+customElements.define('converse-message-actions', MessageActions);

+ 9 - 23
src/components/message.js

@@ -1,4 +1,6 @@
 import "./message-body.js";
+import './dropdown.js';
+import './message-actions.js';
 import MessageVersionsModal from '../modals/message-versions.js';
 import dayjs from 'dayjs';
 import filesize from "filesize";
@@ -8,12 +10,10 @@ import { __ } from '@converse/headless/i18n';
 import { _converse, api, converse } from  "@converse/headless/converse-core";
 import { html } from 'lit-element';
 import { renderAvatar } from './../templates/directives/avatar';
-import { renderRetractionLink } from './../templates/directives/retraction';
 
 const { Strophe } = converse.env;
 const u = converse.env.utils;
 
-const i18n_edit_message = __('Edit this message');
 const i18n_edited = __('This message has been edited');
 const i18n_show = __('Show more');
 const i18n_show_less = __('Show less');
@@ -150,17 +150,13 @@ class Message extends CustomElement {
                         </div>
                         ${ (this.received && !this.is_me_message && !is_groupchat_message) ? html`<span class="fa fa-check chat-msg__receipt"></span>` : '' }
                         ${ (this.edited) ? html`<i title="${ i18n_edited }" class="fa fa-edit chat-msg__edit-modal" @click=${this.showMessageVersionsModal}></i>` : '' }
-                        <div class="chat-msg__actions">
-                            ${ this.editable ?
-                                    html`<button
-                                        class="chat-msg__action chat-msg__action-edit"
-                                        title="${i18n_edit_message}"
-                                        @click=${this.onMessageEditButtonClicked}
-                                    >
-                                    <fa-icon class="fas fa-pencil-alt" path-prefix="dist" color="var(--text-color-lighten-15-percent)" size="1em"></fa-icon>
-                                </button>` : '' }
-                            ${ renderRetractionLink(this) }
-                        </div>
+                        <converse-message-actions
+                            .chatview=${this.chatview}
+                            .model=${this.model}
+                            ?correcting="${this.correcting}"
+                            ?editable="${this.editable}"
+                            ?is_retracted="${this.is_retracted}"
+                            message_type="${this.message_type}"></converse-message-actions>
                     </div>
                 </div>
             </div>`;
@@ -189,16 +185,6 @@ class Message extends CustomElement {
         this.parentElement.removeChild(this);
     }
 
-    onMessageRetractButtonClicked (ev) {
-        ev.preventDefault();
-        this.chatview.onMessageRetractButtonClicked(this.model);
-    }
-
-    onMessageEditButtonClicked (ev) {
-        ev.preventDefault();
-        this.chatview.onMessageEditButtonClicked(this.model);
-    }
-
     isFollowup () {
         const messages = this.model.collection.models;
         const idx = messages.indexOf(this.model);