|
1 | 1 | /** File: bookmark.js |
2 | | - * Candy Plugin - Bookmark rooms |
| 2 | + * Candy Plugin - Bookmark Rooms |
| 3 | + * Author: Rafael Macedo <[email protected]> |
3 | 4 | * Author: Ben Langfeld <[email protected]> |
4 | 5 | */ |
5 | 6 |
|
6 | | -/* global Candy, jQuery, Strophe, $iq */ |
7 | | - |
8 | 7 | var CandyShop = (function(self) { return self; }(CandyShop || {})); |
9 | 8 |
|
10 | 9 | CandyShop.Bookmark = (function(self, Candy, $) { |
11 | | - self.init = function(){ |
12 | | - $(Candy).on('candy:view.connection.status-5', function(){ |
13 | | - self._createBookmarksNode(); |
14 | | - return true; |
15 | | - }); |
| 10 | + |
| 11 | + self.Bookmark = (function() { |
| 12 | + function Bookmark(type, args) { |
| 13 | + this.type = type; |
| 14 | + this.jid = args.jid; |
| 15 | + this.name = args.name || args.jid.split(/@/)[0]; |
| 16 | + this.id = args.room_id || args.user_id; |
| 17 | + this.term = args.term; |
| 18 | + this.to = args.to; |
| 19 | + this.opened = false; |
| 20 | + this.is_private = args.is_private; |
| 21 | + this.pending_removal = false; |
| 22 | + } |
| 23 | + |
| 24 | + Bookmark.prototype.join = function(foreground) { |
| 25 | + if (!this.opened) { |
| 26 | + if (this.type.match(/users/)) { |
| 27 | + Candy.View.Pane.PrivateRoom.open(this.jid, this.name, foreground, true); |
| 28 | + } else if (this.type.match(/rooms/)) { |
| 29 | + var data = { roomJid: this.jid, name: this.name, is_private: this.is_private, room_id: this.id }; |
| 30 | + CandyShop.JoinOnResponse.joinRoom(data, false, foreground); |
| 31 | + } else { |
| 32 | + Candy.Core.warn('Error: unknown bookmark type: ', this.type); |
| 33 | + } |
| 34 | + this.opened = true; |
| 35 | + } |
| 36 | + }; |
| 37 | + |
| 38 | + Bookmark.prototype.leave = function() { |
| 39 | + if (this.type.match(/users/)) { |
| 40 | + Candy.View.Pane.Room.close(this.jid); |
| 41 | + } else if (this.type.match(/rooms/)) { |
| 42 | + Candy.Core.Action.Jabber.Room.Leave(this.jid); |
| 43 | + } |
| 44 | + }; |
| 45 | + |
| 46 | + Bookmark.prototype.toArgs = function() { |
| 47 | + var args = {}; |
| 48 | + |
| 49 | + if (this.id) { |
| 50 | + if (this.type.match(/users/)) { |
| 51 | + args.user_id = this.id; |
| 52 | + } else if (this.type.match(/rooms/)) { |
| 53 | + args.room_id = this.id; |
| 54 | + } |
| 55 | + } else { |
| 56 | + if (this.type.match(/users/)) { |
| 57 | + args.user_jid = this.jid; |
| 58 | + } else if (this.type.match(/rooms/)) { |
| 59 | + args.room_jid = this.jid; |
| 60 | + } |
| 61 | + } |
| 62 | + |
| 63 | + return args; |
| 64 | + }; |
| 65 | + |
| 66 | + return Bookmark; |
| 67 | + })(); |
| 68 | + |
| 69 | + self.BookmarkCollection = (function() { |
| 70 | + function BookmarkCollection() { |
| 71 | + this.collection = []; |
| 72 | + this.version = undefined; |
| 73 | + } |
| 74 | + |
| 75 | + BookmarkCollection.prototype.add = function(bookmark, newlyAdded) { |
| 76 | + if (this.collection.filter(function(item) { return item.jid === bookmark; }).length === 0) { |
| 77 | + if (newlyAdded) { |
| 78 | + this.collection.unshift(bookmark); |
| 79 | + if (Candy.Core.getConnection().connected === true) { |
| 80 | + bookmark.join(); |
| 81 | + } |
| 82 | + } else { |
| 83 | + this.collection.push(bookmark); |
| 84 | + } |
| 85 | + } |
| 86 | + return this.collection; |
| 87 | + }; |
| 88 | + |
| 89 | + BookmarkCollection.prototype.joinAll = function() { |
| 90 | + this.collection.forEach(function (bookmark, position, collection) { |
| 91 | + var foreground = false; |
| 92 | + if (CandyShop.RoomAnchors.roomAnchorJid()) { |
| 93 | + // This bookmark matches the room in the URL |
| 94 | + if (CandyShop.RoomAnchors.roomAnchorJid() === bookmark.jid) { |
| 95 | + foreground = true; |
| 96 | + } |
| 97 | + } else if (position === 0) { |
| 98 | + foreground = true; |
| 99 | + } |
| 100 | + bookmark.join(foreground); |
| 101 | + }); |
| 102 | + } |
| 103 | + |
| 104 | + BookmarkCollection.prototype.jids = function() { |
| 105 | + return this.collection.map(function(bookmark) { |
| 106 | + return bookmark.jid; |
| 107 | + }); |
| 108 | + }; |
| 109 | + |
| 110 | + BookmarkCollection.prototype.pendingRemoval = function() { |
| 111 | + return this.collection.filter(function(item) { return item.pending_removal; }); |
| 112 | + }; |
| 113 | + |
| 114 | + BookmarkCollection.prototype.valid = function() { |
| 115 | + return this.collection.filter(function(item) { return !item.pending_removal; }); |
| 116 | + }; |
| 117 | + |
| 118 | + BookmarkCollection.prototype.tryRemoveByJid = function(jid) { |
| 119 | + this.collection.forEach(function (item) { |
| 120 | + if (item.jid === jid) { |
| 121 | + item.pending_removal = true; |
| 122 | + } |
| 123 | + }); |
| 124 | + }; |
| 125 | + |
| 126 | + BookmarkCollection.prototype.removeByJid = function(jid) { |
| 127 | + this.collection = this.collection.filter(function(bookmark) { |
| 128 | + return bookmark.jid !== jid; |
| 129 | + }); |
| 130 | + }; |
| 131 | + |
| 132 | + BookmarkCollection.prototype.reset = function(data) { |
| 133 | + for (var key in data) { |
| 134 | + data[key].forEach($.proxy(function(obj) { |
| 135 | + self.bookmarks.add(new self.Bookmark(key, obj)); |
| 136 | + }), this); |
| 137 | + } |
| 138 | + }; |
| 139 | + |
| 140 | + BookmarkCollection.prototype.sync = function(data) { |
| 141 | + for (var key in data) { |
| 142 | + data[key].forEach($.proxy(function(obj) { |
| 143 | + if ($.inArray(obj.jid, self.bookmarks.jids()) === -1) { |
| 144 | + self.bookmarks.add(new self.Bookmark(key, obj)); |
| 145 | + } |
| 146 | + }), this); |
| 147 | + } |
| 148 | + |
| 149 | + var jids = []; |
| 150 | + jids = jids.concat(data.connect_bookmarked_users.map(function(i) { return i.jid; })); |
| 151 | + jids = jids.concat(data.connect_bookmarked_rooms.map(function(i) { return i.jid; })); |
| 152 | + |
| 153 | + this.valid().forEach($.proxy(function(item) { |
| 154 | + if ($.inArray(item.jid, jids) === -1) { |
| 155 | + this.removeByJid(item.jid); |
| 156 | + item.leave(); |
| 157 | + } |
| 158 | + }), this); |
| 159 | + }; |
| 160 | + |
| 161 | + BookmarkCollection.prototype.setVersion = function(version) { |
| 162 | + this.version = version; |
| 163 | + }; |
| 164 | + |
| 165 | + BookmarkCollection.prototype.toArgs = function() { |
| 166 | + var args = {}; |
| 167 | + var valid = this.valid(); |
| 168 | + |
| 169 | + args.version = this.version; |
| 170 | + |
| 171 | + args.connect_bookmarked_users = |
| 172 | + valid.filter(function(bookmark) { |
| 173 | + return bookmark.type === 'connect_bookmarked_users'; |
| 174 | + }).map(function(bookmark) { return bookmark.toArgs(); }); |
| 175 | + |
| 176 | + args.connect_bookmarked_rooms = |
| 177 | + valid.filter(function(bookmark) { |
| 178 | + return bookmark.type === 'connect_bookmarked_rooms'; |
| 179 | + }).map(function(bookmark) { return bookmark.toArgs(); }); |
| 180 | + |
| 181 | + return args; |
| 182 | + }; |
| 183 | + |
| 184 | + BookmarkCollection.prototype.type = function(type) { |
| 185 | + return this.collection.filter(function(bookmark) { |
| 186 | + return bookmark.type === 'connect_bookmarked_' + type; |
| 187 | + }); |
| 188 | + }; |
| 189 | + |
| 190 | + return BookmarkCollection; |
| 191 | + })(); |
| 192 | + |
| 193 | + self.requestHandler = undefined; |
| 194 | + |
| 195 | + self.init = function() { |
| 196 | + self.bookmarks = new self.BookmarkCollection(); |
| 197 | + self._addEventListeners(); |
16 | 198 | }; |
17 | | -/** File: bookmark.js |
18 | | - * Candy Plugin - Bookmark rooms |
19 | | - * Author: Ben Langfeld <[email protected]> |
20 | | - */ |
21 | 199 |
|
22 | | -var CandyShop = (function(self) { return self; }(CandyShop || {})); |
| 200 | + self._addEventListeners = function() { |
| 201 | + $(Candy) |
| 202 | + .on('candy:core.presence.error', self._removeBookmarkPresenceError) |
| 203 | + .on('candy:view.room.after-close', self._removeBookmark) |
| 204 | + .on('candy:view.room.after-add', self._onRoomOpen) |
| 205 | + .on('candy:view.connection.status-' + Strophe.Status.CONNECTED, self.joinAll) |
| 206 | + .on('candy:view.connection.status-' + Strophe.Status.ATTACHED, self.joinAll); |
| 207 | + }; |
23 | 208 |
|
24 | | -CandyShop.Bookmark = (function(self, Candy, $) { |
25 | | - /** Object: about |
26 | | - * |
27 | | - * Contains: |
28 | | - * (String) name - Candy Plugin - Bookmark rooms |
29 | | - * (Float) version - Candy Plugin - Bookmark rooms |
30 | | - */ |
31 | | - self.about = { |
32 | | - name: 'Candy Plugin - Bookmark rooms', |
33 | | - version: '0.1' |
| 209 | + self.joinAll = function () { |
| 210 | + self.bookmarks.joinAll(); |
34 | 211 | }; |
35 | 212 |
|
36 | | - self.init = function(){ |
37 | | - Strophe.addNamespace('PUBSUB', 'http://jabber.org/protocol/pubsub'); |
38 | | - $(Candy).on('candy:view.connection.status-5', self._createBookmarksNode); |
39 | | - $(Candy).on('candy:view.connection.status-8', self._createBookmarksNode); |
| 213 | + self.fetchBookmarks = function(data) { |
| 214 | + self.bookmarks.setVersion(data.version); |
| 215 | + delete data.version; |
| 216 | + self.bookmarks.reset(data); |
40 | 217 | }; |
41 | 218 |
|
42 | | - /** Function: add |
43 | | - * Adds a bookmark for the provided MUC room |
44 | | - * |
45 | | - * Parameters: |
46 | | - * (String) roomJid - The JID of the room to bookmark |
47 | | - */ |
48 | | - self.add = function(roomJid) { |
49 | | - Candy.Core.getConnection().sendIQ($iq({ |
50 | | - type: 'set' |
51 | | - }) |
52 | | - .c('pubsub', {xmlns: Strophe.NS.PUBSUB}) |
53 | | - .c('publish', {node: Strophe.NS.BOOKMARKS}) |
54 | | - .c('item', {id: roomJid}) |
55 | | - .c('storage', {xmlns: Strophe.NS.BOOKMARKS}) |
56 | | - .c('conference', {autojoin: 'true', jid: roomJid}) |
57 | | - ); |
| 219 | + self._onRoomOpen = function(e, args) { |
| 220 | + if (self.bookmarks) { |
| 221 | + if ($.inArray(args.roomJid, self.bookmarks.jids()) === -1) { |
| 222 | + var bookmark; |
| 223 | + var type; |
| 224 | + |
| 225 | + if (args.type === 'chat') { |
| 226 | + type = 'connect_bookmarked_users'; |
| 227 | + } else if (args.type === 'groupchat') { |
| 228 | + type = 'connect_bookmarked_rooms'; |
| 229 | + } |
| 230 | + |
| 231 | + if (type) { |
| 232 | + bookmark = new self.Bookmark(type, { jid: args.roomJid }); |
| 233 | + self.bookmarks.add(bookmark, true); |
| 234 | + self._updateBookmarks(); |
| 235 | + } |
| 236 | + } |
| 237 | + |
| 238 | + // Re-sort the order of Rooms, and One-on-One chats to match |
| 239 | + // the order of the bookmarks |
| 240 | + var chatTabs = $('#chat-tabs'); |
| 241 | + var roomsHead = $('#rooms-head').detach(); |
| 242 | + var rooms = chatTabs.find('.roomtype-groupchat').detach(); |
| 243 | + var peopleHead = $('#people-head').detach(); |
| 244 | + var people = chatTabs.find('.roomtype-chat').detach(); |
| 245 | + |
| 246 | + // TODO: DRY |
| 247 | + chatTabs.append(roomsHead); |
| 248 | + self.bookmarks.type('rooms').map(function(bookmark) { |
| 249 | + chatTabs.append(rooms.filter(function(i, room) { |
| 250 | + return $(room).attr('data-roomjid') === bookmark.jid; |
| 251 | + })); |
| 252 | + }); |
| 253 | + |
| 254 | + chatTabs.append(peopleHead); |
| 255 | + self.bookmarks.type('users').map(function(bookmark) { |
| 256 | + chatTabs.append(people.filter(function(i, user) { |
| 257 | + return $(user).attr('data-roomjid') === bookmark.jid; |
| 258 | + })); |
| 259 | + }); |
| 260 | + } |
58 | 261 | }; |
59 | 262 |
|
60 | | - /** Function: remove |
61 | | - * Removes a bookmark for the provided MUC room |
62 | | - * |
63 | | - * Parameters: |
64 | | - * (String) roomJid - The JID of the room to remove from bookmarks |
65 | | - */ |
66 | | - self.remove = function(roomJid) { |
67 | | - Candy.Core.getConnection().sendIQ($iq({ |
68 | | - type: 'set' |
69 | | - }) |
70 | | - .c('pubsub', {xmlns: Strophe.NS.PUBSUB}) |
71 | | - .c('retract', {node: Strophe.NS.BOOKMARKS}) |
72 | | - .c('item', {id: roomJid}) |
73 | | - ); |
| 263 | + self._removeBookmarkPresenceError = function(e, args) { |
| 264 | + if ($(args).attr('error') && Strophe.getBareJidFromJid($(args).attr('from')) !== Strophe.getBareJidFromJid(Candy.Core.getUser().getJid())) { |
| 265 | + self._removeBookmark(e, args); |
| 266 | + } |
74 | 267 | }; |
75 | 268 |
|
76 | | - self._createBookmarksNode = function() { |
77 | | - // We do this instead of using publish-options because this is not mandatory to implement according to XEP-0060 |
78 | | - Candy.Core.getConnection().sendIQ($iq({type: 'set'}) |
79 | | - .c('pubsub', {xmlns: Strophe.NS.PUBSUB}) |
80 | | - .c('create', {node: 'storage:bookmarks'}).up() |
81 | | - .c('configure') |
82 | | - .c('x', {xmlns: 'jabber:x:data', type: 'submit'}) |
83 | | - .c('field', {'var': 'FORM_TYPE', type: 'hidden'}) |
84 | | - .c('value').t('http://jabber.org/protocol/pubsub#node_config').up().up() |
85 | | - .c('field', {'var': 'pubsub#persist_items'}).c('value').t('1').up().up() |
86 | | - .c('field', {'var': 'pubsub#access_model'}).c('value').t('whitelist') |
87 | | - ); |
88 | | - |
89 | | - return true; |
| 269 | + self._removeBookmark = function(e, args) { |
| 270 | + if (args.roomJid) { |
| 271 | + self.bookmarks.tryRemoveByJid(args.roomJid); |
| 272 | + window.clearTimeout(self.requestHandler); |
| 273 | + self.requestHandler = window.setTimeout(self._updateBookmarks, 300); |
| 274 | + } |
90 | 275 | }; |
91 | 276 |
|
| 277 | + self._updateBookmarks = function() { |
| 278 | + $.ajax({ |
| 279 | + type: 'PUT', |
| 280 | + url: '/api/v1/connect/bookmarks', |
| 281 | + contentType: 'application/json', |
| 282 | + data: JSON.stringify(self.bookmarks.toArgs()), |
| 283 | + statusCode: { |
| 284 | + 409: function() { |
| 285 | + CandyShop.ConnectStateData.fetch().done(function(data) { |
| 286 | + self.bookmarks.setVersion(data.bookmarks.version); |
| 287 | + delete data.bookmarks.version; |
| 288 | + self.bookmarks.sync(data.bookmarks); |
| 289 | + self._updateBookmarks(); |
| 290 | + }); |
| 291 | + } |
| 292 | + } |
| 293 | + }).done(function(data) { |
| 294 | + self.bookmarks.setVersion(data.version); |
| 295 | + delete data.version; |
| 296 | + self.bookmarks.sync(data); |
| 297 | + |
| 298 | + self.bookmarks.pendingRemoval().forEach(function (bookmark) { |
| 299 | + if (self._serverHasBookmark(bookmark.jid, data)) { |
| 300 | + // The server still has this bookmark. We need to sync again |
| 301 | + self._removeBookmark(undefined, { roomJid: bookmark.jid }); |
| 302 | + } else { |
| 303 | + // This bookmark was removed from the server. Remove it from our local collection |
| 304 | + self.bookmarks.removeByJid(bookmark.jid); |
| 305 | + } |
| 306 | + }); |
| 307 | + }); |
| 308 | + }; |
| 309 | + |
| 310 | + self._serverHasBookmark = function (jid, data) { |
| 311 | + var has = false; |
| 312 | + for (var key in data) { |
| 313 | + data[key].forEach(function (bookmark) { |
| 314 | + if (bookmark.jid == jid) { |
| 315 | + has = true; |
| 316 | + } |
| 317 | + }) |
| 318 | + } |
| 319 | + return has; |
| 320 | + } |
| 321 | + |
92 | 322 | return self; |
93 | 323 | }(CandyShop.Bookmark || {}, Candy, jQuery)); |
0 commit comments