Commit c0fafcec authored by JC Brand's avatar JC Brand

Move converse-muc into a folder

parent e8536ebc
......@@ -12,7 +12,7 @@ import "./plugins/chatboxes.js";
import "./plugins/disco.js"; // XEP-0030 Service discovery
import "./plugins/headlines.js"; // Support for headline messages
import "./plugins/mam.js"; // XEP-0313 Message Archive Management
import "./plugins/muc.js"; // XEP-0045 Multi-user chat
import "./plugins/muc/index.js"; // XEP-0045 Multi-user chat
import "./plugins/ping.js"; // XEP-0199 XMPP Ping
import "./plugins/pubsub.js"; // XEP-0060 Pubsub
import "./plugins/roster.js"; // RFC-6121 Contacts Roster
......
......@@ -5,7 +5,7 @@
* @copyright 2020, the Converse.js contributors
* @license Mozilla Public License (MPLv2)
*/
import "@converse/headless/plugins/muc";
import "@converse/headless/plugins/muc/index.js";
import log from "../log.js";
import { Collection } from "@converse/skeletor/src/collection";
import { Model } from '@converse/skeletor/src/model.js';
......
This source diff could not be displayed because it is too large. You can view the blob instead.
import log from '../../log';
import u from '../../utils/form';
import { Strophe } from 'strophe.js/src/strophe';
import { _converse, api } from '../../core.js';
export default {
/**
* The "rooms" namespace groups methods relevant to chatrooms
* (aka groupchats).
*
* @namespace api.rooms
* @memberOf api
*/
rooms: {
/**
* Creates a new MUC chatroom (aka groupchat)
*
* Similar to {@link api.rooms.open}, but creates
* the chatroom in the background (i.e. doesn't cause a view to open).
*
* @method api.rooms.create
* @param {(string[]|string)} jid|jids The JID or array of
* JIDs of the chatroom(s) to create
* @param {object} [attrs] attrs The room attributes
* @returns {Promise} Promise which resolves with the Model representing the chat.
*/
create (jids, attrs = {}) {
attrs = typeof attrs === 'string' ? { 'nick': attrs } : attrs || {};
if (!attrs.nick && api.settings.get('muc_nickname_from_jid')) {
attrs.nick = Strophe.getNodeFromJid(_converse.bare_jid);
}
if (jids === undefined) {
throw new TypeError('rooms.create: You need to provide at least one JID');
} else if (typeof jids === 'string') {
return api.rooms.get(u.getJIDFromURI(jids), attrs, true);
}
return jids.map(jid => api.rooms.get(u.getJIDFromURI(jid), attrs, true));
},
/**
* Opens a MUC chatroom (aka groupchat)
*
* Similar to {@link api.chats.open}, but for groupchats.
*
* @method api.rooms.open
* @param {string} jid The room JID or JIDs (if not specified, all
* currently open rooms will be returned).
* @param {string} attrs A map containing any extra room attributes.
* @param {string} [attrs.nick] The current user's nickname for the MUC
* @param {boolean} [attrs.auto_configure] A boolean, indicating
* whether the room should be configured automatically or not.
* If set to `true`, then it makes sense to pass in configuration settings.
* @param {object} [attrs.roomconfig] A map of configuration settings to be used when the room gets
* configured automatically. Currently it doesn't make sense to specify
* `roomconfig` values if `auto_configure` is set to `false`.
* For a list of configuration values that can be passed in, refer to these values
* in the [XEP-0045 MUC specification](https://xmpp.org/extensions/xep-0045.html#registrar-formtype-owner).
* The values should be named without the `muc#roomconfig_` prefix.
* @param {boolean} [attrs.minimized] A boolean, indicating whether the room should be opened minimized or not.
* @param {boolean} [attrs.bring_to_foreground] A boolean indicating whether the room should be
* brought to the foreground and therefore replace the currently shown chat.
* If there is no chat currently open, then this option is ineffective.
* @param {Boolean} [force=false] - By default, a minimized
* room won't be maximized (in `overlayed` view mode) and in
* `fullscreen` view mode a newly opened room won't replace
* another chat already in the foreground.
* Set `force` to `true` if you want to force the room to be
* maximized or shown.
* @returns {Promise} Promise which resolves with the Model representing the chat.
*
* @example
* this.api.rooms.open('group@muc.example.com')
*
* @example
* // To return an array of rooms, provide an array of room JIDs:
* api.rooms.open(['group1@muc.example.com', 'group2@muc.example.com'])
*
* @example
* // To setup a custom nickname when joining the room, provide the optional nick argument:
* api.rooms.open('group@muc.example.com', {'nick': 'mycustomnick'})
*
* @example
* // For example, opening a room with a specific default configuration:
* api.rooms.open(
* 'myroom@conference.example.org',
* { 'nick': 'coolguy69',
* 'auto_configure': true,
* 'roomconfig': {
* 'changesubject': false,
* 'membersonly': true,
* 'persistentroom': true,
* 'publicroom': true,
* 'roomdesc': 'Comfy room for hanging out',
* 'whois': 'anyone'
* }
* }
* );
*/
async open (jids, attrs = {}, force = false) {
await api.waitUntil('chatBoxesFetched');
if (jids === undefined) {
const err_msg = 'rooms.open: You need to provide at least one JID';
log.error(err_msg);
throw new TypeError(err_msg);
} else if (typeof jids === 'string') {
const room = await api.rooms.get(jids, attrs, true);
room && room.maybeShow(force);
return room;
} else {
const rooms = await Promise.all(jids.map(jid => api.rooms.get(jid, attrs, true)));
rooms.forEach(r => r.maybeShow(force));
return rooms;
}
},
/**
* Fetches the object representing a MUC chatroom (aka groupchat)
*
* @method api.rooms.get
* @param {string} [jid] The room JID (if not specified, all rooms will be returned).
* @param {object} [attrs] A map containing any extra room attributes For example, if you want
* to specify a nickname and password, use `{'nick': 'bloodninja', 'password': 'secret'}`.
* @param {boolean} create A boolean indicating whether the room should be created
* if not found (default: `false`)
* @returns { Promise<_converse.ChatRoom> }
* @example
* api.waitUntil('roomsAutoJoined').then(() => {
* const create_if_not_found = true;
* api.rooms.get(
* 'group@muc.example.com',
* {'nick': 'dread-pirate-roberts'},
* create_if_not_found
* )
* });
*/
async get (jids, attrs = {}, create = false) {
async function _get (jid) {
jid = u.getJIDFromURI(jid);
let model = await api.chatboxes.get(jid);
if (!model && create) {
model = await api.chatboxes.create(jid, attrs, _converse.ChatRoom);
} else {
model = model && model.get('type') === _converse.CHATROOMS_TYPE ? model : null;
if (model && Object.keys(attrs).length) {
model.save(attrs);
}
}
return model;
}
if (jids === undefined) {
const chats = await api.chatboxes.get();
return chats.filter(c => c.get('type') === _converse.CHATROOMS_TYPE);
} else if (typeof jids === 'string') {
return _get(jids);
}
return Promise.all(jids.map(jid => _get(jid)));
}
}
}
This diff is collapsed.
import log from '../../log';
import { Strophe } from 'strophe.js/src/strophe';
import { _converse, api } from '../../core.js';
/**
* Mixing that turns a Message model into a ChatRoomMessage model.
* @class
* @namespace _converse.ChatRoomMessage
* @memberOf _converse
*/
const ChatRoomMessageMixin = {
initialize () {
if (!this.checkValidity()) {
return;
}
if (this.get('file')) {
this.on('change:put', this.uploadFile, this);
}
if (!this.setTimerForEphemeralMessage()) {
this.setOccupant();
}
/**
* Triggered once a {@link _converse.ChatRoomMessageInitialized} has been created and initialized.
* @event _converse#chatRoomMessageInitialized
* @type { _converse.ChatRoomMessages}
* @example _converse.api.listen.on('chatRoomMessageInitialized', model => { ... });
*/
api.trigger('chatRoomMessageInitialized', this);
},
/**
* Determines whether this messsage may be moderated,
* based on configuration settings and server support.
* @async
* @private
* @method _converse.ChatRoomMessages#mayBeModerated
* @returns { Boolean }
*/
mayBeModerated () {
return (
['all', 'moderator'].includes(api.settings.get('allow_message_retraction')) &&
this.collection.chatbox.canModerateMessages()
);
},
checkValidity () {
const result = _converse.Message.prototype.checkValidity.call(this);
!result && this.collection.chatbox.debouncedRejoin();
return result;
},
onOccupantRemoved () {
this.stopListening(this.occupant);
delete this.occupant;
const chatbox = this?.collection?.chatbox;
if (!chatbox) {
return log.error(`Could not get collection.chatbox for message: ${JSON.stringify(this.toJSON())}`);
}
this.listenTo(chatbox.occupants, 'add', this.onOccupantAdded);
},
onOccupantAdded (occupant) {
if (occupant.get('nick') === Strophe.getResourceFromJid(this.get('from'))) {
this.occupant = occupant;
this.trigger('occupantAdded');
this.listenTo(this.occupant, 'destroy', this.onOccupantRemoved);
const chatbox = this?.collection?.chatbox;
if (!chatbox) {
return log.error(`Could not get collection.chatbox for message: ${JSON.stringify(this.toJSON())}`);
}
this.stopListening(chatbox.occupants, 'add', this.onOccupantAdded);
}
},
setOccupant () {
if (this.get('type') !== 'groupchat') {
return;
}
const chatbox = this?.collection?.chatbox;
if (!chatbox) {
return log.error(`Could not get collection.chatbox for message: ${JSON.stringify(this.toJSON())}`);
}
const nick = Strophe.getResourceFromJid(this.get('from'));
this.occupant = chatbox.occupants.findWhere({ nick });
if (!this.occupant && api.settings.get('muc_send_probes')) {
this.occupant = chatbox.occupants.create({ nick, 'type': 'unavailable' });
const jid = `${chatbox.get('jid')}/${nick}`;
api.user.presence.send('probe', jid);
}
if (this.occupant) {
this.listenTo(this.occupant, 'destroy', this.onOccupantRemoved);
} else {
this.listenTo(chatbox.occupants, 'add', this.onOccupantAdded);
}
}
};
export default ChatRoomMessageMixin;
This diff is collapsed.
import u from '../../utils/form';
import { Model } from '@converse/skeletor/src/model.js';
import { _converse, api } from '../../core.js';
/**
* Represents a participant in a MUC
* @class
* @namespace _converse.ChatRoomOccupant
* @memberOf _converse
*/
const ChatRoomOccupant = Model.extend({
defaults: {
'hats': [],
'show': 'offline',
'states': []
},
initialize (attributes) {
this.set(Object.assign({ 'id': u.getUniqueId() }, attributes));
this.on('change:image_hash', this.onAvatarChanged, this);
},
onAvatarChanged () {
const hash = this.get('image_hash');
const vcards = [];
if (this.get('jid')) {
vcards.push(_converse.vcards.findWhere({ 'jid': this.get('jid') }));
}
vcards.push(_converse.vcards.findWhere({ 'jid': this.get('from') }));
vcards
.filter(v => v)
.forEach(vcard => {
if (hash && vcard.get('image_hash') !== hash) {
api.vcard.update(vcard, true);
}
});
},
getDisplayName () {
return this.get('nick') || this.get('jid');
},
isMember () {
return ['admin', 'owner', 'member'].includes(this.get('affiliation'));
},
isModerator () {
return ['admin', 'owner'].includes(this.get('affiliation')) || this.get('role') === 'moderator';
},
isSelf () {
return this.get('states').includes('110');
}
});
export default ChatRoomOccupant;
import ChatRoomOccupant from './occupant.js';
import u from '../../utils/form';
import { Collection } from '@converse/skeletor/src/collection';
import { Strophe } from 'strophe.js/src/strophe';
import { _converse, api } from '../../core.js';
const MUC_ROLE_WEIGHTS = {
'moderator': 1,
'participant': 2,
'visitor': 3,
'none': 2
};
/**
* A list of {@link _converse.ChatRoomOccupant} instances, representing participants in a MUC.
* @class
* @namespace _converse.ChatRoomOccupants
* @memberOf _converse
*/
const ChatRoomOccupants = Collection.extend({
model: ChatRoomOccupant,
comparator (occupant1, occupant2) {
const role1 = occupant1.get('role') || 'none';
const role2 = occupant2.get('role') || 'none';
if (MUC_ROLE_WEIGHTS[role1] === MUC_ROLE_WEIGHTS[role2]) {
const nick1 = occupant1.getDisplayName().toLowerCase();
const nick2 = occupant2.getDisplayName().toLowerCase();
return nick1 < nick2 ? -1 : nick1 > nick2 ? 1 : 0;
} else {
return MUC_ROLE_WEIGHTS[role1] < MUC_ROLE_WEIGHTS[role2] ? -1 : 1;
}
},
getAutoFetchedAffiliationLists () {
const affs = api.settings.get('muc_fetch_members');
return Array.isArray(affs) ? affs : affs ? ['member', 'admin', 'owner'] : [];
},
async fetchMembers () {
const affiliations = this.getAutoFetchedAffiliationLists();
if (affiliations.length === 0) {
return;
}
const aff_lists = await Promise.all(affiliations.map(a => this.chatroom.getAffiliationList(a)));
const new_members = aff_lists.reduce((acc, val) => (u.isErrorObject(val) ? acc : [...val, ...acc]), []);
const known_affiliations = affiliations.filter(
a => !u.isErrorObject(aff_lists[affiliations.indexOf(a)])
);
const new_jids = new_members.map(m => m.jid).filter(m => m !== undefined);
const new_nicks = new_members.map(m => (!m.jid && m.nick) || undefined).filter(m => m !== undefined);
const removed_members = this.filter(m => {
return (
known_affiliations.includes(m.get('affiliation')) &&
!new_nicks.includes(m.get('nick')) &&
!new_jids.includes(m.get('jid'))
);
});
removed_members.forEach(occupant => {
if (occupant.get('jid') === _converse.bare_jid) {
return;
}
if (occupant.get('show') === 'offline') {
occupant.destroy();
} else {
occupant.save('affiliation', null);
}
});
new_members.forEach(attrs => {
const occupant = attrs.jid
? this.findOccupant({ 'jid': attrs.jid })
: this.findOccupant({ 'nick': attrs.nick });
if (occupant) {
occupant.save(attrs);
} else {
this.create(attrs);
}
});
/**
* Triggered once the member lists for this MUC have been fetched and processed.
* @event _converse#membersFetched
* @example _converse.api.listen.on('membersFetched', () => { ... });
*/
api.trigger('membersFetched');
},
/**
* @typedef { Object} OccupantData
* @property { String } [jid]
* @property { String } [nick]
*/
/**
* Try to find an existing occupant based on the passed in
* data object.
*
* If we have a JID, we use that as lookup variable,
* otherwise we use the nick. We don't always have both,
* but should have at least one or the other.
* @private
* @method _converse.ChatRoomOccupants#findOccupant
* @param { OccupantData } data
*/
findOccupant (data) {
const jid = Strophe.getBareJidFromJid(data.jid);
return (jid && this.findWhere({ jid })) || this.findWhere({ 'nick': data.nick });
}
});
export default ChatRoomOccupants;
......@@ -2,7 +2,7 @@ import BootstrapModal from "./base.js";
import log from "@converse/headless/log";
import sizzle from "sizzle";
import tpl_moderator_tools_modal from "./templates/moderator-tools.js";
import { AFFILIATIONS, ROLES } from "@converse/headless/plugins/muc.js";
import { AFFILIATIONS, ROLES } from "@converse/headless/plugins/muc/index.js";
import { __ } from '../i18n';
import { api, converse } from "@converse/headless/core";
......
......@@ -4,7 +4,7 @@
* @copyright 2020, the Converse.js contributors
* @license Mozilla Public License (MPLv2)
*/
import "@converse/headless/plugins/muc";
import "@converse/headless/plugins/muc/index.js";
import { _converse, api, converse } from "@converse/headless/core";
import tpl_bookmarks_list from "../templates/bookmarks_list.js"
import tpl_muc_bookmark_form from "../templates/muc_bookmark_form.js";
......
......@@ -6,7 +6,7 @@
import "./chatview/index.js";
import "./controlbox/index.js";
import "./singleton.js";
import "@converse/headless/plugins/muc";
import "@converse/headless/plugins/muc/index.js";
import { api, converse } from "@converse/headless/core";
......
......@@ -6,7 +6,7 @@
* @copyright 2020, the Converse.js contributors
* @license Mozilla Public License (MPLv2)
*/
import "@converse/headless/plugins/muc";
import "@converse/headless/plugins/muc/index.js";
import RoomDetailsModal from 'modals/muc-details.js';
import { _converse, api, converse } from "@converse/headless/core";
import tpl_rooms_list from "../templates/rooms_list.js";
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment