<commit_before>/**
 * @license
 * Patterns @VERSION@ tooltip - tooltips
 *
 * Copyright 2008-2012 Simplon B.V.
 * Copyright 2011 Humberto Sermeño
 * Copyright 2011 SYSLAB.COM GmbH
 */
define([
    'jquery',
    "../registry",
    '../utils',
    './inject'
], function($, patterns, utils, inject) {
    var tooltip = {
        name: "tooltip",
        trigger: "[data-tooltip]",

        count: 0,

        init: function($root) {
            return $root.each(function() {
                var $trigger = $(this),
                    options = utils.parseOptions($trigger.data("tooltip"));
                options.title = $trigger.attr("title");
                $trigger.removeAttr("title");
                $trigger.data("patterns.tooltip", options);
                tooltip.setupShowEvents($trigger);
            });
        },

        setupShowEvents: function($trigger) {
            var parameters = $trigger.data("patterns.tooltip");
            if (parameters.click) {
                $trigger.on("click.tooltip", $trigger, tooltip.show);
            } else {
                $trigger.on("mouseover.tooltip", $trigger, tooltip.show);
                // Make sure click on the trigger element becomes a NOP
                $trigger.on("click.tooltip", $trigger, tooltip.blockDefault);
            }
        },

        removeShowEvents: function($trigger) {
            $trigger.off(".tooltip");
        },

        setupHideEvents: function($trigger) {
            var $container = tooltip.getContainer($trigger),
                parameters = $trigger.data("patterns.tooltip");
            if (parameters.sticky) {
                $container.find(".close-panel")
                    .on("click.tooltip", $trigger, tooltip.hide);
                // Make sure click on the trigger element becomes a NOP
                $trigger.on("click.tooltip", $trigger, tooltip.blockDefault);
            } else {
                if (parameters.click) {
                    $container.on("click.tooltip", $trigger, function(ev) {
                        ev.stopPropagation();
                    });
                    $(document).on("click.tooltip", $trigger, tooltip.hide);
                    $trigger.on("click.tooltip", tooltip.blockDefault);
                    // close if something inside the tooltip triggered an injection
                    $container.on('patterns-inject-triggered.tooltip',
                                  $trigger, tooltip.hide);
                    $container.on('submit.tooltip', $trigger, tooltip.hide);
                } else {
                    $container.on("click.tooltip", $trigger, tooltip.hide);
                    $trigger.on("mouseleave.tooltip", $trigger, tooltip.hide);
                    $trigger.on("click.tooltip", tooltip.blockDefault);
                }
            }
        },

        removeHideEvents: function($trigger) {
            var $container = tooltip.getContainer($trigger);
            $(document).off(".tooltip");
            $container.off(".tooltip");
            $container.find(".close-panel").off(".tooltip");
            $trigger.off(".tooltip");
        },

        blockDefault: function(event) {
            event.preventDefault();
        },

        show: function(event) {
            event.preventDefault();
            var $trigger = event.data,
                $container = tooltip.getContainer($trigger),
                namespace = $container.attr("id"),
                options = $trigger.data("patterns.tooltip");

            tooltip.removeShowEvents($trigger);
            // Wrap in a timeout to make sure this click is not used to
            // trigger a hide as well.
            setTimeout(function() { tooltip.setupHideEvents($trigger); }, 50);

            function ajax_show() {
                $container.find(">div >*").css("opacity", 1);
                tooltip.positionContainer($trigger, $container);
            }

            if (options.ajax) {
                var source = $trigger.attr("href").split("#"),
                    target_id = $container.find("progress").attr("id");
                inject.execute($trigger, source[0], target_id+":replace", source[1] || [],
                        ajax_show, true);
                // always load fresh tooltips
                // delete options.ajax;
                $trigger.data("patterns.tooltip", options);
            }

            tooltip.positionContainer($trigger, $container);
            $container.css("visibility", "visible");

            // reposition tooltip everytime we scroll or resize
            $(window).on("scroll." + namespace + " resize." + namespace, function () {
                 tooltip.positionContainer($trigger, $container);
            });
        },

        hide: function(event) {
            var $trigger = event.data,
                $container = tooltip.getContainer($trigger),
                namespace = $container.attr("id");
            tooltip.removeHideEvents($trigger);
            $container.css("visibility", "hidden");
            $(window).off("." + namespace);
            tooltip.setupShowEvents($trigger);
        },

        getContainer: function($trigger) {
            var $container = $trigger.data("patterns.tooltip.container");
            if ($container===undefined) {
                $container=tooltip.createContainer($trigger);
                $trigger.data("patterns.tooltip.container", $container);
            }
            return $container;
        },

        createContainer: function($trigger) {
            var options = $trigger.data("patterns.tooltip"),
                count = ++tooltip.count,
                $content, $container;

            $container = $("
", {"class": "tooltip-container",
                                     "id": "tooltip" + count});
            $container.css("visibility", "hidden");
            if (options.ajax) {
                $content = $("<progress/>", {"id": "tooltip-load-" + count});
            } else {
                $content = $("
").text(options.title);
            }
            $container.append(
                $("
").css("display", "block").append($content))
                .append($("", {"class": "pointer"}));
            if (options.sticky && !options.noclose) {
                $("<button/>", {"class": "close-panel"})
                    .text("Close")
                    .insertBefore($container.find("*"));
            }
            $("body").append($container);
            return $container;
        },

        boundingBox: function($el) {
            var box = $el.offset();
            box.height = $el.height();
            box.width = $el.width();
            box.bottom = box.top + box.height;
            box.right = box.left + box.width;
            return box;
        },

        positionStatus: function($trigger, $container) {
            var trigger_box = tooltip.boundingBox($trigger),
                tooltip_box = tooltip.boundingBox($container),
                $window = $(window),
                window_width = $window.width(),
                window_height = $window.height(),
                trigger_center,
                scroll = {},
                space = {},
                container_offset = {},
                tip_offset = {},
                cls = "";

            scroll.top = $window.scrollTop();
            scroll.left = $window.scrollLeft();
            trigger_center = {top: trigger_box.top + (trigger_box.height/2),
                              left: trigger_box.left + (trigger_box.width/2)};
            space.top = trigger_box.top - scroll.top;
            space.bottom = window_height - space.top - trigger_box.height;
            space.left = trigger_box.left - scroll.left;
            space.right = window_width - space.left - trigger_box.width;

            return {space: space,
                    trigger_center: trigger_center,
                    trigger_box: trigger_box,
                    tooltip_box: tooltip_box,
                    scroll: scroll,
                    window: {width: window_width, height: window_height}
            };
        },

        // Help function to determine the best position for a tooltip.  Takes
        // the positioning status (as generated by positionStatus) as input
        // and returns a two-character position indiciator.
        findBestPosition: function(status) {
            var space = status.space,
                 cls = "";

            if (space.top > Math.max(space.right, space.bottom, space.left)) {
                cls = "b";
            } else if (space.right > Math.max(space.bottom, space.left, space.top)) {
                cls = "l";
            } else if (space.bottom > Math.max(space.left, space.top, space.right)) {
                cls = "t";
            } else {
                cls = "r";
            }

            switch (cls[0]) {
            case "t":
            case "b":
                if (Math.abs(space.left-space.right) < 20) {
                    cls += "m";
                } else if (space.left > space.right) {
                    cls += "r";
                } else {
                    cls += "l";
                }
                break;
            case "l":
            case "r":
                if (Math.abs(space.top-space.bottom) < 20) {
                    cls += "m";
                } else if (space.top > space.bottom) {
                    cls += "b";
                } else {
                    cls += "t";
                }
            }
            return cls;
        },

        isVisible: function(status, position) {
            var space = status.space,
                tooltip_box = status.tooltip_box;

            switch (position[0]) {
            case "t":
                if (tooltip_box.height > space.bottom) {
                    return false;
                }
                break;
            case "r":
                if (tooltip_box.width > space.left) {
                    return false;
                }
                break;
            case "b":
                if (tooltip_box.height > space.top) {
                    return false;
                }
                break;
            case "l":
                if (tooltip_box.width > space.right) {
                    return false;
                }
                break;
            default:
                return false;
            }

            switch (position[0]) {
            case "t":
            case "b":
                switch (position[1]) {
                    case "l":
                        if ((tooltip_box.width-20)>space.right) {
                            return false;
                        }
                        break;
                    case "m":
                        if ((tooltip_box.width/2)>space.left || (tooltip_box.width/2)>space.right) {
                            return false;
                        }
                        break;
                    case "r":
                        if ((tooltip_box.width-20)>space.left) {
                            return false;
                        }
                        break;
                    default:
                        return false;
                }
                break;
            case "l":
            case "r":
                switch (position[1]) {
                    case "t":
                        if ((tooltip_box.height-20)>space.bottom) {
                            return false;
                        }
                        break;
                    case "m":
                        if ((tooltip_box.height/2)>space.top || (tooltip_box.height/2)>space.bottom) {
                            return false;
                        }
                        break;
                    case "b":
                        if ((tooltip_box.height-20)>space.top) {
                            return false;
                        }
                        break;
                    default:
                        return false;
                }
                break;
            }
            return true;
        },

        VALIDPOSITION: /^([lr][tmb]|[tb][lmr])$/,

        positionContainer: function($trigger, $container) {
            var status = tooltip.positionStatus($trigger, $container),
                options = $trigger.data("patterns.tooltip"),
                container_offset = {},
                tip_offset = {},
                position;

            if (options.position) {
                var positions = options.position.split("-"), i;
                for (i=0; i<positions.length; i++) {
                    if (!tooltip.VALIDPOSITION.test(positions[i])) {
                        continue;
                    }

                    if (options.forcePosition || tooltip.isVisible(status, positions[i])) {
                        position = positions[i];
                        break;
                    }
                }
            }

            if (!position) {
                position = tooltip.findBestPosition(status);
            }

            var trigger_box = status.trigger_box,
                tooltip_box = status.tooltip_box,
                trigger_center = status.trigger_center,
                content_css = {"max-height": "", "max-width": ""},
                $window = $(window),
                bottom_row, x;

            switch (position[0]) {
            case "t":
                container_offset.top = trigger_box.bottom + 20;
                tip_offset.top = -23;
                bottom_row = status.scroll.top + status.window.height,
                content_css["max-height"] = (bottom_row - container_offset.top - 30) + "px";
                break;
            case "l":
                container_offset.left = trigger_box.right + 20;
                tip_offset.left = -23;
                x = status.window.width + status.scroll.left;
                content_css["max-width"] = (x - container_offset.left - 30) + "px";
                break;
            case "b":
                container_offset.top = trigger_box.top - tooltip_box.height + 10;
                tip_offset.top = tooltip_box.height;
                x = (status.scroll.top + 10) - container_offset.top;
                if (x>0) {
                    tip_offset.top -= x;
                    content_css["max-height"] = (tooltip_box.height - x) + "px";
                    container_offset.top += x;
                }
                break;
            case "r":
                container_offset.left = trigger_box.left - tooltip_box.width - 20;
                tip_offset.left = tooltip_box.width;
                break;
            }

            switch (position[0]) {
            case "t":
            case "b":
                switch (position[1]) {
                case "l":
                    container_offset.left = trigger_center.left - 20;
                    tip_offset.left = 0;
                    break;
                case "m":
                    container_offset.left = trigger_center.left - (tooltip_box.width/2);
                    tip_offset.left = tooltip_box.width/2 - 10;
                    break;
                case "r":
                    container_offset.left = trigger_center.left + 29 - tooltip_box.width;
                    tip_offset.left = tooltip_box.width - 20;
                    break;
                }
                break;
            case "l":
            case "r":
                switch (position[1]) {
                    case "t":
                        container_offset.top = trigger_center.top - 30;
                        tip_offset.top = 0;
                        break;
                    case "m":
                        container_offset.top = trigger_center.top - (tooltip_box.height/2);
                        tip_offset.top = tooltip_box.height/2 - 10;
                        break;
                    case "b":
                        container_offset.top = trigger_center.top + 20 - tooltip_box.height;
                        tip_offset.top = tooltip_box.height - 20;
                        break;
                }
                break;
            }

            $container.find("> div").css(content_css);
            $container.attr("class", "tooltip-container " + position);
            $container.css({
                top: container_offset.top+"px",
                left: container_offset.left+"px"
            });
            $container.find(".pointer").css({
                top: tip_offset.top+"px",
                left: tip_offset.left+"px"});
        }
    };

    patterns.register(tooltip);
    return tooltip; // XXX Replace for tests
});

// jshint indent: 4, browser: true, jquery: true, quotmark: double
// vim: sw=4 expandtab
<commit_msg>fix ajax tooltips<commit_after>/**
 * @license
 * Patterns @VERSION@ tooltip - tooltips
 *
 * Copyright 2008-2012 Simplon B.V.
 * Copyright 2011 Humberto Sermeño
 * Copyright 2011 SYSLAB.COM GmbH
 */
define([
    'jquery',
    "../registry",
    '../utils',
    './inject'
], function($, patterns, utils, inject) {
    var tooltip = {
        name: "tooltip",
        trigger: "[data-tooltip]",

        count: 0,

        init: function($root) {
            return $root.each(function() {
                var $trigger = $(this),
                    options = utils.parseOptions($trigger.data("tooltip"));
                options.title = $trigger.attr("title");
                $trigger.removeAttr("title");
                $trigger.data("patterns.tooltip", options);
                tooltip.setupShowEvents($trigger);
            });
        },

        setupShowEvents: function($trigger) {
            var parameters = $trigger.data("patterns.tooltip");
            if (parameters.click) {
                $trigger.on("click.tooltip", $trigger, tooltip.show);
            } else {
                $trigger.on("mouseover.tooltip", $trigger, tooltip.show);
                // Make sure click on the trigger element becomes a NOP
                $trigger.on("click.tooltip", $trigger, tooltip.blockDefault);
            }
        },

        removeShowEvents: function($trigger) {
            $trigger.off(".tooltip");
        },

        setupHideEvents: function($trigger) {
            var $container = tooltip.getContainer($trigger),
                parameters = $trigger.data("patterns.tooltip");
            if (parameters.sticky) {
                $container.find(".close-panel")
                    .on("click.tooltip", $trigger, tooltip.hide);
                // Make sure click on the trigger element becomes a NOP
                $trigger.on("click.tooltip", $trigger, tooltip.blockDefault);
            } else {
                if (parameters.click) {
                    $container.on("click.tooltip", $trigger, function(ev) {
                        ev.stopPropagation();
                    });
                    $(document).on("click.tooltip", $trigger, tooltip.hide);
                    $trigger.on("click.tooltip", tooltip.blockDefault);
                    // close if something inside the tooltip triggered an injection
                    $container.on('patterns-inject-triggered.tooltip',
                                  $trigger, tooltip.hide);
                    $container.on('submit.tooltip', $trigger, tooltip.hide);
                } else {
                    $container.on("click.tooltip", $trigger, tooltip.hide);
                    $trigger.on("mouseleave.tooltip", $trigger, tooltip.hide);
                    $trigger.on("click.tooltip", tooltip.blockDefault);
                }
            }
        },

        removeHideEvents: function($trigger) {
            var $container = tooltip.getContainer($trigger);
            $(document).off(".tooltip");
            $container.off(".tooltip");
            $container.find(".close-panel").off(".tooltip");
            $trigger.off(".tooltip");
        },

        blockDefault: function(event) {
            event.preventDefault();
        },

        show: function(event) {
            event.preventDefault();
            var $trigger = event.data,
                $container = tooltip.getContainer($trigger),
                namespace = $container.attr("id"),
                options = $trigger.data("patterns.tooltip");

            tooltip.removeShowEvents($trigger);
            // Wrap in a timeout to make sure this click is not used to
            // trigger a hide as well.
            setTimeout(function() { tooltip.setupHideEvents($trigger); }, 50);

            function ajax_show() {
                $container.find(">div >*").css("opacity", 1);
                tooltip.positionContainer($trigger, $container);
            }

            if (options.ajax) {
                var source = $trigger.attr("href").split("#"),
                    target_id = $container.find("progress").attr("id");
                inject.execute([{
                    url: source[0],
                    source: '#' + source[1],
                    target: '#' + target_id + "::element"
                }]);
                // always load fresh tooltips
                // delete options.ajax;
                $trigger.data("patterns.tooltip", options);
            }

            tooltip.positionContainer($trigger, $container);
            $container.css("visibility", "visible");

            // reposition tooltip everytime we scroll or resize
            $(window).on("scroll." + namespace + " resize." + namespace, function () {
                 tooltip.positionContainer($trigger, $container);
            });
        },

        hide: function(event) {
            var $trigger = event.data,
                $container = tooltip.getContainer($trigger),
                namespace = $container.attr("id");
            tooltip.removeHideEvents($trigger);
            $container.css("visibility", "hidden");
            $(window).off("." + namespace);
            tooltip.setupShowEvents($trigger);
        },

        getContainer: function($trigger) {
            var $container = $trigger.data("patterns.tooltip.container");
            if ($container===undefined) {
                $container=tooltip.createContainer($trigger);
                $trigger.data("patterns.tooltip.container", $container);
            }
            return $container;
        },

        createContainer: function($trigger) {
            var options = $trigger.data("patterns.tooltip"),
                count = ++tooltip.count,
                $content, $container;

            $container = $("
", {"class": "tooltip-container",
                                     "id": "tooltip" + count});
            $container.css("visibility", "hidden");
            if (options.ajax) {
                $content = $("<progress/>", {"id": "tooltip-load-" + count});
            } else {
                $content = $("
").text(options.title);
            }
            $container.append(
                $("
").css("display", "block").append($content))
                .append($("", {"class": "pointer"}));
            if (options.sticky && !options.noclose) {
                $("<button/>", {"class": "close-panel"})
                    .text("Close")
                    .insertBefore($container.find("*"));
            }
            $("body").append($container);
            return $container;
        },

        boundingBox: function($el) {
            var box = $el.offset();
            box.height = $el.height();
            box.width = $el.width();
            box.bottom = box.top + box.height;
            box.right = box.left + box.width;
            return box;
        },

        positionStatus: function($trigger, $container) {
            var trigger_box = tooltip.boundingBox($trigger),
                tooltip_box = tooltip.boundingBox($container),
                $window = $(window),
                window_width = $window.width(),
                window_height = $window.height(),
                trigger_center,
                scroll = {},
                space = {},
                container_offset = {},
                tip_offset = {},
                cls = "";

            scroll.top = $window.scrollTop();
            scroll.left = $window.scrollLeft();
            trigger_center = {top: trigger_box.top + (trigger_box.height/2),
                              left: trigger_box.left + (trigger_box.width/2)};
            space.top = trigger_box.top - scroll.top;
            space.bottom = window_height - space.top - trigger_box.height;
            space.left = trigger_box.left - scroll.left;
            space.right = window_width - space.left - trigger_box.width;

            return {space: space,
                    trigger_center: trigger_center,
                    trigger_box: trigger_box,
                    tooltip_box: tooltip_box,
                    scroll: scroll,
                    window: {width: window_width, height: window_height}
            };
        },

        // Help function to determine the best position for a tooltip.  Takes
        // the positioning status (as generated by positionStatus) as input
        // and returns a two-character position indiciator.
        findBestPosition: function(status) {
            var space = status.space,
                 cls = "";

            if (space.top > Math.max(space.right, space.bottom, space.left)) {
                cls = "b";
            } else if (space.right > Math.max(space.bottom, space.left, space.top)) {
                cls = "l";
            } else if (space.bottom > Math.max(space.left, space.top, space.right)) {
                cls = "t";
            } else {
                cls = "r";
            }

            switch (cls[0]) {
            case "t":
            case "b":
                if (Math.abs(space.left-space.right) < 20) {
                    cls += "m";
                } else if (space.left > space.right) {
                    cls += "r";
                } else {
                    cls += "l";
                }
                break;
            case "l":
            case "r":
                if (Math.abs(space.top-space.bottom) < 20) {
                    cls += "m";
                } else if (space.top > space.bottom) {
                    cls += "b";
                } else {
                    cls += "t";
                }
            }
            return cls;
        },

        isVisible: function(status, position) {
            var space = status.space,
                tooltip_box = status.tooltip_box;

            switch (position[0]) {
            case "t":
                if (tooltip_box.height > space.bottom) {
                    return false;
                }
                break;
            case "r":
                if (tooltip_box.width > space.left) {
                    return false;
                }
                break;
            case "b":
                if (tooltip_box.height > space.top) {
                    return false;
                }
                break;
            case "l":
                if (tooltip_box.width > space.right) {
                    return false;
                }
                break;
            default:
                return false;
            }

            switch (position[0]) {
            case "t":
            case "b":
                switch (position[1]) {
                    case "l":
                        if ((tooltip_box.width-20)>space.right) {
                            return false;
                        }
                        break;
                    case "m":
                        if ((tooltip_box.width/2)>space.left || (tooltip_box.width/2)>space.right) {
                            return false;
                        }
                        break;
                    case "r":
                        if ((tooltip_box.width-20)>space.left) {
                            return false;
                        }
                        break;
                    default:
                        return false;
                }
                break;
            case "l":
            case "r":
                switch (position[1]) {
                    case "t":
                        if ((tooltip_box.height-20)>space.bottom) {
                            return false;
                        }
                        break;
                    case "m":
                        if ((tooltip_box.height/2)>space.top || (tooltip_box.height/2)>space.bottom) {
                            return false;
                        }
                        break;
                    case "b":
                        if ((tooltip_box.height-20)>space.top) {
                            return false;
                        }
                        break;
                    default:
                        return false;
                }
                break;
            }
            return true;
        },

        VALIDPOSITION: /^([lr][tmb]|[tb][lmr])$/,

        positionContainer: function($trigger, $container) {
            var status = tooltip.positionStatus($trigger, $container),
                options = $trigger.data("patterns.tooltip"),
                container_offset = {},
                tip_offset = {},
                position;

            if (options.position) {
                var positions = options.position.split("-"), i;
                for (i=0; i<positions.length; i++) {
                    if (!tooltip.VALIDPOSITION.test(positions[i])) {
                        continue;
                    }

                    if (options.forcePosition || tooltip.isVisible(status, positions[i])) {
                        position = positions[i];
                        break;
                    }
                }
            }

            if (!position) {
                position = tooltip.findBestPosition(status);
            }

            var trigger_box = status.trigger_box,
                tooltip_box = status.tooltip_box,
                trigger_center = status.trigger_center,
                content_css = {"max-height": "", "max-width": ""},
                $window = $(window),
                bottom_row, x;

            switch (position[0]) {
            case "t":
                container_offset.top = trigger_box.bottom + 20;
                tip_offset.top = -23;
                bottom_row = status.scroll.top + status.window.height,
                content_css["max-height"] = (bottom_row - container_offset.top - 30) + "px";
                break;
            case "l":
                container_offset.left = trigger_box.right + 20;
                tip_offset.left = -23;
                x = status.window.width + status.scroll.left;
                content_css["max-width"] = (x - container_offset.left - 30) + "px";
                break;
            case "b":
                container_offset.top = trigger_box.top - tooltip_box.height + 10;
                tip_offset.top = tooltip_box.height;
                x = (status.scroll.top + 10) - container_offset.top;
                if (x>0) {
                    tip_offset.top -= x;
                    content_css["max-height"] = (tooltip_box.height - x) + "px";
                    container_offset.top += x;
                }
                break;
            case "r":
                container_offset.left = trigger_box.left - tooltip_box.width - 20;
                tip_offset.left = tooltip_box.width;
                break;
            }

            switch (position[0]) {
            case "t":
            case "b":
                switch (position[1]) {
                case "l":
                    container_offset.left = trigger_center.left - 20;
                    tip_offset.left = 0;
                    break;
                case "m":
                    container_offset.left = trigger_center.left - (tooltip_box.width/2);
                    tip_offset.left = tooltip_box.width/2 - 10;
                    break;
                case "r":
                    container_offset.left = trigger_center.left + 29 - tooltip_box.width;
                    tip_offset.left = tooltip_box.width - 20;
                    break;
                }
                break;
            case "l":
            case "r":
                switch (position[1]) {
                    case "t":
                        container_offset.top = trigger_center.top - 30;
                        tip_offset.top = 0;
                        break;
                    case "m":
                        container_offset.top = trigger_center.top - (tooltip_box.height/2);
                        tip_offset.top = tooltip_box.height/2 - 10;
                        break;
                    case "b":
                        container_offset.top = trigger_center.top + 20 - tooltip_box.height;
                        tip_offset.top = tooltip_box.height - 20;
                        break;
                }
                break;
            }

            $container.find("> div").css(content_css);
            $container.attr("class", "tooltip-container " + position);
            $container.css({
                top: container_offset.top+"px",
                left: container_offset.left+"px"
            });
            $container.find(".pointer").css({
                top: tip_offset.top+"px",
                left: tip_offset.left+"px"});
        }
    };

    patterns.register(tooltip);
    return tooltip; // XXX Replace for tests
});

// jshint indent: 4, browser: true, jquery: true, quotmark: double
// vim: sw=4 expandtab
<|endoftext|>


<commit_before>require('./logging');
var mqtt = require('mqtt');
var Web3 = require('web3');
var lightwallet = require('eth-lightwallet');
var HookedWeb3Provider = require("hooked-web3-provider");
var fs = require('fs');

var membershipcontract = require('../app/contracts/LocalsMembership.json');
var membershipcontractaddress = "0x83883514f7fcb0cf627829d067f0e8488201f6b9";
var host = "http://kingflurkel.dtdns.net:8545";
//var channel = "adam";
//var keystoreFile = "adamswallet.json";
//var poesimage = "adampoes";
//var poesname = "Adam";
var keystoreFile = "evaswallet.json";
var channel = "eva";
var poesimage = "evapoes";
var poesname = "Eva";

var validationcontract = require('../app/contracts/LocalsValidation.json');

web3 = new Web3();
web3.setProvider(new web3.providers.HttpProvider(host));

var contents = fs.readFileSync(keystoreFile, 'utf8');
var global_keystore = lightwallet.keystore.deserialize(contents);

global_keystore.passwordProvider = function(callback) {
  callback(null, 'testing')
};

console.log("Your wallet accounts:", global_keystore.getAddresses());
console.log("it s a", typeof global_keystore.getAddresses()[0]);

var account = fixaddress(global_keystore.getAddresses()[0].toString());
console.log('Your first account is ', account);

web3 = new Web3();
var provider = new HookedWeb3Provider({
  host: host,
  transaction_signer: global_keystore
});
web3.setProvider(provider);

web3_monitor = new Web3();
web3_monitor.setProvider(new web3.providers.HttpProvider(host));

var gasPrice;

web3.eth.getGasPrice(function(err, result) {

  gasPrice = result.toNumber(10);
  console.log('gasprice is ', gasPrice);

  connectMQTT();
  printBalance(account);

});

function connectMQTT() {

  console.log('connecting MQTT now');

  var client = mqtt.connect('ws://opantwerpen.be:15674');

  client.on('connect', function() {
    console.log('Adam is connected to MQTT');
    client.subscribe(channel);
  });

  client.on('message', function(topic, message) {
    // message is Buffer
    console.log('Adam received a message');
    console.log(message.toString());

    var msg = message.toString();

    var commandarray = msg.split("|");

    console.log(commandarray);
    switch (commandarray[0]) {
      case "requestmembership":
        var memberaddress = fixaddress(commandarray[1]);
        console.log('requestmembership for address ', memberaddress);

        if (isMember(memberaddress)) {
          console.log('already a member');
        } else {
          console.log('not a member yet');
          // TODO : maak hem member...
          requestMembership(memberaddress);
        }
        break;
      case "validate":
        var contractaddress = fixaddress(commandarray[3]);
        console.log('validate for contractaddress ', contractaddress);
        validate(contractaddress, function(err, res) {
          console.log('validated');
        });
        // Send message back...
        var incomingpussyname = commandarray[1];
        var incomingpussypic = "../../images/poezen/poes" + commandarray[2] + ".png";
        var contractaddresstowrite = commandarray[3];
        var incomingpin = commandarray[4];
        var incomingref = commandarray[5];

        client.publish(incomingpin, "validationstarted" + "|" + poesimage + "|" + poesname + "|" + account + "|" + incomingref);

        break;

      default:
        console.log('unknown command:', commandarray[0]);
        break;
    }
  });
}

var MyContract = web3.eth.contract(membershipcontract.abi);
var myContractInstance = MyContract.at(membershipcontractaddress);

myContractInstance.MemberAdded(function(err, res) {

  console.log('MEMBERADDED triggered SUSKE');
  console.log('err', err);
  console.log('res', res);
  printBalance(account);

});

function validate(contractaddress, cb) {

  web3.eth.getGasPrice(function(err, result) {

    var gasPrice = result.toNumber(10);

    // creation of contract object
    var Myvalidationcontract = web3.eth.contract(validationcontract.abi);
    var myContractInstance = Myvalidationcontract.at(contractaddress);

    var options = {
      from: account,
      value: 1.5 * 1e18,
      gas: 3141590,
      gasPrice: gasPrice,
      nonce: Math.floor(Math.random(999999)) + new Date().getTime(),
    };

    console.log('contract to validate ', contractaddress);
    console.log('contract options', options);

    var result = myContractInstance.addValidation.sendTransaction(options,
      function(err, result) {
        if (err != null) {
          console.log(err);
          console.log("ERROR: Transaction didn't go through. See console.");
        } else {
          console.log("Transaction Successful!");
          console.log(result);
        }
      }
    );

    myContractInstance.ValidationAdded(function(err, res) {

      console.log('ValidationAdded triggered SUSKE');
      console.log('err', err);
      console.log('res', res);

      cb(err, res);

    });
  });

}

function isMember(address) {
  //  var MyContract = web3.eth.contract(membershipcontract.abi);
  //  var myContractInstance = MyContract.at(membershipcontractaddress);
  var r = myContractInstance.membershipStatus(address);
  console.log('membershipStatus voor', address, '=', r.toNumber(10));
  return (r.toNumber(10) != 0);
}

// Add 0x to address
function fixaddress(address) {
  console.log("Fix address", address);
  if (!strStartsWith(address, '0x')) {
    return ('0x' + address);
  }
  return address;
}

function strStartsWith(str, prefix) {
  return str.indexOf(prefix) === 0;
}

function requestMembership(address) {

  address = fixaddress(address);

  // var MyContract = web3.eth.contract(membershipcontract.abi);
  //console.log("Mycontract: ", MyContract);
  //  var myContractInstance = MyContract.at(membershipcontractaddress);

  var options = {
    from: account,
    value: 6 * 1e18,
    gas: 3141590,
    gasPrice: gasPrice,
    nonce: Math.floor(Math.random(999999)) + new Date().getTime(),
  };

  var newMember = fixaddress(address);

  console.log('adding member ', newMember);
  console.log('contract options', options);

  var result = myContractInstance.addMember.sendTransaction(newMember, options,
    function(err, result) {
      if (err != null) {
        console.log(err);
        console.log("ERROR: Transaction didn't go through. See console.");
      } else {
        console.log("Transaction Successful!");
        console.log("Transaction hash=", result);
        printBalance(account);
      }
    }
  );
}

function printBalance(account) {
  var wei = web3.eth.getBalance(account).toNumber(10);
  console.log('Account', account, 'has Ξ', web3.fromWei(wei, 'ether'));
}<commit_msg>adam gives 1ETH upon validation.<commit_after>require('./logging');
var mqtt = require('mqtt');
var Web3 = require('web3');
var lightwallet = require('eth-lightwallet');
var HookedWeb3Provider = require("hooked-web3-provider");
var fs = require('fs');

var membershipcontract = require('../app/contracts/LocalsMembership.json');
var membershipcontractaddress = "0x83883514f7fcb0cf627829d067f0e8488201f6b9";
var host = "http://kingflurkel.dtdns.net:8545";
var channel = "adam";
var keystoreFile = "adamswallet.json";
var poesimage = "adampoes";
var poesname = "Adam";
/*
var keystoreFile = "evaswallet.json";
var channel = "eva";
var poesimage = "evapoes";
var poesname = "Eva";
*/

var validationcontract = require('../app/contracts/LocalsValidation.json');

web3 = new Web3();
web3.setProvider(new web3.providers.HttpProvider(host));

var contents = fs.readFileSync(keystoreFile, 'utf8');
var global_keystore = lightwallet.keystore.deserialize(contents);

global_keystore.passwordProvider = function(callback) {
  callback(null, 'testing')
};

console.log("Your wallet accounts:", global_keystore.getAddresses());
console.log("it s a", typeof global_keystore.getAddresses()[0]);

var account = fixaddress(global_keystore.getAddresses()[0].toString());
console.log('Your first account is ', account);

web3 = new Web3();
var provider = new HookedWeb3Provider({
  host: host,
  transaction_signer: global_keystore
});
web3.setProvider(provider);

web3_monitor = new Web3();
web3_monitor.setProvider(new web3.providers.HttpProvider(host));

var gasPrice;

web3.eth.getGasPrice(function(err, result) {

  gasPrice = result.toNumber(10);
  console.log('gasprice is ', gasPrice);

  connectMQTT();
  printBalance(account);

});

function connectMQTT() {

  console.log('connecting MQTT now');

  var client = mqtt.connect('ws://opantwerpen.be:15674');

  client.on('connect', function() {
    console.log('Adam is connected to MQTT');
    client.subscribe(channel);
  });

  client.on('message', function(topic, message) {
    // message is Buffer
    console.log('Adam received a message');
    console.log(message.toString());

    var msg = message.toString();

    var commandarray = msg.split("|");

    console.log(commandarray);
    switch (commandarray[0]) {
      case "requestmembership":
        var memberaddress = fixaddress(commandarray[1]);
        console.log('requestmembership for address ', memberaddress);

        if (isMember(memberaddress)) {
          console.log('already a member');
        } else {
          console.log('not a member yet');
          // TODO : maak hem member...
          requestMembership(memberaddress);
        }
        break;
      case "validate":
        var contractaddress = fixaddress(commandarray[3]);
        console.log('validate for contractaddress ', contractaddress);
        validate(contractaddress, function(err, res) {
          console.log('validated');
        });
        // Send message back...
        var incomingpussyname = commandarray[1];
        var incomingpussypic = "../../images/poezen/poes" + commandarray[2] + ".png";
        var contractaddresstowrite = commandarray[3];
        var incomingpin = commandarray[4];
        var incomingref = commandarray[5];

        client.publish(incomingpin, "validationstarted" + "|" + poesimage + "|" + poesname + "|" + account + "|" + incomingref);

        break;

      default:
        console.log('unknown command:', commandarray[0]);
        break;
    }
  });
}

var MyContract = web3.eth.contract(membershipcontract.abi);
var myContractInstance = MyContract.at(membershipcontractaddress);

myContractInstance.MemberAdded(function(err, res) {

  console.log('MEMBERADDED triggered SUSKE');
  console.log('err', err);
  console.log('res', res);
  printBalance(account);

});

function validate(contractaddress, cb) {

  web3.eth.getGasPrice(function(err, result) {

    var gasPrice = result.toNumber(10);

    // creation of contract object
    var Myvalidationcontract = web3.eth.contract(validationcontract.abi);
    var myContractInstance = Myvalidationcontract.at(contractaddress);

    var options = {
      from: account,
      value: 1 * 1e18,
      gas: 3141590,
      gasPrice: gasPrice,
      nonce: Math.floor(Math.random(999999)) + new Date().getTime(),
    };

    console.log('contract to validate ', contractaddress);
    console.log('contract options', options);

    var result = myContractInstance.addValidation.sendTransaction(options,
      function(err, result) {
        if (err != null) {
          console.log(err);
          console.log("ERROR: Transaction didn't go through. See console.");
        } else {
          console.log("Transaction Successful!");
          console.log(result);
        }
      }
    );

    myContractInstance.ValidationAdded(function(err, res) {

      console.log('ValidationAdded triggered SUSKE');
      console.log('err', err);
      console.log('res', res);

      cb(err, res);

    });
  });

}

function isMember(address) {
  //  var MyContract = web3.eth.contract(membershipcontract.abi);
  //  var myContractInstance = MyContract.at(membershipcontractaddress);
  var r = myContractInstance.membershipStatus(address);
  console.log('membershipStatus voor', address, '=', r.toNumber(10));
  return (r.toNumber(10) != 0);
}

// Add 0x to address
function fixaddress(address) {
  console.log("Fix address", address);
  if (!strStartsWith(address, '0x')) {
    return ('0x' + address);
  }
  return address;
}

function strStartsWith(str, prefix) {
  return str.indexOf(prefix) === 0;
}

function requestMembership(address) {

  address = fixaddress(address);

  // var MyContract = web3.eth.contract(membershipcontract.abi);
  //console.log("Mycontract: ", MyContract);
  //  var myContractInstance = MyContract.at(membershipcontractaddress);

  var options = {
    from: account,
    value: 6 * 1e18,
    gas: 3141590,
    gasPrice: gasPrice,
    nonce: Math.floor(Math.random(999999)) + new Date().getTime(),
  };

  var newMember = fixaddress(address);

  console.log('adding member ', newMember);
  console.log('contract options', options);

  var result = myContractInstance.addMember.sendTransaction(newMember, options,
    function(err, result) {
      if (err != null) {
        console.log(err);
        console.log("ERROR: Transaction didn't go through. See console.");
      } else {
        console.log("Transaction Successful!");
        console.log("Transaction hash=", result);
        printBalance(account);
      }
    }
  );
}

function printBalance(account) {
  var wei = web3.eth.getBalance(account).toNumber(10);
  console.log('Account', account, 'has Ξ', web3.fromWei(wei, 'ether'));
}<|endoftext|>


<commit_before>'use strict';

describe('ngNonBindable', function() {
  var element;

  afterEach(function(){
    dealoc(element);
  });

  it('should prevent compilation of the owning element and its children',
      inject(function($rootScope, $compile) {
    element = $compile('

')($rootScope);
    $rootScope.name =  'misko';
    $rootScope.$digest();
    expect(element.text()).toEqual('');
    expect(element.attr('text')).toEqual('{{name}}');
  }));
});
<commit_msg>test(ng-non-bindable): test sibling bindings<commit_after>'use strict';

describe('ngNonBindable', function() {
  var element;

  afterEach(function(){
    dealoc(element);
  });

  it('should prevent compilation of the owning element and its children',
      inject(function($rootScope, $compile) {
    element = $compile('

')($rootScope);
    element = $compile('
' +
                       '  {{a}}' +
                       '  ' +
                       '
' +
                       '    {{b}}' +
                       '
' +
                       '  {{a}}' +
                       '  ' +
                       '
')($rootScope);
    $rootScope.a = "one";
    $rootScope.b = "two";
    $rootScope.$digest();
    // Bindings not contained by ng-non-bindable should resolve.
    var spans = element.find("span");
    expect(spans.eq(0).text()).toEqual('one');
    expect(spans.eq(1).text()).toEqual('two');
    expect(spans.eq(3).text()).toEqual('one');
    expect(spans.eq(4).text()).toEqual('two');
    // Bindings contained by ng-non-bindable should be left alone.
    var nonBindableDiv = element.find("div");
    expect(nonBindableDiv.attr('foo')).toEqual('{{a}}');
    expect(trim(nonBindableDiv.text())).toEqual('{{b}}');
  }));
});
<|endoftext|>


<commit_before>/* @flow */

const _ = require('lodash')

const {
  assignId,
  assignPlatformIncompatibilities,
  id,
  markSide
} = require('../../../../core/metadata')
const timestamp = require('../../../../core/timestamp')

const dbBuilders = require('../db')

/*::
import type fs from 'fs-extra'
import type {
  Metadata,
  MetadataRemoteInfo,
  MetadataSidesInfo,
  SideName
} from '../../../../core/metadata'
import type Pouch from '../../../../core/pouch'
*/

module.exports = class BaseMetadataBuilder {
  /*::
  pouch: ?Pouch
  doc: Metadata
  old: ?Metadata
  */

  constructor (pouch /*: ?Pouch */, old /*: ?Metadata */) {
    this.pouch = pouch
    if (old) {
      this.old = old
      this.doc = _.cloneDeep(old)
      this.doc.tags.push('changed-tag')
    } else {
      this.doc = {
        _id: id('foo'),
        docType: 'folder', // To make flow happy (overridden by subclasses)
        path: 'foo',
        remote: {
          _id: dbBuilders.id(),
          _rev: dbBuilders.rev()
        },
        tags: [],
        sides: {},
        updated_at: timestamp.stringify(timestamp.current())
      }
    }
  }

  rev (rev /*: string */) /*: this */ {
    this.doc._rev = rev
    return this
  }

  noRev () /*: this */ {
    delete this.doc._rev
    return this
  }

  incompatible () /*: this */ {
    const { platform } = process

    if (platform === 'win32' || platform === 'darwin') {
      // Colon is currently considered forbidden on both platforms by the app
      // (although it probably shouldn't on macOS).
      return this.path('in:compatible')
    } else {
      throw new Error(`Cannot build incompatible doc on ${platform}`)
    }
  }

  ino (ino /*: number */) /*: this */ {
    this.doc.ino = ino
    return this
  }

  stats ({ino, mtime, ctime} /*: fs.Stats */) /*: this */ {
    return this.ino(ino).updatedAt(timestamp.maxDate(mtime, ctime))
  }

  path (path /*: string */) /*: this */ {
    this.doc.path = path
    assignId(this.doc)
    return this
  }

  overwrite (existingDoc /*: Metadata */) /*: this */ {
    this.doc.overwrite = existingDoc
    return this
  }

  trashed () /*: this */ {
    this.doc.trashed = true
    return this
  }

  updatedAt (date /*: Date */) /*: this */ {
    this.doc.updated_at = timestamp.fromDate(date).toISOString()
    return this
  }

  newerThan (doc /*: Metadata */) /*: this */ {
    this.doc.updated_at = new Date(timestamp.fromDate(doc.updated_at).getTime() + 2000)
    return this
  }

  olderThan (doc /*: Metadata */) /*: this */ {
    this.doc.updated_at = new Date(timestamp.fromDate(doc.updated_at).getTime() - 2000)
    return this
  }

  remoteId (_id /*: string */) /*: this */ {
    this.doc.remote = {
      _id,
      _rev: dbBuilders.rev()
    }
    return this
  }

  upToDate () /*: this */ {
    this.doc.sides = {local: 2, remote: 2}
    return this
  }

  notUpToDate () /*: this */ {
    this.doc.sides = {remote: 1}
    return this
  }

  changedSide (side /*: SideName */) /*: this */ {
    markSide(side, this.doc, this.old)
    return this
  }

  sides (sides /*: MetadataSidesInfo */) /*: this */ {
    this.doc.sides = sides
    return this
  }

  build () /*: Metadata */ {
    // Don't detect incompatibilities according to syncPath for test data, to
    // prevent environment related failures.
    assignPlatformIncompatibilities(this.doc, '')

    return _.cloneDeep(this.doc)
  }

  async create () /*: Promise<Metadata> */ {
    const doc = this.build()
    if (this.pouch == null) {
      throw new Error('Cannot create dir metadata without Pouch')
    }
    const { rev } = await this.pouch.put(doc)
    doc._rev = rev
    return doc
  }
}
<commit_msg>test/support/builders/metadata: Introduce #unmerged()<commit_after>/* @flow */

const _ = require('lodash')

const {
  assignId,
  assignPlatformIncompatibilities,
  id,
  markSide
} = require('../../../../core/metadata')
const timestamp = require('../../../../core/timestamp')

const dbBuilders = require('../db')

/*::
import type fs from 'fs-extra'
import type {
  Metadata,
  MetadataRemoteInfo,
  MetadataSidesInfo,
  SideName
} from '../../../../core/metadata'
import type Pouch from '../../../../core/pouch'
*/

module.exports = class BaseMetadataBuilder {
  /*::
  pouch: ?Pouch
  doc: Metadata
  old: ?Metadata
  */

  constructor (pouch /*: ?Pouch */, old /*: ?Metadata */) {
    this.pouch = pouch
    if (old) {
      this.old = old
      this.doc = _.cloneDeep(old)
      this.doc.tags.push('changed-tag')
    } else {
      this.doc = {
        _id: id('foo'),
        docType: 'folder', // To make flow happy (overridden by subclasses)
        path: 'foo',
        remote: {
          _id: dbBuilders.id(),
          _rev: dbBuilders.rev()
        },
        tags: [],
        sides: {},
        updated_at: timestamp.stringify(timestamp.current())
      }
    }
  }

  unmerged (sideName /*: SideName */) /*: this */ {
    delete this.sides
    if (sideName === 'local') delete this.doc.remote
    return this.noRev()
  }

  rev (rev /*: string */) /*: this */ {
    this.doc._rev = rev
    return this
  }

  noRev () /*: this */ {
    delete this.doc._rev
    return this
  }

  incompatible () /*: this */ {
    const { platform } = process

    if (platform === 'win32' || platform === 'darwin') {
      // Colon is currently considered forbidden on both platforms by the app
      // (although it probably shouldn't on macOS).
      return this.path('in:compatible')
    } else {
      throw new Error(`Cannot build incompatible doc on ${platform}`)
    }
  }

  ino (ino /*: number */) /*: this */ {
    this.doc.ino = ino
    return this
  }

  stats ({ino, mtime, ctime} /*: fs.Stats */) /*: this */ {
    return this.ino(ino).updatedAt(timestamp.maxDate(mtime, ctime))
  }

  path (path /*: string */) /*: this */ {
    this.doc.path = path
    assignId(this.doc)
    return this
  }

  overwrite (existingDoc /*: Metadata */) /*: this */ {
    this.doc.overwrite = existingDoc
    return this
  }

  trashed () /*: this */ {
    this.doc.trashed = true
    return this
  }

  updatedAt (date /*: Date */) /*: this */ {
    this.doc.updated_at = timestamp.fromDate(date).toISOString()
    return this
  }

  newerThan (doc /*: Metadata */) /*: this */ {
    this.doc.updated_at = new Date(timestamp.fromDate(doc.updated_at).getTime() + 2000)
    return this
  }

  olderThan (doc /*: Metadata */) /*: this */ {
    this.doc.updated_at = new Date(timestamp.fromDate(doc.updated_at).getTime() - 2000)
    return this
  }

  remoteId (_id /*: string */) /*: this */ {
    this.doc.remote = {
      _id,
      _rev: dbBuilders.rev()
    }
    return this
  }

  upToDate () /*: this */ {
    this.doc.sides = {local: 2, remote: 2}
    return this
  }

  notUpToDate () /*: this */ {
    this.doc.sides = {remote: 1}
    return this
  }

  changedSide (side /*: SideName */) /*: this */ {
    markSide(side, this.doc, this.old)
    return this
  }

  sides (sides /*: MetadataSidesInfo */) /*: this */ {
    this.doc.sides = sides
    return this
  }

  build () /*: Metadata */ {
    // Don't detect incompatibilities according to syncPath for test data, to
    // prevent environment related failures.
    assignPlatformIncompatibilities(this.doc, '')

    return _.cloneDeep(this.doc)
  }

  async create () /*: Promise<Metadata> */ {
    const doc = this.build()
    if (this.pouch == null) {
      throw new Error('Cannot create dir metadata without Pouch')
    }
    const { rev } = await this.pouch.put(doc)
    doc._rev = rev
    return doc
  }
}
<|endoftext|>


<commit_before>var appGlobal = {
    feedlyApiClient: new FeedlyApiClient(),
    icons: {
        default: "/images/icon.png",
        inactive: "/images/icon_inactive.png",
        defaultBig: "/images/icon128.png"
    },
    options: {
        updateInterval: 2, //minutes
        markReadOnClick: true,
        accessToken: "",
        showDesktopNotifications: true,
        hideNotificationDelay: 60 //seconds
    },
    cachedFeeds: [],
    isLoggedIn: false,
    intervalId : 0,
    lastFeedTime: new Date(),
    maxNotifications: 5
};

// #Event handlers
chrome.runtime.onInstalled.addListener(function (details) {
    //Trying read old options (mostly access token) if possible
    readOptions(function () {
        //Write all options in chrome storage and initialize application
        writeOptions(initialize);
    });
});

chrome.storage.onChanged.addListener(function (changes, areaName) {
    readOptions(initialize);
});

chrome.runtime.onStartup.addListener(function () {
    readOptions(initialize);
});

function togglePopup(){
    chrome.browserAction.getPopup({},function(popup){
        if(popup){
            chrome.browserAction.setPopup({popup: ""});
        }else{
            setTimeout(function(){
                chrome.browserAction.setPopup({popup: "popup.html"});
            }, 1 * 200);
        }
    })
}

/* Initialization all parameters and run feeds check */
function initialize() {
    appGlobal.lastFeedTime = new Date();
    appGlobal.feedlyApiClient.accessToken = appGlobal.options.accessToken;
    startSchedule(appGlobal.options.updateInterval);
}

function startSchedule(updateInterval) {
    stopSchedule(appGlobal.intervalId);
    updateFeeds();
    appGlobal.intervalId = setInterval(updateFeeds, updateInterval * 60000)
}

function stopSchedule(intervalId) {
    clearInterval(intervalId);
}

/* Sends desktop notifications */
function sendDesktopNotification(feeds){
    var notifications = [];
    //if notifications too many, then to show only count
    if(feeds.length > appGlobal.maxNotifications){
        //We can detect only 20 new feeds at time, but actually count of feeds may be more than 20
        var count = feeds.length === 20 ? chrome.i18n.getMessage("many") : feeds.length.toString();
        var notification = window.webkitNotifications.createNotification(
            appGlobal.icons.defaultBig, chrome.i18n.getMessage("NewFeeds"), chrome.i18n.getMessage("YouHaveNewFeeds", count));
        notification.show();
        notifications.push(notification);
    }else{
        for(var i = 0; i < feeds.length; i++){
            var notification = window.webkitNotifications.createNotification(
                appGlobal.icons.defaultBig, chrome.i18n.getMessage("NewFeed"), feeds[i].title);
            notification.show();
            notifications.push(notification);
        }
    }

    //Hide notifications after delay
    if(appGlobal.options.hideNotificationDelay > 0){
        setTimeout(function () {
            for(i=0; i < notifications.length; i++){
                notifications[i].cancel();
            }
        }, appGlobal.options.hideNotificationDelay * 1000);
    }
}

/* Removes feeds from cache by feed ID */
function removeFeedFromCache(feedId){
    var indexFeedForRemove;
    for (var i = 0; i < appGlobal.cachedFeeds.length; i++) {
        if (appGlobal.cachedFeeds[i].id === feedId) {
            indexFeedForRemove = i;
            break;
        }
    }

    //Remove feed from cached feeds
    if (indexFeedForRemove !== undefined) {
        appGlobal.cachedFeeds.splice(indexFeedForRemove, 1);
    }
}

/* Returns only new feeds and set date of last feed */
function filterByNewFeeds(feeds){
    var lastFeedTime = appGlobal.lastFeedTime;
    var newFeeds = [];
    for (var i = 0; i < feeds.length; i++) {
        if (feeds[i].date > appGlobal.lastFeedTime) {
            newFeeds.push(feeds[i]);
            if (feeds[i].date > lastFeedTime) {
                lastFeedTime = feeds[i].date;
            }
        }
    }
    appGlobal.lastFeedTime = lastFeedTime;
    return newFeeds;
}

/* Runs feeds update and stores unread feeds in cache
 * Callback will be started after function complete
 * If silentUpdate is true, then notifications will not be shown
 * */
function updateFeeds(callback, silentUpdate) {
    getUnreadFeedsCount(function (unreadFeedsCount, globalCategoryId, isLoggedIn) {
        chrome.browserAction.setBadgeText({ text: String(unreadFeedsCount > 0 ? unreadFeedsCount : "")});
        appGlobal.isLoggedIn = isLoggedIn;
        if (isLoggedIn === true) {
            chrome.browserAction.setIcon({ path: appGlobal.icons.default }, function () {
            });
            fetchEntries(globalCategoryId, function (feeds, isLoggedIn) {
                appGlobal.isLoggedIn = isLoggedIn;
                if (isLoggedIn === true) {
                    appGlobal.cachedFeeds = feeds;
                    if (appGlobal.options.showDesktopNotifications) {
                        var newFeeds = filterByNewFeeds(feeds);
                        if(!silentUpdate ){
                            sendDesktopNotification(newFeeds);
                        }
                    }
                } else {
                    appGlobal.cachedFeeds = [];
                }
                if (typeof callback === "function") {
                    callback();
                }
            });
        } else {
            chrome.browserAction.setIcon({ path: appGlobal.icons.inactive }, function () {
            });
            stopSchedule();
            if (typeof callback === "function") {
                callback();
            }
        }
    });
}

/* Returns feeds from the cache.
 If the cache is empty, then it will be updated before return */
function getFeeds(callback){
     if(appGlobal.cachedFeeds.length > 0){
         callback(appGlobal.cachedFeeds.slice(0), appGlobal.isLoggedIn);
     }else{
         updateFeeds(function(){
             callback(appGlobal.cachedFeeds.slice(0), appGlobal.isLoggedIn);
         }, true);
     }
}

/* Returns unread feeds count.
 * The callback parameter should specify a function that looks like this:
 * function(number unreadFeedsCount, string globalCategoryId, boolean isLoggedIn) {...};*/
function getUnreadFeedsCount(callback) {
    appGlobal.feedlyApiClient.get("markers/counts", null, function (response) {
        var unreadCounts = response.unreadcounts;

        var unreadFeedsCount = -1;
        var globalCategoryId = "";
        var isLoggedIn;
        if (response.errorCode === undefined) {
            for (var i = 0; i < unreadCounts.length; i++) {
                if (unreadFeedsCount < unreadCounts[i].count) {
                    unreadFeedsCount = unreadCounts[i].count;

                    //Search category(global or uncategorized) with max feeds
                    globalCategoryId = unreadCounts[i].id;
                }
            }
            isLoggedIn = true;
        } else {
            isLoggedIn = false;
        }
        if(typeof  callback === "function"){
            callback(Number(unreadFeedsCount), globalCategoryId, isLoggedIn);
        }
    });
}

/* Download unread feeds.
 * categoryId is feedly category ID.
 * The callback parameter should specify a function that looks like this:
 * function(array feeds, boolean isLoggedIn) {...};*/
function fetchEntries(categoryId, callback) {
    appGlobal.feedlyApiClient.get("streams/" + encodeURIComponent(categoryId) + "/contents", {
        unreadOnly: true
    }, function (response) {
        var isLoggedIn;
        var feeds = [];
        if (response.errorCode === undefined) {
            feeds = response.items.map(function (item) {
                var blogUrl;
                try{
                    blogUrl = item.origin.htmlUrl.match(/http(?:s)?:\/\/[^/]+/i).pop();
                }catch(exception) {
                    blogUrl = "#";
                }
                return {
                    //Feedly wraps rtl titles in div, we remove div because desktopNotification supports only text
                    title: item.title === undefined ? "" : (item.title.indexOf("direction:rtl") === -1 ? item.title : item.title.replace(/<\/?div.*?>/gi, "")),
                    url: item.alternate === undefined || item.alternate[0] === undefined ? "" : item.alternate[0].href,
                    blog: item.origin === undefined ? "" : item.origin.title,
                    blogUrl: blogUrl,
                    id: item.id,
                    content: item.summary === undefined ? (item.content === undefined ? "" : item.content.content) : item.summary.content,
                    isoDate: item.crawled === undefined ? "" : new Date(item.crawled).toISOString(),
                    date: item.crawled === undefined ? "" : new Date(item.crawled),
                    direction: item.summary === undefined ? "" : item.summary.direction
                };
            });
            isLoggedIn = true;
        }else{
            isLoggedIn = false;
        }
        if(typeof callback === "function"){
            callback(feeds, isLoggedIn);
        }
    });
}

/* Marks feed as read, remove it from the cache and decrement badge.
 * array of the ID of feeds
 * The callback parameter should specify a function that looks like this:
 * function(boolean isLoggedIn) {...};*/
function markAsRead(feedIds, callback) {
    appGlobal.feedlyApiClient.post("markers", null, {
        action: "markAsRead",
        type: "entries",
        entryIds: feedIds
    }, function (response) {
        var isLoggedIn;
        if (response.errorCode !== undefined) {
            for(var i = 0; i < feedIds.length; i++){
                removeFeedFromCache(feedIds[i]);
            }
            chrome.browserAction.getBadgeText({}, function (feedsCount) {
                feedsCount = +feedsCount;
                if (feedsCount > 0) {
                    feedsCount-= feedIds.length;
                    chrome.browserAction.setBadgeText({ text: String(feedsCount > 0 ? feedsCount : "")});
                }
            });
            isLoggedIn = true;
        }else{
            isLoggedIn = false;
        }
        if(typeof callback === "function"){
            callback(isLoggedIn);
        }
    });
}

/* Opens feedly site and if user are logged in,
 * then read access token and stores in chrome.storage */
function updateToken() {
    chrome.tabs.create({url: "http://cloud.feedly.com" }, function (feedlytab) {
        chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
            //Execute code in feedly page context
            chrome.tabs.executeScript(tabId, { code: "JSON.parse(localStorage.getItem('session@cloud'))['feedlyToken']"}, function (result) {
                if (result === undefined || result.length !== 1) {
                    return;
                }
                chrome.storage.sync.set({ accessToken: result[0]}, function () {
                });
            });
        });
    });
}

/* Writes all application options in chrome storage and runs callback after it */
function writeOptions(callback) {
    var options = {};
    for (var option in appGlobal.options) {
        options[option] = appGlobal.options[option];
    }
    chrome.storage.sync.set(options, function () {
        if (typeof callback === "function") {
            callback();
        }
    });
}

/* Reads all options from chrome storage and runs callback after it */
function readOptions(callback) {
    chrome.storage.sync.get(null, function (options) {
        for (var optionName in options) {
            if (typeof appGlobal.options[optionName] === "boolean") {
                appGlobal.options[optionName] = Boolean(options[optionName]);
            } else if (typeof appGlobal.options[optionName] === "number") {
                appGlobal.options[optionName] = Number(options[optionName]);
            } else {
                appGlobal.options[optionName] = options[optionName];
            }
        }
        if (typeof callback === "function") {
            callback();
        }
    });
}<commit_msg>Small scheduler bug was fixed<commit_after>var appGlobal = {
    feedlyApiClient: new FeedlyApiClient(),
    icons: {
        default: "/images/icon.png",
        inactive: "/images/icon_inactive.png",
        defaultBig: "/images/icon128.png"
    },
    options: {
        updateInterval: 2, //minutes
        markReadOnClick: true,
        accessToken: "",
        showDesktopNotifications: true,
        hideNotificationDelay: 60 //seconds
    },
    cachedFeeds: [],
    isLoggedIn: false,
    intervalId : 0,
    lastFeedTime: new Date(),
    maxNotifications: 5
};

// #Event handlers
chrome.runtime.onInstalled.addListener(function (details) {
    //Trying read old options (mostly access token) if possible
    readOptions(function () {
        //Write all options in chrome storage and initialize application
        writeOptions(initialize);
    });
});

chrome.storage.onChanged.addListener(function (changes, areaName) {
    readOptions(initialize);
});

chrome.runtime.onStartup.addListener(function () {
    readOptions(initialize);
});

function togglePopup(){
    chrome.browserAction.getPopup({},function(popup){
        if(popup){
            chrome.browserAction.setPopup({popup: ""});
        }else{
            setTimeout(function(){
                chrome.browserAction.setPopup({popup: "popup.html"});
            }, 1 * 200);
        }
    })
}

/* Initialization all parameters and run feeds check */
function initialize() {
    appGlobal.lastFeedTime = new Date();
    appGlobal.feedlyApiClient.accessToken = appGlobal.options.accessToken;
    startSchedule(appGlobal.options.updateInterval);
}

function startSchedule(updateInterval) {
    stopSchedule();
    updateFeeds();
    appGlobal.intervalId = setInterval(updateFeeds, updateInterval * 60000);
}

function stopSchedule() {
    clearInterval(appGlobal.intervalId);
}

/* Sends desktop notifications */
function sendDesktopNotification(feeds){
    var notifications = [];
    //if notifications too many, then to show only count
    if(feeds.length > appGlobal.maxNotifications){
        //We can detect only 20 new feeds at time, but actually count of feeds may be more than 20
        var count = feeds.length === 20 ? chrome.i18n.getMessage("many") : feeds.length.toString();
        var notification = window.webkitNotifications.createNotification(
            appGlobal.icons.defaultBig, chrome.i18n.getMessage("NewFeeds"), chrome.i18n.getMessage("YouHaveNewFeeds", count));
        notification.show();
        notifications.push(notification);
    }else{
        for(var i = 0; i < feeds.length; i++){
            var notification = window.webkitNotifications.createNotification(
                appGlobal.icons.defaultBig, chrome.i18n.getMessage("NewFeed"), feeds[i].title);
            notification.show();
            notifications.push(notification);
        }
    }

    //Hide notifications after delay
    if(appGlobal.options.hideNotificationDelay > 0){
        setTimeout(function () {
            for(i=0; i < notifications.length; i++){
                notifications[i].cancel();
            }
        }, appGlobal.options.hideNotificationDelay * 1000);
    }
}

/* Removes feeds from cache by feed ID */
function removeFeedFromCache(feedId){
    var indexFeedForRemove;
    for (var i = 0; i < appGlobal.cachedFeeds.length; i++) {
        if (appGlobal.cachedFeeds[i].id === feedId) {
            indexFeedForRemove = i;
            break;
        }
    }

    //Remove feed from cached feeds
    if (indexFeedForRemove !== undefined) {
        appGlobal.cachedFeeds.splice(indexFeedForRemove, 1);
    }
}

/* Returns only new feeds and set date of last feed */
function filterByNewFeeds(feeds){
    var lastFeedTime = appGlobal.lastFeedTime;
    var newFeeds = [];
    for (var i = 0; i < feeds.length; i++) {
        if (feeds[i].date > appGlobal.lastFeedTime) {
            newFeeds.push(feeds[i]);
            if (feeds[i].date > lastFeedTime) {
                lastFeedTime = feeds[i].date;
            }
        }
    }
    appGlobal.lastFeedTime = lastFeedTime;
    return newFeeds;
}

/* Runs feeds update and stores unread feeds in cache
 * Callback will be started after function complete
 * If silentUpdate is true, then notifications will not be shown
 * */
function updateFeeds(callback, silentUpdate) {
    getUnreadFeedsCount(function (unreadFeedsCount, globalCategoryId, isLoggedIn) {
        chrome.browserAction.setBadgeText({ text: String(unreadFeedsCount > 0 ? unreadFeedsCount : "")});
        appGlobal.isLoggedIn = isLoggedIn;
        if (isLoggedIn === true) {
            chrome.browserAction.setIcon({ path: appGlobal.icons.default }, function () {
            });
            fetchEntries(globalCategoryId, function (feeds, isLoggedIn) {
                appGlobal.isLoggedIn = isLoggedIn;
                if (isLoggedIn === true) {
                    appGlobal.cachedFeeds = feeds;
                    if (appGlobal.options.showDesktopNotifications) {
                        var newFeeds = filterByNewFeeds(feeds);
                        if(!silentUpdate ){
                            sendDesktopNotification(newFeeds);
                        }
                    }
                } else {
                    appGlobal.cachedFeeds = [];
                }
                if (typeof callback === "function") {
                    callback();
                }
            });
        } else {
            chrome.browserAction.setIcon({ path: appGlobal.icons.inactive }, function () {
            });
            stopSchedule();
            if (typeof callback === "function") {
                callback();
            }
        }
    });
}

/* Returns feeds from the cache.
 If the cache is empty, then it will be updated before return */
function getFeeds(callback){
     if(appGlobal.cachedFeeds.length > 0){
         callback(appGlobal.cachedFeeds.slice(0), appGlobal.isLoggedIn);
     }else{
         updateFeeds(function(){
             callback(appGlobal.cachedFeeds.slice(0), appGlobal.isLoggedIn);
         }, true);
     }
}

/* Returns unread feeds count.
 * The callback parameter should specify a function that looks like this:
 * function(number unreadFeedsCount, string globalCategoryId, boolean isLoggedIn) {...};*/
function getUnreadFeedsCount(callback) {
    appGlobal.feedlyApiClient.get("markers/counts", null, function (response) {
        var unreadCounts = response.unreadcounts;

        var unreadFeedsCount = -1;
        var globalCategoryId = "";
        var isLoggedIn;
        if (response.errorCode === undefined) {
            for (var i = 0; i < unreadCounts.length; i++) {
                if (unreadFeedsCount < unreadCounts[i].count) {
                    unreadFeedsCount = unreadCounts[i].count;

                    //Search category(global or uncategorized) with max feeds
                    globalCategoryId = unreadCounts[i].id;
                }
            }
            isLoggedIn = true;
        } else {
            isLoggedIn = false;
        }
        if(typeof  callback === "function"){
            callback(Number(unreadFeedsCount), globalCategoryId, isLoggedIn);
        }
    });
}

/* Download unread feeds.
 * categoryId is feedly category ID.
 * The callback parameter should specify a function that looks like this:
 * function(array feeds, boolean isLoggedIn) {...};*/
function fetchEntries(categoryId, callback) {
    appGlobal.feedlyApiClient.get("streams/" + encodeURIComponent(categoryId) + "/contents", {
        unreadOnly: true
    }, function (response) {
        var isLoggedIn;
        var feeds = [];
        if (response.errorCode === undefined) {
            feeds = response.items.map(function (item) {
                var blogUrl;
                try{
                    blogUrl = item.origin.htmlUrl.match(/http(?:s)?:\/\/[^/]+/i).pop();
                }catch(exception) {
                    blogUrl = "#";
                }
                return {
                    //Feedly wraps rtl titles in div, we remove div because desktopNotification supports only text
                    title: item.title === undefined ? "" : (item.title.indexOf("direction:rtl") === -1 ? item.title : item.title.replace(/<\/?div.*?>/gi, "")),
                    url: item.alternate === undefined || item.alternate[0] === undefined ? "" : item.alternate[0].href,
                    blog: item.origin === undefined ? "" : item.origin.title,
                    blogUrl: blogUrl,
                    id: item.id,
                    content: item.summary === undefined ? (item.content === undefined ? "" : item.content.content) : item.summary.content,
                    isoDate: item.crawled === undefined ? "" : new Date(item.crawled).toISOString(),
                    date: item.crawled === undefined ? "" : new Date(item.crawled),
                    direction: item.summary === undefined ? "" : item.summary.direction
                };
            });
            isLoggedIn = true;
        }else{
            isLoggedIn = false;
        }
        if(typeof callback === "function"){
            callback(feeds, isLoggedIn);
        }
    });
}

/* Marks feed as read, remove it from the cache and decrement badge.
 * array of the ID of feeds
 * The callback parameter should specify a function that looks like this:
 * function(boolean isLoggedIn) {...};*/
function markAsRead(feedIds, callback) {
    appGlobal.feedlyApiClient.post("markers", null, {
        action: "markAsRead",
        type: "entries",
        entryIds: feedIds
    }, function (response) {
        var isLoggedIn;
        if (response.errorCode !== undefined) {
            for(var i = 0; i < feedIds.length; i++){
                removeFeedFromCache(feedIds[i]);
            }
            chrome.browserAction.getBadgeText({}, function (feedsCount) {
                feedsCount = +feedsCount;
                if (feedsCount > 0) {
                    feedsCount-= feedIds.length;
                    chrome.browserAction.setBadgeText({ text: String(feedsCount > 0 ? feedsCount : "")});
                }
            });
            isLoggedIn = true;
        }else{
            isLoggedIn = false;
        }
        if(typeof callback === "function"){
            callback(isLoggedIn);
        }
    });
}

/* Opens feedly site and if user are logged in,
 * then read access token and stores in chrome.storage */
function updateToken() {
    chrome.tabs.create({url: "http://cloud.feedly.com" }, function (feedlytab) {
        chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
            //Execute code in feedly page context
            chrome.tabs.executeScript(tabId, { code: "JSON.parse(localStorage.getItem('session@cloud'))['feedlyToken']"}, function (result) {
                if (result === undefined || result.length !== 1) {
                    return;
                }
                chrome.storage.sync.set({ accessToken: result[0]}, function () {
                });
            });
        });
    });
}

/* Writes all application options in chrome storage and runs callback after it */
function writeOptions(callback) {
    var options = {};
    for (var option in appGlobal.options) {
        options[option] = appGlobal.options[option];
    }
    chrome.storage.sync.set(options, function () {
        if (typeof callback === "function") {
            callback();
        }
    });
}

/* Reads all options from chrome storage and runs callback after it */
function readOptions(callback) {
    chrome.storage.sync.get(null, function (options) {
        for (var optionName in options) {
            if (typeof appGlobal.options[optionName] === "boolean") {
                appGlobal.options[optionName] = Boolean(options[optionName]);
            } else if (typeof appGlobal.options[optionName] === "number") {
                appGlobal.options[optionName] = Number(options[optionName]);
            } else {
                appGlobal.options[optionName] = options[optionName];
            }
        }
        if (typeof callback === "function") {
            callback();
        }
    });
}<|endoftext|>