<commit_before>var ArrayExtensions = {
    init: function() {
        if (!Array.prototype.remove) {
            Array.prototype.remove = function(value) {
                var index = this.indexOf(value);
                if (index === -1)
                    return false;

                this.splice(index, 1);
                return true;
            };
        }

        if (!Array.prototype.addDistinct) {
            Array.prototype.addDistinct = function(value) {
                var index = this.indexOf(value);
                if (index !== -1)
                    return false;
                this.push(value);
                return true;
            };
        }
    }
};

var Filters = {
    pinnedTabs: function(x) {
        return !!x.pinned;
    },
    tabsWithUrl: function(url) {
        return function(x) {
            return x.url == url;
        };
    }
};

var GlobalPinnedTabs = {

    globalPinnedTabUrls: [],
    tabIdToUrlMapping: {},
    urlToTabIdMapping: {},

    init: function() {
        ArrayExtensions.init();
        GlobalPinnedTabs.loadData();
        GlobalPinnedTabs.registerForChromeEvents();
        GlobalPinnedTabs.displayTabsOnCurrentWindow();
    },

    registerForChromeEvents: function() {
        chrome.windows.onFocusChanged.addListener(GlobalPinnedTabs.activeWindowChanged);
        chrome.tabs.onUpdated.addListener(GlobalPinnedTabs.onTabUpdate);
        chrome.tabs.onRemoved.addListener(GlobalPinnedTabs.onTabClose);
        });

        chrome.alarms.onAlarm.addListener(function(alarm) {
            if (alarm.name === 'Try moving tab again') {
                GlobalPinnedTabs.moveTabs(GlobalPinnedTabs.tabsToMove.target, GlobalPinnedTabs.tabsToMove.ids);
            }
        });
    },

    displayTabsOnCurrentWindow: function() {
        chrome.windows.getCurrent({
            populate: true
        }, function(window) {
            GlobalPinnedTabs.displayTabs(window);
        });
    },

    pinTabs: function(tabs) {

        var tabsPinned = 0;
        var enableUpdateHandling = function(tab) {
            tabsPinned++;
            if (tabsPinned === tabs.length)
                GlobalPinnedTabs.disableTabUpdateHandling = false;
        };

        for (var i = 0; i < tabs.length; ++i) {
            GlobalPinnedTabs.disableTabUpdateHandling = true;
            chrome.tabs.update(tabs[i].id, {
                pinned: true
            }, GlobalPinnedTabs.enableUpdateHandling);
        }
    },

    pinTab: function(tabs) {
        var lastError = chrome.runtime.lastError;
        if (tabs === undefined || lastError !== undefined) {
            console.log("Couldn't move tabs.");
            if (lastError !== undefined && lastError.message)
                console.log('Reason: ' + lastError.message);
            chrome.alarms.create('Try moving tabs again', {
                when: Date.now() + 1000
            });

        } else {
            GlobalPinnedTabs.pinTabs(tabs);
            GlobalPinnedTabs.tabsToMove.target = undefined;
            GlobalPinnedTabs.tabsToMove.ids = [];
        }
    },

    tabsToMove: {
        target: undefined,
        ids: []
    },

    moveTabs: function(windowId, tabIds) {
        GlobalPinnedTabs.tabsToMove.target = windowId;
        GlobalPinnedTabs.tabsToMove.ids = tabIds;

        if (tabIds.length === 0)
            return;

        chrome.tabs.move(tabIds, {
            windowId: windowId,
            index: 0
        }, GlobalPinnedTabs.pinTab);
    },

    activeWindowChanged: function(windowId) {
        if (windowId === chrome.windows.WINDOW_ID_NONE)
            return;
        chrome.windows.get(parseInt(windowId), {
            populate: true
        }, function(window) {
            GlobalPinnedTabs.displayTabs(window);
        });
    },

    windowId: undefined,
    tabId: undefined,
    disableTabUpdateHandling: false,

    displayTabs: function(window) {
        if (window.type !== 'normal')
            return;

        var url;
        var tabsToMove = [];
        for (var key in GlobalPinnedTabs.tabIdToUrlMapping) {
            url = GlobalPinnedTabs.tabIdToUrlMapping[key];
            var matchingTab = GlobalPinnedTabs.getMatchingTab(url, window);
            if (matchingTab === undefined) {
                tabsToMove.push(parseInt(key));
            } else if (key != matchingTab.id) {
                delete GlobalPinnedTabs.tabIdToUrlMapping[key];
                GlobalPinnedTabs.tabIdToUrlMapping[matchingTab.id] = url;
                GlobalPinnedTabs.urlToTabIdMapping[url] = matchingTab.id;
                chrome.tabs.remove(parseInt(key));
            }
        }

        GlobalPinnedTabs.moveTabs(window.id, tabsToMove);

        for (var i = 0; i < GlobalPinnedTabs.globalPinnedTabUrls.length; i++) {
            url = GlobalPinnedTabs.globalPinnedTabUrls[i];
            if (GlobalPinnedTabs.urlToTabIdMapping[url] === undefined)
                GlobalPinnedTabs.createTab(url, window);
        }
    },

    createTabCallback: function(tab) {
        GlobalPinnedTabs.tabIdToUrlMapping[tab.id] = tab.url;
        GlobalPinnedTabs.urlToTabIdMapping[tab.url] = tab.id;
    },

    onTabUpdate: function(tabId, changeInfo, tab) {
        if (GlobalPinnedTabs.disableTabUpdateHandling)
            return;
        if (changeInfo.pinned !== undefined) {
            if (changeInfo.pinned === true) {
                if (GlobalPinnedTabs.tabIdToUrlMapping[tabId] === undefined && confirm('Make this a globally pinned tab that is visible in all windows?')) {
                    GlobalPinnedTabs.tabIdToUrlMapping[tabId] = tab.url;
                    GlobalPinnedTabs.urlToTabIdMapping[tab.url] = tabId;
                    GlobalPinnedTabs.globalPinnedTabUrls.addDistinct(tab.url);
                    GlobalPinnedTabs.persistData();
                }
            } else {
                GlobalPinnedTabs.handleRemovedTab(tabId);
            }
        }
    },

    onTabClose: function(tabId, removeInfo) {
        GlobalPinnedTabs.handleRemovedTab(tabId, !removeInfo.isWindowClosing);
    },

    handleRemovedTab: function(tabId, removeUrl) {
        var url = GlobalPinnedTabs.tabIdToUrlMapping[tabId];
        if (url) {
            delete GlobalPinnedTabs.tabIdToUrlMapping[tabId];
            delete GlobalPinnedTabs.urlToTabIdMapping[url];
            console.log('deleted tab');
            if (removeUrl) {
                GlobalPinnedTabs.globalPinnedTabUrls.remove(url);
                GlobalPinnedTabs.persistData();
                console.log('deleted url');
            }
        }
    },

    getMatchingTab: function(url, window) {
        return window.tabs.filter(function(x) {
            return x.pinned && x.url === url;
        })[0];
    },

    createTab: function(url, window) {
        var matchingTab = GlobalPinnedTabs.getMatchingTab(url, window);
        if (matchingTab !== undefined)
            GlobalPinnedTabs.createTabCallback(matchingTab);
        else {
            console.log('Creating tab with URL ' + url);
            chrome.tabs.create({
                'url': url,
                'pinned': true,
                'active': false,
                'windowId': window.id
            }, GlobalPinnedTabs.createTabCallback);
        }
    },

    loadData: function() {
        chrome.storage.sync.get('globalPinnedTabUrls', function(items) {
            GlobalPinnedTabs.globalPinnedTabUrls = items.globalPinnedTabUrls;
        });
    },

    persistData: function() {
        chrome.storage.sync.set({
            globalPinnedTabUrls: GlobalPinnedTabs.globalPinnedTabUrls
        });
    }
};

GlobalPinnedTabs.init();<commit_msg>Change console.log to console.debug<commit_after>var ArrayExtensions = {
    init: function() {
        if (!Array.prototype.remove) {
            Array.prototype.remove = function(value) {
                var index = this.indexOf(value);
                if (index === -1)
                    return false;

                this.splice(index, 1);
                return true;
            };
        }

        if (!Array.prototype.addDistinct) {
            Array.prototype.addDistinct = function(value) {
                var index = this.indexOf(value);
                if (index !== -1)
                    return false;
                this.push(value);
                return true;
            };
        }
    }
};

var Filters = {
    pinnedTabs: function(x) {
        return !!x.pinned;
    },
    tabsWithUrl: function(url) {
        return function(x) {
            return x.url == url;
        };
    }
};

var GlobalPinnedTabs = {

    globalPinnedTabUrls: [],
    tabIdToUrlMapping: {},
    urlToTabIdMapping: {},

    init: function() {
        ArrayExtensions.init();
        GlobalPinnedTabs.loadData();
        GlobalPinnedTabs.registerForChromeEvents();
        GlobalPinnedTabs.displayTabsOnCurrentWindow();
    },

    registerForChromeEvents: function() {
        chrome.windows.onFocusChanged.addListener(GlobalPinnedTabs.activeWindowChanged);
        chrome.tabs.onUpdated.addListener(GlobalPinnedTabs.onTabUpdate);
        chrome.tabs.onRemoved.addListener(GlobalPinnedTabs.onTabClose);
        });

        chrome.alarms.onAlarm.addListener(function(alarm) {
            if (alarm.name === 'Try moving tab again') {
                GlobalPinnedTabs.moveTabs(GlobalPinnedTabs.tabsToMove.target, GlobalPinnedTabs.tabsToMove.ids);
            }
        });
    },

    displayTabsOnCurrentWindow: function() {
        chrome.windows.getCurrent({
            populate: true
        }, function(window) {
            GlobalPinnedTabs.displayTabs(window);
        });
    },

    pinTabs: function(tabs) {

        var tabsPinned = 0;
        var enableUpdateHandling = function(tab) {
            tabsPinned++;
            if (tabsPinned === tabs.length)
                GlobalPinnedTabs.disableTabUpdateHandling = false;
        };

        for (var i = 0; i < tabs.length; ++i) {
            GlobalPinnedTabs.disableTabUpdateHandling = true;
            chrome.tabs.update(tabs[i].id, {
                pinned: true
            }, GlobalPinnedTabs.enableUpdateHandling);
        }
    },

    pinTab: function(tabs) {
        var lastError = chrome.runtime.lastError;
        if (tabs === undefined || lastError !== undefined) {
            console.debug("Couldn't move tabs.");
            if (lastError !== undefined && lastError.message)
                console.debug('Reason: ' + lastError.message);
            chrome.alarms.create('Try moving tabs again', {
                when: Date.now() + 1000
            });

        } else {
            GlobalPinnedTabs.pinTabs(tabs);
            GlobalPinnedTabs.tabsToMove.target = undefined;
            GlobalPinnedTabs.tabsToMove.ids = [];
        }
    },

    tabsToMove: {
        target: undefined,
        ids: []
    },

    moveTabs: function(windowId, tabIds) {
        GlobalPinnedTabs.tabsToMove.target = windowId;
        GlobalPinnedTabs.tabsToMove.ids = tabIds;

        if (tabIds.length === 0)
            return;

        chrome.tabs.move(tabIds, {
            windowId: windowId,
            index: 0
        }, GlobalPinnedTabs.pinTab);
    },

    activeWindowChanged: function(windowId) {
        if (windowId === chrome.windows.WINDOW_ID_NONE)
            return;
        chrome.windows.get(parseInt(windowId), {
            populate: true
        }, function(window) {
            GlobalPinnedTabs.displayTabs(window);
        });
    },

    windowId: undefined,
    tabId: undefined,
    disableTabUpdateHandling: false,

    displayTabs: function(window) {
        if (window.type !== 'normal')
            return;

        var url;
        var tabsToMove = [];
        for (var key in GlobalPinnedTabs.tabIdToUrlMapping) {
            url = GlobalPinnedTabs.tabIdToUrlMapping[key];
            var matchingTab = GlobalPinnedTabs.getMatchingTab(url, window);
            if (matchingTab === undefined) {
                tabsToMove.push(parseInt(key));
            } else if (key != matchingTab.id) {
                delete GlobalPinnedTabs.tabIdToUrlMapping[key];
                GlobalPinnedTabs.tabIdToUrlMapping[matchingTab.id] = url;
                GlobalPinnedTabs.urlToTabIdMapping[url] = matchingTab.id;
                chrome.tabs.remove(parseInt(key));
            }
        }

        GlobalPinnedTabs.moveTabs(window.id, tabsToMove);

        for (var i = 0; i < GlobalPinnedTabs.globalPinnedTabUrls.length; i++) {
            url = GlobalPinnedTabs.globalPinnedTabUrls[i];
            if (GlobalPinnedTabs.urlToTabIdMapping[url] === undefined)
                GlobalPinnedTabs.createTab(url, window);
        }
    },

    createTabCallback: function(tab) {
        GlobalPinnedTabs.tabIdToUrlMapping[tab.id] = tab.url;
        GlobalPinnedTabs.urlToTabIdMapping[tab.url] = tab.id;
    },

    onTabUpdate: function(tabId, changeInfo, tab) {
        if (GlobalPinnedTabs.disableTabUpdateHandling)
            return;
        if (changeInfo.pinned !== undefined) {
            if (changeInfo.pinned === true) {
                if (GlobalPinnedTabs.tabIdToUrlMapping[tabId] === undefined && confirm('Make this a globally pinned tab that is visible in all windows?')) {
                    GlobalPinnedTabs.tabIdToUrlMapping[tabId] = tab.url;
                    GlobalPinnedTabs.urlToTabIdMapping[tab.url] = tabId;
                    GlobalPinnedTabs.globalPinnedTabUrls.addDistinct(tab.url);
                    GlobalPinnedTabs.persistData();
                }
            } else {
                GlobalPinnedTabs.handleRemovedTab(tabId);
            }
        }
    },

    onTabClose: function(tabId, removeInfo) {
        GlobalPinnedTabs.handleRemovedTab(tabId, !removeInfo.isWindowClosing);
    },

    handleRemovedTab: function(tabId, removeUrl) {
        var url = GlobalPinnedTabs.tabIdToUrlMapping[tabId];
        if (url) {
            delete GlobalPinnedTabs.tabIdToUrlMapping[tabId];
            delete GlobalPinnedTabs.urlToTabIdMapping[url];
            console.debug('deleted tab');
            if (removeUrl) {
                GlobalPinnedTabs.globalPinnedTabUrls.remove(url);
                GlobalPinnedTabs.persistData();
                console.debug('deleted url');
            }
        }
    },

    getMatchingTab: function(url, window) {
        return window.tabs.filter(function(x) {
            return x.pinned && x.url === url;
        })[0];
    },

    createTab: function(url, window) {
        var matchingTab = GlobalPinnedTabs.getMatchingTab(url, window);
        if (matchingTab !== undefined)
            GlobalPinnedTabs.createTabCallback(matchingTab);
        else {
            console.debug('Creating tab with URL ' + url);
            chrome.tabs.create({
                'url': url,
                'pinned': true,
                'active': false,
                'windowId': window.id
            }, GlobalPinnedTabs.createTabCallback);
        }
    },

    loadData: function() {
        chrome.storage.sync.get('globalPinnedTabUrls', function(items) {
            GlobalPinnedTabs.globalPinnedTabUrls = items.globalPinnedTabUrls;
        });
    },

    persistData: function() {
        chrome.storage.sync.set({
            globalPinnedTabUrls: GlobalPinnedTabs.globalPinnedTabUrls
        });
    }
};

GlobalPinnedTabs.init();<|endoftext|>


<commit_before>/*
 * Copyright (C) 2012 Vanderbilt University, All rights reserved.
 *
 * Author: Robert Kereskenyi
 */
"use strict";

define(['logManager',
        'js/Widgets/GraphViz/GraphVizWidget.Zoom',
        'd3',
        'css!/css/Widgets/GraphViz/GraphVizWidget'], function (logManager,
                                                               GraphVizWidgetZoom) {

    var GraphVizWidget,
        GRAPH_VIZ_CLASS = "graph-viz",
        DURATION = 750,
        MARGIN = 20,
        i = 0,
        CLOSED = 'closed',
        OPEN = 'open',
        LEAF = 'LEAF',
        OPENING = 'opening',
        CLOSING = 'CLOSING',
        NODE_SIZE = 15,
        TREE_LEVEL_DISTANCE = 180,
        DEFAULT_ZOOM_VALUES = [0.1, 0.25, 0.5, 0.75, 1, 1.5, 2, 3, 5];

    GraphVizWidget = function (container, params) {
        this._logger = logManager.create("GraphVizWidget");

        this._el = container;
        this.toolBar = params.toolBar;

        //define zoom value
        this._zoom = 1.0;
        this._zoomValues = params.zoomValues || DEFAULT_ZOOM_VALUES;

        this._initialize();

        //init zoom related UI and handlers
        this._initZoom();

        this._logger.debug("GraphVizWidget ctor finished");
    };

    GraphVizWidget.prototype._initialize = function () {
        var width = this._el.width(),
            height = this._el.height(),
            self = this;

        //set Widget title
        this._el.addClass(GRAPH_VIZ_CLASS);

        this._root = undefined;

        this._tree = d3.layout.tree().sort(function (a, b) {
            return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
        });
        this.__svg = d3.select(this._el[0]).append("svg");
        this._resizeD3Tree(width, height);

        this._svg = this.__svg.append("g").attr("transform", "translate(" + MARGIN + "," + MARGIN + ")");

        this._el.on("dblclick", function (event) {
            event.stopPropagation();
            event.preventDefault();
            self.onBackgroundDblClick();
        });
    };

    GraphVizWidget.prototype.onWidgetContainerResize = function (width, height) {
        //call our own resize handler
        this._resizeD3Tree(width, height);
        if (this._root) {
            this._update(undefined);
        }
    };

    GraphVizWidget.prototype._resizeD3Tree = function (width, height) {
        var ew = this._el.width(),
            eh = this._el.height();

        width = Math.max(ew, width);
        height = Math.max(eh, height);

        this._tree.size([height - 2 * MARGIN, width - 2 * MARGIN]);
        this.__svg.attr("width", width).attr("height", height);
    };

    GraphVizWidget.prototype._update = function(source) {
        var self = this;

        // Compute the new tree layout.
        var nodes = this._tree.nodes(this._root).reverse(),
            links = this._tree.links(nodes);

        var diagonal = d3.svg.diagonal()
            .projection(function(d) { return [d.y, d.x]; });

        var getOpenStatus = function (d) {
            var status = LEAF;

            if (d.childrenNum > 0) {
                status = CLOSED;

                if (d.children && d.children.length === d.childrenNum) {
                    status = OPEN;
                }
            }

            return status;
        };

        // Normalize for fixed-depth.
        nodes.forEach(function(d) {
            d.y = d.depth * TREE_LEVEL_DISTANCE;
            d.status = d.status || getOpenStatus(d);
        });

        // Update the nodes…
        var node = this._svg.selectAll("g.node")
            .data(nodes, function(d) { return d.id || (d.id = ++i); });

        var getDisplayName = function (d) {
            var n = d.name;

            if (d.childrenNum > 0) {
                n += " [" + d.childrenNum + "]";
            }

            return n;
        };

        // Enter any new nodes at the parent's previous position.
        var nodeEnter = node.enter().append("g")
            .attr("class", "node")
            .attr("transform", function(d) {
                return d.parent ? "translate(" + d.parent.y0 + "," + d.parent.x0 + ")" : "translate(" + d.y + "," + d.x + ")";
            })
            .on("click", function (d) {
                d3.event.stopPropagation();
                d3.event.preventDefault();
                self._onNodeClick(d);
            })
            .on("dblclick", function (d) {
                d3.event.preventDefault();
                d3.event.stopPropagation();
                self._onNodeDblClick(d);
            });

        nodeEnter.append("circle")
            .attr("r", 1e-6);

        nodeEnter.append("text")
            .attr("dy", ".35em")
            .style("fill-opacity", 1e-6);

        // Transition nodes to their new position.
        var nodeUpdate = node.transition()
            .duration(DURATION)
            .attr("transform", function(d) {
                return "translate(" + d.y + "," + d.x + ")";
            });

        nodeUpdate.select("circle")
            .attr("r", 4.5);

        nodeUpdate.select("circle")
            .style("fill", function(d) {
                var status = d.status,
                    color = "#FFFFFF";
                if (status === CLOSED) {
                    color = "lightsteelblue";
                } else if (status === OPENING) {
                    color =  "#ff0000";
                } else if (status === OPEN) {
                    color =  "#ffFFFF";
                } else if (status === LEAF) {
                    color =  "#ffFFFF";
                } else if (status === CLOSING) {
                    color = "#00FF00";
                }

                return color;
            });

        nodeUpdate.select("text")
            .attr("x", function(d) { return (d.children && d.children.length > 0) ? -10 : 10; })
            .attr("text-anchor", function(d) { return (d.children && d.children.length > 0) ? "end" : "start"; })
            .text(function(d) { return getDisplayName(d); })
            .style("fill-opacity", 1);

        // Transition exiting nodes to the parent's new position.
        var nodeExit = node.exit().transition()
            .duration(DURATION)
            .attr("transform", function(d) {
                return source ? "translate(" + source.y + "," + source.x + ")" : d.parent ? "translate(" + d.parent.y + "," + d.parent.x + ")" : "translate(" + d.y + "," + d.x + ")";
            })
            .remove();

        nodeExit.select("circle")
            .attr("r", 1e-6);

        nodeExit.select("text")
            .style("fill-opacity", 1e-6);

        // Update the links…
        var link = this._svg.selectAll("path.link")
            .data(links, function(d) { return d.target.id; });

        // Enter any new links at the parent's previous position.
        link.enter().insert("path", "g")
            .attr("class", "link")
            .attr("d", function(d) {
                var o = {x: d.source.x0 || d.source.y, y: d.source.y0 || d.source.y};
                return diagonal({source: o, target: o});
            });

        // Transition links to their new position.
        link.transition()
            .duration(DURATION)
            .attr("d", diagonal);

        // Transition exiting nodes to the parent's new position.
        link.exit().transition()
            .duration(DURATION)
            .attr("d", function(d) {
                var o = {x: d.source.x, y: d.source.y};
                return diagonal({source: o, target: o});
            })
            .remove();

        //node vertical positions by depth
        var nodesYByDepth = [];

        nodes.forEach(function(d) {
            // Stash the old positions for transition.
            d.x0 = d.x;
            d.y0 = d.y;

            // and query vertical coordinates of the nodes at the same depth to detect collision
            nodesYByDepth[d.depth] = nodesYByDepth[d.depth] || [];
            nodesYByDepth[d.depth].push(d.x);
        });

        //if already resizing, don't try to optimize again
        if (this.__resizing !== true) {
            var l = nodesYByDepth.length;
            var collideAtDepth = [];
            for (i = 1; i < l; i+= 1) {
                //sort the coordinates for easy check of collision
                nodesYByDepth[i].sort(function(a,b){return a-b});

                //see if any of the nodes overlap
                var j;
                var depthLength = nodesYByDepth[i].length;
                for (j = 0; j < depthLength - 1; j += 1 ) {
                    if (nodesYByDepth[i][j+1] - NODE_SIZE <= nodesYByDepth[i][j]) {
                        collideAtDepth.push([i,depthLength]);
                        break;
                    }
                }
            }

            this._logger.debug("Collide:" + collideAtDepth);

            var sum = 0;
            var len = collideAtDepth.length;
            if (len > 0) {
                //start resize mode
                this.__resizing = true;

                while (len--) {
                    sum += collideAtDepth[len][1];
                }

                this._resizeD3Tree(nodesYByDepth.length * TREE_LEVEL_DISTANCE, sum * NODE_SIZE);

                //redraw the tree
                this._update();

                //finish resize mode
                this.__resizing = false;
            }
        }
    };

    GraphVizWidget.prototype._onNodeClick = function(d) {
        switch(d.status) {
            case CLOSED:
                d.status = OPENING;
                this._update(undefined);
                this.onNodeOpen(d.id);
                break;
            case OPEN:
                d.status = CLOSING;
                d.children = undefined;
                this._update(d);
                this.onNodeClose(d.id);
                break;
            case OPENING:
                break;
            case CLOSING:
                break;
            case LEAF:
                break;
        }
    };

    GraphVizWidget.prototype._onNodeDblClick = function(d) {
        this.onNodeDblClick(d.id);
    };

    GraphVizWidget.prototype.setData = function (data) {
        this.__root = this._root;

        this._root = $.extend(true, {}, data);

        var copyOver = function (srcNode, dstNode) {
            var i, j;
            if (srcNode && dstNode) {
                if (srcNode.id === dstNode.id) {
                    dstNode.x0 = srcNode.x0;
                    dstNode.y0 = srcNode.y0;
                    i = srcNode.children ? srcNode.children.length : 0;
                    if (i > 0) {
                        while (i--) {
                            j = dstNode.children ? dstNode.children.length : 0;
                            if (j > 0) {
                                while (j--) {
                                    copyOver(srcNode.children[i], dstNode.children[j]);
                                }
                            }
                        }
                    }
                }
            }
        };

        copyOver(this.__root, this._root);
        delete this.__root;

        var width = this._el.width(),
            height = this._el.height();
        this._resizeD3Tree(width, height);

        this._update(undefined);

        //approximate width of the root name
        var rootNameWidth = this._root.name.length * 6;
        if (this._root.children) {
            rootNameWidth = (this._root.name + " [" + this._root.children.length + "]").length * 6;
        }
        this._el.find('svg > g').attr("transform", "translate(" + (rootNameWidth + MARGIN) + "," + MARGIN + ")");
    };

    GraphVizWidget.prototype.onNodeOpen = function (id) {
    };

    GraphVizWidget.prototype.onNodeClose = function (id) {
    };

    GraphVizWidget.prototype.onNodeDblClick = function (id) {
    };

    GraphVizWidget.prototype.onBackgroundDblClick = function () {
    };

    _.extend(GraphVizWidget.prototype, GraphVizWidgetZoom.prototype);

    return GraphVizWidget;
});<commit_msg>add destroy method<commit_after>/*
 * Copyright (C) 2012 Vanderbilt University, All rights reserved.
 *
 * Author: Robert Kereskenyi
 */
"use strict";

define(['logManager',
        'js/Widgets/GraphViz/GraphVizWidget.Zoom',
        'd3',
        'css!/css/Widgets/GraphViz/GraphVizWidget'], function (logManager,
                                                               GraphVizWidgetZoom) {

    var GraphVizWidget,
        GRAPH_VIZ_CLASS = "graph-viz",
        DURATION = 750,
        MARGIN = 20,
        i = 0,
        CLOSED = 'closed',
        OPEN = 'open',
        LEAF = 'LEAF',
        OPENING = 'opening',
        CLOSING = 'CLOSING',
        NODE_SIZE = 15,
        TREE_LEVEL_DISTANCE = 180,
        DEFAULT_ZOOM_VALUES = [0.1, 0.25, 0.5, 0.75, 1, 1.5, 2, 3, 5];

    GraphVizWidget = function (container, params) {
        this._logger = logManager.create("GraphVizWidget");

        this._el = container;
        this.toolBar = params.toolBar;

        //define zoom value
        this._zoom = 1.0;
        this._zoomValues = params.zoomValues || DEFAULT_ZOOM_VALUES;

        this._initialize();

        //init zoom related UI and handlers
        this._initZoom();

        this._logger.debug("GraphVizWidget ctor finished");
    };

    GraphVizWidget.prototype._initialize = function () {
        var width = this._el.width(),
            height = this._el.height(),
            self = this;

        //set Widget title
        this._el.addClass(GRAPH_VIZ_CLASS);

        this._root = undefined;

        this._tree = d3.layout.tree().sort(function (a, b) {
            return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
        });
        this.__svg = d3.select(this._el[0]).append("svg");
        this._resizeD3Tree(width, height);

        this._svg = this.__svg.append("g").attr("transform", "translate(" + MARGIN + "," + MARGIN + ")");

        this._el.on("dblclick", function (event) {
            event.stopPropagation();
            event.preventDefault();
            self.onBackgroundDblClick();
        });
    };

    GraphVizWidget.prototype.onWidgetContainerResize = function (width, height) {
        //call our own resize handler
        this._resizeD3Tree(width, height);
        if (this._root) {
            this._update(undefined);
        }
    };

    GraphVizWidget.prototype._resizeD3Tree = function (width, height) {
        var ew = this._el.width(),
            eh = this._el.height();

        width = Math.max(ew, width);
        height = Math.max(eh, height);

        this._tree.size([height - 2 * MARGIN, width - 2 * MARGIN]);
        this.__svg.attr("width", width).attr("height", height);
    };

    GraphVizWidget.prototype._update = function(source) {
        var self = this;

        // Compute the new tree layout.
        var nodes = this._tree.nodes(this._root).reverse(),
            links = this._tree.links(nodes);

        var diagonal = d3.svg.diagonal()
            .projection(function(d) { return [d.y, d.x]; });

        var getOpenStatus = function (d) {
            var status = LEAF;

            if (d.childrenNum > 0) {
                status = CLOSED;

                if (d.children && d.children.length === d.childrenNum) {
                    status = OPEN;
                }
            }

            return status;
        };

        // Normalize for fixed-depth.
        nodes.forEach(function(d) {
            d.y = d.depth * TREE_LEVEL_DISTANCE;
            d.status = d.status || getOpenStatus(d);
        });

        // Update the nodes…
        var node = this._svg.selectAll("g.node")
            .data(nodes, function(d) { return d.id || (d.id = ++i); });

        var getDisplayName = function (d) {
            var n = d.name;

            if (d.childrenNum > 0) {
                n += " [" + d.childrenNum + "]";
            }

            return n;
        };

        // Enter any new nodes at the parent's previous position.
        var nodeEnter = node.enter().append("g")
            .attr("class", "node")
            .attr("transform", function(d) {
                return d.parent ? "translate(" + d.parent.y0 + "," + d.parent.x0 + ")" : "translate(" + d.y + "," + d.x + ")";
            })
            .on("click", function (d) {
                d3.event.stopPropagation();
                d3.event.preventDefault();
                self._onNodeClick(d);
            })
            .on("dblclick", function (d) {
                d3.event.preventDefault();
                d3.event.stopPropagation();
                self._onNodeDblClick(d);
            });

        nodeEnter.append("circle")
            .attr("r", 1e-6);

        nodeEnter.append("text")
            .attr("dy", ".35em")
            .style("fill-opacity", 1e-6);

        // Transition nodes to their new position.
        var nodeUpdate = node.transition()
            .duration(DURATION)
            .attr("transform", function(d) {
                return "translate(" + d.y + "," + d.x + ")";
            });

        nodeUpdate.select("circle")
            .attr("r", 4.5);

        nodeUpdate.select("circle")
            .style("fill", function(d) {
                var status = d.status,
                    color = "#FFFFFF";
                if (status === CLOSED) {
                    color = "lightsteelblue";
                } else if (status === OPENING) {
                    color =  "#ff0000";
                } else if (status === OPEN) {
                    color =  "#ffFFFF";
                } else if (status === LEAF) {
                    color =  "#ffFFFF";
                } else if (status === CLOSING) {
                    color = "#00FF00";
                }

                return color;
            });

        nodeUpdate.select("text")
            .attr("x", function(d) { return (d.children && d.children.length > 0) ? -10 : 10; })
            .attr("text-anchor", function(d) { return (d.children && d.children.length > 0) ? "end" : "start"; })
            .text(function(d) { return getDisplayName(d); })
            .style("fill-opacity", 1);

        // Transition exiting nodes to the parent's new position.
        var nodeExit = node.exit().transition()
            .duration(DURATION)
            .attr("transform", function(d) {
                return source ? "translate(" + source.y + "," + source.x + ")" : d.parent ? "translate(" + d.parent.y + "," + d.parent.x + ")" : "translate(" + d.y + "," + d.x + ")";
            })
            .remove();

        nodeExit.select("circle")
            .attr("r", 1e-6);

        nodeExit.select("text")
            .style("fill-opacity", 1e-6);

        // Update the links…
        var link = this._svg.selectAll("path.link")
            .data(links, function(d) { return d.target.id; });

        // Enter any new links at the parent's previous position.
        link.enter().insert("path", "g")
            .attr("class", "link")
            .attr("d", function(d) {
                var o = {x: d.source.x0 || d.source.y, y: d.source.y0 || d.source.y};
                return diagonal({source: o, target: o});
            });

        // Transition links to their new position.
        link.transition()
            .duration(DURATION)
            .attr("d", diagonal);

        // Transition exiting nodes to the parent's new position.
        link.exit().transition()
            .duration(DURATION)
            .attr("d", function(d) {
                var o = {x: d.source.x, y: d.source.y};
                return diagonal({source: o, target: o});
            })
            .remove();

        //node vertical positions by depth
        var nodesYByDepth = [];

        nodes.forEach(function(d) {
            // Stash the old positions for transition.
            d.x0 = d.x;
            d.y0 = d.y;

            // and query vertical coordinates of the nodes at the same depth to detect collision
            nodesYByDepth[d.depth] = nodesYByDepth[d.depth] || [];
            nodesYByDepth[d.depth].push(d.x);
        });

        //if already resizing, don't try to optimize again
        if (this.__resizing !== true) {
            var l = nodesYByDepth.length;
            var collideAtDepth = [];
            for (i = 1; i < l; i+= 1) {
                //sort the coordinates for easy check of collision
                nodesYByDepth[i].sort(function(a,b){return a-b});

                //see if any of the nodes overlap
                var j;
                var depthLength = nodesYByDepth[i].length;
                for (j = 0; j < depthLength - 1; j += 1 ) {
                    if (nodesYByDepth[i][j+1] - NODE_SIZE <= nodesYByDepth[i][j]) {
                        collideAtDepth.push([i,depthLength]);
                        break;
                    }
                }
            }

            this._logger.debug("Collide:" + collideAtDepth);

            var sum = 0;
            var len = collideAtDepth.length;
            if (len > 0) {
                //start resize mode
                this.__resizing = true;

                while (len--) {
                    sum += collideAtDepth[len][1];
                }

                this._resizeD3Tree(nodesYByDepth.length * TREE_LEVEL_DISTANCE, sum * NODE_SIZE);

                //redraw the tree
                this._update();

                //finish resize mode
                this.__resizing = false;
            }
        }
    };

    GraphVizWidget.prototype._onNodeClick = function(d) {
        switch(d.status) {
            case CLOSED:
                d.status = OPENING;
                this._update(undefined);
                this.onNodeOpen(d.id);
                break;
            case OPEN:
                d.status = CLOSING;
                d.children = undefined;
                this._update(d);
                this.onNodeClose(d.id);
                break;
            case OPENING:
                break;
            case CLOSING:
                break;
            case LEAF:
                break;
        }
    };

    GraphVizWidget.prototype._onNodeDblClick = function(d) {
        this.onNodeDblClick(d.id);
    };

    GraphVizWidget.prototype.setData = function (data) {
        this.__root = this._root;

        this._root = $.extend(true, {}, data);

        var copyOver = function (srcNode, dstNode) {
            var i, j;
            if (srcNode && dstNode) {
                if (srcNode.id === dstNode.id) {
                    dstNode.x0 = srcNode.x0;
                    dstNode.y0 = srcNode.y0;
                    i = srcNode.children ? srcNode.children.length : 0;
                    if (i > 0) {
                        while (i--) {
                            j = dstNode.children ? dstNode.children.length : 0;
                            if (j > 0) {
                                while (j--) {
                                    copyOver(srcNode.children[i], dstNode.children[j]);
                                }
                            }
                        }
                    }
                }
            }
        };

        copyOver(this.__root, this._root);
        delete this.__root;

        var width = this._el.width(),
            height = this._el.height();
        this._resizeD3Tree(width, height);

        this._update(undefined);

        //approximate width of the root name
        var rootNameWidth = this._root.name.length * 6;
        if (this._root.children) {
            rootNameWidth = (this._root.name + " [" + this._root.children.length + "]").length * 6;
        }
        this._el.find('svg > g').attr("transform", "translate(" + (rootNameWidth + MARGIN) + "," + MARGIN + ")");
    };

    GraphVizWidget.prototype.onNodeOpen = function (id) {
    };

    GraphVizWidget.prototype.onNodeClose = function (id) {
    };

    GraphVizWidget.prototype.onNodeDblClick = function (id) {
    };

    GraphVizWidget.prototype.onBackgroundDblClick = function () {
    };

    GraphVizWidget.prototype.destroy = function () {
    };

    _.extend(GraphVizWidget.prototype, GraphVizWidgetZoom.prototype);

    return GraphVizWidget;
});<|endoftext|>


<commit_before>
// -----------------------------------------------------------------
// -------------------- YesScript2 webextension --------------------
// -----------------------------------------------------------------
// Copyright (C) 2017 Andras Horvath <mail@log69.com>
// License: MIT
// Info: blocks scripts on sites with 3 states:
//   1)   no blocking (website is untouched)
//   2) half blocking (allowing internal and blocking external scripts)
//   3) full blocking (blocking internal and external scripts too)
//   page is reloaded automatically after every click

// -------------------- global variables --------------------

// holds a list of blocked urls
var g_urls  = [];
var g_urls2 = [];
// true if data is loaded from local storage
var g_sync_local  = false;
// true if data is loaded from remote storage
var g_sync_remote = false;

// -------------------- array functions --------------------

// ??domain.com and ?domain.com and domain.com are considered the same
function array_uniq(a){
  var b = [];
  for (var i in a) {
    var x = a[i];
    var y = x;
    if (x[0] == "?" && x[1] == "?"){ y = x.substr(2, x.length-1); }
    else if           (x[0] == "?"){ y = x.substr(1, x.length-1); }
    if ( b.indexOf(y) < 0
      && b.indexOf("?" + y) < 0
      && b.indexOf("??" + y) < 0){
      b.push(x);
    }
  }
  return b;
}

// merge arrays and uniq and sort
function array_merge(a, b){
  return array_uniq(a.concat(b)).sort();
}

// -------------------- sync functions --------------------

function debug(obj, text){ console.log("DEBUG / "
  + text.toString() + " / " + JSON.stringify(obj)); }

// load local data and sync with remote if available by merging them
function url_sync(){
  if (!g_sync_local){
    chrome.storage.local.get(function(ldata){
      if (ldata && typeof ldata.urls != "undefined"){
        g_urls = array_merge(ldata.urls, g_urls);
      }
      g_sync_local = true;
      url_sync_remote();
    });
  }
  else {
    url_sync_remote();
  }
}

function url_sync_remote(){
  // check for sync object because it's not available for mobile
  if (chrome.storage.sync){
    // continuously keep fetching remote data from sync if any
    //   and merge it with local one because we cannot know
    //   when user will log in and when data will be available
    chrome.storage.sync.get(function(sdata){
      if (sdata && typeof sdata.urls != "undefined"){
        g_urls = array_merge(g_urls, sdata.urls);
      }
    });
  }
  url_store();
}

function url_store(){
  // storing data when changed only helps keep the number of writes low
  //   to avoid reaching a limit in sync writes
  if (JSON.stringify(g_urls2) != JSON.stringify(g_urls)){
    g_urls2 = g_urls;

    if (g_sync_local){ chrome.storage.local.set({"urls": g_urls}); }
    chrome.storage.sync.set({"urls": g_urls});
  }
}

// -------------------- url functions --------------------

// is domain blocked? (empty means ??domain.com)
//   ??domain.com (1) = no
//    ?domain.com (2) = half
//     domain.com (3) = full
function url_test(u){
  // default value for empty (no dmain name is stored)
  var res = 1;
       if (g_urls.indexOf("??" + u) > -1){ res = 1; }
  else if (g_urls.indexOf("?"  + u) > -1){ res = 2; }
  else if (g_urls.indexOf(       u) > -1){ res = 3; }
  return res;
}

// switch to next state (no -> half -> full)
function url_next_state(u){
  var t = url_test(u);
  if (t == 2){
    g_urls[g_urls.indexOf("?" + u)] = u;
  }
  else if (t == 3){
    g_urls[g_urls.indexOf(u)] = "??" + u;
  }
  else{
    if (g_urls.indexOf("??" + u) > -1){
      g_urls[g_urls.indexOf("??" + u)] = "?" + u;
    } else {
      g_urls.push("?" + u);
    }
  }
}

function set_icon(flag){
  // browserAction.setIcon function is not available for mobile
  //   so check it first
  if (chrome.browserAction.setIcon){
    p = "icons/icon.svg";
    if (flag == 2){ p = "icons/icon2.svg"; }
    if (flag == 3){ p = "icons/icon3.svg"; }
    chrome.browserAction.setIcon({path: p});
  }
  // set tooltip for button on desktop and menu entry name on mobile
  t = "YesScript2 no blocking";
  if (flag == 2){ t = "YesScript2 half blocking"; }
  if (flag == 3){ t = "YesScript2 full blocking"; }
  chrome.browserAction.setTitle({title: t});
}

// check if page is marked untrusted and set icons and return status
//   according to it
function status(url, flag_clicked){
  var flag_state = false;
  var flag_icon  = false;

  if (url){
    // get domain part of the url
    var u2 = url.match(/:\/\/(.[^/]+)/);
    if (u2){
      var u = u2[1];

      // was there any click? toggle state of domain if so
      if (flag_clicked){ url_next_state(u); }

      // url exists? if so it means page is untrusted
      flag_state = url_test(u);
      flag_icon  = flag_state;
    }

    // set state of toolbar icon
    set_icon(flag_icon);
  }

  return flag_state;
}

// -------------------- events --------------------

// mark page untrusted or trusted when icon is clicked
chrome.browserAction.onClicked.addListener(
  function(details){
    status(details.url, 1);
    chrome.tabs.reload({bypassCache: true});
  }
);

// update when switching tabs
chrome.tabs.onActivated.addListener(
  function(details){
    // try to sync remote data from time to time on tab switch
    //   because the user might sign in the Sync service later only
    //   and I need to grab the remote data then
    url_sync();

    chrome.tabs.query({currentWindow: true, active: true},
      function(tabs){
        status(tabs[0].url);
      }
    );
  }
);

// check for windows object because it's not available for mobile
if (chrome.windows){
  // update when switching windows
  chrome.windows.onFocusChanged.addListener(
    function(winid){
      if (winid != chrome.windows.WINDOW_ID_NONE){
        chrome.tabs.query({windowId: winid, active: true},
          function(tabs){
            if (typeof tabs != "undefined"){
              if (typeof tabs[0] != "undefined"){
                status(tabs[0].url);
              }
            }
          }
        );
      }
    }
  );
}

// update when active page has finished loading
chrome.tabs.onUpdated.addListener(
  function(tabId, changeInfo, tab){
    if (tab.status == "complete" && tab.active) {
      url_sync();
      status(tab.url);
    }
  }
);

// block scripts on page if url is marked untrsuted based on
//   whether url exists in storage
// include the original response header merging the two arrays
// the trick of blocking all scripts for a domain is
//   adding CSP to the page header
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
// https://content-security-policy.com
chrome.webRequest.onHeadersReceived.addListener(
  function(details){
    var s = status(details.url);

    // half blocking?
    if (s == 2){
      var rh = details.responseHeaders.concat(
        [{name: "Content-Security-Policy",
          value: "script-src 'self' 'unsafe-inline'"}]
      );
      return {responseHeaders: rh};
    }

    // full blocking?
    if (s == 3){
      var rh = details.responseHeaders.concat(
        [{name: "Content-Security-Policy",
          value: "script-src 'none'"}]
      );
      return {responseHeaders: rh};
    }

    return {};
  },
  {urls: ["<all_urls>"], types: ["main_frame", "sub_frame"]},
  ["blocking", "responseHeaders"]
);

// -------------------- init --------------------

// sync data on startup
url_sync();

<commit_msg>update info<commit_after>
// -----------------------------------------------------------------
// -------------------- YesScript2 webextension --------------------
// -----------------------------------------------------------------
// Copyright (C) 2017 Andras Horvath <mail@log69.com>
// License: MIT
// Info: blocks scripts on sites with 3 states:
//   1)   no blocking (website is untouched)
//   2) half blocking (allowing internal and blocking external scripts)
//   3) full blocking (blocking internal and external scripts too)
//   page is reloaded automatically after every click in icon

// -------------------- global variables --------------------

// holds a list of blocked urls
var g_urls  = [];
var g_urls2 = [];
// true if data is loaded from local storage
var g_sync_local  = false;
// true if data is loaded from remote storage
var g_sync_remote = false;

// -------------------- array functions --------------------

// ??domain.com and ?domain.com and domain.com are considered the same
function array_uniq(a){
  var b = [];
  for (var i in a) {
    var x = a[i];
    var y = x;
    if (x[0] == "?" && x[1] == "?"){ y = x.substr(2, x.length-1); }
    else if           (x[0] == "?"){ y = x.substr(1, x.length-1); }
    if ( b.indexOf(y) < 0
      && b.indexOf("?" + y) < 0
      && b.indexOf("??" + y) < 0){
      b.push(x);
    }
  }
  return b;
}

// merge arrays and uniq and sort
function array_merge(a, b){
  return array_uniq(a.concat(b)).sort();
}

// -------------------- sync functions --------------------

function debug(obj, text){ console.log("DEBUG / "
  + text.toString() + " / " + JSON.stringify(obj)); }

// load local data and sync with remote if available by merging them
function url_sync(){
  if (!g_sync_local){
    chrome.storage.local.get(function(ldata){
      if (ldata && typeof ldata.urls != "undefined"){
        g_urls = array_merge(ldata.urls, g_urls);
      }
      g_sync_local = true;
      url_sync_remote();
    });
  }
  else {
    url_sync_remote();
  }
}

function url_sync_remote(){
  // check for sync object because it's not available for mobile
  if (chrome.storage.sync){
    // continuously keep fetching remote data from sync if any
    //   and merge it with local one because we cannot know
    //   when user will log in and when data will be available
    chrome.storage.sync.get(function(sdata){
      if (sdata && typeof sdata.urls != "undefined"){
        g_urls = array_merge(g_urls, sdata.urls);
      }
    });
  }
  url_store();
}

function url_store(){
  // storing data when changed only helps keep the number of writes low
  //   to avoid reaching a limit in sync writes
  if (JSON.stringify(g_urls2) != JSON.stringify(g_urls)){
    g_urls2 = g_urls;

    if (g_sync_local){ chrome.storage.local.set({"urls": g_urls}); }
    chrome.storage.sync.set({"urls": g_urls});
  }
}

// -------------------- url functions --------------------

// is domain blocked? (empty means ??domain.com)
//   ??domain.com (1) = no
//    ?domain.com (2) = half
//     domain.com (3) = full
function url_test(u){
  // default value for empty (no dmain name is stored)
  var res = 1;
       if (g_urls.indexOf("??" + u) > -1){ res = 1; }
  else if (g_urls.indexOf("?"  + u) > -1){ res = 2; }
  else if (g_urls.indexOf(       u) > -1){ res = 3; }
  return res;
}

// switch to next state (no -> half -> full)
function url_next_state(u){
  var t = url_test(u);
  if (t == 2){
    g_urls[g_urls.indexOf("?" + u)] = u;
  }
  else if (t == 3){
    g_urls[g_urls.indexOf(u)] = "??" + u;
  }
  else{
    if (g_urls.indexOf("??" + u) > -1){
      g_urls[g_urls.indexOf("??" + u)] = "?" + u;
    } else {
      g_urls.push("?" + u);
    }
  }
}

function set_icon(flag){
  // browserAction.setIcon function is not available for mobile
  //   so check it first
  if (chrome.browserAction.setIcon){
    p = "icons/icon.svg";
    if (flag == 2){ p = "icons/icon2.svg"; }
    if (flag == 3){ p = "icons/icon3.svg"; }
    chrome.browserAction.setIcon({path: p});
  }
  // set tooltip for button on desktop and menu entry name on mobile
  t = "YesScript2 no blocking";
  if (flag == 2){ t = "YesScript2 half blocking"; }
  if (flag == 3){ t = "YesScript2 full blocking"; }
  chrome.browserAction.setTitle({title: t});
}

// check if page is marked untrusted and set icons and return status
//   according to it
function status(url, flag_clicked){
  var flag_state = false;
  var flag_icon  = false;

  if (url){
    // get domain part of the url
    var u2 = url.match(/:\/\/(.[^/]+)/);
    if (u2){
      var u = u2[1];

      // was there any click? toggle state of domain if so
      if (flag_clicked){ url_next_state(u); }

      // url exists? if so it means page is untrusted
      flag_state = url_test(u);
      flag_icon  = flag_state;
    }

    // set state of toolbar icon
    set_icon(flag_icon);
  }

  return flag_state;
}

// -------------------- events --------------------

// mark page untrusted or trusted when icon is clicked
chrome.browserAction.onClicked.addListener(
  function(details){
    status(details.url, 1);
    chrome.tabs.reload({bypassCache: true});
  }
);

// update when switching tabs
chrome.tabs.onActivated.addListener(
  function(details){
    // try to sync remote data from time to time on tab switch
    //   because the user might sign in the Sync service later only
    //   and I need to grab the remote data then
    url_sync();

    chrome.tabs.query({currentWindow: true, active: true},
      function(tabs){
        status(tabs[0].url);
      }
    );
  }
);

// check for windows object because it's not available for mobile
if (chrome.windows){
  // update when switching windows
  chrome.windows.onFocusChanged.addListener(
    function(winid){
      if (winid != chrome.windows.WINDOW_ID_NONE){
        chrome.tabs.query({windowId: winid, active: true},
          function(tabs){
            if (typeof tabs != "undefined"){
              if (typeof tabs[0] != "undefined"){
                status(tabs[0].url);
              }
            }
          }
        );
      }
    }
  );
}

// update when active page has finished loading
chrome.tabs.onUpdated.addListener(
  function(tabId, changeInfo, tab){
    if (tab.status == "complete" && tab.active) {
      url_sync();
      status(tab.url);
    }
  }
);

// block scripts on page if url is marked untrsuted based on
//   whether url exists in storage
// include the original response header merging the two arrays
// the trick of blocking all scripts for a domain is
//   adding CSP to the page header
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
// https://content-security-policy.com
chrome.webRequest.onHeadersReceived.addListener(
  function(details){
    var s = status(details.url);

    // half blocking?
    if (s == 2){
      var rh = details.responseHeaders.concat(
        [{name: "Content-Security-Policy",
          value: "script-src 'self' 'unsafe-inline'"}]
      );
      return {responseHeaders: rh};
    }

    // full blocking?
    if (s == 3){
      var rh = details.responseHeaders.concat(
        [{name: "Content-Security-Policy",
          value: "script-src 'none'"}]
      );
      return {responseHeaders: rh};
    }

    return {};
  },
  {urls: ["<all_urls>"], types: ["main_frame", "sub_frame"]},
  ["blocking", "responseHeaders"]
);

// -------------------- init --------------------

// sync data on startup
url_sync();

<|endoftext|>


<commit_before>import * as templates from 'templates';
import * as data from 'data';

function displayForm() {

    Promise.all([templates.get('newTicket'), data.getUsers()])
        .then(([template, users]) => {
            let currentDate = new Date().toString().split(' ');
            currentDate = currentDate[1] + " " + currentDate[2] + " " + currentDate[3] + " " + currentDate[4];

            $('#main-content').html(template({
                owner: data.getLoggedInUser(),
                date: currentDate,
                users: users.result,
                urgency: ["low", "mid", "high"]
            }));
            $('#sendNewTicket').on('click', submitForm);
            $('#select-engineer li a').on('click', selectOptionEngineer);
            $('#select-urgency li a').on('click', selectOptionUrgency);
        })
}

function selectOptionEngineer() {
    const currentSelection = $(this).text();
    const dataUserId = $(this).attr('data-user-id');

    $('#engineer').attr('data-user-id', dataUserId);
    $('#engineer-text').text(currentSelection);
}

function selectOptionUrgency() {
    const currentSelection = $(this).text();
    $('#urgency-text').text(currentSelection);
}

function submitForm() {
    // console.log($('#shortDecription'));
    let newTicket = {
        user: $('#userName').text(),
        shortDescription: $('#shortDescription').val(),
        longDescription: $('#lognDescription').val(),
        date: new Date(),
        engneer: $('#engineer').attr('data-user-id'),
        urgency: $('#urgency-text').text(),
        comment: $('#comment').val()
    }

    data.sendNewTicket(newTicket)
        .then(function(successObj) {
            toastr.success('Ticket successfully filed.');
        }, function(err) {
            toastr.error(err.responseJSON);
        })
    $('#main-content').text('');
    document.location.href = '#';
}

export {
    displayForm
}
<commit_msg>client side ticket validation<commit_after>import * as templates from 'templates';
import * as data from 'data';

function displayForm() {

    Promise.all([templates.get('newTicket'), data.getUsers()])
        .then(([template, users]) => {
            let currentDate = new Date().toString().split(' ');
            currentDate = currentDate[1] + " " + currentDate[2] + " " + currentDate[3] + " " + currentDate[4];

            $('#main-content').html(template({
                owner: data.getLoggedInUser(),
                date: currentDate,
                users: users.result,
                urgency: ["low", "mid", "high"]
            }));
            $('#sendNewTicket').on('click', submitForm);
            $('#select-engineer li a').on('click', selectOptionEngineer);
            $('#select-urgency li a').on('click', selectOptionUrgency);
        })
}

function selectOptionEngineer() {
    const currentSelection = $(this).text();
    const dataUserId = $(this).attr('data-user-id');

    $('#engineer').attr('data-user-id', dataUserId);
    $('#engineer-text').text(currentSelection);
}

function selectOptionUrgency() {
    const currentSelection = $(this).text();
    $('#urgency-text').text(currentSelection);
}

function submitForm() {
   let newTicket = {
        user: $('#userName').text(),
        shortDescription: $('#shortDescription').val(),
        longDescription: $('#lognDescription').val(),
        date: new Date(),
        engneer: $('#engineer').attr('data-user-id'),
        urgency: $('#urgency-text').text(),
        //comment: $('#comment').val()
    }
    console.log(newTicket);
    for(let keys in newTicket){
        console.log(keys);
        if(newTicket[keys].match(/([<>&])./gm)){
            console.log(newTicket[keys]);
             toastr.error("You can't use symbols <> and & in "+keys);
             return;
        }else if(newTicket[keys]===""||newTicket[keys]===undefined){
             toastr.error("You can't have empty filed  "+keys);
             return;
        }

    }
    /*
 for (let k in ticket) {
            console.log("key:" + k + " value:" + ticket[k]);
            if (ticket[k].match(/([<>&])./gm)) {
                status = false;
                res.status(401)
                    .json("You can't use symbols like < > and & in field "+k);
                return;
            }else if(ticket[k]===""||ticket[k]===undefined){
                status = false;
                res.status(401)
                    .json("You can't have empty filed "+k);
                return;
            }
        }
    */

    data.sendNewTicket(newTicket)
        .then(function(successObj) {
            toastr.success('Ticket successfully filed.');
        }, function(err) {
            toastr.error(err.responseJSON);
        })
    $('#main-content').text('');
    document.location.href = '#';
}

export {
    displayForm
}
<|endoftext|>


<commit_before><commit_msg>Added badge text<commit_after>chrome.browserAction.setBadgeBackgroundColor({ color: [255, 0, 0, 255] });
chrome.browserAction.setBadgeText({text: 'Arbit'});
<|endoftext|>


<commit_before>import { Box } from '@material-ui/core'
import moment from 'moment'
import * as R from 'ramda'
import React, { memo } from 'react'

import {
  PropertyCard,
  OVERRIDE_AUTHORIZED,
  OVERRIDE_REJECTED
} from 'src/pages/Customers/components/propertyCard'
import { ifNotNull } from 'src/utils/nullCheck'

import { getName } from '../helper'

import Field from './Field'

const formatGender = field => {
  // In US cards, gender comes as a number. In BC and ZA cards, gender comes as a letter
  const genderMap = {
    1: 'M',
    2: 'F'
  }

  const isNumber = s => {
    if (typeof s !== 'string') return false
    return !isNaN(s) && !isNaN(parseInt(s))
  }

  return !isNumber(field) ? field : genderMap[field]
}

const IdDataCard = memo(({ customerData, updateCustomer }) => {
  const idData = R.path(['idCardData'])(customerData)
  const rawExpirationDate = R.path(['expirationDate'])(idData)
  const country = R.path(['country'])(idData)
  const rawDob = R.path(['dateOfBirth'])(idData)
  console.log('customer', customerData)

  const elements = [
    {
      header: 'Name',
      display: `${getName(customerData)}`,
      size: 190
    },
    {
      header: 'ID number',
      display: R.path(['documentNumber'])(idData),
      size: 160
    },
    {
      header: 'Birth Date',
      display: ifNotNull(rawDob, moment.utc(rawDob).format('YYYY-MM-DD')),
      size: 110
    },
    {
      header: 'Age',
      display: ifNotNull(
        rawDob,
        moment.utc().diff(moment.utc(rawDob).format('YYYY-MM-DD'), 'years')
      ),
      size: 50
    },
    {
      header: 'Gender',
      display: formatGender(
        R.path(['gender'])(idData) ?? R.path(['sex'])(idData)
      ),
      size: 80
    },
    {
      header: country === 'Canada' ? 'Province' : 'State',
      display: R.path(['state'])(idData),
      size: 120
    },
    {
      header: 'Expiration Date',
      display: ifNotNull(
        rawExpirationDate,
        moment.utc(rawExpirationDate).format('YYYY-MM-DD')
      )
    }
  ]

  return (
    <PropertyCard
      title={'ID data'}
      state={R.path(['idCardDataOverride'])(customerData)}
      authorize={() =>
        updateCustomer({ idCardDataOverride: OVERRIDE_AUTHORIZED })
      }
      reject={() => updateCustomer({ idCardDataOverride: OVERRIDE_REJECTED })}>
      <Box display="flex" alignItems="center">
        {elements.map(({ header, display, size }, idx) => (
          <Field key={idx} label={header} display={display} size={size} />
        ))}
      </Box>
    </PropertyCard>
  )
})

export default IdDataCard
<commit_msg>fix: remove log<commit_after>import { Box } from '@material-ui/core'
import moment from 'moment'
import * as R from 'ramda'
import React, { memo } from 'react'

import {
  PropertyCard,
  OVERRIDE_AUTHORIZED,
  OVERRIDE_REJECTED
} from 'src/pages/Customers/components/propertyCard'
import { ifNotNull } from 'src/utils/nullCheck'

import { getName } from '../helper'

import Field from './Field'

const formatGender = field => {
  // In US cards, gender comes as a number. In BC and ZA cards, gender comes as a letter
  const genderMap = {
    1: 'M',
    2: 'F'
  }

  const isNumber = s => {
    if (typeof s !== 'string') return false
    return !isNaN(s) && !isNaN(parseInt(s))
  }

  return !isNumber(field) ? field : genderMap[field]
}

const IdDataCard = memo(({ customerData, updateCustomer }) => {
  const idData = R.path(['idCardData'])(customerData)
  const rawExpirationDate = R.path(['expirationDate'])(idData)
  const country = R.path(['country'])(idData)
  const rawDob = R.path(['dateOfBirth'])(idData)

  const elements = [
    {
      header: 'Name',
      display: `${getName(customerData)}`,
      size: 190
    },
    {
      header: 'ID number',
      display: R.path(['documentNumber'])(idData),
      size: 160
    },
    {
      header: 'Birth Date',
      display: ifNotNull(rawDob, moment.utc(rawDob).format('YYYY-MM-DD')),
      size: 110
    },
    {
      header: 'Age',
      display: ifNotNull(
        rawDob,
        moment.utc().diff(moment.utc(rawDob).format('YYYY-MM-DD'), 'years')
      ),
      size: 50
    },
    {
      header: 'Gender',
      display: formatGender(
        R.path(['gender'])(idData) ?? R.path(['sex'])(idData)
      ),
      size: 80
    },
    {
      header: country === 'Canada' ? 'Province' : 'State',
      display: R.path(['state'])(idData),
      size: 120
    },
    {
      header: 'Expiration Date',
      display: ifNotNull(
        rawExpirationDate,
        moment.utc(rawExpirationDate).format('YYYY-MM-DD')
      )
    }
  ]

  return (
    <PropertyCard
      title={'ID data'}
      state={R.path(['idCardDataOverride'])(customerData)}
      authorize={() =>
        updateCustomer({ idCardDataOverride: OVERRIDE_AUTHORIZED })
      }
      reject={() => updateCustomer({ idCardDataOverride: OVERRIDE_REJECTED })}>
      <Box display="flex" alignItems="center">
        {elements.map(({ header, display, size }, idx) => (
          <Field key={idx} label={header} display={display} size={size} />
        ))}
      </Box>
    </PropertyCard>
  )
})

export default IdDataCard
<|endoftext|>


<commit_before>/* jslint devel: true, browser: true, indent: 4, evil: true */
/* global chrome, $, gapi, B64 */

(function() {
    "use strict";

    function urlChange(request, port){

            port.postMessage(resp)

    }

    // Add listeners for communication from client
    chrome.runtime.onConnect.addListener(function(port) {
        if (port.name === "watchURLChange") {
            chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
                // alert client of urlChange
                port.postMessage("");
            });
        }
    });

}());
<commit_msg>Delete background.js<commit_after><|endoftext|>


<commit_before>(function () {
  'use strict';

  /*
 * checklist:
 * - scope init variable - needed?
 * - scaffolding order
 * - directive scaffolding ord.
 * - check $apply and $timeout order
 * - check if vm.isInitialized should come much later?
 */

  angular
    .module('messages')
    .controller('MessagesThreadController', MessagesThreadController);

  /* @ngInject */
  function MessagesThreadController($rootScope, $scope, $stateParams, $state, $timeout, $filter, $analytics, Authentication, Messages, MessagesRead, messageCenterService, locker, userTo) {

    // Go back to inbox on these cases
    // - No recepient defined
    // - Not signed in
    // - Sending messages to yourself
    if (!$stateParams.username || !Authentication.user || Authentication.user._id === userTo._id) {
      $state.go('inbox');
    }

    // Vars
    var elemThread,
        syncReadTimer,
        flaggedAsRead = [],
        messageIdsInView = [],
        editorContentChangedTimeout;

    // Make cache id unique for this user
    var cachePrefix = 'messages.thread.' + Authentication.user._id + '-' + $stateParams.username;

    // View model
    var vm = this;

    // Exposed to the view
    vm.userFrom = Authentication.user;
    vm.userTo = userTo;
    vm.isSending = false;
    vm.isInitialized = false;
    vm.isQuickReplyPanelHidden = true;
    vm.messages = [];
    vm.messageHandler = new Messages();
    vm.profileDescriptionLength = Authentication.user.description ? $filter('plainTextLength')(Authentication.user.description) : 0;
    vm.sendMessage = sendMessage;
    vm.moreMessages = moreMessages;
    vm.messageRead = messageRead;
    vm.focusToWriting = focusToWriting;
    vm.sendHostingReply = sendHostingReply;
    vm.hostingReplyStatus = hostingReplyStatus;
    vm.editorContentChanged = editorContentChanged;
    vm.content = '';

    activate();

    /**
     * Initialize controller
     */
    function activate() {
      // Tell unread-messages directive to sync itself
      $rootScope.$broadcast('syncUnreadMessagesCount');

      /**
       * Is local/sessionStorage supported? This might fail in browser's incognito mode
       *
       * If it is, unfinished messages are cached to SessionStorage
       * Check for a previously saved message here
       *
       * See also sendMessage(), where message is clared
       */
      if (locker.supported()) {
        // Get message from cache, use default if it doesn't exist
        vm.content = locker.driver('session').get(cachePrefix, '');
      }

      // Fetches first page of messages after receiving user has finished loading (we need the userId from there)
      userTo.$promise.then(function () {
        fetchMessages().$promise.then(function (data) {

          addMessages(data);
          vm.isInitialized = true;

          if (data.length > 0) {
            var userReplied = checkAuthUserReplied(data);
            if (!userReplied) {
              vm.isQuickReplyPanelHidden = false;
            }
          }

          // Timeout makes sure thread-dimensions-directive has finished loading
          // and there would thus be something actually listening to these broadcasts:
          $timeout(function () {
            $scope.$broadcast('threadRefreshLayout');
            if (data.length > 0) {
              $scope.$broadcast('threadScrollToBottom');
            } else if (!vm.messages.length && !vm.content.length) {
              vm.content = '
I’m traveling to ______ because
' +
                           '
I chose to write to you because
';
            }
          });

        });
      },
      // No user...
      function (error) {
        // User not found...
        if (error.status === 404) {
          vm.isInitialized = true;
        // Other Unexpected errors...
        } else {
          messageCenterService.add('warning', error.message || 'Cannot load messages. Please refresh the page and try again.', { timeout: 20000 });
        }
      });

    }

    /**
     * Sends a predefined quickreply
     *
     * @param {String} reply - 'yes' for positive, 'no' for negative
     */
    function sendHostingReply(reply) {
      vm.isQuickReplyPanelHidden = true;

      var quickReplyMessage;

      if (reply === 'yes') {
        quickReplyMessage = 'Yes, I can host!';
      } else {
        quickReplyMessage = 'Sorry, I can\'t host';
      }

      // Send a normal message with predefined content
      sendMessage(
        // This attribute must be whitelisted in `sanitizeOptions` at
        // `modules/core/server/services/text.server.service.js`
        '
' +
          '' +
            quickReplyMessage +
          '' +
        '
'
      );

      $analytics.eventTrack('messages.hostingreply.' + reply, {
        category: 'messages.hostingreply',
        label: 'Message hosting hosting reply "' + reply + '"'
      });
    }

    /**
     * Set focus to message textfield and hide quickreply buttons
     */
    function focusToWriting() {
      vm.isQuickReplyPanelHidden = true;
      angular.element('#message-reply-content').focus();
    }

    /**
     * Used to determine if message contains hosting request reply
     *
     * @param {String} messageContent - Message to be analysed
     * @returns {String} - 'unknown' if it doesn't, 'yes' if it does and reply is positive, 'no' if it does and reply is negative
     */
    function hostingReplyStatus(messageContent) {

      // Message has a hosting request reply and it's positive
      if (messageContent.substr(0, 21) === '<p data-hosting=\"yes\"') {
        return 'yes';
      }

      // Message has a hosting request reply and it's negative
      if (messageContent.substr(0, 20) === '<p data-hosting=\"no\"') {
        return 'no';
      }

      // Message doesn't have hosting request reply
      return 'unknown';
    }

    /**
    *
    * Check if Authenticated Receiver Replied to a request
    */
    function checkAuthUserReplied(data) {
      for (var i = 0; i < data.length; i++) {
        if (Authentication.user._id === data[i].userFrom._id) {
          return true;
        }
      }
      return false;
    }

    /**
     * When contents at the editor change, update layout
     */
    function editorContentChanged() {
      // Add (or reset) timeout to prevent calling this too often
      $timeout.cancel(editorContentChangedTimeout);
      editorContentChangedTimeout = $timeout(refreshAndScrollToBottom, 300);
    }

    /**
     * Call underlaying thread-dimensions directive to update+scroll
     */
    function refreshAndScrollToBottom() {
      $scope.$broadcast('threadRefreshLayout');
      $scope.$broadcast('threadScrollToBottom');

      // Save message to a cache (see sendMessage() where it's emptiet and vm-list for the getter)
      if (locker.supported()) {
        locker.driver('session').put(cachePrefix, vm.content);
      }
    }

    /**
     * Attach userID for backend calls
     */
    function fetchMessages() {
      return (
        vm.messageHandler.fetchMessages({
          userId: userTo._id
        })
      );
    }

    /**
     * Appends returned messages to model
     */
    function addMessages(data) {

      // Loop trough received data (for loop is the fastest)
      for (var i = 0; i < data.length; i++) {

        // Check if message by this ID doesn't exist yet
        // messageIdsInView is used as a key storage for quick reference of messages already in view
        if (messageIdsInView.indexOf(data[i]._id) === -1) {
          messageIdsInView.push(data[i]._id);
          vm.messages.push(data[i]);
        }
      }

      // Finally refresh the layout
      $timeout(function () {
        $scope.$broadcast('threadRefreshLayout');
      });

    }

    /**
     * Gets next page of messages
     * Activates when the first(top most) message hits the top viewport
     */
    function moreMessages() {
      if (vm.messageHandler.nextPage && !vm.messageHandler.paginationTimeout) {

        vm.isQuickReplyPanelHidden = true;

        if (!elemThread) elemThread = angular.element('#messages-thread');

        var oldHeight = elemThread[0].scrollHeight;

        fetchMessages().$promise.then(function (data) {
          addMessages(data);
          setScrollPosition(oldHeight);
        });

        $analytics.eventTrack('thread-pagination', {
          category: 'messages.thread',
          label: 'Message thread page ' + (vm.messageHandler.nextPage ? vm.messageHandler.nextPage.page : 0)
        });
      }
    }

    /**
     * Restores scroll position after pagination
     * Timeout is in place to force function to execute after digest cycle to properly calculate scroll height.
     */
    function setScrollPosition(oldHeight) {
      $timeout(function () {
        var newHeight = elemThread[0].scrollHeight;
        angular.element(elemThread.scrollTop(newHeight - oldHeight));
      });
    }

    /**
     * Send messages marked as read (at frontend) to the backend
     * Has 1s timeout to slow down continuous pinging of the API
     */
    function activateSyncRead() {
      // Cancel previously set timer
      if (syncReadTimer) $timeout.cancel(syncReadTimer);
      // syncRead happens with 1s delay
      // (and gets postponed by 1s if new activateSyncRead() happens)
      if (flaggedAsRead.length > 0) {
        syncReadTimer = $timeout(syncRead, 1000);
      }
    }

    /**
     * Send messages marked at read to the API and then empty buffer
     */
    function syncRead() {
      MessagesRead.query({
        messageIds: flaggedAsRead
      }, function () {
        flaggedAsRead = [];
        // Tell app controller to sync this counter
        $rootScope.$broadcast('syncUnreadMessagesCount');
      });
    }

    /**
     * Mark message read at the frontend
     * This function inits each time message div passes viewport
     * Read message id is stored at array which will be sent to backend and emptied
     *
     * @todo: kill observer after message is marked read
     * @todo: having this as a function is a performance issue
     */
    function messageRead(message, scrollingUp, scrollingDown) {

      // It was read earlier
      if (message.read === true) return true;

      // Own messages are always read
      if (message.userFrom._id === Authentication.user._id) return true;

      // It got marked read just now
      var read = (scrollingUp === true || scrollingDown === true);
      if (message.userFrom._id !== Authentication.user._id && !message.read && read) {
        message.read = true;
        flaggedAsRead.push(message._id);
        activateSyncRead();
      }

      return read;
    }

    /**
     * Send a message
     */
    function sendMessage(msg) {
      vm.isSending = true;

      // Make sure the message isn't empty.
      // Sometimes we'll have some empty blocks due wysiwyg
      if (!msg) {
        if ($filter('plainTextLength')(vm.content) === 0) {
          vm.isSending = false;
          messageCenterService.add('warning', 'Please write a message first...');
          return;
        }
      }

      // eslint-disable-next-line new-cap
      var message = new vm.messageHandler.ajaxCall({
        content: msg,
        userTo: userTo._id,
        read: false
      });

      message.$save(function (response) {

        vm.isSending = false;

        // Remove message from cache
        if (locker.supported()) {
          locker.driver('session').forget(cachePrefix);
        }

        // Remove this when socket is back!
        vm.messages.unshift(response);

        $analytics.eventTrack('message.send', {
          category: 'messages',
          label: 'Message send'
        });

        // $timeout ensures scroll happens only after DOM has finished rendering
        $timeout(function () {
          $scope.$broadcast('threadScrollToBottom');
        });

      }, function (errorResponse) {
        vm.isSending = false;
        messageCenterService.add('danger', (errorResponse.data && errorResponse.data.message) ? errorResponse.data.message : 'Could not send the message. Please try again.');
      });
    }

  }

}());
<commit_msg>Replace default text template when writing to member<commit_after>(function () {
  'use strict';

  /*
 * checklist:
 * - scope init variable - needed?
 * - scaffolding order
 * - directive scaffolding ord.
 * - check $apply and $timeout order
 * - check if vm.isInitialized should come much later?
 */

  angular
    .module('messages')
    .controller('MessagesThreadController', MessagesThreadController);

  /* @ngInject */
  function MessagesThreadController($rootScope, $scope, $stateParams, $state, $timeout, $filter, $analytics, Authentication, Messages, MessagesRead, messageCenterService, locker, userTo) {

    // Go back to inbox on these cases
    // - No recepient defined
    // - Not signed in
    // - Sending messages to yourself
    if (!$stateParams.username || !Authentication.user || Authentication.user._id === userTo._id) {
      $state.go('inbox');
    }

    // Vars
    var elemThread,
        syncReadTimer,
        flaggedAsRead = [],
        messageIdsInView = [],
        editorContentChangedTimeout;

    // Make cache id unique for this user
    var cachePrefix = 'messages.thread.' + Authentication.user._id + '-' + $stateParams.username;

    // View model
    var vm = this;

    // Exposed to the view
    vm.userFrom = Authentication.user;
    vm.userTo = userTo;
    vm.isSending = false;
    vm.isInitialized = false;
    vm.isQuickReplyPanelHidden = true;
    vm.messages = [];
    vm.messageHandler = new Messages();
    vm.profileDescriptionLength = Authentication.user.description ? $filter('plainTextLength')(Authentication.user.description) : 0;
    vm.sendMessage = sendMessage;
    vm.moreMessages = moreMessages;
    vm.messageRead = messageRead;
    vm.focusToWriting = focusToWriting;
    vm.sendHostingReply = sendHostingReply;
    vm.hostingReplyStatus = hostingReplyStatus;
    vm.editorContentChanged = editorContentChanged;
    vm.content = '';

    activate();

    /**
     * Initialize controller
     */
    function activate() {
      // Tell unread-messages directive to sync itself
      $rootScope.$broadcast('syncUnreadMessagesCount');

      /**
       * Is local/sessionStorage supported? This might fail in browser's incognito mode
       *
       * If it is, unfinished messages are cached to SessionStorage
       * Check for a previously saved message here
       *
       * See also sendMessage(), where message is clared
       */
      if (locker.supported()) {
        // Get message from cache, use default if it doesn't exist
        vm.content = locker.driver('session').get(cachePrefix, '');
      }

      // Fetches first page of messages after receiving user has finished loading (we need the userId from there)
      userTo.$promise.then(function () {
        fetchMessages().$promise.then(function (data) {

          addMessages(data);
          vm.isInitialized = true;

          if (data.length > 0) {
            var userReplied = checkAuthUserReplied(data);
            if (!userReplied) {
              vm.isQuickReplyPanelHidden = false;
            }
          }

          // Timeout makes sure thread-dimensions-directive has finished loading
          // and there would thus be something actually listening to these broadcasts:
          $timeout(function () {
            $scope.$broadcast('threadRefreshLayout');
            if (data.length > 0) {
              $scope.$broadcast('threadScrollToBottom');
            } else if (!vm.messages.length && !vm.content.length) {
              vm.content = '';
            }
          });

        });
      },
      // No user...
      function (error) {
        // User not found...
        if (error.status === 404) {
          vm.isInitialized = true;
        // Other Unexpected errors...
        } else {
          messageCenterService.add('warning', error.message || 'Cannot load messages. Please refresh the page and try again.', { timeout: 20000 });
        }
      });

    }

    /**
     * Sends a predefined quickreply
     *
     * @param {String} reply - 'yes' for positive, 'no' for negative
     */
    function sendHostingReply(reply) {
      vm.isQuickReplyPanelHidden = true;

      var quickReplyMessage;

      if (reply === 'yes') {
        quickReplyMessage = 'Yes, I can host!';
      } else {
        quickReplyMessage = 'Sorry, I can\'t host';
      }

      // Send a normal message with predefined content
      sendMessage(
        // This attribute must be whitelisted in `sanitizeOptions` at
        // `modules/core/server/services/text.server.service.js`
        '
' +
          '' +
            quickReplyMessage +
          '' +
        '
'
      );

      $analytics.eventTrack('messages.hostingreply.' + reply, {
        category: 'messages.hostingreply',
        label: 'Message hosting hosting reply "' + reply + '"'
      });
    }

    /**
     * Set focus to message textfield and hide quickreply buttons
     */
    function focusToWriting() {
      vm.isQuickReplyPanelHidden = true;
      angular.element('#message-reply-content').focus();
    }

    /**
     * Used to determine if message contains hosting request reply
     *
     * @param {String} messageContent - Message to be analysed
     * @returns {String} - 'unknown' if it doesn't, 'yes' if it does and reply is positive, 'no' if it does and reply is negative
     */
    function hostingReplyStatus(messageContent) {

      // Message has a hosting request reply and it's positive
      if (messageContent.substr(0, 21) === '<p data-hosting=\"yes\"') {
        return 'yes';
      }

      // Message has a hosting request reply and it's negative
      if (messageContent.substr(0, 20) === '<p data-hosting=\"no\"') {
        return 'no';
      }

      // Message doesn't have hosting request reply
      return 'unknown';
    }

    /**
    *
    * Check if Authenticated Receiver Replied to a request
    */
    function checkAuthUserReplied(data) {
      for (var i = 0; i < data.length; i++) {
        if (Authentication.user._id === data[i].userFrom._id) {
          return true;
        }
      }
      return false;
    }

    /**
     * When contents at the editor change, update layout
     */
    function editorContentChanged() {
      // Add (or reset) timeout to prevent calling this too often
      $timeout.cancel(editorContentChangedTimeout);
      editorContentChangedTimeout = $timeout(refreshAndScrollToBottom, 300);
    }

    /**
     * Call underlaying thread-dimensions directive to update+scroll
     */
    function refreshAndScrollToBottom() {
      $scope.$broadcast('threadRefreshLayout');
      $scope.$broadcast('threadScrollToBottom');

      // Save message to a cache (see sendMessage() where it's emptiet and vm-list for the getter)
      if (locker.supported()) {
        locker.driver('session').put(cachePrefix, vm.content);
      }
    }

    /**
     * Attach userID for backend calls
     */
    function fetchMessages() {
      return (
        vm.messageHandler.fetchMessages({
          userId: userTo._id
        })
      );
    }

    /**
     * Appends returned messages to model
     */
    function addMessages(data) {

      // Loop trough received data (for loop is the fastest)
      for (var i = 0; i < data.length; i++) {

        // Check if message by this ID doesn't exist yet
        // messageIdsInView is used as a key storage for quick reference of messages already in view
        if (messageIdsInView.indexOf(data[i]._id) === -1) {
          messageIdsInView.push(data[i]._id);
          vm.messages.push(data[i]);
        }
      }

      // Finally refresh the layout
      $timeout(function () {
        $scope.$broadcast('threadRefreshLayout');
      });

    }

    /**
     * Gets next page of messages
     * Activates when the first(top most) message hits the top viewport
     */
    function moreMessages() {
      if (vm.messageHandler.nextPage && !vm.messageHandler.paginationTimeout) {

        vm.isQuickReplyPanelHidden = true;

        if (!elemThread) elemThread = angular.element('#messages-thread');

        var oldHeight = elemThread[0].scrollHeight;

        fetchMessages().$promise.then(function (data) {
          addMessages(data);
          setScrollPosition(oldHeight);
        });

        $analytics.eventTrack('thread-pagination', {
          category: 'messages.thread',
          label: 'Message thread page ' + (vm.messageHandler.nextPage ? vm.messageHandler.nextPage.page : 0)
        });
      }
    }

    /**
     * Restores scroll position after pagination
     * Timeout is in place to force function to execute after digest cycle to properly calculate scroll height.
     */
    function setScrollPosition(oldHeight) {
      $timeout(function () {
        var newHeight = elemThread[0].scrollHeight;
        angular.element(elemThread.scrollTop(newHeight - oldHeight));
      });
    }

    /**
     * Send messages marked as read (at frontend) to the backend
     * Has 1s timeout to slow down continuous pinging of the API
     */
    function activateSyncRead() {
      // Cancel previously set timer
      if (syncReadTimer) $timeout.cancel(syncReadTimer);
      // syncRead happens with 1s delay
      // (and gets postponed by 1s if new activateSyncRead() happens)
      if (flaggedAsRead.length > 0) {
        syncReadTimer = $timeout(syncRead, 1000);
      }
    }

    /**
     * Send messages marked at read to the API and then empty buffer
     */
    function syncRead() {
      MessagesRead.query({
        messageIds: flaggedAsRead
      }, function () {
        flaggedAsRead = [];
        // Tell app controller to sync this counter
        $rootScope.$broadcast('syncUnreadMessagesCount');
      });
    }

    /**
     * Mark message read at the frontend
     * This function inits each time message div passes viewport
     * Read message id is stored at array which will be sent to backend and emptied
     *
     * @todo: kill observer after message is marked read
     * @todo: having this as a function is a performance issue
     */
    function messageRead(message, scrollingUp, scrollingDown) {

      // It was read earlier
      if (message.read === true) return true;

      // Own messages are always read
      if (message.userFrom._id === Authentication.user._id) return true;

      // It got marked read just now
      var read = (scrollingUp === true || scrollingDown === true);
      if (message.userFrom._id !== Authentication.user._id && !message.read && read) {
        message.read = true;
        flaggedAsRead.push(message._id);
        activateSyncRead();
      }

      return read;
    }

    /**
     * Send a message
     */
    function sendMessage(msg) {
      vm.isSending = true;

      // Make sure the message isn't empty.
      // Sometimes we'll have some empty blocks due wysiwyg
      if (!msg) {
        if ($filter('plainTextLength')(vm.content) === 0) {
          vm.isSending = false;
          messageCenterService.add('warning', 'Please write a message first...');
          return;
        }
      }

      // eslint-disable-next-line new-cap
      var message = new vm.messageHandler.ajaxCall({
        content: msg,
        userTo: userTo._id,
        read: false
      });

      message.$save(function (response) {

        vm.isSending = false;

        // Remove message from cache
        if (locker.supported()) {
          locker.driver('session').forget(cachePrefix);
        }

        // Remove this when socket is back!
        vm.messages.unshift(response);

        $analytics.eventTrack('message.send', {
          category: 'messages',
          label: 'Message send'
        });

        // $timeout ensures scroll happens only after DOM has finished rendering
        $timeout(function () {
          $scope.$broadcast('threadScrollToBottom');
        });

      }, function (errorResponse) {
        vm.isSending = false;
        messageCenterService.add('danger', (errorResponse.data && errorResponse.data.message) ? errorResponse.data.message : 'Could not send the message. Please try again.');
      });
    }

  }

}());
<|endoftext|>


<commit_before>if (typeof browser == "undefined")
    var browser = chrome;

browser.webRequest.onBeforeSendHeaders.addListener(
    function(details) {
        details.requestHeaders.push({
            name: "X-Molotov-Agent",
            value: "{\"app_id\":\"electron_app\",\"app_build\":3,\"app_version_name\":\"1.8.0\",\"type\":\"desktop\",\"electron_version\":\"1.7.9\",\"os\":\"Unknown\",\"os_version\":\"Unknown\",\"manufacturer\":\"Cocktail\",\"serial\":\"Unknown\",\"model\":\"Cocktail\",\"brand\":\"Cocktail\"}"
        });

        return {requestHeaders: details.requestHeaders};
    },
    {
        urls: [
            "https://fapi.molotov.tv/v2/config",
            "https://fapi.molotov.tv/v2/references",
            "https://fapi.molotov.tv/v2/me/assets*",
            "https://fapi.molotov.tv/v2/me/actions*",
            "https://fapi.molotov.tv/v2/me/whats-up*",
            "https://fapi.molotov.tv/v3/auth/login",
            "https://fapi.molotov.tv/v3/auth/refresh/*",
            "https://multi-screen.molotov.tv/v3/ping*",
            "https://jessica.molotov.tv/video/preroll*"
        ],
        types: ["xmlhttprequest"]
    },
    ["blocking", "requestHeaders"]
);

browser.webRequest.onCompleted.addListener(
    function(details) {
        // User successfully logged in.
        if (details.method == "POST"
            && details.url == "https://fapi.molotov.tv/v3/auth/login"
            && details.statusCode == "200"
            // When the maximum number of registered devices is reached, the
            // login "cookie" is immediately cleared and a device removing
            // dialog is opened. When this happens, we should NOT refresh
            // the page, otherwise the user is immediately redirected to the
            // login page.
            && localStorage.getItem("mltv_session")) {
                browser.tabs.reload();
        }
    },
    {
        urls: [
            "https://fapi.molotov.tv/v3/auth/login"
        ],
        types: ["xmlhttprequest"]
    }
);

browser.browserAction.onClicked.addListener(
    function(details) {
        browser.tabs.create({url: "https://app.molotov.tv/"});
    }
);<commit_msg>Send "X-Molotov-Agent" header to all API endpoints<commit_after>if (typeof browser == "undefined")
    var browser = chrome;

browser.webRequest.onBeforeSendHeaders.addListener(
    function(details) {
        details.requestHeaders.push({
            name: "X-Molotov-Agent",
            value: "{\"app_id\":\"electron_app\",\"app_build\":3,\"app_version_name\":\"1.8.0\",\"type\":\"desktop\",\"electron_version\":\"1.7.9\",\"os\":\"Unknown\",\"os_version\":\"Unknown\",\"manufacturer\":\"Cocktail\",\"serial\":\"Unknown\",\"model\":\"Cocktail\",\"brand\":\"Cocktail\"}"
        });

        return {requestHeaders: details.requestHeaders};
    },
    {
        urls: [
            "https://fapi.molotov.tv/*",
            "https://multi-screen.molotov.tv/*",
            "https://jessica.molotov.tv/*"
        ],
        types: ["xmlhttprequest"]
    },
    ["blocking", "requestHeaders"]
);

browser.webRequest.onCompleted.addListener(
    function(details) {
        // User successfully logged in.
        if (details.method == "POST"
            && details.url == "https://fapi.molotov.tv/v3/auth/login"
            && details.statusCode == "200"
            // When the maximum number of registered devices is reached, the
            // login "cookie" is immediately cleared and a device removing
            // dialog is opened. When this happens, we should NOT refresh
            // the page, otherwise the user is immediately redirected to the
            // login page.
            && localStorage.getItem("mltv_session")) {
                browser.tabs.reload();
        }
    },
    {
        urls: [
            "https://fapi.molotov.tv/v3/auth/login"
        ],
        types: ["xmlhttprequest"]
    }
);

browser.browserAction.onClicked.addListener(
    function(details) {
        browser.tabs.create({url: "https://app.molotov.tv/"});
    }
);<|endoftext|>


<commit_before>chrome.runtime.onInstalled.addListener(async ({reason, previousVersion}) => {
	if(
		   reason === "update"
		&& previousVersion !== chrome.runtime.getManifest().version
	) {
		chrome.windows.create({url: "changes/changes.html", type: "popup"});
	}

	// Convert legacy option keys
	{
		const legacyOptions = {
			bgColor: "backgroundColor",
			fgColor: "textColor",
			cowMods: "cowModifier"
		};
		const legacyKeys = Object.keys(legacyOptions);
		chrome.storage.sync.get(legacyKeys, function(options) {
			for(const [previous, current] of Object.entries(legacyOptions)) {
				if(previous in options) {
					options[current] = options[previous];
					delete options.previous;
				}
			}
			chrome.storage.sync.remove(legacyKeys, ()=>{});
			chrome.storage.sync.set(options, ()=>{});
		}
	}

	// Save defaults options
	{
		const defaultOptions = {};
		const optionsInfo = await fetchJSON("options/options.json");
		for(const [option, data] of Object.entries(optionsInfo)) {
			defaultOptions[option] = data.default;
		}
		await chrome.storage.sync.get(defaultOptions, async function(options) {
			chrome.storage.sync.set(options, ()=>{});
		};
	}
});
<commit_msg>fix parens<commit_after>chrome.runtime.onInstalled.addListener(async ({reason, previousVersion}) => {
	if(
		   reason === "update"
		&& previousVersion !== chrome.runtime.getManifest().version
	) {
		chrome.windows.create({url: "changes/changes.html", type: "popup"});
	}

	// Convert legacy option keys
	{
		const legacyOptions = {
			bgColor: "backgroundColor",
			fgColor: "textColor",
			cowMods: "cowModifier"
		};
		const legacyKeys = Object.keys(legacyOptions);
		chrome.storage.sync.get(legacyKeys, function(options) {
			for(const [previous, current] of Object.entries(legacyOptions)) {
				if(previous in options) {
					options[current] = options[previous];
					delete options.previous;
				}
			}
			chrome.storage.sync.remove(legacyKeys, ()=>{});
			chrome.storage.sync.set(options, ()=>{});
		})
	}

	// Save defaults options
	{
		const defaultOptions = {};
		const optionsInfo = await fetchJSON("options/options.json");
		for(const [option, data] of Object.entries(optionsInfo)) {
			defaultOptions[option] = data.default;
		}
		await chrome.storage.sync.get(defaultOptions, async function(options) {
			chrome.storage.sync.set(options, ()=>{});
		});
	}
});
<|endoftext|>