~launchpad-pqm/launchpad/devel

« back to all changes in this revision

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

[r=gmb][no-qa] Split portlet setup for subscriber widgets from
        bugtask-index.js.

Show diffs side-by-side

added added

removed removed

Lines of Context:
39
39
var privacy_spinner;
40
40
var link_branch_link;
41
41
 
42
 
// The set of subscriber CSS IDs as a JSON struct.
43
 
var subscriber_ids;
44
 
 
45
 
// A boolean telling us whether advanced subscription features are to be
46
 
// used or not.
47
 
// XXX 2011-01-14 gmb bug=702859:
48
 
//     We need to expose feature flags via the API to avoid this kind of
49
 
//     thing.
50
 
var use_advanced_subscriptions = false;
51
 
var subscription_labels = Y.lp.bugs.subscriber.subscription_labels;
52
 
 
53
 
/*
54
 
 * An object representing the bugtask subscribers portlet.
55
 
 *
56
 
 * Since the portlet loads via XHR and inline subscribing
57
 
 * depends on that portlet being loaded, setup a custom
58
 
 * event object, to provide a hook for initializing subscription
59
 
 * link callbacks after custom events.
60
 
 */
61
 
var PortletTarget = function() {};
62
 
Y.augment(PortletTarget, Y.Event.Target);
63
 
namespace.portlet = new PortletTarget();
64
 
 
65
 
function setup_portlet_handlers() {
66
 
    namespace.portlet.subscribe('bugs:portletloaded', function() {
67
 
        load_subscriber_ids();
68
 
    });
69
 
    namespace.portlet.subscribe('bugs:dupeportletloaded', function() {
70
 
        setup_unsubscribe_icon_handlers();
71
 
    });
72
 
    /*
73
 
     * If the subscribers portlet fails to load, clear any
74
 
     * click handlers, so the normal subscribe page can be reached.
75
 
     */
76
 
    namespace.portlet.subscribe('bugs:portletloadfailed', function(handlers) {
77
 
        if (Y.Lang.isArray(handlers)) {
78
 
            var click_handler = handlers[0];
79
 
            click_handler.detach();
80
 
        }
81
 
    });
82
 
    /* If the dupe subscribers portlet fails to load,
83
 
     * be sure to try to handle any unsub icons that may
84
 
     * exist for others.
85
 
     */
86
 
    namespace.portlet.subscribe(
87
 
        'bugs:dupeportletloadfailed',
88
 
        function(handlers) {
89
 
            setup_unsubscribe_icon_handlers();
90
 
        });
91
 
 
92
 
    /* If loading the subscriber IDs JSON has succeeded, set up the
93
 
     * subscription link handlers and load the subscribers from dupes.
94
 
     */
95
 
    namespace.portlet.subscribe(
96
 
        'bugs:portletsubscriberidsloaded',
97
 
        function() {
98
 
            setup_subscription_link_handlers();
99
 
            load_subscribers_from_duplicates();
100
 
        });
101
 
 
102
 
    /* If loading the subscriber IDs JSON fails we still need to load the
103
 
     * subscribers from duplicates but we don't set up the subscription link
104
 
     * handlers.
105
 
     */
106
 
    namespace.portlet.subscribe(
107
 
        'bugs:portletsubscriberidsfailed',
108
 
        function() {
109
 
            load_subscribers_from_duplicates();
110
 
        });
111
 
 
112
 
    /*
113
 
     * Subscribing someone else requires loading a grayed out
114
 
     * username into the DOM until the subscribe action completes.
115
 
     * There are a couple XHR requests in check_can_be_unsubscribed
116
 
     * before the subscribe work can be done, so fire a custom event
117
 
     * bugs:nameloaded and do the work here when the event fires.
118
 
     */
119
 
    namespace.portlet.subscribe('bugs:nameloaded', function(subscription) {
120
 
        var error_handler = new LP.client.ErrorHandler();
121
 
        error_handler.clearProgressUI = function() {
122
 
            var temp_link = Y.one('#temp-username');
123
 
            if (temp_link) {
124
 
                var temp_parent = temp_link.get('parentNode');
125
 
                temp_parent.removeChild(temp_link);
126
 
            }
127
 
        };
128
 
        error_handler.showError = function(error_msg) {
129
 
            Y.lp.app.errors.display_error(
130
 
                Y.one('.menu-link-addsubscriber'), error_msg);
131
 
        };
132
 
 
133
 
        var config = {
134
 
            on: {
135
 
                success: function() {
136
 
                    var temp_link = Y.one('#temp-username');
137
 
                    var temp_spinner = Y.one('#temp-name-spinner');
138
 
                    temp_link.removeChild(temp_spinner);
139
 
                    var anim = Y.lazr.anim.green_flash({ node: temp_link });
140
 
                    anim.on('end', function() {
141
 
                        add_user_name_link(subscription);
142
 
                        var temp_parent = temp_link.get('parentNode');
143
 
                        temp_parent.removeChild(temp_link);
144
 
                    });
145
 
                    anim.run();
146
 
                },
147
 
                failure: error_handler.getFailureHandler()
148
 
            },
149
 
            parameters: {
150
 
                person: LP.client.get_absolute_uri(
151
 
                    subscription.get('person').get('escaped_uri')),
152
 
                suppress_notify: false
153
 
            }
154
 
        };
155
 
        lp_client.named_post(bug_repr.self_link, 'subscribe', config);
156
 
    });
157
 
}
158
 
 
159
42
namespace.setup_bugtask_index = function() {
160
 
    setup_portlet_handlers();
 
43
    Y.lp.bugs.bugtask_index.portlets.setup_portlet_handlers();
 
44
 
161
45
    /*
162
46
     * Check the page for links related to overlay forms and request the HTML
163
47
     * for these forms.
275
159
 
276
160
 
277
161
/*
278
 
 * Initialize click handler for the subscribe someone else link.
279
 
 *
280
 
 * @method setup_subscribe_someone_else_handler
281
 
 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
282
 
 */
283
 
function setup_subscribe_someone_else_handler(subscription) {
284
 
    var config = {
285
 
        header: 'Subscribe someone else',
286
 
        step_title: 'Search',
287
 
        picker_activator: '.menu-link-addsubscriber'
288
 
    };
289
 
 
290
 
    config.save = function(result) {
291
 
        subscribe_someone_else(result, subscription);
292
 
    };
293
 
    var picker = Y.lp.app.picker.create('ValidPersonOrTeam', config);
294
 
}
295
 
 
296
 
 
297
 
/*
298
 
 * Handle the advanced_subscription_overlay's form submissions.
299
 
 *
300
 
 * @method handle_advanced_subscription_overlay
301
 
 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
302
 
 * @param form_data {Object} The data from the submitted form.
303
 
 */
304
 
function handle_advanced_subscription_overlay(subscription, form_data) {
305
 
    var link = subscription.get('link');
306
 
    var link_parent = link.get('parentNode');
307
 
    if (link_parent.hasClass('subscribed-false') &&
308
 
        link_parent.hasClass('dup-subscribed-false')) {
309
 
        // The user isn't subscribed, so subscribe them.
310
 
        subscription.set(
311
 
            'bug_notification_level',
312
 
            form_data['field.bug_notification_level']);
313
 
        subscribe_current_user(subscription);
314
 
    } else if (
315
 
        form_data['field.subscription'] == 'update-subscription') {
316
 
        // The user is already subscribed and wants to update their
317
 
        // subscription.
318
 
        setup_client_and_bug();
319
 
        var person_name = subscription.get('person').get('name');
320
 
        var subscription_url =
321
 
            lp_bug_entry.get('self_link') + '/+subscription/' +
322
 
            person_name;
323
 
        config = {
324
 
            on: {
325
 
                success: function(lp_subscription) {
326
 
                    subscription.enable_spinner('Updating subscription...');
327
 
                    lp_subscription.set(
328
 
                        'bug_notification_level',
329
 
                        form_data['field.bug_notification_level'][0])
330
 
                    save_config = {
331
 
                        on: {
332
 
                            success: function(e) {
333
 
                                subscription.disable_spinner(
334
 
                                    'Edit subscription');
335
 
                                var anim = Y.lazr.anim.green_flash({
336
 
                                    node: link_parent
337
 
                                    });
338
 
                                anim.run();
339
 
                            },
340
 
                            failure: function(e) {
341
 
                                subscription.disable_spinner(
342
 
                                    'Edit subscription');
343
 
                                var anim = Y.lazr.anim.red_flash({
344
 
                                    node: link_parent
345
 
                                    });
346
 
                                anim.run();
347
 
                            }
348
 
                        }
349
 
                    }
350
 
                    lp_subscription.lp_save(save_config);
351
 
                }
352
 
            }
353
 
        }
354
 
        lp_client.get(subscription_url, config);
355
 
    } else {
356
 
        // The user is already subscribed and wants to unsubscribe.
357
 
        unsubscribe_current_user(subscription);
358
 
    }
359
 
}
360
 
 
361
 
 
362
 
/*
363
 
 * Create and return a FormOverlay for advanced subscription
364
 
 * interactions.
365
 
 *
366
 
 * @method setup_advanced_subscription_overlay
367
 
 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
368
 
 */
369
 
function setup_advanced_subscription_overlay(subscription) {
370
 
    var subscription_overlay = new Y.lazr.FormOverlay({
371
 
        headerContent: '<h2>Subscribe to bug</h2>',
372
 
        form_submit_button:
373
 
            Y.Node.create(submit_button_html),
374
 
        form_cancel_button:
375
 
            Y.Node.create(cancel_button_html),
376
 
        centered: true,
377
 
        visible: false
378
 
    });
379
 
    subscription_overlay.set(
380
 
        'form_submit_callback', function(form_data) {
381
 
        handle_advanced_subscription_overlay(subscription, form_data);
382
 
        subscription_overlay.hide();
383
 
    });
384
 
 
385
 
    var subscription_link_url = subscription.get(
386
 
        'link').get('href') + '/++form++';
387
 
    subscription_overlay.loadFormContentAndRender(
388
 
        subscription_link_url);
389
 
    subscription_overlay.render('#privacy-form-container');
390
 
    return subscription_overlay
391
 
}
392
 
 
393
 
 
394
 
/*
395
 
 * Initialize callbacks for subscribe/unsubscribe links.
396
 
 *
397
 
 * @method setup_subscription_link_handlers
398
 
 */
399
 
function setup_subscription_link_handlers() {
400
 
    if (LP.client.links.me === undefined) {
401
 
        return;
402
 
    }
403
 
 
404
 
    setup_client_and_bug();
405
 
    var subscription = new Y.lp.bugs.subscriber.Subscription({
406
 
        link: Y.one('.menu-link-subscription'),
407
 
        spinner: Y.one('#sub-unsub-spinner'),
408
 
        subscriber: new Y.lp.bugs.subscriber.Subscriber({
409
 
            uri: LP.client.links.me,
410
 
            subscriber_ids: subscriber_ids
411
 
        })
412
 
    });
413
 
 
414
 
    var is_direct = subscription.get(
415
 
        'link').get('parentNode').hasClass('subscribed-true');
416
 
    var has_dupes = subscription.get(
417
 
        'link').get('parentNode').hasClass('dup-subscribed-true');
418
 
    subscription.set('is_direct', is_direct);
419
 
    subscription.set('has_dupes', has_dupes);
420
 
 
421
 
    if (subscription.is_node()) {
422
 
        subscription.get('link').on('click', function(e) {
423
 
            e.halt();
424
 
            subscription.set('can_be_unsubscribed', true);
425
 
            subscription.set('person', subscription.get('subscriber'));
426
 
            subscription.set('is_team', false);
427
 
            var parent = e.target.get('parentNode');
428
 
            if (namespace.use_advanced_subscriptions) {
429
 
                var subscription_overlay =
430
 
                    setup_advanced_subscription_overlay(subscription);
431
 
                subscription_overlay.show();
432
 
            } else {
433
 
                // Look for the false conditions of subscription, which
434
 
                // is_direct_subscription, etc. don't report correctly,
435
 
                // to make sure we only use subscribe_current_user for
436
 
                // the current user.
437
 
                if (parent.hasClass('subscribed-false') &&
438
 
                    parent.hasClass('dup-subscribed-false')) {
439
 
                    subscribe_current_user(subscription);
440
 
                }
441
 
                else {
442
 
                    unsubscribe_current_user(subscription);
443
 
                }
444
 
            }
445
 
        });
446
 
        subscription.get('link').addClass('js-action');
447
 
    }
448
 
 
449
 
    setup_subscribe_someone_else_handler(subscription);
450
 
}
451
 
 
452
 
/*
453
 
 * Set click handlers for unsubscribe remove icons.
454
 
 *
455
 
 * @method setup_unsubscribe_icon_handlers
456
 
 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
457
 
 */
458
 
function setup_unsubscribe_icon_handlers() {
459
 
    var subscription = new Y.lp.bugs.subscriber.Subscription({
460
 
        link: Y.one('.menu-link-subscription'),
461
 
        spinner: Y.one('#sub-unsub-spinner'),
462
 
        subscriber: new Y.lp.bugs.subscriber.Subscriber({
463
 
            uri: LP.client.links.me,
464
 
            subscriber_ids: subscriber_ids
465
 
        })
466
 
    });
467
 
 
468
 
    Y.on('click', function(e) {
469
 
        e.halt();
470
 
        unsubscribe_user_via_icon(e.target, subscription);
471
 
    }, '.unsub-icon');
472
 
}
473
 
 
474
 
/*
475
162
 * Create the lp client and bug entry if we haven't done so already.
476
163
 *
477
164
 * @method setup_client_and_bug
850
537
    Y.fire('lp:branch-linked', bug_branch_node);
851
538
}
852
539
 
853
 
/*
854
 
 * Traverse the DOM of a given remove icon to find
855
 
 * the user's link.  Returns a URI of the form "/~username".
856
 
 *
857
 
 * @method get_user_uri_from_icon
858
 
 * @param icon {Node} The node representing a remove icon.
859
 
 * @return user_uri {String} The user's uri, without the hostname.
860
 
 */
861
 
function get_user_uri_from_icon(icon) {
862
 
    var parent_div = icon.get('parentNode').get('parentNode');
863
 
    // This should be parent_div.firstChild, but because of #text
864
 
    // and cross-browser issues, using the YUI query syntax is
865
 
    // safer here.
866
 
    var user_uri = parent_div.one('a').getAttribute('href');
867
 
 
868
 
    // Strip the domain off. We just want a path.
869
 
    var host_start = user_uri.indexOf('//');
870
 
    if (host_start != -1) {
871
 
        var host_end = user_uri.indexOf('/', host_start+2);
872
 
        return user_uri.substring(host_end, user_uri.length);
873
 
    }
874
 
 
875
 
    return user_uri;
876
 
}
877
 
 
878
 
 
879
 
/*
880
 
 * Build the HTML for a user link for the subscribers list.
881
 
 *
882
 
 * @method build_user_link_html
883
 
 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
884
 
 * @return html {String} The HTML used for creating a subscriber link.
885
 
 */
886
 
function build_user_link_html(subscription) {
887
 
    var name = subscription.get('person').get('name');
888
 
    var css_name = subscription.get('person').get('css_name');
889
 
    var full_name = subscription.get('person').get('full_display_name');
890
 
    // Be paranoid about display_name, since timeouts or other errors
891
 
    // could mean display_name wasn't set on initialization.
892
 
    if (subscription.get('person').get('display_name') === '') {
893
 
        subscription.get('person').set_display_name();
894
 
    }
895
 
    var display_name = subscription.get('person').get('display_name');
896
 
    var terms = {
897
 
        name: name,
898
 
        css_name: css_name,
899
 
        display_name: display_name,
900
 
        full_name: full_name
901
 
    };
902
 
 
903
 
    if (subscription.is_current_user_subscribing()) {
904
 
        terms.subscribed_by = 'themselves';
905
 
    } else {
906
 
        terms.subscribed_by = 'by ' + full_name;
907
 
    }
908
 
 
909
 
    var html = Y.Node.create('<div><a></a></div>');
910
 
    html.addClass(terms.css_name);
911
 
 
912
 
    if (subscription.is_direct_subscription()) {
913
 
        html.set('id', 'direct-' + terms.css_name);
914
 
    } else {
915
 
        html.set('id', 'dupe-' + terms.css_name);
916
 
    }
917
 
 
918
 
    html.one('a')
919
 
        .set('href', '/~' + terms.name)
920
 
        .set('name', terms.full_name)
921
 
        .set('title', 'Subscribed ' + terms.subscribed_by);
922
 
 
923
 
    var span;
924
 
    if (subscription.is_team()) {
925
 
        span = '<span class="sprite team"></span>';
926
 
    } else {
927
 
        span = '<span class="sprite person"></span>';
928
 
    }
929
 
 
930
 
    html.one('a')
931
 
        .appendChild(Y.Node.create(span))
932
 
        .appendChild(document.createTextNode(terms.display_name));
933
 
 
934
 
    // Add remove icon if the current user can unsubscribe the subscriber.
935
 
    if (subscription.can_be_unsubscribed_by_user()) {
936
 
        var icon_html = Y.Node.create(
937
 
            '<a href="+subscribe">' +
938
 
            '<img class="unsub-icon" src="/@@/remove" alt="Remove" /></a>');
939
 
        icon_html
940
 
            .set('id', 'unsubscribe-' + terms.css_name)
941
 
            .set('title', 'Unsubscribe ' + terms.full_name);
942
 
        icon_html.one('img')
943
 
            .set('id', 'unsubscribe-icon-' + terms.css_name);
944
 
        html.appendChild(icon_html);
945
 
    }
946
 
 
947
 
    return html;
948
 
}
949
 
 
950
 
/*
951
 
 * Used to remove the user's name from the subscriber's list.
952
 
 *
953
 
 * @method remove_user_name_link
954
 
 * @param user_node {Node} Node representing the user name link.
955
 
 */
956
 
function remove_user_name_link(user_node) {
957
 
    var parent = user_node.get('parentNode');
958
 
    parent.removeChild(user_node);
959
 
}
960
 
 
961
 
/*
962
 
 * Returns the next node in alphabetical order after the subscriber
963
 
 * node now being added.  No node is returned to append to end of list.
964
 
 *
965
 
 * The name can appear in one of two different lists. 1) The list of
966
 
 * subscribers that can be unsubscribed by the current user, and
967
 
 * 2) the list of subscribers that cannont be unsubscribed.
968
 
 *
969
 
 * @method get_next_subscriber_node
970
 
 * @param subscription_link {Node} The sub/unsub link.
971
 
 * @return {Node} The node appearing next in the subscriber list or
972
 
 *          undefined if no node is next.
973
 
 */
974
 
function get_next_subscriber_node(subscription) {
975
 
    var full_name = subscription.get('person').get('full_display_name');
976
 
    var can_be_unsubscribed = subscription.can_be_unsubscribed_by_user();
977
 
    var nodes_by_name = {};
978
 
    var unsubscribables = [];
979
 
    var not_unsubscribables = [];
980
 
 
981
 
    // Use the list of subscribers pulled from the DOM to have sortable
982
 
    // lists of unsubscribable vs. not unsubscribale person links.
983
 
    var all_subscribers = Y.all('#subscribers-links div');
984
 
    if (all_subscribers.size() > 0) {
985
 
        all_subscribers.each(function(sub_link) {
986
 
            if (sub_link.getAttribute('id') != 'temp-username') {
987
 
                // User's displayname is found via the link's "name"
988
 
                // attribute.
989
 
                var sub_link_name = sub_link.one('a').getAttribute('name');
990
 
                nodes_by_name[sub_link_name] = sub_link;
991
 
                if (sub_link.one('img.unsub-icon')) {
992
 
                    unsubscribables.push(sub_link_name);
993
 
                } else {
994
 
                    not_unsubscribables.push(sub_link_name);
995
 
                }
996
 
            }
997
 
        });
998
 
 
999
 
        // Add the current subscription.
1000
 
        if (can_be_unsubscribed) {
1001
 
            unsubscribables.push(full_name);
1002
 
        } else {
1003
 
            not_unsubscribables.push(full_name);
1004
 
        }
1005
 
        unsubscribables.sort();
1006
 
        not_unsubscribables.sort();
1007
 
    } else {
1008
 
        // If there is no all_subscribers, then we're dealing with
1009
 
        // the printed None, so return.
1010
 
        return;
1011
 
    }
1012
 
 
1013
 
    var i;
1014
 
    if ((!unsubscribables && !not_unsubscribables) ||
1015
 
        // If A) neither list exists, B) the user belongs in the second
1016
 
        // list but the second list doesn't exist, or C) user belongs in the
1017
 
        // first list and the second doesn't exist, return no node to append.
1018
 
        (!can_be_unsubscribed && !not_unsubscribables) ||
1019
 
        (can_be_unsubscribed && unsubscribables && !not_unsubscribables)) {
1020
 
        return;
1021
 
    } else if (
1022
 
        // If the user belongs in the first list, and the first list
1023
 
        // doesn't exist, but the second one does, return the first node
1024
 
        // in the second list.
1025
 
        can_be_unsubscribed && !unsubscribables && not_unsubscribables) {
1026
 
        return nodes_by_name[not_unsubscribables[0]];
1027
 
    } else if (can_be_unsubscribed) {
1028
 
        // If the user belongs in the first list, loop the list for position.
1029
 
        for (i=0; i<unsubscribables.length; i++) {
1030
 
            if (unsubscribables[i] == full_name) {
1031
 
                if (i+1 < unsubscribables.length) {
1032
 
                    return nodes_by_name[unsubscribables[i+1]];
1033
 
                // If the current link should go at the end of the first
1034
 
                // list and we're at the end of that list, return the
1035
 
                // first node of the second list.  Due to earlier checks
1036
 
                // we can be sure this list exists.
1037
 
                } else if (i+1 >= unsubscribables.length) {
1038
 
                    return nodes_by_name[not_unsubscribables[0]];
1039
 
                }
1040
 
            }
1041
 
        }
1042
 
    } else if (!can_be_unsubscribed) {
1043
 
        // If user belongs in the second list, loop the list for position.
1044
 
        for (i=0; i<not_unsubscribables.length; i++) {
1045
 
            if (not_unsubscribables[i] == full_name) {
1046
 
                if (i+1 < not_unsubscribables.length) {
1047
 
                    return nodes_by_name[not_unsubscribables[i+1]];
1048
 
                } else {
1049
 
                    return;
1050
 
                }
1051
 
            }
1052
 
        }
1053
 
    }
1054
 
}
1055
 
 
1056
 
/*
1057
 
 * Add the user name to the subscriber's list.
1058
 
 *
1059
 
 * @method add_user_name_link
1060
 
 */
1061
 
function add_user_name_link(subscription) {
1062
 
    var person = subscription.get('person');
1063
 
    var link_node = build_user_link_html(subscription);
1064
 
    var subscribers = Y.one('#subscribers-links');
1065
 
    if (subscription.is_current_user_subscribing()) {
1066
 
        // If this is the current user, then top post the name and be done.
1067
 
        subscribers.insertBefore(link_node, subscribers.get('firstChild'));
1068
 
    } else {
1069
 
        var next = get_next_subscriber_node(subscription);
1070
 
        if (next) {
1071
 
            subscribers.insertBefore(link_node, next);
1072
 
        } else {
1073
 
            // Handle the case of the displayed "None".
1074
 
            var none_subscribers = Y.one('#none-subscribers');
1075
 
            if (none_subscribers) {
1076
 
                var none_parent = none_subscribers.get('parentNode');
1077
 
                none_parent.removeChild(none_subscribers);
1078
 
            }
1079
 
            subscribers.appendChild(link_node);
1080
 
        }
1081
 
    }
1082
 
 
1083
 
    // Set the click handler if adding a remove icon.
1084
 
    if (subscription.can_be_unsubscribed_by_user()) {
1085
 
        var remove_icon =
1086
 
          Y.one('#unsubscribe-icon-' + person.get('css_name'));
1087
 
        remove_icon.on('click', function(e) {
1088
 
            e.halt();
1089
 
            unsubscribe_user_via_icon(e.target, subscription);
1090
 
        });
1091
 
    }
1092
 
}
1093
 
 
1094
 
/*
1095
 
 * Add a grayed out, temporary user name when subscribing
1096
 
 * someone else.
1097
 
 *
1098
 
 * @method add_temp_user_name
1099
 
 * @param subscription_link {Node} The sub/unsub link.
1100
 
 */
1101
 
function add_temp_user_name(subscription) {
1102
 
    // Be paranoid about display_name, since timeouts or other errors
1103
 
    // could mean display_name wasn't set on initialization.
1104
 
    if (subscription.get('person').get('display_name') === '') {
1105
 
        subscription.get('person').set_display_name();
1106
 
    }
1107
 
    var display_name = subscription.get('person').get('display_name');
1108
 
    var img_src;
1109
 
    if (subscription.is_team()) {
1110
 
        img_src = '/@@/teamgray';
1111
 
    } else {
1112
 
        img_src = '/@@/persongray';
1113
 
    }
1114
 
 
1115
 
    // The <span>...</span> below must *not* be <span/>. On FF (maybe
1116
 
    // others, but at least on FF 3.0.11) will then not notice any
1117
 
    // following sibling nodes, like the spinner image.
1118
 
    var link_node = Y.Node.create([
1119
 
        '<div id="temp-username"> ',
1120
 
        '  <img alt="" width="14" height="14" />',
1121
 
        '  <span>Other Display Name</span>',
1122
 
        '  <img id="temp-name-spinner" src="/@@/spinner" alt="" ',
1123
 
        '       style="position:absolute;right:8px" /></div>'].join(''));
1124
 
    link_node.one('img').set('src', img_src);
1125
 
    link_node.replaceChild(
1126
 
        document.createTextNode(display_name),
1127
 
        link_node.one('span'));
1128
 
 
1129
 
    var subscribers = Y.one('#subscribers-links');
1130
 
    var next = get_next_subscriber_node(subscription);
1131
 
    if (next) {
1132
 
        subscribers.insertBefore(link_node, next);
1133
 
    } else {
1134
 
        // Handle the case of the displayed "None".
1135
 
        var none_subscribers = Y.one('#none-subscribers');
1136
 
        if (none_subscribers) {
1137
 
            var none_parent = none_subscribers.get('parentNode');
1138
 
            none_parent.removeChild(none_subscribers);
1139
 
        }
1140
 
        subscribers.appendChild(link_node);
1141
 
    }
1142
 
 
1143
 
    // Fire a custom event to know it's safe to begin
1144
 
    // any actual subscribing work.
1145
 
    namespace.portlet.fire('bugs:nameloaded', subscription);
1146
 
}
1147
 
 
1148
 
/*
1149
 
 * Add the "None" div to the subscribers list if
1150
 
 * there aren't any subscribers left.
1151
 
 *
1152
 
 * @method set_none_for_empty_subscribers
1153
 
 */
1154
 
function set_none_for_empty_subscribers() {
1155
 
    var subscriber_list = Y.one('#subscribers-links');
1156
 
    // Assume if subscriber_list has no child divs
1157
 
    // then the list of subscribers is empty.
1158
 
    if (!Y.Lang.isValue(subscriber_list.one('div')) &&
1159
 
        !Y.Lang.isValue(Y.one('#none-subscribers'))) {
1160
 
        var none_div = Y.Node.create('<div id="none-subscribers">None</div>');
1161
 
        subscriber_list.appendChild(none_div);
1162
 
    }
1163
 
 
1164
 
    // Clear the empty duplicate subscribers list if it exists.
1165
 
    var dup_list = Y.one('#subscribers-from-duplicates');
1166
 
    if (Y.Lang.isValue(dup_list) &&
1167
 
        !Y.Lang.isValue(dup_list.one('div'))) {
1168
 
        var parent = dup_list.get('parentNode');
1169
 
        parent.removeChild(dup_list);
1170
 
    }
1171
 
}
1172
 
 
1173
 
/*
1174
 
 * Set the class on subscription link's parentNode.
1175
 
 *
1176
 
 * This is used to reset the class used by the
1177
 
 * click handler to know which link was clicked.
1178
 
 *
1179
 
 * @method set_subscription_link_parent_class
1180
 
 * @param subscription_link {Node} The sub/unsub link.
1181
 
 * @param subscribed {Boolean} The sub/unsub'ed flag for the class.
1182
 
 * @param dupe_subscribed {Boolean} The sub/unsub'ed flag for dupes
1183
 
 *                                  on the class.
1184
 
 */
1185
 
function set_subscription_link_parent_class(
1186
 
    user_link, subscribed, dupe_subscribed) {
1187
 
 
1188
 
    var parent = user_link.get('parentNode');
1189
 
    if (subscribed) {
1190
 
        parent.removeClass('subscribed-false');
1191
 
        parent.addClass('subscribed-true');
1192
 
    } else {
1193
 
        parent.removeClass('subscribed-true');
1194
 
        parent.addClass('subscribed-false');
1195
 
    }
1196
 
 
1197
 
    if (dupe_subscribed) {
1198
 
        parent.removeClass('dup-subscribed-false');
1199
 
        parent.addClass('dup-subscribed-true');
1200
 
    } else {
1201
 
        parent.removeClass('dup-subscribed-true');
1202
 
        parent.addClass('dup-subscribed-false');
1203
 
    }
1204
 
}
1205
 
 
1206
 
/*
1207
 
 * Unsubscribe a user from this bugtask when a remove icon is clicked.
1208
 
 *
1209
 
 * @method unsubscribe_user_via_icon
1210
 
 * @param icon {Node} The remove icon that was clicked.
1211
 
 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
1212
 
*/
1213
 
function unsubscribe_user_via_icon(icon, subscription) {
1214
 
    icon.set('src', '/@@/spinner');
1215
 
    var icon_parent = icon.get('parentNode');
1216
 
 
1217
 
    var user_uri = get_user_uri_from_icon(icon);
1218
 
    var person = new Y.lp.bugs.subscriber.Subscriber({
1219
 
        uri: user_uri,
1220
 
        subscriber_ids: subscriber_ids
1221
 
    });
1222
 
    subscription.set('person', person);
1223
 
 
1224
 
    // Determine if this is a dupe.
1225
 
    var is_dupe;
1226
 
    var icon_parent_div = icon_parent.get('parentNode');
1227
 
    var dupe_id = 'dupe-' + person.get('css_name');
1228
 
    if (icon_parent_div.get('id') == dupe_id) {
1229
 
        is_dupe = true;
1230
 
    } else {
1231
 
        is_dupe = false;
1232
 
    }
1233
 
 
1234
 
    var error_handler = new LP.client.ErrorHandler();
1235
 
    error_handler.clearProgressUI = function () {
1236
 
        icon.set('src', '/@@/remove');
1237
 
        // Grab the icon again to reset to click handler.
1238
 
        var unsubscribe_icon = Y.one(
1239
 
            '#unsubscribe-icon-' + person.get('css_name'));
1240
 
        unsubscribe_icon.on('click', function(e) {
1241
 
            e.halt();
1242
 
            unsubscribe_user_via_icon(e.target, subscription);
1243
 
        });
1244
 
 
1245
 
    };
1246
 
    error_handler.showError = function (error_msg) {
1247
 
        var flash_node = Y.one('.' + person.get('css_name'));
1248
 
        Y.lp.app.errors.display_error(flash_node, error_msg);
1249
 
 
1250
 
    };
1251
 
 
1252
 
    var subscription_link = subscription.get('link');
1253
 
    var config = {
1254
 
        on: {
1255
 
            success: function(client) {
1256
 
                icon_parent.removeChild(icon);
1257
 
                var anim = Y.lazr.anim.green_flash({ node: icon_parent_div });
1258
 
                anim.on('end', function(e) {
1259
 
                    remove_user_name_link(icon_parent_div);
1260
 
                    set_none_for_empty_subscribers();
1261
 
                    var person_link = Y.one('.' + person.get('css_name'));
1262
 
                    if (Y.Lang.isNull(person_link) &&
1263
 
                        subscription.is_current_user_subscribing()) {
1264
 
                            // Current user has been completely unsubscribed.
1265
 
                            subscription.disable_spinner(
1266
 
                                subscription_labels.SUBSCRIBE);
1267
 
                            set_subscription_link_parent_class(
1268
 
                                subscription_link, false, false);
1269
 
                            subscription.set('is_direct', false);
1270
 
                            subscription.set('has_dupes', false);
1271
 
                    } else {
1272
 
                        if (is_dupe) {
1273
 
                            // A direct subscription remains.
1274
 
                            set_subscription_link_parent_class(
1275
 
                                subscription_link, true, false);
1276
 
                            subscription.set('is_direct', true);
1277
 
                            subscription.set('has_dupes', false);
1278
 
                        } else {
1279
 
                            // A dupe subscription remains.
1280
 
                            set_subscription_link_parent_class(
1281
 
                                subscription_link, false, true);
1282
 
                            subscription.set('is_direct', false);
1283
 
                            subscription.set('has_dupes', true);
1284
 
                        }
1285
 
                    }
1286
 
                });
1287
 
                anim.run();
1288
 
            },
1289
 
 
1290
 
            failure: error_handler.getFailureHandler()
1291
 
        }
1292
 
    };
1293
 
 
1294
 
    if (!subscription.is_current_user_subscribing()) {
1295
 
        config.parameters = {
1296
 
            person: LP.client.get_absolute_uri(user_uri)
1297
 
        };
1298
 
    }
1299
 
 
1300
 
    if (is_dupe) {
1301
 
        lp_client.named_post(
1302
 
            bug_repr.self_link, 'unsubscribeFromDupes', config);
1303
 
    } else {
1304
 
        lp_client.named_post(bug_repr.self_link, 'unsubscribe', config);
1305
 
    }
1306
 
}
1307
 
 
1308
 
/*
1309
 
 * Subscribe the current user via the LP API.
1310
 
 *
1311
 
 * @method subscribe_current_user
1312
 
 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
1313
 
 */
1314
 
function subscribe_current_user(subscription) {
1315
 
    subscription.enable_spinner('Subscribing...');
1316
 
    var subscription_link = subscription.get('link');
1317
 
    var subscriber = subscription.get('subscriber');
1318
 
    var bug_notification_level = subscription.get('bug_notification_level');
1319
 
 
1320
 
    var error_handler = new LP.client.ErrorHandler();
1321
 
    error_handler.clearProgressUI = function () {
1322
 
        subscription.disable_spinner();
1323
 
    };
1324
 
    error_handler.showError = function (error_msg) {
1325
 
        Y.lp.app.errors.display_error(subscription_link, error_msg);
1326
 
    };
1327
 
 
1328
 
    var config = {
1329
 
        on: {
1330
 
            success: function(client) {
1331
 
                if (namespace.use_advanced_subscriptions) {
1332
 
                    subscription.disable_spinner(
1333
 
                        subscription_labels.EDIT);
1334
 
                } else {
1335
 
                    subscription.disable_spinner(
1336
 
                        subscription_labels.UNSUBSCRIBE);
1337
 
                }
1338
 
 
1339
 
                if (subscription.has_duplicate_subscriptions()) {
1340
 
                    set_subscription_link_parent_class(
1341
 
                        subscription_link, true, true);
1342
 
                } else {
1343
 
                    set_subscription_link_parent_class(
1344
 
                        subscription_link, true, false);
1345
 
                }
1346
 
 
1347
 
                // Handle the case where the subscriber's list displays
1348
 
                // "None".
1349
 
                var empty_subscribers = Y.one("#none-subscribers");
1350
 
                if (empty_subscribers) {
1351
 
                    var parent = empty_subscribers.get('parentNode');
1352
 
                    parent.removeChild(empty_subscribers);
1353
 
                }
1354
 
 
1355
 
                add_user_name_link(subscription);
1356
 
 
1357
 
                var flash_node = Y.one('.' + subscriber.get('css_name'));
1358
 
                var anim = Y.lazr.anim.green_flash({ node: flash_node });
1359
 
                anim.run();
1360
 
            },
1361
 
 
1362
 
            failure: error_handler.getFailureHandler()
1363
 
        },
1364
 
 
1365
 
        parameters: {
1366
 
            person: LP.client.get_absolute_uri(subscriber.get('escaped_uri')),
1367
 
            suppress_notify: false,
1368
 
            level: bug_notification_level
1369
 
        }
1370
 
    };
1371
 
    lp_client.named_post(bug_repr.self_link, 'subscribe', config);
1372
 
}
1373
 
 
1374
 
/*
1375
 
 * Unsubscribe the current user via the LP API.
1376
 
 *
1377
 
 * @method unsubscribe_current_user
1378
 
 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
1379
 
 */
1380
 
function unsubscribe_current_user(subscription) {
1381
 
    subscription.enable_spinner('Unsubscribing...');
1382
 
    var subscription_link = subscription.get('link');
1383
 
    var subscriber = subscription.get('subscriber');
1384
 
 
1385
 
    var error_handler = new LP.client.ErrorHandler();
1386
 
    error_handler.clearProgressUI = function () {
1387
 
        subscription.disable_spinner();
1388
 
    };
1389
 
    error_handler.showError = function (error_msg) {
1390
 
        Y.lp.app.errors.display_error(subscription_link, error_msg);
1391
 
    };
1392
 
 
1393
 
    var config = {
1394
 
        on: {
1395
 
            success: function(client) {
1396
 
                if (subscription.is_direct_subscription() &&
1397
 
                    subscription.has_duplicate_subscriptions()) {
1398
 
                    // Don't change the 'Unsubscribe' text if
1399
 
                    // dupe subscriptions remain.
1400
 
                    subscription.disable_spinner();
1401
 
                    set_subscription_link_parent_class(
1402
 
                        subscription_link, false, true);
1403
 
                    subscription.set('is_direct', false);
1404
 
                } else if (subscription.is_direct_subscription() &&
1405
 
                          !subscription.has_duplicate_subscriptions()) {
1406
 
                    // Only unsub'ing a direct subscriber here.
1407
 
                    subscription.disable_spinner(
1408
 
                        subscription_labels.SUBSCRIBE);
1409
 
                    set_subscription_link_parent_class(
1410
 
                        subscription_link, false, false);
1411
 
                    subscription.set('is_direct', false);
1412
 
                } else {
1413
 
                    // Only unsub'ing dupes here.
1414
 
                    subscription.disable_spinner(
1415
 
                        subscription_labels.SUBSCRIBE);
1416
 
                    set_subscription_link_parent_class(
1417
 
                        subscription_link, false, false);
1418
 
                    subscription.set('has_dupes', false);
1419
 
                }
1420
 
 
1421
 
                var flash_node = Y.one('.' + subscriber.get('css_name'));
1422
 
                var anim = Y.lazr.anim.green_flash({ node: flash_node });
1423
 
                anim.on('end', function(e) {
1424
 
                    remove_user_name_link(flash_node);
1425
 
                    set_none_for_empty_subscribers();
1426
 
                });
1427
 
                anim.run();
1428
 
            },
1429
 
 
1430
 
            failure: error_handler.getFailureHandler()
1431
 
        }
1432
 
    };
1433
 
    if (subscription.is_direct_subscription()) {
1434
 
        lp_client.named_post(bug_repr.self_link, 'unsubscribe', config);
1435
 
    } else {
1436
 
        lp_client.named_post(
1437
 
            bug_repr.self_link, 'unsubscribeFromDupes', config);
1438
 
    }
1439
 
}
1440
 
 
1441
540
 
1442
541
/**
1443
542
 * Set up a bug task table row.
1815
914
            LP.client.cache.bug.self_link, 'markUserAffected', config);
1816
915
    }
1817
916
});
1818
 
 
1819
 
/*
1820
 
 * Check if the current user can unsubscribe the person
1821
 
 * being subscribed.
1822
 
 *
1823
 
 * This must be done in JavaScript, since the subscription
1824
 
 * hasn't completed yet, and so, can_be_unsubscribed_by_user
1825
 
 * cannot be used.
1826
 
 *
1827
 
 * @method check_can_be_unsubscribed
1828
 
 * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
1829
 
 */
1830
 
function check_can_be_unsubscribed(subscription) {
1831
 
    var error_handler = new LP.client.ErrorHandler();
1832
 
    error_handler.showError = function (error_msg) {
1833
 
        Y.lp.app.errors.display_error(
1834
 
           Y.one('.menu-link-addsubscriber'), error_msg);
1835
 
    };
1836
 
 
1837
 
    var config = {
1838
 
        on: {
1839
 
            success: function(result) {
1840
 
                var is_team = result.get('is_team');
1841
 
                subscription.set('is_team', is_team);
1842
 
                var final_config = {
1843
 
                    on: {
1844
 
                        success: function(result) {
1845
 
                            var team_member = false;
1846
 
                            for (var i=0; i<result.entries.length; i++) {
1847
 
                                 if (result.entries[i].member_link ==
1848
 
                                    LP.client.get_absolute_uri(
1849
 
                                        subscription.get(
1850
 
                                            'subscriber').get('uri'))) {
1851
 
                                    team_member = true;
1852
 
                                }
1853
 
                            }
1854
 
 
1855
 
                            if (team_member) {
1856
 
                                subscription.set('can_be_unsubscribed', true);
1857
 
                                add_temp_user_name(subscription);
1858
 
                            } else {
1859
 
                                subscription.set(
1860
 
                                   'can_be_unsubscribed', false);
1861
 
                                add_temp_user_name(subscription);
1862
 
                            }
1863
 
                        },
1864
 
 
1865
 
                        failure: error_handler.getFailureHandler()
1866
 
                    }
1867
 
                };
1868
 
 
1869
 
                if (is_team) {
1870
 
                    // Get a list of members to see if current user
1871
 
                    // is a team member.
1872
 
                    var members = result.get(
1873
 
                       'members_details_collection_link');
1874
 
                    lp_client.get(members, final_config);
1875
 
                } else {
1876
 
                    subscription.set('can_be_unsubscribed', false);
1877
 
                    add_temp_user_name(subscription);
1878
 
                }
1879
 
            },
1880
 
 
1881
 
            failure: error_handler.getFailureHandler()
1882
 
        }
1883
 
    };
1884
 
    lp_client.get(LP.client.get_absolute_uri(
1885
 
        subscription.get('person').get('escaped_uri')), config);
1886
 
}
1887
 
 
1888
 
/*
1889
 
 * Subscribe a person or team other than the current user.
1890
 
 * This is a callback for the subscribe someone else picker.
1891
 
 *
1892
 
 * @method subscribe_someone_else
1893
 
 * @result {Object} The object representing a person returned by the API.
1894
 
 */
1895
 
function subscribe_someone_else(result, subscription) {
1896
 
    var person = new Y.lp.bugs.subscriber.Subscriber({
1897
 
        uri: result.api_uri,
1898
 
        display_name: result.title,
1899
 
        subscriber_ids: subscriber_ids
1900
 
    });
1901
 
    subscription.set('person', person);
1902
 
 
1903
 
    var error_handler = new LP.client.ErrorHandler();
1904
 
    error_handler.showError = function(error_msg) {
1905
 
        Y.lp.app.errors.display_error(
1906
 
           Y.one('.menu-link-addsubscriber'), error_msg);
1907
 
    };
1908
 
 
1909
 
    if (subscription.is_already_subscribed()) {
1910
 
        error_handler.showError(
1911
 
             subscription.get('person').get('full_display_name') +
1912
 
             ' has already been subscribed');
1913
 
    } else {
1914
 
        check_can_be_unsubscribed(subscription);
1915
 
    }
1916
 
}
1917
 
 
1918
917
/*
1919
918
 * Click handling to pass comment text to the attachment
1920
919
 * page if there is a comment.
1935
934
    });
1936
935
}
1937
936
 
1938
 
function load_subscribers_from_duplicates() {
1939
 
    if (Y.UA.ie) {
1940
 
        return null;
1941
 
    }
1942
 
 
1943
 
    Y.one('#subscribers-portlet-dupe-spinner').setStyle(
1944
 
        'display', 'block');
1945
 
 
1946
 
    function hide_spinner() {
1947
 
        Y.one('#subscribers-portlet-dupe-spinner').setStyle(
1948
 
            'display', 'none');
1949
 
        // Fire a custom event to signal failure, so that
1950
 
        // any remaining unsub icons can be hooked up.
1951
 
        namespace.portlet.fire('bugs:dupeportletloadfailed');
1952
 
    }
1953
 
 
1954
 
    function on_success(transactionid, response, args) {
1955
 
        hide_spinner();
1956
 
 
1957
 
        var dupe_subscribers_container = Y.one(
1958
 
            '#subscribers-from-duplicates-container');
1959
 
        dupe_subscribers_container.set(
1960
 
            'innerHTML',
1961
 
            dupe_subscribers_container.get('innerHTML') +
1962
 
            response.responseText);
1963
 
 
1964
 
        // Fire a custom portlet loaded event to notify when
1965
 
        // it's safe to setup dupe subscriber link callbacks.
1966
 
        namespace.portlet.fire('bugs:dupeportletloaded');
1967
 
    }
1968
 
 
1969
 
    var config = {on: {success: on_success,
1970
 
                       failure: hide_spinner}};
1971
 
    var url = Y.one(
1972
 
        '#subscribers-from-dupes-content-link').getAttribute(
1973
 
            'href').replace('bugs.', '');
1974
 
    Y.io(url, config);
1975
 
}
1976
 
 
1977
 
namespace.load_subscribers_portlet = function(
1978
 
        subscription_link, subscription_link_handler) {
1979
 
    if (Y.UA.ie) {
1980
 
        return null;
1981
 
    }
1982
 
 
1983
 
    Y.one('#subscribers-portlet-spinner').setStyle('display', 'block');
1984
 
 
1985
 
    function hide_spinner() {
1986
 
        Y.one('#subscribers-portlet-spinner').setStyle('display', 'none');
1987
 
            // Fire a custom event to notify that the initial click
1988
 
            // handler on subscription_link set above should be
1989
 
            // cleared.
1990
 
            if (namespace) {
1991
 
                namespace.portlet.fire(
1992
 
                  'bugs:portletloadfailed', subscription_link_handler);
1993
 
        }
1994
 
    }
1995
 
 
1996
 
    function setup_portlet(transactionid, response, args) {
1997
 
        hide_spinner();
1998
 
        var portlet = Y.one('#portlet-subscribers');
1999
 
        portlet.set('innerHTML',
2000
 
                    portlet.get('innerHTML') + response.responseText);
2001
 
 
2002
 
        // Fire a custom portlet loaded event to notify when
2003
 
        // it's safe to setup subscriber link callbacks.
2004
 
        namespace.portlet.fire('bugs:portletloaded');
2005
 
    }
2006
 
 
2007
 
    var config = {on: {success: setup_portlet,
2008
 
                       failure: hide_spinner}};
2009
 
    var url = Y.one(
2010
 
        '#subscribers-content-link').getAttribute('href').replace(
2011
 
            'bugs.', '');
2012
 
    Y.io(url, config);
2013
 
};
2014
 
 
2015
 
function load_subscriber_ids() {
2016
 
    function on_success(transactionid, response, args) {
2017
 
        try {
2018
 
            subscriber_ids = Y.JSON.parse(response.responseText);
2019
 
 
2020
 
            // Fire a custom event to trigger the setting-up of the
2021
 
            // subscription handlers.
2022
 
            namespace.portlet.fire('bugs:portletsubscriberidsloaded');
2023
 
        } catch (e) {
2024
 
            // Fire an event to signal failure. This ensures that the
2025
 
            // subscribers-from-dupes still get loaded into the portlet.
2026
 
            namespace.portlet.fire('bugs:portletsubscriberidsfailed');
2027
 
        }
2028
 
    }
2029
 
 
2030
 
    function on_failure() {
2031
 
        // Fire an event to signal failure. This ensures that the
2032
 
        // subscribers-from-dupes still get loaded into the portlet.
2033
 
        namespace.portlet.fire('bugs:portletsubscriberidsfailed');
2034
 
    }
2035
 
 
2036
 
    var config = {on: {success: on_success,
2037
 
                       failure: on_failure}};
2038
 
    var url = Y.one(
2039
 
        '#subscribers-ids-link').getAttribute('href');
2040
 
    Y.io(url, config);
2041
 
}
2042
937
 
2043
938
}, "0.1", {"requires": ["base", "oop", "node", "event", "io-base",
2044
939
                        "json-parse", "substitute", "widget-position-ext",
2045
940
                        "lazr.formoverlay", "lazr.anim", "lazr.base",
2046
941
                        "lazr.overlay", "lazr.choiceedit", "lp.app.picker",
2047
 
                        "lp.client.plugins", "lp.bugs.subscriber",
2048
 
                        "lp.app.errors"]});
 
942
                        "lp.client.plugins", "lp.bugs.bugtask_index.portlets",
 
943
                        "lp.bugs.subscriber", "lp.app.errors"]});