<commit_before>/* Copyright 2015 Tosho Toshev

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License. */

(function() {
	var cloudFlarePurge = {
		currentUrl : null,
		currentTabId : null,

		init : function() {
			var owner = this;
			$("#purgeButton").on("click", function(e) {
				e.preventDefault;
				owner.purgeBtnClick();
			});

		},

		purgeBtnClick : function() {
			var owner = this;
			var queryInfo = {
				active : true
			};

			chrome.tabs.query(queryInfo, function(tabs) {
				var tab = tabs[0];

				if(tab.url !== undefined) {
					owner.currentUrl = tab.url;
				}

				owner.currentTabId = tab.id;
				var regex = /^[a-zA-Z0-9]*\./i;
				var domain = owner.getDomain(owner.currentUrl);
			    domain = domain.replace(domain.match(regex)[0], "");
			    owner.getCloudFlareZoneId(domain, $.proxy(owner.purgeCloudFlareUrls, owner));
			});
		},

		getCloudFlareZoneId: function(domain, callback) {
			var owner = this;
			$("#purgeButton").attr("class", "loading");
			chrome.storage.sync.get({
				tag: "options",
                key: null,
                email: null
			},

			function(settings) {
            	$.ajax({
					url: "https://api.cloudflare.com/client/v4/zones?name=" + domain + "&status=active",
					beforeSend: function(xhr) {
				        xhr.setRequestHeader('x-auth-Email', settings.email);
						xhr.setRequestHeader('x-auth-key', settings.key);
				    },
					crossDomain: true,
					contentType: "application/json",
					dataType: "json",
					success: function(data) {
						if(data.success == true && data.result[0]) {
							callback([owner.currentUrl], data.result[0].id, settings);
							return;
						}

						$("#purgeButton").attr("class", "");
						$("#status").attr("class", "error");
						owner.setStatusMessage("#status", "PURGE FAILED", 3000);
					},
					error: function(err) {
						$("#purgeButton").attr("class", "");
						$("#status").attr("class", "error");
						owner.setStatusMessage("#status", "PURGE FAILED", 3000);
					}
				});
			});
		},

		purgeCloudFlareUrls: function(urls, zoneId, settings){
			var owner = this;

			$.ajax({
					url: "https://api.cloudflare.com/client/v4/zones/" + zoneId + "/purge_cache",
					method: "DELETE",
					beforeSend: function(xhr) {
				        xhr.setRequestHeader('x-auth-Email', settings.email);
						xhr.setRequestHeader('x-auth-key', settings.key);
				    },
					data: JSON.stringify({
						"files": urls
					}),
					crossDomain: true,
					contentType: "application/json",
					success: function(data) {
						if(data.success == true && data.result) {
							owner.onPurgeSuccess(data.result.id);
							return;
						}

						$("#purgeButton").attr("class", "");
						$("#status").attr("class", "error");
						owner.setStatusMessage("#status", "PURGE FAILED", 3000);
					},
					error: function(err) {
						$("#purgeButton").attr("class", "");
						$("#status").attr("class", "error");
						owner.setStatusMessage("#status", "PURGE FAILED", 3000);
					}
				});
		},
		onPurgeSuccess : function(id) {
			$("#purgeButton").attr("class", "");
			$("#status").attr("class", "success");
			this.setStatusMessage("#status", "SUCCESS", 3000);
			if($("#refresh").is(":checked")) {
				chrome.tabs.reload(this.currentTabId, { bypassCache : true });
			}
		},

		setStatusMessage: function(element, message, timeout) {
			$(element).text(message);
			setTimeout(function() {
                $(element).text("");
            }, timeout);
		},

		getDomain : function(url) {
			   var domain;
			    if (url.indexOf("://") > -1) {
			        domain = url.split('/')[2];
			    }
			    else {
			        domain = url.split('/')[0];
			    }

			    domain = domain.split(':')[0];

			    return domain;
		}
	};

	cloudFlarePurge.init();
})();<commit_msg>A fix for the current tab issue<commit_after>/* Copyright 2015 Tosho Toshev

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License. */

(function() {
	var cloudFlarePurge = {
		currentUrl : null,
		currentTabId : null,

		init : function() {
			var owner = this;
			$("#purgeButton").on("click", function(e) {
				e.preventDefault;
				owner.purgeBtnClick();
			});

		},

		purgeBtnClick : function() {
			var owner = this;
			var queryInfo = {
				active : true,
				currentWindow: true
			};

			chrome.tabs.query(queryInfo, function(tabs) {
				var tab = tabs[0];

				if(tab.url !== undefined) {
					owner.currentUrl = tab.url;
				}

				owner.currentTabId = tab.id;
				var regex = /^[a-zA-Z0-9]*\./i;
				var domain = owner.getDomain(owner.currentUrl);
			    domain = domain.replace(domain.match(regex)[0], "");
			    owner.getCloudFlareZoneId(domain, $.proxy(owner.purgeCloudFlareUrls, owner));
			});
		},

		getCloudFlareZoneId: function(domain, callback) {
			var owner = this;
			$("#purgeButton").attr("class", "loading");
			chrome.storage.sync.get({
				tag: "options",
                key: null,
                email: null
			},

			function(settings) {
            	$.ajax({
					url: "https://api.cloudflare.com/client/v4/zones?name=" + domain + "&status=active",
					beforeSend: function(xhr) {
				        xhr.setRequestHeader('x-auth-Email', settings.email);
						xhr.setRequestHeader('x-auth-key', settings.key);
				    },
					crossDomain: true,
					contentType: "application/json",
					dataType: "json",
					success: function(data) {
						if(data.success == true && data.result[0]) {
							callback([owner.currentUrl], data.result[0].id, settings);
							return;
						}

						$("#purgeButton").attr("class", "");
						$("#status").attr("class", "error");
						owner.setStatusMessage("#status", "PURGE FAILED", 3000);
					},
					error: function(err) {
						$("#purgeButton").attr("class", "");
						$("#status").attr("class", "error");
						owner.setStatusMessage("#status", "PURGE FAILED", 3000);
					}
				});
			});
		},

		purgeCloudFlareUrls: function(urls, zoneId, settings){
			var owner = this;

			$.ajax({
					url: "https://api.cloudflare.com/client/v4/zones/" + zoneId + "/purge_cache",
					method: "DELETE",
					beforeSend: function(xhr) {
				        xhr.setRequestHeader('x-auth-Email', settings.email);
						xhr.setRequestHeader('x-auth-key', settings.key);
				    },
					data: JSON.stringify({
						"files": urls
					}),
					crossDomain: true,
					contentType: "application/json",
					success: function(data) {
						if(data.success == true && data.result) {
							owner.onPurgeSuccess(data.result.id);
							return;
						}

						$("#purgeButton").attr("class", "");
						$("#status").attr("class", "error");
						owner.setStatusMessage("#status", "PURGE FAILED", 3000);
					},
					error: function(err) {
						$("#purgeButton").attr("class", "");
						$("#status").attr("class", "error");
						owner.setStatusMessage("#status", "PURGE FAILED", 3000);
					}
				});
		},
		onPurgeSuccess : function(id) {
			$("#purgeButton").attr("class", "");
			$("#status").attr("class", "success");
			this.setStatusMessage("#status", "SUCCESS", 3000);
			if($("#refresh").is(":checked")) {
				chrome.tabs.reload(this.currentTabId, { bypassCache : true });
			}
		},

		setStatusMessage: function(element, message, timeout) {
			$(element).text(message);
			setTimeout(function() {
                $(element).text("");
            }, timeout);
		},

		getDomain : function(url) {
			   var domain;
			    if (url.indexOf("://") > -1) {
			        domain = url.split('/')[2];
			    }
			    else {
			        domain = url.split('/')[0];
			    }

			    domain = domain.split(':')[0];

			    return domain;
		}
	};

	cloudFlarePurge.init();
})();<|endoftext|>


<commit_before>/*
 * LocalStore key => "gh_search_text"
 *
 * FORMAT
 *      "gh_search_text" : {
 *          "settings" : {
 *              "background_match" : "COLOR"
 *          },
 *          "text_search" : {
 *              "value" :  "SEARCH_STRING",
 *              "matches" : [ "FILENAME", "FILENAME" ]
 *          }
 *      }
 *
 * */

function init()
{
    // check if background color is stored
    if (window.localStorage.getItem("gh_text_search") === null)
    {
        // create it empty
        window.localStorage.setItem("gh_text_search", "");
        var data = {};
        // init background color if it not exists
        data["settings"] = {"background_color": "#fff0b3"};
        data["text_search"] = {};
        // TODO add history
        window.localStorage.setItem("gh_text_search" , JSON.stringify(data))

    }

}

colorPicker.addEventListener("change", watchColorPicker, false);

<commit_msg>move code to background<commit_after>
colorPicker.addEventListener("change", watchColorPicker, false);

<|endoftext|>


<commit_before>// Copyright (c) 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * Get the current URL.
 *
 * @param {function(string)} callback - called when the URL of the current tab
 *   is found.
 */
function getCurrentTabUrl(callback) {
  // Query filter to be passed to chrome.tabs.query - see
  // https://developer.chrome.com/extensions/tabs#method-query
  var queryInfo = {
    active: true,
    currentWindow: true
  };

  chrome.tabs.query(queryInfo, function(tabs) {
    // chrome.tabs.query invokes the callback with a list of tabs that match the
    // query. When the popup is opened, there is certainly a window and at least
    // one tab, so we can safely assume that |tabs| is a non-empty array.
    // A window can only have one active tab at a time, so the array consists of
    // exactly one tab.
    var tab = tabs[0];

    // A tab is a plain object that provides information about the tab.
    // See https://developer.chrome.com/extensions/tabs#type-Tab
    var url = tab.url;

    // tab.url is only available if the "activeTab" permission is declared.
    // If you want to see the URL of other tabs (e.g. after removing active:true
    // from |queryInfo|), then the "tabs" permission is required to see their
    // "url" properties.
    console.assert(typeof url == 'string', 'tab.url should be a string');

    callback(url);
  });

  // Most methods of the Chrome extension APIs are asynchronous. This means that
  // you CANNOT do something like this:
  //
  // var url;
  // chrome.tabs.query(queryInfo, function(tabs) {
  //   url = tabs[0].url;
  // });
  // alert(url); // Shows "undefined", because chrome.tabs.query is async.
}

/**
 * @param {string} searchTerm - Search term for Google Image search.
 * @param {function(string,number,number)} callback - Called when an image has
 *   been found. The callback gets the URL, width and height of the image.
 * @param {function(string)} errorCallback - Called when the image is not found.
 *   The callback gets a string that describes the failure reason.
 */
function getImageUrl(searchTerm, callback, errorCallback) {
  // Google image search - 100 searches per day.
  // https://developers.google.com/image-search/
  var searchUrl = 'https://ajax.googleapis.com/ajax/services/search/images' +
    '?v=1.0&q=' + encodeURIComponent(searchTerm);
  var x = new XMLHttpRequest();
  x.open('GET', searchUrl);
  // The Google image search API responds with JSON, so let Chrome parse it.
  x.responseType = 'json';
  x.onload = function() {
    // Parse and process the response from Google Image Search.
    var response = x.response;
    if (!response || !response.responseData || !response.responseData.results ||
        response.responseData.results.length === 0) {
      errorCallback('No response from Google Image search!');
      return;
    }
    var firstResult = response.responseData.results[0];
    // Take the thumbnail instead of the full image to get an approximately
    // consistent image size.
    var imageUrl = firstResult.tbUrl;
    var width = parseInt(firstResult.tbWidth);
    var height = parseInt(firstResult.tbHeight);
    console.assert(
        typeof imageUrl == 'string' && !isNaN(width) && !isNaN(height),
        'Unexpected respose from the Google Image Search API!');
    callback(imageUrl, width, height);
  };
  x.onerror = function() {
    errorCallback('Network error.');
  };
  x.send();
}

function renderStatus(statusText) {
  document.getElementById('status').textContent = statusText;
}

document.addEventListener('DOMContentLoaded', function() {
  getCurrentTabUrl(function(url) {
    // Put the image URL in Google search.
    renderStatus('Performing Google Image search for ' + url);

    getImageUrl(url, function(imageUrl, width, height) {

      renderStatus('Search term: ' + url + '\n' +
          'Google image search result: ' + imageUrl);
      var imageResult = document.getElementById('image-result');
      // Explicitly set the width/height to minimize the number of reflows. For
      // a single image, this does not matter, but if you're going to embed
      // multiple external images in your page, then the absence of width/height
      // attributes causes the popup to resize multiple times.
      imageResult.width = width;
      imageResult.height = height;
      imageResult.src = imageUrl;
      imageResult.hidden = false;

    }, function(errorMessage) {
      renderStatus('Cannot display image. ' + errorMessage);
    });
  });
});
<commit_msg>removed much un-needed documentation in file<commit_after>// Author: Conor Jackson cxj3042@rit.edu

/**
 * Get the current URL.
 *
 * @param {function(string)} callback - called when the URL of the current tab
 *   is found.
 */
function getCurrentTabUrl(callback) {
  // Query filter to be passed to chrome.tabs.query - see
  // https://developer.chrome.com/extensions/tabs#method-query
  var queryInfo = {
    active: true,
    currentWindow: true
  };

  chrome.tabs.query(queryInfo, function(tabs) {
    // chrome.tabs.query invokes the callback with a list of tabs that match the
    // query. When the popup is opened, there is certainly a window and at least
    // one tab, so we can safely assume that |tabs| is a non-empty array.
    // A window can only have one active tab at a time, so the array consists of
    // exactly one tab.
    var tab = tabs[0];

    // A tab is a plain object that provides information about the tab.
    // See https://developer.chrome.com/extensions/tabs#type-Tab
    var url = tab.url;

    // tab.url is only available if the "activeTab" permission is declared.
    // If you want to see the URL of other tabs (e.g. after removing active:true
    // from |queryInfo|), then the "tabs" permission is required to see their
    // "url" properties.
    console.assert(typeof url == 'string', 'tab.url should be a string');

    callback(url);
  });
}

/**
 * @param {string} searchTerm - Search term for Google Image search.
 * @param {function(string,number,number)} callback - Called when an image has
 *   been found. The callback gets the URL, width and height of the image.
 * @param {function(string)} errorCallback - Called when the image is not found.
 *   The callback gets a string that describes the failure reason.
 */
function getImageUrl(searchTerm, callback, errorCallback) {
  // Google image search - 100 searches per day.
  // https://developers.google.com/image-search/
  var searchUrl = 'https://ajax.googleapis.com/ajax/services/search/images' +
    '?v=1.0&q=' + encodeURIComponent(searchTerm);
  var x = new XMLHttpRequest();
  x.open('GET', searchUrl);
  // The Google image search API responds with JSON, so let Chrome parse it.
  x.responseType = 'json';
  x.onload = function() {
    // Parse and process the response from Google Image Search.
    var response = x.response;
    if (!response || !response.responseData || !response.responseData.results ||
        response.responseData.results.length === 0) {
      errorCallback('No response from Google Image search!');
      return;
    }
    var firstResult = response.responseData.results[0];
    // Take the thumbnail instead of the full image to get an approximately
    // consistent image size.
    var imageUrl = firstResult.tbUrl;
    var width = parseInt(firstResult.tbWidth);
    var height = parseInt(firstResult.tbHeight);
    console.assert(
        typeof imageUrl == 'string' && !isNaN(width) && !isNaN(height),
        'Unexpected respose from the Google Image Search API!');
    callback(imageUrl, width, height);
  };
  x.onerror = function() {
    errorCallback('Network error.');
  };
  x.send();
}

function renderStatus(statusText) {
  document.getElementById('status').textContent = statusText;
}

document.addEventListener('DOMContentLoaded', function() {
  getCurrentTabUrl(function(url) {
    // Put the image URL in Google search.
    renderStatus('Performing Google Image search for ' + url);

    getImageUrl(url, function(imageUrl, width, height) {

      renderStatus('Search term: ' + url + '\n' +
          'Google image search result: ' + imageUrl);
      var imageResult = document.getElementById('image-result');
      // Explicitly set the width/height to minimize the number of reflows. For
      // a single image, this does not matter, but if you're going to embed
      // multiple external images in your page, then the absence of width/height
      // attributes causes the popup to resize multiple times.
      imageResult.width = width;
      imageResult.height = height;
      imageResult.src = imageUrl;
      imageResult.hidden = false;

    }, function(errorMessage) {
      renderStatus('Cannot display image. ' + errorMessage);
    });
  });
});
<|endoftext|>


<commit_before>// Traverse the bookmark tree, and print the folder and nodes.
function dumpBookmarks(query) {
  var bookmarkTreeNodes = chrome.bookmarks.getTree(
    function(bookmarkTreeNodes) {
      $('#bookmarks').append(dumpTreeNodes(bookmarkTreeNodes, query));
    });
}

function dumpTreeNodes(bookmarkNodes, query) {
  var list = $('
');
  var i;
  for (i = 0; i < bookmarkNodes.length; i++) {
    list.append(dumpNode(bookmarkNodes[i], query));
  }
  return list;
}

function dumpNode(bookmarkNode, query) {
  if (bookmarkNode.title) {
    if (query && !bookmarkNode.children) {
      if (String(bookmarkNode.title).indexOf(query) == -1) {
        return $('');
      }
    }
    var anchor = $('');
    anchor.attr('href', bookmarkNode.url);
    anchor.text(bookmarkNode.title);
    /*
     * When clicking on a bookmark in the extension, a new tab is fired with
     * the bookmark url.
     */
    anchor.click(function() {
      chrome.tabs.create({url: bookmarkNode.url});
    });
    var span = $('');
    span.append(anchor);
  }
  var li = $(bookmarkNode.title ? '
' : '
').append(span);
  if (bookmarkNode.children && bookmarkNode.children.length > 0) {
    li.append(dumpTreeNodes(bookmarkNode.children, query));
  }
  return li;
}

function save() {
  var bookmarks = 0;
  chrome.storage.local.set({'bookmarks': bookmarks});
}

function load() {
  chrome.storage.local.get('bookmarks', function (result) {
    alert(result.bookmarks);
  });
}

document.addEventListener('DOMContentLoaded', function () {
  dumpBookmarks();
  save();
  load();
});
<commit_msg>Add functions which check how many time did user read bookmarks with api<commit_after>// Traverse the bookmark tree, and print the folder and nodes.
function dumpBookmarks(query) {
  var bookmarkTreeNodes = chrome.bookmarks.getTree(
    function(bookmarkTreeNodes) {
      $('#bookmarks').append(dumpTreeNodes(bookmarkTreeNodes, query));
    });
}

function dumpTreeNodes(bookmarkNodes, query) {
  var list = $('
');
  var i;
  for (i = 0; i < bookmarkNodes.length; i++) {
    list.append(dumpNode(bookmarkNodes[i], query));
  }
  return list;
}

function dumpNode(bookmarkNode, query) {
  if (bookmarkNode.title) {
    if (query && !bookmarkNode.children) {
      if (String(bookmarkNode.title).indexOf(query) == -1) {
        return $('');
      }
    }
    var anchor = $('');
    anchor.attr('href', bookmarkNode.url);
    anchor.text(bookmarkNode.title);
    /*
     * When clicking on a bookmark in the extension, a new tab is fired with
     * the bookmark url.
     */
    anchor.click(function() {
      chrome.storage.local.get(bookmarkNode.title, function(result) {
        if (result[bookmarkNode.title] != undefined) {
	  var obj = {};
	  obj[bookmarkNode.title] = result[bookmarkNode.title] + 1;
          chrome.storage.local.set(obj);
	}
      });
      chrome.tabs.create({url: bookmarkNode.url});
    });
    var span = $('');
    span.append(anchor);
    chrome.storage.local.get(bookmarkNode.title, function(result) {
      if (result[bookmarkNode.title]) {
        span.append(result[bookmarkNode.title]);
      } else {
	var obj = {}
	obj[bookmarkNode.title] = 0
        chrome.storage.local.set(obj);
        span.append(0);
      }
    });
  }
  var li = $(bookmarkNode.title ? '
' : '
').append(span);
  if (bookmarkNode.children && bookmarkNode.children.length > 0) {
    li.append(dumpTreeNodes(bookmarkNode.children, query));
  }
  return li;
}

document.addEventListener('DOMContentLoaded', function () {
  dumpBookmarks();
});
<|endoftext|>


<commit_before>var monthNames = ["January", "February", "March", "April", "May", "June",
  "July", "August", "September", "October", "November", "December"
];

var g_month_name = monthNames[new Date().getMonth()];
var g_db = new TimesheetDb();
var g_current_month_charges = [];

function htmlEscapeId(str) {
    return str
        .trim()
        .replace(/\s/g, '-')
        .replace(/[^A-Za-z0-9\-]/g, '');
}

function updateHours(event) {
    var allowed_hours = {}

    for (var i = 0; i < g_current_month_charges.length; i++) {
        var key_as_id = htmlEscapeId(g_current_month_charges[i]);
        var hours = parseFloat($("#" + key_as_id).val());
        var percent = parseFloat($("#" + key_as_id + "-percentage").val());
        if (isNaN(percent)) {
            percent = 10;
        }
        if (isNaN(hours)) {
            hours = 0;
        }
        allowed_hours[g_current_month_charges[i]] = {hours:hours, hidden:false, percent:percent};
    }

    $(":checked").each(function(index, item) {
        for (var i = 0; i < g_current_month_charges.length; i++) {
            var key_as_id = htmlEscapeId(g_current_month_charges[i]);
            if ($(item).attr("id") == (key_as_id + "-checkbox")) {
                allowed_hours[g_current_month_charges[i]].hidden = true;
            }
        }
    });

    console.debug("Update hours: ", allowed_hours);

    g_db.update_allowed_hours(allowed_hours, function() {
        refresh();
    });
}

function refresh() {
    // Clear out before refresh
    $("#deltrek").empty();

    g_db.load_month(function(items) {
        if(!items || !Object.keys(items).length) {
            console.debug("Nothing in database yet. Display help");

            $("#deltrek").prepend(
                $("
").text(
                    "There is nothing in the Deltrek database yet. Start by logging into deltek and opening a timesheet. " +
                    "Check back here once you've opened a timesheet. If there are any previous timesheets for this month that " +
                    "you would like included in the totals, you will need to manually open each one (without using the navigation arrows " +
                    "within deltek)")
            );
        }

        // Calculate current monthly totals for each charge number
        var charge_to_total = {};
        for (key in items) {
            var charge_array = items[key];
            for (var i = 0; i < charge_array.length; i++) {
                if (!(charge_array[i].title in charge_to_total)) {
                    charge_to_total[charge_array[i].title] = 0;
                }
                charge_to_total[charge_array[i].title] += charge_array[i].value;
            }
        }

        g_current_month_charges = Object.keys(charge_to_total);

        g_db.load_allowed_hours(function(items) {
            // Figure out if we should show hidden charges
            var show_hidden = $("#show-hidden").is(":checked");

            console.debug("Allowed hours: ", items);
            console.debug("Showing hidden? ", show_hidden);

            for (key in charge_to_total) {
                var hours = charge_to_total[key];
                var allowed_hours = 0;
                var percent = 10;
                var hidden = false;
                var key_as_id = htmlEscapeId(key);
                if (items && key in items) {
                    allowed_hours = items[key].hours;
                    hidden = items[key].hidden;
                    percent = items[key].percent;
                }

                // Don't display if it's hidden and we're not showing hidden entries
                var checked;
                if (hidden) {
                    checked = "checked";
                } else {
                    checked = "";
                }

                var display;
                if (hidden && !show_hidden) {
                    display = "style='display:none;'";
                } else {
                    display = "";
                }

                var panelType = '';
                if (!allowed_hours && hours > 0) {
                    panelType = 'bg-danger';
                } else if(allowed_hours > 0) {
                    var current_percent = (hours / allowed_hours) * 100.0;
                    var lower_bound = 100 - percent;
                    var upper_bound = 100 + percent;
                    if (current_percent < lower_bound) {
                        panelType = 'bg-warning';
                    } else if (current_percent > upper_bound) {
                        panelType = 'bg-danger';
                    } else if (current_percent > lower_bound && current_percent < upper_bound) {
                        panelType = 'bg-success';
                    }
                }

                $("#deltrek").prepend(
                    $("
").append(
                        $("
").append(
                            $("
").append(
                                $("<button class='btn btn-link text-white' type='button' data-toggle='collapse' data-target='#" + key_as_id + "-collapse' aria-controls='" + key_as_id + "-collapse'>")
                                .text(key + " (Worked: " + hours + "/" + allowed_hours + ")")
                            )
                        )
                    ).append(
                        $("
").append(
                            $("
").append(
                                $("").html(
                                    "Allowed hours: <input type='number' step='0.5' min='0' id='" + key_as_id + "' value='" + allowed_hours + "'>"
                                )
                            ).append(
                                $("").html(
                                    "Plus/Minus: <input type='number' step='1' min='0' id='" + key_as_id + "-percentage' value='" + percent + "'>%"
                                )
                            ).append(
                                $("").html("  Hide this line").prepend($("<input id='" + key_as_id + "-checkbox' type='checkbox' " + checked + " >"))
                            )
                        )
                    )
                );
                // Enable the collapsibles
                $('#' + key_as_id + '-collapse').collapse({toggle: false});
            }
        });
    });
}

$(document).ready(function() {
    $('#save').click(updateHours);

    $("#show-hidden").click(function() {
        refresh();
    });

    $("#month-title").text("Hours for " + g_month_name);

    // Don't show the 'Go to Deltek' link if we're on the Deltek page
    chrome.tabs.query({
        active: true,
        url: "*://*.deltekenterprise.com/*"
    }, function(result) {
        console.debug("Matching tabs: ", result);

        if (result && result.length) {
            console.debug("We're on the deltek page, don't show 'Go to Deltek' link");
            $("#go-to-deltek").hide();
        }
    });

    refresh();
});<commit_msg>Fix for text color on unstyled cards<commit_after>var monthNames = ["January", "February", "March", "April", "May", "June",
  "July", "August", "September", "October", "November", "December"
];

var g_month_name = monthNames[new Date().getMonth()];
var g_db = new TimesheetDb();
var g_current_month_charges = [];

function htmlEscapeId(str) {
    return str
        .trim()
        .replace(/\s/g, '-')
        .replace(/[^A-Za-z0-9\-]/g, '');
}

function updateHours(event) {
    var allowed_hours = {}

    for (var i = 0; i < g_current_month_charges.length; i++) {
        var key_as_id = htmlEscapeId(g_current_month_charges[i]);
        var hours = parseFloat($("#" + key_as_id).val());
        var percent = parseFloat($("#" + key_as_id + "-percentage").val());
        if (isNaN(percent)) {
            percent = 10;
        }
        if (isNaN(hours)) {
            hours = 0;
        }
        allowed_hours[g_current_month_charges[i]] = {hours:hours, hidden:false, percent:percent};
    }

    $(":checked").each(function(index, item) {
        for (var i = 0; i < g_current_month_charges.length; i++) {
            var key_as_id = htmlEscapeId(g_current_month_charges[i]);
            if ($(item).attr("id") == (key_as_id + "-checkbox")) {
                allowed_hours[g_current_month_charges[i]].hidden = true;
            }
        }
    });

    console.debug("Update hours: ", allowed_hours);

    g_db.update_allowed_hours(allowed_hours, function() {
        refresh();
    });
}

function refresh() {
    // Clear out before refresh
    $("#deltrek").empty();

    g_db.load_month(function(items) {
        if(!items || !Object.keys(items).length) {
            console.debug("Nothing in database yet. Display help");

            $("#deltrek").prepend(
                $("
").text(
                    "There is nothing in the Deltrek database yet. Start by logging into deltek and opening a timesheet. " +
                    "Check back here once you've opened a timesheet. If there are any previous timesheets for this month that " +
                    "you would like included in the totals, you will need to manually open each one (without using the navigation arrows " +
                    "within deltek)")
            );
        }

        // Calculate current monthly totals for each charge number
        var charge_to_total = {};
        for (key in items) {
            var charge_array = items[key];
            for (var i = 0; i < charge_array.length; i++) {
                if (!(charge_array[i].title in charge_to_total)) {
                    charge_to_total[charge_array[i].title] = 0;
                }
                charge_to_total[charge_array[i].title] += charge_array[i].value;
            }
        }

        g_current_month_charges = Object.keys(charge_to_total);

        g_db.load_allowed_hours(function(items) {
            // Figure out if we should show hidden charges
            var show_hidden = $("#show-hidden").is(":checked");

            console.debug("Allowed hours: ", items);
            console.debug("Showing hidden? ", show_hidden);

            for (key in charge_to_total) {
                var hours = charge_to_total[key];
                var allowed_hours = 0;
                var percent = 10;
                var hidden = false;
                var key_as_id = htmlEscapeId(key);
                if (items && key in items) {
                    allowed_hours = items[key].hours;
                    hidden = items[key].hidden;
                    percent = items[key].percent;
                }

                // Don't display if it's hidden and we're not showing hidden entries
                var checked;
                if (hidden) {
                    checked = "checked";
                } else {
                    checked = "";
                }

                var display;
                if (hidden && !show_hidden) {
                    display = "style='display:none;'";
                } else {
                    display = "";
                }

                var panelType = '';
                var textColor = '';
                if (!allowed_hours && hours > 0) {
                    panelType = 'bg-danger';
                } else if(allowed_hours > 0) {
                    var current_percent = (hours / allowed_hours) * 100.0;
                    var lower_bound = 100 - percent;
                    var upper_bound = 100 + percent;
                    if (current_percent < lower_bound) {
                        panelType = 'bg-warning';
                    } else if (current_percent > upper_bound) {
                        panelType = 'bg-danger';
                    } else if (current_percent > lower_bound && current_percent < upper_bound) {
                        panelType = 'bg-success';
                    }
                }

                if (panelType.length) {
                    textColor = 'text-white';
                }

                $("#deltrek").prepend(
                    $("
").append(
                        $("
").append(
                            $("
").append(
                                $("<button class='btn btn-link " + textColor + "' type='button' data-toggle='collapse' data-target='#" + key_as_id + "-collapse' aria-controls='" + key_as_id + "-collapse'>")
                                .text(key + " (Worked: " + hours + "/" + allowed_hours + ")")
                            )
                        )
                    ).append(
                        $("
").append(
                            $("
").append(
                                $("").html(
                                    "Allowed hours: <input type='number' step='0.5' min='0' id='" + key_as_id + "' value='" + allowed_hours + "'>"
                                )
                            ).append(
                                $("").html(
                                    "Plus/Minus: <input type='number' step='1' min='0' id='" + key_as_id + "-percentage' value='" + percent + "'>%"
                                )
                            ).append(
                                $("").html("  Hide this line").prepend($("<input id='" + key_as_id + "-checkbox' type='checkbox' " + checked + " >"))
                            )
                        )
                    )
                );
                // Enable the collapsibles
                $('#' + key_as_id + '-collapse').collapse({toggle: false});
            }
        });
    });
}

$(document).ready(function() {
    $('#save').click(updateHours);

    $("#show-hidden").click(function() {
        refresh();
    });

    $("#month-title").text("Hours for " + g_month_name);

    // Don't show the 'Go to Deltek' link if we're on the Deltek page
    chrome.tabs.query({
        active: true,
        url: "*://*.deltekenterprise.com/*"
    }, function(result) {
        console.debug("Matching tabs: ", result);

        if (result && result.length) {
            console.debug("We're on the deltek page, don't show 'Go to Deltek' link");
            $("#go-to-deltek").hide();
        }
    });

    refresh();
});<|endoftext|>


<commit_before>/*global matchRule,loadRules,saveRules,loadCloseSetting*/
angular.module('popup', []).controller('popup', popup);

function popup($scope) {
    var currentTab;
    chrome.tabs.query({active: true, currentWindow: true}, (currentTabs) => {
        currentTab = currentTabs[0];
        var urlWithoutHash = currentTab.url.split('#')[0];
        var baseUrl = urlWithoutHash.split('?')[0];

        chrome.tabs.query({ url: baseUrl + '*' }, (tabs) => {
            $scope.tabs = tabs;
            var matches = _.reject(tabs, {id: currentTab.id});
            $scope.exacts = _.filter(matches, {url: currentTab.url});
            var matchesWithoutHash = _.filter(matches, (tab) =>
                tab.url.split('#')[0] === urlWithoutHash
            );
            $scope.matchesOnlyBase = _.difference(matches, matchesWithoutHash);
            $scope.matchesExceptHash = _.difference(matchesWithoutHash, $scope.exacts);

            var urlParts = baseUrl.split('/');
            $scope.rule = {};
            if(baseUrl.indexOf('://') > -1) {
                $scope.rule.url = urlParts[0] + '//' + urlParts[2];
            } else {
                $scope.rule.url = urlParts[0];
            }
            $scope.rule.match = 'hash';
            $scope.$apply();

            matchRule(baseUrl, (rule) => {
                if(rule) {
                    $scope.rule = rule;
                    $scope.savedRule = _.clone(rule);
                }
                // loading done: safe to show form for user input
                $scope.showRuleForm = true;
                $scope.$apply();
            });
        });
    });

    loadCloseSetting((close) => {
        $scope.closeTab = close;
        $scope.$apply();
    });

    $scope.goto = (tab) => {
        if($scope.closeTab) {
            chrome.tabs.remove(currentTab.id);
        }
        chrome.tabs.update(tab.id, {active:true});
        chrome.windows.update(tab.windowId, {focused:true});
    };

    $scope.pluralizeMsg = (count) => {
        if(count == 1) {
            return 'There is one other tab';
        }
        if(count > 1) {
            return 'There are ' + count + ' tabs';
        }
        return '';
    };

    $scope.saveRule = () => {
        loadRules((rules) => {
            if($scope.savedRule) {
                delete rules[$scope.savedRule.url];
            }
            rules[$scope.rule.url] = $scope.rule.match;
            saveRules(rules);
            $scope.savedRule = _.clone($scope.rule);
            $scope.$apply();
        });
    };

    $scope.showOptions = () => {
        chrome.runtime.openOptionsPage();
    };
}
<commit_msg>add currentTab to scope<commit_after>/*global matchRule,loadRules,saveRules,loadCloseSetting*/
angular.module('popup', []).controller('popup', popup);

function popup($scope) {
    chrome.tabs.query({active: true, currentWindow: true}, (currentTabs) => {
        $scope.currentTab = currentTabs[0];
        var urlWithoutHash = $scope.currentTab.url.split('#')[0];
        var baseUrl = urlWithoutHash.split('?')[0];

        chrome.tabs.query({ url: baseUrl + '*' }, (tabs) => {
            $scope.tabs = tabs;
            var matches = _.reject(tabs, {id: $scope.currentTab.id});
            $scope.exacts = _.filter(matches, {url: $scope.currentTab.url});
            var matchesWithoutHash = _.filter(matches, (tab) =>
                tab.url.split('#')[0] === urlWithoutHash
            );
            $scope.matchesOnlyBase = _.difference(matches, matchesWithoutHash);
            $scope.matchesExceptHash = _.difference(matchesWithoutHash, $scope.exacts);

            var urlParts = baseUrl.split('/');
            $scope.rule = {};
            if(baseUrl.indexOf('://') > -1) {
                $scope.rule.url = urlParts[0] + '//' + urlParts[2];
            } else {
                $scope.rule.url = urlParts[0];
            }
            $scope.rule.match = 'hash';
            $scope.$apply();

            matchRule(baseUrl, (rule) => {
                if(rule) {
                    $scope.rule = rule;
                    $scope.savedRule = _.clone(rule);
                }
                // loading done: safe to show form for user input
                $scope.showRuleForm = true;
                $scope.$apply();
            });
        });
    });

    loadCloseSetting((close) => {
        $scope.closeTab = close;
        $scope.$apply();
    });

    $scope.goto = (tab) => {
        if($scope.closeTab) {
            chrome.tabs.remove($scope.currentTab.id);
        }
        chrome.tabs.update(tab.id, {active:true});
        chrome.windows.update(tab.windowId, {focused:true});
    };

    $scope.pluralizeMsg = (count) => {
        if(count == 1) {
            return 'There is one other tab';
        }
        if(count > 1) {
            return 'There are ' + count + ' tabs';
        }
        return '';
    };

    $scope.saveRule = () => {
        loadRules((rules) => {
            if($scope.savedRule) {
                delete rules[$scope.savedRule.url];
            }
            rules[$scope.rule.url] = $scope.rule.match;
            saveRules(rules);
            $scope.savedRule = _.clone($scope.rule);
            $scope.$apply();
        });
    };

    $scope.showOptions = () => {
        chrome.runtime.openOptionsPage();
    };
}
<|endoftext|>


<commit_before>Array.prototype.removeConditional = function (comparator) {
    var result = [];
    this.forEach(function (arg) { if (!comparator(arg)) result.push(arg) });
    return result;
};

String.prototype.getHash = function () {
    var arr = this.split('');
    var hash = 0;
    for (var i = 0; i < arr.length; i++) {
        hash += arr[i].charCodeAt(0)
    }
    hash = arr[0] + hash + arr[arr.length - 1];
    hash = hash + hash.length;
    return hash;
}

function Settings() {
    this.AutohideCommentsSpells = [];
    this.AutohideCommentsWithImages = false;
    this.AutohideCommentsWithAudio = false;

    this.Bookmarks = [];

    this.AutohidePostsSpells = [];
    this.AutohideReposts = false;

    this.HideEmodzi = false;

    this.Interval = 0;
}

Settings.prototype.Load = function () {
    $(".comment-autohide")[0].value = window.localStorage["vkscript-comments-spells"] || "";

    $(".post-autohide")[0].value = window.localStorage["vkscript-posts-spells"] || "";
    $("input[name='repost-filter']")[0].checked = JSON.parse(window.localStorage["vkscript-repostshide"]);

    $("input[name='image-filter']")[0].checked = JSON.parse(window.localStorage["vkscript-comments-imageshide"]);
    $("input[name='audio-filter']")[0].checked = JSON.parse(window.localStorage["vkscript-comments-audioshide"]);

    $("input[name='emodji-filter']")[0].checked = JSON.parse(window.localStorage["vkscript-emodji-filter"]);

    this.Bookmarks = JSON.parse(window.localStorage["vkscript-bookmarks"] || "[]");
}

Settings.prototype.SavePosts = function () {
    window.localStorage["vkscript-posts-spells"] = $(".post-autohide")[0].value;
    window.localStorage["vkscript-repostshide"] = $("input[name='repost-filter']")[0].checked;

    var posts_spells = $(".post-autohide")[0].value.split(",").map(function (el) {
        return el.trim().toLowerCase();
    });

    this.AutohidePostsSpells = posts_spells;
    this.AutohideReposts = $("input[name='repost-filter']")[0].checked;

    $(".post").removeClass("hidden");
}

Settings.prototype.SaveComments = function () {
    window.localStorage["vkscript-comments-spells"] = $(".comment-autohide")[0].value;

    window.localStorage["vkscript-comments-imageshide"] = $("input[name='image-filter']")[0].checked;
    window.localStorage["vkscript-comments-audioshide"] = $("input[name='audio-filter']")[0].checked;

    var comments_spells = $(".comment-autohide")[0].value.split(",").map(function (el) {
        return el.trim().toLowerCase();
    });

    this.AutohideCommentsSpells = comments_spells;

    this.AutohideCommentsWithImages = $("input[name='image-filter']")[0].checked;
    this.AutohideCommentsWithAudio = $("input[name='audio-filter']")[0].checked;

    $(".fw_reply, .reply").removeClass("script-hidden");
}

Settings.prototype.SaveDiff = function () {
    window.localStorage["vkscript-emodji-filter"] = $("input[name='emodji-filter']")[0].checked;

    this.HideEmodzi = $("input[name='emodji-filter']")[0].checked;
}

Settings.prototype.AddBookmark = function (url, text) {
    var copies = this.Bookmarks.find(function (x) {
        return x.text.getHash() + x.url.getHash() == text.getHash() + url.getHash()
    });

    if (copies)
        return null;

    var mark = { "url": url, "text": text, "id": "mark-" + ("" + Math.random()).split('.')[1] };

    this.Bookmarks.push(mark);

    return mark;
}

Settings.prototype.DeleteBookmark = function (id) {
    this.Bookmarks = this.Bookmarks.removeConditional(function (mark) {
        return mark.id == id;
    });
}

Settings.prototype.ClearBookmarks = function () {
    this.Bookmarks = [];
}

Settings.prototype.SaveBookmarks = function () {
    window.localStorage["vkscript-bookmarks"] = JSON.stringify(this.Bookmarks);
}

var config;
var currentImage;

$(window).load(function () {
    var wrapper = $('

');
    $('body').append(wrapper);
    $('#vkscript-wrapper').load(chrome.extension.getURL("popup.html"), function () {
        config = new Settings();

        $(".autohide-button").click(function () {
            $("#vkscript-about").addClass("hidden");
            $("#vkscript-diff").addClass("hidden");
            $("#vkscript-posts").addClass("hidden");
            $("#vkscript-bookmarks").addClass("hidden");

            $("#vkscript-comments").toggleClass("hidden");
        });
        $(".about-button").click(function () {
            $("#vkscript-comments").addClass("hidden");
            $("#vkscript-diff").addClass("hidden");
            $("#vkscript-posts").addClass("hidden");
            $("#vkscript-bookmarks").addClass("hidden");

            $("#vkscript-about").toggleClass("hidden");
        });
        $(".diff-button").click(function () {
            $("#vkscript-comments").addClass("hidden");
            $("#vkscript-about").addClass("hidden");
            $("#vkscript-posts").addClass("hidden");
            $("#vkscript-bookmarks").addClass("hidden");

            $("#vkscript-diff").toggleClass("hidden");
        });
        $(".posts-button").click(function () {
            $("#vkscript-comments").addClass("hidden");
            $("#vkscript-about").addClass("hidden");
            $("#vkscript-diff").addClass("hidden");
            $("#vkscript-bookmarks").addClass("hidden");

            $("#vkscript-posts").toggleClass("hidden");
        });
        $(".bookmarks-button").click(function () {
            $("#vkscript-comments").addClass("hidden");
            $("#vkscript-about").addClass("hidden");
            $("#vkscript-diff").addClass("hidden");
            $("#vkscript-posts").addClass("hidden");

            $("#vkscript-bookmarks").toggleClass("hidden");
        });

        $("#comment-autohide-apply").click(function () {
            config.SaveComments();
            ProcessScript();
        });
        $("#diff-apply").click(function () {
            config.SaveDiff();
            ProcessScript();
        });
        $("#post-autohide-apply").click(function () {
            config.SavePosts();
            ProcessScript();
        });

        $("#delete-all-bookmarks").click(function () {
            config.ClearBookmarks();
            RedrawBookmarks();
            config.SaveBookmarks();
        })

        config.Interval = setInterval(ProcessScript, 500);

        config.Load();
        config.SaveComments();
        config.SaveDiff();
        config.SavePosts();

        RedrawBookmarks();
    });
});

function ProcessScript() {
    ProcessComments();
    ProcessImages();
    ProcessDiff();
    ProcessPosts();
    ProcessBookmarks();
}

function ProcessImages() {
    if ($("#pv_actions").length > 0) {
        currentImage = $("#pv_photo img").prop("src");
        if ($(".vkscript-find").length == 0) {
            $("#pv_actions").append($(''));
            $("#pv_actions").append($(''));
            $("#pv_actions").append($(''));
        }
    }
}

function ProcessComments() {
    $(".fw_reply, .reply").each(function (index, element) {
        var $el = $(element).find(".fw_reply_text, .wall_reply_text, .reply_text");

        if ($(element).has(".audio").length > 0 && config.AutohideCommentsWithAudio)
            $(element).addClass("script-hidden");
        else if ($(element).has(".page_post_thumb_sized_photo").length > 0 && config.AutohideCommentsWithImages)
            $(element).addClass("script-hidden");
        else
            config.AutohideCommentsSpells.forEach(function (word) {
                if ($el.length > 0 && $el.text().toLowerCase().indexOf(word) > -1 && word.length > 0)
                    $(element).addClass("script-hidden");
            });
    });

}

function ProcessDiff() {
    var $m = $(".im_in:has(.emoji, .emoji_css)");
    var $emoji = $(".emoji, .emoji_css");
    if (config.HideEmodzi) {
        $(".emoji_smile").addClass("hidden");
        $m.each(function (index, element) {
            var $el = $(element);
            var textDom = $el.find(".im_msg_text");
            if (textDom.length > 0 && textDom.text().length == 0)
            {
                $el.addClass("hidden");
            }
        })
        $emoji.addClass("hidden");
    }
    else {
        $(".emoji_smile").removeClass("hidden");
        $m.removeClass("hidden");
        $emoji.removeClass("hidden");
    }
}

function ProcessPosts() {
    $(".post").each(function (index, element) {
        var $el = $(element).find(".wall_post_text");

        if (config.AutohideReposts && $(element).find(".published_by_wrap").length > 0)
            $(element).addClass("hidden");
        else
            config.AutohidePostsSpells.forEach(function (word) {
                if ($el.length > 0 && $el.text().toLowerCase().indexOf(word) > -1 && word.length > 0)
                    $(element).addClass("hidden");
            });
    });

}

function ProcessBookmarks() {
    $(".post").each(function (index, element) {
        if ($(element).find(".add_bookmark").length == 0) {
            var $el = $(element).find(".wall_text_name");
            var url = "http://vk.com/" + $(element).prop("id").replace("post", "wall");

            var attemptToFindReplica = config.Bookmarks.find(function (x) {
                return x.url == url;
            });

            if (attemptToFindReplica)
            {
                $el.append($(''));
                return;
            }
            else
                $el.append($(''));

            var name = $(element).find(".wall_text_name")
                .text()
                .trim()
                .split(" ")
                .map(function (x) { return x[0] })
                .join("");
            var text = $(element).find(".wall_post_text")
                .text()
                .trim()
                .substring(0, 100) + "...";

            $el.find(".add_bookmark").click(function () {
                config.AddBookmark(url, name + " # " + text);
                config.SaveBookmarks();

                $(this).addClass("bookmarked");

                RedrawBookmarks();

            });
        }
    });
}

function RedrawBookmarks() {
    $list = $("#saved-bookmarks");
    $list.html("");

    config.Bookmarks.forEach(function (mark) {
        var elem = $('
' + mark.text + '
');
        $list.append(elem);
        $("#" + mark.id + " .delete-bookmark").click(function() {
            $("#" + mark.id).remove();
            config.DeleteBookmark(mark.id);
            $("#post" + $(this).next().prop("href").split("wall")[1] + " .bookmarked").remove();
        });
    });
}<commit_msg>Logging of loading settings<commit_after>Array.prototype.removeConditional = function (comparator) {
    var result = [];
    this.forEach(function (arg) { if (!comparator(arg)) result.push(arg) });
    return result;
};

String.prototype.getHash = function () {
    var arr = this.split('');
    var hash = 0;
    for (var i = 0; i < arr.length; i++) {
        hash += arr[i].charCodeAt(0)
    }
    hash = arr[0] + hash + arr[arr.length - 1];
    hash = hash + hash.length;
    return hash;
}

function Settings() {
    this.AutohideCommentsSpells = [];
    this.AutohideCommentsWithImages = false;
    this.AutohideCommentsWithAudio = false;

    this.Bookmarks = [];

    this.AutohidePostsSpells = [];
    this.AutohideReposts = false;

    this.HideEmodzi = false;

    this.Interval = 0;
}

Settings.prototype.Load = function () {
    console.log("Loading settings:");

    console.log("- Comments blacklist");
    $(".comment-autohide")[0].value = window.localStorage["vkscript-comments-spells"] || "";

    console.log("- Posts blacklist");
    $(".post-autohide")[0].value = window.localStorage["vkscript-posts-spells"] || "";
    console.log("- Repost hiding option");
    $("input[name='repost-filter']")[0].checked = JSON.parse(window.localStorage["vkscript-repostshide"] || "false");

    console.log("- Comments with images hiding option");
    $("input[name='image-filter']")[0].checked = JSON.parse(window.localStorage["vkscript-comments-imageshide"] || "false");
    console.log("- Comments with audio hiding option");
    $("input[name='audio-filter']")[0].checked = JSON.parse(window.localStorage["vkscript-comments-audioshide"] || "false");

    console.log("- Emoji hiding option");
    $("input[name='emodji-filter']")[0].checked = JSON.parse(window.localStorage["vkscript-emodji-filter"] || "false");

    console.log("- Bookmarks");
    this.Bookmarks = JSON.parse(window.localStorage["vkscript-bookmarks"] || "[]");

    console.log("Settings loading completed.");
}

Settings.prototype.SavePosts = function () {
    window.localStorage["vkscript-posts-spells"] = $(".post-autohide")[0].value;
    window.localStorage["vkscript-repostshide"] = $("input[name='repost-filter']")[0].checked;

    var posts_spells = $(".post-autohide")[0].value.split(",").map(function (el) {
        return el.trim().toLowerCase();
    });

    this.AutohidePostsSpells = posts_spells;
    this.AutohideReposts = $("input[name='repost-filter']")[0].checked;

    $(".post").removeClass("hidden");
}

Settings.prototype.SaveComments = function () {
    window.localStorage["vkscript-comments-spells"] = $(".comment-autohide")[0].value;

    window.localStorage["vkscript-comments-imageshide"] = $("input[name='image-filter']")[0].checked;
    window.localStorage["vkscript-comments-audioshide"] = $("input[name='audio-filter']")[0].checked;

    var comments_spells = $(".comment-autohide")[0].value.split(",").map(function (el) {
        return el.trim().toLowerCase();
    });

    this.AutohideCommentsSpells = comments_spells;

    this.AutohideCommentsWithImages = $("input[name='image-filter']")[0].checked;
    this.AutohideCommentsWithAudio = $("input[name='audio-filter']")[0].checked;

    $(".fw_reply, .reply").removeClass("script-hidden");
}

Settings.prototype.SaveDiff = function () {
    window.localStorage["vkscript-emodji-filter"] = $("input[name='emodji-filter']")[0].checked;

    this.HideEmodzi = $("input[name='emodji-filter']")[0].checked;
}

Settings.prototype.AddBookmark = function (url, text) {
    var copies = this.Bookmarks.find(function (x) {
        return x.text.getHash() + x.url.getHash() == text.getHash() + url.getHash()
    });

    if (copies)
        return null;

    var mark = { "url": url, "text": text, "id": "mark-" + ("" + Math.random()).split('.')[1] };

    this.Bookmarks.push(mark);

    return mark;
}

Settings.prototype.DeleteBookmark = function (id) {
    this.Bookmarks = this.Bookmarks.removeConditional(function (mark) {
        return mark.id == id;
    });
}

Settings.prototype.ClearBookmarks = function () {
    this.Bookmarks = [];
}

Settings.prototype.SaveBookmarks = function () {
    window.localStorage["vkscript-bookmarks"] = JSON.stringify(this.Bookmarks);
}

var config;
var currentImage;

$(window).load(function () {
    var wrapper = $('

');
    $('body').append(wrapper);
    $('#vkscript-wrapper').load(chrome.extension.getURL("popup.html"), function () {
        config = new Settings();

        $(".autohide-button").click(function () {
            $("#vkscript-about").addClass("hidden");
            $("#vkscript-diff").addClass("hidden");
            $("#vkscript-posts").addClass("hidden");
            $("#vkscript-bookmarks").addClass("hidden");

            $("#vkscript-comments").toggleClass("hidden");
        });
        $(".about-button").click(function () {
            $("#vkscript-comments").addClass("hidden");
            $("#vkscript-diff").addClass("hidden");
            $("#vkscript-posts").addClass("hidden");
            $("#vkscript-bookmarks").addClass("hidden");

            $("#vkscript-about").toggleClass("hidden");
        });
        $(".diff-button").click(function () {
            $("#vkscript-comments").addClass("hidden");
            $("#vkscript-about").addClass("hidden");
            $("#vkscript-posts").addClass("hidden");
            $("#vkscript-bookmarks").addClass("hidden");

            $("#vkscript-diff").toggleClass("hidden");
        });
        $(".posts-button").click(function () {
            $("#vkscript-comments").addClass("hidden");
            $("#vkscript-about").addClass("hidden");
            $("#vkscript-diff").addClass("hidden");
            $("#vkscript-bookmarks").addClass("hidden");

            $("#vkscript-posts").toggleClass("hidden");
        });
        $(".bookmarks-button").click(function () {
            $("#vkscript-comments").addClass("hidden");
            $("#vkscript-about").addClass("hidden");
            $("#vkscript-diff").addClass("hidden");
            $("#vkscript-posts").addClass("hidden");

            $("#vkscript-bookmarks").toggleClass("hidden");
        });

        $("#comment-autohide-apply").click(function () {
            config.SaveComments();
            ProcessScript();
        });
        $("#diff-apply").click(function () {
            config.SaveDiff();
            ProcessScript();
        });
        $("#post-autohide-apply").click(function () {
            config.SavePosts();
            ProcessScript();
        });

        $("#delete-all-bookmarks").click(function () {
            config.ClearBookmarks();
            RedrawBookmarks();
            config.SaveBookmarks();
        })

        config.Interval = setInterval(ProcessScript, 500);

        config.Load();
        config.SaveComments();
        config.SaveDiff();
        config.SavePosts();

        RedrawBookmarks();
    });
});

function ProcessScript() {
    ProcessComments();
    ProcessImages();
    ProcessDiff();
    ProcessPosts();
    ProcessBookmarks();
}

function ProcessImages() {
    if ($("#pv_actions").length > 0) {
        currentImage = $("#pv_photo img").prop("src");
        if ($(".vkscript-find").length == 0) {
            $("#pv_actions").append($(''));
            $("#pv_actions").append($(''));
            $("#pv_actions").append($(''));
        }
    }
}

function ProcessComments() {
    $(".fw_reply, .reply").each(function (index, element) {
        var $el = $(element).find(".fw_reply_text, .wall_reply_text, .reply_text");

        if ($(element).has(".audio").length > 0 && config.AutohideCommentsWithAudio)
            $(element).addClass("script-hidden");
        else if ($(element).has(".page_post_thumb_sized_photo").length > 0 && config.AutohideCommentsWithImages)
            $(element).addClass("script-hidden");
        else
            config.AutohideCommentsSpells.forEach(function (word) {
                if ($el.length > 0 && $el.text().toLowerCase().indexOf(word) > -1 && word.length > 0)
                    $(element).addClass("script-hidden");
            });
    });

}

function ProcessDiff() {
    var $m = $(".im_in:has(.emoji, .emoji_css)");
    var $emoji = $(".emoji, .emoji_css");
    if (config.HideEmodzi) {
        $(".emoji_smile").addClass("hidden");
        $m.each(function (index, element) {
            var $el = $(element);
            var textDom = $el.find(".im_msg_text");
            if (textDom.length > 0 && textDom.text().length == 0)
            {
                $el.addClass("hidden");
            }
        })
        $emoji.addClass("hidden");
    }
    else {
        $(".emoji_smile").removeClass("hidden");
        $m.removeClass("hidden");
        $emoji.removeClass("hidden");
    }
}

function ProcessPosts() {
    $(".post").each(function (index, element) {
        var $el = $(element).find(".wall_post_text");

        if (config.AutohideReposts && $(element).find(".published_by_wrap").length > 0)
            $(element).addClass("hidden");
        else
            config.AutohidePostsSpells.forEach(function (word) {
                if ($el.length > 0 && $el.text().toLowerCase().indexOf(word) > -1 && word.length > 0)
                    $(element).addClass("hidden");
            });
    });

}

function ProcessBookmarks() {
    $(".post").each(function (index, element) {
        if ($(element).find(".add_bookmark").length == 0) {
            var $el = $(element).find(".wall_text_name");
            var url = "http://vk.com/" + $(element).prop("id").replace("post", "wall");

            var attemptToFindReplica = config.Bookmarks.find(function (x) {
                return x.url == url;
            });

            if (attemptToFindReplica)
            {
                $el.append($(''));
                return;
            }
            else
                $el.append($(''));

            var name = $(element).find(".wall_text_name")
                .text()
                .trim()
                .split(" ")
                .map(function (x) { return x[0] })
                .join("");
            var text = $(element).find(".wall_post_text")
                .text()
                .trim()
                .substring(0, 100) + "...";

            $el.find(".add_bookmark").click(function () {
                config.AddBookmark(url, name + " # " + text);
                config.SaveBookmarks();

                $(this).addClass("bookmarked");

                RedrawBookmarks();

            });
        }
    });
}

function RedrawBookmarks() {
    $list = $("#saved-bookmarks");
    $list.html("");

    config.Bookmarks.forEach(function (mark) {
        var elem = $('
' + mark.text + '
');
        $list.append(elem);
        $("#" + mark.id + " .delete-bookmark").click(function() {
            $("#" + mark.id).remove();
            config.DeleteBookmark(mark.id);
            $("#post" + $(this).next().prop("href").split("wall")[1] + " .bookmarked").remove();
        });
    });
}<|endoftext|>


<commit_before>var fs = require('fs');
var http = require('http');
var https = require('https');
var httpProxy = require('http-proxy');

var sslkeyPath = '/etc/letsencrypt/live/goita.beta.or.jp/'
var options = {
    key: fs.readFileSync(sslkeyPath + 'privkey.pem', 'utf8'),
    cert: fs.readFileSync(sslkeyPath + 'cert.pem', 'utf8')
}

var proxy = httpProxy.createProxyServer({
    target: "http://goita.beta.or.jp:8880",
    ws: true
});

var server = http.createServer(function(req, res){
    proxy.web(req, res);
});
server.on('upgrade', function (req, socket, head) {
    proxy.ws(req, socket, head);
});

var sslproxy = httpProxy.createServer({
    target: "http://goita.beta.or.jp:8880",
    ws: true,
    secure: true,
    ssl: options
});

server.listen(80); //root is required to run service on port 80.
sslproxy.listen(443); //root is required to run service on port 443.
console.log("proxy server started");<commit_msg>add setuid<commit_after>var fs = require('fs');
var http = require('http');
var https = require('https');
var httpProxy = require('http-proxy');

var uid = 502; //node-http-proxyを動作させるUserId
var sslkeyPath = '/etc/letsencrypt/live/goita.beta.or.jp/'
var options = {
    key: fs.readFileSync(sslkeyPath + 'privkey.pem', 'utf8'),
    cert: fs.readFileSync(sslkeyPath + 'cert.pem', 'utf8')
}

var proxy = httpProxy.createProxyServer({
    target: "http://goita.beta.or.jp:8880",
    ws: true
});

var server = http.createServer(function(req, res){
    proxy.web(req, res);
});
server.on('upgrade', function (req, socket, head) {
    proxy.ws(req, socket, head);
});

var sslproxy = httpProxy.createServer({
    target: "http://goita.beta.or.jp:8880",
    ws: true,
    secure: true,
    ssl: options
});

server.listen(80, function(){
    process.setuid(uid);
}); //root is required to run service on port 80.
sslproxy.listen(443, function(){
    process.setuid(uid);
}); //root is required to run service on port 443.
console.log("proxy server started");<|endoftext|>


<commit_before>/*
Copyright (c) <2013>, <Zizon Qiu zzdtsv@gmail.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software
must display the following acknowledgement:
This product includes software developed by the <organization>.
4. Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ''AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
"use strict";

var config = {
    "servers":"SOCKS5 127.0.0.1:10086;"
};

var engine = {
    "shuffle" : function(more){
        switch(more.length){
            case 0:
            case 1:
                return [more];
            case 2:return [
                [more[0],more[1]],
                [more[1],more[0]]
            ];
        }

        return this.shuffle(more.slice(1)).reduce(
            function(shuffled,unit){
                // for each shuffled unit
                unit.forEach(function(item,index){
                    // patch single to current unit at position index
                    shuffled.push([].concat(unit.slice(0,index),more[0],unit.slice(index)));
                });

                // create new instead of reuse unit.
                // aim to cut reference to local reference.
                shuffled.push([].concat(unit,more[0]));
                return shuffled;
            },
            []
        );
    },

    "gen":function(hints){
        // build lookup
        var lookup = hints.genLookup(hints.marks);
        var marks = hints.marks;

        // backward compatible
        var candidates = config.servers.split(";").reduce(
            function(concated,item){
                item = item.trim();
                if(item.indexOf("HTTPS") !== 0){
                    concated.push("PROXY " + item.substr(5));
                }else if(item.indexOf("HTTP") !== 0){
                    concated.push("PROXY " + item.substr(4));
                }else if(item.length > 0){
                    concated.push(item);
                }

                return concated;
            },
            []
        );
        config.servers = candidates.join(";");

        // shuffle servers,aim to randomize proxy load
        var servers = this.shuffle(candidates).map(function(i){
            return i.join(";") + ";DIRECT;";
        });

        // dereference.
        // Note: hints.match should not contain any 'this' use.
        var matchFuzzy = hints.match;

        //gen template
        var template = function(url,host){
            if( host in marks ){
                return servers[Date.now()%servers.length];
            }

            return matchFuzzy(host,lookup,false).hit ? servers[Date.now()%servers.length] : "DIRECT;";
        };

        return "var lookup = " + JSON.stringify(lookup) + ";\n"
            +   "var marks = " + JSON.stringify(marks) + ";\n"
            +   "var servers = " + JSON.stringify(servers) + ";\n"
            +   "var matchFuzzy = " + matchFuzzy.toString() + ";\n"
            +   "var FindProxyForURL = " + template.toString() + ";";
    }
};

function syncProxyConfig(){
    localStorage.setItem("proxy.config",JSON.stringify(config));
}

function restoreProxyConfig(){
    var stored = localStorage.getItem("proxy.config");
    if( stored !== null ){
        config = JSON.parse(stored);
    }
}

var hints ={
    "exclude":{},

    "marks":{},

    "complete":{},

    "candidate":{},

    "genLookup":function(marks){
        var lookup = {};
        for(var key in marks){
            if( marks[key] <= 0 ){
                delete marks[key];
                continue;
            }

            // gen lookup table in reverse/postfix manner.
            // for example, 'www.google.com' will be transform to
            //	{
            //		"com":{
            //			"google":{
            //				"www":{}
            //			}
            //		}
            //	}
            key.split(".").reduceRight(
                function( previous, current, index, array ){
                    return current in previous ? previous[current] : previous[current] = {};
                },
                lookup
            );
        }

        return lookup;
    },

    "match":function(host,lookup,create_when_miss){
        return host.split(".").reduceRight(
            function( previous, current, index, array ){
                // directive ignore
                if( previous.stop ){
                    return previous;
                }

                var ctx_lookup = previous.lookup;

                // see if current context has fuzzy
                if( "*" in ctx_lookup || current == "*" ){
                    // set up stop flag
                    previous.stop = true;

                    // indicate a fuzzy match
                    previous.fuzzy = true;

                    // modiry current representation.
                    // it is a fuzzy match
                    previous.context.push("*");
                }else if( current in ctx_lookup ){
                    // match , drill down
                    previous.lookup = ctx_lookup[current];
                    previous.context.push(current);
                }else if( create_when_miss ){
                    // not match,and directive to create new
                    previous.lookup = ctx_lookup[current] = {};
                    previous.context.push(current);
                }else{
                    // not match,and directive *NOT* to create new
                    previous.stop = true;
                    previous.hit = false;
                }

                return previous;
            },
            {
                "lookup":lookup,
                "fuzzy":false,
                "stop":false,
                "context":[],
                "hit":true
            }
        );
    },

    "compact":function(){
        // easy job
        if( "*" in this.marks ){
            this.marks = {"*":2};
            return true;
        }

        // lookup table
        var lookup = this.genLookup(hints.marks);
        var gen = false;

        var sweep = function(fuzzy_key,siblings,parent){
            var counting = function(siblings,parent){
                var counter = 0;
                for( var child in siblings ){
                    parent.push(child);
                    counter += counting(siblings[child],parent);
                    parent.pop();
                    delete siblings[child];
                }

                // count parent
                var key = parent.reverse().join(".");
                if( key in hints.marks ){
                    counter += hints.marks[key];
                    delete hints.marks[key];
                }
                parent.reverse();

                return counter;
            };

            // counting
            var old_counter = fuzzy_key in hints.marks ?  hints.marks[fuzzy_key] : 0;
            var counter = counting(siblings,parent);

            // update track context
            siblings["*"] = {};
            hints.marks[fuzzy_key] = counter;

            return old_counter != counter;
        };

        for( var key in this.marks ){
            var match = this.match(key,lookup,false);

            // parent
            var parent = key.split(".").reverse();
            parent.pop();

            // sibling lookup
            var siblings = parent.reduce(
                function( parent, child, index, array ){
                    return parent[child];
                },
                lookup
            );

            if( match.fuzzy ){
                // do sweep,directive code generation
                gen = sweep(match.context.reverse().join("."),siblings,parent) || gen;
                continue;
            }

            // concrete match
            var mergable = true;

            out:
            for( var sibling in siblings ){
                // if lower level has its children,mark it not mergable
                for( var _ in siblings[sibling] ){
                    mergable = false;
                    break out;
                }
            }

            if( mergable ){
                if( parent.length > 1 ){
                    gen = true;
                    parent.push("*");
                    var fuzzy_key = parent.reverse().join(".");
                    parent.reverse().pop();
                    sweep(fuzzy_key,siblings,parent);
                }
            }
        }

        return gen;
    },

    "markOK":function(host){
        if( host in this.complete ){
            if( this.complete[host] < Number.MAX_VALUE ){
                this.complete[host] += 1;
            }
        }else{
            this.complete[host] = 1;
        }
    },

    "codegen":function(){
        chrome.proxy.settings.set(
            {
                "value":{
                    "mode":"pac_script",
                    "pacScript":{
                        "mandatory":true,
                        "data":engine.gen(hints)
                    }
                }
            },

            function(){
                console.log("setting apply");
            }
        );
    },

    "markFail":function(host){
        // if host is in proxy.
        // update marks
        if( host in this.marks ){
            if( --this.marks[host] <= 0 ){
                // proxy fail much,remove from proxy
                console.log("drop proxy:" + host);
                delete this.marks[host];
                this.codegen();
                localStorage.setItem("hints.marks",JSON.stringify(this.marks));
            }
        }else{
            // not in proxy yet,add it
            this.marks[host] = 2;
            this.codegen();
        }
    }
};

function resoreHints(){
    console.log("restore hints");
    var cache = localStorage.getItem("hints.marks");
    if( cache === null ){
        return;
    }

    console.log("restore exclude");
    var exclude = localStorage.getItem("hints.exclude");
    if( exclude === null ){
        hints.exclude = {};
    }else{
        hints.exclude = exclude;
    }

    cache = JSON.parse(cache);
    for( var key in cache ){
        hints.marks[key] = cache[key];
    }

    hints.compact();
    hints.codegen();
}

function extractHost(url){
    var start = url.indexOf("://") + 3;
    return url.substr(start,url.indexOf("/",start) - start);
}

function handInRequest(){
    console.log("handin request");

    chrome.proxy.onProxyError.addListener(function(details){
        console.error("proxy error:" + details);
    });

    chrome.webRequest.onErrorOccurred.addListener(
        function(details){
            console.error(details);

            // inspect potential reset request
            switch(details.error){
                case "net::ERR_CONNECTION_RESET":
                case "net::ERR_CONNECTION_TIMED_OUT":
                case "net::ERR_SSL_PROTOCOL_ERROR":
                    hints.markFail(extractHost(details.url));
                    break;
            }
        },
        {
            "urls":["<all_urls>"]
        }
    );

    chrome.webRequest.onCompleted.addListener(
        function(details){
            // ignore cache
            if(details.fromCache){
                return;
            }

            // mark host ok
            hints.markOK(extractHost(details.url));
        },
        {
            "urls":["<all_urls>"]
        },
        []
    );
}

function schedule(){
    console.log("schedule");

    chrome.alarms.create(
        "sweep-hints-marks",
        {
            "periodInMinutes":5
        }
    );

    chrome.alarms.onAlarm.addListener(function( alarm ){
        console.log("fire alarm:" + alarm.name);
        switch(alarm.name){
            case "sweep-hints-marks":
            // clear candidate
            hints.candidate = {};

            // compact first,make it shorter
            if( hints.compact() ){
                hints.codegen();
            }

            // loop hints.complete list,
            // update counter in marks.
            var lookup = hints.genLookup(hints.marks);
            for( var key in hints.complete ){
                if( key in hints.marks ){
                    hints.marks[key] += hints.complete[key];
                    continue;
                }

                var match = hints.match(key,lookup,false);
                if( match.fuzzy ){
                    hints.marks[match.context.reverse().join(".")] += hints.complete[key];
                }
            }

            // clear
            hints.complete = {};
            console.log("sync-local-cache");
            localStorage.setItem("hints.marks",JSON.stringify(hints.marks));
            break;
        }
    });
}

function handIn(){
    restoreProxyConfig();
    resoreHints();
    handInRequest();
    schedule();
}

(function(){
    handIn();
})();<commit_msg>add switchyd<commit_after>/*
Copyright (c) <2013>, <Zizon Qiu zzdtsv@gmail.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software
must display the following acknowledgement:
This product includes software developed by the <organization>.
4. Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ''AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
"use strict";

var switchyd = {
    config:{
        servers:[
            {
                type:"SOCK5",
                ip:"127.0.0.1",
                port:10086
            }
        ]
    },

    sync:(function(){
        var self = this;
        return {
            load:function(){
                return localStorage.getItem("switchyd.config");
            },

            save:function(){
                localStorage.setItem("switchyd.config",self.config);
            }
        };
    })(),

    tracer:(function(){
        this._tracking = {};
        return function(name){
            if( name in this._tracking ){
                return this._tracking[name];
            }

            // not present yet,create one
            return {
                urls:{},

                track:function(url){
                    url in this.urls ? 0 : this.urls[url]=0;
                },
            };
        }
    })(),

    compile:function(tracer){
        var compiled = {};

        var transform = function(final,part){
            return part in final ? final[part] : final[part] = {};
        };

        // loop each node
        for(var url in tracer.urls){
            url.split(".").reduceRight(transform,compiled);
        }

        return compiled;
    },

    optimize:(function(){
        var no_children = function(node){
            for(var child in node){
                if( child !== "*" ){
                    return false;
                }
            }

            return true;
        };

        return function(compiled,depth){
            // ensure default value
            depth = typeof depth == "undefined" ? 1 : depth;

            // leaf
            if( no_children(compiled) ){
                return compiled;
            }

            var mergable = true;
            // none leaf
            for(var part in compiled){
                // recrusive optimize
                mergable = no_children(compiled[part] = this.optimize(compiled[part],depth+1) );
            }

            // see if mergable
            if( mergable ){
                if( depth > 2 ){
                    for(var child in compiled){
                        delete compiled[child];
                    }

                    // add fuzzy mark
                    compiled["*"]={};
                }
            }

            return compiled;
        };
    })(),

    match:function(compiled,url){
        return url.split(".").reduceRight(function(context,part){
            if( context ){
                return part in context ? context[part] : false;
            }

            return false;
        },compiled) !== false;
    },

    link:function(compiled){
        var shuffle = function(arrays){
            switch(arrays){
                case 0:case 1:
                    return [arrays];
                case 2:
                    return [
                        [arrays[0],arrays[1]],
                        [arrays[1],arrays[0]]
                    ];
            }

            return shuffle(arrays.slice(1)).reduce(function(shuffled,candidate,index,paritial){
                shuffled = index === 0 ? [ [].concat(arrays[0],candidate) ] : shuffled;
                shuffled.push([].concat(candidate.slice(0,index),arrays[0],candidate.slice(index)));
                return shuffled;
            },[]);
        };

        // make server load balance
        var load_balance = shuffle(this.config.servers).map(function(servers){
            return [].concat(servers.map(function(server){
               return [server.type," " , server.ip,":",server.port];
            }),"DIRECT").join(";");
        });

        var search = this.search;
        var template = function(_,host){
            if( search(compiled,host) ){
                return load_balance[Date.now()%load_balance.length];
            }

            return "DIRECT;"
        };

        var script ="var load_balance = " + load_balance + ";\n"
                    + "var compiled = " + JSON.stringify(this.tracer("proxy")) + ";\n"
                    + "var search = " + this.search.toString() + ";\n"
                    + "var FindProxyForURL = " + template.toString() + ";";
        chrome.proxy.settings.set(
            {
                "value":{
                    "mode":"pac_script",
                    "pacScript":{
                        "mandatory":true,
                        "data":script
                    }
                }
            },

            function(){
                console.log("setting apply");
            }
        );

        return script;
    }
};

var config = {
    "servers":"SOCKS5 127.0.0.1:10086;"
};

var engine = {
    "shuffle" : function(more){
        switch(more.length){
            case 0:
            case 1:
                return [more];
            case 2:return [
                [more[0],more[1]],
                [more[1],more[0]]
            ];
        }

        return this.shuffle(more.slice(1)).reduce(
            function(shuffled,unit){
                // for each shuffled unit
                unit.forEach(function(item,index){
                    // patch single to current unit at position index
                    shuffled.push([].concat(unit.slice(0,index),more[0],unit.slice(index)));
                });

                // create new instead of reuse unit.
                // aim to cut reference to local reference.
                shuffled.push([].concat(unit,more[0]));
                return shuffled;
            },
            []
        );
    },

    "gen":function(hints){
        // build lookup
        var lookup = hints.genLookup(hints.marks);
        var marks = hints.marks;

        // backward compatible
        var candidates = config.servers.split(";").reduce(
            function(concated,item){
                item = item.trim();
                if(item.indexOf("HTTPS") !== 0){
                    concated.push("PROXY " + item.substr(5));
                }else if(item.indexOf("HTTP") !== 0){
                    concated.push("PROXY " + item.substr(4));
                }else if(item.length > 0){
                    concated.push(item);
                }

                return concated;
            },
            []
        );
        config.servers = candidates.join(";");

        // shuffle servers,aim to randomize proxy load
        var servers = this.shuffle(candidates).map(function(i){
            return i.join(";") + ";DIRECT;";
        });

        // dereference.
        // Note: hints.match should not contain any 'this' use.
        var matchFuzzy = hints.match;

        //gen template
        var template = function(url,host){
            if( host in marks ){
                return servers[Date.now()%servers.length];
            }

            return matchFuzzy(host,lookup,false).hit ? servers[Date.now()%servers.length] : "DIRECT;";
        };

        return "var lookup = " + JSON.stringify(lookup) + ";\n"
            +   "var marks = " + JSON.stringify(marks) + ";\n"
            +   "var servers = " + JSON.stringify(servers) + ";\n"
            +   "var matchFuzzy = " + matchFuzzy.toString() + ";\n"
            +   "var FindProxyForURL = " + template.toString() + ";";
    }
};

function syncProxyConfig(){
    localStorage.setItem("proxy.config",JSON.stringify(config));
}

function restoreProxyConfig(){
    var stored = localStorage.getItem("proxy.config");
    if( stored !== null ){
        config = JSON.parse(stored);
    }
}

var hints ={
    "exclude":{},

    "marks":{},

    "complete":{},

    "candidate":{},

    "genLookup":function(marks){
        var lookup = {};
        for(var key in marks){
            if( marks[key] <= 0 ){
                delete marks[key];
                continue;
            }

            // gen lookup table in reverse/postfix manner.
            // for example, 'www.google.com' will be transform to
            //	{
            //		"com":{
            //			"google":{
            //				"www":{}
            //			}
            //		}
            //	}
            key.split(".").reduceRight(
                function( previous, current, index, array ){
                    return current in previous ? previous[current] : previous[current] = {};
                },
                lookup
            );
        }

        return lookup;
    },

    "match":function(host,lookup,create_when_miss){
        return host.split(".").reduceRight(
            function( previous, current, index, array ){
                // directive ignore
                if( previous.stop ){
                    return previous;
                }

                var ctx_lookup = previous.lookup;

                // see if current context has fuzzy
                if( "*" in ctx_lookup || current == "*" ){
                    // set up stop flag
                    previous.stop = true;

                    // indicate a fuzzy match
                    previous.fuzzy = true;

                    // modiry current representation.
                    // it is a fuzzy match
                    previous.context.push("*");
                }else if( current in ctx_lookup ){
                    // match , drill down
                    previous.lookup = ctx_lookup[current];
                    previous.context.push(current);
                }else if( create_when_miss ){
                    // not match,and directive to create new
                    previous.lookup = ctx_lookup[current] = {};
                    previous.context.push(current);
                }else{
                    // not match,and directive *NOT* to create new
                    previous.stop = true;
                    previous.hit = false;
                }

                return previous;
            },
            {
                "lookup":lookup,
                "fuzzy":false,
                "stop":false,
                "context":[],
                "hit":true
            }
        );
    },

    "compact":function(){
        // easy job
        if( "*" in this.marks ){
            this.marks = {"*":2};
            return true;
        }

        // lookup table
        var lookup = this.genLookup(hints.marks);
        var gen = false;

        var sweep = function(fuzzy_key,siblings,parent){
            var counting = function(siblings,parent){
                var counter = 0;
                for( var child in siblings ){
                    parent.push(child);
                    counter += counting(siblings[child],parent);
                    parent.pop();
                    delete siblings[child];
                }

                // count parent
                var key = parent.reverse().join(".");
                if( key in hints.marks ){
                    counter += hints.marks[key];
                    delete hints.marks[key];
                }
                parent.reverse();

                return counter;
            };

            // counting
            var old_counter = fuzzy_key in hints.marks ?  hints.marks[fuzzy_key] : 0;
            var counter = counting(siblings,parent);

            // update track context
            siblings["*"] = {};
            hints.marks[fuzzy_key] = counter;

            return old_counter != counter;
        };

        for( var key in this.marks ){
            var match = this.match(key,lookup,false);

            // parent
            var parent = key.split(".").reverse();
            parent.pop();

            // sibling lookup
            var siblings = parent.reduce(
                function( parent, child, index, array ){
                    return parent[child];
                },
                lookup
            );

            if( match.fuzzy ){
                // do sweep,directive code generation
                gen = sweep(match.context.reverse().join("."),siblings,parent) || gen;
                continue;
            }

            // concrete match
            var mergable = true;

            out:
            for( var sibling in siblings ){
                // if lower level has its children,mark it not mergable
                for( var _ in siblings[sibling] ){
                    mergable = false;
                    break out;
                }
            }

            if( mergable ){
                if( parent.length > 1 ){
                    gen = true;
                    parent.push("*");
                    var fuzzy_key = parent.reverse().join(".");
                    parent.reverse().pop();
                    sweep(fuzzy_key,siblings,parent);
                }
            }
        }

        return gen;
    },

    "markOK":function(host){
        if( host in this.complete ){
            if( this.complete[host] < Number.MAX_VALUE ){
                this.complete[host] += 1;
            }
        }else{
            this.complete[host] = 1;
        }
    },

    "codegen":function(){
        chrome.proxy.settings.set(
            {
                "value":{
                    "mode":"pac_script",
                    "pacScript":{
                        "mandatory":true,
                        "data":engine.gen(hints)
                    }
                }
            },

            function(){
                console.log("setting apply");
            }
        );
    },

    "markFail":function(host){
        // if host is in proxy.
        // update marks
        if( host in this.marks ){
            if( --this.marks[host] <= 0 ){
                // proxy fail much,remove from proxy
                console.log("drop proxy:" + host);
                delete this.marks[host];
                this.codegen();
                localStorage.setItem("hints.marks",JSON.stringify(this.marks));
            }
        }else{
            // not in proxy yet,add it
            this.marks[host] = 2;
            this.codegen();
        }
    }
};

function resoreHints(){
    console.log("restore hints");
    var cache = localStorage.getItem("hints.marks");
    if( cache === null ){
        return;
    }

    console.log("restore exclude");
    var exclude = localStorage.getItem("hints.exclude");
    if( exclude === null ){
        hints.exclude = {};
    }else{
        hints.exclude = exclude;
    }

    cache = JSON.parse(cache);
    for( var key in cache ){
        hints.marks[key] = cache[key];
    }

    hints.compact();
    hints.codegen();
}

function extractHost(url){
    var start = url.indexOf("://") + 3;
    return url.substr(start,url.indexOf("/",start) - start);
}

function handInRequest(){
    console.log("handin request");

    chrome.proxy.onProxyError.addListener(function(details){
        console.error("proxy error:" + details);
    });

    chrome.webRequest.onErrorOccurred.addListener(
        function(details){
            console.error(details);

            // inspect potential reset request
            switch(details.error){
                case "net::ERR_CONNECTION_RESET":
                case "net::ERR_CONNECTION_TIMED_OUT":
                case "net::ERR_SSL_PROTOCOL_ERROR":
                    hints.markFail(extractHost(details.url));
                    break;
            }
        },
        {
            "urls":["<all_urls>"]
        }
    );

    chrome.webRequest.onCompleted.addListener(
        function(details){
            // ignore cache
            if(details.fromCache){
                return;
            }

            // mark host ok
            hints.markOK(extractHost(details.url));
        },
        {
            "urls":["<all_urls>"]
        },
        []
    );
}

function schedule(){
    console.log("schedule");

    chrome.alarms.create(
        "sweep-hints-marks",
        {
            "periodInMinutes":5
        }
    );

    chrome.alarms.onAlarm.addListener(function( alarm ){
        console.log("fire alarm:" + alarm.name);
        switch(alarm.name){
            case "sweep-hints-marks":
            // clear candidate
            hints.candidate = {};

            // compact first,make it shorter
            if( hints.compact() ){
                hints.codegen();
            }

            // loop hints.complete list,
            // update counter in marks.
            var lookup = hints.genLookup(hints.marks);
            for( var key in hints.complete ){
                if( key in hints.marks ){
                    hints.marks[key] += hints.complete[key];
                    continue;
                }

                var match = hints.match(key,lookup,false);
                if( match.fuzzy ){
                    hints.marks[match.context.reverse().join(".")] += hints.complete[key];
                }
            }

            // clear
            hints.complete = {};
            console.log("sync-local-cache");
            localStorage.setItem("hints.marks",JSON.stringify(hints.marks));
            break;
        }
    });
}

function handIn(){
    restoreProxyConfig();
    resoreHints();
    handInRequest();
    schedule();
}

(function(){
    handIn();
})();<|endoftext|>