Moderator notes WIP (#144)
This commit is contained in:
parent
20cb668e09
commit
eb76e7ebb9
109
conversejs/custom/plugins/notes/components/muc-note-view.js
Normal file
109
conversejs/custom/plugins/notes/components/muc-note-view.js
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
// 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 } from '@converse/headless'
|
||||||
|
import { tplMucNote } from '../templates/muc-note'
|
||||||
|
import { __ } from 'i18n'
|
||||||
|
|
||||||
|
import '../styles/muc-note.scss'
|
||||||
|
|
||||||
|
export default class MUCNoteView extends CustomElement {
|
||||||
|
static get properties () {
|
||||||
|
return {
|
||||||
|
model: { type: Object, attribute: true },
|
||||||
|
edit: { type: Boolean, attribute: false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async initialize () {
|
||||||
|
this.edit = false
|
||||||
|
if (!this.model) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.listenTo(this.model, 'change', () => this.requestUpdate())
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return tplMucNote(this, this.model)
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldUpdate (changedProperties) {
|
||||||
|
if (!super.shouldUpdate(...arguments)) { return false }
|
||||||
|
// When a note is currently edited, and another users change the order,
|
||||||
|
// it could refresh losing the current form.
|
||||||
|
// To avoid this, we cancel update here.
|
||||||
|
// Note: of course, if 'edit' is part of the edited properties, we must update anyway
|
||||||
|
// (it means we just leaved the form)
|
||||||
|
if (this.edit && !changedProperties.has('edit')) {
|
||||||
|
console.info('Canceling an update on note, because it is currently edited', this)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveNote (ev) {
|
||||||
|
ev?.preventDefault?.()
|
||||||
|
|
||||||
|
const description = ev.target.description.value
|
||||||
|
|
||||||
|
if ((description ?? '') === '') { return }
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.querySelectorAll('input[type=submit]').forEach(el => {
|
||||||
|
el.setAttribute('disabled', true)
|
||||||
|
el.classList.add('disabled')
|
||||||
|
})
|
||||||
|
|
||||||
|
const note = this.model
|
||||||
|
note.set('description', description)
|
||||||
|
await note.saveItem()
|
||||||
|
|
||||||
|
this.edit = false
|
||||||
|
this.requestUpdate() // In case we cancel another update in shouldUpdate
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
} finally {
|
||||||
|
this.querySelectorAll('input[type=submit]').forEach(el => {
|
||||||
|
el.removeAttribute('disabled')
|
||||||
|
el.classList.remove('disabled')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteNote (ev) {
|
||||||
|
ev?.preventDefault?.()
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
const i18nConfirmDelete = __(LOC_moderator_note_delete_confirm)
|
||||||
|
|
||||||
|
const result = await api.confirm(i18nConfirmDelete)
|
||||||
|
if (!result) { return }
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.model.deleteItem()
|
||||||
|
} catch (err) {
|
||||||
|
api.alert(
|
||||||
|
'error', __('Error'), [__('Error')]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async toggleEdit () {
|
||||||
|
this.edit = !this.edit
|
||||||
|
if (this.edit) {
|
||||||
|
await this.updateComplete
|
||||||
|
const textarea = this.querySelector('textarea[name="description"]')
|
||||||
|
if (textarea) {
|
||||||
|
textarea.focus()
|
||||||
|
// Placing cursor at the end:
|
||||||
|
textarea.selectionStart = textarea.value.length
|
||||||
|
textarea.selectionEnd = textarea.selectionStart
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
api.elements.define('livechat-converse-muc-note', MUCNoteView)
|
95
conversejs/custom/plugins/notes/components/muc-notes-view.js
Normal file
95
conversejs/custom/plugins/notes/components/muc-notes-view.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
// 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 } from '@converse/headless'
|
||||||
|
import tplMucNotes from '../templates/muc-notes'
|
||||||
|
import { __ } from 'i18n'
|
||||||
|
|
||||||
|
import '../styles/muc-notes.scss'
|
||||||
|
|
||||||
|
export default class MUCNotesView extends CustomElement {
|
||||||
|
currentDraggedNote = null
|
||||||
|
|
||||||
|
static get properties () {
|
||||||
|
return {
|
||||||
|
model: { type: Object, attribute: true },
|
||||||
|
create_note_error_message: { type: String, attribute: false },
|
||||||
|
create_note_opened: { type: Boolean, attribute: false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async initialize () {
|
||||||
|
this.create_note_error_message = ''
|
||||||
|
|
||||||
|
if (!this.model) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adding or removing a new note: we must update.
|
||||||
|
this.listenTo(this.model, 'add', () => this.requestUpdate())
|
||||||
|
this.listenTo(this.model, 'remove', () => this.requestUpdate())
|
||||||
|
this.listenTo(this.model, 'sort', () => this.requestUpdate())
|
||||||
|
|
||||||
|
// this._handleDragStartBinded = this._handleDragStart.bind(this)
|
||||||
|
// this._handleDragOverBinded = this._handleDragOver.bind(this)
|
||||||
|
// this._handleDragLeaveBinded = this._handleDragLeave.bind(this)
|
||||||
|
// this._handleDragEndBinded = this._handleDragEnd.bind(this)
|
||||||
|
// this._handleDropBinded = this._handleDrop.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return tplMucNotes(this, this.model)
|
||||||
|
}
|
||||||
|
|
||||||
|
async openCreateNoteForm (ev) {
|
||||||
|
ev?.preventDefault?.()
|
||||||
|
this.create_note_opened = true
|
||||||
|
await this.updateComplete
|
||||||
|
const textarea = this.querySelector('.notes-create-note textarea[name="description"]')
|
||||||
|
if (textarea) {
|
||||||
|
textarea.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closeCreateNoteForm (ev) {
|
||||||
|
ev?.preventDefault?.()
|
||||||
|
this.create_note_opened = false
|
||||||
|
}
|
||||||
|
|
||||||
|
async submitCreateNote (ev) {
|
||||||
|
ev.preventDefault()
|
||||||
|
|
||||||
|
const description = ev.target.description.value
|
||||||
|
if (this.create_note_error_message) {
|
||||||
|
this.create_note_error_message = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((description ?? '') === '') { return }
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.querySelectorAll('input[type=submit]').forEach(el => {
|
||||||
|
el.setAttribute('disabled', true)
|
||||||
|
el.classList.add('disabled')
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.model.createNote({
|
||||||
|
description: description
|
||||||
|
})
|
||||||
|
|
||||||
|
this.closeCreateNoteForm()
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
this.create_note_error_message = __(LOC_moderator_notes_create_error)
|
||||||
|
} finally {
|
||||||
|
this.querySelectorAll('input[type=submit]').forEach(el => {
|
||||||
|
el.removeAttribute('disabled')
|
||||||
|
el.classList.remove('disabled')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
api.elements.define('livechat-converse-muc-notes', MUCNotesView)
|
@ -9,6 +9,8 @@ import { ChatRoomNotes } from './notes.js'
|
|||||||
import { initOrDestroyChatRoomNotes, getHeadingButtons, getMessageActionButtons } from './utils.js'
|
import { initOrDestroyChatRoomNotes, getHeadingButtons, getMessageActionButtons } from './utils.js'
|
||||||
|
|
||||||
import './components/muc-note-app-view.js'
|
import './components/muc-note-app-view.js'
|
||||||
|
import './components/muc-notes-view.js'
|
||||||
|
import './components/muc-note-view.js'
|
||||||
|
|
||||||
converse.plugins.add('livechat-converse-notes', {
|
converse.plugins.add('livechat-converse-notes', {
|
||||||
dependencies: ['converse-muc', 'converse-disco', 'converse-pubsub'],
|
dependencies: ['converse-muc', 'converse-disco', 'converse-pubsub'],
|
||||||
|
@ -27,11 +27,20 @@ class ChatRoomNotes extends Collection {
|
|||||||
this.on('change:order', () => this.sort())
|
this.on('change:order', () => this.sort())
|
||||||
}
|
}
|
||||||
|
|
||||||
// async createNote (data) {
|
async createNote (data) {
|
||||||
// console.log('Creating note...')
|
data = Object.assign({}, data)
|
||||||
// await this.chatroom.NoteManager.createItem(this, Object.assign({}, data))
|
|
||||||
// console.log('Note created.')
|
if (!data.order) {
|
||||||
// }
|
data.order = 0 + Math.max(
|
||||||
|
0,
|
||||||
|
...(this.map(n => n.get('order') ?? 0).filter(o => !isNaN(o)))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Creating note...')
|
||||||
|
await this.chatroom.noteManager.createItem(this, data)
|
||||||
|
console.log('Note created.')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
40
conversejs/custom/plugins/notes/styles/muc-note.scss
Normal file
40
conversejs/custom/plugins/notes/styles/muc-note.scss
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
.conversejs {
|
||||||
|
livechat-converse-muc-note {
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.note-line {
|
||||||
|
border: 1px solid var(--chatroom-head-bg-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
justify-content: space-around;
|
||||||
|
margin: 0.25em 0;
|
||||||
|
padding: 0.25em;
|
||||||
|
column-gap: 0.25em;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.note-description {
|
||||||
|
flex-grow: 2;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-action {
|
||||||
|
background: unset;
|
||||||
|
border: 0;
|
||||||
|
padding-left: 0.25em;
|
||||||
|
padding-right: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
conversejs/custom/plugins/notes/styles/muc-notes.scss
Normal file
21
conversejs/custom/plugins/notes/styles/muc-notes.scss
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
.conversejs {
|
||||||
|
.notes-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
justify-content: right;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notes-action {
|
||||||
|
background: unset;
|
||||||
|
border: 0;
|
||||||
|
padding-left: 0.25em;
|
||||||
|
padding-right: 0.25em;
|
||||||
|
}
|
||||||
|
}
|
71
conversejs/custom/plugins/notes/templates/muc-note.js
Normal file
71
conversejs/custom/plugins/notes/templates/muc-note.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { html } from 'lit'
|
||||||
|
import { __ } from 'i18n'
|
||||||
|
|
||||||
|
export function tplMucNote (el, note) {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
const i18nDelete = __(LOC_moderator_note_delete)
|
||||||
|
|
||||||
|
return !el.edit
|
||||||
|
? html`
|
||||||
|
<div draggable="true" class="note-line">
|
||||||
|
<div class="note-description">${note.get('description') ?? ''}</div>
|
||||||
|
<button class="note-action" title="${__('Edit')}"
|
||||||
|
@click=${el.toggleEdit}
|
||||||
|
>
|
||||||
|
<converse-icon class="fa fa-edit" size="1em"></converse-icon>
|
||||||
|
</button>
|
||||||
|
<button class="note-action" title="${i18nDelete}"
|
||||||
|
@click=${el.deleteNote}
|
||||||
|
>
|
||||||
|
<converse-icon class="fa fa-trash-alt" size="1em"></converse-icon>
|
||||||
|
</button>
|
||||||
|
</div>`
|
||||||
|
: html`
|
||||||
|
<div class="note-line">
|
||||||
|
<form class="converse-form" @submit=${el.saveNote}>
|
||||||
|
${_tplNoteForm(note)}
|
||||||
|
<fieldset class="form-group">
|
||||||
|
<input type="submit" class="btn btn-primary" value="${__('Ok')}" />
|
||||||
|
<input type="button" class="btn btn-secondary button-cancel"
|
||||||
|
value="${__('Cancel')}" @click=${el.toggleEdit}
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</div>`
|
||||||
|
}
|
||||||
|
|
||||||
|
function _tplNoteForm (note) {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
const i18nNoteDesc = __(LOC_moderator_note_description)
|
||||||
|
|
||||||
|
return html`<fieldset class="form-group">
|
||||||
|
<textarea
|
||||||
|
class="form-control" name="description"
|
||||||
|
placeholder="${i18nNoteDesc}"
|
||||||
|
>${note ? note.get('description') : ''}</textarea>
|
||||||
|
</fieldset>`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tplMucCreateNoteForm (notesEl) {
|
||||||
|
const i18nOk = __('Ok')
|
||||||
|
const i18nCancel = __('Cancel')
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<form class="notes-create-note converse-form" @submit=${notesEl.submitCreateNote}>
|
||||||
|
${_tplNoteForm(undefined)}
|
||||||
|
<fieldset class="form-group">
|
||||||
|
<input type="submit" class="btn btn-primary" value="${i18nOk}" />
|
||||||
|
<input type="button" class="btn btn-secondary button-cancel"
|
||||||
|
value="${i18nCancel}" @click=${notesEl.closeCreateNoteForm}
|
||||||
|
/>
|
||||||
|
${!notesEl.create_note_error_message
|
||||||
|
? ''
|
||||||
|
: html`<div class="invalid-feedback d-block">${notesEl.create_note_error_message}</div>`
|
||||||
|
}
|
||||||
|
</fieldset>
|
||||||
|
</form>`
|
||||||
|
}
|
35
conversejs/custom/plugins/notes/templates/muc-notes.js
Normal file
35
conversejs/custom/plugins/notes/templates/muc-notes.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { html } from 'lit'
|
||||||
|
import { repeat } from 'lit/directives/repeat.js'
|
||||||
|
import { __ } from 'i18n'
|
||||||
|
import { tplMucCreateNoteForm } from './muc-note'
|
||||||
|
|
||||||
|
export default function tplMucNotes (el, notes) {
|
||||||
|
if (!notes) { // if user loses rights
|
||||||
|
return html`` // FIXME: add a message like "you dont have access"?
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
${
|
||||||
|
el.create_note_opened ? tplMucCreateNoteForm(el) : tplCreateButton(el)
|
||||||
|
}
|
||||||
|
${
|
||||||
|
repeat(notes, (note) => note.get('id'), (note) => {
|
||||||
|
return html`<livechat-converse-muc-note .model=${note}></livechat-converse-muc-note>`
|
||||||
|
})
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function tplCreateButton (el) {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
const i18nCreateNote = __(LOC_moderator_note_create)
|
||||||
|
return html`
|
||||||
|
<div class="notes-actions">
|
||||||
|
<button class="notes-action" title="${i18nCreateNote}" @click=${el.openCreateNoteForm}>
|
||||||
|
<converse-icon class="fa fa-plus" size="1em"></converse-icon>
|
||||||
|
</button>
|
||||||
|
</div>`
|
||||||
|
}
|
@ -86,7 +86,6 @@ function _initChatRoomNotes (mucModel) {
|
|||||||
xmlns: XMLNS_NOTE,
|
xmlns: XMLNS_NOTE,
|
||||||
collection: mucModel.notes,
|
collection: mucModel.notes,
|
||||||
fields: {
|
fields: {
|
||||||
name: String,
|
|
||||||
description: String
|
description: String
|
||||||
},
|
},
|
||||||
attributes: {
|
attributes: {
|
||||||
|
@ -50,7 +50,12 @@ const locKeys = [
|
|||||||
'poll_is_over',
|
'poll_is_over',
|
||||||
'poll_choice_invalid',
|
'poll_choice_invalid',
|
||||||
'poll_anonymous_vote_ok',
|
'poll_anonymous_vote_ok',
|
||||||
'moderator_notes'
|
'moderator_notes',
|
||||||
|
'moderator_notes_create_error',
|
||||||
|
'moderator_note_create',
|
||||||
|
'moderator_note_description',
|
||||||
|
'moderator_note_delete',
|
||||||
|
'moderator_note_delete_confirm'
|
||||||
]
|
]
|
||||||
|
|
||||||
module.exports = locKeys
|
module.exports = locKeys
|
||||||
|
@ -595,3 +595,8 @@ livechat_configuration_channel_anonymize_moderation_desc: |
|
|||||||
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
|
moderator_notes: Moderator notes
|
||||||
|
moderator_notes_create_error: 'Error when saving the note'
|
||||||
|
moderator_note_create: 'Create a new note'
|
||||||
|
moderator_note_description: 'Description'
|
||||||
|
moderator_note_delete: 'Delete note'
|
||||||
|
moderator_note_delete_confirm: 'Are you sure you want to delete this note?'
|
||||||
|
@ -48,9 +48,6 @@ local lib_pubsub = module:require "pubsub";
|
|||||||
local mod_muc = module:depends"muc";
|
local mod_muc = module:depends"muc";
|
||||||
local get_room_from_jid = mod_muc.get_room_from_jid;
|
local get_room_from_jid = mod_muc.get_room_from_jid;
|
||||||
|
|
||||||
local muc_util = module:require "muc/util";
|
|
||||||
local valid_roles = muc_util.valid_roles;
|
|
||||||
|
|
||||||
-- room_jid => object passed to module:add_items()
|
-- room_jid => object passed to module:add_items()
|
||||||
local mep_service_items = {};
|
local mep_service_items = {};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user