Search user messages WIP (#145)

This commit is contained in:
John Livingston 2024-08-01 18:58:25 +02:00
parent dd03075831
commit 4181661faf
No known key found for this signature in database
GPG Key ID: B17B5640CE66CDBC
13 changed files with 308 additions and 64 deletions

View File

@ -15,9 +15,9 @@ livechat-spinner,
height: 48px;
margin: 20px;
/* stylelint-disable-next-line custom-property-pattern */
border: 5px solid var(--greyBackgroundColor);
border: 5px solid var(--greyBackgroundColor) !important; // !important is required for it to work in ConverseJS
/* stylelint-disable-next-line custom-property-pattern */
border-bottom-color: var(--mainColor);
border-bottom-color: var(--mainColor) !important; // !important is required for it to work in ConverseJS
border-radius: 50%;
display: inline-block;
box-sizing: border-box;

View File

@ -220,27 +220,23 @@ async function initConverse (
// 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).
// Same for the moderator notes app.
let enableTask = false
let enableModeratorNotes = false
let enableApps = false
if (chatIncludeMode === 'peertube-video' || chatIncludeMode === 'peertube-fullpage') {
enableTask = true
enableModeratorNotes = true
enableApps = true
} else if (
chatIncludeMode === 'chat-only' &&
usedLivechatToken &&
!initConverseParams.transparent &&
!initConverseParams.forceReadonly
) {
enableTask = true
enableModeratorNotes = true
enableApps = true
}
if (enableTask) {
if (enableApps) {
params.livechat_task_app_enabled = true
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'
params.livechat_mam_search_app_enabled = true
}
try {

View File

@ -95,6 +95,18 @@ async function query (options) {
return { messages }
}
export default {
query
async function showMessagesFrom (occupant) {
const appElement = document.querySelector('livechat-converse-muc-mam-search-app')
if (!appElement) {
throw new Error('Cant find Search App Element')
}
appElement.searchFrom(occupant)
await appElement.showApp()
await appElement.updateComplete // waiting for the app to be open
return appElement
}
export default {
query,
showMessagesFrom
}

View File

@ -0,0 +1,55 @@
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
//
// SPDX-License-Identifier: AGPL-3.0-only
import { api } from '@converse/headless'
import { Collection } from '@converse/skeletor'
import { parseMUCMessage } from '@converse/headless/plugins/muc/parsers.js'
import { MUCApp } from '../../../shared/components/muc-app/index.js'
import { tplMamSearchApp } from '../templates/muc-mam-search-app.js'
/**
* Custom Element to display the Mam Search Application.
*/
export default class MUCMamSearchApp extends MUCApp {
restoreSettingName = undefined
sessionStorageRestoreKey = undefined
static get properties () {
return {
model: { type: Object, attribute: true }, // the muc model
occupant: { type: Object, attribute: true }, // the occupant to search (can be undefined if no current search)
results: { type: Object, attribute: true } // a Collection with the results.
}
}
render () {
return tplMamSearchApp(this, this.model, this.occupant)
}
searchFrom (occupant) {
this.results = undefined
this.occupant = occupant
const p = api.livechat_mam_search.query({
room: this.model.get('jid'),
// FIXME: shouldn't we escape the nick? cant see any code that escapes it in Converse.
from: occupant.get('from') || this.model.get('jid') + '/' + (occupant.get('nick') ?? ''),
occupant_id: occupant.get('occupant_id')
})
// don't wait the result to show something! (there will be a spinner)
p.then(async (results) => {
this.occupant = occupant // in case user did simultaneous requests
const messages = await Promise.all(results.messages.map(s => parseMUCMessage(s, this.model)))
const col = new Collection()
for (const message of messages) {
// FIXME: this does not work for now, the collection is not properly initiated (no storage engine)
col.create(message)
}
this.results = col
})
}
}
api.elements.define('livechat-converse-muc-mam-search-app', MUCMamSearchApp)

View File

@ -0,0 +1,27 @@
// 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 { tplMucMamSearchOccupant } from '../templates/muc-mam-search-occupant'
import { api } from '@converse/headless'
import '../styles/muc-mam-search-occupant.scss'
export default class MUCMamSearchOccupantView extends CustomElement {
static get properties () {
return {
model: { type: Object, attribute: true }
}
}
async initialize () {
this.listenTo(this.model, 'change', () => this.requestUpdate())
}
render () {
return tplMucMamSearchOccupant(this, this.model)
}
}
api.elements.define('livechat-converse-muc-mam-search-occupant', MUCMamSearchOccupantView)

View File

@ -3,9 +3,11 @@
// SPDX-License-Identifier: AGPL-3.0-only
import { api, converse } from '../../../src/headless/index.js'
import { XMLNS_MAM_SEARCH } from './constants.js'
import { getMessageActionButtons } from './utils.js'
import mamSearchApi from './api.js'
import { __ } from 'i18n'
import './components/muc-mam-search-app-view.js'
import './components/muc-mam-search-occupant-view.js'
converse.plugins.add('livechat-converse-mam-search', {
dependencies: ['converse-muc', 'converse-muc-views'],
@ -16,56 +18,13 @@ converse.plugins.add('livechat-converse-mam-search', {
livechat_mam_search: mamSearchApi
})
_converse.api.settings.extend({
livechat_mam_search_app_enabled: false
})
// Adding buttons on message:
_converse.api.listen.on('getMessageActionButtons', getMessageActionButtons)
// FIXME: should we listen to any event (feature/affiliation change?, mam_enabled?) to refresh messageActionButtons?
}
})
function getMessageActionButtons (messageActionsEl, buttons) {
const messageModel = messageActionsEl.model
if (messageModel.get('type') !== 'groupchat') {
// only on groupchat message.
return buttons
}
if (!messageModel.occupant) {
return buttons
}
const muc = messageModel.collection?.chatbox
if (!muc) {
return buttons
}
if (!muc.features?.get?.(XMLNS_MAM_SEARCH)) {
return buttons
}
const myself = muc.getOwnOccupant()
if (!myself || !['admin', 'owner'].includes(myself.get('affiliation'))) {
return buttons
}
// eslint-disable-next-line no-undef
const i18nSearch = __(LOC_search_occupant_message)
buttons.push({
i18n_text: i18nSearch,
handler: async (ev) => {
ev.preventDefault()
console.log(await api.livechat_mam_search.query({
room: muc.get('jid'),
// FIXME: shouldn't we escape the nick? cant see any code that escapes it in Converse.
from: messageModel.occupant.get('from') || muc.get('jid') + '/' + (messageModel.occupant.get('nick') ?? ''),
occupant_id: messageModel.occupant.get('occupant_id')
}))
},
button_class: '',
icon_class: 'fa fa-magnifying-glass',
name: 'muc-mam-search'
})
return buttons
}

View File

@ -0,0 +1,36 @@
/*
* SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
.conversejs {
livechat-converse-muc-mam-search-occupant {
display: flex;
flex-flow: row nowrap;
align-items: center;
justify-content: space-between;
padding: 0.25em;
& > a {
display: flex;
flex-flow: row nowrap;
align-items: center;
span {
font-weight: bold;
margin-left: 0.5em;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
}
}
& > ul {
font-weight: lighter;
font-size: 0.75em;
list-style: none;
text-align: right;
}
}
}

View File

@ -0,0 +1,63 @@
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
//
// SPDX-License-Identifier: AGPL-3.0-only
import { converseLocalizedHelpUrl } from '../../../shared/lib/help'
import { tplMUCApp } from '../../../shared/components/muc-app/templates/muc-app.js'
import { html } from 'lit'
import { repeat } from 'lit/directives/repeat.js'
import { __ } from 'i18n'
function tplContent (el, mucModel, occupantModel) {
return html`
${
occupantModel
? html`
<livechat-converse-muc-mam-search-occupant
.model=${occupantModel}
></livechat-converse-muc-mam-search-occupant>
`
: ''
}
${
el.results
? repeat(el.results, (message) => message.id, message => tplMessage(message))
: html`<livechat-spinner></livechat-spinner>`
}
`
}
function tplMessage (model) {
return html`
<converse-chat-message
jid="${this.model.get('jid')}"
mid="${model.get('id')}"
></converse-chat-message>`
}
export function tplMamSearchApp (el, mucModel, occupantModel) {
if (!mucModel) {
// should not happen
return html``
}
if (!el.show) {
return html``
}
// eslint-disable-next-line no-undef
const i18nSearch = __(LOC_message_search)
// eslint-disable-next-line no-undef
const i18nHelp = __(LOC_online_help)
const helpUrl = converseLocalizedHelpUrl({
page: 'documentation/user/streamers/moderation'
})
return tplMUCApp(
el,
i18nSearch,
helpUrl,
i18nHelp,
tplContent(el, mucModel, occupantModel)
)
}

View File

@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
//
// SPDX-License-Identifier: AGPL-3.0-only
import { html } from 'lit'
import { api } from '@converse/headless'
import { getAuthorStyle } from '../../../../src/utils/color.js'
import { __ } from 'i18n'
export function tplMucMamSearchOccupant (el, occupant) {
const authorStyle = getAuthorStyle(occupant)
const jid = occupant.get('jid')
const occupantId = occupant.get('occupant_id')
return html`
<a @click=${(ev) => {
api.modal.show('converse-muc-occupant-modal', { model: occupant }, ev)
}}>
<converse-avatar
.model=${occupant}
class="avatar chat-msg__avatar"
name="${occupant.getDisplayName()}"
nonce=${occupant.vcard?.get('vcard_updated')}
height="30" width="30"></converse-avatar>
<span style=${authorStyle}>${occupant.getDisplayName()}</span>
</a>
<ul aria-hidden="true">
${jid ? html`<li title=${__('XMPP Address')}>${jid}</li>` : ''}
${occupantId ? html`<li title=${__('Occupant Id')}>${occupantId}</li>` : ''}
</ul>`
}

View File

@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
//
// SPDX-License-Identifier: AGPL-3.0-only
import { api } from '../../../src/headless/index.js'
import { XMLNS_MAM_SEARCH } from './constants.js'
import { __ } from 'i18n'
function getMessageActionButtons (messageActionsEl, buttons) {
const messageModel = messageActionsEl.model
if (!api.settings.get('livechat_mam_search_app_enabled')) {
return buttons
}
if (messageModel.get('type') !== 'groupchat') {
// only on groupchat message.
return buttons
}
if (!messageModel.occupant) {
return buttons
}
const muc = messageModel.collection?.chatbox
if (!muc) {
return buttons
}
if (!muc.features?.get?.(XMLNS_MAM_SEARCH)) {
return buttons
}
const myself = muc.getOwnOccupant()
if (!myself || !['admin', 'owner'].includes(myself.get('affiliation'))) {
return buttons
}
// eslint-disable-next-line no-undef
const i18nSearch = __(LOC_search_occupant_message)
buttons.push({
i18n_text: i18nSearch,
handler: async (ev) => {
ev.preventDefault()
api.livechat_mam_search.showMessagesFrom(messageModel.occupant)
},
button_class: '',
icon_class: 'fa fa-magnifying-glass',
name: 'muc-mam-search'
})
return buttons
}
export {
getMessageActionButtons
}

View File

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

View File

@ -60,7 +60,8 @@ const locKeys = [
'moderator_note_search_for_participant',
'moderator_note_filters',
'moderator_note_original_nick',
'search_occupant_message'
'search_occupant_message',
'message_search'
]
module.exports = locKeys

View File

@ -605,4 +605,5 @@ moderator_note_search_for_participant: 'Search notes about this participant'
moderator_note_filters: 'Search filters'
moderator_note_original_nick: 'Nickname of the participant at the time of the note creation'
search_occupant_message: 'Search all message from this participant'
search_occupant_message: 'Search all messages from this participant'
message_search: 'Message search'