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
|
keyCode: 13 // Enter
|
||||||
});
|
});
|
||||||
await u.waitUntil(() => textarea.value === '');
|
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');
|
const messages = view.querySelectorAll('.chat-msg');
|
||||||
expect(messages.length).toBe(3);
|
expect(messages.length).toBe(3);
|
||||||
expect(messages[0].querySelector('.chat-msg__text').textContent)
|
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 = view.querySelector('.chat-msg .chat-msg__action');
|
||||||
action.style.opacity = 1;
|
action.style.opacity = 1;
|
||||||
action.click();
|
action.click();
|
||||||
expect(textarea.value).toBe('');
|
|
||||||
expect(view.model.messages.at(0).get('correcting')).toBe(false);
|
expect(view.model.messages.at(0).get('correcting')).toBe(false);
|
||||||
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
|
expect(view.querySelectorAll('.chat-msg').length).toBe(1);
|
||||||
|
expect(textarea.value).toBe('');
|
||||||
await u.waitUntil(() => (u.hasClass('correcting', view.querySelector('.chat-msg')) === false), 500);
|
await u.waitUntil(() => (u.hasClass('correcting', view.querySelector('.chat-msg')) === false), 500);
|
||||||
|
|
||||||
// Test that messages from other users don't have the pencil icon
|
// Test that messages from other users don't have the pencil icon
|
||||||
|
|
|
@ -1,41 +1,56 @@
|
||||||
import "./message-history";
|
import "./message-history";
|
||||||
import xss from "xss/dist/xss";
|
|
||||||
import { CustomElement } from './element.js';
|
import { CustomElement } from './element.js';
|
||||||
|
import { _converse, api } from "@converse/headless/core";
|
||||||
import { html } from 'lit-element';
|
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 {
|
export default class ChatContent extends CustomElement {
|
||||||
|
|
||||||
static get properties () {
|
static get properties () {
|
||||||
return {
|
return {
|
||||||
chatview: { type: Object}
|
jid: { type: String }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback () {
|
connectedCallback () {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
const model = this.chatview.model;
|
this.model = _converse.chatboxes.get(this.jid);
|
||||||
this.listenTo(model.messages, 'add', this.requestUpdate);
|
this.listenTo(this.model.messages, 'add', this.requestUpdate);
|
||||||
this.listenTo(model.messages, 'change', this.requestUpdate);
|
this.listenTo(this.model.messages, 'change', this.requestUpdate);
|
||||||
this.listenTo(model.messages, 'remove', this.requestUpdate);
|
this.listenTo(this.model.messages, 'remove', this.requestUpdate);
|
||||||
this.listenTo(model.messages, 'reset', this.requestUpdate);
|
this.listenTo(this.model.messages, 'reset', this.requestUpdate);
|
||||||
this.listenTo(model.notifications, 'change', this.requestUpdate);
|
this.listenTo(this.model.notifications, 'change', this.requestUpdate);
|
||||||
if (model.occupants) {
|
if (this.model.occupants) {
|
||||||
this.listenTo(model.occupants, 'change', this.requestUpdate);
|
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 () {
|
render () {
|
||||||
const notifications = xss.filterXSS(this.chatview.getNotifications(), {'whiteList': {}});
|
|
||||||
return html`
|
return html`
|
||||||
<converse-message-history
|
<converse-message-history
|
||||||
.chatview=${this.chatview}
|
.model=${this.model}
|
||||||
.messages=${[...this.chatview.model.messages.models]}>
|
.messages=${[...this.model.messages.models]}>
|
||||||
</converse-message-history>
|
</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);
|
api.elements.define('converse-chat-content', ChatContent);
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
|
import log from '@converse/headless/log';
|
||||||
import { CustomElement } from './element.js';
|
import { CustomElement } from './element.js';
|
||||||
import { __ } from '../i18n';
|
import { __ } from '../i18n';
|
||||||
import { api } from "@converse/headless/core";
|
import { _converse, api, converse } from "@converse/headless/core";
|
||||||
import { html } from 'lit-element';
|
import { html } from 'lit-element';
|
||||||
import { until } from 'lit-html/directives/until.js';
|
import { until } from 'lit-html/directives/until.js';
|
||||||
|
|
||||||
|
const { Strophe, u } = converse.env;
|
||||||
|
|
||||||
|
|
||||||
class MessageActions extends CustomElement {
|
class MessageActions extends CustomElement {
|
||||||
|
|
||||||
static get properties () {
|
static get properties () {
|
||||||
return {
|
return {
|
||||||
chatview: { type: Object },
|
|
||||||
model: { type: Object },
|
model: { type: Object },
|
||||||
editable: { type: Boolean },
|
editable: { type: Boolean },
|
||||||
correcting: { type: Boolean },
|
correcting: { type: Boolean },
|
||||||
|
@ -22,6 +24,16 @@ class MessageActions extends CustomElement {
|
||||||
return html`${ until(this.renderActions(), '') }`;
|
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) {
|
static getActionsDropdownItem (o) {
|
||||||
return html`
|
return html`
|
||||||
<button class="chat-msg__action ${o.button_class}" @click=${o.handler}>
|
<button class="chat-msg__action ${o.button_class}" @click=${o.handler}>
|
||||||
|
@ -36,12 +48,112 @@ class MessageActions extends CustomElement {
|
||||||
|
|
||||||
onMessageEditButtonClicked (ev) {
|
onMessageEditButtonClicked (ev) {
|
||||||
ev.preventDefault();
|
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) {
|
onMessageRetractButtonClicked (ev) {
|
||||||
ev.preventDefault();
|
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 () {
|
async getActionButtons () {
|
||||||
|
@ -83,16 +195,6 @@ class MessageActions extends CustomElement {
|
||||||
*/
|
*/
|
||||||
return api.hook('getMessageActionButtons', this, buttons);
|
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);
|
api.elements.define('converse-message-actions', MessageActions);
|
||||||
|
|
|
@ -11,7 +11,6 @@ const i18n_no_history = __('No message history available.');
|
||||||
|
|
||||||
const tpl_message = (o) => html`
|
const tpl_message = (o) => html`
|
||||||
<converse-chat-message
|
<converse-chat-message
|
||||||
.chatview=${o.chatview}
|
|
||||||
.hats=${o.hats}
|
.hats=${o.hats}
|
||||||
.model=${o.model}
|
.model=${o.model}
|
||||||
?correcting=${o.correcting}
|
?correcting=${o.correcting}
|
||||||
|
@ -108,7 +107,7 @@ export default class MessageHistory extends CustomElement {
|
||||||
|
|
||||||
static get properties () {
|
static get properties () {
|
||||||
return {
|
return {
|
||||||
chatview: { type: Object},
|
model: { type: Object},
|
||||||
messages: { type: Array}
|
messages: { type: Array}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,8 +128,8 @@ export default class MessageHistory extends CustomElement {
|
||||||
const message = tpl_message(
|
const message = tpl_message(
|
||||||
Object.assign(
|
Object.assign(
|
||||||
model.toJSON(),
|
model.toJSON(),
|
||||||
getDerivedMessageProps(this.chatview.model, model),
|
getDerivedMessageProps(this.model, model),
|
||||||
{ 'chatview': this.chatview, model }
|
{ model }
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
return [...templates, message];
|
return [...templates, message];
|
||||||
|
|
|
@ -2,8 +2,9 @@ import '../shared/registry';
|
||||||
import './dropdown.js';
|
import './dropdown.js';
|
||||||
import './message-actions.js';
|
import './message-actions.js';
|
||||||
import './message-body.js';
|
import './message-body.js';
|
||||||
import { getDerivedMessageProps } from './message-history';
|
|
||||||
import MessageVersionsModal from '../modals/message-versions.js';
|
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 dayjs from 'dayjs';
|
||||||
import filesize from 'filesize';
|
import filesize from 'filesize';
|
||||||
import tpl_chat_message from '../templates/chat_message.js';
|
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 { CustomElement } from './element.js';
|
||||||
import { __ } from '../i18n';
|
import { __ } from '../i18n';
|
||||||
import { _converse, api, converse } from '@converse/headless/core';
|
import { _converse, api, converse } from '@converse/headless/core';
|
||||||
|
import { getDerivedMessageProps } from './message-history';
|
||||||
import { html } from 'lit-element';
|
import { html } from 'lit-element';
|
||||||
import { renderAvatar } from 'templates/directives/avatar';
|
import { renderAvatar } from 'templates/directives/avatar';
|
||||||
|
|
||||||
|
@ -22,7 +24,6 @@ export default class Message extends CustomElement {
|
||||||
|
|
||||||
static get properties () {
|
static get properties () {
|
||||||
return {
|
return {
|
||||||
chatview: { type: Object},
|
|
||||||
correcting: { type: Boolean },
|
correcting: { type: Boolean },
|
||||||
editable: { type: Boolean },
|
editable: { type: Boolean },
|
||||||
edited: { type: String },
|
edited: { type: String },
|
||||||
|
@ -259,9 +260,12 @@ export default class Message extends CustomElement {
|
||||||
if (this.model.get('sender') === 'me') {
|
if (this.model.get('sender') === 'me') {
|
||||||
_converse.xmppstatusview.showProfileModal(ev);
|
_converse.xmppstatusview.showProfileModal(ev);
|
||||||
} else if (this.message_type === 'groupchat') {
|
} else if (this.message_type === 'groupchat') {
|
||||||
this.chatview.showOccupantDetailsModal(ev, this.model);
|
ev.preventDefault();
|
||||||
|
api.modal.show(OccupantModal, { 'model': this.model.occupant }, ev);
|
||||||
} else {
|
} 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();
|
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) {
|
afterMessagesFetched (messages) {
|
||||||
this.most_recent_cached_message = messages ? this.getMostRecentMessage(messages) : null;
|
this.most_recent_cached_message = messages ? this.getMostRecentMessage(messages) : null;
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1855,6 +1855,81 @@ const ChatRoomMixin = {
|
||||||
return false;
|
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} actor - The nickname of the actor that caused the notification
|
||||||
* @param {String|Array<String>} states - The state or states representing the type of notificcation
|
* @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 BaseChatView from 'shared/chatview.js';
|
||||||
import UserDetailsModal from 'modals/user-details.js';
|
import UserDetailsModal from 'modals/user-details.js';
|
||||||
import log from '@converse/headless/log';
|
|
||||||
import tpl_chatbox from 'templates/chatbox.js';
|
import tpl_chatbox from 'templates/chatbox.js';
|
||||||
import tpl_chatbox_head from 'templates/chatbox_head.js';
|
import tpl_chatbox_head from 'templates/chatbox_head.js';
|
||||||
import { __ } from 'i18n';
|
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:hidden', () => !this.model.get('hidden') && this.afterShown());
|
||||||
this.listenTo(this.model, 'change:status', this.onStatusMessageChanged);
|
this.listenTo(this.model, 'change:status', this.onStatusMessageChanged);
|
||||||
this.listenTo(this.model, 'vcard:change', this.renderHeading);
|
this.listenTo(this.model, 'vcard:change', this.renderHeading);
|
||||||
|
this.listenTo(this.model.messages, 'change:correcting', this.onMessageCorrecting);
|
||||||
|
|
||||||
if (this.model.contact) {
|
if (this.model.contact) {
|
||||||
this.listenTo(this.model.contact, 'destroy', this.renderHeading);
|
this.listenTo(this.model.contact, 'destroy', this.renderHeading);
|
||||||
|
@ -77,10 +77,7 @@ export default class ChatView extends BaseChatView {
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const result = tpl_chatbox(Object.assign(
|
const result = tpl_chatbox(Object.assign(
|
||||||
this.model.toJSON(), {
|
this.model.toJSON(), { 'markScrolled': ev => this.markScrolled(ev) })
|
||||||
'markScrolled': ev => this.markScrolled(ev),
|
|
||||||
'chatview': this
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
render(result, this);
|
render(result, this);
|
||||||
this.content = this.querySelector('.chat-content');
|
this.content = this.querySelector('.chat-content');
|
||||||
|
@ -90,18 +87,6 @@ export default class ChatView extends BaseChatView {
|
||||||
return this;
|
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
|
getHelpMessages () { // eslint-disable-line class-methods-use-this
|
||||||
return [
|
return [
|
||||||
`<strong>/clear</strong>: ${__('Remove messages')}`,
|
`<strong>/clear</strong>: ${__('Remove messages')}`,
|
||||||
|
@ -385,26 +370,6 @@ export default class ChatView extends BaseChatView {
|
||||||
this.insertIntoTextArea('', true, false);
|
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
|
inputChanged (ev) { // eslint-disable-line class-methods-use-this
|
||||||
const height = ev.target.scrollHeight + 'px';
|
const height = ev.target.scrollHeight + 'px';
|
||||||
if (ev.target.style.height != height) {
|
if (ev.target.style.height != height) {
|
||||||
|
|
|
@ -51,7 +51,6 @@ class HeadlinesView extends BaseChatView {
|
||||||
this.setAttribute('id', this.model.get('box_id'));
|
this.setAttribute('id', this.model.get('box_id'));
|
||||||
const result = tpl_chatbox(
|
const result = tpl_chatbox(
|
||||||
Object.assign(this.model.toJSON(), {
|
Object.assign(this.model.toJSON(), {
|
||||||
chatview: this,
|
|
||||||
show_send_button: false,
|
show_send_button: false,
|
||||||
show_toolbar: false,
|
show_toolbar: false,
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,7 +4,6 @@ import 'shared/autocomplete/index.js';
|
||||||
import BaseChatView from 'shared/chatview.js';
|
import BaseChatView from 'shared/chatview.js';
|
||||||
import MUCInviteModal from 'modals/muc-invite.js';
|
import MUCInviteModal from 'modals/muc-invite.js';
|
||||||
import ModeratorToolsModal from 'modals/moderator-tools.js';
|
import ModeratorToolsModal from 'modals/moderator-tools.js';
|
||||||
import OccupantModal from 'modals/occupant.js';
|
|
||||||
import RoomDetailsModal from 'modals/muc-details.js';
|
import RoomDetailsModal from 'modals/muc-details.js';
|
||||||
import log from '@converse/headless/log';
|
import log from '@converse/headless/log';
|
||||||
import tpl_chatroom from 'templates/chatroom.js';
|
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:moderated', this.renderBottomPanel);
|
||||||
this.listenTo(this.model.features, 'change:open', this.renderHeading);
|
this.listenTo(this.model.features, 'change:open', this.renderHeading);
|
||||||
this.listenTo(this.model.messages, 'rendered', this.maybeScrollDown);
|
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);
|
this.listenTo(this.model.session, 'change:connection_status', this.onConnectionStatusChanged);
|
||||||
|
|
||||||
// Bind so that we can pass it to addEventListener and removeEventListener
|
// Bind so that we can pass it to addEventListener and removeEventListener
|
||||||
|
@ -137,7 +137,6 @@ export default class MUCView extends BaseChatView {
|
||||||
render(
|
render(
|
||||||
tpl_chatroom({
|
tpl_chatroom({
|
||||||
sidebar_hidden,
|
sidebar_hidden,
|
||||||
'chatview': this,
|
|
||||||
'model': this.model,
|
'model': this.model,
|
||||||
'occupants': this.model.occupants,
|
'occupants': this.model.occupants,
|
||||||
'show_sidebar':
|
'show_sidebar':
|
||||||
|
@ -168,80 +167,6 @@ export default class MUCView extends BaseChatView {
|
||||||
!this.model.get('hidden') && this.show();
|
!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 () {
|
getHelpMessages () {
|
||||||
const setting = api.settings.get('muc_disable_slash_commands');
|
const setting = api.settings.get('muc_disable_slash_commands');
|
||||||
const disabled_commands = Array.isArray(setting) ? setting : [];
|
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);
|
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) {
|
showModeratorToolsModal (affiliation) {
|
||||||
if (!this.verifyRoles(['moderator'])) {
|
if (!this.verifyRoles(['moderator'])) {
|
||||||
return;
|
return;
|
||||||
|
@ -522,11 +389,6 @@ export default class MUCView extends BaseChatView {
|
||||||
api.modal.show(RoomDetailsModal, { 'model': this.model }, ev);
|
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) {
|
showChatStateNotification (message) {
|
||||||
if (message.get('sender') === 'me') {
|
if (message.get('sender') === 'me') {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -234,7 +234,6 @@ export default class BaseChatView extends ElementView {
|
||||||
.reverse()
|
.reverse()
|
||||||
.find(m => m.get('editable'));
|
.find(m => m.get('editable'));
|
||||||
if (message) {
|
if (message) {
|
||||||
this.insertIntoTextArea(u.prefixMentions(message), true, true);
|
|
||||||
message.save('correcting', true);
|
message.save('correcting', true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -309,21 +308,16 @@ export default class BaseChatView extends ElementView {
|
||||||
this.insertIntoTextArea(emoji, autocompleting, false, ac_position);
|
this.insertIntoTextArea(emoji, autocompleting, false, ac_position);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMessageEditButtonClicked (message) {
|
onMessageCorrecting (message) {
|
||||||
const currently_correcting = this.model.messages.findWhere('correcting');
|
if (message.get('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);
|
|
||||||
this.insertIntoTextArea(u.prefixMentions(message), true, true);
|
this.insertIntoTextArea(u.prefixMentions(message), true, true);
|
||||||
} else {
|
} else {
|
||||||
message.save('correcting', false);
|
const currently_correcting = this.model.messages.findWhere('correcting');
|
||||||
this.insertIntoTextArea('', true, false);
|
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
|
'scrollTop': null
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const msgs_container = this.querySelector('.chat-content__messages');
|
this.querySelector('.chat-content__messages').scrollDown();
|
||||||
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.onScrolledDown();
|
this.onScrolledDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,6 @@ export default (o) => {
|
||||||
${ o.is_retracted ? o.renderRetraction() : o.renderMessageText() }
|
${ o.is_retracted ? o.renderRetraction() : o.renderMessageText() }
|
||||||
</div>
|
</div>
|
||||||
<converse-message-actions
|
<converse-message-actions
|
||||||
.chatview=${o.chatview}
|
|
||||||
.model=${o.model}
|
.model=${o.model}
|
||||||
?correcting="${o.correcting}"
|
?correcting="${o.correcting}"
|
||||||
?editable="${o.editable}"
|
?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">
|
<div class="chat-content ${ o.show_send_button ? 'chat-content-sendbutton' : '' }" aria-live="polite">
|
||||||
<converse-chat-content
|
<converse-chat-content
|
||||||
class="chat-content__messages"
|
class="chat-content__messages"
|
||||||
.chatview=${o.chatview}
|
jid=${o.jid}
|
||||||
@scroll=${o.markScrolled}></converse-chat-content>
|
@scroll=${o.markScrolled}></converse-chat-content>
|
||||||
|
|
||||||
<div class="chat-content__help"></div>
|
<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">
|
<div class="chat-content ${ o.show_send_button ? 'chat-content-sendbutton' : '' }" aria-live="polite">
|
||||||
<converse-chat-content
|
<converse-chat-content
|
||||||
class="chat-content__messages"
|
class="chat-content__messages"
|
||||||
.chatview=${o.chatview}
|
jid=${o.model.get('jid')}
|
||||||
@scroll=${o.markScrolled}></converse-chat-content>
|
@scroll=${o.markScrolled}></converse-chat-content>
|
||||||
|
|
||||||
<div class="chat-content__help"></div>
|
<div class="chat-content__help"></div>
|
||||||
|
|
|
@ -1,30 +1,19 @@
|
||||||
import { MessageText } from '../../shared/message/text.js';
|
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 { directive, html } from "lit-html";
|
||||||
import { until } from 'lit-html/directives/until.js';
|
import { until } from 'lit-html/directives/until.js';
|
||||||
|
|
||||||
|
|
||||||
const u = converse.env.utils;
|
|
||||||
|
|
||||||
|
|
||||||
class MessageBodyRenderer {
|
class MessageBodyRenderer {
|
||||||
|
|
||||||
constructor (component) {
|
constructor (component) {
|
||||||
this.model = component.model;
|
this.model = component.model;
|
||||||
this.component = component;
|
this.component = component;
|
||||||
this.chatview = u.ancestor(this.component, 'converse-chat-message')?.chatview;
|
this.text = this.model.getMessageText();
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollDownOnImageLoad () {
|
onImageLoaded () {
|
||||||
if (!this.was_scrolled_up) {
|
this.component.dispatchEvent(new CustomEvent('imageLoaded', { detail: this.component }));
|
||||||
this.chatview.scrollDown();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async transform () {
|
async transform () {
|
||||||
|
@ -35,7 +24,7 @@ class MessageBodyRenderer {
|
||||||
this.model,
|
this.model,
|
||||||
offset,
|
offset,
|
||||||
show_images,
|
show_images,
|
||||||
() => this.scrollDownOnImageLoad(),
|
() => this.onImageLoaded(),
|
||||||
ev => this.component.showImageModal(ev)
|
ev => this.component.showImageModal(ev)
|
||||||
);
|
);
|
||||||
await text.addTemplates();
|
await text.addTemplates();
|
||||||
|
|
Loading…
Reference in New Issue
Block a user