Allow user modals to be opened from message headings
This commit is contained in:
parent
5a3aaeb056
commit
34cba68432
@ -1,4 +1,5 @@
|
||||
#conversejs {
|
||||
|
||||
.chatbox-navback {
|
||||
display: none;
|
||||
}
|
||||
@ -52,6 +53,10 @@
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.show-msg-author-modal {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.chat-head__desc {
|
||||
color: var(--chat-head-color-lighten-50-percent);
|
||||
font-size: var(--font-size-small);
|
||||
|
@ -9,6 +9,10 @@
|
||||
}
|
||||
}
|
||||
.message {
|
||||
.show-msg-author-modal {
|
||||
color: var(--text-color) !important;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin-left: 0.5em;
|
||||
margin-bottom: 0.25em;
|
||||
|
@ -268,6 +268,16 @@ export default class Message extends CustomElement {
|
||||
`;
|
||||
}
|
||||
|
||||
showUserModal (ev) {
|
||||
if (this.model.get('sender') === 'me') {
|
||||
_converse.xmppstatusview.showProfileModal(ev);
|
||||
} else if (this.message_type === 'groupchat') {
|
||||
this.chatview.showOccupantDetailsModal(ev, this.model);
|
||||
} else {
|
||||
this.chatview.showUserDetailsModal(ev, this.model);
|
||||
}
|
||||
}
|
||||
|
||||
showMessageVersionsModal (ev) {
|
||||
ev.preventDefault();
|
||||
if (this.message_versions_modal === undefined) {
|
||||
|
@ -3,24 +3,23 @@
|
||||
* @copyright 2020, the Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
import "./components/chat_content.js";
|
||||
import "./components/help_messages.js";
|
||||
import "./components/toolbar.js";
|
||||
import "converse-chatboxviews";
|
||||
import "converse-modal";
|
||||
import log from "@converse/headless/log";
|
||||
import tpl_chatbox from "templates/chatbox.js";
|
||||
import tpl_chatbox_head from "templates/chatbox_head.js";
|
||||
import tpl_chatbox_message_form from "templates/chatbox_message_form.js";
|
||||
import tpl_spinner from "templates/spinner.js";
|
||||
import tpl_toolbar from "templates/toolbar.js";
|
||||
import tpl_user_details_modal from "templates/user_details_modal.js";
|
||||
import { BootstrapModal } from "./converse-modal.js";
|
||||
import './components/chat_content.js';
|
||||
import './components/help_messages.js';
|
||||
import './components/toolbar.js';
|
||||
import 'converse-chatboxviews';
|
||||
import 'converse-modal';
|
||||
import log from '@converse/headless/log';
|
||||
import tpl_chatbox from 'templates/chatbox.js';
|
||||
import tpl_chatbox_head from 'templates/chatbox_head.js';
|
||||
import tpl_chatbox_message_form from 'templates/chatbox_message_form.js';
|
||||
import tpl_spinner from 'templates/spinner.js';
|
||||
import tpl_toolbar from 'templates/toolbar.js';
|
||||
import UserDetailsModal from 'modals/user-details.js';
|
||||
import { View } from '@converse/skeletor/src/view.js';
|
||||
import { __ } from './i18n';
|
||||
import { _converse, api, converse } from "@converse/headless/converse-core";
|
||||
import { debounce } from "lodash-es";
|
||||
import { html, render } from "lit-html";
|
||||
import { _converse, api, converse } from '@converse/headless/converse-core';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { html, render } from 'lit-html';
|
||||
|
||||
|
||||
const { Strophe, dayjs } = converse.env;
|
||||
@ -240,7 +239,7 @@ export const ChatBoxView = View.extend({
|
||||
showUserDetailsModal (ev) {
|
||||
ev.preventDefault();
|
||||
if (this.user_details_modal === undefined) {
|
||||
this.user_details_modal = new _converse.UserDetailsModal({model: this.model});
|
||||
this.user_details_modal = new UserDetailsModal({model: this.model});
|
||||
}
|
||||
this.user_details_modal.show(ev);
|
||||
},
|
||||
@ -283,17 +282,24 @@ export const ChatBoxView = View.extend({
|
||||
async generateHeadingTemplate () {
|
||||
const vcard = this.model?.vcard;
|
||||
const vcard_json = vcard ? vcard.toJSON() : {};
|
||||
const i18n_profile = __('The User\'s Profile Image');
|
||||
const avatar_data = Object.assign({
|
||||
'alt_text': i18n_profile,
|
||||
'extra_classes': '',
|
||||
'height': 40,
|
||||
'width': 40,
|
||||
}, vcard_json);
|
||||
const heading_btns = await this.getHeadingButtons();
|
||||
const standalone_btns = heading_btns.filter(b => b.standalone);
|
||||
const dropdown_btns = heading_btns.filter(b => !b.standalone);
|
||||
return tpl_chatbox_head(
|
||||
Object.assign(
|
||||
vcard_json,
|
||||
this.model.toJSON(), {
|
||||
'_converse': _converse,
|
||||
avatar_data,
|
||||
'display_name': this.model.getDisplayName(),
|
||||
'dropdown_btns': dropdown_btns.map(b => this.getHeadingDropdownItem(b)),
|
||||
'showUserDetailsModal': ev => this.showUserDetailsModal(ev),
|
||||
'standalone_btns': standalone_btns.map(b => this.getHeadingStandaloneButton(b)),
|
||||
'display_name': this.model.getDisplayName()
|
||||
}
|
||||
)
|
||||
);
|
||||
@ -1053,96 +1059,6 @@ converse.plugins.add('converse-chatview', {
|
||||
|
||||
_converse.ChatBoxView = ChatBoxView;
|
||||
|
||||
|
||||
_converse.UserDetailsModal = BootstrapModal.extend({
|
||||
id: "user-details-modal",
|
||||
|
||||
events: {
|
||||
'click button.refresh-contact': 'refreshContact',
|
||||
'click .fingerprint-trust .btn input': 'toggleDeviceTrust'
|
||||
},
|
||||
|
||||
initialize () {
|
||||
BootstrapModal.prototype.initialize.apply(this, arguments);
|
||||
this.model.rosterContactAdded.then(() => this.registerContactEventHandlers());
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
this.registerContactEventHandlers();
|
||||
/**
|
||||
* Triggered once the _converse.UserDetailsModal has been initialized
|
||||
* @event _converse#userDetailsModalInitialized
|
||||
* @type { _converse.ChatBox }
|
||||
* @example _converse.api.listen.on('userDetailsModalInitialized', chatbox => { ... });
|
||||
*/
|
||||
api.trigger('userDetailsModalInitialized', this.model);
|
||||
},
|
||||
|
||||
toHTML () {
|
||||
const vcard = this.model?.vcard;
|
||||
const vcard_json = vcard ? vcard.toJSON() : {};
|
||||
return tpl_user_details_modal(Object.assign(
|
||||
this.model.toJSON(),
|
||||
vcard_json, {
|
||||
'_converse': _converse,
|
||||
'allow_contact_removal': api.settings.get('allow_contact_removal'),
|
||||
'display_name': this.model.getDisplayName(),
|
||||
'is_roster_contact': this.model.contact !== undefined,
|
||||
'removeContact': ev => this.removeContact(ev),
|
||||
'view': this,
|
||||
'utils': u
|
||||
}));
|
||||
},
|
||||
|
||||
registerContactEventHandlers () {
|
||||
if (this.model.contact !== undefined) {
|
||||
this.listenTo(this.model.contact, 'change', this.render);
|
||||
this.listenTo(this.model.contact.vcard, 'change', this.render);
|
||||
this.model.contact.on('destroy', () => {
|
||||
delete this.model.contact;
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
async refreshContact (ev) {
|
||||
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
||||
const refresh_icon = this.el.querySelector('.fa-refresh');
|
||||
u.addClass('fa-spin', refresh_icon);
|
||||
try {
|
||||
await api.vcard.update(this.model.contact.vcard, true);
|
||||
} catch (e) {
|
||||
log.fatal(e);
|
||||
this.alert(__('Sorry, something went wrong while trying to refresh'), 'danger');
|
||||
}
|
||||
u.removeClass('fa-spin', refresh_icon);
|
||||
},
|
||||
|
||||
removeContact (ev) {
|
||||
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
||||
if (!api.settings.get('allow_contact_removal')) { return; }
|
||||
const result = confirm(__("Are you sure you want to remove this contact?"));
|
||||
if (result === true) {
|
||||
this.modal.hide();
|
||||
// XXX: This is annoying but necessary to get tests to pass.
|
||||
// The `dismissHandler` in bootstrap.native tries to
|
||||
// reference the remove button after it's been cleared from
|
||||
// the DOM, so we delay removing the contact to give it time.
|
||||
setTimeout(() => {
|
||||
this.model.contact.removeFromRoster(
|
||||
() => this.model.contact.destroy(),
|
||||
(err) => {
|
||||
log.error(err);
|
||||
api.alert('error', __('Error'), [
|
||||
__('Sorry, there was an error while trying to remove %1$s as a contact.',
|
||||
this.model.contact.getDisplayName())
|
||||
]);
|
||||
}
|
||||
);
|
||||
}, 1);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
api.listen.on('chatBoxViewsInitialized', () => {
|
||||
const views = _converse.chatboxviews;
|
||||
_converse.chatboxes.on('add', async item => {
|
||||
|
@ -11,6 +11,7 @@ import AddMUCModal from 'modals/add-muc.js';
|
||||
import MUCInviteModal from 'modals/muc-invite.js';
|
||||
import MUCListModal from 'modals/muc-list.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";
|
||||
@ -517,6 +518,14 @@ export const ChatRoomView = ChatBoxView.extend({
|
||||
this.model.room_details_modal.show(ev);
|
||||
},
|
||||
|
||||
showOccupantDetailsModal (ev, message) {
|
||||
ev.preventDefault();
|
||||
if (this.model.occupant_modal === undefined) {
|
||||
this.model.occupant_modal = new OccupantModal({'model': message.occupant});
|
||||
}
|
||||
this.model.occupant_modal.show(ev);
|
||||
},
|
||||
|
||||
showChatStateNotification (message) {
|
||||
if (message.get('sender') === 'me') {
|
||||
return;
|
||||
|
@ -6,6 +6,7 @@
|
||||
/* global libsignal */
|
||||
|
||||
import "converse-profile";
|
||||
import 'modals/user-details.js';
|
||||
import log from "@converse/headless/log";
|
||||
import { Collection } from "@converse/skeletor/src/collection";
|
||||
import { Model } from '@converse/skeletor/src/model.js';
|
||||
|
47
src/modals/occupant.js
Normal file
47
src/modals/occupant.js
Normal file
@ -0,0 +1,47 @@
|
||||
import tpl_occupant_modal from "./templates/occupant.js";
|
||||
import { BootstrapModal } from "../converse-modal.js";
|
||||
import { _converse, api } from "@converse/headless/converse-core";
|
||||
|
||||
|
||||
const OccupantModal = BootstrapModal.extend({
|
||||
id: "muc-occupant-modal",
|
||||
|
||||
initialize () {
|
||||
BootstrapModal.prototype.initialize.apply(this, arguments);
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
/**
|
||||
* Triggered once the OccupantModal has been initialized
|
||||
* @event _converse#userDetailsModalInitialized
|
||||
* @type { _converse.ChatBox }
|
||||
* @example _converse.api.listen.on('userDetailsModalInitialized', chatbox => { ... });
|
||||
*/
|
||||
api.trigger('occupantModalInitialized', this.model);
|
||||
},
|
||||
|
||||
toHTML () {
|
||||
return tpl_occupant_modal(Object.assign(
|
||||
this.model.toJSON(),
|
||||
{
|
||||
'avatar_data': this.getAvatarData(),
|
||||
'display_name': this.model.getDisplayName()
|
||||
}
|
||||
));
|
||||
},
|
||||
|
||||
getAvatarData () {
|
||||
const vcard = _converse.vcards.findWhere({'jid': this.model.get('jid')});
|
||||
const image_type = vcard?.get('image_type') || _converse.DEFAULT_IMAGE_TYPE;
|
||||
const image_data = vcard?.get('image') || _converse.DEFAULT_IMAGE;
|
||||
const image = "data:" + image_type + ";base64," + image_data;
|
||||
return {
|
||||
'classes': 'chat-msg__avatar',
|
||||
'height': 120,
|
||||
'width': 120,
|
||||
image,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
_converse.OccupantModal = OccupantModal;
|
||||
|
||||
export default OccupantModal;
|
23
src/modals/templates/occupant.js
Normal file
23
src/modals/templates/occupant.js
Normal file
@ -0,0 +1,23 @@
|
||||
import { html } from "lit-html";
|
||||
import { modal_close_button, modal_header_close_button } from "../../templates/buttons"
|
||||
import { renderAvatar } from '../../templates/directives/avatar';
|
||||
|
||||
|
||||
export default (o) => {
|
||||
return html`
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="user-details-modal-label">${o.display_name}</h5>
|
||||
${modal_header_close_button}
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
${renderAvatar(o.avatar_data)}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
${modal_close_button}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
100
src/modals/user-details.js
Normal file
100
src/modals/user-details.js
Normal file
@ -0,0 +1,100 @@
|
||||
import log from "@converse/headless/log";
|
||||
import tpl_user_details_modal from "../templates/user_details_modal.js";
|
||||
import { BootstrapModal } from "../converse-modal.js";
|
||||
import { __ } from '../i18n';
|
||||
import { _converse, api, converse } from "@converse/headless/converse-core";
|
||||
|
||||
const u = converse.env.utils;
|
||||
|
||||
|
||||
const UserDetailsModal = BootstrapModal.extend({
|
||||
id: "user-details-modal",
|
||||
|
||||
events: {
|
||||
'click button.refresh-contact': 'refreshContact',
|
||||
'click .fingerprint-trust .btn input': 'toggleDeviceTrust'
|
||||
},
|
||||
|
||||
initialize () {
|
||||
BootstrapModal.prototype.initialize.apply(this, arguments);
|
||||
this.model.rosterContactAdded.then(() => this.registerContactEventHandlers());
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
this.registerContactEventHandlers();
|
||||
/**
|
||||
* Triggered once the UserDetailsModal has been initialized
|
||||
* @event _converse#userDetailsModalInitialized
|
||||
* @type { _converse.ChatBox }
|
||||
* @example _converse.api.listen.on('userDetailsModalInitialized', chatbox => { ... });
|
||||
*/
|
||||
api.trigger('userDetailsModalInitialized', this.model);
|
||||
},
|
||||
|
||||
toHTML () {
|
||||
const vcard = this.model?.vcard;
|
||||
const vcard_json = vcard ? vcard.toJSON() : {};
|
||||
return tpl_user_details_modal(Object.assign(
|
||||
this.model.toJSON(),
|
||||
vcard_json, {
|
||||
'_converse': _converse,
|
||||
'allow_contact_removal': api.settings.get('allow_contact_removal'),
|
||||
'display_name': this.model.getDisplayName(),
|
||||
'is_roster_contact': this.model.contact !== undefined,
|
||||
'removeContact': ev => this.removeContact(ev),
|
||||
'view': this,
|
||||
'utils': u
|
||||
}));
|
||||
},
|
||||
|
||||
registerContactEventHandlers () {
|
||||
if (this.model.contact !== undefined) {
|
||||
this.listenTo(this.model.contact, 'change', this.render);
|
||||
this.listenTo(this.model.contact.vcard, 'change', this.render);
|
||||
this.model.contact.on('destroy', () => {
|
||||
delete this.model.contact;
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
async refreshContact (ev) {
|
||||
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
||||
const refresh_icon = this.el.querySelector('.fa-refresh');
|
||||
u.addClass('fa-spin', refresh_icon);
|
||||
try {
|
||||
await api.vcard.update(this.model.contact.vcard, true);
|
||||
} catch (e) {
|
||||
log.fatal(e);
|
||||
this.alert(__('Sorry, something went wrong while trying to refresh'), 'danger');
|
||||
}
|
||||
u.removeClass('fa-spin', refresh_icon);
|
||||
},
|
||||
|
||||
removeContact (ev) {
|
||||
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
||||
if (!api.settings.get('allow_contact_removal')) { return; }
|
||||
const result = confirm(__("Are you sure you want to remove this contact?"));
|
||||
if (result === true) {
|
||||
this.modal.hide();
|
||||
// XXX: This is annoying but necessary to get tests to pass.
|
||||
// The `dismissHandler` in bootstrap.native tries to
|
||||
// reference the remove button after it's been cleared from
|
||||
// the DOM, so we delay removing the contact to give it time.
|
||||
setTimeout(() => {
|
||||
this.model.contact.removeFromRoster(
|
||||
() => this.model.contact.destroy(),
|
||||
(err) => {
|
||||
log.error(err);
|
||||
api.alert('error', __('Error'), [
|
||||
__('Sorry, there was an error while trying to remove %1$s as a contact.',
|
||||
this.model.contact.getDisplayName())
|
||||
]);
|
||||
}
|
||||
);
|
||||
}, 1);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
_converse.UserDetailsModal = UserDetailsModal;
|
||||
|
||||
export default UserDetailsModal;
|
@ -16,12 +16,12 @@ export default (o) => {
|
||||
<!-- Anchor to allow us to scroll the message into view -->
|
||||
<a id="${o.msgid}"></a>
|
||||
|
||||
${ o.shouldShowAvatar() ? renderAvatar(o.getAvatarData()) : '' }
|
||||
<a class="show-msg-author-modal" @click=${o.showUserModal}>${ o.shouldShowAvatar() ? renderAvatar(o.getAvatarData()) : '' }</a>
|
||||
<div class="chat-msg__content chat-msg__content--${o.sender} ${o.is_me_message ? 'chat-msg__content--action' : ''}">
|
||||
|
||||
${ !o.is_me_message ? html`
|
||||
<span class="chat-msg__heading">
|
||||
<span class="chat-msg__author">${o.username}</span>
|
||||
<span class="chat-msg__author"><a class="show-msg-author-modal" @click=${o.showUserModal}>${o.username}</a></span>
|
||||
${ o.renderAvatarByline() }
|
||||
${ o.is_encrypted ? html`<span class="fa fa-lock"></span>` : '' }
|
||||
</span>` : '' }
|
||||
|
@ -1,26 +1,21 @@
|
||||
import { _converse } from '@converse/headless/converse-core';
|
||||
import { html } from "lit-html";
|
||||
import { __ } from '../i18n';
|
||||
import { renderAvatar } from './directives/avatar.js';
|
||||
import { until } from 'lit-html/directives/until.js';
|
||||
import avatar from "./avatar.js";
|
||||
|
||||
|
||||
export default (o) => {
|
||||
const i18n_profile = __('The User\'s Profile Image');
|
||||
const avatar_data = {
|
||||
'alt_text': i18n_profile,
|
||||
'extra_classes': '',
|
||||
'height': 40,
|
||||
'width': 40,
|
||||
}
|
||||
const tpl_standalone_btns = (o) => o.standalone_btns.reverse().map(b => until(b, ''));
|
||||
|
||||
const avatar = html`<span class="mr-2">${renderAvatar(o.avatar_data)}</span>`;
|
||||
|
||||
return html`
|
||||
<div class="chatbox-title ${ o.status ? '' : "chatbox-title--no-desc"}">
|
||||
<div class="chatbox-title--row">
|
||||
${ (!o._converse.api.settings.get("singleton")) ? html`<div class="chatbox-navback"><i class="fa fa-arrow-left"></i></div>` : '' }
|
||||
${ (o.type !== o._converse.HEADLINES_TYPE) ? html`<span class="mr-2">${avatar(Object.assign({}, o, avatar_data))}</span>` : '' }
|
||||
${ (!_converse.api.settings.get("singleton")) ? html`<div class="chatbox-navback"><i class="fa fa-arrow-left"></i></div>` : '' }
|
||||
${ (o.type !== _converse.HEADLINES_TYPE) ? html`<a class="show-msg-author-modal" @click=${o.showUserDetailsModal}>${ avatar }</a>` : '' }
|
||||
<div class="chatbox-title__text" title="${o.jid}">
|
||||
${ o.url ? html`<a href="${o.url}" target="_blank" rel="noopener" class="user">${o.display_name}</a>` : o.display_name}
|
||||
${ (o.type !== _converse.HEADLINES_TYPE) ? html`<a class="user show-msg-author-modal" @click=${o.showUserDetailsModal}>${ o.display_name }</a>` : o.display_name }
|
||||
</div>
|
||||
</div>
|
||||
<div class="chatbox-title__buttons row no-gutters">
|
||||
|
Loading…
Reference in New Issue
Block a user