Moderator notes WIP (#144)
This commit is contained in:
parent
e81a7c90b8
commit
a46425d51f
@ -18,8 +18,8 @@ set -x
|
||||
CONVERSE_VERSION="v11.0.0"
|
||||
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.
|
||||
# 2024-07-30: using Converse upstream (v11 WIP).
|
||||
CONVERSE_COMMIT="9ddf6e7b7a83fdc04c8b55f0f470e59c09283a39"
|
||||
# 2024-07-31: using Converse upstream (v11 WIP).
|
||||
CONVERSE_COMMIT="2f8cfc02d04bb6191b3b9facb706e475836279f5"
|
||||
# # 2024-07-31: testing the jcbrand/bootstrap5 branch
|
||||
# CONVERSE_COMMIT="e5edeec997d53a8720470a49685be123e8688e1c"
|
||||
|
||||
|
31
conversejs/custom/plugins/notes/api.js
Normal file
31
conversejs/custom/plugins/notes/api.js
Normal file
@ -0,0 +1,31 @@
|
||||
async function openNotes () {
|
||||
const appElement = document.querySelector('livechat-converse-muc-note-app')
|
||||
if (!appElement) {
|
||||
throw new Error('Cant find Note App Element')
|
||||
}
|
||||
await appElement.showApp()
|
||||
await appElement.updateComplete // waiting for the app to be open
|
||||
|
||||
const notesElement = appElement.querySelector('livechat-converse-muc-notes')
|
||||
if (!notesElement) {
|
||||
throw new Error('Cant find Notes Element')
|
||||
}
|
||||
await notesElement.updateComplete
|
||||
return notesElement
|
||||
}
|
||||
|
||||
async function openCreateNoteForm (occupant) {
|
||||
const notesElement = await openNotes()
|
||||
await notesElement.openCreateNoteForm(undefined, occupant)
|
||||
}
|
||||
|
||||
async function searchNotesAbout (occupant) {
|
||||
const notesElement = await openNotes()
|
||||
await notesElement.filterNotes({ occupant })
|
||||
}
|
||||
|
||||
export default {
|
||||
openNotes,
|
||||
openCreateNoteForm,
|
||||
searchNotesAbout
|
||||
}
|
@ -13,7 +13,8 @@ export default class MUCNoteView extends CustomElement {
|
||||
static get properties () {
|
||||
return {
|
||||
model: { type: Object, attribute: true },
|
||||
edit: { type: Boolean, attribute: false }
|
||||
edit: { type: Boolean, attribute: false },
|
||||
is_ocupant_filter: { type: Boolean, attribute: true }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,8 @@ export default class MUCNotesView extends DraggablesCustomElement {
|
||||
model: { type: Object, attribute: true },
|
||||
create_note_error_message: { type: String, attribute: false },
|
||||
create_note_opened: { type: Boolean, attribute: false },
|
||||
create_note_for_occupant: { type: Object, attribute: false }
|
||||
create_note_about_occupant: { type: Object, attribute: false },
|
||||
occupant_filter: { type: Object, attribute: false }
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,7 +46,11 @@ export default class MUCNotesView extends DraggablesCustomElement {
|
||||
async openCreateNoteForm (ev, occupant) {
|
||||
ev?.preventDefault?.()
|
||||
this.create_note_opened = true
|
||||
this.create_note_for_occupant = occupant ?? undefined
|
||||
this.create_note_about_occupant = occupant ?? undefined
|
||||
if (this.create_note_about_occupant === undefined && this.occupant_filter) {
|
||||
// if we have a current filter, we can use it for the new note.
|
||||
this.create_note_about_occupant = this.occupant_filter
|
||||
}
|
||||
await this.updateComplete
|
||||
const textarea = this.querySelector('.notes-create-note textarea[name="description"]')
|
||||
if (textarea) {
|
||||
@ -56,7 +61,11 @@ export default class MUCNotesView extends DraggablesCustomElement {
|
||||
closeCreateNoteForm (ev) {
|
||||
ev?.preventDefault?.()
|
||||
this.create_note_opened = false
|
||||
this.create_note_for_occupant = undefined
|
||||
this.create_note_about_occupant = undefined
|
||||
}
|
||||
|
||||
filterNotes (filters) {
|
||||
this.occupant_filter = filters?.occupant || undefined
|
||||
}
|
||||
|
||||
async submitCreateNote (ev) {
|
||||
|
@ -7,6 +7,7 @@ import { XMLNS_NOTE } from './constants.js'
|
||||
import { ChatRoomNote } from './note.js'
|
||||
import { ChatRoomNotes } from './notes.js'
|
||||
import { initOrDestroyChatRoomNotes, getHeadingButtons, getMessageActionButtons } from './utils.js'
|
||||
import notesApi from './api.js'
|
||||
|
||||
import './components/muc-note-app-view.js'
|
||||
import './components/muc-notes-view.js'
|
||||
@ -30,6 +31,10 @@ converse.plugins.add('livechat-converse-notes', {
|
||||
livechat_note_app_restore: false // should we open the app by default if it was previously oppened?
|
||||
})
|
||||
|
||||
Object.assign(_converse.api, {
|
||||
livechat_notes: notesApi
|
||||
})
|
||||
|
||||
_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),
|
||||
|
@ -27,6 +27,8 @@
|
||||
}
|
||||
|
||||
& > ul {
|
||||
font-weight: lighter;
|
||||
font-size: 0.75em;
|
||||
list-style: none;
|
||||
text-align: right;
|
||||
}
|
||||
|
@ -20,8 +20,11 @@
|
||||
column-gap: 0.25em;
|
||||
width: 100%;
|
||||
|
||||
.note-description {
|
||||
.note-content {
|
||||
flex-grow: 2;
|
||||
}
|
||||
|
||||
.note-description {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
|
@ -18,4 +18,21 @@
|
||||
padding-left: 0.25em;
|
||||
padding-right: 0.25em;
|
||||
}
|
||||
|
||||
.notes-filters {
|
||||
border: 1px solid var(--chatroom-head-bg-color);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 0.25em 0;
|
||||
padding: 0.25em;
|
||||
column-gap: 0.25em;
|
||||
width: 100%;
|
||||
|
||||
livechat-converse-muc-note-occupant {
|
||||
flex-grow: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ export function tplMucNoteOccupant (el, occupant) {
|
||||
</a>
|
||||
${
|
||||
el.full_display
|
||||
? html`<ul>
|
||||
? html`<ul aria-hidden="true">
|
||||
${jid ? html`<li title=${__('XMPP Address')}>${jid}</li>` : ''}
|
||||
${occupantId ? html`<li title=${__('Occupant Id')}>${occupantId}</li>` : ''}
|
||||
</ul>`
|
||||
|
@ -2,26 +2,43 @@
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { api } from '@converse/headless'
|
||||
import { html } from 'lit'
|
||||
import { __ } from 'i18n'
|
||||
|
||||
export function tplMucNote (el, note) {
|
||||
// eslint-disable-next-line no-undef
|
||||
const i18nDelete = __(LOC_moderator_note_delete)
|
||||
// eslint-disable-next-line no-undef
|
||||
const i18nSearch = __(LOC_moderator_note_search_for_participant)
|
||||
|
||||
const aboutOccupant = note.getAboutOccupant()
|
||||
|
||||
return !el.edit
|
||||
? html`
|
||||
<div draggable="true" class="note-line draggables-line">
|
||||
<div class="note-description">${note.get('description') ?? ''}</div>
|
||||
${
|
||||
aboutOccupant
|
||||
? html`
|
||||
<livechat-converse-muc-note-occupant
|
||||
<div class="note-content">
|
||||
${
|
||||
aboutOccupant
|
||||
? html`
|
||||
<livechat-converse-muc-note-occupant
|
||||
.full_display=${el.is_ocupant_filter}
|
||||
.model=${aboutOccupant}
|
||||
></livechat-converse-muc-note-occupant>`
|
||||
: ''
|
||||
: ''
|
||||
}
|
||||
<div class="note-description">${note.get('description') ?? ''}</div>
|
||||
</div>
|
||||
${
|
||||
aboutOccupant && el.is_ocupant_filter
|
||||
? ''
|
||||
: html`
|
||||
<button type="button" class="note-action" @click=${ev => {
|
||||
ev.preventDefault()
|
||||
api.livechat_notes.searchNotesAbout(aboutOccupant)
|
||||
}}>
|
||||
<converse-icon class="fa fa-magnifying-glass" size="1em" title=${i18nSearch}></converse-icon>
|
||||
</button>`
|
||||
}
|
||||
<button class="note-action" title="${__('Edit')}"
|
||||
@click=${el.toggleEdit}
|
||||
|
@ -7,6 +7,57 @@ import { repeat } from 'lit/directives/repeat.js'
|
||||
import { __ } from 'i18n'
|
||||
import { tplMucCreateNoteForm } from './muc-note'
|
||||
|
||||
function tplFilters (el) {
|
||||
const filterOccupant = el.occupant_filter
|
||||
if (!filterOccupant) { return '' }
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const i18nSearch = __(LOC_moderator_note_filters)
|
||||
|
||||
return html`
|
||||
<div class="notes-filters">
|
||||
<converse-icon class="fa fa-magnifying-glass" size="1em" title=${i18nSearch}></converse-icon>
|
||||
${
|
||||
filterOccupant
|
||||
? html`<livechat-converse-muc-note-occupant
|
||||
full_display=${true}
|
||||
.model=${filterOccupant}
|
||||
></livechat-converse-muc-note-occupant>`
|
||||
: ''
|
||||
}
|
||||
<button class="notes-action" @click=${(ev) => {
|
||||
ev?.preventDefault()
|
||||
el.filterNotes({})
|
||||
}} title="${__('Close')}">
|
||||
<converse-icon class="fa fa-times" size="1em"></converse-icon>
|
||||
</button>
|
||||
</div>
|
||||
<hr/>
|
||||
`
|
||||
}
|
||||
|
||||
function isFiltered (el, note) {
|
||||
const filterOccupant = el.occupant_filter
|
||||
if (!filterOccupant) { return false }
|
||||
|
||||
const noteOccupant = note.getAboutOccupant()
|
||||
// there is an occupant filter, so if current note has no associated occupant, we can pass.
|
||||
if (!noteOccupant) { return true }
|
||||
|
||||
if (noteOccupant === filterOccupant) {
|
||||
// Yes!
|
||||
return false
|
||||
}
|
||||
|
||||
// We will also test for nickname, so that we can found anonymous users
|
||||
// (they can have multiple associated occupants)
|
||||
if (filterOccupant.get('nick') && filterOccupant.get('nick') === noteOccupant.get('nick')) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export default function tplMucNotes (el, notes) {
|
||||
if (!notes) { // if user loses rights
|
||||
return html`` // FIXME: add a message like "you dont have access"?
|
||||
@ -14,11 +65,17 @@ export default function tplMucNotes (el, notes) {
|
||||
|
||||
return html`
|
||||
${
|
||||
el.create_note_opened ? tplMucCreateNoteForm(el, el.create_note_for_occupant) : tplCreateButton(el)
|
||||
el.create_note_opened ? tplMucCreateNoteForm(el, el.create_note_about_occupant) : tplCreateButton(el)
|
||||
}
|
||||
${tplFilters(el)}
|
||||
${
|
||||
repeat(notes, (note) => note.get('id'), (note) => {
|
||||
return html`<livechat-converse-muc-note .model=${note}></livechat-converse-muc-note>`
|
||||
return isFiltered(el, note)
|
||||
? ''
|
||||
: html`<livechat-converse-muc-note
|
||||
.model=${note}
|
||||
.is_ocupant_filter=${!!el.occupant_filter}
|
||||
></livechat-converse-muc-note>`
|
||||
})
|
||||
}`
|
||||
}
|
||||
|
@ -51,29 +51,30 @@ export function getMessageActionButtons (messageActionsEl, buttons) {
|
||||
if (messageModel.occupant) {
|
||||
// eslint-disable-next-line no-undef
|
||||
const i18nCreate = __(LOC_moderator_note_create_for_participant)
|
||||
// eslint-disable-next-line no-undef
|
||||
const i18nSearch = __(LOC_moderator_note_search_for_participant)
|
||||
|
||||
buttons.push({
|
||||
i18n_text: i18nCreate,
|
||||
handler: async (ev) => {
|
||||
ev.preventDefault()
|
||||
const appElement = document.querySelector('livechat-converse-muc-note-app')
|
||||
if (!appElement) {
|
||||
throw new Error('Cant find Note App Element')
|
||||
}
|
||||
await appElement.showApp()
|
||||
await appElement.updateComplete // waiting for the app to be open
|
||||
|
||||
const notesElement = appElement.querySelector('livechat-converse-muc-notes')
|
||||
if (!notesElement) {
|
||||
throw new Error('Cant find Notes Element')
|
||||
}
|
||||
await notesElement.updateComplete
|
||||
notesElement.openCreateNoteForm(undefined, messageModel.occupant)
|
||||
await api.livechat_notes.openCreateNoteForm(messageModel.occupant)
|
||||
},
|
||||
button_class: '',
|
||||
icon_class: 'fa fa-note-sticky',
|
||||
name: 'muc-note-create-for-occupant'
|
||||
})
|
||||
|
||||
buttons.push({
|
||||
i18n_text: i18nSearch,
|
||||
handler: async (ev) => {
|
||||
ev.preventDefault()
|
||||
await api.livechat_notes.searchNotesAbout(messageModel.occupant)
|
||||
},
|
||||
button_class: '',
|
||||
icon_class: 'fa fa-magnifying-glass',
|
||||
name: 'muc-note-search-for-occupant'
|
||||
})
|
||||
}
|
||||
|
||||
return buttons
|
||||
|
@ -33,6 +33,11 @@ export default () => {
|
||||
<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>
|
||||
|
||||
<!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.-->
|
||||
<symbol id="icon-magnifying-glass" viewBox="0 0 512 512">
|
||||
<path d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
`
|
||||
}
|
||||
|
@ -56,7 +56,9 @@ const locKeys = [
|
||||
'moderator_note_description',
|
||||
'moderator_note_delete',
|
||||
'moderator_note_delete_confirm',
|
||||
'moderator_note_create_for_participant'
|
||||
'moderator_note_create_for_participant',
|
||||
'moderator_note_search_for_participant',
|
||||
'moderator_note_filters'
|
||||
]
|
||||
|
||||
module.exports = locKeys
|
||||
|
@ -600,4 +600,6 @@ 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?'
|
||||
moderator_note_create_for_participant: 'Create a new note for this participant'
|
||||
moderator_note_create_for_participant: 'Create a new note about this participant'
|
||||
moderator_note_search_for_participant: 'Search notes about this participant'
|
||||
moderator_note_filters: 'Search filters'
|
||||
|
Loading…
Reference in New Issue
Block a user