Skip to content
This repository has been archived by the owner on Nov 21, 2024. It is now read-only.

Updating Bookmarks #176

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
372 changes: 301 additions & 71 deletions bookmark/bookmark.js
Original file line number Diff line number Diff line change
@@ -1,93 +1,323 @@
/** File: bookmark.js
* Candy Plugin - Bookmark rooms
* Candy Plugin - Bookmark Rooms
* Author: Rafael Macedo <[email protected]>
* Author: Ben Langfeld <[email protected]>
*/

/* global Candy, jQuery, Strophe, $iq */

var CandyShop = (function(self) { return self; }(CandyShop || {}));

CandyShop.Bookmark = (function(self, Candy, $) {
self.init = function(){
$(Candy).on('candy:view.connection.status-5', function(){
self._createBookmarksNode();
return true;
});

self.Bookmark = (function() {
function Bookmark(type, args) {
this.type = type;
this.jid = args.jid;
this.name = args.name || args.jid.split(/@/)[0];
this.id = args.room_id || args.user_id;
this.term = args.term;
this.to = args.to;
this.opened = false;
this.is_private = args.is_private;
this.pending_removal = false;
}

Bookmark.prototype.join = function(foreground) {
if (!this.opened) {
if (this.type.match(/users/)) {
Candy.View.Pane.PrivateRoom.open(this.jid, this.name, foreground, true);
} else if (this.type.match(/rooms/)) {
var data = { roomJid: this.jid, name: this.name, is_private: this.is_private, room_id: this.id };
CandyShop.JoinOnResponse.joinRoom(data, false, foreground);
} else {
Candy.Core.warn('Error: unknown bookmark type: ', this.type);
}
this.opened = true;
}
};

Bookmark.prototype.leave = function() {
if (this.type.match(/users/)) {
Candy.View.Pane.Room.close(this.jid);
} else if (this.type.match(/rooms/)) {
Candy.Core.Action.Jabber.Room.Leave(this.jid);
}
};

Bookmark.prototype.toArgs = function() {
var args = {};

if (this.id) {
if (this.type.match(/users/)) {
args.user_id = this.id;
} else if (this.type.match(/rooms/)) {
args.room_id = this.id;
}
} else {
if (this.type.match(/users/)) {
args.user_jid = this.jid;
} else if (this.type.match(/rooms/)) {
args.room_jid = this.jid;
}
}

return args;
};

return Bookmark;
})();

self.BookmarkCollection = (function() {
function BookmarkCollection() {
this.collection = [];
this.version = undefined;
}

BookmarkCollection.prototype.add = function(bookmark, newlyAdded) {
if (this.collection.filter(function(item) { return item.jid === bookmark; }).length === 0) {
if (newlyAdded) {
this.collection.unshift(bookmark);
if (Candy.Core.getConnection().connected === true) {
bookmark.join();
}
} else {
this.collection.push(bookmark);
}
}
return this.collection;
};

BookmarkCollection.prototype.joinAll = function() {
this.collection.forEach(function (bookmark, position, collection) {
var foreground = false;
if (CandyShop.RoomAnchors.roomAnchorJid()) {
// This bookmark matches the room in the URL
if (CandyShop.RoomAnchors.roomAnchorJid() === bookmark.jid) {
foreground = true;
}
} else if (position === 0) {
foreground = true;
}
bookmark.join(foreground);
});
}

BookmarkCollection.prototype.jids = function() {
return this.collection.map(function(bookmark) {
return bookmark.jid;
});
};

BookmarkCollection.prototype.pendingRemoval = function() {
return this.collection.filter(function(item) { return item.pending_removal; });
};

BookmarkCollection.prototype.valid = function() {
return this.collection.filter(function(item) { return !item.pending_removal; });
};

BookmarkCollection.prototype.tryRemoveByJid = function(jid) {
this.collection.forEach(function (item) {
if (item.jid === jid) {
item.pending_removal = true;
}
});
};

BookmarkCollection.prototype.removeByJid = function(jid) {
this.collection = this.collection.filter(function(bookmark) {
return bookmark.jid !== jid;
});
};

BookmarkCollection.prototype.reset = function(data) {
for (var key in data) {
data[key].forEach($.proxy(function(obj) {
self.bookmarks.add(new self.Bookmark(key, obj));
}), this);
}
};

BookmarkCollection.prototype.sync = function(data) {
for (var key in data) {
data[key].forEach($.proxy(function(obj) {
if ($.inArray(obj.jid, self.bookmarks.jids()) === -1) {
self.bookmarks.add(new self.Bookmark(key, obj));
}
}), this);
}

var jids = [];
jids = jids.concat(data.connect_bookmarked_users.map(function(i) { return i.jid; }));
jids = jids.concat(data.connect_bookmarked_rooms.map(function(i) { return i.jid; }));

this.valid().forEach($.proxy(function(item) {
if ($.inArray(item.jid, jids) === -1) {
this.removeByJid(item.jid);
item.leave();
}
}), this);
};

BookmarkCollection.prototype.setVersion = function(version) {
this.version = version;
};

BookmarkCollection.prototype.toArgs = function() {
var args = {};
var valid = this.valid();

args.version = this.version;

args.connect_bookmarked_users =
valid.filter(function(bookmark) {
return bookmark.type === 'connect_bookmarked_users';
}).map(function(bookmark) { return bookmark.toArgs(); });

args.connect_bookmarked_rooms =
valid.filter(function(bookmark) {
return bookmark.type === 'connect_bookmarked_rooms';
}).map(function(bookmark) { return bookmark.toArgs(); });

return args;
};

BookmarkCollection.prototype.type = function(type) {
return this.collection.filter(function(bookmark) {
return bookmark.type === 'connect_bookmarked_' + type;
});
};

return BookmarkCollection;
})();

self.requestHandler = undefined;

self.init = function() {
self.bookmarks = new self.BookmarkCollection();
self._addEventListeners();
};
/** File: bookmark.js
* Candy Plugin - Bookmark rooms
* Author: Ben Langfeld <[email protected]>
*/

var CandyShop = (function(self) { return self; }(CandyShop || {}));
self._addEventListeners = function() {
$(Candy)
.on('candy:core.presence.error', self._removeBookmarkPresenceError)
.on('candy:view.room.after-close', self._removeBookmark)
.on('candy:view.room.after-add', self._onRoomOpen)
.on('candy:view.connection.status-' + Strophe.Status.CONNECTED, self.joinAll)
.on('candy:view.connection.status-' + Strophe.Status.ATTACHED, self.joinAll);
};

CandyShop.Bookmark = (function(self, Candy, $) {
/** Object: about
*
* Contains:
* (String) name - Candy Plugin - Bookmark rooms
* (Float) version - Candy Plugin - Bookmark rooms
*/
self.about = {
name: 'Candy Plugin - Bookmark rooms',
version: '0.1'
self.joinAll = function () {
self.bookmarks.joinAll();
};

self.init = function(){
Strophe.addNamespace('PUBSUB', 'http://jabber.org/protocol/pubsub');
$(Candy).on('candy:view.connection.status-5', self._createBookmarksNode);
$(Candy).on('candy:view.connection.status-8', self._createBookmarksNode);
self.fetchBookmarks = function(data) {
self.bookmarks.setVersion(data.version);
delete data.version;
self.bookmarks.reset(data);
};

/** Function: add
* Adds a bookmark for the provided MUC room
*
* Parameters:
* (String) roomJid - The JID of the room to bookmark
*/
self.add = function(roomJid) {
Candy.Core.getConnection().sendIQ($iq({
type: 'set'
})
.c('pubsub', {xmlns: Strophe.NS.PUBSUB})
.c('publish', {node: Strophe.NS.BOOKMARKS})
.c('item', {id: roomJid})
.c('storage', {xmlns: Strophe.NS.BOOKMARKS})
.c('conference', {autojoin: 'true', jid: roomJid})
);
self._onRoomOpen = function(e, args) {
if (self.bookmarks) {
if ($.inArray(args.roomJid, self.bookmarks.jids()) === -1) {
var bookmark;
var type;

if (args.type === 'chat') {
type = 'connect_bookmarked_users';
} else if (args.type === 'groupchat') {
type = 'connect_bookmarked_rooms';
}

if (type) {
bookmark = new self.Bookmark(type, { jid: args.roomJid });
self.bookmarks.add(bookmark, true);
self._updateBookmarks();
}
}

// Re-sort the order of Rooms, and One-on-One chats to match
// the order of the bookmarks
var chatTabs = $('#chat-tabs');
var roomsHead = $('#rooms-head').detach();
var rooms = chatTabs.find('.roomtype-groupchat').detach();
var peopleHead = $('#people-head').detach();
var people = chatTabs.find('.roomtype-chat').detach();

// TODO: DRY
chatTabs.append(roomsHead);
self.bookmarks.type('rooms').map(function(bookmark) {
chatTabs.append(rooms.filter(function(i, room) {
return $(room).attr('data-roomjid') === bookmark.jid;
}));
});

chatTabs.append(peopleHead);
self.bookmarks.type('users').map(function(bookmark) {
chatTabs.append(people.filter(function(i, user) {
return $(user).attr('data-roomjid') === bookmark.jid;
}));
});
}
};

/** Function: remove
* Removes a bookmark for the provided MUC room
*
* Parameters:
* (String) roomJid - The JID of the room to remove from bookmarks
*/
self.remove = function(roomJid) {
Candy.Core.getConnection().sendIQ($iq({
type: 'set'
})
.c('pubsub', {xmlns: Strophe.NS.PUBSUB})
.c('retract', {node: Strophe.NS.BOOKMARKS})
.c('item', {id: roomJid})
);
self._removeBookmarkPresenceError = function(e, args) {
if ($(args).attr('error') && Strophe.getBareJidFromJid($(args).attr('from')) !== Strophe.getBareJidFromJid(Candy.Core.getUser().getJid())) {
self._removeBookmark(e, args);
}
};

self._createBookmarksNode = function() {
// We do this instead of using publish-options because this is not mandatory to implement according to XEP-0060
Candy.Core.getConnection().sendIQ($iq({type: 'set'})
.c('pubsub', {xmlns: Strophe.NS.PUBSUB})
.c('create', {node: 'storage:bookmarks'}).up()
.c('configure')
.c('x', {xmlns: 'jabber:x:data', type: 'submit'})
.c('field', {'var': 'FORM_TYPE', type: 'hidden'})
.c('value').t('http://jabber.org/protocol/pubsub#node_config').up().up()
.c('field', {'var': 'pubsub#persist_items'}).c('value').t('1').up().up()
.c('field', {'var': 'pubsub#access_model'}).c('value').t('whitelist')
);

return true;
self._removeBookmark = function(e, args) {
if (args.roomJid) {
self.bookmarks.tryRemoveByJid(args.roomJid);
window.clearTimeout(self.requestHandler);
self.requestHandler = window.setTimeout(self._updateBookmarks, 300);
}
};

self._updateBookmarks = function() {
$.ajax({
type: 'PUT',
url: '/api/v1/connect/bookmarks',
contentType: 'application/json',
data: JSON.stringify(self.bookmarks.toArgs()),
statusCode: {
409: function() {
CandyShop.ConnectStateData.fetch().done(function(data) {
self.bookmarks.setVersion(data.bookmarks.version);
delete data.bookmarks.version;
self.bookmarks.sync(data.bookmarks);
self._updateBookmarks();
});
}
}
}).done(function(data) {
self.bookmarks.setVersion(data.version);
delete data.version;
self.bookmarks.sync(data);

self.bookmarks.pendingRemoval().forEach(function (bookmark) {
if (self._serverHasBookmark(bookmark.jid, data)) {
// The server still has this bookmark. We need to sync again
self._removeBookmark(undefined, { roomJid: bookmark.jid });
} else {
// This bookmark was removed from the server. Remove it from our local collection
self.bookmarks.removeByJid(bookmark.jid);
}
});
});
};

self._serverHasBookmark = function (jid, data) {
var has = false;
for (var key in data) {
data[key].forEach(function (bookmark) {
if (bookmark.jid == jid) {
has = true;
}
})
}
return has;
}

return self;
}(CandyShop.Bookmark || {}, Candy, jQuery));