Add the ability to filter contacts by chat state.
The roster filter is now also remembered across page loads.
This commit is contained in:
parent
885c553e2e
commit
8e0f8f0a6d
@ -1848,6 +1848,15 @@
|
||||
background-position: right 3px center; }
|
||||
#conversejs #converse-roster .roster-filter-group .roster-filter.onX {
|
||||
cursor: pointer; }
|
||||
#conversejs #converse-roster .roster-filter-group .state-type {
|
||||
float: left;
|
||||
border: 1px solid #999;
|
||||
font-size: calc(14px - 2px);
|
||||
height: 25px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
padding-left: 0.4em;
|
||||
width: 53%; }
|
||||
#conversejs #converse-roster .roster-filter-group .filter-type {
|
||||
display: table-cell;
|
||||
float: right;
|
||||
|
@ -2,8 +2,10 @@
|
||||
|
||||
## 1.0.0 (Unreleased)
|
||||
|
||||
- Better Sass/CSS for responsive/mobile views. [jcbrand]
|
||||
- Split converse.js up into different plugin modules. [jcbrand]
|
||||
- Better Sass/CSS for responsive/mobile views. New mobile-only build. [jcbrand]
|
||||
- Roster contacts can now be filtered by chat state and roster filters are
|
||||
remembered across page loads. [jcbrand]
|
||||
- Add support for messages with type `headline`, often used for notifications
|
||||
from the server. [jcbrand]
|
||||
- Add stanza-specific event listener `converse.listen.stanza`.
|
||||
|
@ -44,6 +44,16 @@
|
||||
.roster-filter.onX {
|
||||
cursor: pointer;
|
||||
}
|
||||
.state-type {
|
||||
float: left;
|
||||
border: 1px solid #999;
|
||||
font-size: calc(#{$font-size} - 2px);
|
||||
height: $controlbox-dropdown-height;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
padding-left: 0.4em;
|
||||
width: 53%;
|
||||
}
|
||||
.filter-type {
|
||||
display: table-cell;
|
||||
float: right;
|
||||
|
@ -138,10 +138,15 @@
|
||||
test_utils.openControlBox();
|
||||
});
|
||||
|
||||
it("will only appear when roster contacts flow over the visible area", $.proxy(function () {
|
||||
_clearContacts();
|
||||
it("will only appear when roster contacts flow over the visible area", function () {
|
||||
var $filter = converse.rosterview.$('.roster-filter');
|
||||
var names = mock.cur_names;
|
||||
runs(function () {
|
||||
_clearContacts();
|
||||
converse.rosterview.update(); // XXX: Will normally called as event handler
|
||||
});
|
||||
waits(5); // Needed, due to debounce
|
||||
runs(function () {
|
||||
expect($filter.length).toBe(1);
|
||||
expect($filter.is(':visible')).toBeFalsy();
|
||||
for (var i=0; i<names.length; i++) {
|
||||
@ -153,13 +158,18 @@
|
||||
subscription: 'both'
|
||||
});
|
||||
converse.rosterview.update(); // XXX: Will normally called as event handler
|
||||
}
|
||||
});
|
||||
waits(5); // Needed, due to debounce
|
||||
runs(function () {
|
||||
$filter = converse.rosterview.$('.roster-filter');
|
||||
if (converse.rosterview.$roster.hasScrollBar()) {
|
||||
expect($filter.is(':visible')).toBeTruthy();
|
||||
} else {
|
||||
expect($filter.is(':visible')).toBeFalsy();
|
||||
}
|
||||
}
|
||||
}, converse));
|
||||
});
|
||||
});
|
||||
|
||||
it("can be used to filter the contacts shown", function () {
|
||||
var $filter;
|
||||
@ -171,8 +181,9 @@
|
||||
$filter = converse.rosterview.$('.roster-filter');
|
||||
$roster = converse.rosterview.$roster;
|
||||
});
|
||||
waits(350); // Needed, due to debounce
|
||||
waits(5); // Needed, due to debounce in "update" method
|
||||
runs(function () {
|
||||
converse.rosterview.filter_view.delegateEvents();
|
||||
expect($roster.find('dd:visible').length).toBe(15);
|
||||
expect($roster.find('dt:visible').length).toBe(5);
|
||||
$filter.val("candice");
|
||||
@ -180,30 +191,33 @@
|
||||
expect($roster.find('dt:visible').length).toBe(5); // ditto
|
||||
$filter.trigger('keydown');
|
||||
});
|
||||
waits(350); // Needed, due to debounce
|
||||
waits(550); // Needed, due to debounce
|
||||
runs (function () {
|
||||
expect($roster.find('dd:visible').length).toBe(1);
|
||||
expect($roster.find('dd:visible').eq(0).text().trim()).toBe('Candice van der Knijff');
|
||||
expect($roster.find('dt:visible').length).toBe(1);
|
||||
expect($roster.find('dt:visible').eq(0).text()).toBe('colleagues');
|
||||
$filter = converse.rosterview.$('.roster-filter');
|
||||
$filter.val("an");
|
||||
$filter.trigger('keydown');
|
||||
});
|
||||
waits(350); // Needed, due to debounce
|
||||
waits(550); // Needed, due to debounce
|
||||
runs (function () {
|
||||
expect($roster.find('dd:visible').length).toBe(5);
|
||||
expect($roster.find('dt:visible').length).toBe(4);
|
||||
$filter = converse.rosterview.$('.roster-filter');
|
||||
$filter.val("xxx");
|
||||
$filter.trigger('keydown');
|
||||
});
|
||||
waits(350); // Needed, due to debounce
|
||||
waits(550); // Needed, due to debounce
|
||||
runs (function () {
|
||||
expect($roster.find('dd:visible').length).toBe(0);
|
||||
expect($roster.find('dt:visible').length).toBe(0);
|
||||
$filter = converse.rosterview.$('.roster-filter');
|
||||
$filter.val(""); // Check that contacts are shown again, when the filter string is cleared.
|
||||
$filter.trigger('keydown');
|
||||
});
|
||||
waits(350); // Needed, due to debounce
|
||||
waits(550); // Needed, due to debounce
|
||||
runs(function () {
|
||||
expect($roster.find('dd:visible').length).toBe(15);
|
||||
expect($roster.find('dt:visible').length).toBe(5);
|
||||
@ -219,12 +233,13 @@
|
||||
converse.roster_groups = true;
|
||||
_clearContacts();
|
||||
utils.createGroupedContacts();
|
||||
converse.rosterview.filter_view.delegateEvents();
|
||||
$filter = converse.rosterview.$('.roster-filter');
|
||||
$roster = converse.rosterview.$roster;
|
||||
$type = converse.rosterview.$('.filter-type');
|
||||
$type.val('groups');
|
||||
});
|
||||
waits(350); // Needed, due to debounce
|
||||
waits(550); // Needed, due to debounce
|
||||
runs(function () {
|
||||
expect($roster.find('dd:visible').length).toBe(15);
|
||||
expect($roster.find('dt:visible').length).toBe(5);
|
||||
@ -233,22 +248,24 @@
|
||||
expect($roster.find('dt:visible').length).toBe(5); // ditto
|
||||
$filter.trigger('keydown');
|
||||
});
|
||||
waits(350); // Needed, due to debounce
|
||||
waits(550); // Needed, due to debounce
|
||||
runs (function () {
|
||||
expect($roster.find('dt:visible').length).toBe(1);
|
||||
expect($roster.find('dt:visible').eq(0).text()).toBe('colleagues');
|
||||
// Check that all contacts under the group are shown
|
||||
expect($roster.find('dt:visible').nextUntil('dt', 'dd:hidden').length).toBe(0);
|
||||
$filter = converse.rosterview.$('.roster-filter');
|
||||
$filter.val("xxx");
|
||||
$filter.trigger('keydown');
|
||||
});
|
||||
waits(350); // Needed, due to debounce
|
||||
waits(550); // Needed, due to debounce
|
||||
runs (function () {
|
||||
expect($roster.find('dt:visible').length).toBe(0);
|
||||
$filter = converse.rosterview.$('.roster-filter');
|
||||
$filter.val(""); // Check that groups are shown again, when the filter string is cleared.
|
||||
$filter.trigger('keydown');
|
||||
});
|
||||
waits(350); // Needed, due to debounce
|
||||
waits(550); // Needed, due to debounce
|
||||
runs(function () {
|
||||
expect($roster.find('dd:visible').length).toBe(15);
|
||||
expect($roster.find('dt:visible').length).toBe(5);
|
||||
@ -262,12 +279,14 @@
|
||||
utils.createGroupedContacts();
|
||||
var $filter = converse.rosterview.$('.roster-filter');
|
||||
runs (function () {
|
||||
converse.rosterview.filter_view.delegateEvents();
|
||||
$filter.val("xxx");
|
||||
$filter.trigger('keydown');
|
||||
expect($filter.hasClass("x")).toBeFalsy();
|
||||
});
|
||||
waits(350); // Needed, due to debounce
|
||||
waits(550); // Needed, due to debounce
|
||||
runs (function () {
|
||||
$filter = converse.rosterview.$('.roster-filter');
|
||||
expect($filter.hasClass("x")).toBeTruthy();
|
||||
$filter.addClass("onX").click();
|
||||
expect($filter.val()).toBe("");
|
||||
|
@ -324,7 +324,7 @@
|
||||
onControlBoxToggleHidden: function () {
|
||||
this.$el.show('fast', function () {
|
||||
if (converse.rosterview) {
|
||||
converse.rosterview.update();
|
||||
converse.rosterview.updateOnlineCount();
|
||||
}
|
||||
utils.refreshWebkit();
|
||||
converse.emit('controlBoxOpened', this);
|
||||
|
@ -779,20 +779,6 @@
|
||||
.c('item', {jid: this.get('jid'), subscription: "remove"});
|
||||
converse.connection.sendIQ(iq, callback, callback);
|
||||
return this;
|
||||
},
|
||||
|
||||
showInRoster: function () {
|
||||
var chatStatus = this.get('chat_status');
|
||||
if ((converse.show_only_online_users && chatStatus !== 'online') || (converse.hide_offline_users && chatStatus === 'offline')) {
|
||||
// If pending or requesting, show
|
||||
if ((this.get('ask') === 'subscribe') ||
|
||||
(this.get('subscription') === 'from') ||
|
||||
(this.get('requesting') === true)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -67,16 +67,126 @@
|
||||
HEADER_WEIGHTS[HEADER_REQUESTING_CONTACTS] = 2;
|
||||
HEADER_WEIGHTS[HEADER_PENDING_CONTACTS] = 3;
|
||||
|
||||
converse.RosterFilter = Backbone.Model.extend({
|
||||
initialize: function () {
|
||||
this.set({
|
||||
'filter_text': '',
|
||||
'filter_type': 'contacts',
|
||||
'chat_state': ''
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
converse.RosterFilterView = Backbone.View.extend({
|
||||
tagName: 'span',
|
||||
events: {
|
||||
"keydown .roster-filter": "liveFilter",
|
||||
"click .onX": "clearFilter",
|
||||
"mousemove .x": "toggleX",
|
||||
"change .filter-type": "changeTypeFilter",
|
||||
"change .state-type": "changeChatStateFilter"
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
this.model.on('change', this.render, this);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.html(converse.templates.roster(
|
||||
_.extend(this.model.toJSON(), {
|
||||
placeholder: __('Type to filter'),
|
||||
label_contacts: LABEL_CONTACTS,
|
||||
label_groups: LABEL_GROUPS,
|
||||
label_state: __('State'),
|
||||
label_any: __('Any'),
|
||||
label_online: __('Online'),
|
||||
label_chatty: __('Chatty'),
|
||||
label_busy: __('Busy'),
|
||||
label_away: __('Away'),
|
||||
label_xa: __('Extended Away'),
|
||||
label_offline: __('Offline')
|
||||
})
|
||||
));
|
||||
var $roster_filter = this.$('.roster-filter');
|
||||
$roster_filter[this.tog($roster_filter.val())]('x');
|
||||
return this.$el;
|
||||
},
|
||||
|
||||
tog: function (v) {
|
||||
return v?'addClass':'removeClass';
|
||||
},
|
||||
|
||||
toggleX: function (ev) {
|
||||
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
||||
var el = ev.target;
|
||||
$(el)[this.tog(el.offsetWidth-18 < ev.clientX-el.getBoundingClientRect().left)]('onX');
|
||||
},
|
||||
|
||||
changeChatStateFilter: function (ev) {
|
||||
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
||||
this.model.save({
|
||||
'chat_state': this.$('.state-type').val()
|
||||
});
|
||||
},
|
||||
|
||||
changeTypeFilter: function (ev) {
|
||||
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
||||
var type = ev.target.value;
|
||||
if (type === 'state') {
|
||||
this.model.save({
|
||||
'filter_type': type,
|
||||
'chat_state': this.$('.state-type').val()
|
||||
});
|
||||
} else {
|
||||
this.model.save({
|
||||
'filter_type': type,
|
||||
'filter_text': this.$('.roster-filter').val(),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
liveFilter: _.debounce(function (ev) {
|
||||
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
||||
this.model.save({
|
||||
'filter_type': this.$('.filter-type').val(),
|
||||
'filter_text': this.$('.roster-filter').val()
|
||||
});
|
||||
}, 250),
|
||||
|
||||
show: function () {
|
||||
if (this.$el.is(':visible')) { return this; }
|
||||
this.$el.show();
|
||||
return this;
|
||||
},
|
||||
|
||||
hide: function () {
|
||||
if (!this.$el.is(':visible')) { return this; }
|
||||
if (this.$('.roster-filter').val().length > 0) {
|
||||
// Don't hide if user is currently filtering.
|
||||
return;
|
||||
}
|
||||
this.model.save({
|
||||
'filter_text': '',
|
||||
'chat_state': ''
|
||||
});
|
||||
this.$el.hide();
|
||||
return this;
|
||||
},
|
||||
|
||||
clearFilter: function (ev) {
|
||||
if (ev && ev.preventDefault) {
|
||||
ev.preventDefault();
|
||||
$(ev.target).removeClass('x onX').val('');
|
||||
}
|
||||
this.model.save({
|
||||
'filter_text': ''
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
converse.RosterView = Backbone.Overview.extend({
|
||||
tagName: 'div',
|
||||
id: 'converse-roster',
|
||||
events: {
|
||||
"keydown .roster-filter": "liveFilter",
|
||||
"click .onX": "clearFilter",
|
||||
"mousemove .x": "togglePointer",
|
||||
"change .filter-type": "changeFilterType"
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
this.roster_handler_ref = this.registerRosterHandler();
|
||||
@ -89,8 +199,42 @@
|
||||
this.model.on("add", this.onGroupAdd, this);
|
||||
this.model.on("reset", this.reset, this);
|
||||
this.$roster = $('<dl class="roster-contacts" style="display: none;"></dl>');
|
||||
// Create a model on which we can store filter properties
|
||||
var model = new converse.RosterFilter();
|
||||
model.id = b64_sha1('converse.rosterfilter'+converse.bare_jid);
|
||||
model.browserStorage = new Backbone.BrowserStorage.local(this.filter.id);
|
||||
this.filter_view = new converse.RosterFilterView({'model': model});
|
||||
this.filter_view.model.on('change', this.updateFilter, this);
|
||||
this.filter_view.model.fetch();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.html(this.filter_view.render());
|
||||
if (!converse.allow_contact_requests) {
|
||||
// XXX: if we ever support live editing of config then
|
||||
// we'll need to be able to remove this class on the fly.
|
||||
this.$el.addClass('no-contact-requests');
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
updateFilter: _.debounce(function () {
|
||||
/* Filter the roster again.
|
||||
* Called whenever the filter settings have been changed or
|
||||
* when contacts have been added, removed or changed.
|
||||
*
|
||||
* Debounced so that it doesn't get called for every
|
||||
* contact fetched from browser storage.
|
||||
*/
|
||||
converse.log('updateFilter called!!!!!!');
|
||||
var type = this.filter_view.model.get('filter_type');
|
||||
if (type === 'state') {
|
||||
this.filter(this.filter_view.model.get('chat_state'), type);
|
||||
} else {
|
||||
this.filter(this.filter_view.model.get('filter_text'), type);
|
||||
}
|
||||
}, 100),
|
||||
|
||||
unregisterHandlers: function () {
|
||||
converse.connection.deleteHandler(this.roster_handler_ref);
|
||||
delete this.roster_handler_ref;
|
||||
@ -100,28 +244,30 @@
|
||||
delete this.presence_ref;
|
||||
},
|
||||
|
||||
update: _.debounce(function () {
|
||||
updateOnlineCount: function () {
|
||||
var $count = $('#online-count');
|
||||
$count.text('('+converse.roster.getNumOnlineContacts()+')');
|
||||
if (!$count.is(':visible')) {
|
||||
$count.show();
|
||||
}
|
||||
},
|
||||
|
||||
update: _.debounce(function () {
|
||||
this.updateOnlineCount();
|
||||
if (this.$roster.parent().length === 0) {
|
||||
this.$el.append(this.$roster.show());
|
||||
}
|
||||
return this.showHideFilter();
|
||||
}, converse.animate ? 100 : 0),
|
||||
|
||||
render: function () {
|
||||
this.$el.html(converse.templates.roster({
|
||||
placeholder: __('Type to filter'),
|
||||
label_contacts: LABEL_CONTACTS,
|
||||
label_groups: LABEL_GROUPS
|
||||
}));
|
||||
if (!converse.allow_contact_requests) {
|
||||
// XXX: if we ever support live editing of config then
|
||||
// we'll need to be able to remove this class on the fly.
|
||||
this.$el.addClass('no-contact-requests');
|
||||
showHideFilter: function () {
|
||||
if (!this.$el.is(':visible')) {
|
||||
return;
|
||||
}
|
||||
if (this.$roster.hasScrollBar()) {
|
||||
this.filter_view.show();
|
||||
} else {
|
||||
this.filter_view.hide();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
@ -163,26 +309,15 @@
|
||||
return this;
|
||||
},
|
||||
|
||||
changeFilterType: function (ev) {
|
||||
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
||||
this.clearFilter();
|
||||
this.filter(
|
||||
this.$('.roster-filter').val(),
|
||||
ev.target.value
|
||||
);
|
||||
},
|
||||
|
||||
tog: function (v) {
|
||||
return v?'addClass':'removeClass';
|
||||
},
|
||||
|
||||
togglePointer: function (ev) {
|
||||
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
||||
var el = ev.target;
|
||||
$(el)[this.tog(el.offsetWidth-18 < ev.clientX-el.getBoundingClientRect().left)]('onX');
|
||||
},
|
||||
|
||||
filter: function (query, type) {
|
||||
// First we make sure the filter is restored to its
|
||||
// original state
|
||||
_.each(this.getAll(), function (view) {
|
||||
if (view.model.contacts.length > 0) {
|
||||
view.show().filter('');
|
||||
}
|
||||
});
|
||||
// Now we can filter
|
||||
query = query.toLowerCase();
|
||||
if (type === 'groups') {
|
||||
_.each(this.getAll(), function (view, idx) {
|
||||
@ -199,46 +334,6 @@
|
||||
}
|
||||
},
|
||||
|
||||
liveFilter: _.debounce(function (ev) {
|
||||
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
||||
var $filter = this.$('.roster-filter');
|
||||
var q = $filter.val();
|
||||
var t = this.$('.filter-type').val();
|
||||
$filter[this.tog(q)]('x');
|
||||
this.filter(q, t);
|
||||
}, 300),
|
||||
|
||||
clearFilter: function (ev) {
|
||||
if (ev && ev.preventDefault) {
|
||||
ev.preventDefault();
|
||||
$(ev.target).removeClass('x onX').val('');
|
||||
}
|
||||
this.filter('');
|
||||
},
|
||||
|
||||
showHideFilter: function () {
|
||||
if (!this.$el.is(':visible')) {
|
||||
return;
|
||||
}
|
||||
var $filter = this.$('.roster-filter');
|
||||
var $type = this.$('.filter-type');
|
||||
var visible = $filter.is(':visible');
|
||||
if (visible && $filter.val().length > 0) {
|
||||
// Don't hide if user is currently filtering.
|
||||
return;
|
||||
}
|
||||
if (this.$roster.hasScrollBar()) {
|
||||
if (!visible) {
|
||||
$filter.show();
|
||||
$type.show();
|
||||
}
|
||||
} else {
|
||||
$filter.hide();
|
||||
$type.hide();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
reset: function () {
|
||||
converse.roster.reset();
|
||||
this.removeAll();
|
||||
@ -288,6 +383,7 @@
|
||||
|
||||
onContactAdd: function (contact) {
|
||||
this.addRosterContact(contact).update();
|
||||
this.updateFilter();
|
||||
},
|
||||
|
||||
onContactChange: function (contact) {
|
||||
@ -305,7 +401,7 @@
|
||||
if (_.has(contact.changed, 'subscription') && contact.changed.requesting === 'true') {
|
||||
this.addContactToGroup(contact, HEADER_REQUESTING_CONTACTS);
|
||||
}
|
||||
this.liveFilter();
|
||||
this.updateFilter();
|
||||
},
|
||||
|
||||
updateChatBox: function (contact) {
|
||||
@ -438,11 +534,9 @@
|
||||
},
|
||||
|
||||
render: function () {
|
||||
if (!this.model.showInRoster()) {
|
||||
if (!this.mayBeShown()) {
|
||||
this.$el.hide();
|
||||
return this;
|
||||
} else if (this.$el[0].style.display === "none") {
|
||||
this.$el.show();
|
||||
}
|
||||
var item = this.model,
|
||||
ask = item.get('ask'),
|
||||
@ -509,6 +603,45 @@
|
||||
return this;
|
||||
},
|
||||
|
||||
isGroupCollapsed: function () {
|
||||
/* Check whether the group in which this contact appears is
|
||||
* collapsed.
|
||||
*/
|
||||
// XXX: this sucks and is fragile.
|
||||
// It's because I tried to do the "right thing"
|
||||
// and use definition lists to represent roster groups.
|
||||
// If roster group items were inside the group elements, we
|
||||
// would simplify things by not having to check whether the
|
||||
// group is collapsed or not.
|
||||
var name = this.$el.prevAll('dt:first').data('group');
|
||||
var group = converse.rosterview.model.where({'name': name})[0];
|
||||
if (group.get('state') === converse.CLOSED) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
mayBeShown: function () {
|
||||
/* Return a boolean indicating whether this contact should
|
||||
* generally be visible in the roster.
|
||||
*
|
||||
* It doesn't check for the more specific case of whether
|
||||
* the group it's in is collapsed (see isGroupCollapsed).
|
||||
*/
|
||||
var chatStatus = this.model.get('chat_status');
|
||||
if ((converse.show_only_online_users && chatStatus !== 'online') ||
|
||||
(converse.hide_offline_users && chatStatus === 'offline')) {
|
||||
// If pending or requesting, show
|
||||
if ((this.model.get('ask') === 'subscribe') ||
|
||||
(this.model.get('subscription') === 'from') ||
|
||||
(this.model.get('requesting') === true)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
openChat: function (ev) {
|
||||
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
||||
return converse.chatboxviews.showChat(this.model.attributes);
|
||||
@ -606,7 +739,7 @@
|
||||
var view = new converse.RosterContactView({model: contact});
|
||||
this.add(contact.get('id'), view);
|
||||
view = this.positionContact(contact).render();
|
||||
if (contact.showInRoster()) {
|
||||
if (view.mayBeShown()) {
|
||||
if (this.model.get('state') === converse.CLOSED) {
|
||||
if (view.$el[0].style.display !== "none") { view.$el.hide(); }
|
||||
if (!this.$el.is(':visible')) { this.$el.show(); }
|
||||
@ -635,18 +768,19 @@
|
||||
|
||||
show: function () {
|
||||
this.$el.show();
|
||||
_.each(this.getAll(), function (contactView) {
|
||||
if (contactView.model.showInRoster()) {
|
||||
contactView.$el.show();
|
||||
_.each(this.getAll(), function (view) {
|
||||
if (view.mayBeShown() && !view.isGroupCollapsed()) {
|
||||
view.$el.show();
|
||||
}
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
hide: function () {
|
||||
this.$el.nextUntil('dt').addBack().hide();
|
||||
},
|
||||
|
||||
filter: function (q) {
|
||||
filter: function (q, type) {
|
||||
/* Filter the group's contacts based on the query "q".
|
||||
* The query is matched against the contact's full name.
|
||||
* If all contacts are filtered out (i.e. hidden), then the
|
||||
@ -656,16 +790,26 @@
|
||||
if (q.length === 0) {
|
||||
if (this.model.get('state') === converse.OPENED) {
|
||||
this.model.contacts.each(function (item) {
|
||||
if (item.showInRoster()) {
|
||||
this.get(item.get('id')).$el.show();
|
||||
var view = this.get(item.get('id'));
|
||||
if (view.mayBeShown() && !view.isGroupCollapsed()) {
|
||||
view.$el.show();
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
this.showIfNecessary();
|
||||
} else {
|
||||
q = q.toLowerCase();
|
||||
matches = this.model.contacts.filter(utils.contains.not('fullname', q));
|
||||
if (matches.length === this.model.contacts.length) { // hide the whole group
|
||||
if (type === 'state') {
|
||||
matches = this.model.contacts.filter(
|
||||
utils.contains.not('chat_status', q)
|
||||
);
|
||||
} else {
|
||||
matches = this.model.contacts.filter(
|
||||
utils.contains.not('fullname', q)
|
||||
);
|
||||
}
|
||||
if (matches.length === this.model.contacts.length) {
|
||||
// hide the whole group
|
||||
this.hide();
|
||||
} else {
|
||||
_.each(matches, function (item) {
|
||||
@ -696,7 +840,7 @@
|
||||
$el.removeClass("icon-closed").addClass("icon-opened");
|
||||
this.model.save({state: converse.OPENED});
|
||||
this.filter(
|
||||
converse.rosterview.$('.roster-filter').val(),
|
||||
converse.rosterview.$('.roster-filter').val() || '',
|
||||
converse.rosterview.$('.filter-type').val()
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,28 @@
|
||||
<form class="pure-form roster-filter-group input-button-group">
|
||||
<input style="display: none;" class="roster-filter" placeholder="{{placeholder}}">
|
||||
<select style="display: none;" class="filter-type">
|
||||
<option value="contacts">{{label_contacts}}</option>
|
||||
<option value="groups">{{label_groups}}</option>
|
||||
<input value="{{filter_text}}" class="roster-filter"
|
||||
placeholder="{{placeholder}}"
|
||||
{[ if (filter_type === 'state') { ]} style="display: none" {[ } ]} >
|
||||
<select class="state-type" {[ if (filter_type !== 'state') { ]} style="display: none" {[ } ]} >
|
||||
<option value="">{{label_any}}</option>
|
||||
<option {[ if (chat_state === 'online') { ]} selected="selected" {[ } ]}
|
||||
value="online">{{label_online}}</option>
|
||||
<option {[ if (chat_state === 'chatty') { ]} selected="selected" {[ } ]}
|
||||
value="chatty">{{label_chatty}}</option>
|
||||
<option {[ if (chat_state === 'dnd') { ]} selected="selected" {[ } ]}
|
||||
value="dnd">{{label_busy}}</option>
|
||||
<option {[ if (chat_state === 'away') { ]} selected="selected" {[ } ]}
|
||||
value="away">{{label_away}}</option>
|
||||
<option {[ if (chat_state === 'xa') { ]} selected="selected" {[ } ]}
|
||||
value="xa">{{label_xa}}</option>
|
||||
<option {[ if (chat_state === 'offline') { ]} selected="selected" {[ } ]}
|
||||
value="offline">{{label_offline}}</option>
|
||||
</select>
|
||||
<select class="filter-type">
|
||||
<option {[ if (filter_type === 'contacts') { ]} selected="selected" {[ } ]}
|
||||
value="contacts">{{label_contacts}}</option>
|
||||
<option {[ if (filter_type === 'groups') { ]} selected="selected" {[ } ]}
|
||||
value="groups">{{label_groups}}</option>
|
||||
<option {[ if (filter_type === 'state') { ]} selected="selected" {[ } ]}
|
||||
value="state">{{label_state}}</option>
|
||||
</select>
|
||||
</form>
|
||||
|
Loading…
Reference in New Issue
Block a user