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
75ae76ad
Commit
75ae76ad
authored
Mar 28, 2019
by
JC Brand
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Use `_converse.AutoComplete` in "Add Contact" modal
parent
f0848c28
Changes
8
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
201 additions
and
187 deletions
+201
-187
css/converse.css
css/converse.css
+4
-4
dist/converse.js
dist/converse.js
+104
-103
sass/_awesomplete.scss
sass/_awesomplete.scss
+6
-5
spec/controlbox.js
spec/controlbox.js
+13
-15
src/converse-autocomplete.js
src/converse-autocomplete.js
+32
-28
src/converse-headline.js
src/converse-headline.js
+1
-1
src/converse-rosterview.js
src/converse-rosterview.js
+24
-24
src/templates/add_contact_modal.html
src/templates/add_contact_modal.html
+17
-7
No files found.
css/converse.css
View file @
75ae76ad
...
...
@@ -12198,14 +12198,14 @@ body.converse-fullscreen {
display
:
none
;
}
#conversejs
.suggestion-box
.suggestion-box__results--above
:after
,
#conversejs
.awesomplete
.suggestion-box__results--above
:after
{
z-index
:
1
;
z-index
:
-
1
;
content
:
""
;
position
:
absolute
;
bottom
:
-.43em
;
bottom
:
-
0
.43em
;
left
:
1em
;
width
:
0
;
height
:
0
;
padding
:
.4em
;
padding
:
0
.4em
;
background
:
white
;
border
:
inherit
;
border-left
:
0
;
...
...
@@ -12252,7 +12252,7 @@ body.converse-fullscreen {
bottom
:
4.5em
;
}
#conversejs
.converse-overlayed
.suggestion-box__results--above
{
bottom
:
5
.5em
;
}
bottom
:
3
.5em
;
}
#conversejs
.converse-embedded
{
-webkit-box-sizing
:
border-box
;
...
...
dist/converse.js
View file @
75ae76ad
This diff is collapsed.
Click to expand it.
sass/_awesomplete.scss
View file @
75ae76ad
...
...
@@ -71,13 +71,14 @@
display
:
none
;
}
&
:after
{
z-index
:
1
;
z-index
:
-
1
;
content
:
""
;
position
:
absolute
;
bottom
:
-.43em
;
bottom
:
-
0
.43em
;
left
:
1em
;
width
:
0
;
height
:
0
;
padding
:
.4em
;
width
:
0
;
height
:
0
;
padding
:
0
.4em
;
background
:
white
;
border
:
inherit
;
border-left
:
0
;
...
...
@@ -140,6 +141,6 @@
#conversejs
.converse-overlayed
{
.suggestion-box__results--above
{
bottom
:
5
.5em
;
bottom
:
3
.5em
;
}
}
spec/controlbox.js
View file @
75ae76ad
...
...
@@ -207,7 +207,7 @@
input_jid
.
value
=
'
someone@
'
;
const
evt
=
new
Event
(
'
input
'
);
input_jid
.
dispatchEvent
(
evt
);
expect
(
modal
.
el
.
querySelector
(
'
.
awesomplete
li
'
).
textContent
).
toBe
(
'
someone@localhost
'
);
expect
(
modal
.
el
.
querySelector
(
'
.
suggestion-box
li
'
).
textContent
).
toBe
(
'
someone@localhost
'
);
input_jid
.
value
=
'
someone@localhost
'
;
input_name
.
value
=
'
Someone
'
;
modal
.
el
.
querySelector
(
'
button[type="submit"]
'
).
click
();
...
...
@@ -246,30 +246,28 @@
cbview
.
el
.
querySelector
(
'
.add-contact
'
).
click
()
const
modal
=
_converse
.
rosterview
.
add_contact_modal
;
await
test_utils
.
waitUntil
(()
=>
u
.
isVisible
(
modal
.
el
),
1000
);
// We only have autocomplete for the name input
expect
(
modal
.
jid_auto_complete
).
toBe
(
undefined
);
expect
(
modal
.
name_auto_complete
instanceof
_converse
.
AutoComplete
).
toBe
(
true
);
const
input_el
=
modal
.
el
.
querySelector
(
'
input[name="name"]
'
);
input_el
.
value
=
'
marty
'
;
let
evt
=
new
Event
(
'
input
'
);
input_el
.
dispatchEvent
(
evt
);
await
test_utils
.
waitUntil
(()
=>
modal
.
el
.
querySelector
(
'
.awesomplete li
'
),
1000
);
input_el
.
dispatchEvent
(
new
Event
(
'
input
'
));
await
test_utils
.
waitUntil
(()
=>
modal
.
el
.
querySelector
(
'
.suggestion-box li
'
),
1000
);
const
sendIQ
=
_converse
.
connection
.
sendIQ
;
let
sent_stanza
,
IQ_id
;
spyOn
(
_converse
.
connection
,
'
sendIQ
'
).
and
.
callFake
(
function
(
iq
,
callback
,
errback
)
{
sent_stanza
=
iq
;
IQ_id
=
sendIQ
.
bind
(
this
)(
iq
,
callback
,
errback
);
});
expect
(
modal
.
el
.
querySelectorAll
(
'
.
awesomplete
li
'
).
length
).
toBe
(
1
);
const
suggestion
=
modal
.
el
.
querySelector
(
'
.
awesomplete
li
'
);
expect
(
modal
.
el
.
querySelectorAll
(
'
.
suggestion-box
li
'
).
length
).
toBe
(
1
);
const
suggestion
=
modal
.
el
.
querySelector
(
'
.
suggestion-box
li
'
);
expect
(
suggestion
.
textContent
).
toBe
(
'
Marty McFly
'
);
// Can't trigger "mousedown" event so trigger the Awesomplete
// custom event which would have been triggered upon mousedown.
evt
=
document
.
createEvent
(
"
HTMLEvents
"
);
evt
.
initEvent
(
'
awesomplete-selectcomplete
'
,
true
,
true
);
evt
.
text
=
{
'
label
'
:
'
Marty McFly
'
,
'
value
'
:
'
marty@mcfly.net
'
}
modal
.
el
.
dispatchEvent
(
evt
);
// Mock selection
modal
.
name_auto_complete
.
select
(
suggestion
);
expect
(
input_el
.
value
).
toBe
(
'
Marty McFly
'
);
expect
(
modal
.
el
.
querySelector
(
'
input[name="jid"]
'
).
value
).
toBe
(
'
marty@mcfly.net
'
);
modal
.
el
.
querySelector
(
'
button[type="submit"]
'
).
click
();
...
...
src/converse-autocomplete.js
View file @
75ae76ad
...
...
@@ -53,6 +53,32 @@ converse.plugins.add("converse-autocomplete", {
};
class
Suggestion
extends
String
{
constructor
(
data
)
{
super
();
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
;
}
get
lenth
()
{
return
this
.
label
.
length
;
}
toString
()
{
return
""
+
this
.
label
;
}
valueOf
()
{
return
this
.
toString
();
}
}
class
AutoComplete
{
constructor
(
el
,
config
=
{})
{
...
...
@@ -76,7 +102,7 @@ converse.plugins.add("converse-autocomplete", {
'
include_triggers
'
:
[],
// Array of trigger keys which should be included in the returned value
'
min_chars
'
:
2
,
'
max_items
'
:
10
,
'
auto_evaluate
'
:
true
,
'
auto_evaluate
'
:
true
,
// Should evaluation happen automatically without any particular key as trigger?
'
auto_first
'
:
false
,
// Should the first element be automatically selected?
'
data
'
:
_
.
identity
,
'
filter
'
:
_converse
.
FILTER_CONTAINS
,
...
...
@@ -129,7 +155,7 @@ converse.plugins.add("converse-autocomplete", {
list
=
helpers
.
getElement
(
list
);
if
(
list
&&
list
.
children
)
{
const
items
=
[];
slice
.
apply
(
list
.
children
).
forEach
(
function
(
el
)
{
Array
.
prototype
.
slice
.
apply
(
list
.
children
).
forEach
(
function
(
el
)
{
if
(
!
el
.
disabled
)
{
const
text
=
el
.
textContent
.
trim
(),
value
=
el
.
value
||
text
,
...
...
@@ -230,7 +256,7 @@ converse.plugins.add("converse-autocomplete", {
}
}
select
(
selected
,
origin
)
{
select
(
selected
)
{
if
(
selected
)
{
this
.
index
=
u
.
siblingIndex
(
selected
);
}
else
{
...
...
@@ -305,11 +331,11 @@ converse.plugins.add("converse-autocomplete", {
}
evaluate
(
ev
)
{
const
arrow_pressed
=
(
const
selecting
=
this
.
selected
&&
ev
&&
(
ev
.
keyCode
===
_converse
.
keycodes
.
UP_ARROW
||
ev
.
keyCode
===
_converse
.
keycodes
.
DOWN_ARROW
);
if
(
!
this
.
auto_
completing
||
(
this
.
selected
&&
arrow_pressed
)
)
{
if
(
!
this
.
auto_
evaluate
&&
!
this
.
auto_completing
||
selecting
)
{
return
;
}
...
...
@@ -339,7 +365,7 @@ converse.plugins.add("converse-autocomplete", {
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
)));
this
.
suggestions
.
forEach
(
text
=>
this
.
ul
.
appendChild
(
this
.
item
(
text
,
value
)));
if
(
this
.
ul
.
children
.
length
===
0
)
{
this
.
close
({
'
reason
'
:
'
nomatches
'
});
...
...
@@ -357,28 +383,6 @@ converse.plugins.add("converse-autocomplete", {
_
.
extend
(
AutoComplete
.
prototype
,
Backbone
.
Events
);
// 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
;
};
// Helpers
var
slice
=
Array
.
prototype
.
slice
;
const
helpers
=
{
getElement
(
expr
,
el
)
{
...
...
src/converse-headline.js
View file @
75ae76ad
// Converse.js (A browser based XMPP chat client)
// https://conversejs.org
//
// Copyright (c) 201
2-2017
, Jan-Carel Brand <jc@opkode.com>
// Copyright (c) 201
9
, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
import
"
converse-chatview
"
;
...
...
src/converse-rosterview.js
View file @
75ae76ad
...
...
@@ -7,7 +7,6 @@
import
"
@converse/headless/converse-roster
"
;
import
"
@converse/headless/converse-chatboxes
"
;
import
"
converse-modal
"
;
import
Awesomplete
from
"
awesomplete
"
;
import
_FormData
from
"
formdata-polyfill
"
;
import
converse
from
"
@converse/headless/converse-core
"
;
import
tpl_add_contact_modal
from
"
templates/add_contact_modal.html
"
;
...
...
@@ -119,7 +118,7 @@ converse.plugins.add('converse-rosterview', {
toHTML
()
{
const
label_nickname
=
_converse
.
xhr_user_search_url
?
__
(
'
Contact name
'
)
:
__
(
'
Optional nickname
'
);
return
tpl_add_contact_modal
(
_
.
extend
(
this
.
model
.
toJSON
(),
{
return
tpl_add_contact_modal
(
_
.
extend
(
this
.
model
.
toJSON
(),
{
'
_converse
'
:
_converse
,
'
heading_new_contact
'
:
__
(
'
Add a Contact
'
),
'
label_xmpp_address
'
:
__
(
'
XMPP Address
'
),
...
...
@@ -132,47 +131,47 @@ converse.plugins.add('converse-rosterview', {
afterRender
()
{
if
(
_converse
.
xhr_user_search_url
&&
_
.
isString
(
_converse
.
xhr_user_search_url
))
{
this
.
initXHRAutoComplete
(
this
.
el
);
this
.
el
.
addEventListener
(
'
awesomplete
-selectcomplete
'
,
ev
=>
{
this
.
initXHRAutoComplete
();
this
.
name_auto_complete
.
on
(
'
suggestion-box
-selectcomplete
'
,
ev
=>
{
this
.
el
.
querySelector
(
'
input[name="name"]
'
).
value
=
ev
.
text
.
label
;
this
.
el
.
querySelector
(
'
input[name="jid"]
'
).
value
=
ev
.
text
.
value
;
});
}
else
{
this
.
initJIDAutoComplete
(
this
.
el
);
this
.
initJIDAutoComplete
();
}
const
jid_input
=
this
.
el
.
querySelector
(
'
input[name="jid"]
'
);
this
.
el
.
addEventListener
(
'
shown.bs.modal
'
,
()
=>
jid_input
.
focus
(),
false
);
},
initJIDAutoComplete
(
root
)
{
const
jid_input
=
root
.
querySelector
(
'
input[name="jid"]
'
);
const
list
=
_
.
uniq
(
_converse
.
roster
.
map
((
item
)
=>
Strophe
.
getDomainFromJid
(
item
.
get
(
'
jid
'
))));
new
Awesomplete
(
jid_input
,
{
'
list
'
:
list
,
initJIDAutoComplete
()
{
const
el
=
this
.
el
.
querySelector
(
'
.suggestion-box__jid
'
).
parentElement
;
this
.
jid_auto_complete
=
new
_converse
.
AutoComplete
(
el
,
{
'
data
'
:
(
text
,
input
)
=>
`
${
input
.
slice
(
0
,
input
.
indexOf
(
"
@
"
))}
@
${
text
}
`
,
'
filter
'
:
Awesomplete
.
FILTER_STARTSWITH
'
filter
'
:
_converse
.
FILTER_STARTSWITH
,
'
list
'
:
_
.
uniq
(
_converse
.
roster
.
map
(
item
=>
Strophe
.
getDomainFromJid
(
item
.
get
(
'
jid
'
))))
});
},
initXHRAutoComplete
(
root
)
{
const
name_input
=
this
.
el
.
querySelector
(
'
input[name="name"]
'
)
;
const
jid_input
=
this
.
el
.
querySelector
(
'
input[name="jid"]
'
);
const
awesomplete
=
new
Awesomplete
(
name_input
,
{
'
minChars
'
:
1
,
initXHRAutoComplete
()
{
const
el
=
this
.
el
.
querySelector
(
'
.suggestion-box__name
'
).
parentElement
;
this
.
name_auto_complete
=
new
_converse
.
AutoComplete
(
el
,
{
'
auto_evaluate
'
:
false
,
'
filter
'
:
_converse
.
FILTER_STARTSWITH
,
'
list
'
:
[]
});
const
xhr
=
new
window
.
XMLHttpRequest
();
// `open` must be called after `onload` for mock/testing purposes.
xhr
.
onload
=
function
()
{
xhr
.
onload
=
()
=>
{
if
(
xhr
.
responseText
)
{
awesomplete
.
list
=
JSON
.
parse
(
xhr
.
responseText
).
map
((
i
)
=>
{
//eslint-disable-line arrow-body-style
return
{
'
label
'
:
i
.
fullname
||
i
.
jid
,
'
value
'
:
i
.
jid
}
;
})
;
awes
omplete
.
evaluate
();
const
r
=
xhr
.
responseText
;
this
.
name_auto_complete
.
list
=
JSON
.
parse
(
r
).
map
(
i
=>
({
'
label
'
:
i
.
fullname
||
i
.
jid
,
'
value
'
:
i
.
jid
}))
;
this
.
name_auto_complete
.
auto_completing
=
true
;
this
.
name_auto_c
omplete
.
evaluate
();
}
};
name_input
.
addEventListener
(
'
input
'
,
_
.
debounce
(()
=>
{
xhr
.
open
(
"
GET
"
,
`
${
_converse
.
xhr_user_search_url
}
q=
${
name_input
.
value
}
`
,
true
);
const
input_el
=
this
.
el
.
querySelector
(
'
input[name="name"]
'
);
input_el
.
addEventListener
(
'
input
'
,
_
.
debounce
(()
=>
{
xhr
.
open
(
"
GET
"
,
`
${
_converse
.
xhr_user_search_url
}
q=
${
input_el
.
value
}
`
,
true
);
xhr
.
send
()
}
,
300
));
},
...
...
@@ -183,9 +182,10 @@ converse.plugins.add('converse-rosterview', {
jid
=
data
.
get
(
'
jid
'
),
name
=
data
.
get
(
'
name
'
);
if
(
!
jid
||
_
.
compact
(
jid
.
split
(
'
@
'
)).
length
<
2
)
{
// XXX: we have to do this manually, instead of via
// XXX: we
used to
have to do this manually, instead of via
// toHTML because Awesomplete messes things up and
// confuses Snabbdom
// We now use _converse.AutoComplete, can this be removed?
u
.
addClass
(
'
is-invalid
'
,
this
.
el
.
querySelector
(
'
input[name="jid"]
'
));
u
.
addClass
(
'
d-block
'
,
this
.
el
.
querySelector
(
'
.invalid-feedback
'
));
}
else
{
...
...
src/templates/add_contact_modal.html
View file @
75ae76ad
...
...
@@ -10,16 +10,26 @@
<div
class=
"modal-body"
>
<div
class=
"form-group {[ if (o._converse.xhr_user_search_url) { ]} hidden {[ } ]}"
>
<label
class=
"clearfix"
for=
"jid"
>
{{{o.label_xmpp_address}}}:
</label>
<input
type=
"text"
name=
"jid"
required=
"required"
value=
"{{{o.jid}}}"
class=
"form-control"
placeholder=
"{{{o.contact_placeholder}}}"
/>
<div
class=
"invalid-feedback"
>
{{{o.error_message}}}
</div>
<div
class=
"suggestion-box suggestion-box__jid"
>
<ul
class=
"suggestion-box__results suggestion-box__results--above"
hidden=
""
></ul>
<input
type=
"text"
name=
"jid"
required=
"required"
value=
"{{{o.jid}}}"
class=
"form-control suggestion-box__input"
placeholder=
"{{{o.contact_placeholder}}}"
/>
<div
class=
"invalid-feedback"
>
{{{o.error_message}}}
</div>
<span
class=
"suggestion-box__additions visually-hidden"
role=
"status"
aria-live=
"assertive"
aria-relevant=
"additions"
></span>
</div>
</div>
<div
class=
"form-group"
>
<label
class=
"clearfix"
for=
"name"
>
{{{o.label_nickname}}}:
</label>
<input
type=
"text"
name=
"name"
value=
"{{{o.nickname}}}"
class=
"form-control"
placeholder=
"{{{o.nickname_placeholder}}}"
/>
<div
class=
"suggestion-box suggestion-box__name"
>
<ul
class=
"suggestion-box__results suggestion-box__results--above"
hidden=
""
></ul>
<input
type=
"text"
name=
"name"
value=
"{{{o.nickname}}}"
class=
"form-control suggestion-box__input"
placeholder=
"{{{o.nickname_placeholder}}}"
/>
<div
class=
"invalid-feedback"
>
{{{o.error_message}}}
</div>
<span
class=
"suggestion-box__additions visually-hidden"
role=
"status"
aria-live=
"assertive"
aria-relevant=
"additions"
></span>
</div>
</div>
<button
type=
"submit"
class=
"btn btn-primary"
>
{{{o.label_add}}}
</button>
</div>
...
...
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