diff --git a/CHANGELOG.md b/CHANGELOG.md index 80dc6b6f8..82b936620 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,8 @@ * The `chatNewMessage` client-side hook context has new properties: * `message`: Provides access to the raw message object so that plugins can see the original unprocessed message text and any added metadata. + * `rendered`: Allows plugins to completely override how the message is + rendered in the UI. # 1.8.14 diff --git a/doc/api/hooks_client-side.md b/doc/api/hooks_client-side.md index 031d490e1..d507459c2 100755 --- a/doc/api/hooks_client-side.md +++ b/doc/api/hooks_client-side.md @@ -285,11 +285,11 @@ Called from: `src/static/js/chat.js` This hook runs on the client side whenever a chat message is received from the server. It can be used to create different notifications for chat messages. Hook -functions can modify the `author`, `authorName`, `duration`, `sticky`, `text`, -and `timeStr` context properties to change how the message is processed. The -`text` and `timeStr` properties may contain HTML and come pre-sanitized; plugins -should be careful to sanitize any added user input to avoid introducing an XSS -vulnerability. +functions can modify the `author`, `authorName`, `duration`, `rendered`, +`sticky`, `text`, and `timeStr` context properties to change how the message is +processed. The `text` and `timeStr` properties may contain HTML and come +pre-sanitized; plugins should be careful to sanitize any added user input to +avoid introducing an XSS vulnerability. Context properties: @@ -302,6 +302,11 @@ Context properties: time correction and a default `userId` property if missing. Plugins must not modify this object. Warning: Unlike `text`, `message.text` is not pre-sanitized or processed in any way. +* `rendered` - Used to override the default message rendering. Initially set to + `null`. If the hook function sets this to a DOM element object or a jQuery + object, then that object will be used as the rendered message UI. Otherwise, + if this is set to `null`, then Etherpad will render a default UI for the + message using the other context properties. * `sticky` (boolean): Whether the gritter notification should fade out on its own or just sit there until manually closed. * `timestamp`: When the chat message was sent (milliseconds since epoch), diff --git a/src/static/js/chat.js b/src/static/js/chat.js index c9e8046ba..ef150065e 100755 --- a/src/static/js/chat.js +++ b/src/static/js/chat.js @@ -132,6 +132,7 @@ exports.chat = (() => { author: msg.userId, text: padutils.escapeHtmlWithClickableLinks(msg.text, '_blank'), message: msg, + rendered: null, sticky: false, timestamp: msg.time, timeStr: (() => { @@ -164,7 +165,7 @@ exports.chat = (() => { await hooks.aCallAll('chatNewMessage', ctx); const cls = authorClass(ctx.author); - const chatMsg = $('

') + const chatMsg = ctx.rendered != null ? $(ctx.rendered) : $('

') .attr('data-authorId', ctx.author) .addClass(cls) .append($('').text(`${ctx.authorName}:`)) diff --git a/src/tests/frontend/specs/chat_hooks.js b/src/tests/frontend/specs/chat_hooks.js index 314ddfc6b..eb305e40a 100644 --- a/src/tests/frontend/specs/chat_hooks.js +++ b/src/tests/frontend/specs/chat_hooks.js @@ -79,5 +79,18 @@ describe('chat hooks', function () { helper.sendChatMessage(`${msg}{enter}`), ]); }); + + it('`rendered` overrides default rendering', async function () { + let rendered; + await Promise.all([ + checkHook('chatNewMessage', (context) => { + expect(context.rendered == null).to.be.ok(); + rendered = context.rendered = helper.padChrome$.document.createElement('p'); + rendered.append('message rendering overridden'); + }), + helper.sendChatMessage(`${this.test.title}{enter}`), + ]); + expect(helper.chatTextParagraphs().last()[0]).to.be(rendered); + }); }); });