Allow selected characters to precede a mention
This commit is contained in:
parent
35db01d316
commit
8b9c97745f
@ -3,6 +3,7 @@
|
||||
## 8.0.0 (Unreleased)
|
||||
|
||||
- #1083: Add support for XEP-0393 Message Styling
|
||||
- #2275: Allow selected characters to precede a mention
|
||||
- Bugfix: `null` inserted by emoji picker and can't switch between skintones
|
||||
- New configuration setting: [show_tab_notifications](https://conversejs.org/docs/html/configuration.html#show-tab-notifications)
|
||||
|
||||
|
@ -114,6 +114,60 @@ describe("The nickname autocomplete feature", function () {
|
||||
done();
|
||||
}));
|
||||
|
||||
it("shows all autocompletion options when the user presses @ right after an allowed character",
|
||||
mock.initConverse(
|
||||
['rosterGroupsFetched', 'chatBoxesFetched'], {'opening_mention_characters':['(']},
|
||||
async function (done, _converse) {
|
||||
|
||||
await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'tom');
|
||||
const view = _converse.chatboxviews.get('lounge@montague.lit');
|
||||
|
||||
// Nicknames from presences
|
||||
['dick', 'harry'].forEach((nick) => {
|
||||
_converse.connection._dataRecv(mock.createRequest(
|
||||
$pres({
|
||||
'to': 'tom@montague.lit/resource',
|
||||
'from': `lounge@montague.lit/${nick}`
|
||||
})
|
||||
.c('x', {xmlns: Strophe.NS.MUC_USER})
|
||||
.c('item', {
|
||||
'affiliation': 'none',
|
||||
'jid': `${nick}@montague.lit/resource`,
|
||||
'role': 'participant'
|
||||
})));
|
||||
});
|
||||
|
||||
// Nicknames from messages
|
||||
const msg = $msg({
|
||||
from: 'lounge@montague.lit/jane',
|
||||
id: u.getUniqueId(),
|
||||
to: 'romeo@montague.lit',
|
||||
type: 'groupchat'
|
||||
}).c('body').t('Hello world').tree();
|
||||
await view.model.handleMessageStanza(msg);
|
||||
|
||||
// Test that pressing @ brings up all options
|
||||
const textarea = view.el.querySelector('textarea.chat-textarea');
|
||||
const at_event = {
|
||||
'target': textarea,
|
||||
'preventDefault': function preventDefault () {},
|
||||
'stopPropagation': function stopPropagation () {},
|
||||
'keyCode': 50,
|
||||
'key': '@'
|
||||
};
|
||||
textarea.value = '('
|
||||
view.onKeyDown(at_event);
|
||||
textarea.value = '(@';
|
||||
view.onKeyUp(at_event);
|
||||
|
||||
await u.waitUntil(() => view.el.querySelectorAll('.suggestion-box__results li').length === 4);
|
||||
expect(view.el.querySelector('.suggestion-box__results li:first-child').textContent).toBe('dick');
|
||||
expect(view.el.querySelector('.suggestion-box__results li:nth-child(2)').textContent).toBe('harry');
|
||||
expect(view.el.querySelector('.suggestion-box__results li:nth-child(3)').textContent).toBe('jane');
|
||||
expect(view.el.querySelector('.suggestion-box__results li:nth-child(4)').textContent).toBe('tom');
|
||||
done();
|
||||
}));
|
||||
|
||||
it("should order by query index position and length", mock.initConverse(
|
||||
['rosterGroupsFetched', 'chatBoxesFetched'], {}, async function (done, _converse) {
|
||||
await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'tom');
|
||||
|
@ -9,6 +9,7 @@
|
||||
import { Events } from '@converse/skeletor/src/events.js';
|
||||
import { converse } from "@converse/headless/converse-core";
|
||||
|
||||
converse.MENTION_BOUNDARIES = ['"', '(', '<', '#', '!', '\\', '/', '+', '~', '[', '{', '^', '>'];
|
||||
const u = converse.env.utils;
|
||||
|
||||
|
||||
@ -93,6 +94,11 @@ const helpers = {
|
||||
|
||||
regExpEscape (s) {
|
||||
return s.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&");
|
||||
},
|
||||
|
||||
isMention (word, ac_triggers, mention_boundaries) {
|
||||
return (ac_triggers.includes(word[0]) ||
|
||||
(mention_boundaries.includes(word[0]) && ac_triggers.includes(word[1])));
|
||||
}
|
||||
}
|
||||
|
||||
@ -245,7 +251,7 @@ export class AutoComplete {
|
||||
|
||||
insertValue (suggestion) {
|
||||
if (this.match_current_word) {
|
||||
u.replaceCurrentWord(this.input, suggestion.value);
|
||||
u.replaceCurrentWord(this.input, suggestion.value, converse.MENTION_BOUNDARIES);
|
||||
} else {
|
||||
this.input.value = suggestion.value;
|
||||
}
|
||||
@ -365,7 +371,7 @@ export class AutoComplete {
|
||||
this.auto_completing = true;
|
||||
} else if (ev.key === "Backspace") {
|
||||
const word = u.getCurrentWord(ev.target, ev.target.selectionEnd-1);
|
||||
if (this.ac_triggers.includes(word[0])) {
|
||||
if (helpers.isMention(word, this.ac_triggers, converse.MENTION_BOUNDARIES)) {
|
||||
this.auto_completing = true;
|
||||
}
|
||||
}
|
||||
@ -387,11 +393,13 @@ export class AutoComplete {
|
||||
}
|
||||
|
||||
let value = this.match_current_word ? u.getCurrentWord(this.input) : this.input.value;
|
||||
const contains_trigger = this.ac_triggers.includes(value[0]);
|
||||
const contains_trigger = helpers.isMention(value, this.ac_triggers, converse.MENTION_BOUNDARIES);
|
||||
if (contains_trigger) {
|
||||
this.auto_completing = true;
|
||||
if (!this.include_triggers.includes(ev.key)) {
|
||||
value = value.slice('1');
|
||||
value = converse.MENTION_BOUNDARIES.includes(value[0])
|
||||
? value.slice('2')
|
||||
: value.slice('1');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -963,7 +963,9 @@ converse.plugins.add('converse-muc', {
|
||||
getAllKnownNicknamesRegex () {
|
||||
const longNickString = this.getAllKnownNicknames().join('|');
|
||||
const escapedLongNickString = p.escapeRegexString(longNickString)
|
||||
return RegExp(`(?:\\s|^)@(${escapedLongNickString})(?![\\w@-])`, 'ig');
|
||||
const mention_boundaries = converse.MENTION_BOUNDARIES.join('|');
|
||||
const escaped_mention_boundaries = p.escapeRegexString(mention_boundaries);
|
||||
return RegExp(`(?:\\s|^)[${escaped_mention_boundaries}]?@(${escapedLongNickString})(?![\\w@-])`, 'ig');
|
||||
},
|
||||
|
||||
getOccupantByJID (jid) {
|
||||
|
@ -425,12 +425,16 @@ u.getCurrentWord = function (input, index, delineator) {
|
||||
return word;
|
||||
};
|
||||
|
||||
u.replaceCurrentWord = function (input, new_value) {
|
||||
u.replaceCurrentWord = function (input, new_value, mention_boundaries=[]) {
|
||||
const caret = input.selectionEnd || undefined,
|
||||
current_word = last(input.value.slice(0, caret).split(' ')),
|
||||
value = input.value;
|
||||
input.value = value.slice(0, caret - current_word.length) + `${new_value} ` + value.slice(caret);
|
||||
input.selectionEnd = caret - current_word.length + new_value.length + 1;
|
||||
current_word = last(input.value.slice(0, caret).split(/\s/)),
|
||||
value = input.value,
|
||||
mention_boundary = mention_boundaries.includes(current_word[0])
|
||||
? current_word[0]
|
||||
: '';
|
||||
input.value = value.slice(0, caret - current_word.length) + mention_boundary + `${new_value} ` + value.slice(caret);
|
||||
const selection_end = caret - current_word.length + new_value.length + 1;
|
||||
input.selectionEnd = mention_boundary ? selection_end + 1 : selection_end;
|
||||
};
|
||||
|
||||
u.triggerEvent = function (el, name, type="Event", bubbles=true, cancelable=true) {
|
||||
|
@ -7,7 +7,7 @@
|
||||
const helpers = {};
|
||||
|
||||
// Captures all mentions, but includes a space before the @
|
||||
helpers.mention_regex = /(?:\s|^)([@][\w_-]+(?:\.\w+)*)/ig;
|
||||
helpers.mention_regex = /(?:\s|^)([@][\w_-]+(?:\.\w+)*)/gi;
|
||||
|
||||
helpers.matchRegexInText = text => regex => text.matchAll(regex);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user