Add converse-autocomplete and use that in the chat textarea
This commit is contained in:
parent
f0ad326e2e
commit
b6f4f05b9e
|
@ -19,7 +19,7 @@
|
|||
"rules": {
|
||||
"lodash/prefer-lodash-method": [2, {
|
||||
"ignoreMethods": [
|
||||
"find", "endsWith", "startsWith", "filter", "reduce",
|
||||
"find", "endsWith", "startsWith", "filter", "reduce", "isArray", "create",
|
||||
"map", "replace", "toLower", "split", "trim", "forEach", "toUpperCase", "includes"
|
||||
]
|
||||
}],
|
||||
|
@ -215,10 +215,7 @@
|
|||
"one-var": "off",
|
||||
"one-var-declaration-per-line": "off",
|
||||
"operator-assignment": "off",
|
||||
"operator-linebreak": [
|
||||
"error",
|
||||
"after"
|
||||
],
|
||||
"operator-linebreak": "off",
|
||||
"padded-blocks": "off",
|
||||
"prefer-arrow-callback": "off",
|
||||
"prefer-const": "error",
|
||||
|
|
|
@ -7748,6 +7748,8 @@ body.reset {
|
|||
line-height: 27px; }
|
||||
#conversejs.converse-fullscreen .chatbox .sendXMPPMessage ul {
|
||||
width: 100%; }
|
||||
#conversejs.converse-fullscreen .chatbox .sendXMPPMessage .suggestion-box__results:after {
|
||||
display: none; }
|
||||
#conversejs.converse-fullscreen .chatbox .sendXMPPMessage .toggle-smiley ul.emoji-toolbar .emoji-category-picker {
|
||||
margin-right: 5em; }
|
||||
#conversejs.converse-fullscreen .chatbox .sendXMPPMessage .toggle-smiley ul.emoji-toolbar .emoji-category {
|
||||
|
@ -8681,8 +8683,7 @@ body.reset {
|
|||
color: #E77051; }
|
||||
#conversejs.converse-embedded .chatroom .sendXMPPMessage .chat-textarea,
|
||||
#conversejs .chatroom .sendXMPPMessage .chat-textarea {
|
||||
border-bottom-right-radius: 0;
|
||||
resize: none; }
|
||||
border-bottom-right-radius: 0; }
|
||||
#conversejs.converse-embedded .chatroom .sendXMPPMessage .chat-textarea.correcting,
|
||||
#conversejs .chatroom .sendXMPPMessage .chat-textarea.correcting {
|
||||
background-color: #fadfd7; }
|
||||
|
@ -9040,20 +9041,26 @@ body.reset {
|
|||
#conversejs .visually-hidden {
|
||||
position: absolute;
|
||||
clip: rect(0, 0, 0, 0); }
|
||||
#conversejs .form-group .suggestion-box,
|
||||
#conversejs .form-group .awesomplete {
|
||||
width: 100%; }
|
||||
#conversejs div.awesomplete {
|
||||
display: inline-block;
|
||||
#conversejs .suggestion-box,
|
||||
#conversejs .awesomplete {
|
||||
position: relative; }
|
||||
#conversejs div.awesomplete mark {
|
||||
#conversejs .suggestion-box mark,
|
||||
#conversejs .awesomplete mark {
|
||||
background: #FFB9A7; }
|
||||
#conversejs div.awesomplete > input {
|
||||
#conversejs .suggestion-box > input,
|
||||
#conversejs .awesomplete > input {
|
||||
display: block; }
|
||||
#conversejs div.awesomplete > ul {
|
||||
#conversejs .suggestion-box .suggestion-box__results,
|
||||
#conversejs .suggestion-box > ul,
|
||||
#conversejs .awesomplete .suggestion-box__results,
|
||||
#conversejs .awesomplete > ul {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
z-index: 2;
|
||||
min-width: 100%;
|
||||
box-sizing: border-box;
|
||||
list-style: none;
|
||||
|
@ -9061,51 +9068,91 @@ body.reset {
|
|||
border-radius: .3em;
|
||||
margin: .2em 0 0;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
background: linear-gradient(to bottom right, white, rgba(255, 255, 255, 0.8));
|
||||
background: linear-gradient(to bottom right, white, rgba(255, 255, 255, 0.9));
|
||||
border: 1px solid rgba(0, 0, 0, 0.3);
|
||||
box-shadow: 0.05em 0.2em 0.6em rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0.05em 0.2em 0.6em rgba(0, 0, 0, 0.1);
|
||||
text-shadow: none; }
|
||||
#conversejs div.awesomplete > ul:before {
|
||||
#conversejs .suggestion-box .suggestion-box__results:before,
|
||||
#conversejs .suggestion-box > ul:before,
|
||||
#conversejs .awesomplete .suggestion-box__results:before,
|
||||
#conversejs .awesomplete > ul:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -.43em;
|
||||
left: 1em;
|
||||
width: 0;
|
||||
height: 0;
|
||||
padding: .4em;
|
||||
background: white;
|
||||
border: inherit;
|
||||
border-right: 0;
|
||||
border-bottom: 0;
|
||||
-webkit-transform: rotate(45deg);
|
||||
transform: rotate(45deg); }
|
||||
#conversejs div.awesomplete > ul > li {
|
||||
transform: rotate(45deg);
|
||||
z-index: 1; }
|
||||
#conversejs .suggestion-box .suggestion-box__results > li,
|
||||
#conversejs .suggestion-box > ul > li,
|
||||
#conversejs .awesomplete .suggestion-box__results > li,
|
||||
#conversejs .awesomplete > ul > li {
|
||||
text-overflow: ellipsis;
|
||||
overflow-x: hidden;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
padding: 1em; }
|
||||
#conversejs .suggestion-box .suggestion-box__results--above,
|
||||
#conversejs .awesomplete .suggestion-box__results--above {
|
||||
bottom: 4.5em; }
|
||||
#conversejs .suggestion-box .suggestion-box__results--above:before,
|
||||
#conversejs .awesomplete .suggestion-box__results--above:before {
|
||||
display: none; }
|
||||
#conversejs .suggestion-box .suggestion-box__results--above:after,
|
||||
#conversejs .awesomplete .suggestion-box__results--above:after {
|
||||
z-index: 1;
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -.43em;
|
||||
left: 1em;
|
||||
width: 0;
|
||||
height: 0;
|
||||
padding: .4em;
|
||||
background: white;
|
||||
border: inherit;
|
||||
border-left: 0;
|
||||
border-top: 0;
|
||||
-webkit-transform: rotate(45deg);
|
||||
transform: rotate(45deg); }
|
||||
#conversejs .suggestion-box > ul[hidden],
|
||||
#conversejs .suggestion-box > ul:empty,
|
||||
#conversejs div.awesomplete > ul[hidden],
|
||||
#conversejs div.awesomplete > ul:empty {
|
||||
display: none; }
|
||||
@supports (transform: scale(0)) {
|
||||
#conversejs .suggestion-box > ul,
|
||||
#conversejs div.awesomplete > ul {
|
||||
transition: 0.3s cubic-bezier(0.4, 0.2, 0.5, 1.4);
|
||||
transform-origin: 1.43em -.43em; }
|
||||
#conversejs .suggestion-box > ul[hidden],
|
||||
#conversejs .suggestion-box > ul:empty,
|
||||
#conversejs div.awesomplete > ul[hidden],
|
||||
#conversejs div.awesomplete > ul:empty {
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
display: block;
|
||||
transition-timing-function: ease; } }
|
||||
#conversejs .suggestion-box > ul > li:hover,
|
||||
#conversejs div.awesomplete > ul > li:hover {
|
||||
z-index: 2;
|
||||
background: #E77051;
|
||||
color: white; }
|
||||
#conversejs .suggestion-box > ul > li[aria-selected="true"],
|
||||
#conversejs div.awesomplete > ul > li[aria-selected="true"] {
|
||||
background: #3d6d8f;
|
||||
color: white; }
|
||||
#conversejs .suggestion-box li:hover mark,
|
||||
#conversejs div.awesomplete li:hover mark {
|
||||
background: #A53214;
|
||||
color: white; }
|
||||
#conversejs .suggestion-box li[aria-selected="true"] mark,
|
||||
#conversejs div.awesomplete li[aria-selected="true"] mark {
|
||||
background: #3d6b00;
|
||||
color: inherit; }
|
||||
|
|
589
dist/converse.js
vendored
589
dist/converse.js
vendored
|
@ -52182,7 +52182,6 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
|
|||
return;
|
||||
}
|
||||
if (_.isBoolean(plugin.enabled) && plugin.enabled || _.isFunction(plugin.enabled) && plugin.enabled(this.plugged) || _.isNil(plugin.enabled)) {
|
||||
|
||||
_.extend(plugin, this.properties);
|
||||
if (plugin.dependencies) {
|
||||
this.loadPluginDependencies(plugin);
|
||||
|
@ -67560,6 +67559,496 @@ var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;
|
|||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./src/converse-autocomplete.js":
|
||||
/*!**************************************!*\
|
||||
!*** ./src/converse-autocomplete.js ***!
|
||||
\**************************************/
|
||||
/*! no static exports found */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;
|
||||
|
||||
// Converse.js
|
||||
// http://conversejs.org
|
||||
//
|
||||
// Copyright (c) 2013-2018, the Converse.js developers
|
||||
// Licensed under the Mozilla Public License (MPLv2)
|
||||
// This plugin started as a fork of Lea Verou's Awesomplete
|
||||
// https://leaverou.github.io/awesomplete/
|
||||
(function (root, factory) {
|
||||
!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! converse-core */ "./src/converse-core.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory),
|
||||
__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?
|
||||
(__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__),
|
||||
__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
|
||||
})(void 0, function (converse) {
|
||||
const _converse$env = converse.env,
|
||||
_ = _converse$env._,
|
||||
Backbone = _converse$env.Backbone,
|
||||
u = converse.env.utils;
|
||||
converse.plugins.add("converse-autocomplete", {
|
||||
initialize() {
|
||||
const _converse = this._converse;
|
||||
|
||||
_converse.FILTER_CONTAINS = function (text, input) {
|
||||
return RegExp($.regExpEscape(input.trim()), "i").test(text);
|
||||
};
|
||||
|
||||
_converse.FILTER_STARTSWITH = function (text, input) {
|
||||
return RegExp("^" + $.regExpEscape(input.trim()), "i").test(text);
|
||||
};
|
||||
|
||||
const _ac = function _ac(el, o) {
|
||||
const me = this;
|
||||
this.is_opened = false;
|
||||
|
||||
if (u.hasClass('.suggestion-box', el)) {
|
||||
this.container = el;
|
||||
} else {
|
||||
this.container = el.querySelector('.suggestion-box');
|
||||
}
|
||||
|
||||
this.input = $(this.container.querySelector('.suggestion-box__input'));
|
||||
this.input.setAttribute("autocomplete", "off");
|
||||
this.input.setAttribute("aria-autocomplete", "list");
|
||||
this.ul = $(this.container.querySelector('.suggestion-box__results'));
|
||||
this.status = $(this.container.querySelector('.suggestion-box__additions'));
|
||||
o = o || {};
|
||||
configure(this, {
|
||||
'match_current_word': false,
|
||||
// Match only the current word, otherwise all input is matched
|
||||
'match_on_tab': false,
|
||||
// Whether matching should only start when tab's pressed
|
||||
'min_chars': 2,
|
||||
'max_items': 10,
|
||||
'auto_evaluate': true,
|
||||
'auto_first': false,
|
||||
'data': _ac.DATA,
|
||||
'filter': _ac.FILTER_CONTAINS,
|
||||
'sort': o.sort === false ? false : _ac.SORT_BYLENGTH,
|
||||
'item': _ac.ITEM,
|
||||
'replace': _ac.REPLACE
|
||||
}, o);
|
||||
this.index = -1;
|
||||
const input = {
|
||||
"blur": this.close.bind(this, {
|
||||
reason: "blur"
|
||||
}),
|
||||
"keydown": function keydown(evt) {
|
||||
const c = evt.keyCode; // If the dropdown `ul` is in view, then act on keydown for the following keys:
|
||||
// Enter / Esc / Up / Down
|
||||
|
||||
if (me.opened) {
|
||||
if (c === _converse.keycodes.ENTER && me.selected) {
|
||||
evt.preventDefault();
|
||||
me.select();
|
||||
} else if (c === _converse.keycodes.ESCAPE) {
|
||||
me.close({
|
||||
reason: "esc"
|
||||
});
|
||||
} else if (c === _converse.keycodes.UP_ARROW || c === _converse.keycodes.DOWN_ARROW) {
|
||||
evt.preventDefault();
|
||||
me[c === _converse.keycodes.UP_ARROW ? "previous" : "next"]();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (this.auto_evaluate) {
|
||||
input["input"] = this.evaluate.bind(this);
|
||||
} // Bind events
|
||||
|
||||
|
||||
this._events = {
|
||||
'input': input,
|
||||
'form': {
|
||||
"submit": this.close.bind(this, {
|
||||
reason: "submit"
|
||||
})
|
||||
},
|
||||
'ul': {
|
||||
"mousedown": function mousedown(evt) {
|
||||
let li = evt.target;
|
||||
|
||||
if (li !== this) {
|
||||
while (li && !/li/i.test(li.nodeName)) {
|
||||
li = li.parentNode;
|
||||
}
|
||||
|
||||
if (li && evt.button === 0) {
|
||||
// Only select on left click
|
||||
evt.preventDefault();
|
||||
me.select(li, evt.target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
$.bind(this.input, this._events.input);
|
||||
$.bind(this.input.form, this._events.form);
|
||||
$.bind(this.ul, this._events.ul);
|
||||
|
||||
if (this.input.hasAttribute("list")) {
|
||||
this.list = "#" + this.input.getAttribute("list");
|
||||
this.input.removeAttribute("list");
|
||||
} else {
|
||||
this.list = this.input.getAttribute("data-list") || o.list || [];
|
||||
}
|
||||
|
||||
_ac.all.push(this);
|
||||
};
|
||||
|
||||
_ac.prototype = {
|
||||
set list(list) {
|
||||
if (Array.isArray(list)) {
|
||||
this._list = list;
|
||||
} else if (typeof list === "string" && _.includes(list, ",")) {
|
||||
this._list = list.split(/\s*,\s*/);
|
||||
} else {
|
||||
// Element or CSS selector
|
||||
list = $(list);
|
||||
|
||||
if (list && list.children) {
|
||||
const items = [];
|
||||
slice.apply(list.children).forEach(function (el) {
|
||||
if (!el.disabled) {
|
||||
const text = el.textContent.trim(),
|
||||
value = el.value || text,
|
||||
label = el.label || text;
|
||||
|
||||
if (value !== "") {
|
||||
items.push({
|
||||
label: label,
|
||||
value: value
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
this._list = items;
|
||||
}
|
||||
}
|
||||
|
||||
if (document.activeElement === this.input) {
|
||||
this.evaluate();
|
||||
}
|
||||
},
|
||||
|
||||
get selected() {
|
||||
return this.index > -1;
|
||||
},
|
||||
|
||||
get opened() {
|
||||
return this.is_opened;
|
||||
},
|
||||
|
||||
close(o) {
|
||||
if (!this.opened) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.ul.setAttribute("hidden", "");
|
||||
this.is_opened = false;
|
||||
this.index = -1;
|
||||
$.fire(this.input, "suggestion-box-close", o || {});
|
||||
},
|
||||
|
||||
open() {
|
||||
this.ul.removeAttribute("hidden");
|
||||
this.is_opened = true;
|
||||
|
||||
if (this.auto_first && this.index === -1) {
|
||||
this.goto(0);
|
||||
}
|
||||
|
||||
$.fire(this.input, "suggestion-box-open");
|
||||
},
|
||||
|
||||
destroy() {
|
||||
//remove events from the input and its form
|
||||
$.unbind(this.input, this._events.input);
|
||||
$.unbind(this.input.form, this._events.form); //move the input out of the suggestion-box container and remove the container and its children
|
||||
|
||||
const parentNode = this.container.parentNode;
|
||||
parentNode.insertBefore(this.input, this.container);
|
||||
parentNode.removeChild(this.container); //remove autocomplete and aria-autocomplete attributes
|
||||
|
||||
this.input.removeAttribute("autocomplete");
|
||||
this.input.removeAttribute("aria-autocomplete"); //remove this awesomeplete instance from the global array of instances
|
||||
|
||||
var indexOfAutoComplete = _ac.all.indexOf(this);
|
||||
|
||||
if (indexOfAutoComplete !== -1) {
|
||||
_ac.all.splice(indexOfAutoComplete, 1);
|
||||
}
|
||||
},
|
||||
|
||||
next() {
|
||||
var count = this.ul.children.length;
|
||||
this.goto(this.index < count - 1 ? this.index + 1 : count ? 0 : -1);
|
||||
},
|
||||
|
||||
previous() {
|
||||
var count = this.ul.children.length;
|
||||
var pos = this.index - 1;
|
||||
this.goto(this.selected && pos !== -1 ? pos : count - 1);
|
||||
},
|
||||
|
||||
// Should not be used, highlights specific item without any checks!
|
||||
goto(i) {
|
||||
var lis = this.ul.children;
|
||||
|
||||
if (this.selected) {
|
||||
lis[this.index].setAttribute("aria-selected", "false");
|
||||
}
|
||||
|
||||
this.index = i;
|
||||
|
||||
if (i > -1 && lis.length > 0) {
|
||||
lis[i].setAttribute("aria-selected", "true");
|
||||
this.status.textContent = lis[i].textContent; // scroll to highlighted element in case parent's height is fixed
|
||||
|
||||
this.ul.scrollTop = lis[i].offsetTop - this.ul.clientHeight + lis[i].clientHeight;
|
||||
$.fire(this.input, "suggestion-box-highlight", {
|
||||
text: this.suggestions[this.index]
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
select(selected, origin) {
|
||||
if (selected) {
|
||||
this.index = u.siblingIndex(selected);
|
||||
} else {
|
||||
selected = this.ul.children[this.index];
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
const suggestion = this.suggestions[this.index],
|
||||
allowed = $.fire(this.input, "suggestion-box-select", {
|
||||
'text': suggestion,
|
||||
'origin': origin || selected
|
||||
});
|
||||
|
||||
if (allowed) {
|
||||
this.replace(suggestion);
|
||||
this.close({
|
||||
'reason': 'select'
|
||||
});
|
||||
this.auto_completing = false;
|
||||
this.trigger("suggestion-box-selectcomplete", {
|
||||
'text': suggestion
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
keyPressed(ev) {
|
||||
if (_.includes([_converse.keycodes.SHIFT, _converse.keycodes.META, _converse.keycodes.META_RIGHT, _converse.keycodes.ESCAPE, _converse.keycodes.ALT], ev.keyCode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.match_on_tab && ev.keyCode === _converse.keycodes.TAB) {
|
||||
ev.preventDefault();
|
||||
this.auto_completing = true;
|
||||
}
|
||||
|
||||
if (this.auto_completing) {
|
||||
this.evaluate();
|
||||
}
|
||||
},
|
||||
|
||||
evaluate(ev) {
|
||||
let value = this.input.value;
|
||||
|
||||
if (this.match_current_word) {
|
||||
value = u.getCurrentWord(this.input);
|
||||
}
|
||||
|
||||
if (value.length >= this.min_chars && this._list.length > 0) {
|
||||
this.index = -1; // Populate list with options that match
|
||||
|
||||
this.ul.innerHTML = "";
|
||||
this.suggestions = this._list.map(item => new Suggestion(this.data(item, value))).filter(item => this.filter(item, value));
|
||||
|
||||
if (this.sort !== false) {
|
||||
this.suggestions = this.suggestions.sort(this.sort);
|
||||
}
|
||||
|
||||
this.suggestions = this.suggestions.slice(0, this.max_items);
|
||||
this.suggestions.forEach(text => this.ul.appendChild(this.item(text, value)));
|
||||
|
||||
if (this.ul.children.length === 0) {
|
||||
this.close({
|
||||
'reason': 'nomatches'
|
||||
});
|
||||
} else {
|
||||
this.open();
|
||||
}
|
||||
} else {
|
||||
this.close({
|
||||
'reason': 'nomatches'
|
||||
});
|
||||
this.auto_completing = false;
|
||||
}
|
||||
}
|
||||
|
||||
}; // Make it an event emitter
|
||||
|
||||
_.extend(_ac.prototype, Backbone.Events); // Static methods/properties
|
||||
|
||||
|
||||
_ac.all = [];
|
||||
|
||||
_ac.SORT_BYLENGTH = function (a, b) {
|
||||
if (a.length !== b.length) {
|
||||
return a.length - b.length;
|
||||
}
|
||||
|
||||
return a < b ? -1 : 1;
|
||||
};
|
||||
|
||||
_ac.ITEM = function (text, input) {
|
||||
input = input.trim();
|
||||
var element = document.createElement("li");
|
||||
element.setAttribute("aria-selected", "false");
|
||||
var regex = new RegExp("(" + input + ")", "ig");
|
||||
var parts = input ? text.split(regex) : [text];
|
||||
parts.forEach(function (txt) {
|
||||
if (input && txt.match(regex)) {
|
||||
var match = document.createElement("mark");
|
||||
match.textContent = txt;
|
||||
element.appendChild(match);
|
||||
} else {
|
||||
element.appendChild(document.createTextNode(txt));
|
||||
}
|
||||
});
|
||||
return element;
|
||||
};
|
||||
|
||||
_ac.REPLACE = function (text) {
|
||||
this.input.value = text.value;
|
||||
};
|
||||
|
||||
_ac.DATA = function (item
|
||||
/*, input*/
|
||||
) {
|
||||
return item;
|
||||
}; // Private functions
|
||||
|
||||
|
||||
function Suggestion(data) {
|
||||
const o = Array.isArray(data) ? {
|
||||
label: data[0],
|
||||
value: data[1]
|
||||
} : typeof data === "object" && "label" in data && "value" in data ? data : {
|
||||
label: data,
|
||||
value: data
|
||||
};
|
||||
this.label = o.label || o.value;
|
||||
this.value = o.value;
|
||||
}
|
||||
|
||||
Object.defineProperty(Suggestion.prototype = Object.create(String.prototype), "length", {
|
||||
get: function get() {
|
||||
return this.label.length;
|
||||
}
|
||||
});
|
||||
|
||||
Suggestion.prototype.toString = Suggestion.prototype.valueOf = function () {
|
||||
return "" + this.label;
|
||||
};
|
||||
|
||||
function configure(instance, properties, o) {
|
||||
for (var i in properties) {
|
||||
if (!Object.prototype.hasOwnProperty.call(properties, i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const initial = properties[i],
|
||||
attr_value = instance.input.getAttribute("data-" + i.toLowerCase());
|
||||
|
||||
if (typeof initial === "number") {
|
||||
instance[i] = parseInt(attr_value, 10);
|
||||
} else if (initial === false) {
|
||||
// Boolean options must be false by default anyway
|
||||
instance[i] = attr_value !== null;
|
||||
} else if (initial instanceof Function) {
|
||||
instance[i] = null;
|
||||
} else {
|
||||
instance[i] = attr_value;
|
||||
}
|
||||
|
||||
if (!instance[i] && instance[i] !== 0) {
|
||||
instance[i] = i in o ? o[i] : initial;
|
||||
}
|
||||
}
|
||||
} // Helpers
|
||||
|
||||
|
||||
var slice = Array.prototype.slice;
|
||||
|
||||
function $(expr, con) {
|
||||
return typeof expr === "string" ? (con || document).querySelector(expr) : expr || null;
|
||||
}
|
||||
|
||||
function $$(expr, con) {
|
||||
return slice.call((con || document).querySelectorAll(expr));
|
||||
}
|
||||
|
||||
$.bind = function (element, o) {
|
||||
if (element) {
|
||||
for (var event in o) {
|
||||
if (!Object.prototype.hasOwnProperty.call(o, event)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const callback = o[event];
|
||||
event.split(/\s+/).forEach(event => element.addEventListener(event, callback));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$.unbind = function (element, o) {
|
||||
if (element) {
|
||||
for (var event in o) {
|
||||
if (!Object.prototype.hasOwnProperty.call(o, event)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const callback = o[event];
|
||||
event.split(/\s+/).forEach(event => element.removeEventListener(event, callback));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$.fire = function (target, type, properties) {
|
||||
var evt = document.createEvent("HTMLEvents");
|
||||
evt.initEvent(type, true, true);
|
||||
|
||||
for (var j in properties) {
|
||||
if (!Object.prototype.hasOwnProperty.call(properties, j)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
evt[j] = properties[j];
|
||||
}
|
||||
|
||||
return target.dispatchEvent(evt);
|
||||
};
|
||||
|
||||
$.regExpEscape = function (s) {
|
||||
return s.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&");
|
||||
};
|
||||
|
||||
_ac.$ = $;
|
||||
_ac.$$ = $$;
|
||||
_converse.AutoComplete = _ac;
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./src/converse-bookmarks.js":
|
||||
/*!***********************************!*\
|
||||
!*** ./src/converse-bookmarks.js ***!
|
||||
|
@ -69426,18 +69915,6 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
|
|||
sizzle = _converse$env.sizzle,
|
||||
moment = _converse$env.moment;
|
||||
const u = converse.env.utils;
|
||||
const KEY = {
|
||||
ENTER: 13,
|
||||
SHIFT: 17,
|
||||
CTRL: 17,
|
||||
ALT: 18,
|
||||
ESCAPE: 27,
|
||||
UP_ARROW: 38,
|
||||
DOWN_ARROW: 40,
|
||||
FORWARD_SLASH: 47,
|
||||
META: 91,
|
||||
META_RIGHT: 93
|
||||
};
|
||||
converse.plugins.add('converse-chatview', {
|
||||
/* Plugin dependencies are other plugins which might be
|
||||
* overridden or relied upon, and therefore need to be loaded before
|
||||
|
@ -70308,21 +70785,21 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
|
|||
}
|
||||
|
||||
if (!ev.shiftKey && !ev.altKey) {
|
||||
if (ev.keyCode === KEY.FORWARD_SLASH) {
|
||||
if (ev.keyCode === _converse.keycodes.FORWARD_SLASH) {
|
||||
// Forward slash is used to run commands. Nothing to do here.
|
||||
return;
|
||||
} else if (ev.keyCode === KEY.ESCAPE) {
|
||||
} else if (ev.keyCode === _converse.keycodes.ESCAPE) {
|
||||
return this.onEscapePressed(ev);
|
||||
} else if (ev.keyCode === KEY.ENTER) {
|
||||
} else if (ev.keyCode === _converse.keycodes.ENTER) {
|
||||
return this.onFormSubmitted(ev);
|
||||
} else if (ev.keyCode === KEY.UP_ARROW && !ev.target.selectionEnd) {
|
||||
} else if (ev.keyCode === _converse.keycodes.UP_ARROW && !ev.target.selectionEnd) {
|
||||
return this.editEarlierMessage();
|
||||
} else if (ev.keyCode === KEY.DOWN_ARROW && ev.target.selectionEnd === ev.target.value.length) {
|
||||
} else if (ev.keyCode === _converse.keycodes.DOWN_ARROW && ev.target.selectionEnd === ev.target.value.length) {
|
||||
return this.editLaterMessage();
|
||||
}
|
||||
}
|
||||
|
||||
if (_.includes([KEY.SHIFT, KEY.META, KEY.META_RIGHT, KEY.ESCAPE, KEY.ALT], ev.keyCode)) {
|
||||
if (_.includes([_converse.keycodes.SHIFT, _converse.keycodes.META, _converse.keycodes.META_RIGHT, _converse.keycodes.ESCAPE, _converse.keycodes.ALT], ev.keyCode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -71490,13 +71967,26 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
|
|||
_.extend(_converse, Backbone.Events); // Core plugins are whitelisted automatically
|
||||
|
||||
|
||||
_converse.core_plugins = ['converse-bookmarks', 'converse-caps', 'converse-chatboxes', 'converse-chatview', 'converse-controlbox', 'converse-core', 'converse-disco', 'converse-dragresize', 'converse-embedded', 'converse-fullscreen', 'converse-headline', 'converse-mam', 'converse-message-view', 'converse-minimize', 'converse-modal', 'converse-muc', 'converse-muc-views', 'converse-notification', 'converse-oauth', 'converse-ping', 'converse-profile', 'converse-push', 'converse-register', 'converse-roomslist', 'converse-roster', 'converse-rosterview', 'converse-singleton', 'converse-spoilers', 'converse-vcard']; // Setting wait to 59 instead of 60 to avoid timing conflicts with the
|
||||
_converse.core_plugins = ['converse-autocomplete', 'converse-bookmarks', 'converse-caps', 'converse-chatboxes', 'converse-chatview', 'converse-controlbox', 'converse-core', 'converse-disco', 'converse-dragresize', 'converse-embedded', 'converse-fullscreen', 'converse-headline', 'converse-mam', 'converse-message-view', 'converse-minimize', 'converse-modal', 'converse-muc', 'converse-muc-views', 'converse-notification', 'converse-oauth', 'converse-ping', 'converse-profile', 'converse-push', 'converse-register', 'converse-roomslist', 'converse-roster', 'converse-rosterview', 'converse-singleton', 'converse-spoilers', 'converse-vcard']; // Setting wait to 59 instead of 60 to avoid timing conflicts with the
|
||||
// webserver, which is often also set to 60 and might therefore sometimes
|
||||
// return a 504 error page instead of passing through to the BOSH proxy.
|
||||
|
||||
const BOSH_WAIT = 59; // Make converse pluggable
|
||||
|
||||
pluggable.enable(_converse, '_converse', 'pluggable'); // Module-level constants
|
||||
pluggable.enable(_converse, '_converse', 'pluggable');
|
||||
_converse.keycodes = {
|
||||
TAB: 9,
|
||||
ENTER: 13,
|
||||
SHIFT: 16,
|
||||
CTRL: 17,
|
||||
ALT: 18,
|
||||
ESCAPE: 27,
|
||||
UP_ARROW: 38,
|
||||
DOWN_ARROW: 40,
|
||||
FORWARD_SLASH: 47,
|
||||
META: 91,
|
||||
META_RIGHT: 93
|
||||
}; // Module-level constants
|
||||
|
||||
_converse.STATUS_WEIGHTS = {
|
||||
'offline': 6,
|
||||
|
@ -75750,7 +76240,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
|
|||
// Converse.js
|
||||
// http://conversejs.org
|
||||
//
|
||||
// Copyright (c) 2012-2018, the Converse.js developers
|
||||
// Copyright (c) 2013-2018, the Converse.js developers
|
||||
// Licensed under the Mozilla Public License (MPLv2)
|
||||
(function (root, factory) {
|
||||
!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! converse-core */ "./src/converse-core.js"), __webpack_require__(/*! utils/muc */ "./src/utils/muc.js"), __webpack_require__(/*! xss */ "./node_modules/xss/dist/xss.js"), __webpack_require__(/*! templates/add_chatroom_modal.html */ "./src/templates/add_chatroom_modal.html"), __webpack_require__(/*! templates/chatarea.html */ "./src/templates/chatarea.html"), __webpack_require__(/*! templates/chatroom.html */ "./src/templates/chatroom.html"), __webpack_require__(/*! templates/chatroom_details_modal.html */ "./src/templates/chatroom_details_modal.html"), __webpack_require__(/*! templates/chatroom_disconnect.html */ "./src/templates/chatroom_disconnect.html"), __webpack_require__(/*! templates/chatroom_features.html */ "./src/templates/chatroom_features.html"), __webpack_require__(/*! templates/chatroom_form.html */ "./src/templates/chatroom_form.html"), __webpack_require__(/*! templates/chatroom_head.html */ "./src/templates/chatroom_head.html"), __webpack_require__(/*! templates/chatroom_invite.html */ "./src/templates/chatroom_invite.html"), __webpack_require__(/*! templates/chatroom_nickname_form.html */ "./src/templates/chatroom_nickname_form.html"), __webpack_require__(/*! templates/chatroom_password_form.html */ "./src/templates/chatroom_password_form.html"), __webpack_require__(/*! templates/chatroom_sidebar.html */ "./src/templates/chatroom_sidebar.html"), __webpack_require__(/*! templates/chatroom_toolbar.html */ "./src/templates/chatroom_toolbar.html"), __webpack_require__(/*! templates/info.html */ "./src/templates/info.html"), __webpack_require__(/*! templates/list_chatrooms_modal.html */ "./src/templates/list_chatrooms_modal.html"), __webpack_require__(/*! templates/occupant.html */ "./src/templates/occupant.html"), __webpack_require__(/*! templates/room_description.html */ "./src/templates/room_description.html"), __webpack_require__(/*! templates/room_item.html */ "./src/templates/room_item.html"), __webpack_require__(/*! templates/room_panel.html */ "./src/templates/room_panel.html"), __webpack_require__(/*! templates/rooms_results.html */ "./src/templates/rooms_results.html"), __webpack_require__(/*! templates/spinner.html */ "./src/templates/spinner.html"), __webpack_require__(/*! awesomplete */ "./node_modules/awesomplete-avoid-xss/awesomplete.js"), __webpack_require__(/*! converse-modal */ "./src/converse-modal.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory),
|
||||
|
@ -75801,7 +76291,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
|
|||
* If the setting "strict_plugin_dependencies" is set to true,
|
||||
* an error will be raised if the plugin is not found.
|
||||
*/
|
||||
dependencies: ["converse-modal", "converse-controlbox", "converse-chatview"],
|
||||
dependencies: ["converse-autocomplete", "converse-modal", "converse-controlbox", "converse-chatview"],
|
||||
overrides: {
|
||||
ControlBoxView: {
|
||||
renderRoomsPanel() {
|
||||
|
@ -76292,6 +76782,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
|
|||
this.renderHeading();
|
||||
this.renderChatArea();
|
||||
this.renderMessageForm();
|
||||
this.initAutoComplete();
|
||||
|
||||
if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED) {
|
||||
this.showSpinner();
|
||||
|
@ -76321,6 +76812,26 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
|
|||
return this;
|
||||
},
|
||||
|
||||
initAutoComplete() {
|
||||
this.auto_complete = new _converse.AutoComplete(this.el, {
|
||||
'auto_evaluate': false,
|
||||
'min_chars': 1,
|
||||
'match_current_word': true,
|
||||
'match_on_tab': true,
|
||||
'list': this.model.occupants.map(o => ({
|
||||
'label': o.getDisplayName(),
|
||||
'value': o.get('jid')
|
||||
})),
|
||||
'filter': _converse.FILTER_STARTSWITH
|
||||
});
|
||||
this.auto_complete.on('suggestion-box-selectcomplete', () => this.auto_completing = false);
|
||||
},
|
||||
|
||||
keyPressed(ev) {
|
||||
this.auto_complete.keyPressed(ev);
|
||||
return _converse.ChatBoxView.prototype.keyPressed.apply(this, arguments);
|
||||
},
|
||||
|
||||
showRoomDetailsModal(ev) {
|
||||
ev.preventDefault();
|
||||
|
||||
|
@ -76587,9 +77098,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
|
|||
},
|
||||
|
||||
parseMessageForCommands(text) {
|
||||
const _super_ = _converse.ChatBoxView.prototype;
|
||||
|
||||
if (_super_.parseMessageForCommands.apply(this, arguments)) {
|
||||
if (_converse.ChatBoxView.prototype.parseMessageForCommands.apply(this, arguments)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -83819,7 +84328,7 @@ if (true) {
|
|||
* --------------------
|
||||
* Any of the following components may be removed if they're not needed.
|
||||
*/
|
||||
__webpack_require__(/*! converse-bookmarks */ "./src/converse-bookmarks.js"), // XEP-0048 Bookmarks
|
||||
__webpack_require__(/*! converse-autocomplete */ "./src/converse-autocomplete.js"), __webpack_require__(/*! converse-bookmarks */ "./src/converse-bookmarks.js"), // XEP-0048 Bookmarks
|
||||
__webpack_require__(/*! converse-caps */ "./src/converse-caps.js"), // XEP-0115 Entity Capabilities
|
||||
__webpack_require__(/*! converse-chatview */ "./src/converse-chatview.js"), // Renders standalone chat boxes for single user chat
|
||||
__webpack_require__(/*! converse-controlbox */ "./src/converse-controlbox.js"), // The control box
|
||||
|
@ -84504,25 +85013,25 @@ __p += '\n <input type="text" placeholder="' +
|
|||
if (!o.composing_spoiler) { ;
|
||||
__p += ' hidden ';
|
||||
} ;
|
||||
__p += ' spoiler-hint"/>\n <textarea\n type="text"\n class="chat-textarea\n ';
|
||||
__p += ' spoiler-hint"/>\n\n <div class="suggestion-box">\n <ul class="suggestion-box__results suggestion-box__results--above" hidden></ul>\n <textarea\n type="text"\n class="chat-textarea suggestion-box__input\n ';
|
||||
if (o.show_send_button) { ;
|
||||
__p += ' chat-textarea-send-button ';
|
||||
} ;
|
||||
__p += '\n ';
|
||||
__p += '\n ';
|
||||
if (o.composing_spoiler) { ;
|
||||
__p += ' spoiler ';
|
||||
} ;
|
||||
__p += '"\n placeholder="' +
|
||||
__p += '"\n placeholder="' +
|
||||
__e(o.label_personal_message) +
|
||||
'">' +
|
||||
((__t = ( o.message_value )) == null ? '' : __t) +
|
||||
'</textarea>\n ';
|
||||
'</textarea>\n <span class="suggestion-box__additions visually-hidden" role="status" aria-live="assertive" aria-relevant="additions"></span>\n\n\n\n ';
|
||||
if (o.show_send_button) { ;
|
||||
__p += '\n <button type="submit" class="pure-button send-button">' +
|
||||
__p += '\n <button type="submit" class="pure-button send-button">' +
|
||||
__e( o.label_send ) +
|
||||
'</button>\n ';
|
||||
'</button>\n ';
|
||||
} ;
|
||||
__p += '\n</form>\n</div>\n';
|
||||
__p += '\n </div>\n</form>\n</div>\n';
|
||||
return __p
|
||||
};
|
||||
|
||||
|
@ -88055,6 +88564,18 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
|
|||
}
|
||||
};
|
||||
|
||||
u.siblingIndex = function (el) {
|
||||
/* eslint-disable no-cond-assign */
|
||||
for (var i = 0; el = el.previousElementSibling; i++);
|
||||
|
||||
return i;
|
||||
};
|
||||
|
||||
u.getCurrentWord = function (input) {
|
||||
const cursor = input.selectionEnd || undefined;
|
||||
return _.last(input.value.slice(0, cursor).split(' '));
|
||||
};
|
||||
|
||||
u.isVisible = function (el) {
|
||||
if (u.hasClass('hidden', el)) {
|
||||
return false;
|
||||
|
|
|
@ -7,13 +7,14 @@
|
|||
}
|
||||
|
||||
.form-group {
|
||||
.suggestion-box,
|
||||
.awesomplete {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
div.awesomplete {
|
||||
display: inline-block;
|
||||
.suggestion-box,
|
||||
.awesomplete {
|
||||
position: relative;
|
||||
mark {
|
||||
background: $lightest-red;
|
||||
|
@ -23,6 +24,7 @@
|
|||
display: block;
|
||||
}
|
||||
|
||||
.suggestion-box__results,
|
||||
> ul {
|
||||
&:before {
|
||||
content: "";
|
||||
|
@ -30,18 +32,19 @@
|
|||
top: -.43em;
|
||||
left: 1em;
|
||||
width: 0; height: 0;
|
||||
padding: .4em;
|
||||
background: white;
|
||||
border: inherit;
|
||||
border-right: 0;
|
||||
border-bottom: 0;
|
||||
-webkit-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
z-index: 2;
|
||||
min-width: 100%;
|
||||
box-sizing: border-box;
|
||||
list-style: none;
|
||||
|
@ -49,9 +52,9 @@
|
|||
border-radius: .3em;
|
||||
margin: .2em 0 0;
|
||||
background: hsla(0,0%,100%,.9);
|
||||
background: linear-gradient(to bottom right, white, hsla(0,0%,100%,.8));
|
||||
background: linear-gradient(to bottom right, white, hsla(0,0%,100%,.9));
|
||||
border: 1px solid rgba(0,0,0,.3);
|
||||
box-shadow: .05em .2em .6em rgba(0,0,0,.2);
|
||||
box-shadow: .05em .2em .6em rgba(0,0,0,.1);
|
||||
text-shadow: none;
|
||||
|
||||
> li {
|
||||
|
@ -62,19 +65,45 @@
|
|||
padding: 1em;
|
||||
}
|
||||
}
|
||||
.suggestion-box__results--above {
|
||||
bottom: 4.5em;
|
||||
&:before {
|
||||
display: none;
|
||||
}
|
||||
&:after {
|
||||
z-index: 1;
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -.43em;
|
||||
left: 1em;
|
||||
width: 0; height: 0;
|
||||
padding: .4em;
|
||||
background: white;
|
||||
border: inherit;
|
||||
border-left: 0;
|
||||
border-top: 0;
|
||||
-webkit-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.suggestion-box > ul[hidden],
|
||||
.suggestion-box > ul:empty,
|
||||
div.awesomplete > ul[hidden],
|
||||
div.awesomplete > ul:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@supports (transform: scale(0)) {
|
||||
.suggestion-box > ul,
|
||||
div.awesomplete > ul {
|
||||
transition: .3s cubic-bezier(.4,.2,.5,1.4);
|
||||
transform-origin: 1.43em -.43em;
|
||||
}
|
||||
|
||||
.suggestion-box > ul[hidden],
|
||||
.suggestion-box > ul:empty,
|
||||
div.awesomplete > ul[hidden],
|
||||
div.awesomplete > ul:empty {
|
||||
opacity: 0;
|
||||
|
@ -84,21 +113,26 @@
|
|||
}
|
||||
}
|
||||
|
||||
.suggestion-box > ul > li:hover,
|
||||
div.awesomplete > ul > li:hover {
|
||||
z-index: 2;
|
||||
background: $red;
|
||||
color: $inverse-link-color;
|
||||
}
|
||||
|
||||
.suggestion-box > ul > li[aria-selected="true"],
|
||||
div.awesomplete > ul > li[aria-selected="true"] {
|
||||
background: hsl(205, 40%, 40%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.suggestion-box li:hover mark,
|
||||
div.awesomplete li:hover mark {
|
||||
background: $darkest-red;
|
||||
color: $inverse-link-color;
|
||||
}
|
||||
|
||||
.suggestion-box li[aria-selected="true"] mark,
|
||||
div.awesomplete li[aria-selected="true"] mark {
|
||||
background: hsl(86, 100%, 21%);
|
||||
color: inherit;
|
||||
|
|
|
@ -605,6 +605,11 @@
|
|||
ul {
|
||||
width: 100%;
|
||||
}
|
||||
.suggestion-box__results {
|
||||
&:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.toggle-smiley {
|
||||
ul {
|
||||
&.emoji-toolbar {
|
||||
|
|
|
@ -271,7 +271,6 @@
|
|||
}
|
||||
.chat-textarea {
|
||||
border-bottom-right-radius: 0;
|
||||
resize: none;
|
||||
&.correcting {
|
||||
background-color: lighten($chatroom-head-color, 30%);
|
||||
}
|
||||
|
|
465
src/converse-autocomplete.js
Normal file
465
src/converse-autocomplete.js
Normal file
|
@ -0,0 +1,465 @@
|
|||
// Converse.js
|
||||
// http://conversejs.org
|
||||
//
|
||||
// Copyright (c) 2013-2018, the Converse.js developers
|
||||
// Licensed under the Mozilla Public License (MPLv2)
|
||||
|
||||
// This plugin started as a fork of Lea Verou's Awesomplete
|
||||
// https://leaverou.github.io/awesomplete/
|
||||
|
||||
(function (root, factory) {
|
||||
define(["converse-core"], factory);
|
||||
}(this, function (converse) {
|
||||
|
||||
const { _, Backbone } = converse.env,
|
||||
u = converse.env.utils;
|
||||
|
||||
|
||||
converse.plugins.add("converse-autocomplete", {
|
||||
initialize () {
|
||||
const { _converse } = this;
|
||||
|
||||
_converse.FILTER_CONTAINS = function (text, input) {
|
||||
return RegExp($.regExpEscape(input.trim()), "i").test(text);
|
||||
};
|
||||
|
||||
_converse.FILTER_STARTSWITH = function (text, input) {
|
||||
return RegExp("^" + $.regExpEscape(input.trim()), "i").test(text);
|
||||
};
|
||||
|
||||
const _ac = function (el, o) {
|
||||
const me = this;
|
||||
|
||||
this.is_opened = false;
|
||||
|
||||
if (u.hasClass('.suggestion-box', el)) {
|
||||
this.container = el;
|
||||
} else {
|
||||
this.container = el.querySelector('.suggestion-box');
|
||||
}
|
||||
this.input = $(this.container.querySelector('.suggestion-box__input'));
|
||||
this.input.setAttribute("autocomplete", "off");
|
||||
this.input.setAttribute("aria-autocomplete", "list");
|
||||
|
||||
this.ul = $(this.container.querySelector('.suggestion-box__results'));
|
||||
this.status = $(this.container.querySelector('.suggestion-box__additions'));
|
||||
|
||||
o = o || {};
|
||||
|
||||
configure(this, {
|
||||
'match_current_word': false, // Match only the current word, otherwise all input is matched
|
||||
'match_on_tab': false, // Whether matching should only start when tab's pressed
|
||||
'min_chars': 2,
|
||||
'max_items': 10,
|
||||
'auto_evaluate': true,
|
||||
'auto_first': false,
|
||||
'data': _ac.DATA,
|
||||
'filter': _ac.FILTER_CONTAINS,
|
||||
'sort': o.sort === false ? false : _ac.SORT_BYLENGTH,
|
||||
'item': _ac.ITEM,
|
||||
'replace': _ac.REPLACE
|
||||
}, o);
|
||||
|
||||
this.index = -1;
|
||||
|
||||
const input = {
|
||||
"blur": this.close.bind(this, { reason: "blur" }),
|
||||
"keydown": function(evt) {
|
||||
const c = evt.keyCode;
|
||||
|
||||
// If the dropdown `ul` is in view, then act on keydown for the following keys:
|
||||
// Enter / Esc / Up / Down
|
||||
if(me.opened) {
|
||||
if (c === _converse.keycodes.ENTER && me.selected) {
|
||||
evt.preventDefault();
|
||||
me.select();
|
||||
} else if (c === _converse.keycodes.ESCAPE) {
|
||||
me.close({ reason: "esc" });
|
||||
} else if (c === _converse.keycodes.UP_ARROW || c === _converse.keycodes.DOWN_ARROW) {
|
||||
evt.preventDefault();
|
||||
me[c === _converse.keycodes.UP_ARROW ? "previous" : "next"]();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.auto_evaluate) {
|
||||
input["input"] = this.evaluate.bind(this);
|
||||
}
|
||||
|
||||
// Bind events
|
||||
this._events = {
|
||||
'input': input,
|
||||
'form': {
|
||||
"submit": this.close.bind(this, { reason: "submit" })
|
||||
},
|
||||
'ul': {
|
||||
"mousedown": function(evt) {
|
||||
let li = evt.target;
|
||||
if (li !== this) {
|
||||
while (li && !(/li/i).test(li.nodeName)) {
|
||||
li = li.parentNode;
|
||||
}
|
||||
|
||||
if (li && evt.button === 0) { // Only select on left click
|
||||
evt.preventDefault();
|
||||
me.select(li, evt.target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$.bind(this.input, this._events.input);
|
||||
$.bind(this.input.form, this._events.form);
|
||||
$.bind(this.ul, this._events.ul);
|
||||
|
||||
if (this.input.hasAttribute("list")) {
|
||||
this.list = "#" + this.input.getAttribute("list");
|
||||
this.input.removeAttribute("list");
|
||||
}
|
||||
else {
|
||||
this.list = this.input.getAttribute("data-list") || o.list || [];
|
||||
}
|
||||
|
||||
_ac.all.push(this);
|
||||
}
|
||||
|
||||
_ac.prototype = {
|
||||
set list (list) {
|
||||
if (Array.isArray(list)) {
|
||||
this._list = list;
|
||||
}
|
||||
else if (typeof list === "string" && _.includes(list, ",")) {
|
||||
this._list = list.split(/\s*,\s*/);
|
||||
}
|
||||
else { // Element or CSS selector
|
||||
list = $(list);
|
||||
if (list && list.children) {
|
||||
const items = [];
|
||||
slice.apply(list.children).forEach(function (el) {
|
||||
if (!el.disabled) {
|
||||
const text = el.textContent.trim(),
|
||||
value = el.value || text,
|
||||
label = el.label || text;
|
||||
if (value !== "") {
|
||||
items.push({ label: label, value: value });
|
||||
}
|
||||
}
|
||||
});
|
||||
this._list = items;
|
||||
}
|
||||
}
|
||||
|
||||
if (document.activeElement === this.input) {
|
||||
this.evaluate();
|
||||
}
|
||||
},
|
||||
|
||||
get selected() {
|
||||
return this.index > -1;
|
||||
},
|
||||
|
||||
get opened() {
|
||||
return this.is_opened;
|
||||
},
|
||||
|
||||
close (o) {
|
||||
if (!this.opened) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.ul.setAttribute("hidden", "");
|
||||
this.is_opened = false;
|
||||
this.index = -1;
|
||||
|
||||
$.fire(this.input, "suggestion-box-close", o || {});
|
||||
},
|
||||
|
||||
open () {
|
||||
this.ul.removeAttribute("hidden");
|
||||
this.is_opened = true;
|
||||
|
||||
if (this.auto_first && this.index === -1) {
|
||||
this.goto(0);
|
||||
}
|
||||
|
||||
$.fire(this.input, "suggestion-box-open");
|
||||
},
|
||||
|
||||
destroy () {
|
||||
//remove events from the input and its form
|
||||
$.unbind(this.input, this._events.input);
|
||||
$.unbind(this.input.form, this._events.form);
|
||||
|
||||
//move the input out of the suggestion-box container and remove the container and its children
|
||||
const parentNode = this.container.parentNode;
|
||||
|
||||
parentNode.insertBefore(this.input, this.container);
|
||||
parentNode.removeChild(this.container);
|
||||
|
||||
//remove autocomplete and aria-autocomplete attributes
|
||||
this.input.removeAttribute("autocomplete");
|
||||
this.input.removeAttribute("aria-autocomplete");
|
||||
|
||||
//remove this awesomeplete instance from the global array of instances
|
||||
var indexOfAutoComplete = _ac.all.indexOf(this);
|
||||
|
||||
if (indexOfAutoComplete !== -1) {
|
||||
_ac.all.splice(indexOfAutoComplete, 1);
|
||||
}
|
||||
},
|
||||
|
||||
next () {
|
||||
var count = this.ul.children.length;
|
||||
this.goto(this.index < count - 1 ? this.index + 1 : (count ? 0 : -1) );
|
||||
},
|
||||
|
||||
previous () {
|
||||
var count = this.ul.children.length;
|
||||
var pos = this.index - 1;
|
||||
|
||||
this.goto(this.selected && pos !== -1 ? pos : count - 1);
|
||||
},
|
||||
|
||||
// Should not be used, highlights specific item without any checks!
|
||||
goto (i) {
|
||||
var lis = this.ul.children;
|
||||
|
||||
if (this.selected) {
|
||||
lis[this.index].setAttribute("aria-selected", "false");
|
||||
}
|
||||
|
||||
this.index = i;
|
||||
|
||||
if (i > -1 && lis.length > 0) {
|
||||
lis[i].setAttribute("aria-selected", "true");
|
||||
this.status.textContent = lis[i].textContent;
|
||||
|
||||
// scroll to highlighted element in case parent's height is fixed
|
||||
this.ul.scrollTop = lis[i].offsetTop - this.ul.clientHeight + lis[i].clientHeight;
|
||||
|
||||
$.fire(this.input, "suggestion-box-highlight", {
|
||||
text: this.suggestions[this.index]
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
select (selected, origin) {
|
||||
if (selected) {
|
||||
this.index = u.siblingIndex(selected);
|
||||
} else {
|
||||
selected = this.ul.children[this.index];
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
const suggestion = this.suggestions[this.index],
|
||||
allowed = $.fire(this.input, "suggestion-box-select", {
|
||||
'text': suggestion,
|
||||
'origin': origin || selected
|
||||
});
|
||||
|
||||
if (allowed) {
|
||||
this.replace(suggestion);
|
||||
this.close({'reason': 'select'});
|
||||
this.auto_completing = false;
|
||||
this.trigger("suggestion-box-selectcomplete", {'text': suggestion});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
keyPressed (ev) {
|
||||
if (_.includes([
|
||||
_converse.keycodes.SHIFT,
|
||||
_converse.keycodes.META,
|
||||
_converse.keycodes.META_RIGHT,
|
||||
_converse.keycodes.ESCAPE,
|
||||
_converse.keycodes.ALT]
|
||||
, ev.keyCode)) {
|
||||
return;
|
||||
}
|
||||
if (this.match_on_tab && ev.keyCode === _converse.keycodes.TAB) {
|
||||
ev.preventDefault();
|
||||
this.auto_completing = true;
|
||||
}
|
||||
if (this.auto_completing) {
|
||||
this.evaluate();
|
||||
}
|
||||
},
|
||||
|
||||
evaluate (ev) {
|
||||
let value = this.input.value;
|
||||
if (this.match_current_word) {
|
||||
value = u.getCurrentWord(this.input);
|
||||
}
|
||||
|
||||
if (value.length >= this.min_chars && this._list.length > 0) {
|
||||
this.index = -1;
|
||||
// Populate list with options that match
|
||||
this.ul.innerHTML = "";
|
||||
|
||||
this.suggestions = this._list
|
||||
.map(item => new Suggestion(this.data(item, value)))
|
||||
.filter(item => this.filter(item, value));
|
||||
|
||||
if (this.sort !== false) {
|
||||
this.suggestions = this.suggestions.sort(this.sort);
|
||||
}
|
||||
this.suggestions = this.suggestions.slice(0, this.max_items);
|
||||
this.suggestions.forEach((text) => this.ul.appendChild(this.item(text, value)));
|
||||
|
||||
if (this.ul.children.length === 0) {
|
||||
this.close({'reason': 'nomatches'});
|
||||
} else {
|
||||
this.open();
|
||||
}
|
||||
} else {
|
||||
this.close({'reason': 'nomatches'});
|
||||
this.auto_completing = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Make it an event emitter
|
||||
_.extend(_ac.prototype, Backbone.Events);
|
||||
|
||||
// Static methods/properties
|
||||
_ac.all = [];
|
||||
|
||||
_ac.SORT_BYLENGTH = function (a, b) {
|
||||
if (a.length !== b.length) {
|
||||
return a.length - b.length;
|
||||
}
|
||||
|
||||
return a < b? -1 : 1;
|
||||
};
|
||||
|
||||
_ac.ITEM = function (text, input) {
|
||||
input = input.trim();
|
||||
var element = document.createElement("li");
|
||||
element.setAttribute("aria-selected", "false");
|
||||
|
||||
var regex = new RegExp("("+input+")", "ig");
|
||||
var parts = input ? text.split(regex) : [text];
|
||||
parts.forEach(function (txt) {
|
||||
if (input && txt.match(regex)) {
|
||||
var match = document.createElement("mark");
|
||||
match.textContent = txt;
|
||||
element.appendChild(match);
|
||||
} else {
|
||||
element.appendChild(document.createTextNode(txt));
|
||||
}
|
||||
});
|
||||
return element;
|
||||
};
|
||||
|
||||
_ac.REPLACE = function (text) {
|
||||
this.input.value = text.value;
|
||||
};
|
||||
|
||||
_ac.DATA = function (item/*, input*/) { return item; };
|
||||
|
||||
// Private functions
|
||||
|
||||
function Suggestion(data) {
|
||||
const o = Array.isArray(data)
|
||||
? { label: data[0], value: data[1] }
|
||||
: typeof data === "object" && "label" in data && "value" in data ? data : { label: data, value: data };
|
||||
|
||||
this.label = o.label || o.value;
|
||||
this.value = o.value;
|
||||
}
|
||||
|
||||
Object.defineProperty(Suggestion.prototype = Object.create(String.prototype), "length", {
|
||||
get: function() { return this.label.length; }
|
||||
});
|
||||
|
||||
Suggestion.prototype.toString = Suggestion.prototype.valueOf = function () {
|
||||
return "" + this.label;
|
||||
};
|
||||
|
||||
function configure (instance, properties, o) {
|
||||
for (var i in properties) {
|
||||
if (!Object.prototype.hasOwnProperty.call(properties, i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const initial = properties[i],
|
||||
attr_value = instance.input.getAttribute("data-" + i.toLowerCase());
|
||||
|
||||
if (typeof initial === "number") {
|
||||
instance[i] = parseInt(attr_value, 10);
|
||||
} else if (initial === false) { // Boolean options must be false by default anyway
|
||||
instance[i] = attr_value !== null;
|
||||
} else if (initial instanceof Function) {
|
||||
instance[i] = null;
|
||||
} else {
|
||||
instance[i] = attr_value;
|
||||
}
|
||||
|
||||
if (!instance[i] && instance[i] !== 0) {
|
||||
instance[i] = (i in o)? o[i] : initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers
|
||||
var slice = Array.prototype.slice;
|
||||
|
||||
function $(expr, con) {
|
||||
return typeof expr === "string"? (con || document).querySelector(expr) : expr || null;
|
||||
}
|
||||
|
||||
function $$(expr, con) {
|
||||
return slice.call((con || document).querySelectorAll(expr));
|
||||
}
|
||||
|
||||
$.bind = function(element, o) {
|
||||
if (element) {
|
||||
for (var event in o) {
|
||||
if (!Object.prototype.hasOwnProperty.call(o, event)) {
|
||||
continue;
|
||||
}
|
||||
const callback = o[event];
|
||||
event.split(/\s+/).forEach(event => element.addEventListener(event, callback));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$.unbind = function(element, o) {
|
||||
if (element) {
|
||||
for (var event in o) {
|
||||
if (!Object.prototype.hasOwnProperty.call(o, event)) {
|
||||
continue;
|
||||
}
|
||||
const callback = o[event];
|
||||
event.split(/\s+/).forEach(event => element.removeEventListener(event, callback));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$.fire = function(target, type, properties) {
|
||||
var evt = document.createEvent("HTMLEvents");
|
||||
|
||||
evt.initEvent(type, true, true );
|
||||
|
||||
for (var j in properties) {
|
||||
if (!Object.prototype.hasOwnProperty.call(properties, j)) {
|
||||
continue;
|
||||
}
|
||||
evt[j] = properties[j];
|
||||
}
|
||||
|
||||
return target.dispatchEvent(evt);
|
||||
};
|
||||
|
||||
$.regExpEscape = function (s) {
|
||||
return s.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&");
|
||||
};
|
||||
|
||||
_ac.$ = $;
|
||||
_ac.$$ = $$;
|
||||
|
||||
_converse.AutoComplete = _ac;
|
||||
}
|
||||
});
|
||||
}));
|
|
@ -50,18 +50,6 @@
|
|||
"use strict";
|
||||
const { $msg, Backbone, Promise, Strophe, _, b64_sha1, f, sizzle, moment } = converse.env;
|
||||
const u = converse.env.utils;
|
||||
const KEY = {
|
||||
ENTER: 13,
|
||||
SHIFT: 17,
|
||||
CTRL: 17,
|
||||
ALT: 18,
|
||||
ESCAPE: 27,
|
||||
UP_ARROW: 38,
|
||||
DOWN_ARROW: 40,
|
||||
FORWARD_SLASH: 47,
|
||||
META: 91,
|
||||
META_RIGHT: 93
|
||||
};
|
||||
|
||||
converse.plugins.add('converse-chatview', {
|
||||
/* Plugin dependencies are other plugins which might be
|
||||
|
@ -926,20 +914,26 @@
|
|||
return;
|
||||
}
|
||||
if (!ev.shiftKey && !ev.altKey) {
|
||||
if (ev.keyCode === KEY.FORWARD_SLASH) {
|
||||
if (ev.keyCode === _converse.keycodes.FORWARD_SLASH) {
|
||||
// Forward slash is used to run commands. Nothing to do here.
|
||||
return;
|
||||
} else if (ev.keyCode === KEY.ESCAPE) {
|
||||
} else if (ev.keyCode === _converse.keycodes.ESCAPE) {
|
||||
return this.onEscapePressed(ev);
|
||||
} else if (ev.keyCode === KEY.ENTER) {
|
||||
} else if (ev.keyCode === _converse.keycodes.ENTER) {
|
||||
return this.onFormSubmitted(ev);
|
||||
} else if (ev.keyCode === KEY.UP_ARROW && !ev.target.selectionEnd) {
|
||||
} else if (ev.keyCode === _converse.keycodes.UP_ARROW && !ev.target.selectionEnd) {
|
||||
return this.editEarlierMessage();
|
||||
} else if (ev.keyCode === KEY.DOWN_ARROW && ev.target.selectionEnd === ev.target.value.length) {
|
||||
} else if (ev.keyCode === _converse.keycodes.DOWN_ARROW && ev.target.selectionEnd === ev.target.value.length) {
|
||||
return this.editLaterMessage();
|
||||
}
|
||||
}
|
||||
if (_.includes([KEY.SHIFT, KEY.META, KEY.META_RIGHT, KEY.ESCAPE, KEY.ALT], ev.keyCode)) {
|
||||
if (_.includes([
|
||||
_converse.keycodes.SHIFT,
|
||||
_converse.keycodes.META,
|
||||
_converse.keycodes.META_RIGHT,
|
||||
_converse.keycodes.ESCAPE,
|
||||
_converse.keycodes.ALT]
|
||||
, ev.keyCode)) {
|
||||
return;
|
||||
}
|
||||
if (this.model.get('chat_state') !== _converse.COMPOSING) {
|
||||
|
|
|
@ -67,6 +67,7 @@
|
|||
|
||||
// Core plugins are whitelisted automatically
|
||||
_converse.core_plugins = [
|
||||
'converse-autocomplete',
|
||||
'converse-bookmarks',
|
||||
'converse-caps',
|
||||
'converse-chatboxes',
|
||||
|
@ -106,6 +107,21 @@
|
|||
// Make converse pluggable
|
||||
pluggable.enable(_converse, '_converse', 'pluggable');
|
||||
|
||||
_converse.keycodes = {
|
||||
TAB: 9,
|
||||
ENTER: 13,
|
||||
SHIFT: 16,
|
||||
CTRL: 17,
|
||||
ALT: 18,
|
||||
ESCAPE: 27,
|
||||
UP_ARROW: 38,
|
||||
DOWN_ARROW: 40,
|
||||
FORWARD_SLASH: 47,
|
||||
META: 91,
|
||||
META_RIGHT: 93
|
||||
};
|
||||
|
||||
|
||||
// Module-level constants
|
||||
_converse.STATUS_WEIGHTS = {
|
||||
'offline': 6,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Converse.js
|
||||
// http://conversejs.org
|
||||
//
|
||||
// Copyright (c) 2012-2018, the Converse.js developers
|
||||
// Copyright (c) 2013-2018, the Converse.js developers
|
||||
// Licensed under the Mozilla Public License (MPLv2)
|
||||
|
||||
(function (root, factory) {
|
||||
|
@ -93,7 +93,7 @@
|
|||
* If the setting "strict_plugin_dependencies" is set to true,
|
||||
* an error will be raised if the plugin is not found.
|
||||
*/
|
||||
dependencies: ["converse-modal", "converse-controlbox", "converse-chatview"],
|
||||
dependencies: ["converse-autocomplete", "converse-modal", "converse-controlbox", "converse-chatview"],
|
||||
|
||||
overrides: {
|
||||
|
||||
|
@ -584,6 +584,7 @@
|
|||
this.renderHeading();
|
||||
this.renderChatArea();
|
||||
this.renderMessageForm();
|
||||
this.initAutoComplete();
|
||||
if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED) {
|
||||
this.showSpinner();
|
||||
}
|
||||
|
@ -610,6 +611,23 @@
|
|||
return this;
|
||||
},
|
||||
|
||||
initAutoComplete () {
|
||||
this.auto_complete = new _converse.AutoComplete(this.el, {
|
||||
'auto_evaluate': false,
|
||||
'min_chars': 1,
|
||||
'match_current_word': true,
|
||||
'match_on_tab': true,
|
||||
'list': this.model.occupants.map(o => ({'label': o.getDisplayName(), 'value': o.get('jid')})),
|
||||
'filter': _converse.FILTER_STARTSWITH
|
||||
});
|
||||
this.auto_complete.on('suggestion-box-selectcomplete', () => (this.auto_completing = false));
|
||||
},
|
||||
|
||||
keyPressed (ev) {
|
||||
this.auto_complete.keyPressed(ev);
|
||||
return _converse.ChatBoxView.prototype.keyPressed.apply(this, arguments);
|
||||
},
|
||||
|
||||
showRoomDetailsModal (ev) {
|
||||
ev.preventDefault();
|
||||
if (_.isUndefined(this.model.room_details_modal)) {
|
||||
|
@ -834,8 +852,7 @@
|
|||
},
|
||||
|
||||
parseMessageForCommands (text) {
|
||||
const _super_ = _converse.ChatBoxView.prototype;
|
||||
if (_super_.parseMessageForCommands.apply(this, arguments)) {
|
||||
if (_converse.ChatBoxView.prototype.parseMessageForCommands.apply(this, arguments)) {
|
||||
return true;
|
||||
}
|
||||
if (_converse.muc_disable_moderator_commands) {
|
||||
|
|
|
@ -7,6 +7,7 @@ if (typeof define !== 'undefined') {
|
|||
* --------------------
|
||||
* Any of the following components may be removed if they're not needed.
|
||||
*/
|
||||
"converse-autocomplete",
|
||||
"converse-bookmarks", // XEP-0048 Bookmarks
|
||||
"converse-caps", // XEP-0115 Entity Capabilities
|
||||
"converse-chatview", // Renders standalone chat boxes for single user chat
|
||||
|
|
|
@ -6,14 +6,22 @@
|
|||
{[ } ]}
|
||||
<input type="text" placeholder="{{o.label_spoiler_hint}}" value="{{ o.hint_value }}"
|
||||
class="{[ if (!o.composing_spoiler) { ]} hidden {[ } ]} spoiler-hint"/>
|
||||
<textarea
|
||||
type="text"
|
||||
class="chat-textarea
|
||||
{[ if (o.show_send_button) { ]} chat-textarea-send-button {[ } ]}
|
||||
{[ if (o.composing_spoiler) { ]} spoiler {[ } ]}"
|
||||
placeholder="{{{o.label_personal_message}}}">{{ o.message_value }}</textarea>
|
||||
{[ if (o.show_send_button) { ]}
|
||||
<button type="submit" class="pure-button send-button">{{{ o.label_send }}}</button>
|
||||
{[ } ]}
|
||||
|
||||
<div class="suggestion-box">
|
||||
<ul class="suggestion-box__results suggestion-box__results--above" hidden></ul>
|
||||
<textarea
|
||||
type="text"
|
||||
class="chat-textarea suggestion-box__input
|
||||
{[ if (o.show_send_button) { ]} chat-textarea-send-button {[ } ]}
|
||||
{[ if (o.composing_spoiler) { ]} spoiler {[ } ]}"
|
||||
placeholder="{{{o.label_personal_message}}}">{{ o.message_value }}</textarea>
|
||||
<span class="suggestion-box__additions visually-hidden" role="status" aria-live="assertive" aria-relevant="additions"></span>
|
||||
|
||||
|
||||
|
||||
{[ if (o.show_send_button) { ]}
|
||||
<button type="submit" class="pure-button send-button">{{{ o.label_send }}}</button>
|
||||
{[ } ]}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -808,7 +808,18 @@
|
|||
} else {
|
||||
model.set(attributes);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
u.siblingIndex = function (el) {
|
||||
/* eslint-disable no-cond-assign */
|
||||
for (var i = 0; el = el.previousElementSibling; i++);
|
||||
return i;
|
||||
};
|
||||
|
||||
u.getCurrentWord = function (input) {
|
||||
const cursor = input.selectionEnd || undefined;
|
||||
return _.last(input.value.slice(0, cursor).split(' '));
|
||||
};
|
||||
|
||||
u.isVisible = function (el) {
|
||||
if (u.hasClass('hidden', el)) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user