浏览代码

Render mentions in a message

JC Brand 6 年之前
父节点
当前提交
b7eb19e225
共有 9 个文件被更改,包括 102 次插入18 次删除
  1. 2 1
      CHANGES.md
  2. 4 3
      css/converse.css
  3. 0 3
      sass/_chatrooms.scss
  4. 6 0
      sass/_messages.scss
  5. 55 9
      spec/messages.js
  6. 13 0
      src/converse-chatboxes.js
  7. 1 0
      src/converse-message-view.js
  8. 2 2
      src/converse-muc.js
  9. 19 0
      src/utils/core.js

+ 2 - 1
CHANGES.md

@@ -21,8 +21,9 @@
 - Add a checkbox to indicate whether a trusted device is being used or not.
 - Add a checkbox to indicate whether a trusted device is being used or not.
   If the device is not trusted, sessionStorage is used and all user data is deleted from the browser cache upon logout.
   If the device is not trusted, sessionStorage is used and all user data is deleted from the browser cache upon logout.
   If the device is trusted, localStorage is used and user data is cached indefinitely.
   If the device is trusted, localStorage is used and user data is cached indefinitely.
-- Initial support for XEP-0357 Push Notifications, specifically registering an "App Server".
+- Initial support for [XEP-0357 Push Notifications](https://xmpp.org/extensions/xep-0357.html), specifically registering an "App Server".
 - Add support for logging in via OAuth (see the [oauth_providers](https://conversejs.org/docs/html/configurations.html#oauth-providers) setting)
 - Add support for logging in via OAuth (see the [oauth_providers](https://conversejs.org/docs/html/configurations.html#oauth-providers) setting)
+- Add support for [XEP-0372 References](https://xmpp.org/extensions/xep-0372.html), specifically section "3.2 Mentions".
 
 
 ### Bugfixes
 ### Bugfixes
 
 

+ 4 - 3
css/converse.css

@@ -8540,9 +8540,6 @@ body.reset {
         #conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .chat-info.badge,
         #conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .chat-info.badge,
         #conversejs .chatroom .box-flyout .chatroom-body .chat-info.badge {
         #conversejs .chatroom .box-flyout .chatroom-body .chat-info.badge {
           color: white; }
           color: white; }
-      #conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .mentioned,
-      #conversejs .chatroom .box-flyout .chatroom-body .mentioned {
-        font-weight: bold; }
       #conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .disconnect-container,
       #conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .disconnect-container,
       #conversejs .chatroom .box-flyout .chatroom-body .disconnect-container {
       #conversejs .chatroom .box-flyout .chatroom-body .disconnect-container {
         margin: 1em;
         margin: 1em;
@@ -8799,6 +8796,10 @@ body.reset {
   border: 1.2em solid #E7A151;
   border: 1.2em solid #E7A151;
   border-top: 0.8em solid #E7A151; }
   border-top: 0.8em solid #E7A151; }
 
 
+#conversejs .message .mention {
+  font-weight: bold; }
+#conversejs .message .mention--self {
+  font-weight: normal; }
 #conversejs .message.date-separator {
 #conversejs .message.date-separator {
   height: 2em;
   height: 2em;
   margin: 0;
   margin: 0;

+ 0 - 3
sass/_chatrooms.scss

@@ -116,9 +116,6 @@
                         color: $chat-head-text-color;
                         color: $chat-head-text-color;
                     }
                     }
                 }
                 }
-                .mentioned {
-                    font-weight: bold;
-                }
                 .disconnect-container {
                 .disconnect-container {
                     margin: 1em;
                     margin: 1em;
                     width: 100%;
                     width: 100%;

+ 6 - 0
sass/_messages.scss

@@ -1,6 +1,12 @@
 #conversejs {
 #conversejs {
     .message {
     .message {
 
 
+        .mention {
+            font-weight: bold;
+        }
+        .mention--self {
+            font-weight: normal;
+        }
         &.date-separator {
         &.date-separator {
             height: 2em;
             height: 2em;
             margin: 0;
             margin: 0;

+ 55 - 9
spec/messages.js

@@ -2089,6 +2089,52 @@
             }).catch(_.partial(console.error, _));
             }).catch(_.partial(console.error, _));
         }));
         }));
 
 
+        describe("when received", function () {
+
+            it("highlights all users mentioned via XEP-0372 references", 
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched'], {},
+                        function (done, _converse) {
+
+                test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'tom')
+                .then(() => {
+                    const view = _converse.chatboxviews.get('lounge@localhost');
+                    ['z3r0', 'mr.robot', 'gibson', 'sw0rdf1sh'].forEach((nick) => {
+                        _converse.connection._dataRecv(test_utils.createRequest(
+                            $pres({
+                                'to': 'tom@localhost/resource',
+                                'from': `lounge@localhost/${nick}`
+                            })
+                            .c('x', {xmlns: Strophe.NS.MUC_USER})
+                            .c('item', {
+                                'affiliation': 'none',
+                                'jid': `${nick}@localhost/resource`,
+                                'role': 'participant'
+                            }))
+                        );
+                    });
+
+                    const msg = $msg({
+                            from: 'lounge@localhost/gibson',
+                            id: (new Date()).getTime(),
+                            to: 'dummy@localhost',
+                            type: 'groupchat'
+                        }).c('body').t('hello z3r0 tom mr.robot, how are you?').up()
+                            .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'6', 'end':'10', 'type':'mention', 'uri':'xmpp:z3r0@localhost'}).up()
+                            .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'11', 'end':'14', 'type':'mention', 'uri':'xmpp:dummy@localhost'}).up()
+                            .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'15', 'end':'23', 'type':'mention', 'uri':'xmpp:mr.robot@localhost'}).nodeTree;
+                    view.model.onMessage(msg);
+
+                    expect(view.el.querySelectorAll('.chat-msg__text').length).toBe(1);
+                    expect(view.el.querySelector('.chat-msg__text').outerHTML).toBe(
+                        '<div class="chat-msg__text">hello <span class="mention">z3r0</span> '+
+                        '<span class="mention mention--self badge badge-info">tom</span> '+
+                        '<span class="mention">mr.robot</span>, how are you?</div>');
+                    done();
+                }).catch(_.partial(console.error, _));
+            }));
+        });
+
         describe("in which someone is mentioned", function () {
         describe("in which someone is mentioned", function () {
 
 
             it("gets parsed for mentions which get turned into references", 
             it("gets parsed for mentions which get turned into references", 
@@ -2113,33 +2159,33 @@
                             })));
                             })));
                     });
                     });
 
 
-                    // Run a few unit tests for the parseForReferences method
-                    let [text, references] = view.model.parseForReferences('hello z3r0')
+                    // Run a few unit tests for the parseTextForReferences method
+                    let [text, references] = view.model.parseTextForReferences('hello z3r0')
                     expect(references.length).toBe(0);
                     expect(references.length).toBe(0);
                     expect(text).toBe('hello z3r0');
                     expect(text).toBe('hello z3r0');
 
 
-                    [text, references] = view.model.parseForReferences('hello @z3r0')
+                    [text, references] = view.model.parseTextForReferences('hello @z3r0')
                     expect(references.length).toBe(1);
                     expect(references.length).toBe(1);
                     expect(text).toBe('hello z3r0');
                     expect(text).toBe('hello z3r0');
                     expect(JSON.stringify(references))
                     expect(JSON.stringify(references))
                         .toBe('[{"begin":6,"end":10,"type":"mention","uri":"xmpp:z3r0@localhost"}]');
                         .toBe('[{"begin":6,"end":10,"type":"mention","uri":"xmpp:z3r0@localhost"}]');
 
 
-                    [text, references] = view.model.parseForReferences('hello @some1 @z3r0 @gibson @mr.robot, how are you?')
+                    [text, references] = view.model.parseTextForReferences('hello @some1 @z3r0 @gibson @mr.robot, how are you?')
                     expect(text).toBe('hello @some1 z3r0 gibson mr.robot, how are you?');
                     expect(text).toBe('hello @some1 z3r0 gibson mr.robot, how are you?');
                     expect(JSON.stringify(references))
                     expect(JSON.stringify(references))
                         .toBe('[{"begin":13,"end":17,"type":"mention","uri":"xmpp:z3r0@localhost"},'+
                         .toBe('[{"begin":13,"end":17,"type":"mention","uri":"xmpp:z3r0@localhost"},'+
                                '{"begin":18,"end":24,"type":"mention","uri":"xmpp:gibson@localhost"},'+
                                '{"begin":18,"end":24,"type":"mention","uri":"xmpp:gibson@localhost"},'+
                                '{"begin":25,"end":33,"type":"mention","uri":"xmpp:mr.robot@localhost"}]');
                                '{"begin":25,"end":33,"type":"mention","uri":"xmpp:mr.robot@localhost"}]');
 
 
-                    [text, references] = view.model.parseForReferences('yo @gib')
+                    [text, references] = view.model.parseTextForReferences('yo @gib')
                     expect(text).toBe('yo @gib');
                     expect(text).toBe('yo @gib');
                     expect(references.length).toBe(0);
                     expect(references.length).toBe(0);
 
 
-                    [text, references] = view.model.parseForReferences('yo @gibsonian')
+                    [text, references] = view.model.parseTextForReferences('yo @gibsonian')
                     expect(text).toBe('yo @gibsonian');
                     expect(text).toBe('yo @gibsonian');
                     expect(references.length).toBe(0);
                     expect(references.length).toBe(0);
 
 
-                    [text, references] = view.model.parseForReferences('@gibson')
+                    [text, references] = view.model.parseTextForReferences('@gibson')
                     expect(text).toBe('gibson');
                     expect(text).toBe('gibson');
                     expect(references.length).toBe(1);
                     expect(references.length).toBe(1);
                     expect(JSON.stringify(references))
                     expect(JSON.stringify(references))
@@ -2190,9 +2236,9 @@
                                 `xmlns='jabber:client'>`+
                                 `xmlns='jabber:client'>`+
                                     `<body>hello z3r0 gibson mr.robot, how are you?</body>`+
                                     `<body>hello z3r0 gibson mr.robot, how are you?</body>`+
                                     `<active xmlns='http://jabber.org/protocol/chatstates'/>`+
                                     `<active xmlns='http://jabber.org/protocol/chatstates'/>`+
-                                    `<reference xmlns='urn:xmpp:reference:0' begin='6' end='10' type='mention' uri='xmpp:z3r0@localhost'/>`+
-                                    `<reference xmlns='urn:xmpp:reference:0' begin='11' end='17' type='mention' uri='xmpp:gibson@localhost'/>`+
                                     `<reference xmlns='urn:xmpp:reference:0' begin='18' end='26' type='mention' uri='xmpp:mr.robot@localhost'/>`+
                                     `<reference xmlns='urn:xmpp:reference:0' begin='18' end='26' type='mention' uri='xmpp:mr.robot@localhost'/>`+
+                                    `<reference xmlns='urn:xmpp:reference:0' begin='11' end='17' type='mention' uri='xmpp:gibson@localhost'/>`+
+                                    `<reference xmlns='urn:xmpp:reference:0' begin='6' end='10' type='mention' uri='xmpp:z3r0@localhost'/>`+
                               `</message>`);
                               `</message>`);
                     done();
                     done();
                 }).catch(_.partial(console.error, _));
                 }).catch(_.partial(console.error, _));

+ 13 - 0
src/converse-chatboxes.js

@@ -299,6 +299,7 @@
                         older_versions.push(message.get('message'));
                         older_versions.push(message.get('message'));
                         message.save({
                         message.save({
                             'message': _converse.chatboxes.getMessageBody(stanza),
                             'message': _converse.chatboxes.getMessageBody(stanza),
+                            'references': this.getReferencesFromStanza(stanza),
                             'older_versions': older_versions,
                             'older_versions': older_versions,
                             'edited': true
                             'edited': true
                         });
                         });
@@ -459,6 +460,17 @@
                     }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
                     }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
                 },
                 },
 
 
+                getReferencesFromStanza (stanza) {
+                    return sizzle(`reference[xmlns="${Strophe.NS.REFERENCE}"]`, stanza).map(ref => {
+                        return  {
+                            'begin': ref.getAttribute('begin'),
+                            'end': ref.getAttribute('end'),
+                            'type': ref.getAttribute('type'),
+                            'uri': ref.getAttribute('uri')
+                        };
+                    });
+                },
+
                 getMessageAttributesFromStanza (stanza, original_stanza) {
                 getMessageAttributesFromStanza (stanza, original_stanza) {
                     /* Parses a passed in message stanza and returns an object
                     /* Parses a passed in message stanza and returns an object
                      * of attributes.
                      * of attributes.
@@ -488,6 +500,7 @@
                         'is_delayed': !_.isNil(delay),
                         'is_delayed': !_.isNil(delay),
                         'is_spoiler': !_.isNil(spoiler),
                         'is_spoiler': !_.isNil(spoiler),
                         'message': _converse.chatboxes.getMessageBody(stanza) || undefined,
                         'message': _converse.chatboxes.getMessageBody(stanza) || undefined,
+                        'references': this.getReferencesFromStanza(stanza),
                         'msgid': stanza.getAttribute('id'),
                         'msgid': stanza.getAttribute('id'),
                         'time': delay ? delay.getAttribute('stamp') : moment().format(),
                         'time': delay ? delay.getAttribute('stamp') : moment().format(),
                         'type': stanza.getAttribute('type')
                         'type': stanza.getAttribute('type')

+ 1 - 0
src/converse-message-view.js

@@ -167,6 +167,7 @@
                         text = xss.filterXSS(text, {'whiteList': {}});
                         text = xss.filterXSS(text, {'whiteList': {}});
                         msg_content.innerHTML = _.flow(
                         msg_content.innerHTML = _.flow(
                             _.partial(u.geoUriToHttp, _, _converse.geouri_replacement),
                             _.partial(u.geoUriToHttp, _, _converse.geouri_replacement),
+                            _.partial(u.addMentions, _, this.model.get('references'), this.model.collection.chatbox),
                             u.addHyperlinks,
                             u.addHyperlinks,
                             u.renderNewLines,
                             u.renderNewLines,
                             _.partial(u.addEmoji, _converse, emojione, _)
                             _.partial(u.addEmoji, _converse, emojione, _)

+ 2 - 2
src/converse-muc.js

@@ -349,7 +349,7 @@
                     return;
                     return;
                 },
                 },
 
 
-                parseForReferences (text) {
+                parseTextForReferences (text) {
                     const refs = [];
                     const refs = [];
                     let index = 0;
                     let index = 0;
                     while (index < (text || '').length) {
                     while (index < (text || '').length) {
@@ -368,7 +368,7 @@
                 getOutgoingMessageAttributes (text, spoiler_hint) {
                 getOutgoingMessageAttributes (text, spoiler_hint) {
                     const is_spoiler = this.get('composing_spoiler');
                     const is_spoiler = this.get('composing_spoiler');
                     var references;
                     var references;
-                    [text, references] = this.parseForReferences(text);
+                    [text, references] = this.parseTextForReferences(text);
 
 
                     return {
                     return {
                         'from': `${this.get('jid')}/${this.get('nick')}`,
                         'from': `${this.get('jid')}/${this.get('nick')}`,

+ 19 - 0
src/utils/core.js

@@ -229,6 +229,25 @@
         return encodeURI(decodeURI(url)).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
         return encodeURI(decodeURI(url)).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
     };
     };
 
 
+    u.addMentions = function (text, references, chatbox) {
+        if (chatbox.get('message_type') !== 'groupchat') {
+            return text;
+        }
+        const nick = chatbox.get('nick');
+        references
+            .sort((a, b) => b.begin - a.begin)
+            .forEach(ref => {
+                const mention = text.slice(ref.begin, ref.end)
+                chatbox;
+                if (mention === nick) {
+                    text = text.slice(0, ref.begin) + `<span class="mention mention--self badge badge-info">${mention}</span>` + text.slice(ref.end);
+                } else {
+                    text = text.slice(0, ref.begin) + `<span class="mention">${mention}</span>` + text.slice(ref.end);
+                }
+            });
+        return text;
+    };
+
     u.addHyperlinks = function (text) {
     u.addHyperlinks = function (text) {
         return URI.withinString(text, function (url) {
         return URI.withinString(text, function (url) {
             var uri = new URI(url);
             var uri = new URI(url);