aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--css/redirector.css27
-rw-r--r--icon.html68
-rw-r--r--images/icon128active.pngbin0 -> 1289 bytes
-rw-r--r--images/icon128disabled.pngbin0 -> 1275 bytes
-rw-r--r--images/icon16active.pngbin0 -> 272 bytes
-rw-r--r--images/icon16disabled.pngbin0 -> 274 bytes
-rw-r--r--images/icon19active.pngbin0 -> 285 bytes
-rw-r--r--images/icon19disabled.pngbin0 -> 285 bytes
-rw-r--r--images/icon32active.pngbin0 -> 402 bytes
-rw-r--r--images/icon32disabled.pngbin0 -> 399 bytes
-rw-r--r--images/icon38active.pngbin0 -> 417 bytes
-rw-r--r--images/icon38disabled.pngbin0 -> 424 bytes
-rw-r--r--images/icon48active.pngbin0 -> 464 bytes
-rw-r--r--images/icon48disabled.pngbin0 -> 474 bytes
-rw-r--r--images/redirector.pngbin1462 -> 0 bytes
-rw-r--r--images/statusactive.pngbin340 -> 0 bytes
-rw-r--r--images/statusinactive.pngbin621 -> 0 bytes
-rw-r--r--js/app.js270
-rw-r--r--js/background.js4
-rw-r--r--js/controllers/deleteredirect.js25
-rw-r--r--js/controllers/editredirect.js82
-rw-r--r--js/controllers/importexport.js110
-rw-r--r--js/controllers/listredirects.js44
-rw-r--r--js/controllers/redirectorpage.js50
-rw-r--r--js/popup.js13
-rw-r--r--js/redirect.js119
-rw-r--r--manifest.json19
-rw-r--r--popup.html14
-rw-r--r--redirector.html84
29 files changed, 600 insertions, 329 deletions
diff --git a/css/redirector.css b/css/redirector.css
index 6997d32..a439196 100644
--- a/css/redirector.css
+++ b/css/redirector.css
@@ -13,7 +13,11 @@ h1, h2, h3, h5, h6 {
h1 {
font-size:55px;
- margin:20px 0px 0px 0px;
+ margin:-15px 0px 0px 0px;
+ -webkit-user-select:none;
+ -moz-user-select:none;
+ cursor:default;
+ padding:0px;
}
h3 {
@@ -30,7 +34,7 @@ h4 {
h5 {
font-size:20px;
- margin:0;
+ margin:-20px 0 0 0;
color:#5e6163;
}
@@ -84,6 +88,7 @@ input[type="radio"] {
.btn.grey:hover {
background:#333;
+ border:solid 1px #333;
}
.btn.red {
@@ -92,6 +97,7 @@ input[type="radio"] {
.btn.red:hover {
background:rgb(208,52,37);
+ border:solid 1px rgb(208,52,37);
}
.btn.blue {
@@ -100,6 +106,7 @@ input[type="radio"] {
.btn.blue:hover {
background:rgb(21,90,233);
+ border:solid 1px rgb(21,90,233);
}
.btn.green {
@@ -108,6 +115,7 @@ input[type="radio"] {
.btn.green:hover {
background:green;
+ border:solid 1px green;
}
/* Main menu with buttons */
@@ -193,6 +201,7 @@ input[type="radio"] {
.redirect-row a, .redirect-row a:visited {
font-size:13px;
margin-top:5px;
+ padding:2px;
width:60px;
}
@@ -240,6 +249,7 @@ input[type="radio"] {
a.disabled:hover {
cursor:default;
color:#bbb !important;
+ border:solid 1px #bbb !important;
background:white !important;
}
@@ -287,14 +297,24 @@ a.disabled:hover {
#edit-redirect-form .second-column {
display:block;
- padding-left: 134px;
+ margin-left: 133px;
}
#edit-redirect-form div input[type='text'] {
width:300px;
}
+.error {
+ color:red;
+}
+
+.placeholder {
+ color:#c0c0c0;
+ font-size:11px;
+}
+
#advanced-toggle {
+ padding-top:3px;
text-align: center;
}
@@ -318,6 +338,7 @@ a[ng-click] {
.button-container {
margin-top:20px;
text-align: center;
+ padding-bottom:10px;
}
/* Footer with link */
diff --git a/icon.html b/icon.html
new file mode 100644
index 0000000..579a6a9
--- /dev/null
+++ b/icon.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <style>
+ a {
+ display:block;
+ margin:10px auto;
+ padding:0px;
+ }
+
+ img {
+ margin:0;
+ padding:0;
+ border:solid 1px green;
+ }
+
+
+ </style>
+</head>
+<body>
+
+ <script>
+ var data = {
+ 16: [ 32, 0, 16],
+ 19: [ 35, 1, 18],
+ 32: [ 64, 1, 32],
+ 38: [ 75, 2, 38],
+ 48: [ 95, 1, 48],
+ 128: [260, 2, 131],
+ };
+
+
+ function createImageLink(size, logoFontSize, logoX, logoY) {
+ var colors = {'#333' : 'active', '#bbb' : 'disabled'};
+ for (var color in colors) {
+ var canvas = document.createElement('canvas');
+ canvas.width = canvas.height = size;
+ ctx = canvas.getContext('2d');
+ ctx.fillStyle = color;
+ ctx.font = 'Bold ' + logoFontSize + 'px Arial';
+ ctx.fillText('☈', logoX, logoY);
+
+
+ var a = document.createElement('a');
+ var img = document.createElement('img');
+ img.src = canvas.toDataURL();
+ a.href = canvas.toDataURL();
+ a.download = 'icon' + size + colors[color] + '.png';
+ a.appendChild(img);
+ a.style.width = size + 'px'
+ document.body.appendChild(a);
+ }
+ }
+
+ document.addEventListener('DOMContentLoaded', function() {
+ createImageLink(16, 32, 0, 16);
+ createImageLink(19, 35, 1, 18);
+ createImageLink(32, 64, 1, 32);
+ createImageLink(38, 75, 2, 38);
+ createImageLink(48, 95, 1, 48);
+ createImageLink(128, 260, 2, 131);
+
+ });
+
+ </script>
+</body>
+</html> \ No newline at end of file
diff --git a/images/icon128active.png b/images/icon128active.png
new file mode 100644
index 0000000..fa723bf
--- /dev/null
+++ b/images/icon128active.png
Binary files differ
diff --git a/images/icon128disabled.png b/images/icon128disabled.png
new file mode 100644
index 0000000..0f7d19a
--- /dev/null
+++ b/images/icon128disabled.png
Binary files differ
diff --git a/images/icon16active.png b/images/icon16active.png
new file mode 100644
index 0000000..9482d4b
--- /dev/null
+++ b/images/icon16active.png
Binary files differ
diff --git a/images/icon16disabled.png b/images/icon16disabled.png
new file mode 100644
index 0000000..23d6675
--- /dev/null
+++ b/images/icon16disabled.png
Binary files differ
diff --git a/images/icon19active.png b/images/icon19active.png
new file mode 100644
index 0000000..def8e2f
--- /dev/null
+++ b/images/icon19active.png
Binary files differ
diff --git a/images/icon19disabled.png b/images/icon19disabled.png
new file mode 100644
index 0000000..f5eaa06
--- /dev/null
+++ b/images/icon19disabled.png
Binary files differ
diff --git a/images/icon32active.png b/images/icon32active.png
new file mode 100644
index 0000000..2bc01ff
--- /dev/null
+++ b/images/icon32active.png
Binary files differ
diff --git a/images/icon32disabled.png b/images/icon32disabled.png
new file mode 100644
index 0000000..28d2dc0
--- /dev/null
+++ b/images/icon32disabled.png
Binary files differ
diff --git a/images/icon38active.png b/images/icon38active.png
new file mode 100644
index 0000000..2e08aec
--- /dev/null
+++ b/images/icon38active.png
Binary files differ
diff --git a/images/icon38disabled.png b/images/icon38disabled.png
new file mode 100644
index 0000000..7e900ba
--- /dev/null
+++ b/images/icon38disabled.png
Binary files differ
diff --git a/images/icon48active.png b/images/icon48active.png
new file mode 100644
index 0000000..f63956b
--- /dev/null
+++ b/images/icon48active.png
Binary files differ
diff --git a/images/icon48disabled.png b/images/icon48disabled.png
new file mode 100644
index 0000000..e2826c4
--- /dev/null
+++ b/images/icon48disabled.png
Binary files differ
diff --git a/images/redirector.png b/images/redirector.png
deleted file mode 100644
index f8de12c..0000000
--- a/images/redirector.png
+++ /dev/null
Binary files differ
diff --git a/images/statusactive.png b/images/statusactive.png
deleted file mode 100644
index 3127229..0000000
--- a/images/statusactive.png
+++ /dev/null
Binary files differ
diff --git a/images/statusinactive.png b/images/statusinactive.png
deleted file mode 100644
index 4c0438e..0000000
--- a/images/statusinactive.png
+++ /dev/null
Binary files differ
diff --git a/js/app.js b/js/app.js
index 00c23fb..c6cf731 100644
--- a/js/app.js
+++ b/js/app.js
@@ -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) {
diff --git a/manifest.json b/manifest.json
index 06ca42a..20d0b2f 100644
--- a/manifest.json
+++ b/manifest.json
@@ -3,6 +3,11 @@
"manifest_version": 2,
"name": "Redirector",
"version": "3.0",
+
+ "icons": { "16": "images/icon16active.png",
+ "32": "images/icon32active.png",
+ "48": "images/icon48active.png",
+ "128": "images/icon128active.png" },
"permissions" : ["webRequest", "webRequestBlocking", "storage", "http://*/*", "https://*/*"],
"applications": {
@@ -14,5 +19,19 @@
"background": {
"scripts": ["js/background.js"],
"persistent": true
+ },
+
+ "options_ui": {
+ "page": "popup.html",
+ "chrome_style": true
+ },
+
+ "browser_action": {
+ "default_icon": {
+ "19": "images/icon19active.png",
+ "38": "images/icon38active.png"
+ },
+ "default_title": "Redirector",
+ "default_popup": "popup.html"
}
}
diff --git a/popup.html b/popup.html
new file mode 100644
index 0000000..5e7e81f
--- /dev/null
+++ b/popup.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Redirector</title>
+ <link rel="stylesheet" href="css/popup.css" />
+ <meta charset="UTF-8">
+ <script src="js/angular.min.js"></script>
+ <script src="js/popup.js"></script>
+ </head>
+ <body >
+ <h1>Redirector</h1>
+ <button>Disable</button>
+ </body>
+</html>
diff --git a/redirector.html b/redirector.html
index aa0ccbc..4f06438 100644
--- a/redirector.html
+++ b/redirector.html
@@ -7,62 +7,72 @@
<script src="js/angular.min.js"></script>
<script src="js/redirect.js"></script>
<script src="js/app.js"></script>
+ <script src="js/controllers/redirectorpage.js"></script>
+ <script src="js/controllers/editredirect.js"></script>
+ <script src="js/controllers/deleteredirect.js"></script>
+ <script src="js/controllers/importexport.js"></script>
+ <script src="js/controllers/listredirects.js"></script>
</head>
- <body ng-app="redirectorApp" ng-controller="redirectorController">
- <div id="cover" ng-show="showModal">
- </div>
- <div id="delete-redirect-form" ng-show="deleting">
+ <body ng-app="redirectorApp" ng-controller="RedirectorPageCtrl">
+ <div id="cover" ng-show="showEditForm || showDeleteForm"></div>
+
+ <!-- Confirmation form for deleting redirects -->
+ <div id="delete-redirect-form" ng-show="showDeleteForm" ng-controller="DeleteRedirectCtrl">
<h3>Are you sure you want to delete this redirect?</h3>
<div>
<label>Description:</label>
- <span>{{deleting.description}}</span>
+ <span>{{redirect.description}}</span>
</div>
<div>
<label>Example URL:</label>
- <span>{{deleting.exampleUrl}}</span>
+ <span>{{redirect.exampleUrl}}</span>
</div>
<div>
<label>Include pattern:</label>
- <span>{{deleting.includePattern}}
+ <span>{{redirect.includePattern}}
</div>
<div>
<label>Redirect to:</label>
- <span>{{deleting.redirectUrl}}</span>
+ <span>{{redirect.redirectUrl}}</span>
</div>
<div>
<label>Pattern type:</label>
- <span>{{deleting.patternType == 'W' ? 'Wildcard' : 'Regular Expression'}}</span>
+ <span>{{redirect.patternType == 'W' ? 'Wildcard' : 'Regular Expression'}}</span>
</div>
<div class="button-container">
<a class="btn red large" ng-click="deleteRedirect()">Yes, delete it</a>
<a class="btn grey large" ng-click="cancelDelete()">No, don't delete it</a>
</div>
</div>
- <div id="edit-redirect-form" ng-show="activeRedirect">
+
+
+ <!-- Form for creating and editing redirects -->
+ <div id="edit-redirect-form" ng-show="showEditForm" ng-controller="EditRedirectCtrl">
<h3>{{editIndex >= 0 ? 'Edit' : 'Create'}} Redirect</h3>
<div>
<label>Description:</label>
- <input type="text" ng-model="activeRedirect.description" placeholder="Enter a description for your redirect rule" />
+ <input type="text" ng-model="redirect.description" placeholder="Enter a description for your redirect rule" />
</div>
<div>
<label>Example URL:</label>
- <input type="text" ng-model="activeRedirect.exampleUrl" placeholder="http://example.com/some/path?a=1" />
+ <input type="text" ng-model="redirect.exampleUrl" ng-change="redirect.updateExampleResult()" placeholder="http://example.com/some/path?a=1" />
</div>
<div>
<label>Include pattern:</label>
- <input type="text" ng-model="activeRedirect.includePattern" placeholder="Pattern that matches the urls you want to redirect" />
+ <input type="text" ng-model="redirect.includePattern" ng-change="redirect.updateExampleResult()" placeholder="Pattern that matches the urls you want to redirect" />
</div>
<div>
<label>Redirect to:</label>
- <input type="text" ng-model="activeRedirect.redirectUrl" placeholder="The url you want to redirect requests to" />
+ <input type="text" ng-model="redirect.redirectUrl" ng-change="redirect.updateExampleResult()" placeholder="The url you want to redirect requests to" />
</div>
<div>
<label>Pattern type:</label>
- <label id="wildcardtype"><input type="radio" ng-model="activeRedirect.patternType" name="patterntype" value="W">Wildcard</label>
- <label><input type="radio" ng-model="activeRedirect.patternType" name="patterntype" value="R">Regular Expression</label>
+ <label id="wildcardtype"><input type="radio" ng-change="redirect.updateExampleResult()" ng-model="redirect.patternType" name="patterntype" value="W">Wildcard</label>
+ <label><input type="radio" ng-change="redirect.updateExampleResult()" ng-model="redirect.patternType" name="patterntype" value="R">Regular Expression</label>
</div>
<div>
<label>Example result:</label>
+ <span class="error" ng-show="redirect.error">{{redirect.error}}</span><span ng-show="redirect.exampleResult">{{redirect.exampleResult}}</span>
</div>
<div id="advanced-toggle">
<a ng-click="showAdvanced=true" ng-show="!showAdvanced">Show advanced options...</a>
@@ -71,15 +81,15 @@
<div id="advanced" ng-show="showAdvanced">
<div>
<label>Exclude pattern:</label>
- <input type="text" ng-model="activeRedirect.excludePattern" placeholder="Pattern to exclude certain urls from the redirection"/>
+ <input type="text" ng-change="redirect.updateExampleResult()" ng-model="redirect.excludePattern" placeholder="Pattern to exclude certain urls from the redirection"/>
</div>
<div>
<label for="unescape-matches">Unescape matches:</label>
- <input id="unescape-matches" ng-model="activeRedirect.unescapeMatches" type="checkbox">
+ <input id="unescape-matches" ng-change="redirect.updateExampleResult()" ng-model="redirect.unescapeMatches" type="checkbox"><span class="placeholder">E.g. turn %2Fbar%2Ffoo%3Fx%3D2 into /bar/foo?x=2</span>
</div>
<div>
<label for="escape-matches">Escape matches:</label>
- <input id="escape-matches" ng-model="activeRedirect.escapeMatches" type="checkbox">
+ <input id="escape-matches" ng-change="redirect.updateExampleResult()" ng-model="redirect.escapeMatches" type="checkbox"><span class="placeholder">E.g. turn /bar/foo?x=2 into %2Fbar%2Ffoo%3Fx%3D2</span>
</div>
<div>
<label>Apply to:</label>
@@ -89,23 +99,27 @@
</div>
</div>
<div class="button-container">
- <a class="btn green medium" ng-click="saveRedirect()">Save</a>
- <a class="btn red medium" ng-click="cancelEdit()">Cancel</a>
+ <a ng-class="{disabled:redirect.error}" class="btn green large" ng-click="saveRedirect()">Save</a>
+ <a class="btn red large" ng-click="cancelEdit()">Cancel</a>
</div>
</div>
- <div id="blur-wrapper" ng-class="{blur: showModal}">
- <h1>Redirector</h1>
- <!--<h3>DISABLED</h3>-->
+ <div id="blur-wrapper" ng-class="{blur: showEditForm || showDeleteForm}">
+
+ <h1><span style="font-size:100px; position:relative; top:4px">☈</span>edirector</h1>
<h5>Go where <em>YOU</em> want!</h5>
-
<div id="menu">
- <input type="file" id="import-file" fileselected="importRedirects($file)" accept=".rjson,.json,.txt,text/*" />
- <a class="btn blue large" ng-click="createNew()">Create new redirect</a>
- <label for="import-file" class="btn blue large">Import</label>
- <a class="btn blue large" ng-href="{{redirectDownload}}" download="Redirector.rjson">Export</a>
+ <a class="btn blue large" ng-click="createNewRedirect()">Create new redirect</a>
+
+ <!-- Importing/Exporting of redirects -->
+ <span ng-controller="ImportExportCtrl">
+ <input type="file" id="import-file" fileselected="importRedirects($file)" accept=".rjson,.json,.txt,text/*" />
+ <label for="import-file" class="btn blue large">Import</label>
+ <a class="btn blue large" ng-mousedown="updateExportLink()" ng-href="{{redirectDownload}}" download="Redirector.json">Export</a>
+ </span>
+
<a class="btn blue large" href="help.html" target="_blank">Help</a>
</div>
@@ -113,20 +127,22 @@
{{message}}
<a ng-click="message=null">&#x2716;</a>
</div>
- <div class="redirect-table">
+
+ <!-- List of existing redirects -->
+ <div class="redirect-table" ng-controller="ListRedirectsCtrl">
<div class="redirect-row" ng-class="{disabled: r.disabled}" ng-repeat="r in redirects">
<h4><span class="disabled-marker" ng-show="r.disabled">[Disabled] </span><span>{{r.description}}</span></h4>
<div>
<label>Redirect:</label> <span>{{r.includePattern}}</span>
</div>
<div>
- <label>to:</label> <span>foo.is</span>
+ <label>to:</label> <span>{{r.redirectUrl}}</span>
</div>
<div ng-show="r.excludePattern">
<label>excluding:</label> <span>asdfasdf</span>
</div>
<div>
- <label>Example:</label> <span></span>
+ <label>Example:</label> <span>{{example(r)}}</span>
</div>
<div>
<label>Applies to:</label> <span>{{r.appliesTo|requestTypeDisplay}}</span>
@@ -134,15 +150,17 @@
<div>
<a class="btn blue" ng-click="toggleDisabled(r)">{{r.disabled ? "Enable" : "Disable"}}</a>
<a class="btn green" ng-click="editRedirect($index)">Edit</a>
- <a class="btn red" ng-click="confirmDelete($index)">Delete</a>
+ <a class="btn red" ng-click="confirmDeleteRedirect($index)">Delete</a>
<a class="btn grey move-up-btn" ng-class="{disabled:$first}" ng-click="moveUp($index)">▲</a>
<a class="btn grey move-down-btn" ng-class="{disabled:$last}" ng-click="moveDown($index)">▼</a>
</div>
</div>
</div>
+
<footer>
<small>Redirector is created by <a target="_blank" href="http://einaregilsson.com">Einar Egilsson</a></small>
</footer>
+
</div>
</body>
</html>