Skip to content

Commit

Permalink
Moderator notes WIP (#144)
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnXLivingston committed Jul 30, 2024
1 parent 9ed20b4 commit a17a72d
Show file tree
Hide file tree
Showing 12 changed files with 398 additions and 10 deletions.
109 changes: 109 additions & 0 deletions conversejs/custom/plugins/notes/components/muc-note-view.js
Original file line number Diff line number Diff line change
@@ -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 changes: 95 additions & 0 deletions conversejs/custom/plugins/notes/components/muc-notes-view.js
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 2 additions & 0 deletions conversejs/custom/plugins/notes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down
19 changes: 14 additions & 5 deletions conversejs/custom/plugins/notes/notes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
40 changes: 40 additions & 0 deletions conversejs/custom/plugins/notes/styles/muc-note.scss
Original file line number Diff line number Diff line change
@@ -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 changes: 21 additions & 0 deletions conversejs/custom/plugins/notes/styles/muc-notes.scss
Original file line number Diff line number Diff line change
@@ -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 changes: 71 additions & 0 deletions conversejs/custom/plugins/notes/templates/muc-note.js
Original file line number Diff line number Diff line change
@@ -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>`
}
Loading

0 comments on commit a17a72d

Please sign in to comment.