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
26d3455f
Commit
26d3455f
authored
Jan 16, 2018
by
Weblate
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'origin/master'
parents
dcf87368
2cb4a36a
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
178 additions
and
149 deletions
+178
-149
CHANGES.md
CHANGES.md
+1
-0
src/converse-chatview.js
src/converse-chatview.js
+35
-43
src/converse-mam.js
src/converse-mam.js
+98
-94
src/utils.js
src/utils.js
+44
-12
No files found.
CHANGES.md
View file @
26d3455f
...
...
@@ -47,6 +47,7 @@
-
`hide_open_bookmarks`
is now by default
`true`
.
### UX/UI changes
-
#984 Improve loading of archived messages via "infinite scroll"
-
Use CSS3 fade transitions to render various elements.
-
Remove
`Login`
and
`Registration`
tabs and consolidate into one panel.
-
Show validation error messages on the login form.
...
...
src/converse-chatview.js
View file @
26d3455f
...
...
@@ -404,7 +404,7 @@
* This element must have a "data-isodate" attribute
* which specifies its creation date.
*/
const
prev_msg_el
=
this
.
getPreviousMessageElement
(
next_msg_el
),
const
prev_msg_el
=
u
.
getPreviousElement
(
next_msg_el
,
"
.message:not(.chat-event)
"
),
prev_msg_date
=
_
.
isNull
(
prev_msg_el
)
?
null
:
prev_msg_el
.
getAttribute
(
'
data-isodate
'
),
next_msg_date
=
next_msg_el
.
getAttribute
(
'
data-isodate
'
);
...
...
@@ -419,34 +419,6 @@
}
},
isNotPermanentMessage
(
el
)
{
return
!
_
.
isNull
(
el
)
&&
(
u
.
hasClass
(
'
chat-event
'
,
el
)
||
!
u
.
hasClass
(
'
message
'
,
el
));
},
getPreviousMessageElement
(
el
)
{
let
prev_msg_el
=
el
.
previousSibling
;
while
(
this
.
isNotPermanentMessage
(
prev_msg_el
))
{
prev_msg_el
=
prev_msg_el
.
previousSibling
}
return
prev_msg_el
;
},
getLastMessageElement
()
{
let
last_msg_el
=
this
.
content
.
lastElementChild
;
while
(
this
.
isNotPermanentMessage
(
last_msg_el
))
{
last_msg_el
=
last_msg_el
.
previousSibling
}
return
last_msg_el
;
},
getFirstMessageElement
()
{
let
first_msg_el
=
this
.
content
.
firstElementChild
;
while
(
this
.
isNotPermanentMessage
(
first_msg_el
))
{
first_msg_el
=
first_msg_el
.
nextSibling
}
return
first_msg_el
;
},
getLastMessageDate
(
cutoff
)
{
/* Return the ISO8601 format date of the latest message.
*
...
...
@@ -454,12 +426,12 @@
* (Object) cutoff: Moment Date cutoff date. The last
* message received cutoff this date will be returned.
*/
const
first_msg
=
this
.
getFirstMessageElement
(
),
const
first_msg
=
u
.
getFirstChildElement
(
this
.
content
,
'
.message:not(.chat-event)
'
),
oldest_date
=
first_msg
?
first_msg
.
getAttribute
(
'
data-isodate
'
)
:
null
;
if
(
!
_
.
isNull
(
oldest_date
)
&&
moment
(
oldest_date
).
isAfter
(
cutoff
))
{
return
null
;
}
const
last_msg
=
this
.
getLastMessageElement
(
),
const
last_msg
=
u
.
getLastChildElement
(
this
.
content
,
'
.message:not(.chat-event)
'
),
most_recent_date
=
last_msg
?
last_msg
.
getAttribute
(
'
data-isodate
'
)
:
null
;
if
(
_
.
isNull
(
most_recent_date
)
||
moment
(
most_recent_date
).
isBefore
(
cutoff
))
{
return
most_recent_date
;
...
...
@@ -511,7 +483,30 @@
}
this
.
insertDayIndicator
(
message_el
);
this
.
clearStatusNotification
();
this
.
scrollDown
();
this
.
setScrollPosition
(
message_el
);
},
setScrollPosition
(
message_el
)
{
/* Given a newly inserted message, determine whether we
* should keep the scrollbar in place (so as to not scroll
* up when using infinite scroll).
*/
if
(
this
.
model
.
get
(
'
scrolled
'
))
{
const
next_msg_el
=
u
.
getNextElement
(
message_el
,
"
.chat-message
"
);
if
(
next_msg_el
)
{
// The currently received message is not new, there
// are newer messages after it. So let's see if we
// should maintain our current scroll position.
if
(
this
.
content
.
scrollTop
===
0
||
this
.
model
.
get
(
'
top_visible_message
'
))
{
const
top_visible_message
=
this
.
model
.
get
(
'
top_visible_message
'
)
||
next_msg_el
;
this
.
model
.
set
(
'
top_visible_message
'
,
top_visible_message
);
this
.
content
.
scrollTop
=
top_visible_message
.
offsetTop
-
30
;
}
}
}
else
{
this
.
scrollDown
();
}
},
getExtraMessageTemplateAttributes
()
{
...
...
@@ -1027,13 +1022,6 @@
* received.
*/
if
(
ev
&&
ev
.
preventDefault
)
{
ev
.
preventDefault
();
}
if
(
this
.
model
.
get
(
'
auto_scrolled
'
))
{
this
.
model
.
set
({
'
scrolled
'
:
false
,
'
auto_scrolled
'
:
false
});
return
;
}
let
scrolled
=
true
;
const
is_at_bottom
=
(
this
.
content
.
scrollTop
+
this
.
content
.
clientHeight
)
>=
...
...
@@ -1043,11 +1031,17 @@
scrolled
=
false
;
this
.
onScrolledDown
();
}
u
.
safeSave
(
this
.
model
,
{
'
scrolled
'
:
scrolled
});
u
.
safeSave
(
this
.
model
,
{
'
scrolled
'
:
scrolled
,
'
top_visible_message
'
:
null
});
},
viewUnreadMessages
()
{
this
.
model
.
save
(
'
scrolled
'
,
false
);
this
.
model
.
save
({
'
scrolled
'
:
false
,
'
top_visible_message
'
:
null
});
this
.
scrollDown
();
},
...
...
@@ -1058,8 +1052,6 @@
}
if
(
u
.
isVisible
(
this
.
content
)
&&
!
this
.
model
.
get
(
'
scrolled
'
))
{
this
.
content
.
scrollTop
=
this
.
content
.
scrollHeight
;
this
.
onScrolledDown
();
this
.
model
.
save
({
'
auto_scrolled
'
:
true
});
}
},
...
...
src/converse-mam.js
View file @
26d3455f
...
...
@@ -36,6 +36,83 @@
}
}
function
queryForArchivedMessages
(
_converse
,
options
,
callback
,
errback
)
{
/* Internal function, called by the "archive.query" API method.
*/
let
date
;
if
(
_
.
isFunction
(
options
))
{
callback
=
options
;
errback
=
callback
;
}
const
queryid
=
_converse
.
connection
.
getUniqueId
();
const
attrs
=
{
'
type
'
:
'
set
'
};
if
(
!
_
.
isUndefined
(
options
)
&&
options
.
groupchat
)
{
if
(
!
options
[
'
with
'
])
{
// eslint-disable-line dot-notation
throw
new
Error
(
'
You need to specify a "with" value containing
'
+
'
the chat room JID, when querying groupchat messages.
'
);
}
attrs
.
to
=
options
[
'
with
'
];
// eslint-disable-line dot-notation
}
const
stanza
=
$iq
(
attrs
).
c
(
'
query
'
,
{
'
xmlns
'
:
Strophe
.
NS
.
MAM
,
'
queryid
'
:
queryid
});
if
(
!
_
.
isUndefined
(
options
))
{
stanza
.
c
(
'
x
'
,
{
'
xmlns
'
:
Strophe
.
NS
.
XFORM
,
'
type
'
:
'
submit
'
})
.
c
(
'
field
'
,
{
'
var
'
:
'
FORM_TYPE
'
,
'
type
'
:
'
hidden
'
})
.
c
(
'
value
'
).
t
(
Strophe
.
NS
.
MAM
).
up
().
up
();
if
(
options
[
'
with
'
]
&&
!
options
.
groupchat
)
{
// eslint-disable-line dot-notation
stanza
.
c
(
'
field
'
,
{
'
var
'
:
'
with
'
}).
c
(
'
value
'
)
.
t
(
options
[
'
with
'
]).
up
().
up
();
// eslint-disable-line dot-notation
}
_
.
each
([
'
start
'
,
'
end
'
],
function
(
t
)
{
if
(
options
[
t
])
{
date
=
moment
(
options
[
t
]);
if
(
date
.
isValid
())
{
stanza
.
c
(
'
field
'
,
{
'
var
'
:
t
}).
c
(
'
value
'
).
t
(
date
.
format
()).
up
().
up
();
}
else
{
throw
new
TypeError
(
`archive.query: invalid date provided for:
${
t
}
`
);
}
}
});
stanza
.
up
();
if
(
options
instanceof
Strophe
.
RSM
)
{
stanza
.
cnode
(
options
.
toXML
());
}
else
if
(
_
.
intersection
(
RSM_ATTRIBUTES
,
_
.
keys
(
options
)).
length
)
{
stanza
.
cnode
(
new
Strophe
.
RSM
(
options
).
toXML
());
}
}
const
messages
=
[];
const
message_handler
=
_converse
.
connection
.
addHandler
(
function
(
message
)
{
const
result
=
message
.
querySelector
(
'
result
'
);
if
(
!
_
.
isNull
(
result
)
&&
result
.
getAttribute
(
'
queryid
'
)
===
queryid
)
{
messages
.
push
(
message
);
}
return
true
;
},
Strophe
.
NS
.
MAM
);
_converse
.
connection
.
sendIQ
(
stanza
,
function
(
iq
)
{
_converse
.
connection
.
deleteHandler
(
message_handler
);
if
(
_
.
isFunction
(
callback
))
{
const
set
=
iq
.
querySelector
(
'
set
'
);
let
rsm
;
if
(
!
_
.
isUndefined
(
set
))
{
rsm
=
new
Strophe
.
RSM
({
xml
:
set
});
_
.
extend
(
rsm
,
_
.
pick
(
options
,
_
.
concat
(
MAM_ATTRIBUTES
,
[
'
max
'
])));
}
callback
(
messages
,
rsm
);
}
},
function
()
{
_converse
.
connection
.
deleteHandler
(
message_handler
);
if
(
_
.
isFunction
(
errback
))
{
errback
.
apply
(
this
,
arguments
);
}
},
_converse
.
message_archiving_timeout
);
}
converse
.
plugins
.
add
(
'
converse-mam
'
,
{
...
...
@@ -150,7 +227,7 @@
return
;
}
this
.
addSpinner
();
_converse
.
queryForArchivedMessages
(
_converse
.
api
.
archive
.
query
(
_
.
extend
({
'
before
'
:
''
,
// Page backwards from the most recent message
'
max
'
:
_converse
.
archived_messages_page_size
,
...
...
@@ -283,97 +360,6 @@
message_archiving_timeout
:
8000
,
// Time (in milliseconds) to wait before aborting MAM request
});
_converse
.
queryForArchivedMessages
=
function
(
options
,
callback
,
errback
)
{
/* Do a MAM (XEP-0313) query for archived messages.
*
* Parameters:
* (Object) options - Query parameters, either MAM-specific or also for Result Set Management.
* (Function) callback - A function to call whenever we receive query-relevant stanza.
* (Function) errback - A function to call when an error stanza is received.
*
* The options parameter can also be an instance of
* Strophe.RSM to enable easy querying between results pages.
*
* The callback function may be called multiple times, first
* for the initial IQ result and then for each message
* returned. The last time the callback is called, a
* Strophe.RSM object is returned on which "next" or "previous"
* can be called before passing it in again to this method, to
* get the next or previous page in the result set.
*/
let
date
;
if
(
_
.
isFunction
(
options
))
{
callback
=
options
;
errback
=
callback
;
}
const
queryid
=
_converse
.
connection
.
getUniqueId
();
const
attrs
=
{
'
type
'
:
'
set
'
};
if
(
!
_
.
isUndefined
(
options
)
&&
options
.
groupchat
)
{
if
(
!
options
[
'
with
'
])
{
// eslint-disable-line dot-notation
throw
new
Error
(
'
You need to specify a "with" value containing
'
+
'
the chat room JID, when querying groupchat messages.
'
);
}
attrs
.
to
=
options
[
'
with
'
];
// eslint-disable-line dot-notation
}
const
stanza
=
$iq
(
attrs
).
c
(
'
query
'
,
{
'
xmlns
'
:
Strophe
.
NS
.
MAM
,
'
queryid
'
:
queryid
});
if
(
!
_
.
isUndefined
(
options
))
{
stanza
.
c
(
'
x
'
,
{
'
xmlns
'
:
Strophe
.
NS
.
XFORM
,
'
type
'
:
'
submit
'
})
.
c
(
'
field
'
,
{
'
var
'
:
'
FORM_TYPE
'
,
'
type
'
:
'
hidden
'
})
.
c
(
'
value
'
).
t
(
Strophe
.
NS
.
MAM
).
up
().
up
();
if
(
options
[
'
with
'
]
&&
!
options
.
groupchat
)
{
// eslint-disable-line dot-notation
stanza
.
c
(
'
field
'
,
{
'
var
'
:
'
with
'
}).
c
(
'
value
'
)
.
t
(
options
[
'
with
'
]).
up
().
up
();
// eslint-disable-line dot-notation
}
_
.
each
([
'
start
'
,
'
end
'
],
function
(
t
)
{
if
(
options
[
t
])
{
date
=
moment
(
options
[
t
]);
if
(
date
.
isValid
())
{
stanza
.
c
(
'
field
'
,
{
'
var
'
:
t
}).
c
(
'
value
'
).
t
(
date
.
format
()).
up
().
up
();
}
else
{
throw
new
TypeError
(
`archive.query: invalid date provided for:
${
t
}
`
);
}
}
});
stanza
.
up
();
if
(
options
instanceof
Strophe
.
RSM
)
{
stanza
.
cnode
(
options
.
toXML
());
}
else
if
(
_
.
intersection
(
RSM_ATTRIBUTES
,
_
.
keys
(
options
)).
length
)
{
stanza
.
cnode
(
new
Strophe
.
RSM
(
options
).
toXML
());
}
}
const
messages
=
[];
const
message_handler
=
_converse
.
connection
.
addHandler
(
function
(
message
)
{
const
result
=
message
.
querySelector
(
'
result
'
);
if
(
!
_
.
isNull
(
result
)
&&
result
.
getAttribute
(
'
queryid
'
)
===
queryid
)
{
messages
.
push
(
message
);
}
return
true
;
},
Strophe
.
NS
.
MAM
);
_converse
.
connection
.
sendIQ
(
stanza
,
function
(
iq
)
{
_converse
.
connection
.
deleteHandler
(
message_handler
);
if
(
_
.
isFunction
(
callback
))
{
const
set
=
iq
.
querySelector
(
'
set
'
);
let
rsm
;
if
(
!
_
.
isUndefined
(
set
))
{
rsm
=
new
Strophe
.
RSM
({
xml
:
set
});
_
.
extend
(
rsm
,
_
.
pick
(
options
,
_
.
concat
(
MAM_ATTRIBUTES
,
[
'
max
'
])));
}
callback
(
messages
,
rsm
);
}
},
function
()
{
_converse
.
connection
.
deleteHandler
(
message_handler
);
if
(
_
.
isFunction
(
errback
))
{
errback
.
apply
(
this
,
arguments
);
}
},
_converse
.
message_archiving_timeout
);
};
_converse
.
onMAMError
=
function
(
iq
)
{
if
(
iq
.
querySelectorAll
(
'
feature-not-implemented
'
).
length
)
{
...
...
@@ -457,11 +443,29 @@
/* Extend default converse.js API to add methods specific to MAM
*/
'
archive
'
:
{
'
query
'
:
function
()
{
'
query
'
:
function
(
options
,
callback
,
errback
)
{
/* Do a MAM (XEP-0313) query for archived messages.
*
* Parameters:
* (Object) options - Query parameters, either
* MAM-specific or also for Result Set Management.
* (Function) callback - A function to call whenever
* we receive query-relevant stanza.
* (Function) errback - A function to call when an
* error stanza is received.
*
* The options parameter can also be an instance of
* Strophe.RSM to enable easy querying between results pages.
*
* When the the callback is called, a Strophe.RSM object is
* returned on which "next" or "previous" can be called
* before passing it in again to this method, to
* get the next or previous page in the result set.
*/
if
(
!
_converse
.
api
.
connection
.
connected
())
{
throw
new
Error
(
'
Can
\'
t call `api.archive.query` before having established an XMPP session
'
);
}
return
_converse
.
queryForArchivedMessages
.
apply
(
this
,
arguments
);
return
queryForArchivedMessages
(
_converse
,
options
,
callback
,
errback
);
}
}
});
...
...
src/utils.js
View file @
26d3455f
...
...
@@ -64,16 +64,6 @@
});
};
function
calculateElementHeight
(
el
)
{
/* Return the height of the passed in DOM element,
* based on the heights of its children.
*/
return
_
.
reduce
(
el
.
children
,
(
result
,
child
)
=>
result
+
child
.
offsetHeight
,
0
);
}
function
slideOutWrapup
(
el
)
{
/* Wrapup function for slideOut. */
el
.
removeAttribute
(
'
data-slider-marker
'
);
...
...
@@ -85,6 +75,48 @@
var
u
=
{};
u
.
getNextElement
=
function
(
el
,
selector
=
'
*
'
)
{
let
next_el
=
el
.
nextElementSibling
;
while
(
!
_
.
isNull
(
next_el
)
&&
!
sizzle
.
matchesSelector
(
next_el
,
selector
))
{
next_el
=
next_el
.
nextElementSibling
;
}
return
next_el
;
}
u
.
getPreviousElement
=
function
(
el
,
selector
=
'
*
'
)
{
let
prev_el
=
el
.
previousSibling
;
while
(
!
_
.
isNull
(
prev_el
)
&&
!
sizzle
.
matchesSelector
(
prev_el
,
selector
))
{
prev_el
=
prev_el
.
previousSibling
}
return
prev_el
;
}
u
.
getFirstChildElement
=
function
(
el
,
selector
=
'
*
'
)
{
let
first_el
=
el
.
firstElementChild
;
while
(
!
_
.
isNull
(
first_el
)
&&
!
sizzle
.
matchesSelector
(
first_el
,
selector
))
{
first_el
=
first_el
.
nextSibling
}
return
first_el
;
}
u
.
getLastChildElement
=
function
(
el
,
selector
=
'
*
'
)
{
let
last_el
=
el
.
lastElementChild
;
while
(
!
_
.
isNull
(
last_el
)
&&
!
sizzle
.
matchesSelector
(
last_el
,
selector
))
{
last_el
=
last_el
.
previousSibling
}
return
last_el
;
}
u
.
calculateElementHeight
=
function
(
el
)
{
/* Return the height of the passed in DOM element,
* based on the heights of its children.
*/
return
_
.
reduce
(
el
.
children
,
(
result
,
child
)
=>
result
+
child
.
offsetHeight
,
0
);
}
u
.
addClass
=
function
(
className
,
el
)
{
if
(
el
instanceof
Element
)
{
el
.
classList
.
add
(
className
);
...
...
@@ -199,7 +231,7 @@
el
.
removeAttribute
(
'
data-slider-marker
'
);
window
.
cancelAnimationFrame
(
marker
);
}
const
end_height
=
calculateElementHeight
(
el
);
const
end_height
=
u
.
calculateElementHeight
(
el
);
if
(
window
.
converse_disable_effects
)
{
// Effects are disabled (for tests)
el
.
style
.
height
=
end_height
+
'
px
'
;
slideOutWrapup
(
el
);
...
...
@@ -227,7 +259,7 @@
// browser bug where browsers don't know the correct
// offsetHeight beforehand.
el
.
removeAttribute
(
'
data-slider-marker
'
);
el
.
style
.
height
=
calculateElementHeight
(
el
)
+
'
px
'
;
el
.
style
.
height
=
u
.
calculateElementHeight
(
el
)
+
'
px
'
;
el
.
style
.
overflow
=
""
;
el
.
style
.
height
=
""
;
resolve
();
...
...
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