Allow moderators to retract their own messages...
when retractions are restricted to mods only
This commit is contained in:
parent
0c0ca558ed
commit
321a54323e
@ -168,7 +168,7 @@ allow_message_retraction
|
||||
------------------------
|
||||
|
||||
* Default: ``'all'``
|
||||
* Possible values: ``'all'``, ``'own'``, ``'moderator'``
|
||||
* Possible values: ``'all'``, ``'own'``, ``'moderator'`` or any falsy value
|
||||
|
||||
Determines who is allowed to retract messages. If set to ``'all'``, then normal
|
||||
users may retract their own messages and ``'moderators'`` may retract the messages of
|
||||
|
89
package-lock.json
generated
89
package-lock.json
generated
@ -9980,6 +9980,11 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"filesize": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/filesize/-/filesize-4.2.1.tgz",
|
||||
"integrity": "sha512-bP82Hi8VRZX/TUBKfE24iiUGsB/sfm2WUrwTQyAzQrhO3V9IhcBBNBXMyzLY5orACxRyYJ3d2HeRVX+eFv4lmA=="
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
|
||||
@ -10291,7 +10296,6 @@
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^4.0.0",
|
||||
@ -10301,8 +10305,7 @@
|
||||
"graceful-fs": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
|
||||
"integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -11527,8 +11530,7 @@
|
||||
"graceful-fs": {
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
|
||||
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
|
||||
"dev": true
|
||||
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
|
||||
},
|
||||
"handle-thing": {
|
||||
"version": "2.0.0",
|
||||
@ -11996,6 +11998,11 @@
|
||||
"minimatch": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"immediate": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||
"integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps="
|
||||
},
|
||||
"import-cwd": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz",
|
||||
@ -12559,6 +12566,11 @@
|
||||
"integrity": "sha1-5kAN8ea1bhMLYcS80JPap/boyhU=",
|
||||
"dev": true
|
||||
},
|
||||
"jed": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jed/-/jed-1.1.1.tgz",
|
||||
"integrity": "sha1-elSbvZ/+FYWwzQoZHiAwVb7ldLQ="
|
||||
},
|
||||
"jest-worker": {
|
||||
"version": "25.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.1.0.tgz",
|
||||
@ -12718,7 +12730,6 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
@ -12955,6 +12966,14 @@
|
||||
"type-check": "~0.3.2"
|
||||
}
|
||||
},
|
||||
"lie": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
|
||||
"integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=",
|
||||
"requires": {
|
||||
"immediate": "~3.0.5"
|
||||
}
|
||||
},
|
||||
"linkify-it": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz",
|
||||
@ -13017,6 +13036,14 @@
|
||||
"json5": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"localforage": {
|
||||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/localforage/-/localforage-1.7.3.tgz",
|
||||
"integrity": "sha512-1TulyYfc4udS7ECSBT2vwJksWbkwwTX8BzeUIiq8Y07Riy7bDAAnxDaPU/tWyOVmQAcWJIEIFP9lPfBGqVoPgQ==",
|
||||
"requires": {
|
||||
"lie": "3.1.1"
|
||||
}
|
||||
},
|
||||
"locate-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
|
||||
@ -18771,6 +18798,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"pluggable.js": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pluggable.js/-/pluggable.js-2.0.1.tgz",
|
||||
"integrity": "sha512-SBt6v6Tbp20Jf8hU0cpcc/+HBHGMY8/Q+yA6Ih0tBQE8tfdZ6U4PRG0iNvUUjLx/hVyOP53n0UfGBymlfaaXCg==",
|
||||
"requires": {
|
||||
"lodash": "^4.17.11"
|
||||
}
|
||||
},
|
||||
"po-loader": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/po-loader/-/po-loader-0.5.0.tgz",
|
||||
@ -20679,6 +20714,13 @@
|
||||
"integrity": "sha512-Mf37VjirD7RqCVeYgI8jb5K0DymIho/jNJqDgIkMs4cgKbEkvsow8Q6hpvF7Zmys9iEif0oW41hgbeWVZwABJw==",
|
||||
"dev": true
|
||||
},
|
||||
"skeletor.js": {
|
||||
"version": "github:skeletorjs/skeletor#bf6d9c86f9fcf224fa9d9af5a25380b77aa4b561",
|
||||
"from": "github:skeletorjs/skeletor#bf6d9c86f9fcf224fa9d9af5a25380b77aa4b561",
|
||||
"requires": {
|
||||
"lodash": "^4.17.14"
|
||||
}
|
||||
},
|
||||
"slash": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
|
||||
@ -21353,6 +21395,11 @@
|
||||
"through": "^2.3.4"
|
||||
}
|
||||
},
|
||||
"strophe.js": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/strophe.js/-/strophe.js-1.3.4.tgz",
|
||||
"integrity": "sha512-jSLDG8jolhAwGOSgiJ7DTMSYK3wVoEJHKtpVRyEacQZ6CWA6z2WRPJpcFMjsIweq5aP9/XIvKUQqHBu/ZhvESA=="
|
||||
},
|
||||
"style-loader": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz",
|
||||
@ -21806,6 +21853,33 @@
|
||||
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
|
||||
"dev": true
|
||||
},
|
||||
"twemoji": {
|
||||
"version": "12.1.5",
|
||||
"resolved": "https://registry.npmjs.org/twemoji/-/twemoji-12.1.5.tgz",
|
||||
"integrity": "sha512-B0PBVy5xomwb1M/WZxf/IqPZfnoIYy1skXnlHjMwLwTNfZ9ljh8VgWQktAPcJXu8080WoEh6YwQGPVhDVqvrVQ==",
|
||||
"requires": {
|
||||
"fs-extra": "^8.0.1",
|
||||
"jsonfile": "^5.0.0",
|
||||
"twemoji-parser": "12.1.3",
|
||||
"universalify": "^0.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"jsonfile": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-5.0.0.tgz",
|
||||
"integrity": "sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6",
|
||||
"universalify": "^0.1.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"twemoji-parser": {
|
||||
"version": "12.1.3",
|
||||
"resolved": "https://registry.npmjs.org/twemoji-parser/-/twemoji-parser-12.1.3.tgz",
|
||||
"integrity": "sha512-ND4LZXF4X92/PFrzSgGkq6KPPg8swy/U0yRw1k/+izWRVmq1HYi3khPwV3XIB6FRudgVICAaBhJfW8e8G3HC7Q=="
|
||||
},
|
||||
"type-check": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
|
||||
@ -22026,8 +22100,7 @@
|
||||
"universalify": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
|
||||
},
|
||||
"unpipe": {
|
||||
"version": "1.0.0",
|
||||
|
@ -802,6 +802,91 @@
|
||||
expect(view.model.messages.at(0).get('editable')).toBe(false);
|
||||
done();
|
||||
}));
|
||||
|
||||
it("can be retracted by the sender if they're a moderator",
|
||||
mock.initConverse(
|
||||
['rosterGroupsFetched', 'chatBoxesFetched'], {'allow_message_retraction': 'moderator'},
|
||||
async function (done, _converse) {
|
||||
|
||||
const muc_jid = 'lounge@montague.lit';
|
||||
const features = [...mock.default_muc_features, Strophe.NS.MODERATE];
|
||||
await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo', features);
|
||||
const view = _converse.api.chatviews.get(muc_jid);
|
||||
const occupant = view.model.getOwnOccupant();
|
||||
expect(occupant.get('role')).toBe('moderator');
|
||||
|
||||
view.model.sendMessage('Visit this site to get free bitcoin');
|
||||
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 1);
|
||||
const stanza_id = 'retraction-id-1';
|
||||
const msg_obj = view.model.messages.at(0);
|
||||
const reflection_stanza = u.toStanza(`
|
||||
<message xmlns="jabber:client"
|
||||
from="${msg_obj.get('from')}"
|
||||
to="${_converse.connection.jid}"
|
||||
type="groupchat">
|
||||
<msg_body>${msg_obj.get('message')}</msg_body>
|
||||
<stanza-id xmlns="urn:xmpp:sid:0"
|
||||
id="${stanza_id}"
|
||||
by="lounge@montague.lit"/>
|
||||
<origin-id xmlns="urn:xmpp:sid:0" id="${msg_obj.get('origin_id')}"/>
|
||||
</message>`);
|
||||
await view.model.queueMessage(reflection_stanza);
|
||||
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__body.chat-msg__body--received').length, 500);
|
||||
expect(view.model.messages.length).toBe(1);
|
||||
expect(view.model.messages.at(0).get('editable')).toBe(true);
|
||||
|
||||
const retract_button = await u.waitUntil(() => view.msgs_container.querySelector('.chat-msg__content .chat-msg__action-retract'));
|
||||
retract_button.click();
|
||||
await u.waitUntil(() => u.isVisible(document.querySelector('#converse-modals .modal')));
|
||||
const submit_button = document.querySelector('#converse-modals .modal button[type="submit"]');
|
||||
submit_button.click();
|
||||
|
||||
const sent_IQs = _converse.connection.IQ_stanzas;
|
||||
const stanza = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector('iq apply-to[xmlns="urn:xmpp:fasten:0"]')).pop());
|
||||
|
||||
expect(Strophe.serialize(stanza)).toBe(
|
||||
`<iq id="${stanza.getAttribute('id')}" to="${muc_jid}" type="set" xmlns="jabber:client">`+
|
||||
`<apply-to id="${stanza_id}" xmlns="urn:xmpp:fasten:0">`+
|
||||
`<moderate xmlns="urn:xmpp:message-moderate:0">`+
|
||||
`<retract xmlns="urn:xmpp:message-retract:0"/>`+
|
||||
`<reason></reason>`+
|
||||
`</moderate>`+
|
||||
`</apply-to>`+
|
||||
`</iq>`);
|
||||
|
||||
const result_iq = $iq({'from': muc_jid, 'id': stanza.getAttribute('id'), 'to': _converse.bare_jid, 'type': 'result'});
|
||||
_converse.connection._dataRecv(test_utils.createRequest(result_iq));
|
||||
|
||||
// We opportunistically save the message as retracted, even before receiving the retraction message
|
||||
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg--retracted').length === 1);
|
||||
expect(view.model.messages.length).toBe(1);
|
||||
expect(view.model.messages.at(0).get('moderated')).toBe('retracted');
|
||||
expect(view.model.messages.at(0).get('moderation_reason')).toBe(undefined);
|
||||
expect(view.model.messages.at(0).get('is_ephemeral')).toBe(false);
|
||||
expect(view.model.messages.at(0).get('editable')).toBe(false);
|
||||
expect(view.el.querySelectorAll('.chat-msg--retracted').length).toBe(1);
|
||||
|
||||
const msg_el = view.el.querySelector('.chat-msg--retracted .chat-msg__message');
|
||||
expect(msg_el.firstElementChild.textContent.trim()).toBe('romeo has removed this message');
|
||||
expect(msg_el.querySelector('q')).toBe(null);
|
||||
|
||||
// The server responds with a retraction message
|
||||
const retraction = u.toStanza(`
|
||||
<message type="groupchat" id='retraction-id-1' from="${muc_jid}" to="${muc_jid}/romeo">
|
||||
<apply-to id="${stanza_id}" xmlns="urn:xmpp:fasten:0">
|
||||
<moderated by='${_converse.bare_jid}' xmlns='urn:xmpp:message-moderate:0'>
|
||||
<retract xmlns='urn:xmpp:message-retract:0' />
|
||||
</moderated>
|
||||
</apply-to>
|
||||
</message>`);
|
||||
await view.model.queueMessage(retraction);
|
||||
expect(view.model.messages.length).toBe(1);
|
||||
expect(view.model.messages.at(0).get('moderated')).toBe('retracted');
|
||||
expect(view.model.messages.at(0).get('moderation_reason')).toBe(undefined);
|
||||
expect(view.model.messages.at(0).get('is_ephemeral')).toBe(false);
|
||||
expect(view.model.messages.at(0).get('editable')).toBe(false);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
|
@ -230,17 +230,8 @@ converse.plugins.add('converse-message-view', {
|
||||
const role = this.model.vcard ? this.model.vcard.get('role') : null;
|
||||
const roles = role ? role.split(',') : [];
|
||||
const is_retracted = this.model.get('retracted') || this.model.get('moderated') === 'retracted';
|
||||
const is_groupchat = this.model.get('type') === 'groupchat';
|
||||
const is_own_message = this.model.get('sender') === 'me';
|
||||
const chatbox = this.model.collection.chatbox;
|
||||
const may_retract_own_message = is_own_message && (
|
||||
['all', 'own'].includes(_converse.allow_message_retraction) || await chatbox.canModerateMessages()
|
||||
);
|
||||
const may_moderate_message = !is_own_message && is_groupchat &&
|
||||
['all', 'moderator'].includes(_converse.allow_message_retraction) &&
|
||||
await chatbox.canModerateMessages();
|
||||
|
||||
const retractable= !is_retracted && (may_moderate_message || may_retract_own_message);
|
||||
const may_be_moderated = this.model.get('type') === 'groupchat' && await this.model.mayBeModerated();
|
||||
const retractable= !is_retracted && (this.model.mayBeRetracted() || may_be_moderated);
|
||||
const msg = u.stringToElement(tpl_message(
|
||||
Object.assign(
|
||||
this.model.toJSON(), {
|
||||
@ -248,7 +239,7 @@ converse.plugins.add('converse-message-view', {
|
||||
is_retracted,
|
||||
retractable,
|
||||
'extra_classes': this.getExtraMessageClasses(),
|
||||
'is_groupchat_message': is_groupchat,
|
||||
'is_groupchat_message': this.model.get('type') === 'groupchat',
|
||||
'is_me_message': this.model.isMeCommand(),
|
||||
'label_show': __('Show more'),
|
||||
'occupant': this.model.occupant,
|
||||
|
@ -985,7 +985,7 @@ converse.plugins.add('converse-muc-views', {
|
||||
"not yet support retractions and that this message may not "+
|
||||
"be removed everywhere.");
|
||||
|
||||
if (message.get('sender') === 'me') {
|
||||
if (message.mayBeRetracted()) {
|
||||
const messages = [__('Are you sure you want to retract this message?')];
|
||||
if (_converse.show_retraction_warning) {
|
||||
messages[1] = retraction_warning;
|
||||
@ -994,6 +994,15 @@ converse.plugins.add('converse-muc-views', {
|
||||
if (result) {
|
||||
this.retractOwnMessage(message);
|
||||
}
|
||||
} else if (await message.mayBeModerated()) {
|
||||
if (message.get('sender') === 'me') {
|
||||
let messages = [__('Are you sure you want to retract this message?')];
|
||||
if (_converse.show_retraction_warning) {
|
||||
messages = [messages[0], retraction_warning, messages[1]]
|
||||
}
|
||||
if (await _converse.api.confirm(__('Confirm'), messages)) {
|
||||
this.retractOtherMessage(message);
|
||||
}
|
||||
} else {
|
||||
let messages = [
|
||||
__('You are about to retract this message.'),
|
||||
@ -1011,6 +1020,10 @@ converse.plugins.add('converse-muc-views', {
|
||||
this.retractOtherMessage(message, reason);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const err_msg = __(`Sorry, you're not allowed to retract this message`);
|
||||
_converse.api.alert('error', __('Error'), err_msg);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -142,6 +142,17 @@ converse.plugins.add('converse-chat', {
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines whether this messsage may be retracted by the current user.
|
||||
* @private
|
||||
* @method _converse.Messages#mayBeRetracted
|
||||
* @returns { Boolean }
|
||||
*/
|
||||
mayBeRetracted () {
|
||||
const is_own_message = this.get('sender') === 'me';
|
||||
return is_own_message && ['all', 'own'].includes(_converse.allow_message_retraction);
|
||||
},
|
||||
|
||||
safeDestroy () {
|
||||
try {
|
||||
this.destroy()
|
||||
|
@ -250,6 +250,19 @@ converse.plugins.add('converse-muc', {
|
||||
_converse.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(_converse.allow_message_retraction) &&
|
||||
this.collection.chatbox.canModerateMessages();
|
||||
},
|
||||
|
||||
checkValidity () {
|
||||
const result = _converse.Message.prototype.checkValidity.call(this);
|
||||
!result && this.collection.chatbox.debouncedRejoin();
|
||||
@ -757,7 +770,7 @@ converse.plugins.add('converse-muc', {
|
||||
'xmlns': Strophe.NS.FASTEN
|
||||
}).c('moderate', {xmlns: Strophe.NS.MODERATE})
|
||||
.c('retract', {xmlns: Strophe.NS.RETRACT}).up()
|
||||
.c('reason').t(reason);
|
||||
.c('reason').t(reason || '');
|
||||
return _converse.api.sendIQ(iq, null, false);
|
||||
},
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user