Skip to content

Commit

Permalink
Add support for rendering XEP-0392 colored avatars
Browse files Browse the repository at this point in the history
Updates #1349

- Show user initials and XEP-0392 color when no avatar is present
- Show MUC details modal when clicking MUC avatar in header
- Render MUC avatars in the rooms list
  • Loading branch information
jcbrand committed Jun 4, 2024
1 parent 978d465 commit 1da7643
Show file tree
Hide file tree
Showing 85 changed files with 1,203 additions and 530 deletions.
2 changes: 1 addition & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## 11.0.0 (Unreleased)

- #1195: Add actions to quote and copy messages
- #1349: New config option [colorize_username](https://conversejs.org/docs/html/configuration.html#colorize_username)
- #1349: XEP-0392 Consistent Color Generation
- #2716: Fix issue with chat display when opening via URL
- #3033: Add the `muc_grouped_by_domain` option to display MUCs on the same domain in collapsible groups
- #3155: Some ad-hoc commands not working
Expand Down
6 changes: 6 additions & 0 deletions conversejs.doap
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,12 @@
<xmpp:since>4.0.0</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0392.html"/>
<xmpp:since>11.0.0</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0393.html"/>
Expand Down
1 change: 0 additions & 1 deletion dev.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
});

converse.initialize({
colorize_username: true,
i18n: 'af',
theme: 'dracula',
auto_away: 300,
Expand Down
4 changes: 3 additions & 1 deletion karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ module.exports = function(config) {
{ pattern: "src/plugins/chatview/tests/markers.js", type: 'module' },
{ pattern: "src/plugins/chatview/tests/me-messages.js", type: 'module' },
{ pattern: "src/plugins/chatview/tests/message-audio.js", type: 'module' },
{ pattern: "src/plugins/chatview/tests/message-avatar.js", type: 'module' },
{ pattern: "src/plugins/chatview/tests/message-gifs.js", type: 'module' },
{ pattern: "src/plugins/chatview/tests/message-images.js", type: 'module' },
{ pattern: "src/plugins/chatview/tests/message-videos.js", type: 'module' },
Expand All @@ -70,8 +71,8 @@ module.exports = function(config) {
{ pattern: "src/plugins/mam-views/tests/mam.js", type: 'module' },
{ pattern: "src/plugins/mam-views/tests/placeholder.js", type: 'module' },
{ pattern: "src/plugins/minimize/tests/minchats.js", type: 'module' },
{ pattern: "src/plugins/muc-views/tests/autocomplete.js", type: 'module' },
{ pattern: "src/plugins/muc-views/tests/actions.js", type: 'module' },
{ pattern: "src/plugins/muc-views/tests/autocomplete.js", type: 'module' },
{ pattern: "src/plugins/muc-views/tests/component.js", type: 'module' },
{ pattern: "src/plugins/muc-views/tests/corrections.js", type: 'module' },
{ pattern: "src/plugins/muc-views/tests/disco.js", type: 'module' },
Expand All @@ -88,6 +89,7 @@ module.exports = function(config) {
{ pattern: "src/plugins/muc-views/tests/modtools.js", type: 'module' },
{ pattern: "src/plugins/muc-views/tests/muc-add-modal.js", type: 'module' },
{ pattern: "src/plugins/muc-views/tests/muc-api.js", type: 'module' },
{ pattern: "src/plugins/muc-views/tests/muc-avatar.js", type: 'module' },
{ pattern: "src/plugins/muc-views/tests/muc-list-modal.js", type: 'module' },
{ pattern: "src/plugins/muc-views/tests/muc-mentions.js", type: 'module' },
{ pattern: "src/plugins/muc-views/tests/muc-messages.js", type: 'module' },
Expand Down
2 changes: 1 addition & 1 deletion src/headless/plugins/chat/message.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class Message extends ModelWithContact {
setContact () {
if (['chat', 'normal'].includes(this.get('type'))) {
ModelWithContact.prototype.initialize.apply(this, arguments);
return this.setRosterContact(Strophe.getBareJidFromJid(this.get('from')));
return this.setModelContact(Strophe.getBareJidFromJid(this.get('from')));
}
}

Expand Down
42 changes: 30 additions & 12 deletions src/headless/plugins/chat/model-with-contact.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import { Model } from '@converse/skeletor';
import { getOpenPromise } from '@converse/openpromise';
import api from "../../shared/api/index.js";
import { Strophe } from 'strophe.js';
import _converse from '../../shared/_converse.js';
import api from '../../shared/api/index.js';
import { ColorAwareModel } from '../../shared/color.js';

class ModelWithContact extends Model {
class ModelWithContact extends ColorAwareModel {
/**
* @typedef {import('../vcard/vcard').default} VCard
* @typedef {import('../roster/contact').default} RosterContact
* @typedef {import('shared/_converse.js').XMPPStatus} XMPPStatus
*/

initialize () {
initialize() {
super.initialize();
this.rosterContactAdded = getOpenPromise();
/**
* @public
* @type {RosterContact}
* @type {RosterContact|XMPPStatus}
*/

this.contact = null;

/**
* @public
* @type {VCard}
Expand All @@ -27,13 +30,28 @@ class ModelWithContact extends Model {
/**
* @param {string} jid
*/
async setRosterContact (jid) {
const contact = await api.contacts.get(jid);
if (contact) {
this.contact = contact;
this.set('nickname', contact.get('nickname'));
this.rosterContactAdded.resolve();
async setModelContact(jid) {
if (this.contact?.get('jid') === jid) return;

if (Strophe.getBareJidFromJid(jid) === _converse.session.get('bare_jid')) {
this.contact = _converse.state.xmppstatus;
} else {
const contact = await api.contacts.get(jid);
if (contact) {
this.contact = contact;
this.set('nickname', contact.get('nickname'));
}
}

this.listenTo(this.contact, 'change', (changed) => {
if (changed.nickname) {
this.set('nickname', changed.nickname);
}
this.trigger('contact:change', changed);
});

this.rosterContactAdded.resolve();
this.trigger('contactAdded', this.contact);
}
}

Expand Down
29 changes: 16 additions & 13 deletions src/headless/plugins/chat/model.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
/**
* @typedef {import('./message.js').default} Message
* @typedef {import('../muc/muc.js').default} MUC
* @typedef {import('../muc/message.js').default} MUCMessage
* @typedef {module:plugin-chat-parsers.MessageAttributes} MessageAttributes
* @typedef {import('strophe.js/src/builder.js').Builder} Strophe.Builder
*/
import isMatch from "lodash-es/isMatch";
import pick from "lodash-es/pick";
import { getOpenPromise } from '@converse/openpromise';
Expand Down Expand Up @@ -32,6 +25,13 @@ const { Strophe, $msg, u } = converse.env;
* Represents an open/ongoing chat conversation.
*/
class ChatBox extends ModelWithContact {
/**
* @typedef {import('./message.js').default} Message
* @typedef {import('../muc/muc.js').default} MUC
* @typedef {import('../muc/message.js').default} MUCMessage
* @typedef {module:plugin-chat-parsers.MessageAttributes} MessageAttributes
* @typedef {import('strophe.js').Builder} Builder
*/

defaults () {
return {
Expand Down Expand Up @@ -72,8 +72,8 @@ class ChatBox extends ModelWithContact {
if (this.get('type') === PRIVATE_CHAT_TYPE) {
const { presences } = _converse.state;
this.presence = presences.get(jid) || presences.create({ jid });
await this.setRosterContact(jid);
this.presence.on('change:show', item => this.onPresenceChanged(item));
await this.setModelContact(jid);
this.presence.on('change:show', (item) => this.onPresenceChanged(item));
}
this.on('change:chat_state', this.sendChatState, this);
this.ui.on('change:scrolled', this.onScrolledChanged, this);
Expand Down Expand Up @@ -155,6 +155,9 @@ class ChatBox extends ModelWithContact {
return this.messages.fetched;
}

/**
* @param {Element} stanza
*/
async handleErrorMessageStanza (stanza) {
const { __ } = _converse;
const attrs = await parseMessage(stanza);
Expand Down Expand Up @@ -828,12 +831,12 @@ class ChatBox extends ModelWithContact {
/**
* *Hook* which allows plugins to update an outgoing message stanza
* @event _converse#createMessageStanza
* @param { ChatBox | MUC } chat - The chat from
* @param {ChatBox|MUC} chat - The chat from
* which this message stanza is being sent.
* @param { Object } data - Message data
* @param { Message | MUCMessage } data.message
* @param {Object} data - Message data
* @param {Message|MUCMessage} data.message
* The message object from which the stanza is created and which gets persisted to storage.
* @param { Strophe.Builder } data.stanza
* @param {Builder} data.stanza
* The stanza that will be sent out, as a Strophe.Builder object.
* You can use the Strophe.Builder functions to extend the stanza.
* See http://strophe.im/strophejs/doc/1.4.3/files/strophe-umd-js.html#Strophe.Builder.Functions
Expand Down
2 changes: 1 addition & 1 deletion src/headless/plugins/muc/muc.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
import _converse from '../../shared/_converse.js';
import api from '../../shared/api/index.js';
import converse from '../../shared/api/public.js';
import ChatBox from '../chat/model';
import debounce from 'lodash-es/debounce';
import log from '../../log';
import p from '../../utils/parse-helpers';
import pick from 'lodash-es/pick';
import sizzle from 'sizzle';
import { Model } from '@converse/skeletor';
import ChatBox from '../chat/model.js';
import { ROOMSTATUS } from './constants.js';
import { CHATROOMS_TYPE, GONE } from '../../shared/constants.js';
import { Strophe, $build, $iq, $msg, $pres } from 'strophe.js';
Expand Down
25 changes: 5 additions & 20 deletions src/headless/plugins/muc/occupant.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import { Model } from '@converse/skeletor';
import api from '../../shared/api/index.js';
import { ColorAwareModel } from '../../shared/color.js';
import { AFFILIATIONS, ROLES } from './constants.js';
import u from '../../utils/index.js';

const { safeSave, colorize } = u;

/**
* Represents a participant in a MUC
* @class
* @namespace _converse.MUCOccupant
* @memberOf _converse
*/
class MUCOccupant extends Model {
class MUCOccupant extends ColorAwareModel {

constructor (attributes, options) {
super(attributes, options);
Expand Down Expand Up @@ -69,9 +66,9 @@ class MUCOccupant extends Model {
}

/**
* Return affiliations which may be assigned by this occupant
* @returns {typeof AFFILIATIONS} An array of assignable affiliations
*/
* Return affiliations which may be assigned by this occupant
* @returns {typeof AFFILIATIONS} An array of assignable affiliations
*/
getAssignableAffiliations () {
let disabled = api.settings.get('modtools_disable_assign');
if (!Array.isArray(disabled)) {
Expand All @@ -86,18 +83,6 @@ class MUCOccupant extends Model {
}
}

async setColor () {
const color = await colorize(this.getDisplayName());
safeSave(this, { color });
}

async getColor () {
if (!this.get('color')) {
await this.setColor();
}
return this.get('color');
}

isMember () {
return ['admin', 'owner', 'member'].includes(this.get('affiliation'));
}
Expand Down
8 changes: 3 additions & 5 deletions src/headless/plugins/roster/contact.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { getOpenPromise } from '@converse/openpromise';
import '../../plugins/status/api.js';
import _converse from '../../shared/_converse.js';
import api from '../../shared/api/index.js';
import converse from '../../shared/api/public.js';
import { Model } from '@converse/skeletor';
import { getOpenPromise } from '@converse/openpromise';
import { ColorAwareModel } from '../../shared/color.js';
import { rejectPresenceSubscription } from './utils.js';

const { Strophe, $iq, $pres } = converse.env;

class RosterContact extends Model {
class RosterContact extends ColorAwareModel {
get idAttribute () {
return 'jid';
}
Expand All @@ -17,8 +17,6 @@ class RosterContact extends Model {
return {
'chat_state': undefined,
'groups': [],
'image': _converse.DEFAULT_IMAGE,
'image_type': _converse.DEFAULT_IMAGE_TYPE,
'num_unread': 0,
'status': undefined,
}
Expand Down
5 changes: 2 additions & 3 deletions src/headless/plugins/roster/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import _converse from '../../shared/_converse.js';
import api from '../../shared/api/index.js';
import converse from '../../shared/api/public.js';
import log from "../../log.js";
import { Strophe } from 'strophe.js';
import { Model } from '@converse/skeletor';
import { RosterFilter } from '../../plugins/roster/filter.js';
import { PRIVATE_CHAT_TYPE } from "../../shared/constants";
Expand Down Expand Up @@ -191,7 +190,7 @@ export function onChatBoxesInitialized () {

chatboxes.on('add', chatbox => {
if (chatbox.get('type') === PRIVATE_CHAT_TYPE) {
chatbox.setRosterContact(chatbox.get('jid'));
chatbox.setModelContact(chatbox.get('jid'));
}
});
}
Expand All @@ -206,7 +205,7 @@ export function onRosterContactsFetched () {
// When a new contact is added, check if we already have a
// chatbox open for it, and if so attach it to the chatbox.
const chatbox = _converse.state.chatboxes.findWhere({ 'jid': contact.get('jid') });
chatbox?.setRosterContact(contact.get('jid'));
chatbox?.setModelContact(contact.get('jid'));
});
}

Expand Down
30 changes: 27 additions & 3 deletions src/headless/plugins/status/status.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import _converse from '../../shared/_converse.js';
import api from '../../shared/api/index.js';
import converse from '../../shared/api/public.js';
import { Model } from '@converse/skeletor';
import { ColorAwareModel } from '../../shared/color.js';
import { isIdle, getIdleSeconds } from './utils.js';

const { Strophe, $pres } = converse.env;

export default class XMPPStatus extends Model {
export default class XMPPStatus extends ColorAwareModel {

constructor(attributes, options) {
super(attributes, options);
Expand All @@ -17,6 +17,30 @@ export default class XMPPStatus extends Model {
return { "status": api.settings.get("default_state") }
}

/**
* @param {string} attr
*/
get(attr) {
if (attr === 'jid') {
return _converse.session.get('bare_jid');
} else if (attr === 'nickname') {
return api.settings.get('nickname');
}
return ColorAwareModel.prototype.get.call(this, attr);
}

/**
* @param {string|Object} key
* @param {string|Object} [val]
* @param {Object} [options]
*/
set(key, val, options) {
if (key === 'jid' || key === 'nickname') {
throw new Error('Readonly property')
}
return ColorAwareModel.prototype.set.call(this, key, val, options);
}

initialize () {
this.on('change', item => {
if (!(item.changed instanceof Object)) {
Expand All @@ -29,7 +53,7 @@ export default class XMPPStatus extends Model {
}

getDisplayName () {
return this.getFullname() || this.getNickname() || _converse.session.get('bare_jid');
return this.getFullname() || this.getNickname() || this.get('jid');
}

getNickname () {
Expand Down
4 changes: 2 additions & 2 deletions src/headless/plugins/vcard/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ converse.plugins.add('converse-vcard', {
getNickname () {
const { _converse } = this.__super__;
const nick = this.__super__.getNickname.apply(this);
if (!nick && _converse.xmppstatus.vcard) {
return _converse.xmppstatus.vcard.get('nickname');
if (!nick && _converse.state.xmppstatus.vcard) {
return _converse.state.xmppstatus.vcard.get('nickname');
} else {
return nick;
}
Expand Down
Loading

0 comments on commit 1da7643

Please sign in to comment.