From f2b017ec89ba18342a209c35288cd37acc583758 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Wed, 15 Aug 2018 17:22:24 +0200 Subject: [PATCH] Add method to determine references from message text --- spec/autocomplete.js | 6 ++-- spec/messages.js | 54 +++++++++++++++++++++++++++++++ src/converse-autocomplete.js | 1 - src/converse-muc-views.js | 2 +- src/converse-muc.js | 63 +++++++++++++++++++++++++++++++++++- src/utils/core.js | 15 +++++++++ 6 files changed, 135 insertions(+), 6 deletions(-) diff --git a/spec/autocomplete.js b/spec/autocomplete.js index 492252574..b34b18ff3 100644 --- a/spec/autocomplete.js +++ b/spec/autocomplete.js @@ -13,7 +13,7 @@ const Strophe = converse.env.Strophe; const u = converse.env.utils; - return describe("A groupchat textarea", function () { + describe("The nickname autocomplete feature", function () { it("shows all autocompletion options when the user presses @", mock.initConverseWithPromises( @@ -146,7 +146,7 @@ 'stopPropagation': _.noop, 'keyCode': 13 // Enter }); - expect(textarea.value).toBe('hello s some2 '); + expect(textarea.value).toBe('hello s @some2 '); // Test that pressing tab twice selects presence = $pres({ @@ -166,7 +166,7 @@ view.keyPressed(tab_event); view.keyUp(tab_event); - expect(textarea.value).toBe('hello z3r0 '); + expect(textarea.value).toBe('hello @z3r0 '); done(); }).catch(_.partial(console.error, _)); diff --git a/spec/messages.js b/spec/messages.js index 6df995dc1..0940c9baa 100644 --- a/spec/messages.js +++ b/spec/messages.js @@ -1202,6 +1202,60 @@ }); })); + describe("in which someone is mentioned", function () { + + it("includes XEP-0372 references to that person", + 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' + }))); + }); + + let [text, references] = view.model.parseForReferences('hello z3r0') + expect(references.length).toBe(0); + expect(text).toBe('hello z3r0'); + + [text, references] = view.model.parseForReferences('hello @z3r0') + expect(references.length).toBe(1); + expect(text).toBe('hello z3r0'); + expect(JSON.stringify(references)) + .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?') + expect(text).toBe('hello @some1 z3r0 gibson mr.robot, how are you?'); + expect(JSON.stringify(references)) + .toBe('[{"begin":13,"end":17,"type":"mention","uri":"xmpp:z3r0@localhost"},'+ + '{"begin":18,"end":24,"type":"mention","uri":"xmpp:gibson@localhost"},'+ + '{"begin":25,"end":33,"type":"mention","uri":"xmpp:mr.robot@localhost"}]'); + + [text, references] = view.model.parseForReferences('yo @gib') + expect(text).toBe('yo @gib'); + expect(references.length).toBe(0); + + [text, references] = view.model.parseForReferences('yo @gibsonian') + expect(text).toBe('yo @gibsonian'); + expect(references.length).toBe(0); + done(); + }).catch(_.partial(console.error, _)); + })); + }); + + describe("when received from someone else", function () { it("will open a chatbox and be displayed inside it", diff --git a/src/converse-autocomplete.js b/src/converse-autocomplete.js index abdd0c854..dfc5fb4df 100644 --- a/src/converse-autocomplete.js +++ b/src/converse-autocomplete.js @@ -237,7 +237,6 @@ } else { selected = this.ul.children[this.index]; } - if (selected) { const suggestion = this.suggestions[this.index]; this.insertValue(suggestion); diff --git a/src/converse-muc-views.js b/src/converse-muc-views.js index df484d3b4..bd9c17ae7 100644 --- a/src/converse-muc-views.js +++ b/src/converse-muc-views.js @@ -617,7 +617,7 @@ 'min_chars': 1, 'match_current_word': true, 'match_on_tab': true, - 'list': () => this.model.occupants.map(o => ({'label': o.get('nick'), 'value': o.get('nick')})), + 'list': () => this.model.occupants.map(o => ({'label': o.get('nick'), 'value': `@${o.get('nick')}`})), 'filter': _converse.FILTER_STARTSWITH, 'trigger_on_at': true }); diff --git a/src/converse-muc.js b/src/converse-muc.js index b403f53f3..4fac4995e 100644 --- a/src/converse-muc.js +++ b/src/converse-muc.js @@ -308,14 +308,75 @@ _converse.connection.sendPresence(presence); }, + getReferenceForMention (mention, index) { + const longest_match = u.getLongestSubstring(mention, this.occupants.map(o => o.get('nick'))); + if (!longest_match) { + return null; + } + if ((mention[longest_match.length] || '').match(/[A-Za-zäëïöüâêîôûáéíóúàèìòùÄËÏÖÜÂÊÎÔÛÁÉÍÓÚÀÈÌÒÙ]/i)) { + // avoid false positives, i.e. mentions that have + // further alphabetical characters than our longest + // match. + return null; + } + const occupant = this.occupants.findOccupant({'nick': longest_match}); + if (!occupant) { + return null; + } + const obj = { + 'begin': index, + 'end': index + longest_match.length, + 'type': 'mention' + }; + if (occupant.get('jid')) { + obj.uri = `xmpp:${occupant.get('jid')}` + } + return obj; + }, + + extractReference (text, index) { + for (let i=index; i accumulator.length) { + return current_value; + } else { + return accumulator; + } + } else { + return accumulator; + } + } + return candidates.reduce(reducer, ''); + } + u.getNextElement = function (el, selector='*') { let next_el = el.nextElementSibling; while (!_.isNull(next_el) && !sizzle.matchesSelector(next_el, selector)) {