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 './components/muc-note-app-view.js'
|
||||
import './components/muc-notes-view.js'
|
||||
import './components/muc-note-view.js'
|
||||
|
||||
converse.plugins.add('livechat-converse-notes', {
|
||||
dependencies: ['converse-muc', 'converse-disco', 'converse-pubsub'],
|
||||
|
@ -27,11 +27,20 @@ class ChatRoomNotes extends Collection {
|
||||
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.')
|
||||
// }
|
||||
async createNote (data) {
|
||||
data = Object.assign({}, data)
|
||||
|
||||
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 {
|
||||
|
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,
|
||||
collection: mucModel.notes,
|
||||
fields: {
|
||||
name: String,
|
||||
description: String
|
||||
},
|
||||
attributes: {
|
||||
|
@ -50,7 +50,12 @@ const locKeys = [
|
||||
'poll_is_over',
|
||||
'poll_choice_invalid',
|
||||
'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
|
||||
|
@ -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.
|
||||
|
||||
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 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()
|
||||
local mep_service_items = {};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user