Don't pass chatview object to converse-chat-content
and any child components. This makes it easier to use these components independently of one another and the overarching view.
This commit is contained in:
parent
f81292e955
commit
95c14e5a26
|
@ -146,6 +146,9 @@ describe("A Chat Message", function () {
|
|||
keyCode: 13 // Enter
|
||||
});
|
||||
await u.waitUntil(() => textarea.value === '');
|
||||
await u.waitUntil(() => Array.from(view.querySelectorAll('.chat-msg__text')).filter(
|
||||
m => m.textContent === 'It is the east, and Juliet is the sun.').length);
|
||||
|
||||
const messages = view.querySelectorAll('.chat-msg');
|
||||
expect(messages.length).toBe(3);
|
||||
expect(messages[0].querySelector('.chat-msg__text').textContent)
|
||||
|
@ -244,9 +247,9 @@ describe("A Chat Message", function () {
|
|||
action = view.querySelector('.chat-msg .chat-msg__action');
|
||||
action.style.opacity = 1;
|
||||
action.click();
|
||||
expect(textarea.value).toBe('');
|
||||
expect(view.model.messages.at(0).get('correcting')).toBe(false);
|
||||
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
|
||||
expect(textarea.value).toBe('');
|
||||
await u.waitUntil(() => (u.hasClass('correcting', view.querySelector('.chat-msg')) === false), 500);
|
||||
|
||||
// Test that messages from other users don't have the pencil icon
|
||||
|
|
|
@ -1,41 +1,56 @@
|
|||
import "./message-history";
|
||||
import xss from "xss/dist/xss";
|
||||
import { CustomElement } from './element.js';
|
||||
import { _converse, api } from "@converse/headless/core";
|
||||
import { html } from 'lit-element';
|
||||
import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
|
||||
import { api } from "@converse/headless/core";
|
||||
|
||||
export default class ChatContent extends CustomElement {
|
||||
|
||||
static get properties () {
|
||||
return {
|
||||
chatview: { type: Object}
|
||||
jid: { type: String }
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback () {
|
||||
super.connectedCallback();
|
||||
const model = this.chatview.model;
|
||||
this.listenTo(model.messages, 'add', this.requestUpdate);
|
||||
this.listenTo(model.messages, 'change', this.requestUpdate);
|
||||
this.listenTo(model.messages, 'remove', this.requestUpdate);
|
||||
this.listenTo(model.messages, 'reset', this.requestUpdate);
|
||||
this.listenTo(model.notifications, 'change', this.requestUpdate);
|
||||
if (model.occupants) {
|
||||
this.listenTo(model.occupants, 'change', this.requestUpdate);
|
||||
this.model = _converse.chatboxes.get(this.jid);
|
||||
this.listenTo(this.model.messages, 'add', this.requestUpdate);
|
||||
this.listenTo(this.model.messages, 'change', this.requestUpdate);
|
||||
this.listenTo(this.model.messages, 'remove', this.requestUpdate);
|
||||
this.listenTo(this.model.messages, 'reset', this.requestUpdate);
|
||||
this.listenTo(this.model.notifications, 'change', this.requestUpdate);
|
||||
if (this.model.occupants) {
|
||||
this.listenTo(this.model.occupants, 'change', this.requestUpdate);
|
||||
}
|
||||
|
||||
// We jot down whether we were scrolled down before rendering, because when an
|
||||
// image loads, it triggers 'scroll' and the chat will be marked as scrolled,
|
||||
// which is technically true, but not what we want because the user
|
||||
// didn't initiate the scrolling.
|
||||
this.was_scrolled_up = this.model.get('scrolled');
|
||||
this.addEventListener('imageLoaded', () => {
|
||||
!this.was_scrolled_up && this.scrollDown();
|
||||
});
|
||||
}
|
||||
|
||||
render () {
|
||||
const notifications = xss.filterXSS(this.chatview.getNotifications(), {'whiteList': {}});
|
||||
return html`
|
||||
<converse-message-history
|
||||
.chatview=${this.chatview}
|
||||
.messages=${[...this.chatview.model.messages.models]}>
|
||||
.model=${this.model}
|
||||
.messages=${[...this.model.messages.models]}>
|
||||
</converse-message-history>
|
||||
<div class="chat-content__notifications">${unsafeHTML(notifications)}</div>
|
||||
<div class="chat-content__notifications">${this.model.getNotificationsText()}</div>
|
||||
`;
|
||||
}
|
||||
|
||||
scrollDown () {
|
||||
if (this.scrollTo) {
|
||||
const behavior = this.scrollTop ? 'smooth' : 'auto';
|
||||
this.scrollTo({ 'top': this.scrollHeight, behavior });
|
||||
} else {
|
||||
this.scrollTop = this.scrollHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
api.elements.define('converse-chat-content', ChatContent);
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
import log from '@converse/headless/log';
|
||||
import { CustomElement } from './element.js';
|
||||
import { __ } from '../i18n';
|
||||
import { api } from "@converse/headless/core";
|
||||
import { _converse, api, converse } from "@converse/headless/core";
|
||||
import { html } from 'lit-element';
|
||||
import { until } from 'lit-html/directives/until.js';
|
||||
|
||||
const { Strophe, u } = converse.env;
|
||||
|
||||
|
||||
class MessageActions extends CustomElement {
|
||||
|
||||
static get properties () {
|
||||
return {
|
||||
chatview: { type: Object },
|
||||
model: { type: Object },
|
||||
editable: { type: Boolean },
|
||||
correcting: { type: Boolean },
|
||||
|
@ -22,6 +24,16 @@ class MessageActions extends CustomElement {
|
|||
return html`${ until(this.renderActions(), '') }`;
|
||||
}
|
||||
|
||||
async renderActions () {
|
||||
const buttons = await this.getActionButtons();
|
||||
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 '';
|
||||
}
|
||||
}
|
||||
|
||||
static getActionsDropdownItem (o) {
|
||||
return html`
|
||||
<button class="chat-msg__action ${o.button_class}" @click=${o.handler}>
|
||||
|
@ -36,12 +48,112 @@ class MessageActions extends CustomElement {
|
|||
|
||||
onMessageEditButtonClicked (ev) {
|
||||
ev.preventDefault();
|
||||
this.chatview.onMessageEditButtonClicked(this.model);
|
||||
const currently_correcting = this.model.collection.findWhere('correcting');
|
||||
// TODO: Use state intead of DOM querying
|
||||
// Then this code can also be put on the model
|
||||
const unsent_text = u.ancestor(this, '.chatbox')?.querySelector('.chat-textarea')?.value;
|
||||
if (unsent_text && (!currently_correcting || currently_correcting.get('message') !== unsent_text)) {
|
||||
if (!confirm(__('You have an unsent message which will be lost if you continue. Are you sure?'))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (currently_correcting !== this.model) {
|
||||
currently_correcting?.save('correcting', false);
|
||||
this.model.save('correcting', true);
|
||||
} else {
|
||||
this.model.save('correcting', false);
|
||||
}
|
||||
}
|
||||
|
||||
async onDirectMessageRetractButtonClicked () {
|
||||
if (this.model.get('sender') !== 'me') {
|
||||
return log.error("onMessageRetractButtonClicked called for someone else's this.model!");
|
||||
}
|
||||
const retraction_warning = __(
|
||||
'Be aware that other XMPP/Jabber clients (and servers) may ' +
|
||||
'not yet support retractions and that this this.model may not ' +
|
||||
'be removed everywhere.'
|
||||
);
|
||||
const messages = [__('Are you sure you want to retract this this.model?')];
|
||||
if (api.settings.get('show_retraction_warning')) {
|
||||
messages[1] = retraction_warning;
|
||||
}
|
||||
const result = await api.confirm(__('Confirm'), messages);
|
||||
if (result) {
|
||||
const chatbox = this.model.collection.chatbox;
|
||||
chatbox.retractOwnMessage(this.model);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retract someone else's message in this groupchat.
|
||||
* @private
|
||||
* @param { _converse.Message } message - The message which we're retracting.
|
||||
* @param { string } [reason] - The reason for retracting the message.
|
||||
*/
|
||||
async retractOtherMessage (reason) {
|
||||
const chatbox = this.model.collection.chatbox;
|
||||
const result = await chatbox.retractOtherMessage(this.model, reason);
|
||||
if (result === null) {
|
||||
const err_msg = __(`A timeout occurred while trying to retract the message`);
|
||||
api.alert('error', __('Error'), err_msg);
|
||||
log(err_msg, Strophe.LogLevel.WARN);
|
||||
} else if (u.isErrorStanza(result)) {
|
||||
const err_msg = __(`Sorry, you're not allowed to retract this message.`);
|
||||
api.alert('error', __('Error'), err_msg);
|
||||
log(err_msg, Strophe.LogLevel.WARN);
|
||||
log(result, Strophe.LogLevel.WARN);
|
||||
}
|
||||
}
|
||||
|
||||
async onMUCMessageRetractButtonClicked () {
|
||||
const retraction_warning = __(
|
||||
'Be aware that other XMPP/Jabber clients (and servers) may ' +
|
||||
'not yet support retractions and that this this.model may not ' +
|
||||
'be removed everywhere.'
|
||||
);
|
||||
|
||||
if (this.model.mayBeRetracted()) {
|
||||
const messages = [__('Are you sure you want to retract this this.model?')];
|
||||
if (api.settings.get('show_retraction_warning')) {
|
||||
messages[1] = retraction_warning;
|
||||
}
|
||||
if (await api.confirm(__('Confirm'), messages)) {
|
||||
const chatbox = this.model.collection.chatbox;
|
||||
chatbox.retractOwnMessage(this.model);
|
||||
}
|
||||
} else if (await this.model.mayBeModerated()) {
|
||||
if (this.model.get('sender') === 'me') {
|
||||
let messages = [__('Are you sure you want to retract this this.model?')];
|
||||
if (api.settings.get('show_retraction_warning')) {
|
||||
messages = [messages[0], retraction_warning, messages[1]];
|
||||
}
|
||||
!!(await api.confirm(__('Confirm'), messages)) && this.retractOtherMessage();
|
||||
} else {
|
||||
let messages = [
|
||||
__('You are about to retract this this.model.'),
|
||||
__('You may optionally include a this.model, explaining the reason for the retraction.')
|
||||
];
|
||||
if (api.settings.get('show_retraction_warning')) {
|
||||
messages = [messages[0], retraction_warning, messages[1]];
|
||||
}
|
||||
const reason = await api.prompt(__('this.model Retraction'), messages, __('Optional reason'));
|
||||
reason !== false && this.retractOtherMessage(reason);
|
||||
}
|
||||
} else {
|
||||
const err_msg = __(`Sorry, you're not allowed to retract this this.model`);
|
||||
api.alert('error', __('Error'), err_msg);
|
||||
}
|
||||
}
|
||||
|
||||
onMessageRetractButtonClicked (ev) {
|
||||
ev.preventDefault();
|
||||
this.chatview.onMessageRetractButtonClicked(this.model);
|
||||
const chatbox = this.model.collection.chatbox;
|
||||
if (chatbox.get('type') === _converse.CHATROOMS_TYPE) {
|
||||
this.onMUCMessageRetractButtonClicked();
|
||||
} else {
|
||||
this.onDirectMessageRetractButtonClicked();
|
||||
}
|
||||
}
|
||||
|
||||
async getActionButtons () {
|
||||
|
@ -83,16 +195,6 @@ class MessageActions extends CustomElement {
|
|||
*/
|
||||
return api.hook('getMessageActionButtons', this, buttons);
|
||||
}
|
||||
|
||||
async renderActions () {
|
||||
const buttons = await this.getActionButtons();
|
||||
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 '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
api.elements.define('converse-message-actions', MessageActions);
|
||||
|
|
|
@ -11,7 +11,6 @@ const i18n_no_history = __('No message history available.');
|
|||
|
||||
const tpl_message = (o) => html`
|
||||
<converse-chat-message
|
||||
.chatview=${o.chatview}
|
||||
.hats=${o.hats}
|
||||
.model=${o.model}
|
||||
?correcting=${o.correcting}
|
||||
|
@ -108,7 +107,7 @@ export default class MessageHistory extends CustomElement {
|
|||
|
||||
static get properties () {
|
||||
return {
|
||||
chatview: { type: Object},
|
||||
model: { type: Object},
|
||||
messages: { type: Array}
|
||||
}
|
||||
}
|
||||
|
@ -129,8 +128,8 @@ export default class MessageHistory extends CustomElement {
|
|||
const message = tpl_message(
|
||||
Object.assign(
|
||||
model.toJSON(),
|
||||
getDerivedMessageProps(this.chatview.model, model),
|
||||
{ 'chatview': this.chatview, model }
|
||||
getDerivedMessageProps(this.model, model),
|
||||
{ model }
|
||||
)
|
||||
);
|
||||
return [...templates, message];
|
||||
|
|
|
@ -2,8 +2,9 @@ import '../shared/registry';
|
|||
import './dropdown.js';
|
||||
import './message-actions.js';
|
||||
import './message-body.js';
|
||||
import { getDerivedMessageProps } from './message-history';
|
||||
import MessageVersionsModal from '../modals/message-versions.js';
|
||||
import OccupantModal from 'modals/occupant.js';
|
||||
import UserDetailsModal from 'modals/user-details.js';
|
||||
import dayjs from 'dayjs';
|
||||
import filesize from 'filesize';
|
||||
import tpl_chat_message from '../templates/chat_message.js';
|
||||
|
@ -11,6 +12,7 @@ import tpl_spinner from '../templates/spinner.js';
|
|||
import { CustomElement } from './element.js';
|
||||
import { __ } from '../i18n';
|
||||
import { _converse, api, converse } from '@converse/headless/core';
|
||||
import { getDerivedMessageProps } from './message-history';
|
||||
import { html } from 'lit-element';
|
||||
import { renderAvatar } from 'templates/directives/avatar';
|
||||
|
||||
|
@ -22,7 +24,6 @@ export default class Message extends CustomElement {
|
|||
|
||||
static get properties () {
|
||||
return {
|
||||
chatview: { type: Object},
|
||||
correcting: { type: Boolean },
|
||||
editable: { type: Boolean },
|
||||
edited: { type: String },
|
||||
|
@ -259,9 +260,12 @@ export default class Message extends CustomElement {
|
|||
if (this.model.get('sender') === 'me') {
|
||||
_converse.xmppstatusview.showProfileModal(ev);
|
||||
} else if (this.message_type === 'groupchat') {
|
||||
this.chatview.showOccupantDetailsModal(ev, this.model);
|
||||
ev.preventDefault();
|
||||
api.modal.show(OccupantModal, { 'model': this.model.occupant }, ev);
|
||||
} else {
|
||||
this.chatview.showUserDetailsModal(ev, this.model);
|
||||
ev.preventDefault();
|
||||
const chatbox = this.model.collection.chatbox;
|
||||
api.modal.show(UserDetailsModal, { model: chatbox }, ev);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -103,6 +103,19 @@ const ChatBox = ModelWithContact.extend({
|
|||
this.notifications = new Model();
|
||||
},
|
||||
|
||||
getNotificationsText () {
|
||||
const { __ } = _converse;
|
||||
if (this.notifications?.get('chat_state') === _converse.COMPOSING) {
|
||||
return __('%1$s is typing', this.getDisplayName());
|
||||
} else if (this.notifications?.get('chat_state') === _converse.PAUSED) {
|
||||
return __('%1$s has stopped typing', this.getDisplayName());
|
||||
} else if (this.notifications?.get('chat_state') === _converse.GONE) {
|
||||
return __('%1$s has gone away', this.getDisplayName());
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
|
||||
afterMessagesFetched (messages) {
|
||||
this.most_recent_cached_message = messages ? this.getMostRecentMessage(messages) : null;
|
||||
/**
|
||||
|
|
|
@ -1855,6 +1855,81 @@ const ChatRoomMixin = {
|
|||
return false;
|
||||
},
|
||||
|
||||
getNotificationsText () {
|
||||
const { __ } = _converse;
|
||||
const actors_per_state = this.notifications.toJSON();
|
||||
|
||||
const role_changes = api.settings
|
||||
.get('muc_show_info_messages')
|
||||
.filter(role_change => converse.MUC_ROLE_CHANGES_LIST.includes(role_change));
|
||||
|
||||
const join_leave_events = api.settings
|
||||
.get('muc_show_info_messages')
|
||||
.filter(join_leave_event => converse.MUC_TRAFFIC_STATES_LIST.includes(join_leave_event));
|
||||
|
||||
const states = [...converse.CHAT_STATES, ...join_leave_events, ...role_changes];
|
||||
|
||||
return states.reduce((result, state) => {
|
||||
const existing_actors = actors_per_state[state];
|
||||
if (!existing_actors?.length) {
|
||||
return result;
|
||||
}
|
||||
const actors = existing_actors.map(a => this.getOccupant(a)?.getDisplayName() || a);
|
||||
if (actors.length === 1) {
|
||||
if (state === 'composing') {
|
||||
return `${result}${__('%1$s is typing', actors[0])}\n`;
|
||||
} else if (state === 'paused') {
|
||||
return `${result}${__('%1$s has stopped typing', actors[0])}\n`;
|
||||
} else if (state === _converse.GONE) {
|
||||
return `${result}${__('%1$s has gone away', actors[0])}\n`;
|
||||
} else if (state === 'entered') {
|
||||
return `${result}${__('%1$s has entered the groupchat', actors[0])}\n`;
|
||||
} else if (state === 'exited') {
|
||||
return `${result}${__('%1$s has left the groupchat', actors[0])}\n`;
|
||||
} else if (state === 'op') {
|
||||
return `${result}${__('%1$s is now a moderator', actors[0])}\n`;
|
||||
} else if (state === 'deop') {
|
||||
return `${result}${__('%1$s is no longer a moderator', actors[0])}\n`;
|
||||
} else if (state === 'voice') {
|
||||
return `${result}${__('%1$s has been given a voice', actors[0])}\n`;
|
||||
} else if (state === 'mute') {
|
||||
return `${result}${__('%1$s has been muted', actors[0])}\n`;
|
||||
}
|
||||
} else if (actors.length > 1) {
|
||||
let actors_str;
|
||||
if (actors.length > 3) {
|
||||
actors_str = `${Array.from(actors)
|
||||
.slice(0, 2)
|
||||
.join(', ')} and others`;
|
||||
} else {
|
||||
const last_actor = actors.pop();
|
||||
actors_str = __('%1$s and %2$s', actors.join(', '), last_actor);
|
||||
}
|
||||
|
||||
if (state === 'composing') {
|
||||
return `${result}${__('%1$s are typing', actors_str)}\n`;
|
||||
} else if (state === 'paused') {
|
||||
return `${result}${__('%1$s have stopped typing', actors_str)}\n`;
|
||||
} else if (state === _converse.GONE) {
|
||||
return `${result}${__('%1$s have gone away', actors_str)}\n`;
|
||||
} else if (state === 'entered') {
|
||||
return `${result}${__('%1$s have entered the groupchat', actors_str)}\n`;
|
||||
} else if (state === 'exited') {
|
||||
return `${result}${__('%1$s have left the groupchat', actors_str)}\n`;
|
||||
} else if (state === 'op') {
|
||||
return `${result}${__('%1$s are now moderators', actors[0])}\n`;
|
||||
} else if (state === 'deop') {
|
||||
return `${result}${__('%1$s are no longer moderators', actors[0])}\n`;
|
||||
} else if (state === 'voice') {
|
||||
return `${result}${__('%1$s have been given voices', actors[0])}\n`;
|
||||
} else if (state === 'mute') {
|
||||
return `${result}${__('%1$s have been muted', actors[0])}\n`;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}, '');
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {String} actor - The nickname of the actor that caused the notification
|
||||
* @param {String|Array<String>} states - The state or states representing the type of notificcation
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import BaseChatView from 'shared/chatview.js';
|
||||
import UserDetailsModal from 'modals/user-details.js';
|
||||
import log from '@converse/headless/log';
|
||||
import tpl_chatbox from 'templates/chatbox.js';
|
||||
import tpl_chatbox_head from 'templates/chatbox_head.js';
|
||||
import { __ } from 'i18n';
|
||||
|
@ -45,6 +44,7 @@ export default class ChatView extends BaseChatView {
|
|||
this.listenTo(this.model, 'change:hidden', () => !this.model.get('hidden') && this.afterShown());
|
||||
this.listenTo(this.model, 'change:status', this.onStatusMessageChanged);
|
||||
this.listenTo(this.model, 'vcard:change', this.renderHeading);
|
||||
this.listenTo(this.model.messages, 'change:correcting', this.onMessageCorrecting);
|
||||
|
||||
if (this.model.contact) {
|
||||
this.listenTo(this.model.contact, 'destroy', this.renderHeading);
|
||||
|
@ -77,10 +77,7 @@ export default class ChatView extends BaseChatView {
|
|||
|
||||
render () {
|
||||
const result = tpl_chatbox(Object.assign(
|
||||
this.model.toJSON(), {
|
||||
'markScrolled': ev => this.markScrolled(ev),
|
||||
'chatview': this
|
||||
})
|
||||
this.model.toJSON(), { 'markScrolled': ev => this.markScrolled(ev) })
|
||||
);
|
||||
render(result, this);
|
||||
this.content = this.querySelector('.chat-content');
|
||||
|
@ -90,18 +87,6 @@ export default class ChatView extends BaseChatView {
|
|||
return this;
|
||||
}
|
||||
|
||||
getNotifications () {
|
||||
if (this.model.notifications.get('chat_state') === _converse.COMPOSING) {
|
||||
return __('%1$s is typing', this.model.getDisplayName());
|
||||
} else if (this.model.notifications.get('chat_state') === _converse.PAUSED) {
|
||||
return __('%1$s has stopped typing', this.model.getDisplayName());
|
||||
} else if (this.model.notifications.get('chat_state') === _converse.GONE) {
|
||||
return __('%1$s has gone away', this.model.getDisplayName());
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
getHelpMessages () { // eslint-disable-line class-methods-use-this
|
||||
return [
|
||||
`<strong>/clear</strong>: ${__('Remove messages')}`,
|
||||
|
@ -385,26 +370,6 @@ export default class ChatView extends BaseChatView {
|
|||
this.insertIntoTextArea('', true, false);
|
||||
}
|
||||
|
||||
async onMessageRetractButtonClicked (message) {
|
||||
if (message.get('sender') !== 'me') {
|
||||
return log.error("onMessageRetractButtonClicked called for someone else's message!");
|
||||
}
|
||||
const retraction_warning = __(
|
||||
'Be aware that other XMPP/Jabber clients (and servers) may ' +
|
||||
'not yet support retractions and that this message may not ' +
|
||||
'be removed everywhere.'
|
||||
);
|
||||
|
||||
const messages = [__('Are you sure you want to retract this message?')];
|
||||
if (api.settings.get('show_retraction_warning')) {
|
||||
messages[1] = retraction_warning;
|
||||
}
|
||||
const result = await api.confirm(__('Confirm'), messages);
|
||||
if (result) {
|
||||
this.model.retractOwnMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
inputChanged (ev) { // eslint-disable-line class-methods-use-this
|
||||
const height = ev.target.scrollHeight + 'px';
|
||||
if (ev.target.style.height != height) {
|
||||
|
|
|
@ -51,7 +51,6 @@ class HeadlinesView extends BaseChatView {
|
|||
this.setAttribute('id', this.model.get('box_id'));
|
||||
const result = tpl_chatbox(
|
||||
Object.assign(this.model.toJSON(), {
|
||||
chatview: this,
|
||||
show_send_button: false,
|
||||
show_toolbar: false,
|
||||
})
|
||||
|
|
|
@ -4,7 +4,6 @@ import 'shared/autocomplete/index.js';
|
|||
import BaseChatView from 'shared/chatview.js';
|
||||
import MUCInviteModal from 'modals/muc-invite.js';
|
||||
import ModeratorToolsModal from 'modals/moderator-tools.js';
|
||||
import OccupantModal from 'modals/occupant.js';
|
||||
import RoomDetailsModal from 'modals/muc-details.js';
|
||||
import log from '@converse/headless/log';
|
||||
import tpl_chatroom from 'templates/chatroom.js';
|
||||
|
@ -97,6 +96,7 @@ export default class MUCView extends BaseChatView {
|
|||
this.listenTo(this.model.features, 'change:moderated', this.renderBottomPanel);
|
||||
this.listenTo(this.model.features, 'change:open', this.renderHeading);
|
||||
this.listenTo(this.model.messages, 'rendered', this.maybeScrollDown);
|
||||
this.listenTo(this.model.messages, 'change:correcting', this.onMessageCorrecting);
|
||||
this.listenTo(this.model.session, 'change:connection_status', this.onConnectionStatusChanged);
|
||||
|
||||
// Bind so that we can pass it to addEventListener and removeEventListener
|
||||
|
@ -137,7 +137,6 @@ export default class MUCView extends BaseChatView {
|
|||
render(
|
||||
tpl_chatroom({
|
||||
sidebar_hidden,
|
||||
'chatview': this,
|
||||
'model': this.model,
|
||||
'occupants': this.model.occupants,
|
||||
'show_sidebar':
|
||||
|
@ -168,80 +167,6 @@ export default class MUCView extends BaseChatView {
|
|||
!this.model.get('hidden') && this.show();
|
||||
}
|
||||
|
||||
getNotifications () {
|
||||
const actors_per_state = this.model.notifications.toJSON();
|
||||
|
||||
const role_changes = api.settings
|
||||
.get('muc_show_info_messages')
|
||||
.filter(role_change => converse.MUC_ROLE_CHANGES_LIST.includes(role_change));
|
||||
|
||||
const join_leave_events = api.settings
|
||||
.get('muc_show_info_messages')
|
||||
.filter(join_leave_event => converse.MUC_TRAFFIC_STATES_LIST.includes(join_leave_event));
|
||||
|
||||
const states = [...converse.CHAT_STATES, ...join_leave_events, ...role_changes];
|
||||
|
||||
return states.reduce((result, state) => {
|
||||
const existing_actors = actors_per_state[state];
|
||||
if (!existing_actors?.length) {
|
||||
return result;
|
||||
}
|
||||
const actors = existing_actors.map(a => this.model.getOccupant(a)?.getDisplayName() || a);
|
||||
if (actors.length === 1) {
|
||||
if (state === 'composing') {
|
||||
return `${result}${__('%1$s is typing', actors[0])}\n`;
|
||||
} else if (state === 'paused') {
|
||||
return `${result}${__('%1$s has stopped typing', actors[0])}\n`;
|
||||
} else if (state === _converse.GONE) {
|
||||
return `${result}${__('%1$s has gone away', actors[0])}\n`;
|
||||
} else if (state === 'entered') {
|
||||
return `${result}${__('%1$s has entered the groupchat', actors[0])}\n`;
|
||||
} else if (state === 'exited') {
|
||||
return `${result}${__('%1$s has left the groupchat', actors[0])}\n`;
|
||||
} else if (state === 'op') {
|
||||
return `${result}${__('%1$s is now a moderator', actors[0])}\n`;
|
||||
} else if (state === 'deop') {
|
||||
return `${result}${__('%1$s is no longer a moderator', actors[0])}\n`;
|
||||
} else if (state === 'voice') {
|
||||
return `${result}${__('%1$s has been given a voice', actors[0])}\n`;
|
||||
} else if (state === 'mute') {
|
||||
return `${result}${__('%1$s has been muted', actors[0])}\n`;
|
||||
}
|
||||
} else if (actors.length > 1) {
|
||||
let actors_str;
|
||||
if (actors.length > 3) {
|
||||
actors_str = `${Array.from(actors)
|
||||
.slice(0, 2)
|
||||
.join(', ')} and others`;
|
||||
} else {
|
||||
const last_actor = actors.pop();
|
||||
actors_str = __('%1$s and %2$s', actors.join(', '), last_actor);
|
||||
}
|
||||
|
||||
if (state === 'composing') {
|
||||
return `${result}${__('%1$s are typing', actors_str)}\n`;
|
||||
} else if (state === 'paused') {
|
||||
return `${result}${__('%1$s have stopped typing', actors_str)}\n`;
|
||||
} else if (state === _converse.GONE) {
|
||||
return `${result}${__('%1$s have gone away', actors_str)}\n`;
|
||||
} else if (state === 'entered') {
|
||||
return `${result}${__('%1$s have entered the groupchat', actors_str)}\n`;
|
||||
} else if (state === 'exited') {
|
||||
return `${result}${__('%1$s have left the groupchat', actors_str)}\n`;
|
||||
} else if (state === 'op') {
|
||||
return `${result}${__('%1$s are now moderators', actors[0])}\n`;
|
||||
} else if (state === 'deop') {
|
||||
return `${result}${__('%1$s are no longer moderators', actors[0])}\n`;
|
||||
} else if (state === 'voice') {
|
||||
return `${result}${__('%1$s have been given voices', actors[0])}\n`;
|
||||
} else if (state === 'mute') {
|
||||
return `${result}${__('%1$s have been muted', actors[0])}\n`;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}, '');
|
||||
}
|
||||
|
||||
getHelpMessages () {
|
||||
const setting = api.settings.get('muc_disable_slash_commands');
|
||||
const disabled_commands = Array.isArray(setting) ? setting : [];
|
||||
|
@ -445,64 +370,6 @@ export default class MUCView extends BaseChatView {
|
|||
return _converse.ChatBoxView.prototype.onKeyUp.call(this, ev);
|
||||
}
|
||||
|
||||
async onMessageRetractButtonClicked (message) {
|
||||
const retraction_warning = __(
|
||||
'Be aware that other XMPP/Jabber clients (and servers) may ' +
|
||||
'not yet support retractions and that this message may not ' +
|
||||
'be removed everywhere.'
|
||||
);
|
||||
|
||||
if (message.mayBeRetracted()) {
|
||||
const messages = [__('Are you sure you want to retract this message?')];
|
||||
if (api.settings.get('show_retraction_warning')) {
|
||||
messages[1] = retraction_warning;
|
||||
}
|
||||
!!(await api.confirm(__('Confirm'), messages)) && this.model.retractOwnMessage(message);
|
||||
} else if (await message.mayBeModerated()) {
|
||||
if (message.get('sender') === 'me') {
|
||||
let messages = [__('Are you sure you want to retract this message?')];
|
||||
if (api.settings.get('show_retraction_warning')) {
|
||||
messages = [messages[0], retraction_warning, messages[1]];
|
||||
}
|
||||
!!(await api.confirm(__('Confirm'), messages)) && this.retractOtherMessage(message);
|
||||
} else {
|
||||
let messages = [
|
||||
__('You are about to retract this message.'),
|
||||
__('You may optionally include a message, explaining the reason for the retraction.')
|
||||
];
|
||||
if (api.settings.get('show_retraction_warning')) {
|
||||
messages = [messages[0], retraction_warning, messages[1]];
|
||||
}
|
||||
const reason = await api.prompt(__('Message Retraction'), messages, __('Optional reason'));
|
||||
reason !== false && this.retractOtherMessage(message, reason);
|
||||
}
|
||||
} else {
|
||||
const err_msg = __(`Sorry, you're not allowed to retract this message`);
|
||||
api.alert('error', __('Error'), err_msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retract someone else's message in this groupchat.
|
||||
* @private
|
||||
* @method _converse.ChatRoomView#retractOtherMessage
|
||||
* @param { _converse.Message } message - The message which we're retracting.
|
||||
* @param { string } [reason] - The reason for retracting the message.
|
||||
*/
|
||||
async retractOtherMessage (message, reason) {
|
||||
const result = await this.model.retractOtherMessage(message, reason);
|
||||
if (result === null) {
|
||||
const err_msg = __(`A timeout occurred while trying to retract the message`);
|
||||
api.alert('error', __('Error'), err_msg);
|
||||
log(err_msg, Strophe.LogLevel.WARN);
|
||||
} else if (u.isErrorStanza(result)) {
|
||||
const err_msg = __(`Sorry, you're not allowed to retract this message.`);
|
||||
api.alert('error', __('Error'), err_msg);
|
||||
log(err_msg, Strophe.LogLevel.WARN);
|
||||
log(result, Strophe.LogLevel.WARN);
|
||||
}
|
||||
}
|
||||
|
||||
showModeratorToolsModal (affiliation) {
|
||||
if (!this.verifyRoles(['moderator'])) {
|
||||
return;
|
||||
|
@ -522,11 +389,6 @@ export default class MUCView extends BaseChatView {
|
|||
api.modal.show(RoomDetailsModal, { 'model': this.model }, ev);
|
||||
}
|
||||
|
||||
showOccupantDetailsModal (ev, message) { // eslint-disable-line class-methods-use-this
|
||||
ev.preventDefault();
|
||||
api.modal.show(OccupantModal, { 'model': message.occupant }, ev);
|
||||
}
|
||||
|
||||
showChatStateNotification (message) {
|
||||
if (message.get('sender') === 'me') {
|
||||
return;
|
||||
|
|
|
@ -234,7 +234,6 @@ export default class BaseChatView extends ElementView {
|
|||
.reverse()
|
||||
.find(m => m.get('editable'));
|
||||
if (message) {
|
||||
this.insertIntoTextArea(u.prefixMentions(message), true, true);
|
||||
message.save('correcting', true);
|
||||
}
|
||||
}
|
||||
|
@ -309,21 +308,16 @@ export default class BaseChatView extends ElementView {
|
|||
this.insertIntoTextArea(emoji, autocompleting, false, ac_position);
|
||||
}
|
||||
|
||||
onMessageEditButtonClicked (message) {
|
||||
const currently_correcting = this.model.messages.findWhere('correcting');
|
||||
const unsent_text = this.querySelector('.chat-textarea')?.value;
|
||||
if (unsent_text && (!currently_correcting || currently_correcting.get('message') !== unsent_text)) {
|
||||
if (!confirm(__('You have an unsent message which will be lost if you continue. Are you sure?'))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (currently_correcting !== message) {
|
||||
currently_correcting?.save('correcting', false);
|
||||
message.save('correcting', true);
|
||||
onMessageCorrecting (message) {
|
||||
if (message.get('correcting')) {
|
||||
this.insertIntoTextArea(u.prefixMentions(message), true, true);
|
||||
} else {
|
||||
message.save('correcting', false);
|
||||
this.insertIntoTextArea('', true, false);
|
||||
const currently_correcting = this.model.messages.findWhere('correcting');
|
||||
if (currently_correcting && currently_correcting !== message) {
|
||||
this.insertIntoTextArea(u.prefixMentions(message), true, true);
|
||||
} else {
|
||||
this.insertIntoTextArea('', true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -415,13 +409,7 @@ export default class BaseChatView extends ElementView {
|
|||
'scrollTop': null
|
||||
});
|
||||
}
|
||||
const msgs_container = this.querySelector('.chat-content__messages');
|
||||
if (msgs_container.scrollTo) {
|
||||
const behavior = msgs_container.scrollTop ? 'smooth' : 'auto';
|
||||
msgs_container.scrollTo({ 'top': msgs_container.scrollHeight, behavior });
|
||||
} else {
|
||||
msgs_container.scrollTop = msgs_container.scrollHeight;
|
||||
}
|
||||
this.querySelector('.chat-content__messages').scrollDown();
|
||||
this.onScrolledDown();
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,6 @@ export default (o) => {
|
|||
${ o.is_retracted ? o.renderRetraction() : o.renderMessageText() }
|
||||
</div>
|
||||
<converse-message-actions
|
||||
.chatview=${o.chatview}
|
||||
.model=${o.model}
|
||||
?correcting="${o.correcting}"
|
||||
?editable="${o.editable}"
|
||||
|
|
|
@ -8,7 +8,7 @@ export default (o) => html`
|
|||
<div class="chat-content ${ o.show_send_button ? 'chat-content-sendbutton' : '' }" aria-live="polite">
|
||||
<converse-chat-content
|
||||
class="chat-content__messages"
|
||||
.chatview=${o.chatview}
|
||||
jid=${o.jid}
|
||||
@scroll=${o.markScrolled}></converse-chat-content>
|
||||
|
||||
<div class="chat-content__help"></div>
|
||||
|
|
|
@ -9,7 +9,7 @@ export default (o) => html`
|
|||
<div class="chat-content ${ o.show_send_button ? 'chat-content-sendbutton' : '' }" aria-live="polite">
|
||||
<converse-chat-content
|
||||
class="chat-content__messages"
|
||||
.chatview=${o.chatview}
|
||||
jid=${o.model.get('jid')}
|
||||
@scroll=${o.markScrolled}></converse-chat-content>
|
||||
|
||||
<div class="chat-content__help"></div>
|
||||
|
|
|
@ -1,30 +1,19 @@
|
|||
import { MessageText } from '../../shared/message/text.js';
|
||||
import { api, converse } from "@converse/headless/core";
|
||||
import { api } from "@converse/headless/core";
|
||||
import { directive, html } from "lit-html";
|
||||
import { until } from 'lit-html/directives/until.js';
|
||||
|
||||
|
||||
const u = converse.env.utils;
|
||||
|
||||
|
||||
class MessageBodyRenderer {
|
||||
|
||||
constructor (component) {
|
||||
this.model = component.model;
|
||||
this.component = component;
|
||||
this.chatview = u.ancestor(this.component, 'converse-chat-message')?.chatview;
|
||||
// We jot down whether we were scrolled down before rendering, because when an
|
||||
// image loads, it triggers 'scroll' and the chat will be marked as scrolled,
|
||||
// which is technically true, but not what we want because the user
|
||||
// didn't initiate the scrolling.
|
||||
this.was_scrolled_up = this.chatview.model.get('scrolled');
|
||||
this.text = this.component.model.getMessageText();
|
||||
this.text = this.model.getMessageText();
|
||||
}
|
||||
|
||||
scrollDownOnImageLoad () {
|
||||
if (!this.was_scrolled_up) {
|
||||
this.chatview.scrollDown();
|
||||
}
|
||||
onImageLoaded () {
|
||||
this.component.dispatchEvent(new CustomEvent('imageLoaded', { detail: this.component }));
|
||||
}
|
||||
|
||||
async transform () {
|
||||
|
@ -35,7 +24,7 @@ class MessageBodyRenderer {
|
|||
this.model,
|
||||
offset,
|
||||
show_images,
|
||||
() => this.scrollDownOnImageLoad(),
|
||||
() => this.onImageLoaded(),
|
||||
ev => this.component.showImageModal(ev)
|
||||
);
|
||||
await text.addTemplates();
|
||||
|
|
Loading…
Reference in New Issue
Block a user