-
Notifications
You must be signed in to change notification settings - Fork 3
/
main.js
320 lines (283 loc) · 8.42 KB
/
main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
// note that we're already in the background page scope here, but I'd like to
// not rely on that fact
var app = chrome.extension.getBackgroundPage();
var comm = new Comm();
var just_joined = false; // used to track joins (data/user.js)
/**
* This is the main extension object. Not only does it hold a number of function
* and data to make the addon work, it's also the scope the other pieces of the
* addon load themselves into. This gives the extension a single namespace for
* all its libraries.
*/
var ext = {
// holds "install" "upgrade" "downgrade" "open"
load_reason: false,
// tracks all open Turtl tabs
app_tabs: [],
// holds the tab object of the last tab opened
last_opened_tab: false,
/**
* Called on extension init. Initializes any listeners for the "clean slate"
* addon state.
*/
setup: function()
{
// remove all events
comm.unbind();
ext.invites.init();
ext.messages.init();
ext.personas.init();
// bind to login/logout
app.turtl.user.bind('login', function() {
ext.do_login({join: just_joined});
just_joined = false;
});
app.turtl.user.bind('logout', ext.do_logout);
// sometimes we want to open the personas dialogue from within the app
comm.bind('personas-add-open', function() {
var container = ext.panel.last_container;
var inject = ext.panel.last_inject;
ext.panel.release();
ext.personas.open(container, inject, {add: true});
});
// when we get a resize event from a panel controller, tell the panel
// to resize
comm.bind('resize', function() {
ext.panel.reset_height();
});
// when the main panel (ext popup) closes, release the controller loaded
// in the panel (if one exists)
comm.bind('panel-close', function() {
ext.panel.release();
comm.unbind_context('panel');
});
// listen for new messages/notifications
comm.bind('num-messages', function() {
// messages are folded into invites for now
ext.invites.notify();
});
// listen for changes in invite/message data
comm.bind('invites-change', function() {
ext.update_badge();
});
},
/**
* Called when we wish to open the Turtl app in a tab. If turtl already
* exists in a tab in this window, we activate that tab (instead of opening
* a new one). IF there's no Turtl app tab opened in this window, we open
* one and set up event handling for it.
*/
open_app: function()
{
// get the current window (async)
chrome.windows.getLastFocused(function(win) {
// check if this window has an app tab already
var window_id = win.id;
chrome.tabs.query({windowId: window_id}, function(tabs) {
var tab = tabs.filter(function(t) {
return t.url.match(chrome.extension.getURL('/'));
});
if(tab[0])
{
// found a tab! select the first one
chrome.tabs.update(tab[0].id, {selected: true});
return true;
}
// no app tab for this window! create one.
chrome.tabs.create({
url: chrome.extension.getURL('/data/index.html'),
}, function(tab) {
// set up a PERSONALIZED comm for this tab, allowing it to pass
// its own events around without muddying up our global comm
// object
tab.comm = new Comm();
ext.setup_tab(tab);
// track the tab/window pair
ext.app_tabs.push(tab);
// track last opened tab so it can grab its own comm object from
// ext once it runs its setup. bit of a hack, but hoesntly works
// 10x better than trying to pull out the window object via
// ext.getViews() and trying to time the injection right. ugh.
ext.last_opened_tab = tab;
});
});
});
},
close_app_tab: function(tab_id, options)
{
options || (options = {});
// close the tab if needed
if(options.do_close) chrome.tabs.remove(tab_id);
ext.app_tabs = ext.app_tabs.filter(function(tab) {
// if the tab is being removed, unbind its comm object
if(tab.id == tab_id)
{
console.log('tab: '+ tab_id +': unbind comm');
tab.comm.unbind();
tab.comm = false;
comm.unbind_context(tab);
}
return !(tab.id == tab_id);
});
},
/**
* Set up event listening/forwarding for a newly opened app tab. Mainly what
* we do here is wire up events between the new tab's port (tabcomm) and the
* global/background port (comm).
*/
setup_tab: function(tab)
{
// forward some background comm events to this tab's comm. note that we
// pass the tab object as the binding context (useful for unbinding all
// the tab's events later on)
tab.comm.bind('logout', function() {
app.turtl.user.logout();
});
tab.comm.bind('personas-add-open', function() { comm.trigger('personas-add-open'); });
tab.comm.bind('tab-unload', function() {
// we wait here because this may have been initiated from a tab
// close, in which case we don't actually want to do anything.
if(!tab.comm) return false;
(function() {
chrome.tabs.get(tab.id, function(query_tab) {
if(!tab.comm) return false;
if(!query_tab.url.match(chrome.extension.getURL('/')))
{
// tab URL changed! unbind the comm/untrack tab
ext.close_app_tab(tab.id);
}
else
{
// tab was reloaded, need to re-setup comm
console.log('open tab');
ext.close_app_tab(tab.id, {do_close: true});
ext.open_app.delay(100, this);
}
});
}).delay(10, this);
});
},
/**
* Set a loading gif icon into the browser action icon. Doesn't work because
* the chrome devs don't want to support gif icons. Fair enough.
*/
loading: function(yesno)
{
yesno || (yesno = false);
if(yesno)
{
chrome.browserAction.setIcon({
path: '/data/app/images/site/icons/load_16x16.gif'
});
}
else
{
chrome.browserAction.setIcon({
path: '/data/app/images/favicon.19.png'
});
}
},
/**
* Called when a user logs in (via the ext login popup).
*/
do_login: function(options)
{
options || (options = {});
ext.loading(true);
chrome.browserAction.disable();
comm.bind('profile-load-complete', function() {
ext.loading(false);
comm.unbind('profile-load-complete', arguments.callee);
chrome.browserAction.enable();
chrome.browserAction.setPopup({popup: '/data/menu.html'});
var login_finish = function()
{
// update invites/badge
ext.invites.notify();
ext.update_badge();
};
if(options.join)
{
// if we're here, the personas dialog is already showing. wait
// for it to close then finish logging in
comm.bind('panel-close', function() {
comm.unbind('panel-close', arguments.callee);
login_finish();
// open the app once we're done
ext.open_app();
});
}
else
{
login_finish();
}
});
},
/**
* Called when a user logs out of Turtl
*/
do_logout: function(options)
{
options || (options = {});
// close all app tabs
chrome.tabs.remove(ext.app_tabs.map(function(tab) { return tab.id; }));
chrome.browserAction.setPopup({popup: '/data/user.html'});
chrome.browserAction.setIcon({
path: '/data/app/images/favicon.19.gray.png'
});
ext.update_badge();
// run setup again
if(!options.skip_setup) ext.setup();
},
update_badge: function()
{
var badge = '';
if(app.turtl.user && app.turtl.user.logged_in && app.turtl.user.get('personas').models().length > 0)
{
badge = ext.invites.num_pending() + ext.messages.num_pending();
badge = badge.toString();
}
if(badge == '0') badge = '';
chrome.browserAction.setBadgeText({text: badge});
},
// NOTE: other libs (bookmark, personas, etc) will add their own objects
// into the `ext` namespace
};
// listen for tab closes and update our app tab list as needed
chrome.tabs.onRemoved.addListener(function(tab_id, info) {
ext.close_app_tab(tab_id);
});
// listen for commands!
chrome.commands.onCommand.addListener(function(cmd) {
switch(cmd)
{
case 'open':
if(app.turtl.user.logged_in) ext.open_app();
break;
case 'logout':
app.turtl.user.logout();
break;
}
});
// set up the app once all the background stuff is done loading
comm.bind('loaded', function() {
ext.setup();
});
// this sets up the main menu
ext.do_logout({skip_setup: true});
// determine what kind of run we're doing
var cur_version = chrome.app.getDetails().version;
if(!localStorage.version)
{
ext.load_reason = 'install';
}
else
{
var last_version = localStorage.version;
var comp = compare_versions(cur_version, last_version);
if(comp > 0) ext.load_reason = 'upgrade';
if(comp < 0) ext.load_reason = 'downgrade';
if(comp == 0) ext.load_reason = 'open';
}
console.log('load reason: ', ext.load_reason);
localStorage.version = cur_version;