diff options
author | Einar Egilsson <einar@einaregilsson.com> | 2015-09-09 14:53:22 +0000 |
---|---|---|
committer | Einar Egilsson <einar@einaregilsson.com> | 2015-09-09 14:53:22 +0000 |
commit | 8a77401f48360fe0d5501d4fd6b4cc194279d4f0 (patch) | |
tree | 6c6069b54de898c72a2281caa9702039df8a2bfb /js | |
parent | a7506a34544f4df3ba65a854c81fadcca2eb303f (diff) |
Plenty of changes
Diffstat (limited to 'js')
-rw-r--r-- | js/app.js | 270 | ||||
-rw-r--r-- | js/background.js | 4 | ||||
-rw-r--r-- | js/controllers/deleteredirect.js | 25 | ||||
-rw-r--r-- | js/controllers/editredirect.js | 82 | ||||
-rw-r--r-- | js/controllers/importexport.js | 110 | ||||
-rw-r--r-- | js/controllers/listredirects.js | 44 | ||||
-rw-r--r-- | js/controllers/redirectorpage.js | 50 | ||||
-rw-r--r-- | js/popup.js | 13 | ||||
-rw-r--r-- | js/redirect.js | 119 |
9 files changed, 424 insertions, 293 deletions
@@ -1,276 +1,14 @@ -var redirectorApp = angular.module('redirectorApp', []) -.config( [ - '$compileProvider', - function( $compileProvider ) - { - $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|chrome-extension|data):/); - } -]); +//Nothing really here except the app object. Filters, and directives are +//include with the controllers that use them. If we need to add any that +//are used by multiple controllers then we'll define them here. +var redirectorApp = angular.module('redirectorApp', []); -//Directive for file upload: -redirectorApp.directive('fileselected', function() { - return { - restrict: 'A', - link: function(scope, element, attr, ctrl) { - element.bind('change', function(e) { - var f = element[0].files[0]; - element[0].value = ''; - scope.$eval(attr.fileselected, {'$file':f}); - }); - } - } -}); -//Filter for displaying nice names for request types -redirectorApp.filter('requestTypeDisplay', function() { - return function(input) { - return input.map(function(key) { return requestTypes[key]; }).join(', '); - } -}); -var storage = chrome.storage.local; //TODO: Change to sync when Firefox supports it... -var requestTypes = { - main_frame: "Main window (address bar)", - sub_frame: "IFrames", - stylesheet : "Stylesheets", - script : "Scripts", - image : "Images", - object : "Objects (e.g. Flash videos, Java applets)", - xmlhttprequest : "XMLHttpRequests (Ajax)", - other : "Other" -}; -redirectorApp.controller('redirectorController', ['$scope', '$timeout', function($s, $timeout) { - $s.activeRedirect = null; - $s.editIndex = -1; - $s.requestTypes = requestTypes; - $s.appliesTo = function(key) { - if (!$s.activeRedirect) { - return; - } - return $s.activeRedirect.appliesTo.indexOf(key) != -1; - }; - $s.toggleApplies = function(key) { - if (!$s.activeRedirect) { - return; - } - var arr = $s.activeRedirect.appliesTo; - - var index = arr.indexOf(key); - if (index == -1) { - arr.push(key); - } else { - arr.splice(index, 1); - } - - var order = 'main_frame,sub_frame,stylesheet,script,image,object,xmlhttprequest,other'; - - arr.sort(function(a,b) { - return order.indexOf(a) - order.indexOf(b); - }); - }; - - - function closeEditForm() { - $s.editIndex = -1; - $s.activeRedirect = null; - $s.showAdvanced = false; - $s.showModal = false; - } - - $s.createNew = function() { - $s.activeRedirect = new Redirect({}).toObject(); - $s.showModal = true; - }; - - $s.saveRedirect = function() { - if ($s.editIndex >= 0) { - $s.redirects[$s.editIndex] = $s.activeRedirect; - } else { - $s.redirects.push(new Redirect($s.activeRedirect).toObject()); - } - closeEditForm(); - saveChanges(); - }; - - $s.cancelEdit = function() { - closeEditForm(); - } - - function saveChanges() { - - var arr = []; - for (var i=0; i < $s.redirects.length; i++) { - var r = $s.redirects[i]; - arr.push(new Redirect(r).toObject()); - } - - storage.set({redirects:arr}, function() { - console.log('Saved redirects'); - }); - updateExportLink(); - } - - function swap(arr, i, n) { - var item = arr[i]; - arr[i] = arr[n]; - arr[n] = item; - } - - $s.moveUp = function(index) { - if (index == 0) { - return; - } - swap($s.redirects, index, index-1); - saveChanges(); - }; - - $s.moveDown = function(index) { - if (index == $s.redirects.length-1) { - return; - } - swap($s.redirects, index, index+1); - saveChanges(); - }; - - $s.confirmDelete = function(index) { - $s.deleting = $s.redirects[index]; - $s.deletingIndex = index; - $s.showModal = true; - } - - $s.cancelDelete = function(index) { - delete $s.deleting; - delete $s.deletingIndex; - $s.showModal = false; - } - - $s.deleteRedirect = function() { - $s.redirects.splice($s.deletingIndex, 1); - delete $s.deleting; - delete $s.deletingIndex; - $s.showModal = false; - saveChanges(); - }; - - $s.editRedirect = function(index) { - $s.activeRedirect = new Redirect($s.redirects[index]).toObject(); - $s.editIndex = index; - $s.showModal = true; - }; - - $s.toggleDisabled = function(redirect) { - redirect.disabled = !redirect.disabled; - saveChanges(); - } - - $s.redirects = []; - storage.get('redirects', function(results) { - if (!results || !results.redirects) { - return; - } - for (var i=0; i < results.redirects.length; i++) { - $s.redirects.push(new Redirect(results.redirects[i]).toObject()); - } - updateExportLink(); - $s.$apply(); - }); - - function msg(message, success) { - $s.message = message; - $s.messageType = success ? 'success' : 'error'; - - var m = message; - - //Remove the message in 5 seconds if it hasn't been changed... - $timeout(function() { - if ($s.message == m) { - $s.message = null; - } - }, 20 * 1000); - } - - /* Import/Export of Redirects */ - $s.importRedirects = function(file) { - if (!file) { - return; - } - var reader = new FileReader(); - - reader.onload = function(e) { - var data; - try { - var data = JSON.parse(reader.result); - } catch(e) { - msg('Failed to parse JSON data, invalid JSON: ' + (e.message||'').substr(0,100)); - return $s.$apply(); - } - - if (!data.redirects) { - msg('Invalid JSON, missing "redirects" property'); - return $s.$apply(); - } - - var imported = 0, existing = 0; - for (var i = 0; i < data.redirects.length; i++) { - var r = new Redirect(data.redirects[i]); - - if ($s.redirects.some(function(i) { return new Redirect(i).equals(r);})) { - existing++; - } else { - $s.redirects.push(r.toObject()); - imported++; - } - } - if (imported == 0 && existing == 0) { - msg('No redirects existed in the file.'); - } - if (imported > 0 && existing == 0) { - msg('Successfully imported ' + imported + ' redirect' + (imported > 1 ? 's.' : '.'), true); - } - if (imported == 0 && existing > 0) { - msg('All redirects in the file already existed and were ignored.'); - } - if (imported > 0 && existing > 0) { - var m = 'Successfully imported ' + imported + ' redirect' + (imported > 1 ? 's' : '') + '. '; - if (existing == 1) { - m += '1 redirect already existed and was ignored.'; - } else { - m += existing + ' redirects already existed and were ignored.'; - } - msg(m, true); - } - - saveChanges(); - $s.$apply(); - }; - try { - reader.readAsText(file, 'utf-8'); - } catch(e) { - msg('Failed to read import file'); - } - } - - function updateExportLink() { - var redirects = $s.redirects.map(function(r) { - return new Redirect(r).toObject(); - }); - - var exportObj = { - createdBy : 'Redirector v' + chrome.app.getDetails().version, - createdAt : new Date(), - redirects : redirects - }; - - var json = JSON.stringify(exportObj, null, 4); - - $s.redirectDownload = 'data:text/plain;charset=utf-8,' + encodeURIComponent(json); - } - -}]); diff --git a/js/background.js b/js/background.js index b9ef47b..b6260d7 100644 --- a/js/background.js +++ b/js/background.js @@ -19,7 +19,7 @@ function checkForRedirect(details) { var filter = {urls:["http://*/*", "https://*/*"]}; //TODO: Better browser detection... -var isChrome = !!navigator.userAgent.match(/ Chrome\//); +var isFirefox = !!navigator.userAgent.match(/Firefox\//); -var ev = isChrome ? chrome.webRequest.onBeforeRequest : chrome.webRequest.onBeforeSendHeaders; +var ev = isFirefox ? chrome.webRequest.onBeforeSendHeaders : chrome.webRequest.onBeforeRequest; ev.addListener(checkForRedirect, filter, ["blocking"]); diff --git a/js/controllers/deleteredirect.js b/js/controllers/deleteredirect.js new file mode 100644 index 0000000..f480339 --- /dev/null +++ b/js/controllers/deleteredirect.js @@ -0,0 +1,25 @@ +redirectorApp.controller('DeleteRedirectCtrl', ['$scope', function($s) { + + // Ok, this is pretty ugly. But I want to make this controller to control + // everything about the deleting process, so I make this available on + // the parent scope, so the RedirectListCtrl can access it. + $s.$parent.confirmDeleteRedirect = function(index) { + $s.redirect = $s.redirects[index]; + $s.deleteIndex = index; + $s.$parent.showDeleteForm = true; + }; + + $s.cancelDelete = function(index) { + delete $s.redirect; + delete $s.deletingIndex; + $s.$parent.showDeleteForm = false; + } + + $s.deleteRedirect = function() { + $s.redirects.splice($s.deletingIndex, 1); + delete $s.redirect; + delete $s.deletingIndex; + $s.$parent.showDeleteForm = false; + $s.saveChanges(); + }; +}]);
\ No newline at end of file diff --git a/js/controllers/editredirect.js b/js/controllers/editredirect.js new file mode 100644 index 0000000..f24f3e6 --- /dev/null +++ b/js/controllers/editredirect.js @@ -0,0 +1,82 @@ +redirectorApp.controller('EditRedirectCtrl', ['$scope', function($s) { + + + $s.requestTypes = Redirect.requestTypes; + + // Ok, this is pretty ugly. But I want to make this controller to control + // everything about the editing process, so I make this available on + // the parent scope, so the RedirectListCtrl can access it. + $s.$parent.editRedirect = function(index) { + $s.redirect = new Redirect($s.redirects[index]); + $s.editIndex = index; + $s.redirect.updateExampleResult(); + if ($s.redirect.escapeMatches || $s.redirect.unescapeMatches || $s.redirect.excludePattern + || !($s.redirect.appliesTo.length == 1 && $s.redirect.appliesTo[0] == "main_frame")) { + $s.showAdvanced = true; //Auto show advanced if redirect uses advanced options + } + $s.$parent.showEditForm = true; + }; + + // Same, this is for the Create New button, which is starting + // the edit form, so I want to control it from here. + $s.$parent.createNewRedirect = function() { + $s.redirect = new Redirect({}); + $s.$parent.showEditForm = true; + }; + + $s.saveRedirect = function() { + if ($s.redirect.error) { + return; //Button is already disabled, but we still get the click + } + + if ($s.editIndex >= 0) { + $s.redirects[$s.editIndex] = $s.redirect; + } else { + $s.redirects.push($s.redirect); + } + closeEditForm(); + $s.saveChanges(); + }; + + $s.cancelEdit = function() { + closeEditForm(); + } + + // To bind a list of strings to a list of checkboxes + $s.appliesTo = function(key) { + if (!$s.redirect) { + return; + } + return $s.redirect.appliesTo.indexOf(key) != -1; + }; + + // Add or remove string from array based on whether checkbox is checked + $s.toggleApplies = function(key) { + if (!$s.redirect) { + return; + } + var arr = $s.redirect.appliesTo; + + var index = arr.indexOf(key); + if (index == -1) { + arr.push(key); + } else { + arr.splice(index, 1); + } + + var order = 'main_frame,sub_frame,stylesheet,script,image,object,xmlhttprequest,other'; + + arr.sort(function(a,b) { + return order.indexOf(a) - order.indexOf(b); + }); + + $s.redirect.updateExampleResult(); + }; + + function closeEditForm() { + $s.editIndex = -1; + $s.redirect = null; + $s.showAdvanced = false; + $s.$parent.showEditForm = false; + } +}]); diff --git a/js/controllers/importexport.js b/js/controllers/importexport.js new file mode 100644 index 0000000..191990c --- /dev/null +++ b/js/controllers/importexport.js @@ -0,0 +1,110 @@ + +//This controller, and associated directives and config, are responsible for import and exporting redirects +//from .json files. + +redirectorApp.config([ + '$compileProvider', + function($compileProvider) { + $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|chrome-extension|data):/); + }] +).directive('fileselected', function() { //Directive for file upload: + return { + restrict: 'A', + link: function(scope, element, attr, ctrl) { + element.bind('change', function(e) { + var f = element[0].files[0]; + element[0].value = ''; + scope.$eval(attr.fileselected, {'$file':f}); + }); + } + } +}).controller('ImportExportCtrl', ['$scope', function($s) { + + // Shows a message explaining how many redirects were imported. + function showImportedMessage(imported, existing) { + if (imported == 0 && existing == 0) { + $s.showMessage('No redirects existed in the file.'); + } + if (imported > 0 && existing == 0) { + $s.showMessage('Successfully imported ' + imported + ' redirect' + (imported > 1 ? 's.' : '.'), true); + } + if (imported == 0 && existing > 0) { + $s.showMessage('All redirects in the file already existed and were ignored.'); + } + if (imported > 0 && existing > 0) { + var m = 'Successfully imported ' + imported + ' redirect' + (imported > 1 ? 's' : '') + '. '; + if (existing == 1) { + m += '1 redirect already existed and was ignored.'; + } else { + m += existing + ' redirects already existed and were ignored.'; + } + $s.showMessage(m, true); + } + } + + $s.importRedirects = function(file) { + if (!file) { + return; + } + var reader = new FileReader(); + + reader.onload = function(e) { + var data; + try { + var data = JSON.parse(reader.result); + } catch(e) { + $s.showMessage('Failed to parse JSON data, invalid JSON: ' + (e.message||'').substr(0,100)); + return $s.$parent.$apply(); + } + + if (!data.redirects) { + $s.showMessage('Invalid JSON, missing "redirects" property'); + return $s.$parent.$apply(); + } + + var imported = 0, existing = 0; + for (var i = 0; i < data.redirects.length; i++) { + var r = new Redirect(data.redirects[i]); + + if ($s.redirects.some(function(i) { return new Redirect(i).equals(r);})) { + existing++; + } else { + $s.redirects.push(r.toObject()); + imported++; + } + } + + showImportedMessage(imported, existing); + + $s.saveChanges(); + $s.$parent.$apply(); + }; + try { + reader.readAsText(file, 'utf-8'); + } catch(e) { + $s.showMessage('Failed to read import file'); + } + } + + // Updates the export link with a data url containing all the redirects. + // We want to have the href updated instead of just generated on click to + // allow people to right click and choose Save As... + $s.updateExportLink = function() { + var redirects = $s.redirects.map(function(r) { + return new Redirect(r).toObject(); + }); + + var exportObj = { + createdBy : 'Redirector v' + chrome.app.getDetails().version, + createdAt : new Date(), + redirects : redirects + }; + + var json = JSON.stringify(exportObj, null, 4); + + //Using encodeURIComponent here instead of base64 because base64 always messed up our encoding for some reason... + $s.redirectDownload = 'data:text/plain;charset=utf-8,' + encodeURIComponent(json); + } + + $s.updateExportLink(); //Run once so the a will have a href to begin with +}]);
\ No newline at end of file diff --git a/js/controllers/listredirects.js b/js/controllers/listredirects.js new file mode 100644 index 0000000..008a527 --- /dev/null +++ b/js/controllers/listredirects.js @@ -0,0 +1,44 @@ +// This controller is responsible for the list of redirects and the actions +// that can be taken from there. +redirectorApp.filter('requestTypeDisplay', function() { //Filter for displaying nice names for request types + return function(input) { + return input.map(function(key) { return Redirect.requestTypes[key]; }).join(', '); + } +}).controller('ListRedirectsCtrl', ['$scope', function($s) { + + function swap(arr, i, n) { + var item = arr[i]; + arr[i] = arr[n]; + arr[n] = item; + } + + // Move the redirect at index up in the list, giving it higher priority + $s.moveUp = function(index) { + if (index == 0) { + return; + } + swap($s.redirects, index, index-1); + $s.saveChanges(); + }; + + // Move the redirect at index down in the list, giving it lower priority + $s.moveDown = function(index) { + if (index == $s.redirects.length-1) { + return; + } + swap($s.redirects, index, index+1); + $s.saveChanges(); + }; + + $s.toggleDisabled = function(redirect) { + redirect.disabled = !redirect.disabled; + $s.saveChanges(); + }; + + $s.example = function(redirect) { + return new Redirect(redirect).getMatch(redirect.exampleUrl).redirectTo; + }; + + //Edit button is defined in EditRedirectCtrl + //Delete button is defined in DeleteRedirectCtrl +}]);
\ No newline at end of file diff --git a/js/controllers/redirectorpage.js b/js/controllers/redirectorpage.js new file mode 100644 index 0000000..05fbe9f --- /dev/null +++ b/js/controllers/redirectorpage.js @@ -0,0 +1,50 @@ +// This is the main controller of the page. It is responsible for showing messages, +// modal windows and loading and saving the list of redirects, that all of the +// controllers work with. +redirectorApp.controller('RedirectorPageCtrl', ['$scope', '$timeout', function($s, $timeout) { + + $s.deleting = null; //Variable for redirect being edited, of the form { index:<nr>, redirect:<redirect>}; + $s.showEditForm = $s.showDeleteForm = false; // Variables, child controllers can set them to show their forms + + var storage = chrome.storage.local; //TODO: Change to sync when Firefox supports it... + + function normalize(r) { + return new Redirect(r).toObject(); //Cleans out any extra props, and adds default values for missing ones. + } + + // Saves the entire list of redirects to storage. + $s.saveChanges = function() { + + // Clean them up so angular $$hash things and stuff don't get serialized. + var arr = $s.redirects.map(normalize); + + storage.set({redirects:arr}, function() { + console.log('Saved redirects at ' + new Date()); + }); + } + + $s.redirects = []; + storage.get('redirects', function(results) { + if (!results || !results.redirects) { + return; + } + + for (var i=0; i < results.redirects.length; i++) { + $s.redirects.push(normalize(results.redirects[i])); + } + $s.$apply(); + }); + + // Shows a message bar above the list of redirects. + $s.showMessage = function(message, success) { + $s.message = message; + $s.messageType = success ? 'success' : 'error'; + + //Remove the message in 20 seconds if it hasn't been changed... + $timeout(function() { + if ($s.message == message) { + $s.message = null; + } + }, 20 * 1000); + } +}]); diff --git a/js/popup.js b/js/popup.js new file mode 100644 index 0000000..fbff178 --- /dev/null +++ b/js/popup.js @@ -0,0 +1,13 @@ +function hi() { + chrome.browserAction.setIcon({ + path: { + 19: "images/icon19disabled.png", + 38: "images/icon38disabled.png" + } + }); + open('redirector.html'); +} + +document.addEventListener('DOMContentLoaded', function() { + document.getElementsByTagName('button')[0].addEventListener('click', hi); +})
\ No newline at end of file diff --git a/js/redirect.js b/js/redirect.js index 41e3c1d..b895ed9 100644 --- a/js/redirect.js +++ b/js/redirect.js @@ -7,11 +7,24 @@ function Redirect(o) { Redirect.WILDCARD = 'W'; Redirect.REGEX = 'R'; +Redirect.requestTypes = { + main_frame: "Main window (address bar)", + sub_frame: "IFrames", + stylesheet : "Stylesheets", + script : "Scripts", + image : "Images", + object : "Objects (e.g. Flash videos, Java applets)", + xmlhttprequest : "XMLHttpRequests (Ajax)", + other : "Other" +}; + Redirect.prototype = { //attributes description : '', exampleUrl : '', + exampleResult : '', + error : null, includePattern : '', excludePattern : '', redirectUrl : '', @@ -21,8 +34,16 @@ Redirect.prototype = { disabled : false, compile : function() { - this._rxInclude = this._compile(this._includePattern); - this._rxExclude = this._compile(this._excludePattern); + + var incPattern = this._preparePattern(this.includePattern); + var excPattern = this._preparePattern(this.excludePattern); + + if (incPattern) { + this._rxInclude = new RegExp(incPattern, 'gi'); + } + if (excPattern) { + this._rxExclude = new RegExp(excPattern, 'gi'); + } }, equals : function(redirect) { @@ -41,6 +62,8 @@ Redirect.prototype = { return { description : this.description, exampleUrl : this.exampleUrl, + exampleResult : this.exampleResult, + error : this.error, includePattern : this.includePattern, excludePattern : this.excludePattern, redirectUrl : this.redirectUrl, @@ -52,7 +75,10 @@ Redirect.prototype = { }; }, - getMatch: function(url) { + getMatch: function(url, forceIgnoreDisabled) { + if (!this._rxInclude) { + this.compile(); + } var result = { isMatch : false, isExcludeMatch : false, @@ -64,7 +90,7 @@ Redirect.prototype = { redirectTo = this._includeMatch(url); if (redirectTo !== null) { - if (this.disabled) { + if (this.disabled && !forceIgnoreDisabled) { result.isDisabledMatch = true; } else if (this._excludeMatch(url)) { result.isExcludeMatch = true; @@ -76,6 +102,66 @@ Redirect.prototype = { return result; }, + //Updates the .exampleResult field or the .error + //field depending on if the example url and patterns match + //and make a good redirect + updateExampleResult : function() { + + //Default values + this.error = null; + this.exampleResult = ''; + + + if (!this.exampleUrl) { + this.error = 'No example URL defined'; + return; + } + + if (this.patternType == Redirect.REGEX && this.includePattern) { + try { + new RegExp(this.includePattern, 'gi'); + } catch(e) { + this.error = 'Invalid regular expression in Include pattern.'; + return; + } + } + + if (this.patternType == Redirect.REGEX && this.excludePattern) { + try { + new RegExp(this.excludePattern, 'gi'); + } catch(e) { + this.error = 'Invalid regular expression in Exclude pattern.'; + return; + } + } + + if (!this.appliesTo || this.appliesTo.length == 0) { + this.error = 'At least one request type must be chosen.'; + return; + } + + this.compile(); + + var match = this.getMatch(this.exampleUrl, true); + + if (match.isExcludeMatch) { + this.error = 'The exclude pattern excludes the example url.' + return; + } + + if (!match.isMatch) { + this.error = 'The include pattern does not match the example url.'; + return; + } + + this.exampleResult = match.redirectTo; + + if (this.getMatch(this.exampleResult, true).isMatch) { + this.exampleResult = ''; + this.error = 'Result matches the redirect again, causing endless loop.' + } + }, + isRegex: function() { return this.patternType == Redirect.REGEX; }, @@ -89,14 +175,13 @@ Redirect.prototype = { }, //Private functions below - - _includePattern : null, - _excludePattern : null, - _patternType : null, _rxInclude : null, _rxExclude : null, _preparePattern : function(pattern) { + if (!pattern) { + return null; + } if (this.patternType == Redirect.REGEX) { return pattern; } else { //Convert wildcard to regex pattern @@ -115,13 +200,6 @@ Redirect.prototype = { return converted; } }, - - _compile : function(pattern) { - if (!pattern) { - return null; - } - return new RegExp(this._preparePattern(pattern),"gi"); - }, _init : function(o) { this.description = o.description || '', @@ -141,16 +219,7 @@ Redirect.prototype = { }, toString : function() { - return 'REDIRECT: {' - + '\n\tExample url : ' + this.exampleUrl - + '\n\tInclude pattern : ' + this.includePattern - + '\n\tExclude pattern : ' + this.excludePattern - + '\n\tRedirect url : ' + this.redirectUrl - + '\n\tPattern type : ' + this.patternType - + '\n\tUnescape matches : ' + this.unescapeMatches - + '\n\tEscape matches : ' + this.escapeMatches - + '\n\tDisabled : ' + this.disabled - + '\n}\n'; + return JSON.stringify(this.toObject(), null, 2); }, _includeMatch : function(url) { |