New features: announcements WIP (#518).
This commit is contained in:
parent
b357619f7a
commit
8944bb95d8
@ -3,7 +3,7 @@
|
|||||||
## 12.0.0 (Not Released Yet)
|
## 12.0.0 (Not Released Yet)
|
||||||
|
|
||||||
TODO Before releasing:
|
TODO Before releasing:
|
||||||
* update ConverseJS with latest merges.
|
* update ConverseJS with latest merges (there are currently some known bugs).
|
||||||
|
|
||||||
### Importante Notes
|
### Importante Notes
|
||||||
|
|
||||||
@ -15,6 +15,7 @@ It also requires NodeJS 16 or superior (same as Peertube 5.2.0.)
|
|||||||
* #131: Emoji only mode.
|
* #131: Emoji only mode.
|
||||||
* #516: new option for the moderation bot: forbid duplicate messages.
|
* #516: new option for the moderation bot: forbid duplicate messages.
|
||||||
* #517: new option for the moderation bot: forbid messages with too many special characters.
|
* #517: new option for the moderation bot: forbid messages with too many special characters.
|
||||||
|
* #518: moderators can send announcements and highlighted messages.
|
||||||
|
|
||||||
### Minor changes and fixes
|
### Minor changes and fixes
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ const clientFiles = [
|
|||||||
'admin-plugin-client-plugin'
|
'admin-plugin-client-plugin'
|
||||||
]
|
]
|
||||||
|
|
||||||
function loadLocs() {
|
function loadLocs(globalFile) {
|
||||||
// Loading english strings, so we can inject them as constants.
|
// Loading english strings, so we can inject them as constants.
|
||||||
const refFile = path.resolve(__dirname, 'dist', 'languages', 'en.reference.json')
|
const refFile = path.resolve(__dirname, 'dist', 'languages', 'en.reference.json')
|
||||||
if (!fs.existsSync(refFile)) {
|
if (!fs.existsSync(refFile)) {
|
||||||
@ -25,7 +25,6 @@ function loadLocs() {
|
|||||||
|
|
||||||
// Reading client/@types/global.d.ts, to have a list of needed localized strings.
|
// Reading client/@types/global.d.ts, to have a list of needed localized strings.
|
||||||
const r = {}
|
const r = {}
|
||||||
const globalFile = path.resolve(__dirname, 'client', '@types', 'global.d.ts')
|
|
||||||
const globalFileContent = '' + fs.readFileSync(globalFile)
|
const globalFileContent = '' + fs.readFileSync(globalFile)
|
||||||
const matches = globalFileContent.matchAll(/^declare const LOC_(\w+)\b/gm)
|
const matches = globalFileContent.matchAll(/^declare const LOC_(\w+)\b/gm)
|
||||||
for (const match of matches) {
|
for (const match of matches) {
|
||||||
@ -41,7 +40,7 @@ function loadLocs() {
|
|||||||
const define = Object.assign({
|
const define = Object.assign({
|
||||||
PLUGIN_CHAT_PACKAGE_NAME: JSON.stringify(packagejson.name),
|
PLUGIN_CHAT_PACKAGE_NAME: JSON.stringify(packagejson.name),
|
||||||
PLUGIN_CHAT_SHORT_NAME: JSON.stringify(packagejson.name.replace(/^peertube-plugin-/, ''))
|
PLUGIN_CHAT_SHORT_NAME: JSON.stringify(packagejson.name.replace(/^peertube-plugin-/, ''))
|
||||||
}, loadLocs())
|
}, loadLocs(path.resolve(__dirname, 'client', '@types', 'global.d.ts')))
|
||||||
|
|
||||||
const configs = clientFiles.map(f => ({
|
const configs = clientFiles.map(f => ({
|
||||||
entryPoints: [ path.resolve(__dirname, 'client', f + '.ts') ],
|
entryPoints: [ path.resolve(__dirname, 'client', f + '.ts') ],
|
||||||
@ -59,8 +58,14 @@ const configs = clientFiles.map(f => ({
|
|||||||
outfile: path.resolve(__dirname, 'dist/client', f + '.js'),
|
outfile: path.resolve(__dirname, 'dist/client', f + '.js'),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
const defineBuiltin = Object.assign(
|
||||||
|
{},
|
||||||
|
loadLocs(path.resolve(__dirname, 'conversejs', 'lib', '@types', 'global.d.ts'))
|
||||||
|
)
|
||||||
|
|
||||||
configs.push({
|
configs.push({
|
||||||
entryPoints: ["./conversejs/builtin.ts"],
|
entryPoints: ["./conversejs/builtin.ts"],
|
||||||
|
define: defineBuiltin,
|
||||||
bundle: true,
|
bundle: true,
|
||||||
minify: true,
|
minify: true,
|
||||||
sourcemap,
|
sourcemap,
|
||||||
|
@ -18,8 +18,8 @@ set -x
|
|||||||
CONVERSE_VERSION="v11.0.0"
|
CONVERSE_VERSION="v11.0.0"
|
||||||
CONVERSE_REPO="https://github.com/conversejs/converse.js.git"
|
CONVERSE_REPO="https://github.com/conversejs/converse.js.git"
|
||||||
# You can eventually set CONVERSE_COMMIT to a specific commit ID, if you want to apply some patches.
|
# You can eventually set CONVERSE_COMMIT to a specific commit ID, if you want to apply some patches.
|
||||||
# 2024-09-02: using Converse upstream (v11 WIP).
|
# 2024-09-11: using Converse upstream (v11 WIP).
|
||||||
CONVERSE_COMMIT="9952046d580bc2930e29833f4c9987a3d4c95bc2"
|
CONVERSE_COMMIT="b5452466b90ff646e9ba5aa19e572a8ba958db83"
|
||||||
|
|
||||||
# It is possible to use another repository, if we want some customization that are not upstream (yet):
|
# It is possible to use another repository, if we want some customization that are not upstream (yet):
|
||||||
# CONVERSE_VERSION="livechat"
|
# CONVERSE_VERSION="livechat"
|
||||||
@ -30,7 +30,7 @@ CONVERSE_COMMIT="9952046d580bc2930e29833f4c9987a3d4c95bc2"
|
|||||||
# 2024-09-03: include badges short label and quick fix for sendMessage button
|
# 2024-09-03: include badges short label and quick fix for sendMessage button
|
||||||
CONVERSE_REPO="https://github.com/JohnXLivingston/converse.js"
|
CONVERSE_REPO="https://github.com/JohnXLivingston/converse.js"
|
||||||
CONVERSE_VERSION="livechat-12.0.0"
|
CONVERSE_VERSION="livechat-12.0.0"
|
||||||
CONVERSE_COMMIT="a910586fa83bd64db7182add6fc4bbf71cef0ae8"
|
# CONVERSE_COMMIT=""
|
||||||
|
|
||||||
rootdir="$(pwd)"
|
rootdir="$(pwd)"
|
||||||
src_dir="$rootdir/conversejs"
|
src_dir="$rootdir/conversejs"
|
||||||
|
@ -23,6 +23,7 @@ import { livechatViewerModePlugin } from './lib/plugins/livechat-viewer-mode'
|
|||||||
import { livechatMiniMucHeadPlugin } from './lib/plugins/livechat-mini-muc-head'
|
import { livechatMiniMucHeadPlugin } from './lib/plugins/livechat-mini-muc-head'
|
||||||
import { livechatEmojisPlugin } from './lib/plugins/livechat-emojis'
|
import { livechatEmojisPlugin } from './lib/plugins/livechat-emojis'
|
||||||
import { moderationDelayPlugin } from './lib/plugins/moderation-delay'
|
import { moderationDelayPlugin } from './lib/plugins/moderation-delay'
|
||||||
|
import { livechatAnnouncementsPlugin } from './lib/plugins/livechat-announcements'
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
@ -37,6 +38,7 @@ declare global {
|
|||||||
html: Function
|
html: Function
|
||||||
sizzle: Function
|
sizzle: Function
|
||||||
dayjs: Function
|
dayjs: Function
|
||||||
|
__: Function
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
initConversePlugins: typeof initConversePlugins
|
initConversePlugins: typeof initConversePlugins
|
||||||
@ -76,6 +78,8 @@ function initConversePlugins (peertubeEmbedded: boolean): void {
|
|||||||
converse.plugins.add('livechatViewerModePlugin', livechatViewerModePlugin)
|
converse.plugins.add('livechatViewerModePlugin', livechatViewerModePlugin)
|
||||||
|
|
||||||
converse.plugins.add('converse-moderation-delay', moderationDelayPlugin)
|
converse.plugins.add('converse-moderation-delay', moderationDelayPlugin)
|
||||||
|
|
||||||
|
converse.plugins.add('livechatAnnouncementsPlugin', livechatAnnouncementsPlugin)
|
||||||
}
|
}
|
||||||
window.initConversePlugins = initConversePlugins
|
window.initConversePlugins = initConversePlugins
|
||||||
|
|
||||||
|
42
conversejs/custom/shared/styles/_announcements.scss
Normal file
42
conversejs/custom/shared/styles/_announcements.scss
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
// FIXME: this should be with the livechat-announcement plugin.
|
||||||
|
// But for now, there is no way to build scss from there.
|
||||||
|
|
||||||
|
#conversejs {
|
||||||
|
.message.chat-msg {
|
||||||
|
&.livechat-announcement {
|
||||||
|
--livechat-announcement-color: #000;
|
||||||
|
--livechat-announcement-background-color: #dbf2d8;
|
||||||
|
--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-announcement-color: #000;
|
||||||
|
--livechat-announcement-background-color: #dce8fa;
|
||||||
|
--livechat-announcement-border-color: #3075e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.livechat-announcement,
|
||||||
|
&.livechat-highlight {
|
||||||
|
converse-chat-message-body {
|
||||||
|
border: 2px solid var(--livechat-announcement-border-color);
|
||||||
|
color: var(--livechat-announcement-color);
|
||||||
|
background-color: var(--livechat-announcement-background-color);
|
||||||
|
min-width: 50%;
|
||||||
|
padding: 2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@
|
|||||||
@import "./variables";
|
@import "./variables";
|
||||||
@import "shared/styles/index";
|
@import "shared/styles/index";
|
||||||
@import "./peertubetheme";
|
@import "./peertubetheme";
|
||||||
|
@import "./announcements";
|
||||||
|
|
||||||
body.livechat-iframe {
|
body.livechat-iframe {
|
||||||
#conversejs .chat-head {
|
#conversejs .chat-head {
|
||||||
|
9
conversejs/lib/@types/global.d.ts
vendored
Normal file
9
conversejs/lib/@types/global.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
// Important note: loc segments that are declared here must also be in loc.keys.js (for now).
|
||||||
|
|
||||||
|
declare const LOC_ANNOUNCEMENTS_MESSAGE_TYPE_STANDARD: string
|
||||||
|
declare const LOC_ANNOUNCEMENTS_MESSAGE_TYPE_ANNOUNCEMENT: string
|
||||||
|
declare const LOC_ANNOUNCEMENTS_MESSAGE_TYPE_HIGHLIGHT: string
|
@ -86,7 +86,8 @@ function defaultConverseParams (
|
|||||||
'livechatDisconnectOnUnloadPlugin',
|
'livechatDisconnectOnUnloadPlugin',
|
||||||
'converse-slow-mode',
|
'converse-slow-mode',
|
||||||
'livechatEmojis',
|
'livechatEmojis',
|
||||||
'converse-moderation-delay'
|
'converse-moderation-delay',
|
||||||
|
'livechatAnnouncementsPlugin'
|
||||||
],
|
],
|
||||||
show_retraction_warning: false, // No need to use this warning (except if we open to external clients?)
|
show_retraction_warning: false, // No need to use this warning (except if we open to external clients?)
|
||||||
muc_show_info_messages: mucShowInfoMessages,
|
muc_show_info_messages: mucShowInfoMessages,
|
||||||
|
156
conversejs/lib/plugins/livechat-announcements.ts
Normal file
156
conversejs/lib/plugins/livechat-announcements.ts
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
const { __ } = _converse
|
||||||
|
|
||||||
|
// This is a closure variable, to get the current form status when sending a message.
|
||||||
|
let currentAnnouncementType: string | 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).
|
||||||
|
_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) => {
|
||||||
|
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) => {
|
||||||
|
// For outgoing message, adding the announcement type if there is a current one.
|
||||||
|
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) => {
|
||||||
|
// 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)
|
||||||
|
return data
|
||||||
|
})
|
||||||
|
|
||||||
|
_converse.api.listen.on('parseMUCMessage', (stanza: any, attrs: any) => {
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
const i18nStandard = __(LOC_ANNOUNCEMENTS_MESSAGE_TYPE_STANDARD)
|
||||||
|
const i18nAnnouncement = __(LOC_ANNOUNCEMENTS_MESSAGE_TYPE_ANNOUNCEMENT)
|
||||||
|
const i18nHighlight = __(LOC_ANNOUNCEMENTS_MESSAGE_TYPE_HIGHLIGHT)
|
||||||
|
|
||||||
|
const select = html`<select name="livechat-announcements">
|
||||||
|
<option value="">${i18nStandard}</option>
|
||||||
|
<option value="announcement">${i18nAnnouncement}</option>
|
||||||
|
<option value="highlight">${i18nHighlight}</option>
|
||||||
|
</select>`
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
@ -63,7 +63,10 @@ const locKeys = [
|
|||||||
'search_occupant_message',
|
'search_occupant_message',
|
||||||
'message_search',
|
'message_search',
|
||||||
'message_search_original_nick',
|
'message_search_original_nick',
|
||||||
'emoji_only_info'
|
'emoji_only_info',
|
||||||
|
'announcements_message_type_standard',
|
||||||
|
'announcements_message_type_announcement',
|
||||||
|
'announcements_message_type_highlight'
|
||||||
]
|
]
|
||||||
|
|
||||||
module.exports = locKeys
|
module.exports = locKeys
|
||||||
|
@ -670,3 +670,7 @@ livechat_configuration_channel_no_duplicate_desc: |
|
|||||||
livechat_configuration_channel_no_duplicate_delay_label: Time interval
|
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_announcement: Announcement
|
||||||
|
announcements_message_type_highlight: Highlighted message
|
||||||
|
Loading…
Reference in New Issue
Block a user