Add the ability to retract XEP-0316 MEP messages
This commit is contained in:
parent
3cfdf4c946
commit
d2a33bc210
@ -37,8 +37,14 @@ const ChatRoomMessageMixin = {
|
||||
* @returns { Boolean }
|
||||
*/
|
||||
mayBeModerated () {
|
||||
if (typeof this.get('from_muc') === 'undefined') {
|
||||
// If from_muc is not defined, then this message hasn't been
|
||||
// reflected yet, which means we won't have a XEP-0359 stanza id.
|
||||
return;
|
||||
}
|
||||
return (
|
||||
['all', 'moderator'].includes(api.settings.get('allow_message_retraction')) &&
|
||||
this.get(`stanza_id ${this.get('from_muc')}`) &&
|
||||
this.collection.chatbox.canModerateMessages()
|
||||
);
|
||||
},
|
||||
|
@ -1825,6 +1825,8 @@ const ChatRoomMixin = {
|
||||
|
||||
getUpdatedMessageAttributes (message, attrs) {
|
||||
const new_attrs = _converse.ChatBox.prototype.getUpdatedMessageAttributes.call(this, message, attrs);
|
||||
new_attrs['from_muc'] = attrs['from_muc'];
|
||||
|
||||
if (this.isOwnMessage(attrs)) {
|
||||
const stanza_id_keys = Object.keys(attrs).filter(k => k.startsWith('stanza_id'));
|
||||
Object.assign(new_attrs, pick(attrs, stanza_id_keys));
|
||||
@ -2114,12 +2116,7 @@ const ChatRoomMixin = {
|
||||
return false;
|
||||
}
|
||||
attrs.activities?.forEach(activity_attrs => {
|
||||
const data = Object.assign({
|
||||
'from_muc': attrs.from,
|
||||
'msgid': attrs.msgid,
|
||||
'received': attrs.received,
|
||||
'time': attrs.time,
|
||||
}, activity_attrs);
|
||||
const data = Object.assign(attrs,activity_attrs);
|
||||
this.createMessage(data)
|
||||
// Trigger so that notifications are shown
|
||||
api.trigger('message', { 'attrs': data, 'chatbox': this });
|
||||
@ -2137,7 +2134,7 @@ const ChatRoomMixin = {
|
||||
*/
|
||||
getDuplicateMessage (attrs) {
|
||||
if (attrs.activities?.length) {
|
||||
return this.messages.findWhere({'type': 'info', 'msgid': attrs.msgid});
|
||||
return this.messages.findWhere({'type': 'mep', 'msgid': attrs.msgid});
|
||||
} else {
|
||||
return _converse.ChatBox.prototype.getDuplicateMessage.call(this, attrs);
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ export function getMEPActivities (stanza) {
|
||||
if (message) {
|
||||
const references = getReferences(stanza);
|
||||
const reason = el.querySelector('reason')?.textContent;
|
||||
return { from, msgid, message, reason, references, 'type': 'info' };
|
||||
return { from, msgid, message, reason, references, 'type': 'mep' };
|
||||
}
|
||||
return {};
|
||||
});
|
||||
@ -175,6 +175,7 @@ export async function parseMUCMessage (stanza, chatbox, _converse) {
|
||||
* @property { String } to - The recipient JID
|
||||
* @property { String } type - The type of message
|
||||
*/
|
||||
|
||||
let attrs = Object.assign(
|
||||
{
|
||||
from,
|
||||
|
32
src/plugins/muc-views/templates/mep-message.js
Normal file
32
src/plugins/muc-views/templates/mep-message.js
Normal file
@ -0,0 +1,32 @@
|
||||
import { converse } from '@converse/headless/core';
|
||||
import { html } from 'lit';
|
||||
|
||||
const { dayjs } = converse.env;
|
||||
|
||||
export default (el) => {
|
||||
const isodate = dayjs(el.model.get('time')).toISOString();
|
||||
return html`
|
||||
<div class="message chat-info message--mep ${ el.getExtraMessageClasses() }"
|
||||
data-isodate="${isodate}"
|
||||
data-type="${el.data_name}"
|
||||
data-value="${el.data_value}">
|
||||
|
||||
<div class="chat-msg__content">
|
||||
<div class="chat-msg__body chat-msg__body--${el.model.get('type')} ${el.model.get('is_delayed') ? 'chat-msg__body--delayed' : '' }">
|
||||
<div class="chat-info__message">
|
||||
${ el.isRetracted() ? el.renderRetraction() : html`
|
||||
<converse-rich-text
|
||||
.mentions=${el.model.get('references')}
|
||||
render_styling
|
||||
text=${el.model.getMessageText()}>
|
||||
</converse-rich-text>
|
||||
${ el.model.get('reason') ? html`<q class="reason">${el.model.get('reason')}</q>` : `` }
|
||||
`}
|
||||
</div>
|
||||
<converse-message-actions
|
||||
?is_retracted=${el.isRetracted()}
|
||||
.model=${el.model}></converse-message-actions>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*global mock, converse */
|
||||
|
||||
const { u } = converse.env;
|
||||
const { u, Strophe } = converse.env;
|
||||
|
||||
describe("A XEP-0316 MEP notification", function () {
|
||||
|
||||
@ -36,7 +36,7 @@ describe("A XEP-0316 MEP notification", function () {
|
||||
|
||||
_converse.connection._dataRecv(mock.createRequest(message));
|
||||
await u.waitUntil(() => view.querySelectorAll('.chat-info').length === 1);
|
||||
expect(view.querySelector('.chat-info__message').textContent.trim()).toBe(msg);
|
||||
expect(view.querySelector('.chat-info__message converse-rich-text').textContent.trim()).toBe(msg);
|
||||
expect(view.querySelector('.reason').textContent.trim()).toBe(reason);
|
||||
|
||||
// Check that duplicates aren't created
|
||||
@ -74,7 +74,7 @@ describe("A XEP-0316 MEP notification", function () {
|
||||
|
||||
_converse.connection._dataRecv(mock.createRequest(message));
|
||||
await u.waitUntil(() => view.querySelectorAll('.chat-info').length === 2);
|
||||
expect(view.querySelector('converse-chat-message:last-child .chat-info__message').textContent.trim()).toBe(msg);
|
||||
expect(view.querySelector('converse-chat-message:last-child .chat-info__message converse-rich-text').textContent.trim()).toBe(msg);
|
||||
expect(view.querySelector('converse-chat-message:last-child .reason').textContent.trim()).toBe(reason);
|
||||
|
||||
// Check that duplicates aren't created
|
||||
@ -126,7 +126,86 @@ describe("A XEP-0316 MEP notification", function () {
|
||||
|
||||
const view = await u.waitUntil(() => _converse.chatboxviews.get(muc_jid));
|
||||
await u.waitUntil(() => view.querySelectorAll('.chat-info').length === 1, 1000);
|
||||
expect(view.querySelector('.chat-info__message').textContent.trim()).toBe(msg);
|
||||
expect(view.querySelector('.chat-info__message converse-rich-text').textContent.trim()).toBe(msg);
|
||||
expect(view.querySelector('.reason').textContent.trim()).toBe(reason);
|
||||
}));
|
||||
|
||||
it("can be retracted by a moderator",
|
||||
mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {
|
||||
|
||||
const muc_jid = 'lounge@montague.lit';
|
||||
const nick = 'romeo';
|
||||
const features = [...mock.default_muc_features, Strophe.NS.MODERATE];
|
||||
await mock.openAndEnterChatRoom(_converse, muc_jid, nick, features);
|
||||
const view = _converse.chatboxviews.get(muc_jid);
|
||||
const msg = 'An anonymous user has saluted romeo';
|
||||
const reason = 'Thank you for helping me yesterday';
|
||||
_converse.connection._dataRecv(mock.createRequest(u.toStanza(`
|
||||
<message from='${muc_jid}'
|
||||
to='${_converse.jid}'
|
||||
type='headline'
|
||||
id='zns61f38'>
|
||||
<event xmlns='http://jabber.org/protocol/pubsub#event'>
|
||||
<items node='urn:ietf:params:xml:ns:conference-info'>
|
||||
<item id='ehs51f40'>
|
||||
<conference-info xmlns='urn:ietf:params:xml:ns:conference-info'>
|
||||
<activity xmlns='http://jabber.org/protocol/activity'>
|
||||
<other/>
|
||||
<text id="activity-text" xml:lang="en">${msg}</text>
|
||||
<reference anchor="activity-text" xmlns="urn:xmpp:reference:0" begin="30" end="35" type="mention" uri="xmpp:${_converse.bare_jid}"/>
|
||||
<reason id="activity-reason">${reason}</reason>
|
||||
</activity>
|
||||
</conference-info>
|
||||
</item>
|
||||
</items>
|
||||
</event>
|
||||
<stanza-id xmlns='urn:xmpp:sid:0' id='stanza-id-1' by='${muc_jid}'/>
|
||||
</message>`
|
||||
)));
|
||||
|
||||
await u.waitUntil(() => view.querySelectorAll('.chat-info').length === 1);
|
||||
expect(view.querySelector('.chat-info__message converse-rich-text').textContent.trim()).toBe(msg);
|
||||
expect(view.querySelector('.reason').textContent.trim()).toBe(reason);
|
||||
expect(view.querySelectorAll('converse-message-actions converse-dropdown .chat-msg__action').length).toBe(1);
|
||||
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')));
|
||||
const submit_button = document.querySelector('#converse-modals .modal button[type="submit"]');
|
||||
submit_button.click();
|
||||
|
||||
const sent_IQs = _converse.connection.IQ_stanzas;
|
||||
const stanza = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector('iq apply-to[xmlns="urn:xmpp:fasten:0"]')).pop());
|
||||
const message = view.model.messages.at(0);
|
||||
const stanza_id = message.get(`stanza_id ${view.model.get('jid')}`);
|
||||
|
||||
expect(Strophe.serialize(stanza)).toBe(
|
||||
`<iq id="${stanza.getAttribute('id')}" to="${muc_jid}" type="set" xmlns="jabber:client">`+
|
||||
`<apply-to id="${stanza_id}" xmlns="urn:xmpp:fasten:0">`+
|
||||
`<moderate xmlns="urn:xmpp:message-moderate:0">`+
|
||||
`<retract xmlns="urn:xmpp:message-retract:0"/>`+
|
||||
`<reason></reason>`+
|
||||
`</moderate>`+
|
||||
`</apply-to>`+
|
||||
`</iq>`);
|
||||
|
||||
// The server responds with a retraction message
|
||||
const retraction = u.toStanza(`
|
||||
<message type="groupchat" id='retraction-id-1' from="${muc_jid}" to="${muc_jid}/${nick}">
|
||||
<apply-to id="${stanza_id}" xmlns="urn:xmpp:fasten:0">
|
||||
<moderated by='${_converse.bare_jid}' xmlns='urn:xmpp:message-moderate:0'>
|
||||
<retract xmlns='urn:xmpp:message-retract:0' />
|
||||
<reason></reason>
|
||||
</moderated>
|
||||
</apply-to>
|
||||
</message>`);
|
||||
await view.model.handleMessageStanza(retraction);
|
||||
expect(view.model.messages.length).toBe(1);
|
||||
expect(view.model.messages.at(0).get('moderated')).toBe('retracted');
|
||||
expect(view.model.messages.at(0).get('moderation_reason')).toBe('');
|
||||
expect(view.model.messages.at(0).get('is_ephemeral')).toBe(false);
|
||||
expect(view.model.messages.at(0).get('editable')).toBe(false);
|
||||
const msg_el = view.querySelector('.chat-msg--retracted .chat-info__message div');
|
||||
expect(msg_el.textContent).toBe(`${nick} has removed this message`);
|
||||
}));
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*global mock, converse */
|
||||
|
||||
const { Promise, Strophe, $msg, $pres, sizzle } = converse.env;
|
||||
const { Promise, $msg, $pres, sizzle } = converse.env;
|
||||
const u = converse.env.utils;
|
||||
const original_timeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
|
||||
|
||||
|
@ -753,6 +753,12 @@ describe("Message Retractions", function () {
|
||||
|
||||
view.model.sendMessage({'body': 'Visit this site to get free bitcoin'});
|
||||
await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 1);
|
||||
|
||||
// Check that you can only edit a message before it's been
|
||||
// reflected. You can't retract because it hasn't
|
||||
await u.waitUntil(() => view.querySelector('.chat-msg__content .chat-msg__action-edit'));
|
||||
expect(view.querySelectorAll('.chat-msg__action').length).toBe(1);
|
||||
|
||||
const stanza_id = 'retraction-id-1';
|
||||
const msg_obj = view.model.messages.at(0);
|
||||
const reflection_stanza = u.toStanza(`
|
||||
@ -766,6 +772,7 @@ describe("Message Retractions", function () {
|
||||
by="lounge@montague.lit"/>
|
||||
<origin-id xmlns="urn:xmpp:sid:0" id="${msg_obj.get('origin_id')}"/>
|
||||
</message>`);
|
||||
|
||||
await view.model.handleMessageStanza(reflection_stanza);
|
||||
await u.waitUntil(() => view.querySelectorAll('.chat-msg__body.chat-msg__body--received').length, 500);
|
||||
expect(view.model.messages.length).toBe(1);
|
||||
|
@ -8,17 +8,15 @@ import { html } from 'lit';
|
||||
import { isMediaURLDomainAllowed, isDomainWhitelisted } from '@converse/headless/utils/url.js';
|
||||
import { until } from 'lit/directives/until.js';
|
||||
|
||||
import './styles/message-actions.scss';
|
||||
|
||||
const { Strophe, u } = converse.env;
|
||||
|
||||
class MessageActions extends CustomElement {
|
||||
static get properties () {
|
||||
return {
|
||||
correcting: { type: Boolean },
|
||||
editable: { type: Boolean },
|
||||
is_retracted: { type: Boolean },
|
||||
message_type: { type: String },
|
||||
model: { type: Object },
|
||||
unfurls: { type: Number },
|
||||
model: { type: Object }
|
||||
};
|
||||
}
|
||||
|
||||
@ -28,7 +26,7 @@ class MessageActions extends CustomElement {
|
||||
this.listenTo(settings, 'change:allowed_image_domains', () => this.requestUpdate());
|
||||
this.listenTo(settings, 'change:allowed_video_domains', () => this.requestUpdate());
|
||||
this.listenTo(settings, 'change:render_media', () => this.requestUpdate());
|
||||
this.listenTo(this.model, 'change:hide_url_previews', () => this.requestUpdate());
|
||||
this.listenTo(this.model, 'change', () => this.requestUpdate());
|
||||
}
|
||||
|
||||
render () {
|
||||
@ -262,7 +260,7 @@ class MessageActions extends CustomElement {
|
||||
|
||||
async getActionButtons () {
|
||||
const buttons = [];
|
||||
if (this.editable) {
|
||||
if (this.model.get('editable')) {
|
||||
/**
|
||||
* @typedef { Object } MessageActionAttributes
|
||||
* An object which represents a message action (as shown in the message dropdown);
|
||||
@ -273,14 +271,16 @@ class MessageActions extends CustomElement {
|
||||
* @property { String } name
|
||||
*/
|
||||
buttons.push({
|
||||
'i18n_text': this.correcting ? __('Cancel Editing') : __('Edit'),
|
||||
'i18n_text': this.model.get('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 may_be_moderated = ['groupchat', 'mep'].includes(this.model.get('type')) &&
|
||||
(await this.model.mayBeModerated());
|
||||
const retractable = !this.is_retracted && (this.model.mayBeRetracted() || may_be_moderated);
|
||||
if (retractable) {
|
||||
buttons.push({
|
||||
|
@ -8,16 +8,18 @@ import UserDetailsModal from 'modals/user-details.js';
|
||||
import filesize from 'filesize';
|
||||
import log from '@converse/headless/log';
|
||||
import tpl_info_message from './templates/info-message.js';
|
||||
import tpl_mep_message from 'plugins/muc-views/templates/mep-message.js';
|
||||
import tpl_message from './templates/message.js';
|
||||
import tpl_message_text from './templates/message-text.js';
|
||||
import tpl_retraction from './templates/retraction.js';
|
||||
import tpl_spinner from 'templates/spinner.js';
|
||||
import { CustomElement } from 'shared/components/element.js';
|
||||
import { __ } from 'i18n';
|
||||
import { _converse, api, converse } from '@converse/headless/core';
|
||||
import { getAppSettings } from '@converse/headless/shared/settings/utils.js';
|
||||
import { getHats } from './utils.js';
|
||||
import { html } from 'lit';
|
||||
import { renderAvatar } from 'shared/directives/avatar';
|
||||
import { getAppSettings } from '@converse/headless/shared/settings/utils.js';
|
||||
|
||||
const { Strophe, dayjs } = converse.env;
|
||||
|
||||
@ -76,6 +78,8 @@ export default class Message extends CustomElement {
|
||||
return tpl_spinner();
|
||||
} else if (this.model.get('file') && this.model.get('upload') !== _converse.SUCCESS) {
|
||||
return this.renderFileProgress();
|
||||
} else if (['mep'].includes(this.model.get('type'))) {
|
||||
return this.renderMEPMessage();
|
||||
} else if (['error', 'info'].includes(this.model.get('type'))) {
|
||||
return this.renderInfoMessage();
|
||||
} else {
|
||||
@ -90,6 +94,18 @@ export default class Message extends CustomElement {
|
||||
);
|
||||
}
|
||||
|
||||
renderRetraction () {
|
||||
return tpl_retraction(this);
|
||||
}
|
||||
|
||||
renderMessageText () {
|
||||
return tpl_message_text(this);
|
||||
}
|
||||
|
||||
renderMEPMessage () {
|
||||
return tpl_mep_message(this);
|
||||
}
|
||||
|
||||
renderInfoMessage () {
|
||||
return tpl_info_message(this);
|
||||
}
|
||||
@ -117,7 +133,9 @@ export default class Message extends CustomElement {
|
||||
}
|
||||
|
||||
shouldShowAvatar () {
|
||||
return api.settings.get('show_message_avatar') && !this.model.isMeCommand() && this.type !== 'headline';
|
||||
return api.settings.get('show_message_avatar') &&
|
||||
!this.model.isMeCommand() &&
|
||||
['chat', 'groupchat'].includes(this.model.get('type'));
|
||||
}
|
||||
|
||||
getAvatarData () {
|
||||
@ -202,7 +220,7 @@ export default class Message extends CustomElement {
|
||||
}
|
||||
|
||||
getRetractionText () {
|
||||
if (this.model.get('type') === 'groupchat' && this.model.get('moderated_by')) {
|
||||
if (['groupchat', 'mep'].includes(this.model.get('type')) && this.model.get('moderated_by')) {
|
||||
const retracted_by_mod = this.model.get('moderated_by');
|
||||
const chatbox = this.model.collection.chatbox;
|
||||
if (!this.model.mod) {
|
||||
@ -217,19 +235,6 @@ export default class Message extends CustomElement {
|
||||
}
|
||||
}
|
||||
|
||||
renderRetraction () {
|
||||
const retraction_text = this.isRetracted() ? this.getRetractionText() : null;
|
||||
return html`
|
||||
<div>${retraction_text}</div>
|
||||
${ this.model.get('moderation_reason') ?
|
||||
html`<q class="chat-msg--retracted__reason">${this.model.get('moderation_reason')}</q>` : '' }
|
||||
`;
|
||||
}
|
||||
|
||||
renderMessageText () {
|
||||
return tpl_message_text(this);
|
||||
}
|
||||
|
||||
showUserModal (ev) {
|
||||
if (this.model.get('sender') === 'me') {
|
||||
api.modal.show(_converse.ProfileModal, {model: this.model}, ev);
|
||||
|
38
src/shared/chat/styles/message-actions.scss
Normal file
38
src/shared/chat/styles/message-actions.scss
Normal file
@ -0,0 +1,38 @@
|
||||
converse-message-actions {
|
||||
margin-left: 0.5em;
|
||||
|
||||
.chat-msg__actions {
|
||||
.dropdown-menu {
|
||||
min-width: 5rem;
|
||||
}
|
||||
i {
|
||||
color: var(--text-color-lighten-15-percent);
|
||||
font-size: 70%;
|
||||
}
|
||||
button {
|
||||
border: none;
|
||||
background: transparent;
|
||||
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;
|
||||
|
||||
converse-icon {
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--text-color);
|
||||
background-color: var(--list-item-hover-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
10
src/shared/chat/styles/retraction.scss
Normal file
10
src/shared/chat/styles/retraction.scss
Normal file
@ -0,0 +1,10 @@
|
||||
converse-chat-message {
|
||||
.message {
|
||||
&.chat-msg--retracted {
|
||||
.chat-msg__message {
|
||||
color: var(--subdued-color);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -37,11 +37,7 @@ export default (el, o) => {
|
||||
</div>
|
||||
<converse-message-actions
|
||||
.model=${el.model}
|
||||
?correcting=${o.correcting}
|
||||
?editable=${o.editable}
|
||||
?is_retracted=${o.is_retracted}
|
||||
unfurls="${el.model.get('ogp_metadata')?.length}"
|
||||
message_type="${o.message_type}"></converse-message-actions>
|
||||
?is_retracted=${o.is_retracted}></converse-message-actions>
|
||||
</div>
|
||||
|
||||
${ el.model.get('ogp_metadata')?.map(m => {
|
||||
|
11
src/shared/chat/templates/retraction.js
Normal file
11
src/shared/chat/templates/retraction.js
Normal file
@ -0,0 +1,11 @@
|
||||
import { html } from 'lit';
|
||||
|
||||
import '../styles/retraction.scss';
|
||||
|
||||
export default (el) => {
|
||||
const retraction_text = el.isRetracted() ? el.getRetractionText() : null;
|
||||
return html`
|
||||
<div class="retraction">${retraction_text}</div>
|
||||
${ el.model.get('moderation_reason') ?
|
||||
html`<q class="chat-msg--retracted__reason">${el.model.get('moderation_reason')}</q>` : '' }`;
|
||||
}
|
@ -2,6 +2,8 @@ import renderRichText from 'shared/directives/rich-text.js';
|
||||
import { CustomElement } from 'shared/components/element.js';
|
||||
import { api } from "@converse/headless/core";
|
||||
|
||||
import './styles/rich-text.scss';
|
||||
|
||||
/**
|
||||
* The RichText custom element allows you to parse transform text into rich DOM elements.
|
||||
* @example <converse-rich-text text="*_hello_ world!*"></converse-rich-text>
|
||||
|
3
src/shared/components/styles/rich-text.scss
Normal file
3
src/shared/components/styles/rich-text.scss
Normal file
@ -0,0 +1,3 @@
|
||||
converse-rich-text {
|
||||
display: block;
|
||||
}
|
@ -62,12 +62,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.chat-msg--retracted {
|
||||
.chat-msg__message {
|
||||
color: var(--subdued-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.chat-info {
|
||||
color: var(--chat-head-color);
|
||||
font-size: var(--message-font-size);
|
||||
@ -149,20 +143,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.chat-msg__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: stretch;
|
||||
margin-left: 0.5rem;
|
||||
width: calc(100% - var(--message-avatar-width));
|
||||
&:hover {
|
||||
.btn--standalone {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-msg__content--me {
|
||||
.chat-msg__body--groupchat {
|
||||
.chat-msg__text {
|
||||
@ -180,12 +160,6 @@
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.chat-msg__body {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
converse-chat-message-body {
|
||||
display: inline;
|
||||
}
|
||||
@ -257,46 +231,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
converse-message-actions {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
.chat-msg__actions {
|
||||
.dropdown-menu {
|
||||
min-width: 5rem;
|
||||
}
|
||||
i {
|
||||
color: var(--text-color-lighten-15-percent);
|
||||
font-size: 70%;
|
||||
}
|
||||
button {
|
||||
border: none;
|
||||
background: transparent;
|
||||
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;
|
||||
|
||||
converse-icon {
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
|
||||
|
||||
&:hover {
|
||||
color: var(--text-color);
|
||||
background-color: var(--list-item-hover-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-msg__avatar {
|
||||
margin-top: 0.5em;
|
||||
vertical-align: middle;
|
||||
@ -356,6 +290,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.chat-msg__content {
|
||||
width: calc(100% - var(--message-avatar-width));
|
||||
}
|
||||
|
||||
&.chat-msg--followup {
|
||||
.chat-msg__heading,
|
||||
.chat-msg__avatar {
|
||||
@ -367,12 +305,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.chat-msg__receipt {
|
||||
margin-left: 0.5em;
|
||||
margin-right: 0.5em;
|
||||
color: var(--message-receipt-color);
|
||||
}
|
||||
}
|
||||
|
||||
.chat-msg__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: stretch;
|
||||
margin-left: 0.5rem;
|
||||
&:hover {
|
||||
.btn--standalone {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-msg__body {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
.chatroom-body .message {
|
||||
|
Loading…
Reference in New Issue
Block a user