2024-09-11 17:21:57 +00:00
|
|
|
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
|
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
2024-09-12 09:17:44 +00:00
|
|
|
interface Current {
|
|
|
|
announcementType: string | undefined
|
|
|
|
}
|
|
|
|
|
2024-09-11 17:21:57 +00:00
|
|
|
/**
|
|
|
|
* livechat announcements ConverseJS plugin:
|
|
|
|
* with this plugin, moderators can send highlighted/announcements messages.
|
|
|
|
*
|
|
|
|
* Moderators will have a special select field in the chat toolbar, so that they can choose a messaging style.
|
|
|
|
* These special messages will have a first line with a generated title (for XMPP compatibility).
|
|
|
|
* They will also have a special attribute on the body tag.
|
|
|
|
* This attribute will be used to apply some CSS with this plugin.
|
|
|
|
*/
|
|
|
|
export const livechatAnnouncementsPlugin = {
|
|
|
|
dependencies: ['converse-muc', 'converse-muc-views'],
|
|
|
|
initialize: function (this: any) {
|
|
|
|
const _converse = this._converse
|
|
|
|
|
|
|
|
// This is a closure variable, to get the current form status when sending a message.
|
2024-09-12 09:17:44 +00:00
|
|
|
const current: Current = {
|
|
|
|
announcementType: undefined
|
2024-09-11 17:21:57 +00:00
|
|
|
}
|
|
|
|
|
2024-09-12 09:17:44 +00:00
|
|
|
overrideMUCMessageForm(_converse, current)
|
|
|
|
|
2024-09-11 17:21:57 +00:00
|
|
|
_converse.api.listen.on('getToolbarButtons', getToolbarButtons.bind(this))
|
|
|
|
|
2024-09-12 09:17:44 +00:00
|
|
|
_converse.api.listen.on('chatRoomInitialized', (muc: any) => onAffiliationChange(_converse, muc))
|
2024-09-11 17:21:57 +00:00
|
|
|
|
|
|
|
_converse.api.listen.on('getOutgoingMessageAttributes', (chatbox: any, attrs: any) => {
|
2024-09-12 09:17:44 +00:00
|
|
|
return onGetOutgoingMessageAttributes(current, _converse, chatbox, attrs)
|
2024-09-11 17:21:57 +00:00
|
|
|
})
|
|
|
|
|
2024-09-12 09:17:44 +00:00
|
|
|
_converse.api.listen.on('createMessageStanza', createMessageStanza)
|
2024-09-11 17:21:57 +00:00
|
|
|
|
2024-09-12 09:17:44 +00:00
|
|
|
_converse.api.listen.on('parseMUCMessage', parseMUCMessage)
|
2024-09-11 17:21:57 +00:00
|
|
|
|
2024-09-12 09:17:44 +00:00
|
|
|
overrideMessage(_converse)
|
|
|
|
}
|
|
|
|
}
|
2024-09-11 17:21:57 +00:00
|
|
|
|
2024-09-12 09:17:44 +00:00
|
|
|
/**
|
|
|
|
* Overloads the MUCMessageForm to handle the announcement type field (if present) when sending a message.
|
2024-09-12 10:07:55 +00:00
|
|
|
*
|
|
|
|
* Also hides the announcement type field if we are correcting a previous message.
|
2024-09-12 09:17:44 +00:00
|
|
|
*/
|
|
|
|
function overrideMUCMessageForm (_converse: any, current: Current): void {
|
|
|
|
const MUCMessageForm = _converse.api.elements.registry['converse-muc-message-form']
|
|
|
|
if (MUCMessageForm) {
|
|
|
|
class MUCMessageFormloaded extends MUCMessageForm {
|
|
|
|
async onFormSubmitted (ev?: Event): Promise<void> {
|
|
|
|
const announcementSelect = this.querySelector('[name=livechat-announcements]')
|
|
|
|
current.announcementType = announcementSelect?.selectedOptions?.[0]?.value || undefined
|
|
|
|
try {
|
|
|
|
await super.onFormSubmitted(ev)
|
|
|
|
if (announcementSelect) { announcementSelect.selectedIndex = 0 } // set back to default
|
|
|
|
} catch (err) {
|
|
|
|
console.log(err)
|
2024-09-11 17:21:57 +00:00
|
|
|
}
|
2024-09-12 09:17:44 +00:00
|
|
|
current.announcementType = undefined
|
2024-09-11 17:21:57 +00:00
|
|
|
}
|
2024-09-12 10:07:55 +00:00
|
|
|
|
|
|
|
insertIntoTextArea (...args: any[]): void {
|
|
|
|
super.insertIntoTextArea(...args)
|
|
|
|
try {
|
|
|
|
// FIXME: doing this here is not very clean.
|
|
|
|
// But that's how ConverseJS adds or removes the 'correction' class to the textarea.
|
|
|
|
const textarea = this.querySelector('.chat-textarea')
|
|
|
|
if (!textarea) { return }
|
|
|
|
const correcting = window.converse.env.u.hasClass('correcting', textarea)
|
|
|
|
const announcementForm = this.querySelector('.livechat-announcements-form')
|
|
|
|
const announcementSelect = this.querySelector('[name=livechat-announcements]')
|
|
|
|
if (correcting) {
|
|
|
|
if (announcementSelect) { announcementSelect.selectedIndex = 0 }
|
|
|
|
if (announcementForm) { announcementForm.style.display = 'none' }
|
|
|
|
} else {
|
|
|
|
if (announcementForm) { announcementForm.style.display = 'block' }
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
console.error(err)
|
|
|
|
}
|
|
|
|
}
|
2024-09-11 17:21:57 +00:00
|
|
|
}
|
2024-09-12 09:17:44 +00:00
|
|
|
_converse.api.elements.define('converse-muc-message-form', MUCMessageFormloaded)
|
2024-09-11 17:21:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-12 09:17:44 +00:00
|
|
|
/**
|
|
|
|
* Adds the announcement selector in the toolbar for owner/admin.
|
|
|
|
* @param this the plugin
|
|
|
|
* @param toolbarEl the toolbar element
|
|
|
|
* @param buttons the button list
|
|
|
|
* @returns the updated "button" list
|
|
|
|
*/
|
2024-09-11 17:21:57 +00:00
|
|
|
function getToolbarButtons (this: any, toolbarEl: any, buttons: any[]): Parameters<typeof getToolbarButtons>[1] {
|
|
|
|
const _converse = this._converse
|
|
|
|
const mucModel = toolbarEl.model
|
|
|
|
if (!toolbarEl.is_groupchat) {
|
|
|
|
return buttons
|
|
|
|
}
|
|
|
|
const myself = mucModel.getOwnOccupant()
|
|
|
|
if (!myself || !['admin', 'owner'].includes(myself.get('affiliation') as string)) {
|
|
|
|
return buttons
|
|
|
|
}
|
|
|
|
|
|
|
|
const { __ } = _converse
|
|
|
|
const { html } = window.converse.env
|
|
|
|
|
2024-09-12 09:17:44 +00:00
|
|
|
const i18n = __(LOC_ANNOUNCEMENTS_MESSAGE_TYPE)
|
2024-09-11 17:21:57 +00:00
|
|
|
const i18nStandard = __(LOC_ANNOUNCEMENTS_MESSAGE_TYPE_STANDARD)
|
|
|
|
const i18nAnnouncement = __(LOC_ANNOUNCEMENTS_MESSAGE_TYPE_ANNOUNCEMENT)
|
|
|
|
const i18nHighlight = __(LOC_ANNOUNCEMENTS_MESSAGE_TYPE_HIGHLIGHT)
|
2024-09-12 09:17:44 +00:00
|
|
|
const i18nWarning = __(LOC_ANNOUNCEMENTS_MESSAGE_TYPE_WARNING)
|
|
|
|
|
|
|
|
const select = html`<span class="livechat-announcements-form form-inline">
|
|
|
|
<label for="livechat-announcements-select">${i18n}</label>
|
|
|
|
<select
|
|
|
|
name="livechat-announcements"
|
|
|
|
id="livechat-announcements-select"
|
|
|
|
class="form-control form-control-sm"
|
|
|
|
title=${i18n}
|
|
|
|
>
|
|
|
|
<option value="">${i18nStandard}</option>
|
|
|
|
<option value="highlight">${i18nHighlight}</option>
|
|
|
|
<option value="announcement">${i18nAnnouncement}</option>
|
|
|
|
<option value="warning">${i18nWarning}</option>
|
|
|
|
</select>
|
|
|
|
</span>`
|
2024-09-11 17:21:57 +00:00
|
|
|
|
|
|
|
if (_converse.api.settings.get('visible_toolbar_buttons').emoji) {
|
|
|
|
// Emojis should be the first entry, so adding select in second place.
|
|
|
|
buttons = [
|
|
|
|
buttons.shift(),
|
|
|
|
select,
|
|
|
|
...buttons
|
|
|
|
]
|
|
|
|
} else {
|
|
|
|
// Adding the select in first place.
|
|
|
|
buttons.unshift(select)
|
|
|
|
}
|
|
|
|
return buttons
|
|
|
|
}
|
2024-09-12 09:17:44 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Refreshed the toolbar when current user affiliation changes.
|
|
|
|
* @param _converse _converse object
|
|
|
|
* @param muc the current muc
|
|
|
|
*/
|
|
|
|
function onAffiliationChange (_converse: any, muc: any): void {
|
|
|
|
muc.occupants.on('change:affiliation', (occupant: any) => {
|
|
|
|
if (occupant.get('jid') !== _converse.bare_jid) { // only for myself
|
|
|
|
return
|
|
|
|
}
|
|
|
|
document.querySelectorAll('converse-chat-toolbar').forEach(e => (e as any).requestUpdate?.())
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* For outgoing message, adding the announcement type if there is a current one.
|
|
|
|
* @param current current object
|
|
|
|
* @param _converse _converse object
|
|
|
|
* @param chatbox the chatbox
|
|
|
|
* @param attrs message attributes
|
|
|
|
* @returns
|
|
|
|
*/
|
|
|
|
function onGetOutgoingMessageAttributes (
|
|
|
|
current: Current,
|
|
|
|
_converse: any,
|
|
|
|
chatbox: any,
|
|
|
|
attrs: any
|
|
|
|
): Parameters<typeof onGetOutgoingMessageAttributes>[3] {
|
|
|
|
if (!current.announcementType) { return attrs }
|
|
|
|
|
|
|
|
const { __ } = _converse
|
|
|
|
attrs.livechat_announcement_type = current.announcementType
|
|
|
|
if (current.announcementType === 'announcement') {
|
|
|
|
attrs.body = '* ' + __(LOC_ANNOUNCEMENTS_MESSAGE_TYPE_ANNOUNCEMENT) + ' * \n' + attrs.body
|
|
|
|
} else if (current.announcementType === 'warning') {
|
|
|
|
attrs.body = '* ' + __(LOC_ANNOUNCEMENTS_MESSAGE_TYPE_WARNING) + ' *\n' + attrs.body
|
|
|
|
}
|
|
|
|
return attrs
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Outgoing messages: adding an attribute on body for announcements.
|
|
|
|
* @param chat
|
|
|
|
* @param data
|
|
|
|
*/
|
|
|
|
async function createMessageStanza (
|
|
|
|
chat: any,
|
|
|
|
data: any
|
|
|
|
): Promise<Parameters<typeof createMessageStanza>[1]> {
|
|
|
|
const { message, stanza } = data
|
|
|
|
const announcementType = message.get('livechat_announcement_type')
|
|
|
|
if (!announcementType) {
|
|
|
|
return data
|
|
|
|
}
|
|
|
|
|
|
|
|
stanza.tree().querySelector('message body')?.setAttribute('x-livechat-announcement-type', announcementType)
|
|
|
|
return data
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Incoming messages: checking if there is an announcement attribute, and adding it in computed attributes.
|
|
|
|
* @param stanza
|
|
|
|
* @param attrs
|
|
|
|
*/
|
|
|
|
function parseMUCMessage (stanza: any, attrs: any): Parameters<typeof parseMUCMessage>[1] {
|
|
|
|
const { sizzle } = window.converse.env
|
|
|
|
const body = sizzle('message body', stanza)?.[0]
|
|
|
|
if (!body) { return attrs }
|
|
|
|
|
|
|
|
const announcementType = body.getAttribute('x-livechat-announcement-type')
|
|
|
|
if (!announcementType) { return attrs }
|
|
|
|
|
|
|
|
// Note: we don't check the value here. Will be done in getExtraMessageClasses.
|
|
|
|
// Moreover, the backend server will ensure that only admins/owners can send this attribute.
|
|
|
|
attrs.livechat_announcement_type = announcementType
|
|
|
|
return attrs
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Overloading the Message class to add CSS for announcements.
|
|
|
|
* @param _converse
|
|
|
|
*/
|
|
|
|
function overrideMessage (_converse: any): void {
|
|
|
|
const Message = _converse.api.elements.registry['converse-chat-message']
|
|
|
|
if (Message) {
|
|
|
|
class MessageOverloaded extends Message {
|
|
|
|
getExtraMessageClasses (this: any): string {
|
|
|
|
// Adding CSS class if the message is an announcement.
|
|
|
|
let extraClasses = super.getExtraMessageClasses() ?? ''
|
|
|
|
const announcementType: string | undefined = this.model.get('livechat_announcement_type')
|
|
|
|
if (!announcementType) {
|
|
|
|
return extraClasses
|
|
|
|
}
|
|
|
|
if (['announcement', 'highlight', 'warning'].includes(announcementType)) {
|
|
|
|
extraClasses += ' livechat-' + announcementType
|
|
|
|
}
|
|
|
|
return extraClasses
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_converse.api.elements.define('converse-chat-message', MessageOverloaded)
|
|
|
|
}
|
|
|
|
}
|