New features: announcements WIP (#518):
* Front-end implementation finished. * Refactoring.
This commit is contained in:
parent
8944bb95d8
commit
d92bf9073e
@ -13,13 +13,6 @@
|
|||||||
--livechat-announcement-color: #000;
|
--livechat-announcement-color: #000;
|
||||||
--livechat-announcement-background-color: #dbf2d8;
|
--livechat-announcement-background-color: #dbf2d8;
|
||||||
--livechat-announcement-border-color: #2ab218;
|
--livechat-announcement-border-color: #2ab218;
|
||||||
|
|
||||||
converse-chat-message-body::first-line {
|
|
||||||
// Different color for the title line
|
|
||||||
color: #FFF;
|
|
||||||
background-color: #2ab218;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.livechat-highlight {
|
&.livechat-highlight {
|
||||||
@ -28,15 +21,39 @@
|
|||||||
--livechat-announcement-border-color: #3075e5;
|
--livechat-announcement-border-color: #3075e5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.livechat-warning {
|
||||||
|
--livechat-announcement-color: #000;
|
||||||
|
--livechat-announcement-background-color: #fadede;
|
||||||
|
--livechat-announcement-border-color: #e03e3e;
|
||||||
|
}
|
||||||
|
|
||||||
&.livechat-announcement,
|
&.livechat-announcement,
|
||||||
&.livechat-highlight {
|
&.livechat-highlight,
|
||||||
|
&.livechat-warning {
|
||||||
converse-chat-message-body {
|
converse-chat-message-body {
|
||||||
border: 2px solid var(--livechat-announcement-border-color);
|
border: 2px solid var(--livechat-announcement-border-color);
|
||||||
color: var(--livechat-announcement-color);
|
color: var(--livechat-announcement-color);
|
||||||
background-color: var(--livechat-announcement-background-color);
|
background-color: var(--livechat-announcement-background-color);
|
||||||
min-width: 50%;
|
min-width: 50%;
|
||||||
padding: 2em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.livechat-announcements-form {
|
||||||
|
label {
|
||||||
|
// only for screen readers
|
||||||
|
border: 0 !important;
|
||||||
|
clip: rect(1px, 1px, 1px, 1px) !important;
|
||||||
|
/* stylelint-disable-next-line property-no-vendor-prefix */
|
||||||
|
-webkit-clip-path: inset(50%) !important;
|
||||||
|
clip-path: inset(50%) !important;
|
||||||
|
height: 1px !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
position: absolute !important;
|
||||||
|
width: 1px !important;
|
||||||
|
white-space: nowrap !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
2
conversejs/lib/@types/global.d.ts
vendored
2
conversejs/lib/@types/global.d.ts
vendored
@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
// Important note: loc segments that are declared here must also be in loc.keys.js (for now).
|
// Important note: loc segments that are declared here must also be in loc.keys.js (for now).
|
||||||
|
|
||||||
|
declare const LOC_ANNOUNCEMENTS_MESSAGE_TYPE: string
|
||||||
declare const LOC_ANNOUNCEMENTS_MESSAGE_TYPE_STANDARD: string
|
declare const LOC_ANNOUNCEMENTS_MESSAGE_TYPE_STANDARD: string
|
||||||
declare const LOC_ANNOUNCEMENTS_MESSAGE_TYPE_ANNOUNCEMENT: string
|
declare const LOC_ANNOUNCEMENTS_MESSAGE_TYPE_ANNOUNCEMENT: string
|
||||||
declare const LOC_ANNOUNCEMENTS_MESSAGE_TYPE_HIGHLIGHT: string
|
declare const LOC_ANNOUNCEMENTS_MESSAGE_TYPE_HIGHLIGHT: string
|
||||||
|
declare const LOC_ANNOUNCEMENTS_MESSAGE_TYPE_WARNING: string
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
interface Current {
|
||||||
|
announcementType: string | undefined
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* livechat announcements ConverseJS plugin:
|
* livechat announcements ConverseJS plugin:
|
||||||
* with this plugin, moderators can send highlighted/announcements messages.
|
* with this plugin, moderators can send highlighted/announcements messages.
|
||||||
@ -15,108 +19,60 @@ export const livechatAnnouncementsPlugin = {
|
|||||||
dependencies: ['converse-muc', 'converse-muc-views'],
|
dependencies: ['converse-muc', 'converse-muc-views'],
|
||||||
initialize: function (this: any) {
|
initialize: function (this: any) {
|
||||||
const _converse = this._converse
|
const _converse = this._converse
|
||||||
const { __ } = _converse
|
|
||||||
|
|
||||||
// This is a closure variable, to get the current form status when sending a message.
|
// This is a closure variable, to get the current form status when sending a message.
|
||||||
let currentAnnouncementType: string | undefined
|
const current: Current = {
|
||||||
|
announcementType: undefined
|
||||||
// Overloading the MUCMessageForm to handle the announcement type field (if present).
|
|
||||||
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]')
|
|
||||||
currentAnnouncementType = announcementSelect?.selectedOptions?.[0]?.value || undefined
|
|
||||||
try {
|
|
||||||
await super.onFormSubmitted(ev)
|
|
||||||
if (announcementSelect) { announcementSelect.selectedIndex = 0 } // set back to default
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err)
|
|
||||||
}
|
|
||||||
currentAnnouncementType = undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_converse.api.elements.define('converse-muc-message-form', MUCMessageFormloaded)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toolbar: adding the announcement type field (if user has rights).
|
overrideMUCMessageForm(_converse, current)
|
||||||
|
|
||||||
_converse.api.listen.on('getToolbarButtons', getToolbarButtons.bind(this))
|
_converse.api.listen.on('getToolbarButtons', getToolbarButtons.bind(this))
|
||||||
|
|
||||||
// When current user affiliation changes, we must refresh the toolbar.
|
_converse.api.listen.on('chatRoomInitialized', (muc: any) => onAffiliationChange(_converse, muc))
|
||||||
_converse.api.listen.on('chatRoomInitialized', (muc: any) => {
|
|
||||||
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?.())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
_converse.api.listen.on('getOutgoingMessageAttributes', (chatbox: any, attrs: any) => {
|
_converse.api.listen.on('getOutgoingMessageAttributes', (chatbox: any, attrs: any) => {
|
||||||
// For outgoing message, adding the announcement type if there is a current one.
|
return onGetOutgoingMessageAttributes(current, _converse, chatbox, attrs)
|
||||||
if (!currentAnnouncementType) { return attrs }
|
|
||||||
|
|
||||||
attrs.livechat_announcement_type = currentAnnouncementType
|
|
||||||
if (currentAnnouncementType === 'announcement') {
|
|
||||||
attrs.body = '* ' + __(LOC_ANNOUNCEMENTS_MESSAGE_TYPE_ANNOUNCEMENT) + ' * \n' + attrs.body
|
|
||||||
}
|
|
||||||
return attrs
|
|
||||||
})
|
})
|
||||||
|
|
||||||
_converse.api.listen.on('createMessageStanza', async (chat: any, data: any) => {
|
_converse.api.listen.on('createMessageStanza', createMessageStanza)
|
||||||
// Outgoing messages: adding an attribute on body for announcements.
|
|
||||||
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)
|
_converse.api.listen.on('parseMUCMessage', parseMUCMessage)
|
||||||
return data
|
|
||||||
})
|
|
||||||
|
|
||||||
_converse.api.listen.on('parseMUCMessage', (stanza: any, attrs: any) => {
|
overrideMessage(_converse)
|
||||||
// Incoming messages: checking if there is an announcement attribute
|
|
||||||
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.
|
|
||||||
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 = this.model.get('livechat_announcement_type')
|
|
||||||
if (!announcementType) {
|
|
||||||
return extraClasses
|
|
||||||
}
|
|
||||||
switch (announcementType) {
|
|
||||||
case 'announcement':
|
|
||||||
extraClasses += ' livechat-announcement'
|
|
||||||
break
|
|
||||||
case 'highlight':
|
|
||||||
extraClasses += ' livechat-highlight'
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return extraClasses
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_converse.api.elements.define('converse-chat-message', MessageOverloaded)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overloads the MUCMessageForm to handle the announcement type field (if present) when sending a message.
|
||||||
|
*/
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
current.announcementType = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_converse.api.elements.define('converse-muc-message-form', MUCMessageFormloaded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
function getToolbarButtons (this: any, toolbarEl: any, buttons: any[]): Parameters<typeof getToolbarButtons>[1] {
|
function getToolbarButtons (this: any, toolbarEl: any, buttons: any[]): Parameters<typeof getToolbarButtons>[1] {
|
||||||
const _converse = this._converse
|
const _converse = this._converse
|
||||||
const mucModel = toolbarEl.model
|
const mucModel = toolbarEl.model
|
||||||
@ -131,15 +87,26 @@ function getToolbarButtons (this: any, toolbarEl: any, buttons: any[]): Paramete
|
|||||||
const { __ } = _converse
|
const { __ } = _converse
|
||||||
const { html } = window.converse.env
|
const { html } = window.converse.env
|
||||||
|
|
||||||
|
const i18n = __(LOC_ANNOUNCEMENTS_MESSAGE_TYPE)
|
||||||
const i18nStandard = __(LOC_ANNOUNCEMENTS_MESSAGE_TYPE_STANDARD)
|
const i18nStandard = __(LOC_ANNOUNCEMENTS_MESSAGE_TYPE_STANDARD)
|
||||||
const i18nAnnouncement = __(LOC_ANNOUNCEMENTS_MESSAGE_TYPE_ANNOUNCEMENT)
|
const i18nAnnouncement = __(LOC_ANNOUNCEMENTS_MESSAGE_TYPE_ANNOUNCEMENT)
|
||||||
const i18nHighlight = __(LOC_ANNOUNCEMENTS_MESSAGE_TYPE_HIGHLIGHT)
|
const i18nHighlight = __(LOC_ANNOUNCEMENTS_MESSAGE_TYPE_HIGHLIGHT)
|
||||||
|
const i18nWarning = __(LOC_ANNOUNCEMENTS_MESSAGE_TYPE_WARNING)
|
||||||
|
|
||||||
const select = html`<select name="livechat-announcements">
|
const select = html`<span class="livechat-announcements-form form-inline">
|
||||||
<option value="">${i18nStandard}</option>
|
<label for="livechat-announcements-select">${i18n}</label>
|
||||||
<option value="announcement">${i18nAnnouncement}</option>
|
<select
|
||||||
<option value="highlight">${i18nHighlight}</option>
|
name="livechat-announcements"
|
||||||
</select>`
|
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>`
|
||||||
|
|
||||||
if (_converse.api.settings.get('visible_toolbar_buttons').emoji) {
|
if (_converse.api.settings.get('visible_toolbar_buttons').emoji) {
|
||||||
// Emojis should be the first entry, so adding select in second place.
|
// Emojis should be the first entry, so adding select in second place.
|
||||||
@ -154,3 +121,106 @@ function getToolbarButtons (this: any, toolbarEl: any, buttons: any[]): Paramete
|
|||||||
}
|
}
|
||||||
return buttons
|
return buttons
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -64,9 +64,11 @@ const locKeys = [
|
|||||||
'message_search',
|
'message_search',
|
||||||
'message_search_original_nick',
|
'message_search_original_nick',
|
||||||
'emoji_only_info',
|
'emoji_only_info',
|
||||||
|
'announcements_message_type',
|
||||||
'announcements_message_type_standard',
|
'announcements_message_type_standard',
|
||||||
'announcements_message_type_announcement',
|
'announcements_message_type_announcement',
|
||||||
'announcements_message_type_highlight'
|
'announcements_message_type_highlight',
|
||||||
|
'announcements_message_type_warning'
|
||||||
]
|
]
|
||||||
|
|
||||||
module.exports = locKeys
|
module.exports = locKeys
|
||||||
|
@ -671,6 +671,8 @@ livechat_configuration_channel_no_duplicate_delay_label: Time interval
|
|||||||
livechat_configuration_channel_no_duplicate_delay_desc: |
|
livechat_configuration_channel_no_duplicate_delay_desc: |
|
||||||
The interval, in seconds, during which a user can't send again the same message.
|
The interval, in seconds, during which a user can't send again the same message.
|
||||||
|
|
||||||
announcements_message_type_standard: Standard message
|
announcements_message_type: Message type
|
||||||
|
announcements_message_type_standard: Standard
|
||||||
announcements_message_type_announcement: Announcement
|
announcements_message_type_announcement: Announcement
|
||||||
announcements_message_type_highlight: Highlighted message
|
announcements_message_type_highlight: Highlight
|
||||||
|
announcements_message_type_warning: Warning
|
||||||
|
Loading…
Reference in New Issue
Block a user