Makes mentions case-insensitive, plus parsing functionality refactor (#2061)
This commit is contained in:
parent
2009a94ba4
commit
73d33e1161
10
package-lock.json
generated
10
package-lock.json
generated
@ -13293,9 +13293,9 @@
|
||||
}
|
||||
},
|
||||
"lerna": {
|
||||
"version": "3.22.1",
|
||||
"resolved": "https://registry.npmjs.org/lerna/-/lerna-3.22.1.tgz",
|
||||
"integrity": "sha512-vk1lfVRFm+UuEFA7wkLKeSF7Iz13W+N/vFd48aW2yuS7Kv0RbNm2/qcDPV863056LMfkRlsEe+QYOw3palj5Lg==",
|
||||
"version": "3.22.0",
|
||||
"resolved": "https://registry.npmjs.org/lerna/-/lerna-3.22.0.tgz",
|
||||
"integrity": "sha512-xWlHdAStcqK/IjKvjsSMHPZjPkBV1lS60PmsIeObU8rLljTepc4Sg/hncw4HWfQxPIewHAUTqhrxPIsqf9L2Eg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@lerna/add": "3.21.0",
|
||||
@ -13311,9 +13311,9 @@
|
||||
"@lerna/init": "3.21.0",
|
||||
"@lerna/link": "3.21.0",
|
||||
"@lerna/list": "3.21.0",
|
||||
"@lerna/publish": "3.22.1",
|
||||
"@lerna/publish": "3.22.0",
|
||||
"@lerna/run": "3.21.0",
|
||||
"@lerna/version": "3.22.1",
|
||||
"@lerna/version": "3.22.0",
|
||||
"import-local": "^2.0.0",
|
||||
"npmlog": "^4.1.2"
|
||||
}
|
||||
|
@ -117,5 +117,6 @@
|
||||
"webpack-dev-server": "^3.11.0",
|
||||
"webpack-merge": "^4.2.1",
|
||||
"xss": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
|
@ -906,7 +906,7 @@ describe("Chatboxes", function () {
|
||||
}));
|
||||
});
|
||||
|
||||
describe("An inactive notifciation", function () {
|
||||
describe("An inactive notification", function () {
|
||||
|
||||
it("is sent if the user has stopped typing since 2 minutes",
|
||||
mock.initConverse(
|
||||
@ -1056,7 +1056,7 @@ describe("Chatboxes", function () {
|
||||
}));
|
||||
});
|
||||
|
||||
describe("A gone notifciation", function () {
|
||||
describe("A gone notification", function () {
|
||||
|
||||
it("will be shown if received",
|
||||
mock.initConverse(
|
||||
|
@ -1024,15 +1024,15 @@ describe("A Groupchat Message", function () {
|
||||
[text, references] = view.model.parseTextForReferences('hello @z3r0')
|
||||
expect(references.length).toBe(1);
|
||||
expect(text).toBe('hello z3r0');
|
||||
expect(JSON.stringify(references))
|
||||
.toBe('[{"begin":6,"end":10,"value":"z3r0","type":"mention","uri":"xmpp:z3r0@montague.lit"}]');
|
||||
expect(references)
|
||||
.toEqual([{"begin":6,"end":10,"value":"z3r0","type":"mention","uri":"xmpp:z3r0@montague.lit"}]);
|
||||
|
||||
[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(JSON.stringify(references))
|
||||
.toBe('[{"begin":13,"end":17,"value":"z3r0","type":"mention","uri":"xmpp:z3r0@montague.lit"},'+
|
||||
'{"begin":18,"end":24,"value":"gibson","type":"mention","uri":"xmpp:gibson@montague.lit"},'+
|
||||
'{"begin":25,"end":33,"value":"mr.robot","type":"mention","uri":"xmpp:mr.robot@montague.lit"}]');
|
||||
expect(references)
|
||||
.toEqual([{"begin":13,"end":17,"value":"z3r0","type":"mention","uri":"xmpp:z3r0@montague.lit"},
|
||||
{"begin":18,"end":24,"value":"gibson","type":"mention","uri":"xmpp:gibson@montague.lit"},
|
||||
{"begin":25,"end":33,"value":"mr.robot","type":"mention","uri":"xmpp:mr.robot@montague.lit"}]);
|
||||
|
||||
[text, references] = view.model.parseTextForReferences('yo @gib')
|
||||
expect(text).toBe('yo @gib');
|
||||
@ -1042,43 +1042,47 @@ describe("A Groupchat Message", function () {
|
||||
expect(text).toBe('yo @gibsonian');
|
||||
expect(references.length).toBe(0);
|
||||
|
||||
[text, references] = view.model.parseTextForReferences('yo @GiBsOn')
|
||||
expect(text).toBe('yo gibson');
|
||||
expect(references.length).toBe(1);
|
||||
|
||||
[text, references] = view.model.parseTextForReferences('@gibson')
|
||||
expect(text).toBe('gibson');
|
||||
expect(references.length).toBe(1);
|
||||
expect(JSON.stringify(references))
|
||||
.toBe('[{"begin":0,"end":6,"value":"gibson","type":"mention","uri":"xmpp:gibson@montague.lit"}]');
|
||||
expect(references)
|
||||
.toEqual([{"begin":0,"end":6,"value":"gibson","type":"mention","uri":"xmpp:gibson@montague.lit"}]);
|
||||
|
||||
[text, references] = view.model.parseTextForReferences('hi @Link Mauve how are you?')
|
||||
expect(text).toBe('hi Link Mauve how are you?');
|
||||
expect(references.length).toBe(1);
|
||||
expect(JSON.stringify(references))
|
||||
.toBe('[{"begin":3,"end":13,"value":"Link Mauve","type":"mention","uri":"xmpp:Link-Mauve@montague.lit"}]');
|
||||
expect(references)
|
||||
.toEqual([{"begin":3,"end":13,"value":"Link Mauve","type":"mention","uri":"xmpp:Link-Mauve@montague.lit"}]);
|
||||
|
||||
[text, references] = view.model.parseTextForReferences('https://example.org/@gibson')
|
||||
expect(text).toBe('https://example.org/@gibson');
|
||||
expect(references.length).toBe(0);
|
||||
expect(JSON.stringify(references))
|
||||
.toBe('[]');
|
||||
expect(references)
|
||||
.toEqual([]);
|
||||
|
||||
[text, references] = view.model.parseTextForReferences('mail@gibson.com')
|
||||
expect(text).toBe('mail@gibson.com');
|
||||
expect(references.length).toBe(0);
|
||||
expect(JSON.stringify(references))
|
||||
.toBe('[]');
|
||||
expect(references)
|
||||
.toEqual([]);
|
||||
|
||||
[text, references] = view.model.parseTextForReferences(
|
||||
'https://linkmauve.fr@Link Mauve/ https://linkmauve.fr/@github/is_back gibson@gibson.com gibson@Link Mauve.fr')
|
||||
expect(text).toBe(
|
||||
'https://linkmauve.fr@Link Mauve/ https://linkmauve.fr/@github/is_back gibson@gibson.com gibson@Link Mauve.fr');
|
||||
expect(references.length).toBe(0);
|
||||
expect(JSON.stringify(references))
|
||||
.toBe('[]');
|
||||
expect(references)
|
||||
.toEqual([]);
|
||||
|
||||
[text, references] = view.model.parseTextForReferences('@gh0st where are you?')
|
||||
expect(text).toBe('gh0st where are you?');
|
||||
expect(references.length).toBe(1);
|
||||
expect(JSON.stringify(references))
|
||||
.toBe('[{"begin":0,"end":5,"value":"gh0st","type":"mention","uri":"xmpp:lounge@montague.lit/gh0st"}]');
|
||||
expect(references)
|
||||
.toEqual([{"begin":0,"end":5,"value":"gh0st","type":"mention","uri":"xmpp:lounge@montague.lit/gh0st"}]);
|
||||
done();
|
||||
}));
|
||||
|
||||
|
@ -15,6 +15,7 @@ import log from "./log";
|
||||
import muc_utils from "./utils/muc";
|
||||
import st from "./utils/stanza";
|
||||
import u from "./utils/form";
|
||||
import p from "./utils/parse-helpers";
|
||||
|
||||
export const ROLES = ['moderator', 'participant', 'visitor'];
|
||||
export const AFFILIATIONS = ['owner', 'admin', 'member', 'outcast', 'none'];
|
||||
@ -941,74 +942,61 @@ converse.plugins.add('converse-muc', {
|
||||
])].filter(n => n);
|
||||
},
|
||||
|
||||
getReferenceForMention (mention, index) {
|
||||
const nicknames = this.getAllKnownNicknames();
|
||||
const longest_match = u.getLongestSubstring(mention, nicknames);
|
||||
if (!longest_match) {
|
||||
return null;
|
||||
}
|
||||
if ((mention[longest_match.length] || '').match(/[A-Za-zäëïöüâêîôûáéíóúàèìòùÄËÏÖÜÂÊÎÔÛÁÉÍÓÚÀÈÌÒÙ0-9]/i)) {
|
||||
// avoid false positives, i.e. mentions that have
|
||||
// further alphabetical characters than our longest
|
||||
// match.
|
||||
return null;
|
||||
}
|
||||
getAllKnownNicknamesRegex () {
|
||||
const longNickString = this.getAllKnownNicknames().join('|');
|
||||
const escapedLongNickString = p.escapeRegexString(longNickString)
|
||||
return RegExp(`(?:\\s|^)@(${escapedLongNickString})(?![\\w@-])`, 'ig');
|
||||
},
|
||||
|
||||
let uri;
|
||||
const occupant = this.occupants.findOccupant({'nick': longest_match}) ||
|
||||
u.isValidJID(longest_match) && this.occupants.findOccupant({'jid': longest_match});
|
||||
getOccupantByJID (jid) {
|
||||
return this.occupants.findOccupant({ jid });
|
||||
},
|
||||
|
||||
if (occupant) {
|
||||
uri = occupant.get('jid') || `${this.get('jid')}/${occupant.get('nick')}`;
|
||||
} else if (nicknames.includes(longest_match)) {
|
||||
// TODO: show a warning to the user that the person is not currently in the chat
|
||||
uri = `${this.get('jid')}/${longest_match}`;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
const obj = {
|
||||
'begin': index,
|
||||
'end': index + longest_match.length,
|
||||
'value': longest_match,
|
||||
'type': 'mention',
|
||||
'uri': encodeURI(`xmpp:${uri}`)
|
||||
getOccupantByNickname (nick) {
|
||||
return this.occupants.findOccupant({ nick });
|
||||
},
|
||||
|
||||
parseTextForReferences (original_message) {
|
||||
if (!original_message) return ['', []];
|
||||
const findRegexInMessage = p.matchRegexInText(original_message);
|
||||
const raw_mentions = findRegexInMessage(p.mention_regex);
|
||||
if (!raw_mentions) return [original_message, []];
|
||||
|
||||
const known_nicknames = this.getAllKnownNicknames();
|
||||
const known_nicknames_with_at_regex = this.getAllKnownNicknamesRegex();
|
||||
const getMatchesForNickRegex = nick_regex => [...findRegexInMessage(nick_regex)];
|
||||
const getNicknameFromRegex = p.findFirstMatchInArray(known_nicknames);
|
||||
|
||||
const uriFromNickname = nickname => {
|
||||
const jid = this.get('jid');
|
||||
const occupant = this.getOccupant(nickname) || this.getOccupant(jid);
|
||||
const uri = (occupant && occupant.get('jid')) || `${jid}/${nickname}`;
|
||||
return encodeURI(`xmpp:${uri}`);
|
||||
};
|
||||
return obj;
|
||||
},
|
||||
|
||||
extractReference (text, index) {
|
||||
for (let i=index; i<text.length; i++) {
|
||||
if (text[i] === '@' && (i === 0 || text[i - 1] === ' ')) {
|
||||
const match = text.slice(i+1),
|
||||
ref = this.getReferenceForMention(match, i);
|
||||
if (ref) {
|
||||
return [text.slice(0, i) + match, ref, i]
|
||||
}
|
||||
}
|
||||
const matchToReference = match => {
|
||||
const at_sign_index = match[0].indexOf('@');
|
||||
const begin = match.index + at_sign_index;
|
||||
const end = begin + match[0].length - at_sign_index;
|
||||
const value = getNicknameFromRegex(RegExp(match[1], 'i'));
|
||||
const type = 'mention';
|
||||
const uri = uriFromNickname(value);
|
||||
return { begin, end, value, type, uri }
|
||||
}
|
||||
return;
|
||||
|
||||
const mentions = getMatchesForNickRegex(known_nicknames_with_at_regex);
|
||||
const references = mentions.map(matchToReference);
|
||||
|
||||
const [updated_message, updated_references] = p.reduceTextFromReferences(
|
||||
original_message,
|
||||
references
|
||||
);
|
||||
return [updated_message, updated_references];
|
||||
},
|
||||
|
||||
parseTextForReferences (text) {
|
||||
const refs = [];
|
||||
let index = 0;
|
||||
while (index < (text || '').length) {
|
||||
const result = this.extractReference(text, index);
|
||||
if (result) {
|
||||
text = result[0]; // @ gets filtered out
|
||||
refs.push(result[1]);
|
||||
index = result[2];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return [text, refs];
|
||||
},
|
||||
|
||||
getOutgoingMessageAttributes (text, spoiler_hint) {
|
||||
getOutgoingMessageAttributes (original_message, spoiler_hint) {
|
||||
const is_spoiler = this.get('composing_spoiler');
|
||||
var references;
|
||||
[text, references] = this.parseTextForReferences(text);
|
||||
const [text, references] = this.parseTextForReferences(original_message);
|
||||
const origin_id = u.getUniqueId();
|
||||
const body = text ? u.httpToGeoUri(u.shortnameToUnicode(text), _converse) : undefined;
|
||||
return {
|
||||
@ -1393,13 +1381,13 @@ converse.plugins.add('converse-muc', {
|
||||
/**
|
||||
* @private
|
||||
* @method _converse.ChatRoom#getOccupant
|
||||
* @param { String } nick_or_jid - The nickname or JID of the occupant to be returned
|
||||
* @param { String } nickname_or_jid - The nickname or JID of the occupant to be returned
|
||||
* @returns { _converse.ChatRoomOccupant }
|
||||
*/
|
||||
getOccupant (nick_or_jid) {
|
||||
return (u.isValidJID(nick_or_jid) &&
|
||||
this.occupants.findWhere({'jid': nick_or_jid})) ||
|
||||
this.occupants.findWhere({'nick': nick_or_jid});
|
||||
getOccupant (nickname_or_jid) {
|
||||
return u.isValidJID(nickname_or_jid)
|
||||
? this.getOccupantByJID(nickname_or_jid)
|
||||
: this.getOccupantByNickname(nickname_or_jid);
|
||||
},
|
||||
|
||||
/**
|
||||
|
43
src/headless/utils/parse-helpers.js
Normal file
43
src/headless/utils/parse-helpers.js
Normal file
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* @copyright 2020, the Converse.js contributors
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
* @description Pure functions to help funcitonally parse messages.
|
||||
* @todo Other parsing helpers can be made more abstract and placed here.
|
||||
*/
|
||||
const helpers = {};
|
||||
|
||||
// Captures all mentions, but includes a space before the @
|
||||
helpers.mention_regex = /(?:\s|^)([@][\w_-]+(?:\.\w+)*)/ig;
|
||||
|
||||
helpers.matchRegexInText = text => regex => text.matchAll(regex);
|
||||
|
||||
const escapeRegexChars = (string, char) => string.replace(RegExp('\\' + char, 'ig'), '\\' + char);
|
||||
|
||||
helpers.escapeCharacters = characters => string =>
|
||||
characters.split('').reduce(escapeRegexChars, string);
|
||||
|
||||
helpers.escapeRegexString = helpers.escapeCharacters('[\\^$.?*+(){}');
|
||||
|
||||
// `for` is ~25% faster than using `Array.find()`
|
||||
helpers.findFirstMatchInArray = array => regex => {
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
if (regex.test(array[i])) {
|
||||
return array[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const reduceReferences = ([text, refs], ref, index) => {
|
||||
let updated_text = text;
|
||||
let { begin, end } = ref;
|
||||
const { value } = ref;
|
||||
begin = begin - index;
|
||||
end = end - index - 1; // -1 to compensate for the removed @
|
||||
updated_text = `${updated_text.slice(0, begin)}${value}${updated_text.slice(end + 1)}`;
|
||||
return [updated_text, [...refs, { ...ref, begin, end }]]
|
||||
}
|
||||
|
||||
helpers.reduceTextFromReferences = (text, refs) => refs.reduce(reduceReferences, [text, []]);
|
||||
|
||||
export default helpers;
|
Loading…
Reference in New Issue
Block a user