Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
converse.js
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
converse.js
Commits
b6f4f05b
Commit
b6f4f05b
authored
Aug 10, 2018
by
JC Brand
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add converse-autocomplete and use that in the chat textarea
parent
f0ad326e
Changes
13
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
1206 additions
and
91 deletions
+1206
-91
.eslintrc.json
.eslintrc.json
+2
-5
css/converse.css
css/converse.css
+60
-13
dist/converse.js
dist/converse.js
+555
-34
sass/_awesomplete.scss
sass/_awesomplete.scss
+40
-6
sass/_chatbox.scss
sass/_chatbox.scss
+5
-0
sass/_chatrooms.scss
sass/_chatrooms.scss
+0
-1
src/converse-autocomplete.js
src/converse-autocomplete.js
+465
-0
src/converse-chatview.js
src/converse-chatview.js
+12
-18
src/converse-core.js
src/converse-core.js
+16
-0
src/converse-muc-views.js
src/converse-muc-views.js
+21
-4
src/converse.js
src/converse.js
+1
-0
src/templates/chatbox_message_form.html
src/templates/chatbox_message_form.html
+17
-9
src/utils/core.js
src/utils/core.js
+12
-1
No files found.
.eslintrc.json
View file @
b6f4f05b
...
...
@@ -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"
,
...
...
css/converse.css
View file @
b6f4f05b
...
...
@@ -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
;
}
...
...
dist/converse.js
View file @
b6f4f05b
...
...
@@ -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) 201
2
-2018, the Converse.js developers
// Copyright (c) 201
3
-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;
sass/_awesomplete.scss
View file @
b6f4f05b
...
...
@@ -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
;
...
...
sass/_chatbox.scss
View file @
b6f4f05b
...
...
@@ -605,6 +605,11 @@
ul
{
width
:
100%
;
}
.suggestion-box__results
{
&
:after
{
display
:
none
;
}
}
.toggle-smiley
{
ul
{
&
.emoji-toolbar
{
...
...
sass/_chatrooms.scss
View file @
b6f4f05b
...
...
@@ -271,7 +271,6 @@
}
.chat-textarea
{
border-bottom-right-radius
:
0
;
resize
:
none
;
&
.correcting
{
background-color
:
lighten
(
$chatroom-head-color
,
30%
);
}
...
...
src/converse-autocomplete.js
0 → 100644
View file @
b6f4f05b
// 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
;
}
});
}));
src/converse-chatview.js
View file @
b6f4f05b
...
...
@@ -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
)
{
...
...
src/converse-core.js
View file @
b6f4f05b
...
...
@@ -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
,
...
...
src/converse-muc-views.js
View file @
b6f4f05b
// Converse.js
// http://conversejs.org
//
// Copyright (c) 201
2
-2018, the Converse.js developers
// Copyright (c) 201
3
-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
)
{
...
...
src/converse.js
View file @
b6f4f05b
...
...
@@ -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
...
...
src/templates/chatbox_message_form.html
View file @
b6f4f05b
...
...
@@ -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>
src/utils/core.js
View file @
b6f4f05b
...
...
@@ -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
))
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment