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
fbf2e56b
Commit
fbf2e56b
authored
Dec 03, 2016
by
JC Brand
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
converse-muc: Cache the room configuration on the Backbone.Model object
parent
6379676c
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
224 additions
and
69 deletions
+224
-69
spec/chatroom.js
spec/chatroom.js
+6
-4
src/converse-muc.js
src/converse-muc.js
+218
-65
No files found.
spec/chatroom.js
View file @
fbf2e56b
...
...
@@ -246,7 +246,7 @@
it
(
"
can be configured if you're its owner
"
,
mock
.
initConverse
(
function
(
converse
)
{
converse_api
.
rooms
.
open
(
'
room@conference.example.org
'
,
{
'
nick
'
:
'
some1
'
});
var
view
=
converse
.
chatboxviews
.
get
(
'
room@conference.example.org
'
);
spyOn
(
view
,
'
showConfigureButtonIfRoomOwner
'
).
andCallThrough
();
spyOn
(
view
,
'
findAndSaveOwnAffiliation
'
).
andCallThrough
();
/* <presence to="dummy@localhost/converse.js-29092160"
* from="room@conference.example.org/some1">
...
...
@@ -267,7 +267,7 @@
}).
up
()
.
c
(
'
status
'
,
{
code
:
'
110
'
});
converse
.
connection
.
_dataRecv
(
test_utils
.
createRequest
(
presence
));
expect
(
view
.
showConfigureButtonIfRoomOwner
).
toHaveBeenCalled
();
expect
(
view
.
findAndSaveOwnAffiliation
).
toHaveBeenCalled
();
expect
(
view
.
$
(
'
.configure-chatroom-button
'
).
is
(
'
:visible
'
)).
toBeTruthy
();
expect
(
view
.
$
(
'
.toggle-chatbox-button
'
).
is
(
'
:visible
'
)).
toBeTruthy
();
expect
(
view
.
$
(
'
.toggle-bookmark
'
).
is
(
'
:visible
'
)).
toBeTruthy
();
...
...
@@ -738,9 +738,10 @@
* type='unavailable'>
* <x xmlns='http://jabber.org/protocol/muc#user'>
* <item affiliation='none' role='none'>
* <actor nick='Fluellen'/>
* <reason>Avaunt, you cullion!</reason>
*
<actor nick='Fluellen'/>
*
<reason>Avaunt, you cullion!</reason>
* </item>
* <status code='110'/>
* <status code='307'/>
* </x>
* </presence>
...
...
@@ -760,6 +761,7 @@
.
c
(
'
actor
'
).
attrs
({
nick
:
'
Fluellen
'
}).
up
()
.
c
(
'
reason
'
).
t
(
'
Avaunt, you cullion!
'
).
up
()
.
up
()
.
c
(
'
status
'
).
attrs
({
code
:
'
110
'
}).
up
()
.
c
(
'
status
'
).
attrs
({
code
:
'
307
'
}).
nodeTree
;
var
view
=
converse
.
chatboxviews
.
get
(
'
lounge@localhost
'
);
...
...
src/converse-muc.js
View file @
fbf2e56b
...
...
@@ -454,9 +454,15 @@
},
directInvite
:
function
(
recipient
,
reason
)
{
/* Send a direct invitation as per XEP-0249
*
* Parameters:
* (String) recipient - JID of the person being invited
* (String) reason - Optional reason for the invitation
*/
var
attrs
=
{
xmlns
:
'
jabber:x:conference
'
,
jid
:
this
.
model
.
get
(
'
jid
'
)
'
xmlns
'
:
'
jabber:x:conference
'
,
'
jid
'
:
this
.
model
.
get
(
'
jid
'
)
};
if
(
reason
!==
null
)
{
attrs
.
reason
=
reason
;
}
if
(
this
.
model
.
get
(
'
password
'
))
{
attrs
.
password
=
this
.
model
.
get
(
'
password
'
);
}
...
...
@@ -473,10 +479,6 @@
});
},
onCommandError
:
function
(
stanza
)
{
this
.
showStatusNotification
(
__
(
"
Error: could not execute the command
"
),
true
);
},
handleChatStateMessage
:
function
(
message
)
{
/* Override the method on the ChatBoxView base class to
* ignore <gone/> notifications in groupchats.
...
...
@@ -583,6 +585,10 @@
return
this
;
},
onCommandError
:
function
()
{
this
.
showStatusNotification
(
__
(
"
Error: could not execute the command
"
),
true
);
},
onMessageSubmitted
:
function
(
text
)
{
/* Gets called when the user presses enter to send off a
* message in a chat room.
...
...
@@ -788,6 +794,21 @@
},
renderConfigurationForm
:
function
(
stanza
)
{
/* Renders a form given an IQ stanza containing the current
* room configuration.
*
* Returns a promise which resolves once the user has
* either submitted the form, or canceled it.
*
* Parameters:
* (XMLElement) stanza: The IQ stanza containing the room config.
*/
var
that
=
this
,
deferred
=
new
$
.
Deferred
(),
$body
=
this
.
$
(
'
.chatroom-body
'
);
$body
.
children
().
addClass
(
'
hidden
'
);
$body
.
append
(
converse
.
templates
.
chatroom_form
());
var
$form
=
this
.
$el
.
find
(
'
form.chatroom-form
'
),
$fieldset
=
$form
.
children
(
'
fieldset:first
'
),
$stanza
=
$
(
stanza
),
...
...
@@ -806,47 +827,86 @@
$fieldset
=
$form
.
children
(
'
fieldset:last
'
);
$fieldset
.
append
(
'
<input type="submit" class="pure-button button-primary" value="
'
+
__
(
'
Save
'
)
+
'
"/>
'
);
$fieldset
.
append
(
'
<input type="button" class="pure-button button-cancel" value="
'
+
__
(
'
Cancel
'
)
+
'
"/>
'
);
$fieldset
.
find
(
'
input[type=button]
'
).
on
(
'
click
'
,
this
.
cancelConfiguration
.
bind
(
this
));
$form
.
on
(
'
submit
'
,
this
.
saveConfiguration
.
bind
(
this
));
$fieldset
.
find
(
'
input[type=button]
'
).
on
(
'
click
'
,
function
(
ev
)
{
ev
.
preventDefault
();
that
.
cancelConfiguration
();
deferred
.
reject
(
stanza
);
});
$form
.
on
(
'
submit
'
,
function
(
ev
)
{
ev
.
preventDefault
();
that
.
saveConfiguration
(
ev
.
target
)
.
done
(
deferred
.
resolve
)
.
fail
(
_
.
partial
(
deferred
,
stanza
));
});
return
deferred
.
promise
();
},
sendConfiguration
:
function
(
config
,
onSuccess
,
onError
)
{
// Send an IQ stanza with the room configuration.
/* Send an IQ stanza with the room configuration.
*
* Parameters:
* (Array) config: The room configuration
* (Function) onSuccess: Callback upon succesful IQ response
* The first parameter passed in is IQ containing the
* room configuration.
* The second is the response IQ from the server.
* (Function) onError: Callback upon error IQ response
* The first parameter passed in is IQ containing the
* room configuration.
* The second is the response IQ from the server.
*/
var
iq
=
$iq
({
to
:
this
.
model
.
get
(
'
jid
'
),
type
:
"
set
"
})
.
c
(
"
query
"
,
{
xmlns
:
Strophe
.
NS
.
MUC_OWNER
})
.
c
(
"
x
"
,
{
xmlns
:
Strophe
.
NS
.
XFORM
,
type
:
"
submit
"
});
_
.
each
(
config
,
function
(
node
)
{
iq
.
cnode
(
node
).
up
();
});
_
.
each
(
config
||
[],
function
(
node
)
{
iq
.
cnode
(
node
).
up
();
});
onSuccess
=
_
.
isUndefined
(
onSuccess
)
?
_
.
noop
:
_
.
partial
(
onSuccess
,
iq
.
nodeTree
);
onError
=
_
.
isUndefined
(
onError
)
?
_
.
noop
:
_
.
partial
(
onError
,
iq
.
nodeTree
);
return
converse
.
connection
.
sendIQ
(
iq
,
onSuccess
,
onError
);
},
saveConfiguration
:
function
(
ev
)
{
ev
.
preventDefault
();
saveConfiguration
:
function
(
form
)
{
/* Submit the room configuration form by sending an IQ
* stanza to the server.
*
* Returns a promise which resolves once the XMPP server
* has return a response IQ.
*
* Parameters:
* (HTMLElement) form: The configuration form DOM element.
*/
var
deferred
=
new
$
.
Deferred
();
var
that
=
this
;
var
$inputs
=
$
(
ev
.
target
).
find
(
'
:input:not([type=button]):not([type=submit])
'
),
count
=
$inputs
.
length
,
var
$inputs
=
$
(
form
).
find
(
'
:input:not([type=button]):not([type=submit])
'
),
configArray
=
[];
$inputs
.
each
(
function
()
{
configArray
.
push
(
utils
.
webForm2xForm
(
this
));
if
(
!--
count
)
{
that
.
sendConfiguration
(
configArray
,
that
.
onConfigSaved
.
bind
(
that
),
that
.
onErrorConfigSaved
.
bind
(
that
)
);
}
});
this
.
sendConfiguration
(
configArray
,
deferred
.
resolve
,
deferred
.
reject
);
this
.
$el
.
find
(
'
div.chatroom-form-container
'
).
hide
(
function
()
{
$
(
this
).
remove
();
that
.
$el
.
find
(
'
.chat-area
'
).
removeClass
(
'
hidden
'
);
that
.
$el
.
find
(
'
.occupants
'
).
removeClass
(
'
hidden
'
);
});
return
deferred
.
promise
();
},
autoConfigureChatRoom
:
function
(
stanza
)
{
/* Automatically configure room based on the
* 'roomconfigure' data on this view's model.
*
* Returns a promise which resolves once a response IQ has
* been received.
*
* Parameters:
* (XMLElement) stanza: IQ stanza from the server,
* containing the configuration.
*/
var
deferred
=
new
$
.
Deferred
();
var
that
=
this
,
configArray
=
[],
$fields
=
$
(
stanza
).
find
(
'
field
'
),
count
=
$fields
.
length
,
...
...
@@ -874,23 +934,18 @@
if
(
!--
count
)
{
that
.
sendConfiguration
(
configArray
,
that
.
onConfigSaved
.
bind
(
that
)
,
that
.
onErrorConfigSaved
.
bind
(
that
)
deferred
.
resolve
,
_
.
partial
(
deferred
.
reject
,
stanza
)
);
}
});
return
deferred
.
promise
();
},
onConfigSaved
:
function
(
stanza
)
{
// TODO: provide feedback
},
onErrorConfigSaved
:
function
(
stanza
)
{
this
.
showStatusNotification
(
__
(
"
An error occurred while trying to save the form.
"
));
},
cancelConfiguration
:
function
(
ev
)
{
ev
.
preventDefault
();
cancelConfiguration
:
function
()
{
/* Remove the configuration form without submitting and
* return to the chat view.
*/
var
that
=
this
;
this
.
$el
.
find
(
'
div.chatroom-form-container
'
).
hide
(
function
()
{
...
...
@@ -900,29 +955,85 @@
});
},
configureChatRoom
:
function
(
ev
)
{
var
handleIQ
;
if
(
typeof
ev
!==
'
undefined
'
&&
ev
.
preventDefault
)
{
ev
.
preventDefault
();
}
if
(
this
.
model
.
get
(
'
auto_configure
'
))
{
handleIQ
=
this
.
autoConfigureChatRoom
.
bind
(
this
);
}
else
{
if
(
this
.
$el
.
find
(
'
div.chatroom-form-container
'
).
length
)
{
return
;
}
var
$body
=
this
.
$
(
'
.chatroom-body
'
);
$body
.
children
().
addClass
(
'
hidden
'
);
$body
.
append
(
converse
.
templates
.
chatroom_form
());
handleIQ
=
this
.
renderConfigurationForm
.
bind
(
this
);
}
fetchRoomConfiguration
:
function
(
handler
)
{
/* Send an IQ stanza to fetch the room configuration data.
* Returns a promise which resolves once the response IQ
* has been received.
*
* Parameters:
* (Function) handler: The handler for the response IQ
*/
var
that
=
this
;
var
deferred
=
new
$
.
Deferred
();
converse
.
connection
.
sendIQ
(
$iq
({
'
to
'
:
this
.
model
.
get
(
'
jid
'
),
'
type
'
:
"
get
"
}).
c
(
"
query
"
,
{
xmlns
:
Strophe
.
NS
.
MUC_OWNER
}),
handleIQ
function
(
iq
)
{
if
(
handler
)
{
handler
.
apply
(
that
,
arguments
);
}
deferred
.
resolve
(
iq
);
},
deferred
.
reject
// errback
);
return
deferred
.
promise
();
},
cacheRoomConfiguration
:
function
()
{
/* Fetch the room configuration, parse it and then
* save it on the Backbone.Model of this chat rooms.
*/
var
that
=
this
;
this
.
fetchRoomConfiguration
().
then
(
function
(
iq
)
{
var
roomconfig
=
{};
_
.
each
(
iq
.
querySelectorAll
(
'
field
'
),
function
(
field
)
{
var
type
=
field
.
getAttribute
(
'
type
'
);
if
(
type
===
'
hidden
'
&&
type
===
'
fixed
'
)
{
return
;
}
var
fieldname
=
field
.
getAttribute
(
'
var
'
).
replace
(
'
muc#roomconfig_
'
,
''
);
var
value
=
_
.
propertyOf
(
field
.
querySelector
(
'
value
'
)
||
{})(
'
textContent
'
);
/* Unfortunately we don't have enough information
* to determine which values are actually integers, only
* booleans.
*/
if
(
type
===
"
boolean
"
)
{
value
=
parseInt
(
value
,
10
);
}
roomconfig
[
fieldname
]
=
value
;
});
that
.
model
.
save
(
roomconfig
);
});
},
configureChatRoom
:
function
(
ev
)
{
/* Start the process of configuring a chat room, either by
* rendering a configuration form, or by auto-configuring
* based on the "roomconfig" data stored on the
* Backbone.Model.
*
* Stores the new configuration on the Backbone.Model once
* completed.
*
* Paremeters:
* (Event) ev: DOM event that might be passed in if this
* method is called due to a user action. In this
* case, auto-configure won't happen, regardless of
* the settings.
*/
var
that
=
this
;
if
(
_
.
isUndefined
(
ev
)
&&
this
.
model
.
get
(
'
auto_configure
'
))
{
this
.
fetchRoomConfiguration
().
then
(
function
(
iq
)
{
that
.
autoConfigureChatRoom
(
iq
).
then
(
that
.
cacheRoomConfiguration
.
bind
(
that
));
});
}
else
{
if
(
typeof
ev
!==
'
undefined
'
&&
ev
.
preventDefault
)
{
ev
.
preventDefault
();
}
this
.
fetchRoomConfiguration
().
then
(
function
(
iq
)
{
that
.
renderConfigurationForm
(
iq
).
then
(
that
.
cacheRoomConfiguration
.
bind
(
that
));
});
}
},
submitNickname
:
function
(
ev
)
{
...
...
@@ -1097,8 +1208,9 @@
return
;
},
showConfigureButtonIfRoomOwner
:
function
(
pres
)
{
/* Show the configure button if the user is the room owner.
findAndSaveOwnAffiliation
:
function
(
pres
)
{
/* Parse the presence stanza for the current user's
* affiliation.
*
* Parameters:
* (XMLElement) pres: A <presence> stanza.
...
...
@@ -1187,12 +1299,22 @@
}
},
showStatusMessages
:
function
(
stanza
,
is_self
)
{
showStatusMessages
:
function
(
stanza
)
{
/* Check for status codes and communicate their purpose to the user.
* Allows user to configure chat room if they are the owner.
* See: http://xmpp.org/registrar/mucstatus.html
*
* Parameters:
* (XMLElement) stanza: The message or presence stanza
* containing the status codes.
*/
var
elements
=
stanza
.
querySelectorAll
(
'
x[xmlns="
'
+
Strophe
.
NS
.
MUC_USER
+
'
"]
'
);
var
is_self
=
stanza
.
querySelectorAll
(
"
status[code='110']
"
).
length
;
// Unfortunately this doesn't work (returns empty list)
// var elements = stanza.querySelectorAll('x[xmlns="'+Strophe.NS.MUC_USER+'"]');
var
elements
=
_
.
chain
(
stanza
.
querySelectorAll
(
'
x
'
)).
filter
(
function
(
x
)
{
return
x
.
getAttribute
(
'
xmlns
'
)
==
Strophe
.
NS
.
MUC_USER
;
}).
value
();
var
notifications
=
_
.
map
(
elements
,
_
.
partial
(
this
.
parseXUserElement
.
bind
(
this
),
_
,
stanza
,
is_self
)
...
...
@@ -1255,28 +1377,59 @@
return
this
;
},
createInstantRoom
:
function
()
{
/* Sends an empty IQ config stanza to inform the server that the
* room should be created with its default configuration.
*
* See * http://xmpp.org/extensions/xep-0045.html#createroom-instant
*/
this
.
sendConfiguration
().
then
(
this
.
cacheRoomConfiguration
.
bind
(
this
));
},
onChatRoomPresence
:
function
(
pres
)
{
/* Handles all MUC presence stanzas.
*
* Parameters:
* (XMLElement) pres: The stanza
*/
var
$presence
=
$
(
pres
),
is_self
,
new_room
;
var
nick
=
this
.
model
.
get
(
'
nick
'
)
;
var
show_status_messages
=
true
;
if
(
$presence
.
attr
(
'
type
'
)
===
'
error
'
)
{
this
.
model
.
set
(
'
connection_status
'
,
Strophe
.
Status
.
DISCONNECTED
);
this
.
showErrorMessage
(
pres
);
}
else
{
is_self
=
(
$presence
.
find
(
"
status[code='110']
"
).
length
)
||
(
$presence
.
attr
(
'
from
'
)
===
this
.
model
.
get
(
'
id
'
)
+
'
/
'
+
Strophe
.
escapeNode
(
nick
));
is_self
=
$presence
.
find
(
"
status[code='110']
"
).
length
;
new_room
=
$presence
.
find
(
"
status[code='201']
"
).
length
;
if
(
is_self
)
{
this
.
model
.
set
(
'
connection_status
'
,
Strophe
.
Status
.
CONNECTED
);
if
(
!
converse
.
muc_instant_rooms
&&
new_room
)
{
this
.
configureChatRoom
();
this
.
findAndSaveOwnAffiliation
(
pres
);
}
if
(
is_self
&&
new_room
)
{
// This is a new room. It will now be configured
// and the configuration cached on the
// Backbone.Model.
if
(
converse
.
muc_instant_rooms
)
{
this
.
createInstantRoom
();
// Accept default configuration
}
else
{
this
.
hideSpinner
().
showStatusMessages
(
pres
,
is_self
);
this
.
showConfigureButtonIfRoomOwner
(
pres
);
this
.
configureChatRoom
();
if
(
!
this
.
model
.
get
(
'
auto_configure
'
))
{
// We don't show status messages if the
// configuration form is being shown.
show_status_messages
=
false
;
}
}
}
else
if
(
this
.
model
.
get
(
'
connection_status
'
)
!==
Strophe
.
Status
.
CONNECTED
)
{
// This is not a new room, and this is the first
// presence received for this room (hence the
// "connection_status" check), so we now cache the
// room configuration.
this
.
cacheRoomConfiguration
();
}
if
(
show_status_messages
)
{
this
.
hideSpinner
().
showStatusMessages
(
pres
);
}
this
.
occupantsview
.
updateOccupantsOnPresence
(
pres
);
this
.
model
.
set
(
'
connection_status
'
,
Strophe
.
Status
.
CONNECTED
);
}
this
.
occupantsview
.
updateOccupantsOnPresence
(
pres
);
return
true
;
},
...
...
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