Commit 8c20388b authored by JC Brand's avatar JC Brand

Merge branch 'groups-refactor'

Conflicts:
	index.html
parents d24c1610 0c7252f9
......@@ -8,6 +8,7 @@
.svn/
.project
.pydevproject
Backbone.Overview
node_modules
components
docs/doctrees/environment.pickle
......
......@@ -9,13 +9,6 @@ deactivate () {
unset _OLD_VIRTUAL_PATH
fi
# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
if [ -n "$BASH" -o -n "$ZSH_VERSION" ] ; then
hash -r
fi
if [ -n "$_OLD_VIRTUAL_PS1" ] ; then
PS1="$_OLD_VIRTUAL_PS1"
export PS1
......@@ -32,7 +25,7 @@ deactivate () {
# unset irrelavent variables
deactivate nondestructive
VIRTUAL_ENV="/home/jc/dev/converse.js"
VIRTUAL_ENV="~/converse.js"
export VIRTUAL_ENV
_OLD_VIRTUAL_PATH="$PATH"
......@@ -54,10 +47,3 @@ if [ -z "$VIRTUAL_ENV_DISABLE_PROMPT" ] ; then
fi
export PS1
fi
# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
if [ -n "$BASH" -o -n "$ZSH_VERSION" ] ; then
hash -r
fi
......@@ -25,7 +25,9 @@
root.converse = factory(jQuery, _, OTR, DSA, JST, moment);
}
}(this, function ($, _, OTR, DSA, templates, moment) {
"use strict";
// "use strict";
// Cannot use this due to Safari bug.
// See https://github.com/jcbrand/converse.js/issues/196
if (typeof console === "undefined" || typeof console.log === "undefined") {
console = { log: function () {}, error: function () {} };
}
......@@ -104,7 +106,7 @@
if ($.browser.webkit) {
var conversejs = document.getElementById('conversejs');
conversejs.style.display = 'none';
conversejs.style.height = conversejs.offsetHeight;
conversejs.offsetHeight = conversejs.offsetHeight;
conversejs.style.display = 'block';
}
}
......@@ -122,6 +124,14 @@
var KEY = {
ENTER: 13
};
var STATUS_WEIGHTS = {
'offline': 6,
'unavailable': 5,
'xa': 4,
'away': 3,
'dnd': 2,
'online': 1
};
var HAS_CSPRNG = ((typeof crypto !== 'undefined') &&
((typeof crypto.randomBytes === 'function') ||
(typeof crypto.getRandomValues === 'function')
......@@ -132,6 +142,9 @@
(typeof DSA !== "undefined")
);
var OPENED = 'opened';
var CLOSED = 'closed';
// Default configuration values
// ----------------------------
this.allow_contact_requests = true;
......@@ -146,13 +159,14 @@
this.cache_otr_key = false;
this.debug = false;
this.default_box_height = 324; // The default height, in pixels, for the control box, chat boxes and chatrooms.
this.message_carbons = false;
this.expose_rid_and_sid = false;
this.forward_messages = false;
this.hide_muc_server = false;
this.i18n = locales.en;
this.message_carbons = false;
this.no_trimming = false; // Set to true for phantomjs tests (where browser apparently has no width)
this.prebind = false;
this.roster_groups = false;
this.show_controlbox_by_default = false;
this.show_only_online_users = false;
this.show_toolbar = true;
......@@ -190,10 +204,11 @@
'fullname',
'hide_muc_server',
'i18n',
'no_trimming',
'jid',
'no_trimming',
'prebind',
'rid',
'roster_groups',
'show_controlbox_by_default',
'show_only_online_users',
'show_toolbar',
......@@ -270,6 +285,18 @@
'xa': __('This contact is away for an extended period'),
'away': __('This contact is away')
};
var DESC_GROUP_TOGGLE = __('Click to hide these contacts');
var HEADER_CURRENT_CONTACTS = __('My contacts');
var HEADER_PENDING_CONTACTS = __('Pending contacts');
var HEADER_REQUESTING_CONTACTS = __('Contact requests');
var HEADER_UNGROUPED = __('Ungrouped');
var HEADER_WEIGHTS = {};
HEADER_WEIGHTS[HEADER_CURRENT_CONTACTS] = 0;
HEADER_WEIGHTS[HEADER_UNGROUPED] = 1;
HEADER_WEIGHTS[HEADER_REQUESTING_CONTACTS] = 2;
HEADER_WEIGHTS[HEADER_PENDING_CONTACTS] = 3;
// Module-level variables
// ----------------------
......@@ -313,10 +340,10 @@
img_type = $vcard.find('TYPE').text(),
url = $vcard.find('URL').text();
if (jid) {
var rosteritem = converse.roster.get(jid);
if (rosteritem) {
fullname = _.isEmpty(fullname)? rosteritem.get('fullname') || jid: fullname;
rosteritem.save({
var contact = converse.roster.get(jid);
if (contact) {
fullname = _.isEmpty(fullname)? contact.get('fullname') || jid: fullname;
contact.save({
'fullname': fullname,
'image_type': img_type,
'image': img,
......@@ -332,9 +359,9 @@
jid,
function (iq) {
// Error callback
var rosteritem = converse.roster.get(jid);
if (rosteritem) {
rosteritem.save({
var contact = converse.roster.get(jid);
if (contact) {
contact.save({
'vcard_updated': moment().format()
});
}
......@@ -454,40 +481,6 @@
this.xmppstatus.fetch({success: callback, error: callback});
};
this.registerRosterHandler = function () {
// Register handlers that depend on the roster
this.connection.roster.registerCallback(
$.proxy(this.roster.rosterHandler, this.roster),
null, 'presence', null);
};
this.registerRosterXHandler = function () {
this.connection.addHandler(
$.proxy(this.roster.subscribeToSuggestedItems, this.roster),
'http://jabber.org/protocol/rosterx', 'message', null);
};
this.registerPresenceHandler = function () {
this.connection.addHandler(
$.proxy(function (presence) {
this.presenceHandler(presence);
return true;
}, this.roster), null, 'presence', null);
};
this.initRoster = function () {
// Set up the roster
this.roster = new this.RosterItems();
this.roster.browserStorage = new Backbone.BrowserStorage[converse.storage](
b64_sha1('converse.rosteritems-'+converse.bare_jid));
this.registerRosterHandler();
this.registerRosterXHandler();
this.registerPresenceHandler();
// Now create the view which will fetch roster items from
// browserStorage
this.rosterview = new this.RosterView({'model':this.roster});
};
this.registerGlobalEventHandlers = function () {
$(document).click(function() {
if ($('.toggle-otr ul').is(':visible')) {
......@@ -574,7 +567,8 @@
this.features = new this.Features();
this.enableCarbons();
this.initStatus($.proxy(function () {
this.initRoster();
this.roster = new converse.RosterContacts();
this.rosterview = new this.RosterView({model: new this.RosterGroups()});
this.chatboxes.onConnected();
this.connection.roster.get(function () {});
this.giveFeedback(__('Online Contacts'));
......@@ -635,8 +629,7 @@
}
});
this.Message = Backbone.Model.extend();
this.Message = Backbone.Model;
this.Messages = Backbone.Collection.extend({
model: converse.Message
});
......@@ -1343,8 +1336,8 @@
updateVCard: function () {
var jid = this.model.get('jid'),
rosteritem = converse.roster.get(jid);
if ((rosteritem) && (!rosteritem.get('vcard_updated'))) {
contact = converse.roster.get(jid);
if ((contact) && (!contact.get('vcard_updated'))) {
converse.getVCard(
jid,
$.proxy(function (jid, fullname, image, image_type, url) {
......@@ -2712,9 +2705,7 @@
},
toggle: function (ev) {
if (ev && ev.preventDefault) {
ev.preventDefault();
}
if (ev && ev.preventDefault) { ev.preventDefault(); }
this.toggleview.model.save({'collapsed': !this.toggleview.model.get('collapsed')});
this.$('.minimized-chats-flyout').toggle();
},
......@@ -2746,7 +2737,8 @@
},
updateUnreadMessagesCounter: function () {
var ls = this.model.pluck('num_unread'), count = 0;
var ls = this.model.pluck('num_unread'),
count = 0, i;
for (i=0; i<ls.length; i++) { count += ls[i]; }
this.toggleview.model.set({'num_unread': count});
this.render();
......@@ -2787,7 +2779,7 @@
},
});
this.RosterItem = Backbone.Model.extend({
this.RosterContact = Backbone.Model.extend({
initialize: function (attributes, options) {
var jid = attributes.jid;
if (!attributes.fullname) {
......@@ -2797,15 +2789,15 @@
'id': jid,
'user_id': Strophe.getNodeFromJid(jid),
'resources': [],
'groups': [],
'status': ''
}, attributes);
attrs.sorted = false;
attrs.chat_status = 'offline';
this.set(attrs);
}
});
this.RosterItemView = Backbone.View.extend({
this.RosterContactView = Backbone.View.extend({
tagName: 'dd',
events: {
......@@ -2815,8 +2807,27 @@
"click .remove-xmpp-contact": "removeContact"
},
initialize: function () {
this.model.on("change", this.onChange, this);
this.model.on("remove", this.remove, this);
this.model.on("destroy", this.remove, this);
this.model.on("open", this.openChat, this);
},
onChange: function () {
if (converse.show_only_online_users) {
if (this.model.get('chat_status') !== 'online') {
this.$el.hide();
} else {
this.$el.show();
}
} else {
this.render();
}
},
openChat: function (ev) {
ev.preventDefault();
if (ev && ev.preventDefault) { ev.preventDefault(); }
return converse.chatboxviews.showChat({
'id': this.model.get('jid'),
'jid': this.model.get('jid'),
......@@ -2829,14 +2840,16 @@
},
removeContact: function (ev) {
ev.preventDefault();
if (ev && ev.preventDefault) { ev.preventDefault(); }
var result = confirm(__("Are you sure you want to remove this contact?"));
if (result === true) {
var bare_jid = this.model.get('jid');
converse.connection.roster.remove(bare_jid, function (iq) {
converse.connection.roster.remove(bare_jid, $.proxy(function (iq) {
converse.connection.roster.unauthorize(bare_jid);
converse.rosterview.model.remove(bare_jid);
});
this.model.destroy();
this.remove();
}, this));
}
},
......@@ -2862,6 +2875,7 @@
render: function () {
var item = this.model,
ask = item.get('ask'),
chat_status = item.get('chat_status'),
requesting = item.get('requesting'),
subscription = item.get('subscription');
......@@ -2877,10 +2891,20 @@
this.$el.removeClass(cls);
}
}, this);
this.$el.addClass(chat_status).data('status', chat_status);
this.$el.addClass(item.get('chat_status'));
if (ask === 'subscribe') {
if ((ask === 'subscribe') || (subscription === 'from')) {
/* ask === 'subscribe'
* Means we have asked to subscribe to them.
*
* subscription === 'from'
* They are subscribed to use, but not vice versa.
* We assume that there is a pending subscription
* from us to them (otherwise we're in a state not
* supported by converse.js).
*
* So in both cases the user is a "pending" contact.
*/
this.$el.addClass('pending-xmpp-contact');
this.$el.html(converse.templates.pending_contact(
_.extend(item.toJSON(), {
......@@ -2900,7 +2924,7 @@
this.$el.addClass('current-xmpp-contact');
this.$el.html(converse.templates.roster_item(
_.extend(item.toJSON(), {
'desc_status': STATUSES[item.get('chat_status')||'offline'],
'desc_status': STATUSES[chat_status||'offline'],
'desc_chat': __('Click to chat with this contact'),
'desc_remove': __('Click to remove this contact')
})
......@@ -2910,32 +2934,21 @@
}
});
this.RosterItems = Backbone.Collection.extend({
model: converse.RosterItem,
comparator : function (rosteritem) {
var chat_status = rosteritem.get('chat_status'),
rank = 4;
switch(chat_status) {
case 'offline':
rank = 0;
break;
case 'unavailable':
rank = 1;
break;
case 'xa':
rank = 2;
break;
case 'away':
rank = 3;
break;
case 'dnd':
rank = 4;
break;
case 'online':
rank = 5;
break;
this.RosterContacts = Backbone.Collection.extend({
model: converse.RosterContact,
browserStorage: new Backbone.BrowserStorage[converse.storage](
b64_sha1('converse.contacts-'+converse.bare_jid)),
comparator: function (contact1, contact2) {
var name1 = contact1.get('fullname').toLowerCase();
var status1 = contact1.get('chat_status') || 'offline';
var name2 = contact2.get('fullname').toLowerCase();
var status2 = contact2.get('chat_status') || 'offline';
if (STATUS_WEIGHTS[status1] === STATUS_WEIGHTS[status2]) {
return name1 < name2 ? -1 : (name1 > name2? 1 : 0);
} else {
return STATUS_WEIGHTS[status1] < STATUS_WEIGHTS[status2] ? -1 : 1;
}
return rank;
},
subscribeToSuggestedItems: function (msg) {
......@@ -3070,7 +3083,6 @@
ask: item.ask,
fullname: item.name || item.jid,
groups: item.groups,
is_last: is_last,
jid: item.jid,
subscription: item.subscription
});
......@@ -3136,8 +3148,7 @@
image: img,
image_type: img_type,
url: url,
vcard_updated: moment().format(),
is_last: true
vcard_updated: moment().format()
});
}, this),
$.proxy(function (jid, fullname, img, img_type, url) {
......@@ -3148,8 +3159,7 @@
subscription: 'none',
ask: null,
requesting: true,
fullname: jid,
is_last: true
fullname: jid
});
}, this)
);
......@@ -3173,7 +3183,7 @@
$show = $presence.find('show'),
chat_status = $show.text() || 'online',
status_message = $presence.find('status'),
item;
contact;
if (this.isSelf(bare_jid)) {
if ((converse.connection.jid !== jid)&&(presence_type !== 'unavailable')) {
......@@ -3184,9 +3194,9 @@
} else if (($presence.find('x').attr('xmlns') || '').indexOf(Strophe.NS.MUC) === 0) {
return true; // Ignore MUC
}
item = this.get(bare_jid);
if (item && (status_message.text() != item.get('status'))) {
item.save({'status': status_message.text()});
contact = this.get(bare_jid);
if (contact && (status_message.text() != contact.get('status'))) {
contact.save({'status': status_message.text()});
}
if ((presence_type === 'subscribed') || (presence_type === 'unsubscribe')) {
return true;
......@@ -3196,185 +3206,300 @@
this.unsubscribe(bare_jid);
} else if (presence_type === 'unavailable') {
if (this.removeResource(bare_jid, resource) === 0) {
if (item) {
item.save({'chat_status': 'offline'});
if (contact) {
contact.save({'chat_status': 'offline'});
}
}
} else if (item) {
} else if (contact) {
// presence_type is undefined
this.addResource(bare_jid, resource);
item.save({'chat_status': chat_status});
contact.save({'chat_status': chat_status});
}
return true;
}
});
this.RosterView = Backbone.Overview.extend({
tagName: 'dl',
id: 'converse-roster',
this.RosterGroup = Backbone.Model.extend({
initialize: function (attributes, options) {
this.set(_.extend({
description: DESC_GROUP_TOGGLE,
state: OPENED
}, attributes));
// Collection of contacts belonging to this group.
this.contacts = new converse.RosterContacts();
}
});
this.RosterGroupView = Backbone.Overview.extend({
tagName: 'dt',
className: 'roster-group',
events: {
"click a.group-toggle": "toggle"
},
initialize: function () {
this.model.on("add", function (item) {
this.addRosterItemView(item).render(item);
if (!item.get('vcard_updated')) {
// This will update the vcard, which triggers a change
// request which will rerender the roster item.
converse.getVCard(item.get('jid'));
}
this.model.contacts.on("add", this.addContact, this);
this.model.contacts.on("change:chat_status", function (contact) {
// This might be optimized by instead of first sorting, finding the correct position in positionContact
this.model.contacts.sort();
this.positionContact(contact).render();
}, this);
this.model.contacts.on("destroy", this.onRemove, this);
this.model.contacts.on("remove", this.onRemove, this);
converse.roster.on('change:groups', this.onContactGroupChange, this);
},
this.model.on('change', function (item) {
if ((_.size(item.changed) === 1) && _.contains(_.keys(item.changed), 'sorted')) {
return;
render: function () {
this.$el.attr('data-group', this.model.get('name'));
this.$el.html(
$(converse.templates.group_header({
label_group: this.model.get('name'),
desc_group_toggle: this.model.get('description'),
toggle_state: this.model.get('state')
}))
);
return this;
},
positionContact: function (contact) {
/* Place the contact's DOM element in the correct alphabetical
* position amongst the other contacts in this group.
*/
var view = this.get(contact.get('id'));
var index = this.model.contacts.indexOf(contact);
if (index === 0) {
this.$el.after(view.$el);
} else if (index == (this.model.contacts.length-1)) {
this.$el.nextUntil('dt').last().after(view.$el);
} else {
this.$el.nextUntil('dt').eq(index).before(view.$el);
}
this.updateChatBox(item).render(item);
}, this);
return view;
},
this.model.on("remove", function (item) { this.removeRosterItemView(item); }, this);
this.model.on("destroy", function (item) { this.removeRosterItemView(item); }, this);
this.model.on("reset", function () { this.removeAllRosterItemViewss(); }, this);
toggle: function (ev) {
if (ev && ev.preventDefault) { ev.preventDefault(); }
var $el = $(ev.target);
this.$el.nextUntil('dt').slideToggle();
if ($el.hasClass("icon-opened")) {
this.model.save({state: CLOSED});
$el.removeClass("icon-opened").addClass("icon-closed");
} else {
$el.removeClass("icon-closed").addClass("icon-opened");
this.model.save({state: OPENED});
}
},
var roster_markup = converse.templates.contacts({
'label_contacts': __('My contacts')
});
if (converse.allow_contact_requests) {
roster_markup =
converse.templates.requesting_contacts({
'label_contact_requests': __('Contact requests')
}) +
roster_markup +
converse.templates.pending_contacts({
'label_pending_contacts': __('Pending contacts')
});
addContact: function (contact) {
var view = new converse.RosterContactView({model: contact});
this.add(contact.get('id'), view);
var view = this.positionContact(contact).render();
if (this.model.get('state') === CLOSED) {
view.$el.hide();
}
this.$el.hide().html(roster_markup);
this.model.fetch({add: true}); // Get the cached roster items from localstorage
this.$el.show();
},
updateChatBox: function (item, changed) {
var chatbox = converse.chatboxes.get(item.get('jid')),
changes = {};
if (!chatbox) {
return this;
onContactGroupChange: function (contact) {
var in_this_group = _.contains(contact.get('groups'), this.model.get('name'));
var cid = contact.get('id');
var in_this_overview = !this.get(cid);
if (in_this_group && !in_this_overview) {
this.remove(cid); // Contact has been added to this group
} else if (!in_this_group && in_this_overview) {
this.addContact(contact); // Contact has been removed from this group
}
if (_.has(item.changed, 'chat_status')) {
changes.chat_status = item.get('chat_status');
},
onRemove: function (contact) {
if (this.model.contacts.length === 0) {
this.$el.hide();
}
if (_.has(item.changed, 'status')) {
changes.status = item.get('status');
}
chatbox.save(changes);
return this;
},
});
addRosterItemView: function (item) {
var view = new converse.RosterItemView({model: item});
this.add(item.id, view);
return this;
this.RosterGroups = Backbone.Collection.extend({
model: converse.RosterGroup,
comparator: function (a, b) {
/* Groups are sorted alphabetically, ignoring case.
* However, Ungrouped, Requesting Contacts and Pending Contacts
* appear last and in that order. */
a = a.get('name');
b = b.get('name');
var special_groups = _.keys(HEADER_WEIGHTS);
var a_is_special = _.contains(special_groups, a);
var b_is_special = _.contains(special_groups, b);
if (!a_is_special && !b_is_special ) {
return a.toLowerCase() < b.toLowerCase() ? -1 : (a.toLowerCase() > b.toLowerCase() ? 1 : 0);
} else if (a_is_special && b_is_special) {
return HEADER_WEIGHTS[a] < HEADER_WEIGHTS[b] ? -1 : (HEADER_WEIGHTS[a] > HEADER_WEIGHTS[b] ? 1 : 0);
} else if (!a_is_special && b_is_special) {
return (b === HEADER_CURRENT_CONTACTS) ? 1 : -1;
} else if (a_is_special && !b_is_special) {
return (a === HEADER_CURRENT_CONTACTS) ? -1 : 1;
}
},
removeAllRosterItemViewss: function () {
var views = this.removeAll();
initialize: function () {
this.browserStorage = new Backbone.BrowserStorage[converse.storage](
b64_sha1('converse.roster.groups'+converse.bare_jid));
}
});
this.RosterView = Backbone.Overview.extend({
tagName: 'dl',
id: 'converse-roster',
initialize: function () {
this.registerRosterHandler();
this.registerRosterXHandler();
this.registerPresenceHandler();
converse.roster.on("add", this.onContactAdd, this);
converse.roster.on('change', this.onContactChange, this);
converse.roster.on("destroy", this.update, this);
converse.roster.on("remove", this.update, this);
this.model.on("add", this.onGroupAdd, this);
this.model.on("reset", this.reset, this);
this.render();
this.model.fetch({add: true});
converse.roster.fetch({add: true});
},
render: function () {
this.$el.empty();
return this;
},
removeRosterItemView: function (item) {
if (this.get(item.id)) {
this.get(item.id).remove();
this.render();
update: function () {
// XXX: Is this still being used/valid?
var $count = $('#online-count');
$count.text('('+converse.roster.getNumOnlineContacts()+')');
if (!$count.is(':visible')) {
$count.show();
}
return this;
},
renderRosterItem: function (item, view) {
if ((converse.show_only_online_users) && (item.get('chat_status') !== 'online')) {
view.$el.remove();
view.delegateEvents();
reset: function () {
converse.roster.reset();
this.removeAll();
this.render().update();
return this;
}
if ($.contains(document.documentElement, view.el)) {
view.render();
} else {
this.$el.find('#xmpp-contacts').after(view.render().el);
}
},
render: function (item) {
var $my_contacts = this.$el.find('#xmpp-contacts'),
$contact_requests = this.$el.find('#xmpp-contact-requests'),
$pending_contacts = this.$el.find('#pending-xmpp-contacts'),
sorted = false,
$count, changed_presence;
if (item) {
var jid = item.id,
view = this.get(item.id),
ask = item.get('ask'),
subscription = item.get('subscription'),
requesting = item.get('requesting'),
crit = {order:'asc'};
if ((ask === 'subscribe') && (subscription == 'none')) {
$pending_contacts.after(view.render().el);
$pending_contacts.after($pending_contacts.siblings('dd.pending-xmpp-contact').tsort(crit));
} else if ((ask === 'subscribe') && (subscription == 'from')) {
// TODO: We have accepted an incoming subscription
// request and (automatically) made our own subscription request back.
// It would be useful to update the roster here to show
// things are happening... (see docs/DEVELOPER.rst)
$pending_contacts.after(view.render().el);
$pending_contacts.after($pending_contacts.siblings('dd.pending-xmpp-contact').tsort(crit));
} else if (requesting === true) {
$contact_requests.after(view.render().el);
$contact_requests.after($contact_requests.siblings('dd.requesting-xmpp-contact').tsort(crit));
} else if (subscription === 'both' || subscription === 'to') {
this.renderRosterItem(item, view);
registerRosterHandler: function () {
// Register handlers that depend on the roster
converse.connection.roster.registerCallback(
$.proxy(converse.roster.rosterHandler, converse.roster),
null, 'presence', null);
},
registerRosterXHandler: function () {
converse.connection.addHandler(
$.proxy(converse.roster.subscribeToSuggestedItems, converse.roster),
'http://jabber.org/protocol/rosterx', 'message', null);
},
registerPresenceHandler: function () {
converse.connection.addHandler(
$.proxy(function (presence) {
converse.roster.presenceHandler(presence);
return true;
}, this), null, 'presence', null);
},
onGroupAdd: function (group) {
var view = new converse.RosterGroupView({model: group});
this.add(group.get('name'), view.render());
this.positionGroup(view);
},
onContactAdd: function (contact) {
this.addRosterContact(contact).update();
if (!contact.get('vcard_updated')) {
// This will update the vcard, which triggers a change
// request which will rerender the roster contact.
converse.getVCard(contact.get('jid'));
}
changed_presence = item.changed.chat_status;
if (changed_presence) {
this.sortRoster(changed_presence);
sorted = true;
},
onContactChange: function (contact) {
this.updateChatBox(contact).update();
},
updateChatBox: function (contact, changed) {
var chatbox = converse.chatboxes.get(contact.get('jid')),
changes = {};
if (!chatbox) {
return this;
}
if (item.get('is_last')) {
if (!sorted) {
this.sortRoster(item.get('chat_status'));
if (_.has(contact.changed, 'chat_status')) {
changes.chat_status = contact.get('chat_status');
}
if (!this.$el.is(':visible')) {
// Once all initial roster items have been added, we
// can show the roster.
this.$el.show();
if (_.has(contact.changed, 'status')) {
changes.status = contact.get('status');
}
chatbox.save(changes);
return this;
},
positionGroup: function (view) {
/* Place the group's DOM element in the correct alphabetical
* position amongst the other groups in the roster.
*/
var index = this.model.indexOf(view.model);
if (index === 0) {
this.$el.prepend(view.$el);
} else if (index == (this.model.length-1)) {
this.$('.roster-group').last().siblings('dd').last().after(view.$el);
} else {
$(this.$('.roster-group').eq(index)).before(view.$el);
}
},
getGroup: function (name) {
/* Returns the group as specified by name.
* Creates the group if it doesn't exist.
*/
var view = this.get(name);
if (view) {
return view.model;
}
// Hide the headings if there are no contacts under them
_.each([$my_contacts, $contact_requests, $pending_contacts], function (h) {
if (h.nextUntil('dt').length) {
if (!h.is(':visible')) {
h.show();
return this.model.create({name: name, id: b64_sha1(name)});
},
addContactToGroup: function (contact, name) {
this.getGroup(name).contacts.add(contact);
},
addCurrentContact: function (contact) {
var groups;
if (converse.roster_groups) {
groups = contact.get('groups');
if (groups.length === 0) {
groups = [HEADER_UNGROUPED];
}
} else {
groups = [HEADER_CURRENT_CONTACTS];
}
else if (h.is(':visible')) {
h.hide();
_.each(groups, $.proxy(function (name) {
this.addContactToGroup(contact, name);
}, this));
},
addRosterContact: function (contact) {
var view;
if (contact.get('subscription') === 'both' || contact.get('subscription') === 'to') {
this.addCurrentContact(contact);
} else {
view = (new converse.RosterContactView({model: contact})).render();
if ((contact.get('ask') === 'subscribe') || (contact.get('subscription') === 'from')) {
this.addContactToGroup(contact, HEADER_PENDING_CONTACTS);
} else if (contact.get('requesting') === true) {
this.addContactToGroup(contact, HEADER_REQUESTING_CONTACTS);
}
});
$count = $('#online-count');
$count.text('('+this.model.getNumOnlineContacts()+')');
if (!$count.is(':visible')) {
$count.show();
}
converse.emit('rosterViewUpdated');
return this;
},
sortRoster: function (chat_status) {
var $my_contacts = this.$el.find('#xmpp-contacts');
$my_contacts.siblings('dd.current-xmpp-contact.'+chat_status).tsort('a', {order:'asc'});
$my_contacts.after($my_contacts.siblings('dd.current-xmpp-contact.offline'));
$my_contacts.after($my_contacts.siblings('dd.current-xmpp-contact.unavailable'));
$my_contacts.after($my_contacts.siblings('dd.current-xmpp-contact.xa'));
$my_contacts.after($my_contacts.siblings('dd.current-xmpp-contact.away'));
$my_contacts.after($my_contacts.siblings('dd.current-xmpp-contact.dnd'));
$my_contacts.after($my_contacts.siblings('dd.current-xmpp-contact.online'));
}
});
......
......@@ -38,8 +38,14 @@
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-closed:before {
content: "\25ba";
}
.icon-opened:before {
content: "\25bc";
}
.icon-checkmark:before {
content: "\e600";
content: "\2713";
}
#conversejs .icon-home:before {
content: "\e000";
......@@ -812,8 +818,7 @@ dl.add-converse-contact {
top: 1px;
}
#conversejs .controlbox-pane dt {
margin: 0;
padding-top: 0.5em;
padding: 3px;
}
#conversejs .chatroom-form-container {
height: 100%;
......@@ -847,11 +852,11 @@ dl.add-converse-contact {
#conversejs .chatroom-form label select {
float: right;
}
#conversejs #converse-roster dd.odd {
#converse-roster dd.odd {
background-color: #DCEAC5;
/* Make this difference */
}
#conversejs #converse-roster dd.current-xmpp-contact span {
#converse-roster dd.current-xmpp-contact span {
font-size: 16px;
float: left;
color: #4f4f4f;
......@@ -860,18 +865,25 @@ dl.add-converse-contact {
margin-left: 0.5em;
float: right;
}
#conversejs #converse-roster dd a,
#conversejs #converse-roster dd span {
#converse-roster dd a,
#converse-roster dd span {
text-shadow: 0 1px 0 #fafafa;
display: inline-block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
#conversejs #converse-roster dd span {
#conversejs #converse-roster span.req-contact-name {
width: 65%;
}
#conversejs #converse-roster span.pending-contact-name,
#conversejs #converse-roster a.open-chat {
width: 80%;
}
#converse-roster dd span {
padding: 0 5px 0 0;
}
#conversejs #converse-roster {
#converse-roster {
overflow-y: auto;
overflow-x: hidden;
width: 100%;
......@@ -880,6 +892,9 @@ dl.add-converse-contact {
height: 254px;
height: calc(100% - 70px);
}
#converse-roster .group-toggle {
color: #666;
}
#conversejs dd.available-chatroom {
overflow-x: hidden;
text-overflow: ellipsis;
......@@ -889,8 +904,8 @@ dl.add-converse-contact {
#conversejs dd.available-chatroom a.open-room {
width: 148px;
}
#conversejs #available-chatrooms dt,
#conversejs #converse-roster dt {
#available-chatrooms dt,
#converse-roster dt {
font-weight: normal;
font-size: 13px;
color: #666;
......@@ -898,7 +913,7 @@ dl.add-converse-contact {
padding: 0.3em 0 0 0.5em;
text-shadow: 0 1px 0 #fafafa;
}
#conversejs #converse-roster dt {
#converse-roster dt {
display: none;
}
#conversejs .room-info {
......@@ -950,6 +965,11 @@ dl.add-converse-contact {
#conversejs #converse-roster dd {
line-height: 16px;
}
#conversejs .group-toggle {
display: block;
width: 100%;
}
#conversejs .roster-group:hover,
#conversejs dd.available-chatroom:hover,
#conversejs #converse-roster dd:hover {
background-color: #eee;
......@@ -967,9 +987,6 @@ dl.add-converse-contact {
#conversejs #converse-roster dd:hover a.remove-xmpp-contact {
display: inline-block;
}
#conversejs #converse-roster a.open-chat {
width: 80%;
}
#conversejs .chatbox,
#conversejs .chatroom {
height: 25px;
......@@ -1287,6 +1304,9 @@ input.custom-xmpp-status {
#conversejs .chatbox .dropdown dd ul li:hover {
background-color: #bed6e5;
}
#conversejs .chatbox .dropdown dd.search-xmpp ul li:hover {
background-color: #bed6e5;
}
#conversejs .xmpp-status-menu li a {
width: 100%;
}
......
......@@ -20,6 +20,10 @@ Changelog
Message forwarding was before a default behavior but is now optional (and disabled by default). [jcbrand]
* Newly opened chat boxes always appear immediately left of the controlbox. [jcbrand]
* #71 Chat boxes and rooms can be minimized. [jcbrand]
* #83 Roster contacts can be shown according to their groups. [jcbrand]
Note: Converse.js can show users under groups if you have assigned them
already via another client or server configuration. There is not yet a way
to assign contacts to groups from within converse.js itself.
* #123 Show converse.js in the resource assigned to a user. [jcbrand]
* #130 Fixed bootstrap conflicts. [jcbrand]
* #132 Support for `XEP-0280: Message Carbons <https://xmpp.org/extensions/xep-0280.html'>`_.
......
......@@ -30,7 +30,7 @@ subscription and subscribes back.
<presence type="subscribed" to="contact1@localhost"/>
<presence type="subscribe" to="contact1@localhost"/>
Contact2 receives a roster update
IF Contact1 is still online and likewise subscribes back, Contact2 will receive a roster update
::
<iq type="set" to="contact2@localhost">
......@@ -39,6 +39,13 @@ Contact2 receives a roster update
</query>
</iq>
ELSE, Contact 2 will receive a roster update (but not an IQ stanza)
::
ask = null
subscription = "from"
Contact1's converse.js client will automatically
approve.
......
......@@ -784,8 +784,6 @@ Here are the different events that are emitted:
+----------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
| **roster** | When the roster is updated. | ``converse.on('roster', function (items) { ... });`` |
+----------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
| **rosterViewUpdated** | Whenever the roster view (i.e. the rendered HTML) has changed. | ``converse.on('rosterViewUpdated', function (items) { ... });`` |
+----------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
| **callButtonClicked** | When a call button (i.e. with class .toggle-call) on a chat box has been clicked. | ``converse.on('callButtonClicked', function (connection, model) { ... });`` |
+----------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
| **chatBoxOpened** | When a chat box has been opened. | ``converse.on('chatBoxOpened', function (chatbox) { ... });`` |
......@@ -1003,6 +1001,18 @@ values as ``jid``, ``sid``, ``rid``.
Additionally, you have to specify ``bosh_service_url``.
roster_groups
-------------
Default: ``false``
If set to ``true``, converse.js will show any roster groups you might have
configured.
.. Note ::
It's currently not possible to use converse.js to assign contacts to groups.
Converse.js can only show users and groups that were previously configured
elsewhere.
show_controlbox_by_default
--------------------------
......
......@@ -150,6 +150,3 @@ p {
.fs1 {
font-size: 32px;
}
.fs2 {
font-size: 32px;
}
......@@ -9,27 +9,50 @@
<link rel="stylesheet" href="style.css"></head>
<body>
<div class="bgc1 clearfix">
<h1 class="mhmm mvm"><span class="fgc1">Font Name:</span> icomoon <small class="fgc1">(Glyphs:&nbsp;86)</small></h1>
<h1 class="mhmm mvm"><span class="fgc1">Font Name:</span> icomoon <small class="fgc1">(Glyphs:&nbsp;88)</small></h1>
</div>
<div class="clearfix mhl ptl">
<h1 class="mvm mtn bshadow fgc1">Grid Size: 16</h1>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-checkmark"></span><span class="mls"> icon-checkmark</span>
<span class="icon-closed"></span><span class="mls"> icon-closed</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e600" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe600;" class="unitRight size1of2 talign-right" />
<input type="text" readonly value="25ba" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#x25ba;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-opened"></span><span class="mls"> icon-opened</span>
</div>
<div class="clearfix mhl ptl">
<h1 class="mvm mtn bshadow fgc1">Grid Size: Unknown</h1>
<div class="glyph fs2">
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="25bc" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#x25bc;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-checkmark"></span><span class="mls"> icon-checkmark</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="2713" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#x2713;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-home"></span><span class="mls"> icon-home</span>
</div>
......@@ -42,7 +65,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-pencil"></span><span class="mls"> icon-pencil</span>
</div>
......@@ -55,7 +78,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-camera"></span><span class="mls"> icon-camera</span>
</div>
......@@ -68,7 +91,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-camera2"></span><span class="mls"> icon-camera2</span>
</div>
......@@ -81,9 +104,9 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-play"></span><span class="mls"> icon-play</span>
<span class="icon-play22"></span><span class="mls"> icon-play22</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="25d9" class="unit size1of2" />
......@@ -94,7 +117,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-music"></span><span class="mls"> icon-music</span>
</div>
......@@ -107,7 +130,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-headphones"></span><span class="mls"> icon-headphones</span>
</div>
......@@ -120,7 +143,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-phone"></span><span class="mls"> icon-phone</span>
</div>
......@@ -133,7 +156,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-phone-hang-up"></span><span class="mls"> icon-phone-hang-up</span>
</div>
......@@ -146,7 +169,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-address-book"></span><span class="mls"> icon-address-book</span>
</div>
......@@ -159,7 +182,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-notebook"></span><span class="mls"> icon-notebook</span>
</div>
......@@ -172,7 +195,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-envelop"></span><span class="mls"> icon-envelop</span>
</div>
......@@ -185,7 +208,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-pushpin"></span><span class="mls"> icon-pushpin</span>
</div>
......@@ -198,7 +221,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-bubble"></span><span class="mls"> icon-bubble</span>
</div>
......@@ -211,7 +234,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-bubble2"></span><span class="mls"> icon-bubble2</span>
</div>
......@@ -224,7 +247,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-bubbles"></span><span class="mls"> icon-bubbles</span>
</div>
......@@ -237,7 +260,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-bubbles2"></span><span class="mls"> icon-bubbles2</span>
</div>
......@@ -250,7 +273,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-bubbles3"></span><span class="mls"> icon-bubbles3</span>
</div>
......@@ -263,7 +286,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-user"></span><span class="mls"> icon-user</span>
</div>
......@@ -276,7 +299,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-users"></span><span class="mls"> icon-users</span>
</div>
......@@ -289,7 +312,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-quotes-left"></span><span class="mls"> icon-quotes-left</span>
</div>
......@@ -302,7 +325,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-spinner"></span><span class="mls"> icon-spinner</span>
</div>
......@@ -315,7 +338,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-search"></span><span class="mls"> icon-search</span>
</div>
......@@ -328,7 +351,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-cogs"></span><span class="mls"> icon-cogs</span>
</div>
......@@ -341,7 +364,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-wrench"></span><span class="mls"> icon-wrench</span>
</div>
......@@ -354,7 +377,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-unlocked"></span><span class="mls"> icon-unlocked</span>
</div>
......@@ -367,7 +390,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-lock"></span><span class="mls"> icon-lock</span>
</div>
......@@ -380,7 +403,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-lock2"></span><span class="mls"> icon-lock2</span>
</div>
......@@ -393,7 +416,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-key"></span><span class="mls"> icon-key</span>
</div>
......@@ -406,7 +429,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-key2"></span><span class="mls"> icon-key2</span>
</div>
......@@ -419,7 +442,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-zoomout"></span><span class="mls"> icon-zoomout</span>
</div>
......@@ -432,7 +455,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-zoomin"></span><span class="mls"> icon-zoomin</span>
</div>
......@@ -445,7 +468,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-cog"></span><span class="mls"> icon-cog</span>
</div>
......@@ -458,7 +481,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-remove"></span><span class="mls"> icon-remove</span>
</div>
......@@ -471,7 +494,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-remove2"></span><span class="mls"> icon-remove2</span>
</div>
......@@ -484,7 +507,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-eye"></span><span class="mls"> icon-eye</span>
</div>
......@@ -497,7 +520,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-eye-blocked"></span><span class="mls"> icon-eye-blocked</span>
</div>
......@@ -510,7 +533,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-attachment"></span><span class="mls"> icon-attachment</span>
</div>
......@@ -523,7 +546,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-globe"></span><span class="mls"> icon-globe</span>
</div>
......@@ -536,7 +559,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-heart"></span><span class="mls"> icon-heart</span>
</div>
......@@ -549,7 +572,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-happy"></span><span class="mls"> icon-happy</span>
</div>
......@@ -562,7 +585,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-thumbs-up"></span><span class="mls"> icon-thumbs-up</span>
</div>
......@@ -575,7 +598,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-smiley"></span><span class="mls"> icon-smiley</span>
</div>
......@@ -588,7 +611,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-tongue"></span><span class="mls"> icon-tongue</span>
</div>
......@@ -601,7 +624,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-sad"></span><span class="mls"> icon-sad</span>
</div>
......@@ -614,7 +637,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-wink"></span><span class="mls"> icon-wink</span>
</div>
......@@ -627,7 +650,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-wondering"></span><span class="mls"> icon-wondering</span>
</div>
......@@ -640,7 +663,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-confused"></span><span class="mls"> icon-confused</span>
</div>
......@@ -653,7 +676,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-shocked"></span><span class="mls"> icon-shocked</span>
</div>
......@@ -666,7 +689,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-evil"></span><span class="mls"> icon-evil</span>
</div>
......@@ -679,7 +702,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-angry"></span><span class="mls"> icon-angry</span>
</div>
......@@ -692,7 +715,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-cool"></span><span class="mls"> icon-cool</span>
</div>
......@@ -705,7 +728,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-grin"></span><span class="mls"> icon-grin</span>
</div>
......@@ -718,7 +741,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-info"></span><span class="mls"> icon-info</span>
</div>
......@@ -731,7 +754,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-notification"></span><span class="mls"> icon-notification</span>
</div>
......@@ -744,7 +767,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-warning"></span><span class="mls"> icon-warning</span>
</div>
......@@ -757,7 +780,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-spell-check"></span><span class="mls"> icon-spell-check</span>
</div>
......@@ -770,7 +793,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-volume-high"></span><span class="mls"> icon-volume-high</span>
</div>
......@@ -783,7 +806,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-volume-medium"></span><span class="mls"> icon-volume-medium</span>
</div>
......@@ -796,7 +819,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-volume-low"></span><span class="mls"> icon-volume-low</span>
</div>
......@@ -809,7 +832,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-volume-mute"></span><span class="mls"> icon-volume-mute</span>
</div>
......@@ -822,7 +845,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-volume-mute2"></span><span class="mls"> icon-volume-mute2</span>
</div>
......@@ -835,7 +858,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-volume-decrease"></span><span class="mls"> icon-volume-decrease</span>
</div>
......@@ -848,7 +871,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-volume-increase"></span><span class="mls"> icon-volume-increase</span>
</div>
......@@ -861,7 +884,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-bold"></span><span class="mls"> icon-bold</span>
</div>
......@@ -874,7 +897,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-underline"></span><span class="mls"> icon-underline</span>
</div>
......@@ -887,7 +910,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-italic"></span><span class="mls"> icon-italic</span>
</div>
......@@ -900,7 +923,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-strikethrough"></span><span class="mls"> icon-strikethrough</span>
</div>
......@@ -913,7 +936,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-newtab"></span><span class="mls"> icon-newtab</span>
</div>
......@@ -926,7 +949,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-youtube"></span><span class="mls"> icon-youtube</span>
</div>
......@@ -939,7 +962,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-close"></span><span class="mls"> icon-close</span>
</div>
......@@ -952,7 +975,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-blocked"></span><span class="mls"> icon-blocked</span>
</div>
......@@ -965,7 +988,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-cancel-circle"></span><span class="mls"> icon-cancel-circle</span>
</div>
......@@ -978,7 +1001,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-minus"></span><span class="mls"> icon-minus</span>
</div>
......@@ -991,7 +1014,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-plus"></span><span class="mls"> icon-plus</span>
</div>
......@@ -1004,7 +1027,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-checkbox-checked"></span><span class="mls"> icon-checkbox-checked</span>
</div>
......@@ -1017,7 +1040,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-checkbox-unchecked"></span><span class="mls"> icon-checkbox-unchecked</span>
</div>
......@@ -1030,7 +1053,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-checkbox-partial"></span><span class="mls"> icon-checkbox-partial</span>
</div>
......@@ -1043,7 +1066,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-radio-checked"></span><span class="mls"> icon-radio-checked</span>
</div>
......@@ -1056,7 +1079,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-radio-unchecked"></span><span class="mls"> icon-radio-unchecked</span>
</div>
......@@ -1069,7 +1092,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-info2"></span><span class="mls"> icon-info2</span>
</div>
......@@ -1082,7 +1105,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-newspaper"></span><span class="mls"> icon-newspaper</span>
</div>
......@@ -1095,7 +1118,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-image"></span><span class="mls"> icon-image</span>
</div>
......@@ -1108,7 +1131,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-offline"></span><span class="mls"> icon-offline</span>
</div>
......@@ -1121,7 +1144,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-busy"></span><span class="mls"> icon-busy</span>
</div>
......
......@@ -12,6 +12,8 @@
<glyph unicode="&#x2364;" d="M256-32c141.385 0 256 114.615 256 256s-114.615 256-256 256-256-114.615-256-256 114.615-256 256-256zM256 432c114.875 0 208-93.125 208-208s-93.125-208-208-208-208 93.125-208 208 93.125 208 208 208zM192 128c0 35.346 28.654 64 64 64s64-28.654 64-64c0-35.346-28.654-64-64-64s-64 28.654-64 64zM320 304c0-26.51 14.327-48 32-48s32 21.49 32 48c0 26.51-14.327 48-32 48s-32-21.49-32-48zM128 304c0-26.51 14.327-48 32-48s32 21.49 32 48c0 26.51-14.327 48-32 48s-32-21.49-32-48z" />
<glyph unicode="&#x2368;" d="M256-32c141.385 0 256 114.615 256 256s-114.615 256-256 256-256-114.615-256-256 114.615-256 256-256zM256 432c114.875 0 208-93.125 208-208s-93.125-208-208-208-208 93.125-208 208 93.125 208 208 208zM128 320c0-17.673 14.327-32 32-32s32 14.327 32 32c0 17.673-14.327 32-32 32s-32-14.327-32-32zM320 320c0-17.673 14.327-32 32-32s32 14.327 32 32c0 17.673-14.327 32-32 32s-32-14.327-32-32zM363.053 160h32.432c4.623-36.253-16.226-72.265-51.979-85.28-41.452-15.088-87.45 6.358-102.54 47.808-9.054 24.872-36.653 37.741-61.524 28.686-22.781-8.294-35.478-32.149-30.494-55.212h-32.43c-4.621 36.254 16.225 72.264 51.978 85.28 41.452 15.089 87.451-6.358 102.541-47.807 9.052-24.874 36.653-37.741 61.522-28.686 22.781 8.292 35.478 32.149 30.494 55.211z" />
<glyph unicode="&#x2369;" d="M256-32c141.385 0 256 114.615 256 256s-114.615 256-256 256-256-114.615-256-256 114.615-256 256-256zM256 432c114.875 0 208-93.125 208-208s-93.125-208-208-208-208 93.125-208 208 93.125 208 208 208zM372.87 179.19l11.244-38.388-218.504-64.001-11.244 38.388zM128 320c0 17.673 14.327 32 32 32s32-14.327 32-32c0-17.673-14.327-32-32-32s-32 14.327-32 32zM320 320c0 17.673 14.327 32 32 32s32-14.327 32-32c0-17.673-14.327-32-32-32s-32 14.327-32 32z" />
<glyph unicode="&#x25ba;" d="M96 416l320-192-320-192z" />
<glyph unicode="&#x25bc;" d="M448 384l-192-320-192 320z" />
<glyph unicode="&#x25d9;" d="M490.594 399.946c-71.816 10.325-151.166 16.054-234.593 16.054-83.43 0-162.778-5.729-234.597-16.054-13.765-53.863-21.404-113.375-21.404-175.946 0-62.57 7.639-122.083 21.404-175.945 71.819-10.326 151.168-16.055 234.597-16.055 83.427 0 162.776 5.729 234.593 16.055 13.766 53.862 21.406 113.375 21.406 175.945 0 62.571-7.64 122.083-21.406 175.946zM192.001 128v192l160-96-160-96z" />
<glyph unicode="&#x25fb;" d="M256 384c-27.466 0-53.994-4.331-78.847-12.871-23.356-8.027-44.153-19.372-61.814-33.722-33.107-26.899-51.339-61.492-51.339-97.407 0-20.149 5.594-39.689 16.626-58.076 11.376-18.96 28.491-36.293 49.494-50.126 15.178-9.996 25.39-25.974 28.088-43.947 0.9-5.992 1.464-12.044 1.685-18.062 3.735 3.097 7.375 6.423 10.94 9.988 12.077 12.076 28.39 18.745 45.251 18.745 2.684 0 5.381-0.168 8.078-0.512 10.487-1.333 21.199-2.010 31.838-2.010 27.467 0 53.994 4.33 78.847 12.871 23.356 8.027 44.153 19.372 61.814 33.722 33.107 26.898 51.339 61.492 51.339 97.407s-18.232 70.508-51.339 97.407c-17.661 14.349-38.458 25.695-61.814 33.722-24.853 8.54-51.38 12.871-78.847 12.871zM256 448v0c141.385 0 256-93.125 256-208s-114.615-208-256-208c-13.578 0-26.905 0.867-39.912 2.522-54.989-54.989-120.625-64.85-184.088-66.298v13.458c34.268 16.789 64 47.37 64 82.318 0 4.877-0.379 9.665-1.082 14.348-57.898 38.132-94.918 96.377-94.918 161.652 0 114.875 114.615 208 256 208z" />
<glyph unicode="&#x25fc;" d="M256 448c141.385 0 256-93.125 256-208s-114.615-208-256-208c-13.578 0-26.905 0.867-39.912 2.522-54.989-54.989-120.625-64.85-184.088-66.298v13.458c34.268 16.789 64 47.37 64 82.318 0 4.877-0.379 9.665-1.082 14.348-57.898 38.132-94.918 96.377-94.918 161.652 0 114.875 114.615 208 256 208z" />
......@@ -31,6 +33,7 @@
<glyph unicode="&#x270e;" d="M432 480c44.182 0 80-35.817 80-80 0-18.010-5.955-34.629-16-48l-32-32-112 112 32 32c13.371 10.045 29.989 16 48 16zM32 112l-32-144 144 32 296 296-112 112-296-296zM357.789 298.211l-224-224-27.578 27.578 224 224 27.578-27.578z" />
<glyph unicode="&#x270f;" d="M480 352c17.673 0 32 14.327 32 32v64h-64v32h-416c-17.6 0-32-14.399-32-32v-448c0-17.6 14.398-32 32-32h416v128h32c17.673 0 32 14.327 32 32v64h-64v32h32c17.673 0 32 14.327 32 32v64h-64v32h32zM288 351.835c35.255 0 63.835-28.58 63.835-63.835s-28.58-63.835-63.835-63.835c-35.255 0-63.835 28.58-63.835 63.835s28.58 63.835 63.835 63.835zM128 0h-32v448h32v-448zM384 96h-192v32c0 35.347 28.654 64 64 64v0h64c35.348 0 64-28.653 64-64v-32z" />
<glyph unicode="&#x2710;" d="M449.18 448h-385.18v-64h-46.82c-8.8 0-17.18-6.264-17.18-15.064v-32c0-8.8 8.38-16.936 17.18-16.936h46.82v-32h-46.82c-8.8 0-17.18-6.264-17.18-15.064v-32c0-8.8 8.38-16.936 17.18-16.936h46.82v-32h-46.82c-8.8 0-17.18-6.264-17.18-15.064v-32c0-8.799 8.38-16.936 17.18-16.936h46.82v-32h-46.82c-8.8 0-17.18-6.264-17.18-15.064v-32c0-8.8 8.38-16.936 17.18-16.936h46.82v-64h385.18c17.674 0 30.82 15.263 30.82 32.936v416c0 17.673-13.146 31.064-30.82 31.064zM160 0h-64v32h17.18c8.8 0 14.82 8.136 14.82 16.936v32c0 8.801-6.021 15.064-14.82 15.064h-17.18v32h17.18c8.8 0 14.82 8.136 14.82 16.936v32c0 8.801-6.021 15.064-14.82 15.064h-17.18v32h17.18c8.8 0 14.82 8.136 14.82 16.936v32c0 8.801-6.021 15.064-14.82 15.064h-17.18v32h17.18c8.8 0 14.82 8.136 14.82 16.936v32c0 8.801-6.020 15.064-14.82 15.064h-17.18v32h64v-416z" />
<glyph unicode="&#x2713;" d="M432 416l-240-240-112 112-80-80 192-192 320 320z" />
<glyph unicode="&#x2715;" d="M507.331 68.67c-0.002 0.002-0.004 0.004-0.006 0.005l-155.322 155.325 155.322 155.325c0.002 0.002 0.004 0.003 0.006 0.005 1.672 1.673 2.881 3.627 3.656 5.708 2.123 5.688 0.912 12.341-3.662 16.915l-73.373 73.373c-4.574 4.573-11.225 5.783-16.914 3.66-2.080-0.775-4.035-1.984-5.709-3.655 0-0.002-0.002-0.003-0.004-0.005l-155.324-155.326-155.324 155.325c-0.002 0.002-0.003 0.003-0.005 0.005-1.673 1.671-3.627 2.88-5.707 3.655-5.69 2.124-12.341 0.913-16.915-3.66l-73.374-73.374c-4.574-4.574-5.784-11.226-3.661-16.914 0.776-2.080 1.985-4.036 3.656-5.708 0.002-0.001 0.003-0.003 0.005-0.005l155.325-155.324-155.325-155.326c-0.001-0.002-0.003-0.003-0.004-0.005-1.671-1.673-2.88-3.627-3.657-5.707-2.124-5.688-0.913-12.341 3.661-16.915l73.374-73.373c4.575-4.574 11.226-5.784 16.915-3.661 2.080 0.776 4.035 1.985 5.708 3.656 0.001 0.002 0.003 0.003 0.005 0.005l155.324 155.325 155.324-155.325c0.002-0.001 0.004-0.003 0.006-0.004 1.674-1.672 3.627-2.881 5.707-3.657 5.689-2.123 12.342-0.913 16.914 3.661l73.373 73.374c4.574 4.574 5.785 11.227 3.662 16.915-0.776 2.080-1.985 4.034-3.657 5.707z" />
<glyph unicode="&#x2718;" d="M0 224c0-141.385 114.615-256 256-256s256 114.615 256 256-114.614 256-256 256c-141.385 0-256-114.615-256-256zM448 224c0-36.618-10.256-70.84-28.044-99.956l-263.911 263.912c29.115 17.789 63.337 28.044 99.955 28.044 106.038 0 192-85.961 192-192zM64 224c0 36.618 10.256 70.839 28.045 99.956l263.911-263.912c-29.117-17.789-63.338-28.044-99.956-28.044-106.038 0-192 85.961-192 192z" />
<glyph unicode="&#x271a;" d="M496 288h-176v176c0 8.836-7.164 16-16 16h-96c-8.836 0-16-7.164-16-16v-176h-176c-8.836 0-16-7.164-16-16v-96c0-8.836 7.164-16 16-16h176v-176c0-8.836 7.164-16 16-16h96c8.836 0 16 7.164 16 16v176h176c8.836 0 16 7.164 16 16v96c0 8.836-7.164 16-16 16z" />
......@@ -92,5 +95,4 @@
<glyph unicode="&#xe058;" d="M256 480c-141.385 0-256-114.615-256-256s114.615-256 256-256 256 114.615 256 256-114.615 256-256 256zM384 306.745l-82.744-82.745 82.744-82.744v-45.256h-45.256l-82.744 82.744-82.745-82.744h-45.255v45.256l82.745 82.744-82.745 82.745v45.255h45.255l82.745-82.745 82.744 82.745h45.256v-45.255z" />
<glyph unicode="&#xe059;" d="M256 480c-141.385 0-256-114.615-256-256s114.615-256 256-256 256 114.615 256 256-114.615 256-256 256zM224 384h64v-64h-64v64zM320 64h-128v32h32v128h-32v32h96v-160h32v-32z" />
<glyph unicode="&#xe05a;" d="M0 272v-96c0-8.836 7.164-16 16-16h480c8.836 0 16 7.164 16 16v96c0 8.836-7.164 16-16 16h-480c-8.836 0-16-7.164-16-16z" />
<glyph unicode="&#xe600;" d="M432 416l-240-240-112 112-80-80 192-192 320 320z" />
</font></defs></svg>
\ No newline at end of file
{
"IcoMoonType": "selection",
"icons": [
{
"icon": {
"paths": [
"M192 128l640 384-640 384z"
],
"tags": [
"play",
"media control",
"audio"
],
"grid": 16
},
"properties": {
"order": 89,
"id": 451,
"prevSize": 32,
"code": 9658,
"name": "closed",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 266
},
{
"icon": {
"paths": [
"M896 192l-384 640-384-640z"
],
"tags": [
"opened"
],
"grid": 16
},
"properties": {
"id": 266,
"order": 88,
"prevSize": 32,
"code": 9660,
"name": "opened",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 267
},
{
"icon": {
"paths": [
......@@ -19,12 +63,12 @@
"order": 1,
"id": 85,
"prevSize": 32,
"code": 58880,
"code": 10003,
"name": "checkmark",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 0
"setIdx": 1,
"iconIdx": 88
},
{
"icon": {
......@@ -37,7 +81,7 @@
"building"
],
"defaultCode": 57344,
"grid": 0
"grid": 16
},
"properties": {
"id": 0,
......@@ -47,8 +91,8 @@
"name": "home",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 1
"setIdx": 1,
"iconIdx": 89
},
{
"icon": {
......@@ -63,7 +107,7 @@
"note"
],
"defaultCode": 9998,
"grid": 0
"grid": 16
},
"properties": {
"id": 1,
......@@ -73,8 +117,8 @@
"name": "pencil",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 2
"setIdx": 1,
"iconIdx": 90
},
{
"icon": {
......@@ -88,7 +132,7 @@
"image"
],
"defaultCode": 57347,
"grid": 0
"grid": 16
},
"properties": {
"id": 2,
......@@ -98,8 +142,8 @@
"name": "camera",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 3
"setIdx": 1,
"iconIdx": 91
},
{
"icon": {
......@@ -114,7 +158,7 @@
"movie"
],
"defaultCode": 9750,
"grid": 0
"grid": 16
},
"properties": {
"id": 3,
......@@ -124,8 +168,8 @@
"name": "camera2",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 4
"setIdx": 1,
"iconIdx": 92
},
{
"icon": {
......@@ -138,18 +182,18 @@
"movie"
],
"defaultCode": 9689,
"grid": 0
"grid": 16
},
"properties": {
"id": 4,
"order": 6,
"prevSize": 32,
"code": 9689,
"name": "play",
"name": "play22",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 5
"setIdx": 1,
"iconIdx": 93
},
{
"icon": {
......@@ -163,7 +207,7 @@
"sound"
],
"defaultCode": 9835,
"grid": 0
"grid": 16
},
"properties": {
"id": 5,
......@@ -173,8 +217,8 @@
"name": "music",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 6
"setIdx": 1,
"iconIdx": 94
},
{
"icon": {
......@@ -189,7 +233,7 @@
"audio"
],
"defaultCode": 9836,
"grid": 0
"grid": 16
},
"properties": {
"id": 6,
......@@ -199,8 +243,8 @@
"name": "headphones",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 7
"setIdx": 1,
"iconIdx": 95
},
{
"icon": {
......@@ -215,7 +259,7 @@
"call"
],
"defaultCode": 9743,
"grid": 0
"grid": 16
},
"properties": {
"id": 7,
......@@ -225,8 +269,8 @@
"name": "phone",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 8
"setIdx": 1,
"iconIdx": 96
},
{
"icon": {
......@@ -241,7 +285,7 @@
"call"
],
"defaultCode": 9742,
"grid": 0
"grid": 16
},
"properties": {
"id": 8,
......@@ -251,8 +295,8 @@
"name": "phone-hang-up",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 9
"setIdx": 1,
"iconIdx": 97
},
{
"icon": {
......@@ -265,7 +309,7 @@
"contacts"
],
"defaultCode": 9999,
"grid": 0
"grid": 16
},
"properties": {
"id": 9,
......@@ -275,8 +319,8 @@
"name": "address-book",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 10
"setIdx": 1,
"iconIdx": 98
},
{
"icon": {
......@@ -290,7 +334,7 @@
"journal"
],
"defaultCode": 10000,
"grid": 0
"grid": 16
},
"properties": {
"id": 10,
......@@ -300,8 +344,8 @@
"name": "notebook",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 11
"setIdx": 1,
"iconIdx": 99
},
{
"icon": {
......@@ -316,7 +360,7 @@
"letter"
],
"defaultCode": 9993,
"grid": 0
"grid": 16
},
"properties": {
"id": 11,
......@@ -326,8 +370,8 @@
"name": "envelop",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 12
"setIdx": 1,
"iconIdx": 100
},
{
"icon": {
......@@ -339,7 +383,7 @@
"pin"
],
"defaultCode": 57362,
"grid": 0
"grid": 16
},
"properties": {
"id": 12,
......@@ -349,8 +393,8 @@
"name": "pushpin",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 13
"setIdx": 1,
"iconIdx": 101
},
{
"icon": {
......@@ -364,7 +408,7 @@
"talk"
],
"defaultCode": 9724,
"grid": 0
"grid": 16
},
"properties": {
"id": 13,
......@@ -374,8 +418,8 @@
"name": "bubble",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 14
"setIdx": 1,
"iconIdx": 102
},
{
"icon": {
......@@ -389,7 +433,7 @@
"talk"
],
"defaultCode": 9723,
"grid": 0
"grid": 16
},
"properties": {
"id": 14,
......@@ -399,8 +443,8 @@
"name": "bubble2",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 15
"setIdx": 1,
"iconIdx": 103
},
{
"icon": {
......@@ -415,7 +459,7 @@
"talk"
],
"defaultCode": 57365,
"grid": 0
"grid": 16
},
"properties": {
"id": 15,
......@@ -425,8 +469,8 @@
"name": "bubbles",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 16
"setIdx": 1,
"iconIdx": 104
},
{
"icon": {
......@@ -441,7 +485,7 @@
"talk"
],
"defaultCode": 57366,
"grid": 0
"grid": 16
},
"properties": {
"id": 16,
......@@ -451,8 +495,8 @@
"name": "bubbles2",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 17
"setIdx": 1,
"iconIdx": 105
},
{
"icon": {
......@@ -467,7 +511,7 @@
"talk"
],
"defaultCode": 57367,
"grid": 0
"grid": 16
},
"properties": {
"id": 17,
......@@ -477,8 +521,8 @@
"name": "bubbles3",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 18
"setIdx": 1,
"iconIdx": 106
},
{
"icon": {
......@@ -494,7 +538,7 @@
"member"
],
"defaultCode": 57370,
"grid": 0
"grid": 16
},
"properties": {
"id": 18,
......@@ -504,8 +548,8 @@
"name": "user",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 19
"setIdx": 1,
"iconIdx": 107
},
{
"icon": {
......@@ -521,7 +565,7 @@
"community"
],
"defaultCode": 57371,
"grid": 0
"grid": 16
},
"properties": {
"id": 19,
......@@ -531,8 +575,8 @@
"name": "users",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 20
"setIdx": 1,
"iconIdx": 108
},
{
"icon": {
......@@ -544,7 +588,7 @@
"ldquo"
],
"defaultCode": 57373,
"grid": 0
"grid": 16
},
"properties": {
"id": 20,
......@@ -554,8 +598,8 @@
"name": "quotes-left",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 21
"setIdx": 1,
"iconIdx": 109
},
{
"icon": {
......@@ -570,7 +614,7 @@
"wheel"
],
"defaultCode": 8987,
"grid": 0
"grid": 16
},
"properties": {
"id": 21,
......@@ -580,8 +624,8 @@
"name": "spinner",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 22
"setIdx": 1,
"iconIdx": 110
},
{
"icon": {
......@@ -595,7 +639,7 @@
"find"
],
"defaultCode": 57377,
"grid": 0
"grid": 16
},
"properties": {
"id": 22,
......@@ -605,8 +649,8 @@
"name": "search",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 23
"setIdx": 1,
"iconIdx": 111
},
{
"icon": {
......@@ -622,7 +666,7 @@
"options"
],
"defaultCode": 57378,
"grid": 0
"grid": 16
},
"properties": {
"id": 23,
......@@ -632,8 +676,8 @@
"name": "cogs",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 24
"setIdx": 1,
"iconIdx": 112
},
{
"icon": {
......@@ -650,7 +694,7 @@
"fix"
],
"defaultCode": 57380,
"grid": 0
"grid": 16
},
"properties": {
"id": 24,
......@@ -660,8 +704,8 @@
"name": "wrench",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 25
"setIdx": 1,
"iconIdx": 113
},
{
"icon": {
......@@ -673,7 +717,7 @@
"lock"
],
"defaultCode": 57381,
"grid": 0
"grid": 16
},
"properties": {
"id": 25,
......@@ -683,8 +727,8 @@
"name": "unlocked",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 26
"setIdx": 1,
"iconIdx": 114
},
{
"icon": {
......@@ -698,7 +742,7 @@
"encrypted"
],
"defaultCode": 57382,
"grid": 0
"grid": 16
},
"properties": {
"id": 26,
......@@ -708,8 +752,8 @@
"name": "lock",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 27
"setIdx": 1,
"iconIdx": 115
},
{
"icon": {
......@@ -723,7 +767,7 @@
"encrypted"
],
"defaultCode": 57383,
"grid": 0
"grid": 16
},
"properties": {
"id": 27,
......@@ -733,8 +777,8 @@
"name": "lock2",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 28
"setIdx": 1,
"iconIdx": 116
},
{
"icon": {
......@@ -750,7 +794,7 @@
"sign in"
],
"defaultCode": 57384,
"grid": 0
"grid": 16
},
"properties": {
"id": 28,
......@@ -760,8 +804,8 @@
"name": "key",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 29
"setIdx": 1,
"iconIdx": 117
},
{
"icon": {
......@@ -777,7 +821,7 @@
"sign in"
],
"defaultCode": 57385,
"grid": 0
"grid": 16
},
"properties": {
"id": 29,
......@@ -787,8 +831,8 @@
"name": "key2",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 30
"setIdx": 1,
"iconIdx": 118
},
{
"icon": {
......@@ -802,7 +846,7 @@
"reduce"
],
"defaultCode": 57386,
"grid": 0
"grid": 16
},
"properties": {
"id": 30,
......@@ -812,8 +856,8 @@
"name": "zoomout",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 31
"setIdx": 1,
"iconIdx": 119
},
{
"icon": {
......@@ -826,7 +870,7 @@
"scale"
],
"defaultCode": 57387,
"grid": 0
"grid": 16
},
"properties": {
"id": 31,
......@@ -836,8 +880,8 @@
"name": "zoomin",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 32
"setIdx": 1,
"iconIdx": 120
},
{
"icon": {
......@@ -854,7 +898,7 @@
"options"
],
"defaultCode": 57391,
"grid": 0
"grid": 16
},
"properties": {
"id": 32,
......@@ -864,8 +908,8 @@
"name": "cog",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 33
"setIdx": 1,
"iconIdx": 121
},
{
"icon": {
......@@ -881,7 +925,7 @@
"dispose"
],
"defaultCode": 57389,
"grid": 0
"grid": 16
},
"properties": {
"id": 33,
......@@ -891,8 +935,8 @@
"name": "remove",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 34
"setIdx": 1,
"iconIdx": 122
},
{
"icon": {
......@@ -908,7 +952,7 @@
"dispose"
],
"defaultCode": 57390,
"grid": 0
"grid": 16
},
"properties": {
"id": 34,
......@@ -918,8 +962,8 @@
"name": "remove2",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 35
"setIdx": 1,
"iconIdx": 123
},
{
"icon": {
......@@ -933,7 +977,7 @@
"visit"
],
"defaultCode": 57392,
"grid": 0
"grid": 16
},
"properties": {
"id": 35,
......@@ -943,8 +987,8 @@
"name": "eye",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 36
"setIdx": 1,
"iconIdx": 124
},
{
"icon": {
......@@ -962,7 +1006,7 @@
"private"
],
"defaultCode": 57393,
"grid": 0
"grid": 16
},
"properties": {
"id": 36,
......@@ -972,8 +1016,8 @@
"name": "eye-blocked",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 37
"setIdx": 1,
"iconIdx": 125
},
{
"icon": {
......@@ -985,7 +1029,7 @@
"paperclip"
],
"defaultCode": 57394,
"grid": 0
"grid": 16
},
"properties": {
"id": 37,
......@@ -995,8 +1039,8 @@
"name": "attachment",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 38
"setIdx": 1,
"iconIdx": 126
},
{
"icon": {
......@@ -1011,7 +1055,7 @@
"sphere"
],
"defaultCode": 57395,
"grid": 0
"grid": 16
},
"properties": {
"id": 38,
......@@ -1021,8 +1065,8 @@
"name": "globe",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 39
"setIdx": 1,
"iconIdx": 127
},
{
"icon": {
......@@ -1036,7 +1080,7 @@
"favorite"
],
"defaultCode": 10084,
"grid": 0
"grid": 16
},
"properties": {
"id": 39,
......@@ -1046,8 +1090,8 @@
"name": "heart",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 40
"setIdx": 1,
"iconIdx": 128
},
{
"icon": {
......@@ -1061,7 +1105,7 @@
"face"
],
"defaultCode": 9787,
"grid": 0
"grid": 16
},
"properties": {
"id": 40,
......@@ -1071,8 +1115,8 @@
"name": "happy",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 41
"setIdx": 1,
"iconIdx": 129
},
{
"icon": {
......@@ -1087,7 +1131,7 @@
"vote up"
],
"defaultCode": 9757,
"grid": 0
"grid": 16
},
"properties": {
"id": 41,
......@@ -1097,8 +1141,8 @@
"name": "thumbs-up",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 42
"setIdx": 1,
"iconIdx": 130
},
{
"icon": {
......@@ -1111,7 +1155,7 @@
"face"
],
"defaultCode": 9786,
"grid": 0
"grid": 16
},
"properties": {
"id": 42,
......@@ -1121,8 +1165,8 @@
"name": "smiley",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 43
"setIdx": 1,
"iconIdx": 131
},
{
"icon": {
......@@ -1136,7 +1180,7 @@
"face"
],
"defaultCode": 57400,
"grid": 0
"grid": 16
},
"properties": {
"id": 43,
......@@ -1146,8 +1190,8 @@
"name": "tongue",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 44
"setIdx": 1,
"iconIdx": 132
},
{
"icon": {
......@@ -1161,7 +1205,7 @@
"face"
],
"defaultCode": 9785,
"grid": 0
"grid": 16
},
"properties": {
"id": 44,
......@@ -1171,8 +1215,8 @@
"name": "sad",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 45
"setIdx": 1,
"iconIdx": 133
},
{
"icon": {
......@@ -1186,7 +1230,7 @@
"face"
],
"defaultCode": 57402,
"grid": 0
"grid": 16
},
"properties": {
"id": 45,
......@@ -1196,8 +1240,8 @@
"name": "wink",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 46
"setIdx": 1,
"iconIdx": 134
},
{
"icon": {
......@@ -1212,7 +1256,7 @@
"question"
],
"defaultCode": 9065,
"grid": 0
"grid": 16
},
"properties": {
"id": 46,
......@@ -1222,8 +1266,8 @@
"name": "wondering",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 47
"setIdx": 1,
"iconIdx": 135
},
{
"icon": {
......@@ -1238,7 +1282,7 @@
"bewildered"
],
"defaultCode": 9064,
"grid": 0
"grid": 16
},
"properties": {
"id": 47,
......@@ -1248,8 +1292,8 @@
"name": "confused",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 48
"setIdx": 1,
"iconIdx": 136
},
{
"icon": {
......@@ -1263,7 +1307,7 @@
"face"
],
"defaultCode": 9060,
"grid": 0
"grid": 16
},
"properties": {
"id": 48,
......@@ -1273,8 +1317,8 @@
"name": "shocked",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 49
"setIdx": 1,
"iconIdx": 137
},
{
"icon": {
......@@ -1288,7 +1332,7 @@
"face"
],
"defaultCode": 9759,
"grid": 0
"grid": 16
},
"properties": {
"id": 49,
......@@ -1298,8 +1342,8 @@
"name": "evil",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 50
"setIdx": 1,
"iconIdx": 138
},
{
"icon": {
......@@ -1314,7 +1358,7 @@
"rage"
],
"defaultCode": 57407,
"grid": 0
"grid": 16
},
"properties": {
"id": 50,
......@@ -1324,8 +1368,8 @@
"name": "angry",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 51
"setIdx": 1,
"iconIdx": 139
},
{
"icon": {
......@@ -1339,7 +1383,7 @@
"face"
],
"defaultCode": 57408,
"grid": 0
"grid": 16
},
"properties": {
"id": 51,
......@@ -1349,8 +1393,8 @@
"name": "cool",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 52
"setIdx": 1,
"iconIdx": 140
},
{
"icon": {
......@@ -1364,7 +1408,7 @@
"face"
],
"defaultCode": 57409,
"grid": 0
"grid": 16
},
"properties": {
"id": 52,
......@@ -1374,8 +1418,8 @@
"name": "grin",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 53
"setIdx": 1,
"iconIdx": 141
},
{
"icon": {
......@@ -1387,7 +1431,7 @@
"information"
],
"defaultCode": 9056,
"grid": 0
"grid": 16
},
"properties": {
"id": 53,
......@@ -1397,8 +1441,8 @@
"name": "info",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 54
"setIdx": 1,
"iconIdx": 142
},
{
"icon": {
......@@ -1413,7 +1457,7 @@
"exclamation"
],
"defaultCode": 57375,
"grid": 0
"grid": 16
},
"properties": {
"id": 54,
......@@ -1423,8 +1467,8 @@
"name": "notification",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 55
"setIdx": 1,
"iconIdx": 143
},
{
"icon": {
......@@ -1436,7 +1480,7 @@
"sign"
],
"defaultCode": 9888,
"grid": 0
"grid": 16
},
"properties": {
"id": 55,
......@@ -1446,8 +1490,8 @@
"name": "warning",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 56
"setIdx": 1,
"iconIdx": 144
},
{
"icon": {
......@@ -1459,7 +1503,7 @@
"correct"
],
"defaultCode": 57413,
"grid": 0
"grid": 16
},
"properties": {
"id": 56,
......@@ -1469,8 +1513,8 @@
"name": "spell-check",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 57
"setIdx": 1,
"iconIdx": 145
},
{
"icon": {
......@@ -1485,7 +1529,7 @@
"audio"
],
"defaultCode": 57414,
"grid": 0
"grid": 16
},
"properties": {
"id": 57,
......@@ -1495,8 +1539,8 @@
"name": "volume-high",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 58
"setIdx": 1,
"iconIdx": 146
},
{
"icon": {
......@@ -1510,7 +1554,7 @@
"audio"
],
"defaultCode": 57415,
"grid": 0
"grid": 16
},
"properties": {
"id": 58,
......@@ -1520,8 +1564,8 @@
"name": "volume-medium",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 59
"setIdx": 1,
"iconIdx": 147
},
{
"icon": {
......@@ -1535,7 +1579,7 @@
"audio"
],
"defaultCode": 57416,
"grid": 0
"grid": 16
},
"properties": {
"id": 59,
......@@ -1545,8 +1589,8 @@
"name": "volume-low",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 60
"setIdx": 1,
"iconIdx": 148
},
{
"icon": {
......@@ -1561,7 +1605,7 @@
"mute"
],
"defaultCode": 57417,
"grid": 0
"grid": 16
},
"properties": {
"id": 60,
......@@ -1571,8 +1615,8 @@
"name": "volume-mute",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 61
"setIdx": 1,
"iconIdx": 149
},
{
"icon": {
......@@ -1587,7 +1631,7 @@
"mute"
],
"defaultCode": 57418,
"grid": 0
"grid": 16
},
"properties": {
"id": 61,
......@@ -1597,8 +1641,8 @@
"name": "volume-mute2",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 62
"setIdx": 1,
"iconIdx": 150
},
{
"icon": {
......@@ -1612,7 +1656,7 @@
"audio"
],
"defaultCode": 57419,
"grid": 0
"grid": 16
},
"properties": {
"id": 62,
......@@ -1622,8 +1666,8 @@
"name": "volume-decrease",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 63
"setIdx": 1,
"iconIdx": 151
},
{
"icon": {
......@@ -1637,7 +1681,7 @@
"audio"
],
"defaultCode": 57420,
"grid": 0
"grid": 16
},
"properties": {
"id": 63,
......@@ -1647,8 +1691,8 @@
"name": "volume-increase",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 64
"setIdx": 1,
"iconIdx": 152
},
{
"icon": {
......@@ -1660,7 +1704,7 @@
"wysiwyg"
],
"defaultCode": 57421,
"grid": 0
"grid": 16
},
"properties": {
"id": 64,
......@@ -1670,8 +1714,8 @@
"name": "bold",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 65
"setIdx": 1,
"iconIdx": 153
},
{
"icon": {
......@@ -1683,7 +1727,7 @@
"wysiwyg"
],
"defaultCode": 57422,
"grid": 0
"grid": 16
},
"properties": {
"id": 65,
......@@ -1693,8 +1737,8 @@
"name": "underline",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 66
"setIdx": 1,
"iconIdx": 154
},
{
"icon": {
......@@ -1706,7 +1750,7 @@
"wysiwyg"
],
"defaultCode": 57423,
"grid": 0
"grid": 16
},
"properties": {
"id": 66,
......@@ -1716,8 +1760,8 @@
"name": "italic",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 67
"setIdx": 1,
"iconIdx": 155
},
{
"icon": {
......@@ -1729,7 +1773,7 @@
"wysiwyg"
],
"defaultCode": 57424,
"grid": 0
"grid": 16
},
"properties": {
"id": 67,
......@@ -1739,8 +1783,8 @@
"name": "strikethrough",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 68
"setIdx": 1,
"iconIdx": 156
},
{
"icon": {
......@@ -1756,7 +1800,7 @@
"blank"
],
"defaultCode": 57427,
"grid": 0
"grid": 16
},
"properties": {
"id": 68,
......@@ -1766,8 +1810,8 @@
"name": "newtab",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 69
"setIdx": 1,
"iconIdx": 157
},
{
"icon": {
......@@ -1779,7 +1823,7 @@
"social"
],
"defaultCode": 57429,
"grid": 0
"grid": 16
},
"properties": {
"id": 69,
......@@ -1789,8 +1833,8 @@
"name": "youtube",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 70
"setIdx": 1,
"iconIdx": 158
},
{
"icon": {
......@@ -1805,7 +1849,7 @@
"cross"
],
"defaultCode": 10005,
"grid": 0
"grid": 16
},
"properties": {
"id": 70,
......@@ -1815,8 +1859,8 @@
"name": "close",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 71
"setIdx": 1,
"iconIdx": 159
},
{
"icon": {
......@@ -1832,7 +1876,7 @@
"banned"
],
"defaultCode": 10008,
"grid": 0
"grid": 16
},
"properties": {
"id": 71,
......@@ -1842,8 +1886,8 @@
"name": "blocked",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 72
"setIdx": 1,
"iconIdx": 160
},
{
"icon": {
......@@ -1857,7 +1901,7 @@
"delete"
],
"defaultCode": 57432,
"grid": 0
"grid": 16
},
"properties": {
"id": 72,
......@@ -1867,8 +1911,8 @@
"name": "cancel-circle",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 73
"setIdx": 1,
"iconIdx": 161
},
{
"icon": {
......@@ -1881,7 +1925,7 @@
"subtract"
],
"defaultCode": 57434,
"grid": 0
"grid": 16
},
"properties": {
"id": 73,
......@@ -1891,8 +1935,8 @@
"name": "minus",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 74
"setIdx": 1,
"iconIdx": 162
},
{
"icon": {
......@@ -1905,7 +1949,7 @@
"sum"
],
"defaultCode": 10010,
"grid": 0
"grid": 16
},
"properties": {
"id": 74,
......@@ -1915,8 +1959,8 @@
"name": "plus",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 75
"setIdx": 1,
"iconIdx": 163
},
{
"icon": {
......@@ -1930,7 +1974,7 @@
"selected"
],
"defaultCode": 9745,
"grid": 0
"grid": 16
},
"properties": {
"id": 75,
......@@ -1940,8 +1984,8 @@
"name": "checkbox-checked",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 76
"setIdx": 1,
"iconIdx": 164
},
{
"icon": {
......@@ -1954,7 +1998,7 @@
"square"
],
"defaultCode": 11047,
"grid": 0
"grid": 16
},
"properties": {
"id": 76,
......@@ -1964,8 +2008,8 @@
"name": "checkbox-unchecked",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 77
"setIdx": 1,
"iconIdx": 165
},
{
"icon": {
......@@ -1977,7 +2021,7 @@
"partial"
],
"defaultCode": 11048,
"grid": 0
"grid": 16
},
"properties": {
"id": 77,
......@@ -1987,8 +2031,8 @@
"name": "checkbox-partial",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 78
"setIdx": 1,
"iconIdx": 166
},
{
"icon": {
......@@ -1999,7 +2043,7 @@
"radio-checked"
],
"defaultCode": 11046,
"grid": 0
"grid": 16
},
"properties": {
"id": 78,
......@@ -2009,8 +2053,8 @@
"name": "radio-checked",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 79
"setIdx": 1,
"iconIdx": 167
},
{
"icon": {
......@@ -2022,7 +2066,7 @@
"circle"
],
"defaultCode": 11045,
"grid": 0
"grid": 16
},
"properties": {
"id": 79,
......@@ -2032,8 +2076,8 @@
"name": "radio-unchecked",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 80
"setIdx": 1,
"iconIdx": 168
},
{
"icon": {
......@@ -2045,7 +2089,7 @@
"information"
],
"defaultCode": 57433,
"grid": 0
"grid": 16
},
"properties": {
"id": 80,
......@@ -2055,8 +2099,8 @@
"name": "info2",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 81
"setIdx": 1,
"iconIdx": 169
},
{
"icon": {
......@@ -2068,7 +2112,7 @@
"paper"
],
"defaultCode": 57345,
"grid": 0
"grid": 16
},
"properties": {
"id": 81,
......@@ -2078,8 +2122,8 @@
"name": "newspaper",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 82
"setIdx": 1,
"iconIdx": 170
},
{
"icon": {
......@@ -2093,7 +2137,7 @@
"graphic"
],
"defaultCode": 11028,
"grid": 0
"grid": 16
},
"properties": {
"id": 82,
......@@ -2103,8 +2147,8 @@
"name": "image",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 83
"setIdx": 1,
"iconIdx": 171
},
{
"icon": {
......@@ -2115,7 +2159,7 @@
"offline"
],
"defaultCode": 57346,
"grid": 0
"grid": 16
},
"properties": {
"id": 83,
......@@ -2125,8 +2169,8 @@
"name": "offline",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 84
"setIdx": 1,
"iconIdx": 172
},
{
"icon": {
......@@ -2137,7 +2181,7 @@
"busy"
],
"defaultCode": 57348,
"grid": 0
"grid": 16
},
"properties": {
"id": 84,
......@@ -2147,8 +2191,8 @@
"name": "busy",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 85
"setIdx": 1,
"iconIdx": 173
}
],
"height": 1024,
......@@ -2172,6 +2216,7 @@
"imagePref": {},
"historySize": 100,
"showCodes": true,
"search": ""
"search": "",
"gridSize": 16
}
}
\ No newline at end of file
@font-face {
font-family: 'icomoon';
src:url('fonts/icomoon.eot?7poj4t');
src:url('fonts/icomoon.eot?#iefix7poj4t') format('embedded-opentype'),
url('fonts/icomoon.woff?7poj4t') format('woff'),
url('fonts/icomoon.ttf?7poj4t') format('truetype'),
url('fonts/icomoon.svg?7poj4t#icomoon') format('svg');
src:url('fonts/icomoon.eot?-m2p76k');
src:url('fonts/icomoon.eot?#iefix-m2p76k') format('embedded-opentype'),
url('fonts/icomoon.woff?-m2p76k') format('woff'),
url('fonts/icomoon.ttf?-m2p76k') format('truetype'),
url('fonts/icomoon.svg?-m2p76k#icomoon') format('svg');
font-weight: normal;
font-style: normal;
}
......@@ -23,8 +23,14 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-closed:before {
content: "\25ba";
}
.icon-opened:before {
content: "\25bc";
}
.icon-checkmark:before {
content: "\e600";
content: "\2713";
}
.icon-home:before {
content: "\e000";
......@@ -38,7 +44,7 @@
.icon-camera2:before {
content: "\2616";
}
.icon-play:before {
.icon-play22:before {
content: "\25d9";
}
.icon-music:before {
......
......@@ -226,13 +226,14 @@
allow_otr: true,
auto_list_rooms: false,
auto_subscribe: false,
bosh_service_url: 'https://bind.opkode.com', // Please use this connection manager only for testing purposes
bosh_service_url: 'https://bind.conversejs.org', // Please use this connection manager only for testing purposes
debug: true ,
hide_muc_server: false,
i18n: locales['en'], // Refer to ./locale/locales.js to see which locales are supported
prebind: false,
show_controlbox_by_default: true,
xhr_user_search: false,
roster_groups: true
});
});
</script>
......
......@@ -46,8 +46,14 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-closed:before {
content: "\25ba";
}
.icon-opened:before {
content: "\25bc";
}
.icon-checkmark:before {
content: "\e600";
content: "\2713";
}
#conversejs .icon-home:before {
content: "\e000";
......@@ -897,8 +903,7 @@ dl.add-converse-contact {
}
#conversejs .controlbox-pane dt {
margin: 0;
padding-top: 0.5em;
padding: 3px;
}
#conversejs .chatroom-form-container {
......@@ -939,11 +944,11 @@ dl.add-converse-contact {
float: right;
}
#conversejs #converse-roster dd.odd {
#converse-roster dd.odd {
background-color: #DCEAC5; /* Make this difference */
}
#conversejs #converse-roster dd.current-xmpp-contact span {
#converse-roster dd.current-xmpp-contact span {
font-size: 16px;
float: left;
color: rgb(79, 79, 79);
......@@ -954,8 +959,8 @@ dl.add-converse-contact {
float: right;
}
#conversejs #converse-roster dd a,
#conversejs #converse-roster dd span {
#converse-roster dd a,
#converse-roster dd span {
text-shadow: 0 1px 0 rgba(250, 250, 250, 1);
display: inline-block;
overflow: hidden;
......@@ -963,11 +968,20 @@ dl.add-converse-contact {
text-overflow: ellipsis;
}
#conversejs #converse-roster dd span {
#conversejs #converse-roster span.req-contact-name {
width: 65%;
}
#conversejs #converse-roster span.pending-contact-name,
#conversejs #converse-roster a.open-chat {
width: 80%;
}
#converse-roster dd span {
padding: 0 5px 0 0;
}
#conversejs #converse-roster {
#converse-roster {
overflow-y: auto;
overflow-x: hidden;
width: 100%;
......@@ -977,6 +991,10 @@ dl.add-converse-contact {
height: ~"calc(100% - 70px)";
}
#converse-roster .group-toggle {
color: #666;
}
#conversejs dd.available-chatroom {
overflow-x: hidden;
text-overflow: ellipsis;
......@@ -988,8 +1006,8 @@ dl.add-converse-contact {
width: 148px;
}
#conversejs #available-chatrooms dt,
#conversejs #converse-roster dt {
#available-chatrooms dt,
#converse-roster dt {
font-weight: normal;
font-size: 13px;
color: #666;
......@@ -998,7 +1016,7 @@ dl.add-converse-contact {
text-shadow: 0 1px 0 rgba(250, 250, 250, 1);
}
#conversejs #converse-roster dt {
#converse-roster dt {
display: none;
}
......@@ -1060,6 +1078,12 @@ dl.add-converse-contact {
line-height: 16px;
}
#conversejs .group-toggle {
display: block;
width: 100%;
}
#conversejs .roster-group:hover,
#conversejs dd.available-chatroom:hover,
#conversejs #converse-roster dd:hover {
background-color: #eee;
......@@ -1081,10 +1105,6 @@ dl.add-converse-contact {
display: inline-block;
}
#conversejs #converse-roster a.open-chat {
width: 80%;
}
#conversejs .chatbox,
#conversejs .chatroom {
height: 25px;
......@@ -1462,6 +1482,10 @@ input.custom-xmpp-status {
background-color: #bed6e5;
}
#conversejs .chatbox .dropdown dd.search-xmpp ul li:hover {
background-color: #bed6e5;
}
#conversejs .xmpp-status-menu li a {
width: 100%;
}
......
<!DOCTYPE html>
<html lang="en">
<head>
<title id="pageTitle">Converse.js: Mockup</title>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="description" content="Converse.js: Mockup" />
<link type="text/css" href="../css/theme.css" rel="stylesheet" media="screen" />
<link type="text/css" href="../css/converse.css" rel="stylesheet" media="screen" />
<script src="../components/jquery/dist/jquery.min.js"></script>
</head>
<body id="page-top" data-spy="scroll" data-target=".navbar-custom">
<!-- HEADER -->
<div id="header_wrap" class="outer">
<header class="inner">
<h1 id="project_title"><a href="http://conversejs.org">Converse.js</a></h1>
<h2 id="project_tagline">Static Mockup</h2>
</header>
</div>
<div id="conversejs">
<a id="toggle-controlbox" href="#" class="toggle-controlbox">
<span class="conn-feedback">Toggle Chat</span>
<span style="display: none" id="online-count">(0)</span>
</a>
<div id="controlbox" class="chatbox" style="opacity: 1; display: inline;">
<div class="box-flyout">
<div class="dragresize dragresize-tm"></div>
<div class="chat-head controlbox-head">
<ul id="controlbox-tabs">
<li><a class="current" href="#login">Sign in</a></li>
</ul>
<a class="close-chatbox-button icon-close"></a>
</div>
<div id="login-dialog">
<form id="converse-login">
<label>XMPP/Jabber Username:</label><input type="text" id="jid">
<label>Password:</label><input type="password" id="password">
<input class="login-submit" type="submit" value="Log In">
</form>
<span class="conn-feedback"></span>
</div>
</div>
</div>
<div id="controlbox" class="chatbox" style="opacity: 1; display: inline;">
<div class="box-flyout">
<div class="dragresize dragresize-tm"></div>
<div class="chat-head controlbox-head">
<ul id="controlbox-tabs">
<li><a class="s current" href="#users">Contacts</a></li>
<li><a class="s" href="#chatrooms">Rooms</a></li>
</ul>
<a class="close-chatbox-button icon-close"></a>
</div>
<div id="users" class="controlbox-pane" style="display: block;">
<form class="set-xmpp-status" action="" method="post">
<span id="xmpp-status-holder">
<dl id="target" class="dropdown">
<dt id="fancy-xmpp-status-select" class="fancy-dropdown">
<div class="xmpp-status">
<a class="choose-xmpp-status online" data-value="I am online" href="#" title="Click to change your chat status">
<span class="icon-online"></span>
I am online
</a>
<a class="change-xmpp-status-message icon-pencil" href="#" title="Click here to write a custom status message"></a>
</div>
</dt>
<dd>
<ul style="display: none;" class="xmpp-status-menu">
<li>
<a href="#" class="online" data-value="online">
<span class="icon-online"></span>
Online</a>
</li>
<li>
<a href="#" class="dnd" data-value="dnd">
<span class="icon-dnd"></span>
Busy</a>
</li>
<li>
<a href="#" class="away" data-value="away">
<span class="icon-away"></span>
Away</a>
</li>
<li>
<a href="#" class="offline" data-value="offline">
<span class="icon-offline"></span>
Offline</a>
</li>
</ul>
</dd>
</dl>
</span>
</form>
<dl class="add-converse-contact dropdown">
<dt id="xmpp-contact-search" class="fancy-dropdown">
<a class="toggle-xmpp-contact-form" href="#" title="Click to add new chat contacts">
<span class="icon-plus"></span>
Add a contact
</a>
</dt>
<dd class="search-xmpp" style="display:none">
<ul>
<li>
<form class="add-xmpp-contact">
<input type="text" name="identifier" class="username" placeholder="Contact username">
<button type="submit">Add</button>
</form>
</li>
<li></li>
</ul>
</dd>
</dl>
<dl id="converse-roster" style="display: block;">
<dt class="roster-group" style="display: block;">
<a href="#" data-group="Colleagues" class="group-toggle icon-opened" title="Click to hide these contacts">Colleagues</a>
</dt>
<dd class="online current-xmpp-contact">
<a class="open-chat" title="Click to chat with this contact" href="#">
<span class="icon-online" title="This contact is online"></span>
Victor Matfield
</a>
<a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
</dd>
<dd class="away current-xmpp-contact">
<a class="open-chat" title="Click to chat with this contact" href="#">
<span class="icon-away" title="this contact is away"></span>
William Winterbottom
</a>
<a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
</dd>
<dd class="dnd current-xmpp-contact">
<a class="open-chat" title="Click to chat with this contact" href="#">
<span class="icon-dnd" title="This contact is busy"></span>
Gary Teichmann
</a>
<a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
</dd>
<dt class="roster-group" style="display: block;">
<a href="#" data-group="Family" class="group-toggle icon-opened" title="Click to hide these contacts">Family</a>
</dt>
<dd class="away current-xmpp-contact">
<a class="open-chat" title="Click to chat with this contact" href="#">
<span class="icon-away" title="this contact is away"></span>
Allan Donald
</a>
<a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
</dd>
<dd class="offline current-xmpp-contact">
<a class="open-chat" title="Click to chat with this contact" href="#">
<span class="icon-offline" title="This contact is offline"></span>
Corné Krige
</a>
<a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
</dd>
<dt class="roster-group" style="display: block;">
<a href="#" data-group="Friends" class="group-toggle icon-opened" title="Click to hide these contacts">Friends</a>
</dt>
<dd class="online current-xmpp-contact">
<a class="open-chat" title="Click to chat with this contact" href="#">
<span class="icon-online" title="This contact is online"></span>
John Smit
</a>
<a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
</dd>
<dd class="online current-xmpp-contact">
<a class="open-chat" title="Click to chat with this contact" href="#">
<span class="icon-online" title="This contact is online"></span>
Bakkies Botha
</a>
<a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
</dd>
<dt class="roster-group" style="display: block;">
<a href="#" class="group-toggle icon-opened" title="Click to hide these contacts">Ungrouped</a>
</dt>
<dd class="online current-xmpp-contact">
<a class="open-chat" title="Click to chat with this contact" href="#">
<span class="icon-online" title="This contact is online"></span>
James Small
</a>
<a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
</dd>
<dt id="xmpp-contact-requests" style="display: block;">
<a href="#" class="group-toggle icon-opened" title="Click to hide these contacts">Contact Requests</a>
</dt>
<dd class="offline requesting-xmpp-contact">
<span class="req-contact-name">Bob Skinstad</span>
<span class="request-actions">
<a class="accept-xmpp-request icon-checkmark" title="Click here to accept this contact's request" href="#"></a>
<a class="decline-xmpp-request icon-close" title="Click here to decline this contact's request" href="#"></a>
</span>
</dd>
<dd class="offline requesting-xmpp-contact">
<span class="req-contact-name">André Vos</span>
<span class="request-actions">
<a class="accept-xmpp-request icon-checkmark" title="Click here to accept this contact's request" href="#"></a>
<a class="decline-xmpp-request icon-close" title="Click here to decline this contact's request" href="#"></a>
</span>
</dd>
<dt id="pending-xmpp-contacts" style="display: block;">
<a href="#" class="group-toggle icon-opened" title="Click to hide these contacts">Pending Contacts</a>
</dt>
<dd class="offline pending-xmpp-contact"><span class="pending-contact-name">Rassie Erasmus</span>
<a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
</dd>
<dd class="offline pending-xmpp-contact"><span class="pending-contact-name">Victor Matfield</span>
<a class="remove-xmpp-contact icon-remove" title="Click to remove this contact" href="#"></a>
</dd>
</dl>
</div>
<div id="chatrooms" style="display: none;">
<form class="add-chatroom" action="" method="post">
<input type="text" name="chatroom" class="new-chatroom-name" placeholder="Room name">
<input type="text" name="nick" class="new-chatroom-nick" placeholder="Nickname">
<input type="text" name="server" class="new-chatroom-server" placeholder="Server">
<input type="submit" name="join" value="Join">
<input type="button" name="show" id="show-rooms" value="Show rooms" style="display: inline-block;">
</form>
<dl id="available-chatrooms">
<dt>Rooms on conference.opkode.im</dt>
<dd class="available-chatroom">
<a class="open-room"
data-room-jid="converse.js@conference.opkode.im"
title="Click to open this room" href="#">Special chatroom with a long name (2)</a>
<a class="room-info icon-room-info"
data-room-jid="converse.js@conference.opkode.im"
title="Show more information on this room" href="#">&nbsp;</a>
<div class="room-info">
<p class="room-info"><strong>Description:</strong></p>
<p class="room-info"><strong>Occupants:</strong> 2</p>
<p class="room-info"><strong>Features:</strong> </p>
<ul>
<li class="room-info">Moderated</li><li class="room-info">Open room</li>
<li class="room-info">Permanent room</li><li class="room-info">Public</li>
<li class="room-info">Semi-anonymous</li>
<li class="room-info">Requires authentication <span class="icon-lock"></span></li>
<p></p>
</ul>
</div>
</dd>
</dl>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$('a[href=#chatrooms]').click(function (ev) {
switchTab(ev);
});
$('a[href=#users]').click(function (ev) {
switchTab(ev);
});
$("a.choose-xmpp-status").click(function (ev) {
ev.preventDefault();
$(ev.target).parent().parent().siblings('dd').find('ul').toggle('fast');
});
$("a.change-xmpp-status-message").click(function (ev) {
ev.preventDefault();
var form = ''+
'<form id="set-custom-xmpp-status">' +
'<input type="text" class="custom-xmpp-status"I am online"'+
'placeholder="I am online"/>' +
'<button type="submit">Save</button>' +
'</form>';
$(ev.target).closest('.xmpp-status').replaceWith(form);
$(ev.target).closest('.custom-xmpp-status').focus().focus();
});
$('.toggle-xmpp-contact-form').click(function (ev) {
ev.preventDefault();
$(ev.target).parent().parent().find('.search-xmpp').toggle('fast', function () {
if ($(this).is(':visible')) {
$(this).find('input.username').focus();
}
});
});
var switchTab = function (ev) {
ev.preventDefault();
var $tab = $(ev.target),
$sibling = $tab.parent().siblings('li').children('a'),
$tab_panel = $($tab.attr('href')),
$sibling_panel = $($sibling.attr('href'));
$sibling_panel.hide();
$sibling.removeClass('current');
$tab.addClass('current');
$tab_panel.show();
}
$(function() {
$('.group-toggle').click(function(ev) {
ev.preventDefault();
var $el = $(ev.target);
$el.parent().nextUntil('dt').slideToggle();
if ($el.hasClass("icon-opened")) {
$el.removeClass("icon-opened").addClass("icon-closed");
} else {
$el.removeClass("icon-closed").addClass("icon-opened");
}
});
$('.close-chatbox-button').click(function(ev) {
var $grandparent = $(ev.target).parent().parent().parent();
$grandparent.hide(300, function () {
// Webkit fix
document.getElementById('conversejs').style.display = 'none';
document.getElementById('conversejs').offsetHeight; // no need to store this anywhere, the reference is enough
document.getElementById('conversejs').style.display = 'block';
});
});
$('.toggle-chatbox-button').click(function(ev) {
var $grandparent = $(ev.target).parent().parent().parent();
$grandparent.fadeOut('fast');
});
// Clickable Dropdown
$('.toggle-otr').click(function(e) {
$('.toggle-otr ul').slideToggle(200);
e.stopPropagation();
});
$('.toggle-smiley').click(function(e) {
$(e.target).find('ul').slideToggle(200);
e.stopPropagation();
});
$(document).click(function() {
if ($('.toggle-otr ul').is(':visible')) {
$('.toggle-otr ul', this).slideUp(200);
}
if ($('.toggle-smiley ul').is(':visible')) {
$('.toggle-smiley ul', this).slideUp(200);
}
});
});
});
</script>
</html>
......@@ -13,7 +13,7 @@
runs(function () {
utils.closeAllChatBoxes();
utils.removeControlBox();
converse.roster.browserStorage._clear();
utils.clearBrowserStorage();
utils.initConverse();
utils.createContacts();
utils.openControlBox();
......@@ -22,23 +22,19 @@
});
it("is created when you click on a roster item", $.proxy(function () {
var i, $el, click, jid, view, chatboxview;
var i, $el, click, jid, chatboxview;
// openControlBox was called earlier, so the controlbox is
// visible, but no other chat boxes have been created.
expect(this.chatboxes.length).toEqual(1);
spyOn(this.chatboxviews, 'trimChats');
expect($("#conversejs .chatbox").length).toBe(1); // Controlbox is open
var online_contacts = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact').find('a.open-chat');
var online_contacts = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact').find('a.open-chat');
for (i=0; i<online_contacts.length; i++) {
$el = $(online_contacts[i]);
jid = $el.text().replace(/ /g,'.').toLowerCase() + '@localhost';
view = this.rosterview.get(jid);
spyOn(view, 'openChat').andCallThrough();
view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
$el.click();
chatboxview = this.chatboxviews.get(jid);
expect(view.openChat).toHaveBeenCalled();
expect(this.chatboxes.length).toEqual(i+2);
expect(this.chatboxviews.trimChats).toHaveBeenCalled();
// Check that new chat boxes are created to the left of the
......@@ -49,7 +45,7 @@
}, converse));
it("can be trimmed to conserve space", $.proxy(function () {
var i, $el, click, jid, key, view, chatbox, chatboxview;
var i, $el, click, jid, key, chatbox, chatboxview;
// openControlBox was called earlier, so the controlbox is
// visible, but no other chat boxes have been created.
var trimmed_chatboxes = converse.minimized_chats;
......@@ -60,11 +56,10 @@
expect($("#conversejs .chatbox").length).toBe(1); // Controlbox is open
// Test that they can be trimmed
var online_contacts = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact').find('a.open-chat');
var online_contacts = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact').find('a.open-chat');
for (i=0; i<online_contacts.length; i++) {
$el = $(online_contacts[i]);
jid = $el.text().replace(/ /g,'.').toLowerCase() + '@localhost';
view = this.rosterview.get(jid);
$el.click();
expect(this.chatboxviews.trimChats).toHaveBeenCalled();
......@@ -96,7 +91,7 @@
it("is focused if its already open and you click on its corresponding roster item", $.proxy(function () {
var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
var i, $el, click, jid, view, chatboxview, chatbox;
var i, $el, click, jid, chatboxview, chatbox;
// openControlBox was called earlier, so the controlbox is
// visible, but no other chat boxes have been created.
expect(this.chatboxes.length).toEqual(1);
......@@ -105,11 +100,7 @@
spyOn(chatboxview, 'focus');
$el = this.rosterview.$el.find('a.open-chat:contains("'+chatbox.get('fullname')+'")');
jid = $el.text().replace(/ /g,'.').toLowerCase() + '@localhost';
view = this.rosterview.get(jid);
spyOn(view, 'openChat').andCallThrough();
view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
$el.click();
expect(view.openChat).toHaveBeenCalled();
expect(this.chatboxes.length).toEqual(2);
expect(chatboxview.focus).toHaveBeenCalled();
}, converse));
......
......@@ -7,6 +7,23 @@
}
);
} (this, function (mock, utils) {
var checkHeaderToggling = function ($header) {
var $toggle = $header.find('a.group-toggle');
expect($header.css('display')).toEqual('block');
expect($header.nextUntil('dt', 'dd').length === $header.nextUntil('dt', 'dd:visible').length).toBeTruthy();
expect($toggle.hasClass('icon-closed')).toBeFalsy();
expect($toggle.hasClass('icon-opened')).toBeTruthy();
$toggle.click();
expect($toggle.hasClass('icon-closed')).toBeTruthy();
expect($toggle.hasClass('icon-opened')).toBeFalsy();
expect($header.nextUntil('dt', 'dd').length === $header.nextUntil('dt', 'dd:hidden').length).toBeTruthy();
$toggle.click();
expect($toggle.hasClass('icon-closed')).toBeFalsy();
expect($toggle.hasClass('icon-opened')).toBeTruthy();
expect($header.nextUntil('dt', 'dd').length === $header.nextUntil('dt', 'dd:visible').length).toBeTruthy();
};
describe("The Control Box", $.proxy(function (mock, utils) {
beforeEach(function () {
runs(function () {
......@@ -107,264 +124,416 @@
describe("The Contacts Roster", $.proxy(function (mock, utils) {
describe("Pending Contacts", $.proxy(function () {
beforeEach($.proxy(function () {
runs(function () {
describe("A Roster Group", $.proxy(function () {
beforeEach(function () {
converse.roster_groups = true;
});
afterEach(function () {
converse.roster_groups = false;
});
function _clearContacts () {
utils.clearBrowserStorage();
converse.rosterview.model.reset();
utils.createContacts('pending').openControlBox();
}
it("can be used to organize existing contacts", $.proxy(function () {
_clearContacts();
var i=0, j=0, t;
spyOn(converse, 'emit');
spyOn(this.rosterview, 'update').andCallThrough();
converse.rosterview.render();
utils.createContacts('pending');
utils.createContacts('requesting');
var groups = {
'colleagues': 3,
'friends & acquaintences': 3,
'Family': 4,
'ænemies': 3,
'Ungrouped': 2
};
_.each(_.keys(groups), $.proxy(function (name) {
j = i;
for (i=j; i<j+groups[name]; i++) {
this.roster.create({
jid: mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost',
subscription: 'both',
ask: null,
groups: name === 'ungrouped'? [] : [name],
fullname: mock.cur_names[i]
});
waits(50);
runs(function () {
utils.openContactsPanel();
}
}, converse));
// Check that the groups appear alphabetically and that
// requesting and pending contacts are last.
var group_titles = $.map(this.rosterview.$el.find('dt'), function (o) { return $(o).text().trim(); });
expect(group_titles).toEqual([
"colleagues",
"Family",
"friends & acquaintences",
"ænemies",
"Ungrouped",
"Contact requests",
"Pending contacts"
]);
// Check that usernames appear alphabetically per group
_.each(_.keys(groups), $.proxy(function (name) {
var $contacts = this.rosterview.$('dt.roster-group[data-group="'+name+'"]').nextUntil('dt', 'dd');
var names = $.map($contacts, function (o) { return $(o).text().trim(); });
expect(names).toEqual(_.clone(names).sort());
}, converse));
}, converse));
it("can share contacts with other roster groups", $.proxy(function () {
_clearContacts();
var i=0, j=0, t;
spyOn(converse, 'emit');
spyOn(this.rosterview, 'update').andCallThrough();
converse.rosterview.render();
var groups = ['colleagues', 'friends'];
for (i=0; i<mock.cur_names.length; i++) {
this.roster.create({
jid: mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost',
subscription: 'both',
ask: null,
groups: groups,
fullname: mock.cur_names[i]
});
}
// Check that usernames appear alphabetically per group
_.each(groups, $.proxy(function (name) {
var $contacts = this.rosterview.$('dt.roster-group[data-group="'+name+'"]').nextUntil('dt', 'dd');
var names = $.map($contacts, function (o) { return $(o).text().trim(); });
expect(names).toEqual(_.clone(names).sort());
expect(names.length).toEqual(mock.cur_names.length);
}, converse));
}, converse));
it("remembers whether it is closed or opened", $.proxy(function () {
var i=0, j=0, t;
var groups = {
'colleagues': 3,
'friends & acquaintences': 3,
'Ungrouped': 2
};
_.each(_.keys(groups), $.proxy(function (name) {
j = i;
for (i=j; i<j+groups[name]; i++) {
this.roster.create({
jid: mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost',
subscription: 'both',
ask: null,
groups: name === 'ungrouped'? [] : [name],
fullname: mock.cur_names[i]
});
}
}, converse));
var view = this.rosterview.get('colleagues');
var $toggle = view.$el.find('a.group-toggle');
expect(view.model.get('state')).toBe('opened');
$toggle.click();
expect(view.model.get('state')).toBe('closed');
$toggle.click();
expect(view.model.get('state')).toBe('opened');
}, converse));
}, converse));
it("do not have a heading if there aren't any", $.proxy(function () {
describe("Pending Contacts", $.proxy(function () {
function _clearContacts () {
utils.clearBrowserStorage();
converse.rosterview.model.reset();
expect(this.rosterview.$el.find('dt#pending-xmpp-contacts').css('display')).toEqual('none');
}, converse));
}
it("will have their own heading once they have been added", $.proxy(function () {
expect(this.rosterview.$el.find('dt#pending-xmpp-contacts').css('display')).toEqual('block');
function _addContacts () {
_clearContacts();
// Must be initialized, so that render is called and documentFragment set up.
utils.createContacts('pending').openControlBox().openContactsPanel();
}
it("can be collapsed under their own header", $.proxy(function () {
_addContacts();
checkHeaderToggling.apply(this, [this.rosterview.get('Pending contacts').$el]);
}, converse));
it("can be added to the roster", $.proxy(function () {
converse.rosterview.model.reset(); // We want to manually create users so that we can spy
_clearContacts();
spyOn(converse, 'emit');
spyOn(this.rosterview, 'render').andCallThrough();
spyOn(this.rosterview, 'update').andCallThrough();
runs($.proxy(function () {
this.roster.create({
jid: mock.pend_names[0].replace(/ /g,'.').toLowerCase() + '@localhost',
subscription: 'none',
ask: 'subscribe',
fullname: mock.pend_names[0],
is_last: true
fullname: mock.pend_names[0]
});
}, converse));
waits(300);
runs($.proxy(function () {
expect(converse.emit).toHaveBeenCalledWith('rosterViewUpdated');
expect(this.rosterview.$el.is(':visible')).toEqual(true);
expect(this.rosterview.render).toHaveBeenCalled();
expect(this.rosterview.update).toHaveBeenCalled();
}, converse));
}, converse));
it("can be removed by the user", $.proxy(function () {
var jid = mock.pend_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
var view = this.rosterview.get(jid);
_addContacts();
var name = mock.pend_names[0];
var jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';
spyOn(window, 'confirm').andReturn(true);
spyOn(converse, 'emit');
spyOn(this.connection.roster, 'remove').andCallThrough();
spyOn(this.connection.roster, 'unauthorize');
spyOn(this.rosterview.model, 'remove').andCallThrough();
view.$el.find('.remove-xmpp-contact').click();
converse.rosterview.$el.find(".pending-contact-name:contains('"+name+"')")
.siblings('.remove-xmpp-contact').click();
expect(window.confirm).toHaveBeenCalled();
expect(this.connection.roster.remove).toHaveBeenCalled();
expect(this.connection.roster.unauthorize).toHaveBeenCalled();
expect(this.rosterview.model.remove).toHaveBeenCalled();
// The element must now be detached from the DOM.
expect(view.$el.closest('html').length).toBeFalsy();
expect(converse.emit).toHaveBeenCalledWith('rosterViewUpdated');
expect(converse.rosterview.$el.find(".pending-contact-name:contains('"+name+"')").length).toEqual(0);
}, converse));
it("do not have a header if there aren't any", $.proxy(function () {
var name = mock.pend_names[0];
_clearContacts();
spyOn(window, 'confirm').andReturn(true);
this.roster.create({
jid: name.replace(/ /g,'.').toLowerCase() + '@localhost',
subscription: 'none',
ask: 'subscribe',
fullname: name
});
expect(this.rosterview.get('Pending contacts').$el.is(':visible')).toEqual(true);
converse.rosterview.$el.find(".pending-contact-name:contains('"+name+"')")
.siblings('.remove-xmpp-contact').click();
expect(window.confirm).toHaveBeenCalled();
expect(this.rosterview.get('Pending contacts').$el.is(':visible')).toEqual(false);
}, converse));
it("will lose their own heading once the last one has been removed", $.proxy(function () {
var view;
it("will lose their own header once the last one has been removed", $.proxy(function () {
_addContacts();
var name;
spyOn(window, 'confirm').andReturn(true);
for (i=0; i<mock.pend_names.length; i++) {
view = this.rosterview.get(mock.pend_names[i].replace(/ /g,'.').toLowerCase() + '@localhost');
view.$el.find('.remove-xmpp-contact').click();
name = mock.pend_names[i];
converse.rosterview.$el.find(".pending-contact-name:contains('"+name+"')")
.siblings('.remove-xmpp-contact').click();
}
expect(this.rosterview.$el.find('dt#pending-xmpp-contacts').is(':visible')).toBeFalsy();
}, converse));
it("can be added to the roster and they will be sorted alphabetically", $.proxy(function () {
converse.rosterview.model.reset(); // We want to manually create users so that we can spy
var i, t, is_last;
_clearContacts();
var i, t;
spyOn(converse, 'emit');
spyOn(this.rosterview, 'render').andCallThrough();
spyOn(this.rosterview, 'update').andCallThrough();
for (i=0; i<mock.pend_names.length; i++) {
is_last = i===(mock.pend_names.length-1);
this.roster.create({
jid: mock.pend_names[i].replace(/ /g,'.').toLowerCase() + '@localhost',
subscription: 'none',
ask: 'subscribe',
fullname: mock.pend_names[i],
is_last: is_last
fullname: mock.pend_names[i]
});
expect(this.rosterview.render).toHaveBeenCalled();
expect(converse.emit).toHaveBeenCalledWith('rosterViewUpdated');
expect(this.rosterview.update).toHaveBeenCalled();
}
// Check that they are sorted alphabetically
t = this.rosterview.$el.find('dt#pending-xmpp-contacts').siblings('dd.pending-xmpp-contact').find('span').text();
t = this.rosterview.get('Pending contacts').$el.siblings('dd.pending-xmpp-contact').find('span').text();
expect(t).toEqual(mock.pend_names.slice(0,i+1).sort().join(''));
}
}, converse));
}, converse));
describe("Existing Contacts", $.proxy(function () {
beforeEach($.proxy(function () {
runs(function () {
function _clearContacts () {
utils.clearBrowserStorage();
converse.rosterview.model.reset();
utils.createContacts().openControlBox();
});
waits(50);
runs(function () {
utils.openContactsPanel();
});
}
var _addContacts = function () {
_clearContacts();
utils.createContacts().openControlBox().openContactsPanel();
};
it("can be collapsed under their own header", $.proxy(function () {
_addContacts();
checkHeaderToggling.apply(this, [this.rosterview.$el.find('dt.roster-group')]);
}, converse));
it("do not have a heading if there aren't any", $.proxy(function () {
converse.rosterview.model.reset();
expect(this.rosterview.$el.find('dt#xmpp-contacts').css('display')).toEqual('none');
it("will be hidden when appearing under a collapsed group", $.proxy(function () {
_addContacts();
this.rosterview.$el.find('dt.roster-group').find('a.group-toggle').click();
var name = "Max Mustermann";
var jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';
converse.roster.create({
ask: null,
fullname: name,
jid: jid,
requesting: false,
subscription: 'both'
});
var view = this.rosterview.get('My contacts').get(jid);
expect(view.$el.is(':visible')).toBe(false);
}, converse));
it("can be added to the roster and they will be sorted alphabetically", $.proxy(function () {
_clearContacts();
var i, t;
converse.rosterview.model.reset();
spyOn(converse, 'emit');
spyOn(this.rosterview, 'render').andCallThrough();
spyOn(this.rosterview, 'update').andCallThrough();
for (i=0; i<mock.cur_names.length; i++) {
this.roster.create({
jid: mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost',
subscription: 'both',
ask: null,
fullname: mock.cur_names[i],
is_last: i===(mock.cur_names.length-1)
fullname: mock.cur_names[i]
});
expect(this.rosterview.render).toHaveBeenCalled();
expect(converse.emit).toHaveBeenCalledWith('rosterViewUpdated');
expect(this.rosterview.update).toHaveBeenCalled();
}
// Check that they are sorted alphabetically
t = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.offline').find('a.open-chat').text();
t = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.offline').find('a.open-chat').text();
expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join(''));
}
}, converse));
it("will have their own heading once they have been added", $.proxy(function () {
expect(this.rosterview.$el.find('dt#xmpp-contacts').css('display')).toEqual('block');
it("can be removed by the user", $.proxy(function () {
_addContacts();
var name = mock.cur_names[0];
var jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';
spyOn(window, 'confirm').andReturn(true);
spyOn(converse, 'emit');
spyOn(this.connection.roster, 'remove').andCallThrough();
spyOn(this.connection.roster, 'unauthorize');
spyOn(this.rosterview.model, 'remove').andCallThrough();
converse.rosterview.$el.find(".open-chat:contains('"+name+"')")
.siblings('.remove-xmpp-contact').click();
expect(window.confirm).toHaveBeenCalled();
expect(this.connection.roster.remove).toHaveBeenCalled();
expect(this.connection.roster.unauthorize).toHaveBeenCalled();
expect(this.rosterview.model.remove).toHaveBeenCalled();
expect(converse.rosterview.$el.find(".open-chat:contains('"+name+"')").length).toEqual(0);
}, converse));
it("do not have a header if there aren't any", $.proxy(function () {
var name = mock.cur_names[0];
_clearContacts();
spyOn(window, 'confirm').andReturn(true);
this.roster.create({
jid: name.replace(/ /g,'.').toLowerCase() + '@localhost',
subscription: 'both',
ask: null,
fullname: name
});
expect(this.rosterview.$el.find('dt.roster-group').css('display')).toEqual('block');
converse.rosterview.$el.find(".open-chat:contains('"+name+"')")
.siblings('.remove-xmpp-contact').click();
expect(window.confirm).toHaveBeenCalled();
expect(this.rosterview.$el.find('dt.roster-group').css('display')).toEqual('none');
}, converse));
it("can change their status to online and be sorted alphabetically", $.proxy(function () {
var item, view, jid, t;
_addContacts();
var jid, t;
spyOn(converse, 'emit');
spyOn(this.rosterview, 'render').andCallThrough();
spyOn(this.rosterview, 'update').andCallThrough();
for (i=0; i<mock.cur_names.length; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
view = this.rosterview.get(jid);
spyOn(view, 'render').andCallThrough();
item = view.model;
item.set('chat_status', 'online');
expect(view.render).toHaveBeenCalled();
expect(this.rosterview.render).toHaveBeenCalled();
expect(converse.emit).toHaveBeenCalledWith('rosterViewUpdated');
this.roster.get(jid).set('chat_status', 'online');
expect(this.rosterview.update).toHaveBeenCalled();
// Check that they are sorted alphabetically
t = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.online').find('a.open-chat').text();
t = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.online').find('a.open-chat').text();
expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join(''));
}
}, converse));
it("can change their status to busy and be sorted alphabetically", $.proxy(function () {
var item, view, jid, t;
_addContacts();
var jid, t;
spyOn(converse, 'emit');
spyOn(this.rosterview, 'render').andCallThrough();
spyOn(this.rosterview, 'update').andCallThrough();
for (i=0; i<mock.cur_names.length; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
view = this.rosterview.get(jid);
spyOn(view, 'render').andCallThrough();
item = view.model;
item.set('chat_status', 'dnd');
expect(view.render).toHaveBeenCalled();
expect(this.rosterview.render).toHaveBeenCalled();
expect(converse.emit).toHaveBeenCalledWith('rosterViewUpdated');
this.roster.get(jid).set('chat_status', 'dnd');
expect(this.rosterview.update).toHaveBeenCalled();
// Check that they are sorted alphabetically
t = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.dnd').find('a.open-chat').text();
t = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.dnd').find('a.open-chat').text();
expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join(''));
}
}, converse));
it("can change their status to away and be sorted alphabetically", $.proxy(function () {
var item, view, jid, t;
_addContacts();
var jid, t;
spyOn(converse, 'emit');
spyOn(this.rosterview, 'render').andCallThrough();
spyOn(this.rosterview, 'update').andCallThrough();
for (i=0; i<mock.cur_names.length; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
view = this.rosterview.get(jid);
spyOn(view, 'render').andCallThrough();
item = view.model;
item.set('chat_status', 'away');
expect(view.render).toHaveBeenCalled();
expect(this.rosterview.render).toHaveBeenCalled();
expect(converse.emit).toHaveBeenCalledWith('rosterViewUpdated');
this.roster.get(jid).set('chat_status', 'away');
expect(this.rosterview.update).toHaveBeenCalled();
// Check that they are sorted alphabetically
t = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.away').find('a.open-chat').text();
t = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.away').find('a.open-chat').text();
expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join(''));
}
}, converse));
it("can change their status to xa and be sorted alphabetically", $.proxy(function () {
var item, view, jid, t;
_addContacts();
var jid, t;
spyOn(converse, 'emit');
spyOn(this.rosterview, 'render').andCallThrough();
spyOn(this.rosterview, 'update').andCallThrough();
for (i=0; i<mock.cur_names.length; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
view = this.rosterview.get(jid);
spyOn(view, 'render').andCallThrough();
item = view.model;
item.set('chat_status', 'xa');
expect(view.render).toHaveBeenCalled();
expect(this.rosterview.render).toHaveBeenCalled();
expect(converse.emit).toHaveBeenCalledWith('rosterViewUpdated');
this.roster.get(jid).set('chat_status', 'xa');
expect(this.rosterview.update).toHaveBeenCalled();
// Check that they are sorted alphabetically
t = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.xa').find('a.open-chat').text();
t = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.xa').find('a.open-chat').text();
expect(t).toEqual(mock.cur_names.slice(0,i+1).sort().join(''));
}
}, converse));
it("can change their status to unavailable and be sorted alphabetically", $.proxy(function () {
var item, view, jid, t;
_addContacts();
var jid, t;
spyOn(converse, 'emit');
spyOn(this.rosterview, 'render').andCallThrough();
spyOn(this.rosterview, 'update').andCallThrough();
for (i=0; i<mock.cur_names.length; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
view = this.rosterview.get(jid);
spyOn(view, 'render').andCallThrough();
item = view.model;
item.set('chat_status', 'unavailable');
expect(view.render).toHaveBeenCalled();
expect(this.rosterview.render).toHaveBeenCalled();
expect(converse.emit).toHaveBeenCalledWith('rosterViewUpdated');
this.roster.get(jid).set('chat_status', 'unavailable');
expect(this.rosterview.update).toHaveBeenCalled();
// Check that they are sorted alphabetically
t = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact.unavailable').find('a.open-chat').text();
t = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact.unavailable').find('a.open-chat').text();
expect(t).toEqual(mock.cur_names.slice(0, i+1).sort().join(''));
}
}, converse));
it("are ordered according to status: online, busy, away, xa, unavailable, offline", $.proxy(function () {
_addContacts();
var i;
for (i=0; i<3; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
view = this.rosterview.get(jid);
view.model.set('chat_status', 'online');
this.roster.get(jid).set('chat_status', 'online');
}
for (i=3; i<6; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
view = this.rosterview.get(jid);
view.model.set('chat_status', 'dnd');
this.roster.get(jid).set('chat_status', 'dnd');
}
for (i=6; i<9; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
view = this.rosterview.get(jid);
view.model.set('chat_status', 'away');
this.roster.get(jid).set('chat_status', 'away');
}
for (i=9; i<12; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
view = this.rosterview.get(jid);
view.model.set('chat_status', 'xa');
this.roster.get(jid).set('chat_status', 'xa');
}
for (i=12; i<15; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
view = this.rosterview.get(jid);
view.model.set('chat_status', 'unavailable');
this.roster.get(jid).set('chat_status', 'unavailable');
}
var contacts = this.rosterview.$el.find('dd.current-xmpp-contact');
......@@ -392,6 +561,7 @@
describe("Requesting Contacts", $.proxy(function () {
beforeEach($.proxy(function () {
runs(function () {
utils.clearBrowserStorage();
converse.rosterview.model.reset();
utils.createContacts('requesting').openControlBox();
});
......@@ -401,81 +571,89 @@
});
}, converse));
it("do not have a heading if there aren't any", $.proxy(function () {
// by default the dts are hidden from css class and only later they will be hidden
// by jQuery therefore for the first check we will see if visible instead of none
converse.rosterview.model.reset();
expect(this.rosterview.$el.find('dt#xmpp-contact-requests').is(':visible')).toEqual(false);
}, converse));
it("can be added to the roster and they will be sorted alphabetically", $.proxy(function () {
converse.rosterview.model.reset(); // We want to manually create users so that we can spy
var i, children;
var names = [];
spyOn(converse, 'emit');
spyOn(this.rosterview, 'render').andCallThrough();
spyOn(this.rosterview, 'update').andCallThrough();
spyOn(this.controlboxtoggle, 'showControlBox').andCallThrough();
var addName = function (idx, item) {
if (!$(item).hasClass('request-actions')) {
names.push($(item).text().replace(/^\s+|\s+$/g, ''));
}
};
for (i=0; i<mock.req_names.length; i++) {
this.roster.create({
jid: mock.req_names[i].replace(/ /g,'.').toLowerCase() + '@localhost',
subscription: 'none',
ask: null,
requesting: true,
fullname: mock.req_names[i],
is_last: i===(mock.req_names.length-1)
fullname: mock.req_names[i]
});
expect(this.rosterview.render).toHaveBeenCalled();
// Check that they are sorted alphabetically
children = this.rosterview.$el.find('dt#xmpp-contact-requests').siblings('dd.requesting-xmpp-contact').children('span');
names = [];
children.each(function (idx, item) {
if (!$(item).hasClass('request-actions')) {
names.push($(item).text().replace(/^\s+|\s+$/g, ''));
}
});
expect(names.join('')).toEqual(mock.req_names.slice(0,i+1).sort().join(''));
expect(this.rosterview.update).toHaveBeenCalled();
// When a requesting contact is added, the controlbox must
// be opened.
expect(this.controlboxtoggle.showControlBox).toHaveBeenCalled();
expect(converse.emit).toHaveBeenCalledWith('rosterViewUpdated');
}
// Check that they are sorted alphabetically
children = this.rosterview.get('Contact requests').$el.siblings('dd.requesting-xmpp-contact').children('span');
names = [];
children.each(addName);
expect(names.join('')).toEqual(mock.req_names.slice(0,i+1).sort().join(''));
}, converse));
it("do not have a header if there aren't any", $.proxy(function () {
converse.rosterview.model.reset(); // We want to manually create users so that we can spy
var name = mock.req_names[0];
spyOn(window, 'confirm').andReturn(true);
this.roster.create({
jid: name.replace(/ /g,'.').toLowerCase() + '@localhost',
subscription: 'none',
ask: null,
requesting: true,
fullname: name
});
expect(this.rosterview.get('Contact requests').$el.is(':visible')).toEqual(true);
converse.rosterview.$el.find(".req-contact-name:contains('"+name+"')")
.siblings('.request-actions')
.find('.decline-xmpp-request').click();
expect(window.confirm).toHaveBeenCalled();
expect(this.rosterview.get('Contact requests').$el.is(':visible')).toEqual(false);
}, converse));
it("will have their own heading once they have been added", $.proxy(function () {
expect(this.rosterview.$el.find('dt#xmpp-contact-requests').css('display')).toEqual('block');
it("can be collapsed under their own header", $.proxy(function () {
checkHeaderToggling.apply(this, [this.rosterview.get('Contact requests').$el]);
}, converse));
it("can have their requests accepted by the user", $.proxy(function () {
// TODO: Testing can be more thorough here, the user is
// actually not accepted/authorized because of
// mock_connection.
var jid = mock.req_names.sort()[0].replace(/ /g,'.').toLowerCase() + '@localhost';
var view = this.rosterview.get(jid);
var name = mock.req_names.sort()[0];
var jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';
spyOn(this.connection.roster, 'authorize');
spyOn(view, 'acceptRequest').andCallThrough();
view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
var accept_button = view.$el.find('.accept-xmpp-request');
accept_button.click();
expect(view.acceptRequest).toHaveBeenCalled();
converse.rosterview.$el.find(".req-contact-name:contains('"+name+"')")
.siblings('.request-actions')
.find('.accept-xmpp-request').click();
expect(this.connection.roster.authorize).toHaveBeenCalled();
}, converse));
it("can have their requests denied by the user", $.proxy(function () {
var jid = mock.req_names.sort()[1].replace(/ /g,'.').toLowerCase() + '@localhost';
var view = this.rosterview.get(jid);
this.rosterview.model.reset();
spyOn(converse, 'emit');
spyOn(this.connection.roster, 'unauthorize');
spyOn(this.rosterview, 'removeRosterItemView').andCallThrough();
spyOn(window, 'confirm').andReturn(true);
spyOn(view, 'declineRequest').andCallThrough();
view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
var accept_button = view.$el.find('.decline-xmpp-request');
accept_button.click();
expect(view.declineRequest).toHaveBeenCalled();
utils.createContacts('requesting').openControlBox();
var name = mock.req_names.sort()[1];
var jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';
converse.rosterview.$el.find(".req-contact-name:contains('"+name+"')")
.siblings('.request-actions')
.find('.decline-xmpp-request').click();
expect(window.confirm).toHaveBeenCalled();
expect(this.rosterview.removeRosterItemView).toHaveBeenCalled();
expect(this.connection.roster.unauthorize).toHaveBeenCalled();
expect(converse.emit).toHaveBeenCalledWith('rosterViewUpdated');
// There should now be one less contact
expect(this.roster.length).toEqual(mock.req_names.length-1);
}, converse));
......@@ -483,28 +661,19 @@
describe("All Contacts", $.proxy(function () {
beforeEach($.proxy(function () {
runs(function () {
utils.clearBrowserStorage();
converse.rosterview.model.reset();
converse.rosterview.model.browserStorage._clear();
utils.createContacts('all').openControlBox();
});
waits(50);
runs(function () {
utils.openContactsPanel();
});
}, converse));
it("are saved to, and can be retrieved from, browserStorage", $.proxy(function () {
var new_attrs, old_attrs, attrs, old_roster;
var num_contacts = this.roster.length;
new_roster = new this.RosterItems();
new_roster = new this.RosterContacts();
// Roster items are yet to be fetched from browserStorage
expect(new_roster.length).toEqual(0);
new_roster.browserStorage = new Backbone.BrowserStorage.session(
b64_sha1('converse.rosteritems-dummy@localhost'));
new_roster.browserStorage = this.roster.browserStorage;
new_roster.fetch();
expect(new_roster.length).toEqual(num_contacts);
// Check that the roster items retrieved from browserStorage
......@@ -518,7 +687,6 @@
// comparison
expect(_.isEqual(new_attrs.sort(), old_attrs.sort())).toEqual(true);
}
this.rosterview.render();
}, converse));
afterEach($.proxy(function () {
......@@ -528,8 +696,7 @@
// we make some online now
for (i=0; i<5; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
view = this.rosterview.get(jid);
view.model.set('chat_status', 'online');
this.roster.get(jid).set('chat_status', 'online');
}
}, converse));
}, converse));
......
......@@ -10,7 +10,6 @@ define("converse-templates", [
"tpl!src/templates/chatrooms_tab",
"tpl!src/templates/chats_panel",
"tpl!src/templates/choose_status",
"tpl!src/templates/contacts",
"tpl!src/templates/contacts_panel",
"tpl!src/templates/contacts_tab",
"tpl!src/templates/controlbox",
......@@ -19,6 +18,7 @@ define("converse-templates", [
"tpl!src/templates/form_checkbox",
"tpl!src/templates/form_input",
"tpl!src/templates/form_select",
"tpl!src/templates/group_header",
"tpl!src/templates/info",
"tpl!src/templates/login_panel",
"tpl!src/templates/login_tab",
......@@ -35,9 +35,9 @@ define("converse-templates", [
"tpl!src/templates/roster_item",
"tpl!src/templates/select_option",
"tpl!src/templates/status_option",
"tpl!src/templates/toggle_chats",
"tpl!src/templates/toolbar",
"tpl!src/templates/trimmed_chat",
"tpl!src/templates/toggle_chats"
"tpl!src/templates/trimmed_chat"
], function () {
return {
action: arguments[0],
......@@ -51,15 +51,15 @@ define("converse-templates", [
chatrooms_tab: arguments[8],
chats_panel: arguments[9],
choose_status: arguments[10],
contacts: arguments[11],
contacts_panel: arguments[12],
contacts_tab: arguments[13],
controlbox: arguments[14],
controlbox_toggle: arguments[15],
field: arguments[16],
form_checkbox: arguments[17],
form_input: arguments[18],
form_select: arguments[19],
contacts_panel: arguments[11],
contacts_tab: arguments[12],
controlbox: arguments[13],
controlbox_toggle: arguments[14],
field: arguments[15],
form_checkbox: arguments[16],
form_input: arguments[17],
form_select: arguments[18],
group_header: arguments[19],
info: arguments[20],
login_panel: arguments[21],
login_tab: arguments[22],
......@@ -76,8 +76,8 @@ define("converse-templates", [
roster_item: arguments[33],
select_option: arguments[34],
status_option: arguments[35],
toolbar: arguments[36],
trimmed_chat: arguments[37],
toggle_chats: arguments[38]
toggle_chats: arguments[36],
toolbar: arguments[37],
trimmed_chat: arguments[38]
};
});
<dt id="xmpp-contacts">{{label_contacts}}</dt>
<a href="#" class="group-toggle icon-{{toggle_state}}" title="{{desc_group_toggle}}">{{label_group}}</a>
<span>{{fullname}}</span> <a class="remove-xmpp-contact icon-remove" title="{{desc_remove}}" href="#"></a>
<span class="pending-contact-name">{{fullname}}</span> <a class="remove-xmpp-contact icon-remove" title="{{desc_remove}}" href="#"></a>
<dt id="pending-xmpp-contacts">{{label_pending_contacts}}</dt>
<dt id="pending-xmpp-contacts"><a href="#" class="group-toggle icon-{{toggle_state}}" title="{{desc_group_toggle}}">{{label_pending_contacts}}</a></dt>
<span>{{fullname}}</span>
<span class="req-contact-name">{{fullname}}</span>
<span class="request-actions">
<a class="accept-xmpp-request icon-checkmark" title="{{desc_accept}}" href="#"></a>
<a class="decline-xmpp-request icon-close" title="{{desc_decline}}" href="#"></a>
......
<dt id="xmpp-contact-requests">{{label_contact_requests}}</dt>
<dt id="xmpp-contact-requests"><a href="#" class="group-toggle icon-{{toggle_state}}" title="{{desc_group_toggle}}">{{label_contact_requests}}</a></dt>
......@@ -80,13 +80,21 @@
var i = 0, jid, views = [];
for (i; i<amount; i++) {
jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
views[i] = converse.rosterview.get(jid).openChat(mock.event);
views[i] = converse.roster.get(jid).trigger("open");
}
return views;
};
utils.openChatBoxFor = function (jid) {
return converse.rosterview.get(jid).openChat(mock.event);
return converse.roster.get(jid).trigger("open");
};
utils.removeRosterContacts = function () {
var model;
while (converse.rosterview.model.length) {
model = converse.rosterview.model.pop();
converse.rosterview.model.remove(model);
}
};
utils.clearBrowserStorage = function () {
......@@ -129,7 +137,6 @@
converse.roster.create({
ask: ask,
fullname: names[i],
is_last: i===(names.length-1),
jid: names[i].replace(/ /g,'.').toLowerCase() + '@localhost',
requesting: requesting,
subscription: subscription
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment