~allenap/launchpad/localpackagediffs-time-out-bug-798301

« back to all changes in this revision

Viewing changes to lib/lp/bugs/javascript/bugtask_index_portlets.js

  • Committer: Launchpad Patch Queue Manager
  • Date: 2011-06-16 13:03:29 UTC
  • mfrom: (13246.1.1 revert-13243)
  • Revision ID: launchpad@pqm.canonical.com-20110616130329-fknnte1z05adfwqm
[testfix][rs=danilo][no-qa] Revert r13243, bug 772754 fix.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* Copyright 2011 Canonical Ltd.  This software is licensed under the
 
2
 * GNU Affero General Public License version 3 (see the file LICENSE).
 
3
 *
 
4
 * Form overlay widgets and subscriber handling for bug pages.
 
5
 *
 
6
 * @module bugs
 
7
 * @submodule bugtask_index.portlets
 
8
 */
 
9
 
 
10
YUI.add('lp.bugs.bugtask_index.portlets', function(Y) {
 
11
 
 
12
var namespace = Y.namespace('lp.bugs.bugtask_index.portlets');
 
13
 
 
14
// The launchpad js client used.
 
15
var lp_client;
 
16
 
 
17
// The launchpad client entry for the current bug.
 
18
var lp_bug_entry;
 
19
 
 
20
// The bug itself, taken from cache.
 
21
var bug_repr;
 
22
 
 
23
// A boolean telling us whether advanced subscription features are to be
 
24
// used or not.
 
25
var use_advanced_subscriptions = false;
 
26
 
 
27
var subscription_labels = Y.lp.bugs.subscriber.subscription_labels;
 
28
 
 
29
submit_button_html =
 
30
    '<button type="submit" name="field.actions.change" ' +
 
31
    'value="Change" class="lazr-pos lazr-btn" >OK</button>';
 
32
cancel_button_html =
 
33
    '<button type="button" name="field.actions.cancel" ' +
 
34
    'class="lazr-neg lazr-btn" >Cancel</button>';
 
35
 
 
36
// The set of subscriber CSS IDs as a JSON struct.
 
37
var subscriber_ids;
 
38
 
 
39
/*
 
40
 * An object representing the bugtask subscribers portlet.
 
41
 *
 
42
 * Since the portlet loads via XHR and inline subscribing
 
43
 * depends on that portlet being loaded, setup a custom
 
44
 * event object, to provide a hook for initializing subscription
 
45
 * link callbacks after custom events.
 
46
 */
 
47
var PortletTarget = function() {};
 
48
Y.augment(PortletTarget, Y.Event.Target);
 
49
namespace.portlet = new PortletTarget();
 
50
 
 
51
/*
 
52
 * Create the lp client and bug entry if we haven't done so already.
 
53
 *
 
54
 * @method setup_client_and_bug
 
55
 */
 
56
function setup_client_and_bug() {
 
57
    lp_client = new Y.lp.client.Launchpad();
 
58
 
 
59
    if (bug_repr === undefined) {
 
60
        bug_repr = LP.cache.bug;
 
61
        lp_bug_entry = new Y.lp.client.Entry(
 
62
            lp_client, bug_repr, bug_repr.self_link);
 
63
    }
 
64
}
 
65
 
 
66
namespace.load_subscribers_portlet = function(
 
67
        subscription_link, subscription_link_handler) {
 
68
    if (Y.UA.ie) {
 
69
        return null;
 
70
    }
 
71
 
 
72
    Y.one('#subscribers-portlet-spinner').setStyle('display', 'block');
 
73
 
 
74
    function hide_spinner() {
 
75
        Y.one('#subscribers-portlet-spinner').setStyle('display', 'none');
 
76
            // Fire a custom event to notify that the initial click
 
77
            // handler on subscription_link set above should be
 
78
            // cleared.
 
79
            if (namespace) {
 
80
                namespace.portlet.fire(
 
81
                  'bugs:portletloadfailed', subscription_link_handler);
 
82
        }
 
83
    }
 
84
 
 
85
    function setup_portlet(transactionid, response, args) {
 
86
        hide_spinner();
 
87
        Y.one('#portlet-subscribers')
 
88
            .appendChild(Y.Node.create(response.responseText));
 
89
 
 
90
        // Fire a custom portlet loaded event to notify when
 
91
        // it's safe to setup subscriber link callbacks.
 
92
        namespace.portlet.fire('bugs:portletloaded');
 
93
    }
 
94
 
 
95
    var config = {on: {success: setup_portlet,
 
96
                       failure: hide_spinner}};
 
97
    var url = Y.one(
 
98
        '#subscribers-content-link').getAttribute('href').replace(
 
99
            'bugs.', '');
 
100
    Y.io(url, config);
 
101
};
 
102
 
 
103
 
 
104
namespace.setup_portlet_handlers = function() {
 
105
    namespace.portlet.subscribe('bugs:portletloaded', function() {
 
106
        load_subscriber_ids();
 
107
    });
 
108
    /*
 
109
     * If the subscribers portlet fails to load, clear any
 
110
     * click handlers, so the normal subscribe page can be reached.
 
111
     */
 
112
    namespace.portlet.subscribe('bugs:portletloadfailed', function(handler) {
 
113
        handler.detach();
 
114
    });
 
115
    namespace.portlet.subscribe('bugs:dupeportletloaded', function() {
 
116
        setup_subscription_link_handlers();
 
117
        setup_unsubscribe_icon_handlers();
 
118
    });
 
119
    /* If the dupe subscribers portlet fails to load,
 
120
     * be sure to try to handle any unsub icons that may
 
121
     * exist for others.
 
122
     */
 
123
    namespace.portlet.subscribe(
 
124
        'bugs:dupeportletloadfailed',
 
125
        function(handlers) {
 
126
            setup_subscription_link_handlers();
 
127
            setup_unsubscribe_icon_handlers();
 
128
        });
 
129
 
 
130
    /* If loading the subscriber IDs JSON has succeeded, set up the
 
131
     * subscription link handlers and load the subscribers from dupes.
 
132
     */
 
133
    namespace.portlet.subscribe(
 
134
        'bugs:portletsubscriberidsloaded',
 
135
        function() {
 
136
            load_subscribers_from_duplicates();
 
137
        });
 
138
 
 
139
    /* If loading the subscriber IDs JSON fails we still need to load the
 
140
     * subscribers from duplicates but we don't set up the subscription link
 
141
     * handlers.
 
142
     */
 
143
    namespace.portlet.subscribe(
 
144
        'bugs:portletsubscriberidsfailed',
 
145
        function() {
 
146
            load_subscribers_from_duplicates();
 
147
        });
 
148
 
 
149
    /*
 
150
     * Subscribing someone else requires loading a grayed out
 
151
     * username into the DOM until the subscribe action completes.
 
152
     * There are a couple XHR requests in check_can_be_unsubscribed
 
153
     * before the subscribe work can be done, so fire a custom event
 
154
     * bugs:nameloaded and do the work here when the event fires.
 
155
     */
 
156
    namespace.portlet.subscribe('bugs:nameloaded', function(subscription) {
 
157
        var error_handler = new Y.lp.client.ErrorHandler();
 
158
        error_handler.clearProgressUI = function() {
 
159
            var temp_link = Y.one('#temp-username');
 
160
            if (temp_link) {
 
161
                var temp_parent = temp_link.get('parentNode');
 
162
                temp_parent.removeChild(temp_link);
 
163
            }
 
164
        };
 
165
        error_handler.showError = function(error_msg) {
 
166
            Y.lp.app.errors.display_error(
 
167
                Y.one('.menu-link-addsubscriber'), error_msg);
 
168
        };
 
169
 
 
170
        var config = {
 
171
            on: {
 
172
                success: function() {
 
173
                    var temp_link = Y.one('#temp-username');
 
174
                    var temp_spinner = Y.one('#temp-name-spinner');
 
175
                    temp_link.removeChild(temp_spinner);
 
176
                    var anim = Y.lazr.anim.green_flash({ node: temp_link });
 
177
                    anim.on('end', function() {
 
178
                        add_user_name_link(subscription);
 
179
                        var temp_parent = temp_link.get('parentNode');
 
180
                        temp_parent.removeChild(temp_link);
 
181
                    });
 
182
                    anim.run();
 
183
                },
 
184
                failure: error_handler.getFailureHandler()
 
185
            },
 
186
            parameters: {
 
187
                person: Y.lp.client.get_absolute_uri(
 
188
                    subscription.get('person').get('escaped_uri')),
 
189
                suppress_notify: false
 
190
            }
 
191
        };
 
192
        lp_client.named_post(bug_repr.self_link, 'subscribe', config);
 
193
    });
 
194
};
 
195
 
 
196
function load_subscriber_ids() {
 
197
    function on_success(transactionid, response, args) {
 
198
        try {
 
199
            subscriber_ids = Y.JSON.parse(response.responseText);
 
200
 
 
201
            // Fire a custom event to trigger the setting-up of the
 
202
            // subscription handlers.
 
203
            namespace.portlet.fire('bugs:portletsubscriberidsloaded');
 
204
        } catch (e) {
 
205
            // Fire an event to signal failure. This ensures that the
 
206
            // subscribers-from-dupes still get loaded into the portlet.
 
207
            namespace.portlet.fire('bugs:portletsubscriberidsfailed');
 
208
        }
 
209
    }
 
210
 
 
211
    function on_failure() {
 
212
        // Fire an event to signal failure. This ensures that the
 
213
        // subscribers-from-dupes still get loaded into the portlet.
 
214
        namespace.portlet.fire('bugs:portletsubscriberidsfailed');
 
215
    }
 
216
 
 
217
    var config = {on: {success: on_success,
 
218
                       failure: on_failure}};
 
219
    var url = Y.one(
 
220
        '#subscribers-ids-link').getAttribute('href');
 
221
    Y.io(url, config);
 
222
}
 
223
 
 
224
/*
 
225
 * Set click handlers for unsubscribe remove icons.
 
226
 *
 
227
 * @method setup_unsubscribe_icon_handlers
 
228
 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
 
229
 */
 
230
function setup_unsubscribe_icon_handlers() {
 
231
    var subscription = new Y.lp.bugs.subscriber.Subscription({
 
232
        link: Y.one('.menu-link-subscription'),
 
233
        spinner: Y.one('#sub-unsub-spinner'),
 
234
        subscriber: new Y.lp.bugs.subscriber.Subscriber({
 
235
            uri: LP.links.me,
 
236
            subscriber_ids: subscriber_ids
 
237
        })
 
238
    });
 
239
 
 
240
    Y.on('click', function(e) {
 
241
        e.halt();
 
242
        unsubscribe_user_via_icon(e.target, subscription);
 
243
    }, '.unsub-icon');
 
244
}
 
245
 
 
246
 
 
247
/**
 
248
 * We can have at most one advanced subscription overlay shown,
 
249
 * so we keep it globally to be able to clean-up before constructing
 
250
 * another one.
 
251
 */
 
252
var subscription_overlay;
 
253
 
 
254
/**
 
255
 * Cleans-up the existing subscription overlay (if any).
 
256
 */
 
257
function remove_subscription_overlay() {
 
258
    if (Y.Lang.isValue(subscription_overlay)) {
 
259
        subscription_overlay.get('boundingBox').remove();
 
260
    }
 
261
    subscription_overlay = undefined;
 
262
}
 
263
 
 
264
/**
 
265
 * Creates and shows a new subscription overlay for the given subscription.
 
266
 */
 
267
function create_new_subscription_overlay(subscription, header_text) {
 
268
    remove_subscription_overlay();
 
269
    subscription_overlay = setup_advanced_subscription_overlay(
 
270
        subscription, header_text);
 
271
    load_and_show_advanced_subscription_overlay(
 
272
        subscription, subscription_overlay);
 
273
}
 
274
 
 
275
/*
 
276
 * Set up the handlers for the mute / unmute link.
 
277
 */
 
278
function setup_mute_link_handlers() {
 
279
    if (LP.links.me === undefined) {
 
280
        return;
 
281
    }
 
282
    var link = Y.one('.menu-link-mute_subscription');
 
283
    if (Y.Lang.isNull(link)) {
 
284
        return;
 
285
    }
 
286
    link.addClass('js-action');
 
287
    setup_client_and_bug();
 
288
    link.on('click', _get_toggle_mute(link));
 
289
}
 
290
 
 
291
// This is a helper, factored out for reusability by setup_mute_link_handlers
 
292
// and toggle_mute.
 
293
function _get_toggle_mute(link) {
 
294
    var parent_node = link.get('parentNode');
 
295
    var spinner = Y.one('#mute-unmute-spinner');
 
296
    var hide_spinner = function() {
 
297
        link.set('display', 'inline');
 
298
        spinner.set('display', 'none');
 
299
    };
 
300
    var handler = new Y.lp.client.ErrorHandler();
 
301
    handler.showError = function(error_msg) {
 
302
        Y.lp.app.errors.display_error(link, error_msg);
 
303
    };
 
304
    handler.clearProgressUI = hide_spinner;
 
305
    return function (e, do_next) {
 
306
        if (Y.Lang.isValue(e)) {
 
307
            e.halt();
 
308
        }
 
309
        var is_muted = parent_node.hasClass('muted-true');
 
310
        var spinner_text, method_name;
 
311
        if (! is_muted) {
 
312
            spinner_text = 'Muting...';
 
313
            method_name = 'mute';
 
314
        } else {
 
315
            spinner_text = 'Unmuting...';
 
316
            method_name = 'unmute';
 
317
        }
 
318
        spinner.set('innerHTML', spinner_text);
 
319
        link.set('display', 'none');
 
320
        spinner.set('display', 'block');
 
321
        var config = {
 
322
            on: {
 
323
                success: function(response) {
 
324
                    is_muted = ! is_muted; // We successfully toggled.
 
325
                    var subscription = get_subscribe_self_subscription();
 
326
                    subscription.enable_spinner('Updating...');
 
327
                    var subscription_link = subscription.get('link');
 
328
                    var is_subscribed = false;
 
329
                    var is_dupe_subscribed =
 
330
                        subscription.has_duplicate_subscriptions();
 
331
                    var label = subscription_labels.SUBSCRIBE;
 
332
                    update_mute_after_subscription_change(is_muted);
 
333
                    if (is_muted) {
 
334
                        // Remove the subscriber's name from the subscriber
 
335
                        // list, if it's there.
 
336
                        Y.lp.bugs.subscribers_list.remove_user_link(
 
337
                            subscription.get('subscriber'));
 
338
                    } else {
 
339
                        // When unmuting, the ``response`` is the previously
 
340
                        // muted subscription, or null.
 
341
                        is_subscribed = ! Y.Lang.isNull(response);
 
342
                        if (is_subscribed) {
 
343
                            subscription.set('is_direct', true);
 
344
                            add_user_name_link(subscription);
 
345
                            label = subscription_labels.EDIT;
 
346
                        }
 
347
                    }
 
348
                    hide_spinner();
 
349
                    Y.lazr.anim.green_flash({ node: parent_node }).run();
 
350
                    set_subscription_link_parent_class(
 
351
                        subscription_link, is_subscribed, is_dupe_subscribed);
 
352
                    subscription.disable_spinner(label);
 
353
                    if (Y.Lang.isFunction(do_next)) {
 
354
                        do_next(response);
 
355
                    }
 
356
                },
 
357
                failure: handler.getFailureHandler()
 
358
            },
 
359
            parameters: {}
 
360
        };
 
361
        lp_client.named_post(bug_repr.self_link, method_name, config);
 
362
    };
 
363
}
 
364
 
 
365
/*
 
366
 * Toggle the mute state.
 
367
 */
 
368
 
 
369
function toggle_mute(do_next) {
 
370
    if (LP.links.me === undefined) {
 
371
        return;
 
372
    }
 
373
    var link = Y.one('.menu-link-mute_subscription');
 
374
    if (Y.Lang.isNull(link)) {
 
375
        return;
 
376
    }
 
377
    return _get_toggle_mute(link)(null, do_next);
 
378
}
 
379
 
 
380
/*
 
381
 * Update the Mute link after the user's subscriptions or mutes have
 
382
 * changed.
 
383
 */
 
384
function update_mute_after_subscription_change(is_muted) {
 
385
    var link = Y.one('.menu-link-mute_subscription');
 
386
    var parent_node = link.get('parentNode');
 
387
    if (Y.Lang.isUndefined(is_muted)) {
 
388
        is_muted = parent_node.hasClass('muted-true');
 
389
    }
 
390
    parent_node.removeClass('hidden');
 
391
    if (is_muted) {
 
392
        parent_node.replaceClass('muted-false', 'muted-true');
 
393
        link.set('innerHTML', "Unmute bug mail");
 
394
        link.replaceClass('mute', 'unmute');
 
395
    } else {
 
396
        parent_node.replaceClass('muted-true', 'muted-false');
 
397
        link.set('innerHTML', "Mute bug mail");
 
398
        link.replaceClass('unmute', 'mute');
 
399
    }
 
400
}
 
401
 
 
402
/*
 
403
 * Set up and return a Subscription object for the direct subscription
 
404
 * link.
 
405
 */
 
406
function get_subscribe_self_subscription() {
 
407
    setup_client_and_bug();
 
408
    var subscription = new Y.lp.bugs.subscriber.Subscription({
 
409
        link: Y.one('.menu-link-subscription'),
 
410
        spinner: Y.one('#sub-unsub-spinner'),
 
411
        subscriber: new Y.lp.bugs.subscriber.Subscriber({
 
412
            uri: LP.links.me,
 
413
            subscriber_ids: subscriber_ids
 
414
        })
 
415
    });
 
416
 
 
417
    subscription.set('can_be_unsubscribed', true);
 
418
    subscription.set('person', subscription.get('subscriber'));
 
419
    subscription.set('is_team', false);
 
420
    var css_name = subscription.get('person').get('css_name');
 
421
    var direct_css_name = '#direct-' + css_name;
 
422
    var direct_node = Y.one(direct_css_name);
 
423
    var is_direct = direct_node !== null;
 
424
    subscription.set('is_direct', is_direct);
 
425
    var dupe_css_name = '#dupe-' + css_name;
 
426
    var dupe_node = Y.one(dupe_css_name);
 
427
    var has_dupes = dupe_node !== null;
 
428
    subscription.set('has_dupes', has_dupes);
 
429
    return subscription;
 
430
}
 
431
 
 
432
 
 
433
/*
 
434
 * Set up and return a Subscription object for the team subscription
 
435
 * link.
 
436
 */
 
437
function get_team_subscription(team_uri) {
 
438
    setup_client_and_bug();
 
439
    var subscription = new Y.lp.bugs.subscriber.Subscription({
 
440
        link: Y.one('.menu-link-subscription'),
 
441
        spinner: Y.one('#sub-unsub-spinner'),
 
442
        subscriber: new Y.lp.bugs.subscriber.Subscriber({
 
443
            uri: team_uri,
 
444
            subscriber_ids: subscriber_ids
 
445
        })
 
446
    });
 
447
 
 
448
    subscription.set('is_direct', true);
 
449
    subscription.set('has_dupes', false);
 
450
    subscription.set('can_be_unsubscribed', true);
 
451
    subscription.set('person', subscription.get('subscriber'));
 
452
    subscription.set('is_team', true);
 
453
    return subscription;
 
454
}
 
455
 
 
456
/*
 
457
 * Initialize callbacks for subscribe/unsubscribe links.
 
458
 *
 
459
 * @method setup_subscription_link_handlers
 
460
 */
 
461
function setup_subscription_link_handlers() {
 
462
    if (LP.links.me === undefined) {
 
463
        return;
 
464
    }
 
465
 
 
466
    var subscription = get_subscribe_self_subscription();
 
467
 
 
468
    if (subscription.is_node()) {
 
469
        subscription.get('link').on('click', function(e) {
 
470
            e.halt();
 
471
            subscription.set('can_be_unsubscribed', true);
 
472
            subscription.set('person', subscription.get('subscriber'));
 
473
            subscription.set('is_team', false);
 
474
            var parent = e.target.get('parentNode');
 
475
            if (namespace.use_advanced_subscriptions) {
 
476
                create_new_subscription_overlay(
 
477
                    subscription, "Subscribe to bug");
 
478
            } else {
 
479
                // Look for the false conditions of subscription, which
 
480
                // is_direct_subscription, etc. don't report correctly,
 
481
                // to make sure we only use subscribe_current_user for
 
482
                // the current user.
 
483
                if (parent.hasClass('subscribed-false') &&
 
484
                    parent.hasClass('dup-subscribed-false')) {
 
485
                    subscribe_current_user(subscription);
 
486
                }
 
487
                else {
 
488
                    unsubscribe_current_user(subscription);
 
489
                }
 
490
            }
 
491
        });
 
492
        subscription.get('link').addClass('js-action');
 
493
    }
 
494
 
 
495
    setup_subscribe_someone_else_handler(subscription);
 
496
    if (namespace.use_advanced_subscriptions) {
 
497
        setup_mute_link_handlers();
 
498
    }
 
499
}
 
500
 
 
501
function load_subscribers_from_duplicates() {
 
502
    if (Y.UA.ie) {
 
503
        return null;
 
504
    }
 
505
 
 
506
    Y.one('#subscribers-portlet-dupe-spinner').setStyle(
 
507
        'display', 'block');
 
508
 
 
509
    function hide_spinner() {
 
510
        Y.one('#subscribers-portlet-dupe-spinner').setStyle(
 
511
            'display', 'none');
 
512
    }
 
513
 
 
514
    function on_failure(transactionid, response, args) {
 
515
        hide_spinner();
 
516
        // Fire a custom event to signal failure, so that
 
517
        // any remaining unsub icons can be hooked up.
 
518
        namespace.portlet.fire('bugs:dupeportletloadfailed');
 
519
    }
 
520
 
 
521
    function on_success(transactionid, response, args) {
 
522
        hide_spinner();
 
523
 
 
524
        var dupe_subscribers_container = Y.one(
 
525
            '#subscribers-from-duplicates-container');
 
526
        dupe_subscribers_container.set(
 
527
            'innerHTML',
 
528
            dupe_subscribers_container.get('innerHTML') +
 
529
            response.responseText);
 
530
 
 
531
        // Fire a custom portlet loaded event to notify when
 
532
        // it's safe to setup dupe subscriber link callbacks.
 
533
        namespace.portlet.fire('bugs:dupeportletloaded');
 
534
    }
 
535
 
 
536
    var config = {on: {success: on_success,
 
537
                       failure: on_failure}};
 
538
    var url = Y.one(
 
539
        '#subscribers-from-dupes-content-link').getAttribute(
 
540
            'href').replace('bugs.', '');
 
541
    Y.io(url, config);
 
542
}
 
543
 
 
544
/*
 
545
 * Add the user name to the subscriber's list.
 
546
 *
 
547
 * @method add_user_name_link
 
548
 */
 
549
function add_user_name_link(subscription) {
 
550
    // Be paranoid about display_name, since timeouts or other errors
 
551
    // could mean display_name wasn't set on initialization.
 
552
    subscription.get('person').set_display_name(function () {
 
553
        _add_user_name_link(subscription);
 
554
    });
 
555
}
 
556
 
 
557
function _add_user_name_link(subscription) {
 
558
    var person = subscription.get('person');
 
559
    var link_node = build_user_link_html(subscription);
 
560
    var subscribers = Y.one('#subscribers-links');
 
561
    if (subscription.is_current_user_subscribing()) {
 
562
        // If this is the current user, then top post the name and be done.
 
563
        subscribers.insertBefore(link_node, subscribers.get('firstChild'));
 
564
    } else {
 
565
        var next = get_next_subscriber_node(subscription);
 
566
        if (next) {
 
567
            subscribers.insertBefore(link_node, next);
 
568
        } else {
 
569
            subscribers.appendChild(link_node);
 
570
        }
 
571
    }
 
572
    // Handle the case of no previous subscribers.
 
573
    var none_subscribers = Y.one('#none-subscribers');
 
574
    if (none_subscribers) {
 
575
        var none_parent = none_subscribers.get('parentNode');
 
576
        none_parent.removeChild(none_subscribers);
 
577
    }
 
578
    // Highlight the new addition with a green flash.
 
579
    Y.lazr.anim.green_flash({ node: link_node }).run();
 
580
    // Set the click handler if adding a remove icon.
 
581
    if (subscription.can_be_unsubscribed_by_user()) {
 
582
        var remove_icon =
 
583
          Y.one('#unsubscribe-icon-' + person.get('css_name'));
 
584
        remove_icon.on('click', function(e) {
 
585
            e.halt();
 
586
            unsubscribe_user_via_icon(e.target, subscription);
 
587
        });
 
588
    }
 
589
}
 
590
 
 
591
/*
 
592
 * Unsubscribe a user from this bugtask when a remove icon is clicked.
 
593
 *
 
594
 * @method unsubscribe_user_via_icon
 
595
 * @param icon {Node} The remove icon that was clicked.
 
596
 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
 
597
*/
 
598
function unsubscribe_user_via_icon(icon, subscription) {
 
599
    icon.set('src', '/@@/spinner');
 
600
    var icon_parent = icon.get('parentNode');
 
601
 
 
602
    var user_uri = get_user_uri_from_icon(icon);
 
603
    var person = new Y.lp.bugs.subscriber.Subscriber({
 
604
        uri: user_uri,
 
605
        subscriber_ids: subscriber_ids
 
606
    });
 
607
    subscription.set('person', person);
 
608
 
 
609
    // Determine if this is a dupe.
 
610
    var is_dupe;
 
611
    var icon_parent_div = icon_parent.get('parentNode');
 
612
    var dupe_id = 'dupe-' + person.get('css_name');
 
613
    if (icon_parent_div.get('id') === dupe_id) {
 
614
        is_dupe = true;
 
615
    } else {
 
616
        is_dupe = false;
 
617
    }
 
618
 
 
619
    var error_handler = new Y.lp.client.ErrorHandler();
 
620
    error_handler.clearProgressUI = function () {
 
621
        icon.set('src', '/@@/remove');
 
622
        // Grab the icon again to reset to click handler.
 
623
        var unsubscribe_icon = Y.one(
 
624
            '#unsubscribe-icon-' + person.get('css_name'));
 
625
        unsubscribe_icon.on('click', function(e) {
 
626
            e.halt();
 
627
            unsubscribe_user_via_icon(e.target, subscription);
 
628
        });
 
629
 
 
630
    };
 
631
    error_handler.showError = function (error_msg) {
 
632
        var flash_node = Y.one('.' + person.get('css_name'));
 
633
        Y.lp.app.errors.display_error(flash_node, error_msg);
 
634
 
 
635
    };
 
636
 
 
637
    var subscription_link = subscription.get('link');
 
638
    var config = {
 
639
        on: {
 
640
            success: function(client) {
 
641
                var num_person_links = Y.all(
 
642
                    '.' + person.get('css_name')).size();
 
643
                Y.lp.bugs.subscribers_list.remove_user_link(person, is_dupe);
 
644
                var has_direct, has_dupes;
 
645
                if (num_person_links === 1 &&
 
646
                    subscription.is_current_user_subscribing()) {
 
647
                    // Current user has been completely unsubscribed.
 
648
                    subscription.disable_spinner(
 
649
                        subscription_labels.SUBSCRIBE);
 
650
                    has_direct = false;
 
651
                    has_dupes = false;
 
652
                } else {
 
653
                    // If we removed the duplicate subscription,
 
654
                    // we are left with the direct one, and vice versa.
 
655
                    has_direct = is_dupe;
 
656
                    has_dupes = !is_dupe;
 
657
                }
 
658
                subscription.set('is_direct', has_direct);
 
659
                subscription.set('has_dupes', has_dupes);
 
660
                set_subscription_link_parent_class(
 
661
                    subscription_link, has_direct, has_dupes);
 
662
            },
 
663
 
 
664
            failure: error_handler.getFailureHandler()
 
665
        }
 
666
    };
 
667
 
 
668
    if (!subscription.is_current_user_subscribing()) {
 
669
        config.parameters = {
 
670
            person: Y.lp.client.get_absolute_uri(user_uri)
 
671
        };
 
672
    }
 
673
 
 
674
    if (is_dupe) {
 
675
        lp_client.named_post(
 
676
            bug_repr.self_link, 'unsubscribeFromDupes', config);
 
677
    } else {
 
678
        lp_client.named_post(bug_repr.self_link, 'unsubscribe', config);
 
679
    }
 
680
}
 
681
 
 
682
/*
 
683
 * Create and return a FormOverlay for advanced subscription
 
684
 * interactions.
 
685
 *
 
686
 * @method setup_advanced_subscription_overlay
 
687
 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
 
688
 */
 
689
function setup_advanced_subscription_overlay(subscription, text) {
 
690
    var header = Y.Node.create('<h2 />').set('text', text);
 
691
    var subscription_overlay = new Y.lazr.FormOverlay({
 
692
        headerContent: header,
 
693
        form_submit_button:
 
694
            Y.Node.create(submit_button_html),
 
695
        form_cancel_button:
 
696
            Y.Node.create(cancel_button_html),
 
697
        centered: true,
 
698
        visible: false
 
699
    });
 
700
    subscription_overlay.render('#privacy-form-container');
 
701
    return subscription_overlay;
 
702
}
 
703
 
 
704
/*
 
705
 * Load the content for and display the advanced subscription overlay.
 
706
 * The call to show() the overlay happens only when the form has been
 
707
 * loaded. That way the overlay won't appear empty.
 
708
 *
 
709
 * @method load_and_show_advanced_subscription_overlay
 
710
 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
 
711
 * @param subscription_overlay {Object} A Y.lazr.FormOverlay to load
 
712
 *                                      content into.
 
713
 */
 
714
function load_and_show_advanced_subscription_overlay(subscription,
 
715
                                                     subscription_overlay) {
 
716
    subscription.enable_spinner('Loading...');
 
717
    subscription_overlay.set(
 
718
        'form_submit_callback', function(form_data) {
 
719
            handle_advanced_subscription_overlay(form_data);
 
720
            subscription_overlay.hide();
 
721
    });
 
722
 
 
723
    var subscription_link_url = subscription.get(
 
724
        'link').get('href') + '/++form++';
 
725
    subscription_overlay.loadFormContentAndRender(subscription_link_url);
 
726
 
 
727
    Y.on('contentready', function() {
 
728
        Y.lp.bugs.bug_notification_level.setup();
 
729
    }, '.bug-notification-level-field, .bug-subscription-basic');
 
730
 
 
731
    // Show the overlay when the special event indicating that the form is
 
732
    // ready to be displayed is received.
 
733
    Y.on(['bugnotificationlevel:contentready', 'io:failure'], function() {
 
734
        subscription.disable_spinner();
 
735
        subscription_overlay.show();
 
736
    });
 
737
}
 
738
 
 
739
/*
 
740
 * Subscribe the current user via the LP API.
 
741
 *
 
742
 * @method subscribe_current_user
 
743
 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
 
744
 */
 
745
function subscribe_current_user(subscription) {
 
746
    subscription.enable_spinner('Subscribing...');
 
747
    var subscription_link = subscription.get('link');
 
748
    var subscriber = subscription.get('subscriber');
 
749
    var bug_notification_level = subscription.get('bug_notification_level');
 
750
 
 
751
    // This is always a direct subscription.
 
752
    subscription.set('is_direct', true);
 
753
 
 
754
    var error_handler = new Y.lp.client.ErrorHandler();
 
755
    error_handler.clearProgressUI = function () {
 
756
        subscription.disable_spinner();
 
757
    };
 
758
    error_handler.showError = function (error_msg) {
 
759
        Y.lp.app.errors.display_error(subscription_link, error_msg);
 
760
    };
 
761
 
 
762
    var config = {
 
763
        on: {
 
764
            success: function(client) {
 
765
                if (namespace.use_advanced_subscriptions) {
 
766
                    subscription.disable_spinner(
 
767
                        subscription_labels.EDIT);
 
768
                } else {
 
769
                    subscription.disable_spinner(
 
770
                        subscription_labels.UNSUBSCRIBE);
 
771
                }
 
772
 
 
773
                if (subscription.has_duplicate_subscriptions()) {
 
774
                    set_subscription_link_parent_class(
 
775
                        subscription_link, true, true);
 
776
                } else {
 
777
                    set_subscription_link_parent_class(
 
778
                        subscription_link, true, false);
 
779
                }
 
780
 
 
781
                // Handle the case where the subscriber's list displays
 
782
                // "No subscribers."
 
783
                var empty_subscribers = Y.one("#none-subscribers");
 
784
                if (empty_subscribers) {
 
785
                    var parent = empty_subscribers.get('parentNode');
 
786
                    parent.removeChild(empty_subscribers);
 
787
                }
 
788
 
 
789
                add_user_name_link(subscription);
 
790
                if (namespace.use_advanced_subscriptions) {
 
791
                    update_mute_after_subscription_change();
 
792
                }
 
793
            },
 
794
 
 
795
            failure: error_handler.getFailureHandler()
 
796
        },
 
797
 
 
798
        parameters: {
 
799
            person: Y.lp.client.get_absolute_uri(
 
800
                subscriber.get('escaped_uri')),
 
801
            suppress_notify: false,
 
802
            level: bug_notification_level
 
803
        }
 
804
    };
 
805
    lp_client.named_post(bug_repr.self_link, 'subscribe', config);
 
806
}
 
807
 
 
808
/*
 
809
 * Unsubscribe the current user via the LP API.
 
810
 *
 
811
 * @method unsubscribe_current_user
 
812
 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
 
813
 */
 
814
function unsubscribe_current_user(subscription) {
 
815
    subscription.enable_spinner('Unsubscribing...');
 
816
    var subscription_link = subscription.get('link');
 
817
    var subscriber = subscription.get('subscriber');
 
818
 
 
819
    var error_handler = new Y.lp.client.ErrorHandler();
 
820
    error_handler.clearProgressUI = function () {
 
821
        subscription.disable_spinner();
 
822
    };
 
823
    error_handler.showError = function (error_msg) {
 
824
        Y.lp.app.errors.display_error(subscription_link, error_msg);
 
825
    };
 
826
 
 
827
    var subscriber_link = Y.lp.client.get_absolute_uri(
 
828
        subscriber.get('escaped_uri'));
 
829
    var config = {
 
830
        on: {
 
831
            success: function(client) {
 
832
                // We need to track if this is direct or not,
 
833
                // before we toggle the is_direct flag.
 
834
                var original_is_direct = subscription.get('is_direct');
 
835
 
 
836
                if (subscription.is_direct_subscription() &&
 
837
                    subscription.has_duplicate_subscriptions()) {
 
838
                    // Don't change the 'Unsubscribe' text if
 
839
                    // dupe subscriptions remain.
 
840
                    subscription.disable_spinner();
 
841
                    set_subscription_link_parent_class(
 
842
                        subscription_link, false, true);
 
843
                    subscription.set('is_direct', false);
 
844
                } else if (subscription.is_direct_subscription() &&
 
845
                          !subscription.has_duplicate_subscriptions()) {
 
846
                    // Only unsub'ing a direct subscriber here.
 
847
                    subscription.disable_spinner(
 
848
                        subscription_labels.SUBSCRIBE);
 
849
                    set_subscription_link_parent_class(
 
850
                        subscription_link, false, false);
 
851
                    subscription.set('is_direct', false);
 
852
                } else {
 
853
                    // Only unsub'ing dupes here.
 
854
                    subscription.disable_spinner(
 
855
                        subscription_labels.SUBSCRIBE);
 
856
                    set_subscription_link_parent_class(
 
857
                        subscription_link, false, false);
 
858
                    subscription.set('has_dupes', false);
 
859
                }
 
860
 
 
861
                var is_dupe = !original_is_direct;
 
862
                Y.lp.bugs.subscribers_list.remove_user_link(
 
863
                    subscriber, is_dupe);
 
864
            },
 
865
 
 
866
            failure: error_handler.getFailureHandler()
 
867
        },
 
868
 
 
869
        parameters: { person: subscriber_link }
 
870
    };
 
871
 
 
872
    // A team must be unsubcribed from both the current bug and from
 
873
    // duplicates.  This configuration handles the first and then
 
874
    // chains to the other upon success.
 
875
    var team_config = {
 
876
        on: {
 
877
            success: function(client) {
 
878
                lp_client.named_post(
 
879
                    bug_repr.self_link, 'unsubscribeFromDupes', config);
 
880
            },
 
881
            failure: error_handler.getFailureHandler()
 
882
        },
 
883
        parameters: { person: subscriber_link }
 
884
    };
 
885
 
 
886
    if (subscription.is_team()){
 
887
        lp_client.named_post(bug_repr.self_link, 'unsubscribe', team_config);
 
888
    }
 
889
    else {
 
890
        if (subscription.is_direct_subscription()) {
 
891
            lp_client.named_post(bug_repr.self_link, 'unsubscribe', config);
 
892
        } else {
 
893
            lp_client.named_post(
 
894
                bug_repr.self_link, 'unsubscribeFromDupes', config);
 
895
        }
 
896
    }
 
897
}
 
898
 
 
899
/*
 
900
 * Initialize click handler for the subscribe someone else link.
 
901
 *
 
902
 * @method setup_subscribe_someone_else_handler
 
903
 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
 
904
 */
 
905
function setup_subscribe_someone_else_handler(subscription) {
 
906
    var config = {
 
907
        header: 'Subscribe someone else',
 
908
        step_title: 'Search',
 
909
        picker_activator: '.menu-link-addsubscriber'
 
910
    };
 
911
 
 
912
    config.save = function(result) {
 
913
        subscribe_someone_else(result, subscription);
 
914
    };
 
915
    var picker = Y.lp.app.picker.create('ValidPersonOrTeam', config);
 
916
}
 
917
 
 
918
/*
 
919
 * Build the HTML for a user link for the subscribers list.
 
920
 *
 
921
 * @method build_user_link_html
 
922
 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
 
923
 * @return html {String} The HTML used for creating a subscriber link.
 
924
 */
 
925
function build_user_link_html(subscription) {
 
926
    var name = subscription.get('person').get('name');
 
927
    var css_name = subscription.get('person').get('css_name');
 
928
    var full_name = subscription.get('person').get('full_display_name');
 
929
    var display_name = subscription.get('person').get('display_name');
 
930
    var terms = {
 
931
        name: name,
 
932
        css_name: css_name,
 
933
        display_name: display_name,
 
934
        full_name: full_name
 
935
    };
 
936
 
 
937
    if (subscription.is_current_user_subscribing()) {
 
938
        terms.subscribed_by = 'themselves';
 
939
    } else {
 
940
        terms.subscribed_by = 'by ' + full_name;
 
941
    }
 
942
 
 
943
    var html = Y.Node.create('<div><a></a></div>');
 
944
    html.addClass(terms.css_name);
 
945
 
 
946
    if (subscription.has_duplicate_subscriptions()) {
 
947
        html.set('id', 'dupe-' + terms.css_name);
 
948
    } else {
 
949
        html.set('id', 'direct-' + terms.css_name);
 
950
    }
 
951
 
 
952
    html.one('a')
 
953
        .set('href', '/~' + terms.name)
 
954
        .set('name', terms.full_name)
 
955
        .set('title', 'Subscribed ' + terms.subscribed_by);
 
956
 
 
957
    var span;
 
958
    if (subscription.is_team()) {
 
959
        span = '<span class="sprite team"></span>';
 
960
    } else {
 
961
        span = '<span class="sprite person"></span>';
 
962
    }
 
963
 
 
964
    html.one('a')
 
965
        .appendChild(Y.Node.create(span))
 
966
        .appendChild(document.createTextNode(terms.display_name));
 
967
 
 
968
    // Add remove icon if the current user can unsubscribe the subscriber.
 
969
    if (subscription.can_be_unsubscribed_by_user()) {
 
970
        var icon_html = Y.Node.create(
 
971
            '<a href="+subscribe">' +
 
972
            '<img class="unsub-icon" src="/@@/remove" alt="Remove" /></a>');
 
973
        icon_html
 
974
            .set('id', 'unsubscribe-' + terms.css_name)
 
975
            .set('title', 'Unsubscribe ' + terms.full_name);
 
976
        icon_html.one('img')
 
977
            .set('id', 'unsubscribe-icon-' + terms.css_name);
 
978
        html.appendChild(icon_html);
 
979
    }
 
980
 
 
981
    return html;
 
982
}
 
983
 
 
984
/*
 
985
 * Returns the next node in alphabetical order after the subscriber
 
986
 * node now being added.  No node is returned to append to end of list.
 
987
 *
 
988
 * The name can appear in one of two different lists. 1) The list of
 
989
 * subscribers that can be unsubscribed by the current user, and
 
990
 * 2) the list of subscribers that cannot be unsubscribed.
 
991
 *
 
992
 * @method get_next_subscriber_node
 
993
 * @param subscription_link {Node} The sub/unsub link.
 
994
 * @return {Node} The node appearing next in the subscriber list or
 
995
 *          undefined if no node is next.
 
996
 */
 
997
function get_next_subscriber_node(subscription) {
 
998
    var full_name = subscription.get('person').get('full_display_name');
 
999
    var can_be_unsubscribed = subscription.can_be_unsubscribed_by_user();
 
1000
    var nodes_by_name = {};
 
1001
    var unsubscribables = [];
 
1002
    var not_unsubscribables = [];
 
1003
 
 
1004
    // Use the list of subscribers pulled from the DOM to have sortable
 
1005
    // lists of unsubscribable vs. not unsubscribable person links.
 
1006
    var all_subscribers = Y.all('#subscribers-links div');
 
1007
    if (all_subscribers.size() > 0) {
 
1008
        all_subscribers.each(function(sub_link) {
 
1009
            if (sub_link.getAttribute('id') !== 'temp-username') {
 
1010
                // User's displayname is found via the link's "name"
 
1011
                // attribute.
 
1012
                var sub_link_name = sub_link.one('a').getAttribute('name');
 
1013
                nodes_by_name[sub_link_name] = sub_link;
 
1014
                if (sub_link.one('img.unsub-icon')) {
 
1015
                    unsubscribables.push(sub_link_name);
 
1016
                } else {
 
1017
                    not_unsubscribables.push(sub_link_name);
 
1018
                }
 
1019
            }
 
1020
        });
 
1021
 
 
1022
        // Add the current subscription.
 
1023
        if (can_be_unsubscribed) {
 
1024
            unsubscribables.push(full_name);
 
1025
        } else {
 
1026
            not_unsubscribables.push(full_name);
 
1027
        }
 
1028
        unsubscribables.sort();
 
1029
        not_unsubscribables.sort();
 
1030
    } else {
 
1031
        // If there is no all_subscribers, then we're dealing with
 
1032
        // the printed None, so return.
 
1033
        return;
 
1034
    }
 
1035
 
 
1036
    var i;
 
1037
    if ((!unsubscribables && !not_unsubscribables) ||
 
1038
        // If A) neither list exists, B) the user belongs in the second
 
1039
        // list but the second list doesn't exist, or C) user belongs in the
 
1040
        // first list and the second doesn't exist, return no node to append.
 
1041
        (!can_be_unsubscribed && !not_unsubscribables) ||
 
1042
        (can_be_unsubscribed && unsubscribables && !not_unsubscribables)) {
 
1043
        return;
 
1044
    } else if (
 
1045
        // If the user belongs in the first list, and the first list
 
1046
        // doesn't exist, but the second one does, return the first node
 
1047
        // in the second list.
 
1048
        can_be_unsubscribed && !unsubscribables && not_unsubscribables) {
 
1049
        return nodes_by_name[not_unsubscribables[0]];
 
1050
    } else if (can_be_unsubscribed) {
 
1051
        // If the user belongs in the first list, loop the list for position.
 
1052
        for (i=0; i<unsubscribables.length; i++) {
 
1053
            if (unsubscribables[i] === full_name) {
 
1054
                if (i+1 < unsubscribables.length) {
 
1055
                    return nodes_by_name[unsubscribables[i+1]];
 
1056
                // If the current link should go at the end of the first
 
1057
                // list and we're at the end of that list, return the
 
1058
                // first node of the second list.  Due to earlier checks
 
1059
                // we can be sure this list exists.
 
1060
                } else if (i+1 >= unsubscribables.length) {
 
1061
                    return nodes_by_name[not_unsubscribables[0]];
 
1062
                }
 
1063
            }
 
1064
        }
 
1065
    } else if (!can_be_unsubscribed) {
 
1066
        // If user belongs in the second list, loop the list for position.
 
1067
        for (i=0; i<not_unsubscribables.length; i++) {
 
1068
            if (not_unsubscribables[i] === full_name) {
 
1069
                if (i+1 < not_unsubscribables.length) {
 
1070
                    return nodes_by_name[not_unsubscribables[i+1]];
 
1071
                } else {
 
1072
                    return;
 
1073
                }
 
1074
            }
 
1075
        }
 
1076
    }
 
1077
}
 
1078
 
 
1079
/*
 
1080
 * Traverse the DOM of a given remove icon to find
 
1081
 * the user's link.  Returns a URI of the form "/~username".
 
1082
 *
 
1083
 * @method get_user_uri_from_icon
 
1084
 * @param icon {Node} The node representing a remove icon.
 
1085
 * @return user_uri {String} The user's uri, without the hostname.
 
1086
 */
 
1087
function get_user_uri_from_icon(icon) {
 
1088
    var parent_div = icon.get('parentNode').get('parentNode');
 
1089
    // This should be parent_div.firstChild, but because of #text
 
1090
    // and cross-browser issues, using the YUI query syntax is
 
1091
    // safer here.
 
1092
    var user_uri = parent_div.one('a').getAttribute('href');
 
1093
 
 
1094
    // Strip the domain off. We just want a path.
 
1095
    var host_start = user_uri.indexOf('//');
 
1096
    if (host_start !== -1) {
 
1097
        var host_end = user_uri.indexOf('/', host_start+2);
 
1098
        return user_uri.substring(host_end, user_uri.length);
 
1099
    }
 
1100
 
 
1101
    return user_uri;
 
1102
}
 
1103
 
 
1104
/*
 
1105
 * Set the class on subscription link's parentNode.
 
1106
 *
 
1107
 * This is used to reset the class used by the
 
1108
 * click handler to know which link was clicked.
 
1109
 *
 
1110
 * @method set_subscription_link_parent_class
 
1111
 * @param subscription_link {Node} The sub/unsub link.
 
1112
 * @param subscribed {Boolean} The sub/unsub'ed flag for the class.
 
1113
 * @param dupe_subscribed {Boolean} The sub/unsub'ed flag for dupes
 
1114
 *                                  on the class.
 
1115
 */
 
1116
function set_subscription_link_parent_class(
 
1117
    user_link, subscribed, dupe_subscribed) {
 
1118
 
 
1119
    var parent = user_link.get('parentNode');
 
1120
    if (subscribed) {
 
1121
        parent.removeClass('subscribed-false');
 
1122
        parent.addClass('subscribed-true');
 
1123
    } else {
 
1124
        parent.removeClass('subscribed-true');
 
1125
        parent.addClass('subscribed-false');
 
1126
    }
 
1127
 
 
1128
    if (dupe_subscribed) {
 
1129
        parent.removeClass('dup-subscribed-false');
 
1130
        parent.addClass('dup-subscribed-true');
 
1131
    } else {
 
1132
        parent.removeClass('dup-subscribed-true');
 
1133
        parent.addClass('dup-subscribed-false');
 
1134
    }
 
1135
}
 
1136
 
 
1137
 
 
1138
/*
 
1139
 * Handle the advanced_subscription_overlay's form submissions.
 
1140
 *
 
1141
 * @method handle_advanced_subscription_overlay
 
1142
 * @param form_data {Object} The data from the submitted form.
 
1143
 */
 
1144
function handle_advanced_subscription_overlay(form_data) {
 
1145
    var subscription;
 
1146
    var mute_link = Y.one('.menu-link-mute_subscription');
 
1147
    var parent_node = mute_link.get('parentNode');
 
1148
    var is_muted = parent_node.hasClass('muted-true');
 
1149
    var requested_subscriber;
 
1150
    var request_for_self = false;
 
1151
    // XXX Danilo 20110422: this is a very lousy "special string"
 
1152
    // that will make it break for a team/person named 'update-subscription'.
 
1153
    // We should probably use special characters not allowed in team names
 
1154
    // (maybe all-uppercase would work).
 
1155
    var UPDATE_ACTION = 'update-subscription';
 
1156
 
 
1157
    if (form_data['field.subscription'][0] === UPDATE_ACTION) {
 
1158
        requested_subscriber = LP.links.me;
 
1159
        request_for_self = true;
 
1160
    } else {
 
1161
        requested_subscriber = '/~' + form_data['field.subscription'][0];
 
1162
        if (requested_subscriber === LP.links.me) {
 
1163
            request_for_self = true;
 
1164
        }
 
1165
    }
 
1166
    if (request_for_self) {
 
1167
        subscription = get_subscribe_self_subscription();
 
1168
    } else {
 
1169
        subscription = get_team_subscription(requested_subscriber);
 
1170
    }
 
1171
    var link = subscription.get('link');
 
1172
    var link_parent = link.get('parentNode');
 
1173
 
 
1174
    if (form_data['field.subscription'][0] === UPDATE_ACTION) {
 
1175
        // The user is already subscribed or is muted and wants to
 
1176
        // update their subscription.
 
1177
        setup_client_and_bug();
 
1178
        var person_name = subscription.get('person').get('name');
 
1179
        var subscription_url =
 
1180
            lp_bug_entry.get('self_link') + '/+subscription/' +
 
1181
            person_name;
 
1182
        var update_subscription = function(lp_subscription) {
 
1183
            subscription.enable_spinner('Updating subscription...');
 
1184
            lp_subscription.set(
 
1185
                'bug_notification_level',
 
1186
                form_data['field.bug_notification_level'][0]);
 
1187
            save_config = {
 
1188
                on: {
 
1189
                    success: function(e) {
 
1190
                        subscription.disable_spinner(
 
1191
                            'Edit subscription');
 
1192
                        link_parent.addClass('subscribed-true');
 
1193
                        link_parent.removeClass('subscribed-false');
 
1194
                        var anim = Y.lazr.anim.green_flash({
 
1195
                            node: link_parent
 
1196
                            });
 
1197
                        anim.run();
 
1198
                    },
 
1199
                    failure: function(e) {
 
1200
                        subscription.disable_spinner(
 
1201
                            'Edit subscription');
 
1202
                        var anim = Y.lazr.anim.red_flash({
 
1203
                            node: link_parent
 
1204
                            });
 
1205
                        anim.run();
 
1206
                    }
 
1207
                }
 
1208
            };
 
1209
            lp_subscription.lp_save(save_config);
 
1210
        };
 
1211
        if (is_muted) {
 
1212
            toggle_mute(function(lp_subscription) {
 
1213
                if (Y.Lang.isValue(lp_subscription)) {
 
1214
                    update_subscription(lp_subscription);
 
1215
                } else {
 
1216
                    subscription.set(
 
1217
                        'bug_notification_level',
 
1218
                        form_data['field.bug_notification_level']);
 
1219
                    subscribe_current_user(subscription);
 
1220
                }
 
1221
            });
 
1222
        } else {
 
1223
            lp_client.get(
 
1224
                subscription_url,
 
1225
                {on: {success: update_subscription}}
 
1226
            );
 
1227
        }
 
1228
 
 
1229
    } else if (link_parent.hasClass('subscribed-false') &&
 
1230
               link_parent.hasClass('dup-subscribed-false') &&
 
1231
               !is_muted && request_for_self) {
 
1232
        // The user isn't subscribed or muted, and the request is
 
1233
        // for himself (iow, not for a team).
 
1234
        subscription.set(
 
1235
            'bug_notification_level',
 
1236
            form_data['field.bug_notification_level']);
 
1237
        subscribe_current_user(subscription);
 
1238
    } else if (is_muted && request_for_self) {
 
1239
        // When a person has a bug muted, we show 2+ options:
 
1240
        // a. unmute, b. unmute and subscribe, c. unsubscribe team1...
 
1241
        // "b" is treated as 'update-subscription' case, and for any
 
1242
        // of the teams, request_for_self won't be true.
 
1243
        toggle_mute();
 
1244
    } else {
 
1245
        // Unsubscribe this person/team.
 
1246
        unsubscribe_current_user(subscription);
 
1247
    }
 
1248
}
 
1249
 
 
1250
/*
 
1251
 * Subscribe a person or team other than the current user.
 
1252
 * This is a callback for the subscribe someone else picker.
 
1253
 *
 
1254
 * @method subscribe_someone_else
 
1255
 * @result {Object} The object representing a person returned by the API.
 
1256
 */
 
1257
function subscribe_someone_else(result, subscription) {
 
1258
    var person = new Y.lp.bugs.subscriber.Subscriber({
 
1259
        uri: result.api_uri,
 
1260
        display_name: result.title,
 
1261
        subscriber_ids: subscriber_ids
 
1262
    });
 
1263
    subscription.set('person', person);
 
1264
 
 
1265
    var error_handler = new Y.lp.client.ErrorHandler();
 
1266
    error_handler.showError = function(error_msg) {
 
1267
        Y.lp.app.errors.display_error(
 
1268
           Y.one('.menu-link-addsubscriber'), error_msg);
 
1269
    };
 
1270
 
 
1271
    if (subscription.is_already_subscribed()) {
 
1272
        error_handler.showError(
 
1273
             subscription.get('person').get('full_display_name') +
 
1274
             ' has already been subscribed');
 
1275
    } else {
 
1276
        check_can_be_unsubscribed(subscription);
 
1277
    }
 
1278
}
 
1279
 
 
1280
/*
 
1281
 * Check if the current user can unsubscribe the person
 
1282
 * being subscribed.
 
1283
 *
 
1284
 * This must be done in JavaScript, since the subscription
 
1285
 * hasn't completed yet, and so, can_be_unsubscribed_by_user
 
1286
 * cannot be used.
 
1287
 *
 
1288
 * @method check_can_be_unsubscribed
 
1289
 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
 
1290
 */
 
1291
function check_can_be_unsubscribed(subscription) {
 
1292
    var error_handler = new Y.lp.client.ErrorHandler();
 
1293
    error_handler.showError = function (error_msg) {
 
1294
        Y.lp.app.errors.display_error(
 
1295
           Y.one('.menu-link-addsubscriber'), error_msg);
 
1296
    };
 
1297
 
 
1298
    var config = {
 
1299
        on: {
 
1300
            success: function(result) {
 
1301
                var is_team = result.get('is_team');
 
1302
                subscription.set('is_team', is_team);
 
1303
                var final_config = {
 
1304
                    on: {
 
1305
                        success: function(result) {
 
1306
                            var team_member = false;
 
1307
                            var i;
 
1308
                            for (i=0; i<result.entries.length; i++) {
 
1309
                                if (result.entries[i].get('member_link') ===
 
1310
                                    Y.lp.client.get_absolute_uri(
 
1311
                                        subscription.get(
 
1312
                                            'subscriber').get('uri'))) {
 
1313
                                    team_member = true;
 
1314
                                }
 
1315
                            }
 
1316
 
 
1317
                            if (team_member) {
 
1318
                                subscription.set('can_be_unsubscribed', true);
 
1319
                                add_temp_user_name(subscription);
 
1320
                                if (namespace.use_advanced_subscriptions) {
 
1321
                                    update_mute_after_subscription_change();
 
1322
                                }
 
1323
                            } else {
 
1324
                                subscription.set(
 
1325
                                   'can_be_unsubscribed', false);
 
1326
                                add_temp_user_name(subscription);
 
1327
                            }
 
1328
                        },
 
1329
 
 
1330
                        failure: error_handler.getFailureHandler()
 
1331
                    }
 
1332
                };
 
1333
 
 
1334
                if (is_team) {
 
1335
                    // Get a list of members to see if current user
 
1336
                    // is a team member.
 
1337
                    var members = result.get(
 
1338
                       'members_details_collection_link');
 
1339
                    lp_client.get(members, final_config);
 
1340
                } else {
 
1341
                    subscription.set('can_be_unsubscribed', false);
 
1342
                    add_temp_user_name(subscription);
 
1343
                }
 
1344
            },
 
1345
 
 
1346
            failure: error_handler.getFailureHandler()
 
1347
        }
 
1348
    };
 
1349
    lp_client.get(Y.lp.client.get_absolute_uri(
 
1350
        subscription.get('person').get('escaped_uri')), config);
 
1351
}
 
1352
 
 
1353
/*
 
1354
 * Add a grayed out, temporary user name when subscribing
 
1355
 * someone else.
 
1356
 *
 
1357
 * @method add_temp_user_name
 
1358
 * @param subscription_link {Node} The sub/unsub link.
 
1359
 */
 
1360
function add_temp_user_name(subscription) {
 
1361
    // Be paranoid about display_name, since timeouts or other errors
 
1362
    // could mean display_name wasn't set on initialization.
 
1363
    subscription.get('person').set_display_name(function () {
 
1364
        _add_temp_user_name(subscription);
 
1365
    });
 
1366
}
 
1367
 
 
1368
function _add_temp_user_name(subscription) {
 
1369
    var display_name = subscription.get('person').get('display_name');
 
1370
    var img_src;
 
1371
    if (subscription.is_team()) {
 
1372
        img_src = '/@@/teamgray';
 
1373
    } else {
 
1374
        img_src = '/@@/persongray';
 
1375
    }
 
1376
 
 
1377
    // The <span>...</span> below must *not* be <span/>. On FF (maybe
 
1378
    // others, but at least on FF 3.0.11) will then not notice any
 
1379
    // following sibling nodes, like the spinner image.
 
1380
    var link_node = Y.Node.create([
 
1381
        '<div id="temp-username"> ',
 
1382
        '  <img alt="" width="14" height="14" />',
 
1383
        '  <span>Other Display Name</span>',
 
1384
        '  <img id="temp-name-spinner" src="/@@/spinner" alt="" ',
 
1385
        '       style="position:absolute;right:8px" /></div>'].join(''));
 
1386
    link_node.one('img').set('src', img_src);
 
1387
    link_node.replaceChild(
 
1388
        document.createTextNode(display_name),
 
1389
        link_node.one('span'));
 
1390
 
 
1391
    var subscribers = Y.one('#subscribers-links');
 
1392
    var next = get_next_subscriber_node(subscription);
 
1393
    if (next) {
 
1394
        subscribers.insertBefore(link_node, next);
 
1395
    } else {
 
1396
        // Handle the case of no subscribers.
 
1397
        var none_subscribers = Y.one('#none-subscribers');
 
1398
        if (none_subscribers) {
 
1399
            var none_parent = none_subscribers.get('parentNode');
 
1400
            none_parent.removeChild(none_subscribers);
 
1401
        }
 
1402
        subscribers.appendChild(link_node);
 
1403
    }
 
1404
 
 
1405
    // Fire a custom event to know it's safe to begin
 
1406
    // any actual subscribing work.
 
1407
    namespace.portlet.fire('bugs:nameloaded', subscription);
 
1408
}
 
1409
 
 
1410
}, "0.1", {"requires": ["base", "oop", "node", "event", "io-base",
 
1411
                        "json-parse", "substitute", "widget-position-ext",
 
1412
                        "lazr.formoverlay", "lazr.anim", "lazr.base",
 
1413
                        "lazr.overlay", "lazr.choiceedit", "lp.app.picker",
 
1414
                        "lp.client",
 
1415
                        "lp.client.plugins", "lp.bugs.subscriber",
 
1416
                        "lp.bugs.subscribers_list",
 
1417
                        "lp.bugs.bug_notification_level", "lp.app.errors"]});