From 9fbd2e5c3d771ae57078ca6c9d5c6b5ec118ee60 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 26 Oct 2021 02:08:14 -0400 Subject: [PATCH] chat: New `chatSendMessage` client-side hook --- CHANGELOG.md | 3 ++ doc/api/hooks_client-side.md | 12 ++++++ src/static/js/chat.js | 6 ++- src/tests/frontend/specs/chat_hooks.js | 53 +++++++++++++++++++++++++- 4 files changed, 71 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82b936620..a2f74e1b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,9 @@ see the original unprocessed message text and any added metadata. * `rendered`: Allows plugins to completely override how the message is rendered in the UI. + * New `chatSendMessage` client-side hook that enables plugins to process the + text before sending it to the server or augment the message object with + custom metadata. # 1.8.14 diff --git a/doc/api/hooks_client-side.md b/doc/api/hooks_client-side.md index dc5608931..45ef18a01 100755 --- a/doc/api/hooks_client-side.md +++ b/doc/api/hooks_client-side.md @@ -315,6 +315,18 @@ Context properties: * `duration`: How long (in milliseconds) to display the gritter notification (0 to disable). +## `chatSendMessage` + +Called from: `src/static/js/chat.js` + +This hook runs on the client side whenever the user sends a new chat message. +Plugins can mutate the message object to change the message text or add metadata +to control how the message will be rendered by the `chatNewMessage` hook. + +Context properties: + +* `message`: The message object that will be sent to the Etherpad server. + ## collectContentPre Called from: src/static/js/contentcollector.js diff --git a/src/static/js/chat.js b/src/static/js/chat.js index 51da91c6d..088d16111 100755 --- a/src/static/js/chat.js +++ b/src/static/js/chat.js @@ -100,10 +100,12 @@ exports.chat = (() => { } } }, - send() { + async send() { const text = $('#chatinput').val(); if (text.replace(/\s+/, '').length === 0) return; - this._pad.collabClient.sendMessage({type: 'CHAT_MESSAGE', message: new ChatMessage(text)}); + const message = new ChatMessage(text); + await hooks.aCallAll('chatSendMessage', Object.freeze({message})); + this._pad.collabClient.sendMessage({type: 'CHAT_MESSAGE', message}); $('#chatinput').val(''); }, async addMessage(msg, increment, isHistoryAdd) { diff --git a/src/tests/frontend/specs/chat_hooks.js b/src/tests/frontend/specs/chat_hooks.js index a307ad5b6..eb6aeb6ea 100644 --- a/src/tests/frontend/specs/chat_hooks.js +++ b/src/tests/frontend/specs/chat_hooks.js @@ -4,9 +4,10 @@ describe('chat hooks', function () { let ChatMessage; let hooks; const hooksBackup = {}; + let padId; const loadPad = async (opts = {}) => { - await helper.aNewPad(opts); + padId = await helper.aNewPad(opts); ChatMessage = helper.padChrome$.window.require('ep_etherpad-lite/static/js/ChatMessage'); ({hooks} = helper.padChrome$.window.require('ep_etherpad-lite/static/js/pluginfw/plugin_defs')); for (const [name, defs] of Object.entries(hooks)) { @@ -95,4 +96,54 @@ describe('chat hooks', function () { expect(helper.chatTextParagraphs().last()[0]).to.be(rendered); }); }); + + describe('chatSendMessage', function () { + it('message is a ChatMessage object', async function () { + await Promise.all([ + checkHook('chatSendMessage', ({message}) => { + expect(message).to.be.a(ChatMessage); + }), + helper.sendChatMessage(`${this.test.title}{enter}`), + ]); + }); + + it('message metadata propagates end-to-end', async function () { + const metadata = {foo: this.test.title}; + await Promise.all([ + checkHook('chatSendMessage', ({message}) => { + message.customMetadata = metadata; + }), + checkHook('chatNewMessage', ({message: {customMetadata}}) => { + expect(JSON.stringify(customMetadata)).to.equal(JSON.stringify(metadata)); + }), + helper.sendChatMessage(`${this.test.title}{enter}`), + ]); + }); + + it('message metadata is saved in the database', async function () { + const msg = this.test.title; + const metadata = {foo: this.test.title}; + await Promise.all([ + checkHook('chatSendMessage', ({message}) => { + message.customMetadata = metadata; + }), + helper.sendChatMessage(`${msg}{enter}`), + ]); + let gotMessage; + const messageP = new Promise((resolve) => gotMessage = resolve); + await loadPad({ + id: padId, + hookFns: { + chatNewMessage: [ + (hookName, {message}) => { + if (message.text === `${msg}\n`) gotMessage(message); + }, + ], + }, + }); + const message = await messageP; + expect(message).to.be.a(ChatMessage); + expect(JSON.stringify(message.customMetadata)).to.equal(JSON.stringify(metadata)); + }); + }); });