New moderator app WIP:

* #144: moderator notes WIP,
* plugin size: adding an API,
* refactoring the code from the task app, to create a new MUC App
  system.
This commit is contained in:
John Livingston 2024-07-29 18:58:02 +02:00
parent 34da786b65
commit 074e688ed8
No known key found for this signature in database
GPG Key ID: B17B5640CE66CDBC
20 changed files with 496 additions and 32 deletions

View File

@ -7,6 +7,7 @@
* Updating ConverseJS, to use upstream (v11 WIP). This comes with many improvments and new features. * Updating ConverseJS, to use upstream (v11 WIP). This comes with many improvments and new features.
* #146: copy message button for moderators. * #146: copy message button for moderators.
* #137: option to hide moderator name who made actions (kick, ban, message moderation, ...). * #137: option to hide moderator name who made actions (kick, ban, message moderation, ...).
* #144: [moderator notes](https://livingston.frama.io/peertube-plugin-livechat/documentation/user/streamers/notes/).
### Minor changes and fixes ### Minor changes and fixes

View File

@ -219,9 +219,12 @@ async function initConverse (
// * mode === chat-only + !transparent + !readonly + is using a livechat token // * mode === chat-only + !transparent + !readonly + is using a livechat token
// Technically it would work in 'chat-only' mode, but i don't want to add too many things to test // Technically it would work in 'chat-only' mode, but i don't want to add too many things to test
// (and i now there is some CSS bugs in the task list). // (and i now there is some CSS bugs in the task list).
// Same for the moderator notes app.
let enableTask = false let enableTask = false
let enableModeratorNotes = false
if (chatIncludeMode === 'peertube-video' || chatIncludeMode === 'peertube-fullpage') { if (chatIncludeMode === 'peertube-video' || chatIncludeMode === 'peertube-fullpage') {
enableTask = true enableTask = true
enableModeratorNotes = true
} else if ( } else if (
chatIncludeMode === 'chat-only' && chatIncludeMode === 'chat-only' &&
usedLivechatToken && usedLivechatToken &&
@ -229,11 +232,16 @@ async function initConverse (
!initConverseParams.forceReadonly !initConverseParams.forceReadonly
) { ) {
enableTask = true enableTask = true
enableModeratorNotes = true
} }
if (enableTask) { if (enableTask) {
params.livechat_task_app_enabled = true params.livechat_task_app_enabled = true
params.livechat_task_app_restore = chatIncludeMode === 'peertube-fullpage' || chatIncludeMode === 'chat-only' params.livechat_task_app_restore = chatIncludeMode === 'peertube-fullpage' || chatIncludeMode === 'chat-only'
} }
if (enableModeratorNotes) {
params.livechat_note_app_enabled = true
params.livechat_note_app_restore = chatIncludeMode === 'peertube-fullpage' || chatIncludeMode === 'chat-only'
}
try { try {
if (window.reconnectConverse) { // this is set in the livechatSpecificsPlugin if (window.reconnectConverse) { // this is set in the livechatSpecificsPlugin

View File

@ -44,6 +44,7 @@ import './plugins/singleton/index.js'
import './plugins/fullscreen/index.js' import './plugins/fullscreen/index.js'
import '../custom/plugins/size/index.js' import '../custom/plugins/size/index.js'
import '../custom/plugins/notes/index.js'
import '../custom/plugins/tasks/index.js' import '../custom/plugins/tasks/index.js'
import '../custom/plugins/terms/index.js' import '../custom/plugins/terms/index.js'
import '../custom/plugins/poll/index.js' import '../custom/plugins/poll/index.js'
@ -59,6 +60,7 @@ CORE_PLUGINS.push('livechat-converse-size')
CORE_PLUGINS.push('livechat-converse-tasks') CORE_PLUGINS.push('livechat-converse-tasks')
CORE_PLUGINS.push('livechat-converse-terms') CORE_PLUGINS.push('livechat-converse-terms')
CORE_PLUGINS.push('livechat-converse-poll') CORE_PLUGINS.push('livechat-converse-poll')
CORE_PLUGINS.push('livechat-converse-notes')
// We must also add our custom ROOM_FEATURES, so that they correctly resets // We must also add our custom ROOM_FEATURES, so that they correctly resets
// (see headless/plugins/muc, getDiscoInfoFeatures, which loops on this const) // (see headless/plugins/muc, getDiscoInfoFeatures, which loops on this const)
ROOM_FEATURES.push('x_peertubelivechat_mute_anonymous') ROOM_FEATURES.push('x_peertubelivechat_mute_anonymous')

View File

@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
//
// SPDX-License-Identifier: AGPL-3.0-only
import { api } from '@converse/headless'
import { MUCApp } from '../../../shared/components/muc-app.js'
import { tplMUCNoteApp } from '../templates/muc-note-app.js'
/**
* Custom Element to display the Notes Application.
*/
export default class MUCNoteApp extends MUCApp {
enableSettingName = 'livechat_note_app_restore'
sessionStorangeShowKey = 'livechat-converse-note-app-show'
render () {
return tplMUCNoteApp(this, this.model)
}
}
api.elements.define('livechat-converse-muc-note-app', MUCNoteApp)

View File

@ -0,0 +1,5 @@
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
//
// SPDX-License-Identifier: AGPL-3.0-only
export const XMLNS_NOTE = 'urn:peertube-plugin-livechat:note'

View File

@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
//
// SPDX-License-Identifier: AGPL-3.0-only
import { _converse, converse } from '../../../src/headless/index.js'
import { XMLNS_NOTE } from './constants.js'
import { ChatRoomNote } from './note.js'
import { ChatRoomNotes } from './notes.js'
import { initOrDestroyChatRoomNotes, getHeadingButtons, getMessageActionButtons } from './utils.js'
import './components/muc-note-app-view.js'
converse.plugins.add('livechat-converse-notes', {
dependencies: ['converse-muc', 'converse-disco', 'converse-pubsub'],
initialize () {
Object.assign(
_converse.exports,
{
ChatRoomNotes,
ChatRoomNote
}
)
_converse.api.settings.extend({
livechat_note_app_enabled: false,
livechat_note_app_restore: false // should we open the app by default if it was previously oppened?
})
_converse.api.listen.on('chatRoomInitialized', muc => {
muc.session.on('change:connection_status', _session => {
// When joining a room, initializing the Notes object (if user has access),
// When disconnected from a room, destroying the Notes object:
initOrDestroyChatRoomNotes(muc)
})
// When the current user affiliation changes, we must also delete or initialize the TaskLists object:
muc.occupants.on('change:affiliation', occupant => {
if (occupant.get('jid') !== _converse.bare_jid) { // only for myself
return
}
initOrDestroyChatRoomNotes(muc)
})
// To be sure that everything works in any case, we also must listen for addition in muc.features.
muc.features.on('change:' + XMLNS_NOTE, () => {
initOrDestroyChatRoomNotes(muc)
})
})
// adding the "Notes" button in the MUC heading buttons:
_converse.api.listen.on('getHeadingButtons', getHeadingButtons)
// Adding buttons on message:
_converse.api.listen.on('getMessageActionButtons', getMessageActionButtons)
}
})

View File

@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
//
// SPDX-License-Identifier: AGPL-3.0-only
import { Model } from '@converse/skeletor/src/model.js'
/**
* A chat room note.
* @class
* @namespace _converse.exports.ChatRoomNote
* @memberof _converse
*/
class ChatRoomNote extends Model {
idAttribute = 'id'
async saveItem () {
console.log('Saving note ' + this.get('id') + '...')
await this.collection.chatroom.noteManager.saveItem(this)
console.log('Note ' + this.get('id') + ' saved.')
}
async deleteItem () {
return this.collection.chatroom.noteManager.deleteItems([this])
}
}
export {
ChatRoomNote
}

View File

@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
//
// SPDX-License-Identifier: AGPL-3.0-only
import { Collection } from '@converse/skeletor/src/collection.js'
import { ChatRoomNote } from './note'
import { initStorage } from '@converse/headless/utils/storage.js'
/**
* A list of {@link _converse.exports.ChatRoomNote} instances, representing notes associated to a MUC.
* @class
* @namespace _converse.exports.ChatRoomNotes
* @memberOf _converse
*/
class ChatRoomNotes extends Collection {
model = ChatRoomNote
comparator = 'order'
initialize (models, options) {
this.model = ChatRoomNote // don't know why, must do it again here
super.initialize(arguments)
this.chatroom = options.chatroom
const id = `converse-livechat-notes-${this.chatroom.get('jid')}`
initStorage(this, id, 'session')
this.on('change:order', () => this.sort())
}
// async createNote (data) {
// console.log('Creating note...')
// await this.chatroom.NoteManager.createItem(this, Object.assign({}, data))
// console.log('Note created.')
// }
}
export {
ChatRoomNotes
}

View File

@ -0,0 +1,51 @@
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
//
// SPDX-License-Identifier: AGPL-3.0-only
import { converseLocalizedHelpUrl } from '../../../shared/lib/help'
import { html } from 'lit'
import { __ } from 'i18n'
export function tplMUCNoteApp (el, mucModel) {
if (!mucModel) {
// should not happen
el.classList.add('hidden') // we must do this, otherwise will have CSS side effects
return html``
}
if (!mucModel.notes) {
// too soon, not initialized yet (this will happen)
el.classList.add('hidden') // we must do this, otherwise will have CSS side effects
return html``
}
if (!el.show) {
el.classList.add('hidden')
return html``
}
el.classList.remove('hidden')
// eslint-disable-next-line no-undef
const i18nNotes = __(LOC_moderator_notes)
// eslint-disable-next-line no-undef
const i18nHelp = __(LOC_online_help)
const helpUrl = converseLocalizedHelpUrl({
page: 'documentation/user/streamers/notes'
})
return html`
<div class="livechat-converse-muc-app-header">
<h5>${i18nNotes}</h5>
<a href="${helpUrl}" target="_blank"><converse-icon
class="fa fa-circle-question"
size="1em"
title="${i18nHelp}"
></converse-icon></a>
<button class="livechat-converse-muc-app-close" @click=${el.toggleApp} title="${__('Close')}">
<converse-icon class="fa fa-times" size="1em"></converse-icon>
</button>
</div>
<div class="livechat-converse-muc-app-body">
<livechat-converse-muc-notes .model=${mucModel.notes}></livechat-converse-muc-notes>
</div>`
}

View File

@ -0,0 +1,146 @@
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
//
// SPDX-License-Identifier: AGPL-3.0-only
import { XMLNS_NOTE } from './constants.js'
import { PubSubManager } from '../../shared/lib/pubsub-manager.js'
import { converse, _converse, api } from '../../../src/headless/index.js'
import { __ } from 'i18n'
export function getHeadingButtons (view, buttons) {
const muc = view.model
if (muc.get('type') !== _converse.constants.CHATROOMS_TYPE) {
// only on MUC.
return buttons
}
if (!muc.notes) { // this is defined only if user has access (see initOrDestroyChatRoomNotes)
return buttons
}
// Adding a "Open moderator noteds" button.
buttons.unshift({
// eslint-disable-next-line no-undef
i18n_text: __(LOC_moderator_notes),
handler: async (ev) => {
ev.preventDefault()
// opening or closing the muc notes:
const NoteAppEl = ev.target.closest('converse-root').querySelector('livechat-converse-muc-note-app')
NoteAppEl.toggleApp()
},
a_class: '',
icon_class: 'fa-note-sticky',
name: 'muc-notes'
})
return buttons
}
export function getMessageActionButtons (messageActionsEl, buttons) {
const messageModel = messageActionsEl.model
if (messageModel.get('type') !== 'groupchat') {
// only on groupchat message.
return buttons
}
const muc = messageModel.collection?.chatbox
if (!muc?.notes) {
return buttons
}
// TODO: button to create a note from a message.
// // eslint-disable-next-line no-undef
// const i18nCreate = __(LOC_task_create)
// buttons.push({
// i18n_text: i18nCreate,
// handler: async (ev) => {
// ev.preventDefault()
// api.modal.show('livechat-converse-pick-task-list-modal', {
// muc,
// message: messageModel
// }, ev)
// },
// button_class: '',
// icon_class: 'fa fa-list-check',
// name: 'muc-task-create-from-message'
// })
return buttons
}
function _initChatRoomNotes (mucModel) {
if (mucModel.noteManager) {
// already initiliazed
return
}
mucModel.notes = new _converse.exports.ChatRoomNotes(undefined, { chatroom: mucModel })
mucModel.noteManager = new PubSubManager(
mucModel.get('jid'),
'livechat-notes', // the node name
{
note: {
itemTag: 'note',
xmlns: XMLNS_NOTE,
collection: mucModel.notes,
fields: {
name: String,
description: String
},
attributes: {
order: Number
}
}
}
)
mucModel.noteManager.start().catch(err => console.log(err))
// We must requestUpdate for all message actions, to add the "create note" button.
// FIXME: this should not be done here (but it is simplier for now)
document.querySelectorAll('converse-message-actions').forEach(el => el.requestUpdate())
}
function _destroyChatRoomNotes (mucModel) {
if (!mucModel.noteManager) { return }
mucModel.noteManager.stop().catch(err => console.log(err))
mucModel.noteManager = undefined
mucModel.notes = undefined
// We must requestUpdate for all message actions, to remove the "create note" button.
// FIXME: this should not be done here (but it is simplier for now)
document.querySelectorAll('converse-message-actions').forEach(el => el.requestUpdate())
}
export function initOrDestroyChatRoomNotes (mucModel) {
if (mucModel.get('type') !== _converse.constants.CHATROOMS_TYPE) {
// only on MUC.
return _destroyChatRoomNotes(mucModel)
}
if (!api.settings.get('livechat_note_app_enabled')) {
// Feature disabled, no need to handle notes.
return _destroyChatRoomNotes(mucModel)
}
if (mucModel.session.get('connection_status') !== converse.ROOMSTATUS.ENTERED) {
return _destroyChatRoomNotes(mucModel)
}
// We must check disco features
// (if the chat is remote, the server could use a livechat version that does not support this feature)
if (!mucModel.features?.get?.(XMLNS_NOTE)) {
return _destroyChatRoomNotes(mucModel)
}
const myself = mucModel.getOwnOccupant()
if (!myself || !['admin', 'owner'].includes(myself.get('affiliation'))) {
// User must be admin or owner
return _destroyChatRoomNotes(mucModel)
}
return _initChatRoomNotes(mucModel)
}

View File

@ -4,6 +4,8 @@
import { _converse, converse, api } from '../../../src/headless/index.js' import { _converse, converse, api } from '../../../src/headless/index.js'
let currentSize
/** /**
* This plugin computes the available width of converse-root, and adds classes * This plugin computes the available width of converse-root, and adds classes
* and events so we can adapt the display of some elements to the current * and events so we can adapt the display of some elements to the current
@ -16,6 +18,27 @@ converse.plugins.add('livechat-converse-size', {
dependencies: [], dependencies: [],
initialize () { initialize () {
Object.assign(api, {
livechat_size: {
current: () => {
return currentSize
},
width_is: (sizes) => {
if (!Array.isArray(sizes)) {
sizes = [sizes]
}
if (!currentSize) { return false }
return sizes.includes(currentSize.width)
},
height_is: (sizes) => {
if (!Array.isArray(sizes)) {
sizes = [sizes]
}
if (!currentSize) { return false }
return sizes.includes(currentSize.height)
}
}
})
_converse.api.listen.on('connected', start) _converse.api.listen.on('connected', start)
_converse.api.listen.on('reconnected', start) _converse.api.listen.on('reconnected', start)
_converse.api.listen.on('disconnected', stop) _converse.api.listen.on('disconnected', stop)
@ -42,6 +65,7 @@ function start () {
} }
function stop () { function stop () {
currentSize = undefined
rootResizeObserver.disconnect() rootResizeObserver.disconnect()
const root = document.querySelector('converse-root') const root = document.querySelector('converse-root')
if (root) { if (root) {
@ -60,8 +84,9 @@ function handle (el) {
el.setAttribute('livechat-converse-root-width', width) el.setAttribute('livechat-converse-root-width', width)
el.setAttribute('livechat-converse-root-height', height) el.setAttribute('livechat-converse-root-height', height)
api.trigger('livechatSizeChanged', { currentSize = {
height: height, height: height,
width: width width: width
}) }
api.trigger('livechatSizeChanged', Object.assign({}, currentSize)) // cloning...
} }

View File

@ -3,35 +3,19 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { api } from '@converse/headless' import { api } from '@converse/headless'
import { CustomElement } from 'shared/components/element.js' import { MUCApp } from '../../../shared/components/muc-app.js'
import { tplMUCTaskApp } from '../templates/muc-task-app.js' import { tplMUCTaskApp } from '../templates/muc-task-app.js'
import '../styles/muc-task-app.scss'
/** /**
* Custom Element to display the Task Application. * Custom Element to display the Task Application.
*/ */
export default class MUCTaskApp extends CustomElement { export default class MUCTaskApp extends MUCApp {
static get properties () { enableSettingName = 'livechat_task_app_restore'
return { sessionStorangeShowKey = 'livechat-converse-task-app-show'
model: { type: Object, attribute: true }, // mucModel
show: { type: Boolean, attribute: false }
}
}
async initialize () {
this.show = api.settings.get('livechat_task_app_restore') &&
(window.sessionStorage?.getItem?.('livechat-converse-task-app-show') === '1')
}
render () { render () {
return tplMUCTaskApp(this, this.model) return tplMUCTaskApp(this, this.model)
} }
toggleApp () {
this.show = !this.show
window.sessionStorage?.setItem?.('livechat-converse-task-app-show', this.show ? '1' : '')
}
} }
api.elements.define('livechat-converse-muc-task-app', MUCTaskApp) api.elements.define('livechat-converse-muc-task-app', MUCTaskApp)

View File

@ -28,6 +28,11 @@ export default () => {
<symbol id="icon-square-poll-horizontal" viewBox="0 0 448 512"> <symbol id="icon-square-poll-horizontal" viewBox="0 0 448 512">
<path d="M448 96c0-35.3-28.7-64-64-64L64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l320 0c35.3 0 64-28.7 64-64l0-320zM256 160c0 17.7-14.3 32-32 32l-96 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l96 0c17.7 0 32 14.3 32 32zm64 64c17.7 0 32 14.3 32 32s-14.3 32-32 32l-192 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l192 0zM192 352c0 17.7-14.3 32-32 32l-32 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l32 0c17.7 0 32 14.3 32 32z"/> <path d="M448 96c0-35.3-28.7-64-64-64L64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l320 0c35.3 0 64-28.7 64-64l0-320zM256 160c0 17.7-14.3 32-32 32l-96 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l96 0c17.7 0 32 14.3 32 32zm64 64c17.7 0 32 14.3 32 32s-14.3 32-32 32l-192 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l192 0zM192 352c0 17.7-14.3 32-32 32l-32 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l32 0c17.7 0 32 14.3 32 32z"/>
</symbol> </symbol>
<!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.-->
<symbol id="icon-note-sticky" viewBox="0 0 448 512">
<path d="M64 80c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l224 0 0-80c0-17.7 14.3-32 32-32l80 0 0-224c0-8.8-7.2-16-16-16L64 80zM288 480L64 480c-35.3 0-64-28.7-64-64L0 96C0 60.7 28.7 32 64 32l320 0c35.3 0 64 28.7 64 64l0 224 0 5.5c0 17-6.7 33.3-18.7 45.3l-90.5 90.5c-12 12-28.3 18.7-45.3 18.7l-5.5 0z"/>
</symbol>
</svg> </svg>
` `
} }

View File

@ -0,0 +1,77 @@
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
//
// SPDX-License-Identifier: AGPL-3.0-only
import { CustomElement } from 'shared/components/element.js'
import { api, _converse } from '@converse/headless'
import './styles/muc-app.scss'
/**
* Base class for MUC App custom elements (task app, notes app, ...).
* This is an abstract class, should not be called directly.
*/
export class MUCApp extends CustomElement {
enableSettingName = undefined // must be overloaded
sessionStorangeShowKey = undefined // must be overloaded
static get properties () {
return {
model: { type: Object, attribute: true }, // mucModel
show: { type: Boolean, attribute: false }
}
}
async initialize () {
this.classList.add('livechat-converse-muc-app')
this.show = this.enableSettingName &&
api.settings.get(this.enableSettingName) &&
this.sessionStorangeShowKey &&
(window.sessionStorage?.getItem?.(this.sessionStorangeShowKey) === '1')
// we listen for livechatSizeChanged event,
// and close all apps except the first if small or medium width.
// Note: this will also be triggered when we first open the page
this.listenTo(_converse, 'livechatSizeChanged', () => {
if (!this.show || !api.livechat_size?.width_is(['small', 'medium'])) {
return
}
// are we the first opened app?
for (const el of document.querySelectorAll('.livechat-converse-muc-app')) {
if (el === this) { break }
if (!el.show) { continue }
console.debug('The livechat size is small or medium, there is already an opened app, so closing myself', this)
// ok, there is already an opened app.
this.toggleApp() // we know we are open
break
}
})
}
render () { // must be overloaded.
return ''
}
toggleApp () {
this.show = !this.show
if (this.sessionStorangeShowKey) {
window.sessionStorage?.setItem?.(this.sessionStorangeShowKey, this.show ? '1' : '')
}
if (
this.show &&
api.livechat_size?.width_is(['small', 'medium'])
) {
// When showing an App, if the screen width is small or medium, we hide the others.
this._closeOtherApps()
}
}
_closeOtherApps () {
document.querySelectorAll('.livechat-converse-muc-app').forEach((el) => {
if (el !== this && el.show) {
console.debug('Closing another app, because livechat width is small or medium', el)
el.toggleApp()
}
})
}
}

View File

@ -5,7 +5,7 @@
*/ */
.conversejs { .conversejs {
livechat-converse-muc-task-app { .livechat-converse-muc-app {
border: var(--occupants-border-left); border: var(--occupants-border-left);
display: flex; display: flex;
flex-flow: column nowrap; flex-flow: column nowrap;
@ -42,8 +42,8 @@
&[livechat-converse-root-width="small"], &[livechat-converse-root-width="small"],
&[livechat-converse-root-width="medium"] { &[livechat-converse-root-width="medium"] {
converse-muc-chatarea livechat-converse-muc-task-app:not(.hidden) ~ * { converse-muc-chatarea .livechat-converse-muc-app:not(.hidden) ~ * {
// on small and medium width, we hide all subsequent siblings of the task app // on small and medium width, we hide all subsequent siblings of the app
// (when app is not hidden) // (when app is not hidden)
display: none !important; display: none !important;
} }

View File

@ -13,5 +13,10 @@ export default (o) => {
? html`<livechat-converse-muc-task-app .model=${o.model}></livechat-converse-muc-task-app>` ? html`<livechat-converse-muc-task-app .model=${o.model}></livechat-converse-muc-task-app>`
: '' : ''
} }
${
o?.model && api.settings.get('livechat_note_app_enabled')
? html`<livechat-converse-muc-note-app .model=${o.model}></livechat-converse-muc-note-app>`
: ''
}
${tplMUCChatarea(o)}` ${tplMUCChatarea(o)}`
} }

View File

@ -49,7 +49,8 @@ const locKeys = [
'poll_vote_instructions_xmpp', 'poll_vote_instructions_xmpp',
'poll_is_over', 'poll_is_over',
'poll_choice_invalid', 'poll_choice_invalid',
'poll_anonymous_vote_ok' 'poll_anonymous_vote_ok',
'moderator_notes'
] ]
module.exports = locKeys module.exports = locKeys

View File

@ -593,3 +593,5 @@ livechat_configuration_channel_anonymize_moderation_label: "Anonymize moderation
livechat_configuration_channel_anonymize_moderation_desc: | livechat_configuration_channel_anonymize_moderation_desc: |
Anonymize moderation actions default value for new rooms. Anonymize moderation actions default value for new rooms.
When this is enabled, moderation actions will be anonymized, to avoid disclosing who is banning/kicking/… occupants. When this is enabled, moderation actions will be anonymized, to avoid disclosing who is banning/kicking/… occupants.
moderator_notes: Moderator notes

View File

@ -7,13 +7,16 @@ SPDX-License-Identifier: AGPL-3.0-only
This module is a custom module that provide some pubsub services associated to a MUC room. This module is a custom module that provide some pubsub services associated to a MUC room.
This module is entended to be used in the peertube-plugin-livechat project. This module is entended to be used in the peertube-plugin-livechat project.
For each MUC room, there will be an associated pubsub node. For each MUC room, there will be a associated pubsub nodes.
This node in only accessible by the ROOM admin/owner. These nodes are only accessible by the ROOM admins/owners.
This node can contains various objects: Here are a description of existing nodes, and objects they can contain:
* task lists * livechat-tasks:
* tasks * task lists
* tasks
* livechat-notes:
* notes
* ... (more to come) * ... (more to come)
These objects are meant te be shared between admin/owner. These objects are meant te be shared between admin/owner.

View File

@ -15,6 +15,7 @@
-- Implemented nodes: -- Implemented nodes:
-- * livechat-tasks: contains tasklist and task items, specific to livechat plugin. -- * livechat-tasks: contains tasklist and task items, specific to livechat plugin.
-- * livechat-notes: contains notes, specific to livechat plugin.
-- There are some other tricks in this module: -- There are some other tricks in this module:
-- * unsubscribing users that have left the room (the front-end will subscribe again when needed) -- * unsubscribing users that have left the room (the front-end will subscribe again when needed)
@ -39,7 +40,8 @@ local xmlns_pubsub = "http://jabber.org/protocol/pubsub";
local xmlns_pubsub_event = "http://jabber.org/protocol/pubsub#event"; local xmlns_pubsub_event = "http://jabber.org/protocol/pubsub#event";
local xmlns_pubsub_owner = "http://jabber.org/protocol/pubsub#owner"; local xmlns_pubsub_owner = "http://jabber.org/protocol/pubsub#owner";
local xmlns_tasklist = "urn:peertube-plugin-livechat:tasklist"; local xmlns_tasklist = "urn:peertube-plugin-livechat:tasklist";
local xmlns_task = "urn:peertube-plugin-livechat:task" local xmlns_task = "urn:peertube-plugin-livechat:task";
local xmlns_note = "urn:peertube-plugin-livechat:note";
local lib_pubsub = module:require "pubsub"; local lib_pubsub = module:require "pubsub";
@ -389,4 +391,5 @@ end);
module:hook("muc-disco#info", function (event) module:hook("muc-disco#info", function (event)
event.reply:tag("feature", { var = xmlns_task }):up(); event.reply:tag("feature", { var = xmlns_task }):up();
event.reply:tag("feature", { var = xmlns_tasklist }):up(); event.reply:tag("feature", { var = xmlns_tasklist }):up();
event.reply:tag("feature", { var = xmlns_note }):up();
end); end);