From 4181661fafa1742f2b8c785dfb63fed0a8e297ad Mon Sep 17 00:00:00 2001 From: John Livingston Date: Thu, 1 Aug 2024 18:58:25 +0200 Subject: [PATCH] Search user messages WIP (#145) --- assets/styles/elements/_spinner.scss | 4 +- conversejs/builtin.ts | 14 ++--- conversejs/custom/plugins/mam-search/api.js | 16 ++++- .../components/muc-mam-search-app-view.js | 55 ++++++++++++++++ .../muc-mam-search-occupant-view.js | 27 ++++++++ conversejs/custom/plugins/mam-search/index.js | 57 +++-------------- .../styles/muc-mam-search-occupant.scss | 36 +++++++++++ .../templates/muc-mam-search-app.js | 63 +++++++++++++++++++ .../templates/muc-mam-search-occupant.js | 32 ++++++++++ conversejs/custom/plugins/mam-search/utils.js | 57 +++++++++++++++++ conversejs/custom/templates/muc-chatarea.js | 5 ++ conversejs/loc.keys.js | 3 +- languages/en.yml | 3 +- 13 files changed, 308 insertions(+), 64 deletions(-) create mode 100644 conversejs/custom/plugins/mam-search/components/muc-mam-search-app-view.js create mode 100644 conversejs/custom/plugins/mam-search/components/muc-mam-search-occupant-view.js create mode 100644 conversejs/custom/plugins/mam-search/styles/muc-mam-search-occupant.scss create mode 100644 conversejs/custom/plugins/mam-search/templates/muc-mam-search-app.js create mode 100644 conversejs/custom/plugins/mam-search/templates/muc-mam-search-occupant.js create mode 100644 conversejs/custom/plugins/mam-search/utils.js diff --git a/assets/styles/elements/_spinner.scss b/assets/styles/elements/_spinner.scss index 672bf01c..9fa9e39d 100644 --- a/assets/styles/elements/_spinner.scss +++ b/assets/styles/elements/_spinner.scss @@ -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; diff --git a/conversejs/builtin.ts b/conversejs/builtin.ts index 2f6c3928..9cb1cc5a 100644 --- a/conversejs/builtin.ts +++ b/conversejs/builtin.ts @@ -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 { diff --git a/conversejs/custom/plugins/mam-search/api.js b/conversejs/custom/plugins/mam-search/api.js index 93f7e2be..77fe71fe 100644 --- a/conversejs/custom/plugins/mam-search/api.js +++ b/conversejs/custom/plugins/mam-search/api.js @@ -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 } diff --git a/conversejs/custom/plugins/mam-search/components/muc-mam-search-app-view.js b/conversejs/custom/plugins/mam-search/components/muc-mam-search-app-view.js new file mode 100644 index 00000000..3b3585f0 --- /dev/null +++ b/conversejs/custom/plugins/mam-search/components/muc-mam-search-app-view.js @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// 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) diff --git a/conversejs/custom/plugins/mam-search/components/muc-mam-search-occupant-view.js b/conversejs/custom/plugins/mam-search/components/muc-mam-search-occupant-view.js new file mode 100644 index 00000000..c88f60dd --- /dev/null +++ b/conversejs/custom/plugins/mam-search/components/muc-mam-search-occupant-view.js @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// 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) diff --git a/conversejs/custom/plugins/mam-search/index.js b/conversejs/custom/plugins/mam-search/index.js index 17ca2551..d5efd510 100644 --- a/conversejs/custom/plugins/mam-search/index.js +++ b/conversejs/custom/plugins/mam-search/index.js @@ -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 -} diff --git a/conversejs/custom/plugins/mam-search/styles/muc-mam-search-occupant.scss b/conversejs/custom/plugins/mam-search/styles/muc-mam-search-occupant.scss new file mode 100644 index 00000000..2678b926 --- /dev/null +++ b/conversejs/custom/plugins/mam-search/styles/muc-mam-search-occupant.scss @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2024 John Livingston + * + * 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; + } + } +} diff --git a/conversejs/custom/plugins/mam-search/templates/muc-mam-search-app.js b/conversejs/custom/plugins/mam-search/templates/muc-mam-search-app.js new file mode 100644 index 00000000..c4ee4371 --- /dev/null +++ b/conversejs/custom/plugins/mam-search/templates/muc-mam-search-app.js @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// 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` + + ` + : '' + } + ${ + el.results + ? repeat(el.results, (message) => message.id, message => tplMessage(message)) + : html`` + } + ` +} + +function tplMessage (model) { + return html` + ` +} + +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) + ) +} diff --git a/conversejs/custom/plugins/mam-search/templates/muc-mam-search-occupant.js b/conversejs/custom/plugins/mam-search/templates/muc-mam-search-occupant.js new file mode 100644 index 00000000..408dcb18 --- /dev/null +++ b/conversejs/custom/plugins/mam-search/templates/muc-mam-search-occupant.js @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// 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` + { + api.modal.show('converse-muc-occupant-modal', { model: occupant }, ev) + }}> + + + ${occupant.getDisplayName()} + + ` +} diff --git a/conversejs/custom/plugins/mam-search/utils.js b/conversejs/custom/plugins/mam-search/utils.js new file mode 100644 index 00000000..baaf7788 --- /dev/null +++ b/conversejs/custom/plugins/mam-search/utils.js @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// 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 +} diff --git a/conversejs/custom/templates/muc-chatarea.js b/conversejs/custom/templates/muc-chatarea.js index 6980b17f..d2067c4e 100644 --- a/conversejs/custom/templates/muc-chatarea.js +++ b/conversejs/custom/templates/muc-chatarea.js @@ -18,5 +18,10 @@ export default (o) => { ? html`` : '' } + ${ + o?.model && api.settings.get('livechat_mam_search_app_enabled') + ? html`` + : '' + } ${tplMUCChatarea(o)}` } diff --git a/conversejs/loc.keys.js b/conversejs/loc.keys.js index e761bdc6..8cdd6edc 100644 --- a/conversejs/loc.keys.js +++ b/conversejs/loc.keys.js @@ -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 diff --git a/languages/en.yml b/languages/en.yml index 4db677b6..47c850d4 100644 --- a/languages/en.yml +++ b/languages/en.yml @@ -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' \ No newline at end of file