diff options
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 Binary files differnew file mode 100644 index 0000000..fa723bf --- /dev/null +++ b/images/icon128active.png diff --git a/images/icon128disabled.png b/images/icon128disabled.png Binary files differnew file mode 100644 index 0000000..0f7d19a --- /dev/null +++ b/images/icon128disabled.png diff --git a/images/icon16active.png b/images/icon16active.png Binary files differnew file mode 100644 index 0000000..9482d4b --- /dev/null +++ b/images/icon16active.png diff --git a/images/icon16disabled.png b/images/icon16disabled.png Binary files differnew file mode 100644 index 0000000..23d6675 --- /dev/null +++ b/images/icon16disabled.png diff --git a/images/icon19active.png b/images/icon19active.png Binary files differnew file mode 100644 index 0000000..def8e2f --- /dev/null +++ b/images/icon19active.png diff --git a/images/icon19disabled.png b/images/icon19disabled.png Binary files differnew file mode 100644 index 0000000..f5eaa06 --- /dev/null +++ b/images/icon19disabled.png diff --git a/images/icon32active.png b/images/icon32active.png Binary files differnew file mode 100644 index 0000000..2bc01ff --- /dev/null +++ b/images/icon32active.png diff --git a/images/icon32disabled.png b/images/icon32disabled.png Binary files differnew file mode 100644 index 0000000..28d2dc0 --- /dev/null +++ b/images/icon32disabled.png diff --git a/images/icon38active.png b/images/icon38active.png Binary files differnew file mode 100644 index 0000000..2e08aec --- /dev/null +++ b/images/icon38active.png diff --git a/images/icon38disabled.png b/images/icon38disabled.png Binary files differnew file mode 100644 index 0000000..7e900ba --- /dev/null +++ b/images/icon38disabled.png diff --git a/images/icon48active.png b/images/icon48active.png Binary files differnew file mode 100644 index 0000000..f63956b --- /dev/null +++ b/images/icon48active.png diff --git a/images/icon48disabled.png b/images/icon48disabled.png Binary files differnew file mode 100644 index 0000000..e2826c4 --- /dev/null +++ b/images/icon48disabled.png diff --git a/images/redirector.png b/images/redirector.png Binary files differdeleted file mode 100644 index f8de12c..0000000 --- a/images/redirector.png +++ /dev/null diff --git a/images/statusactive.png b/images/statusactive.png Binary files differdeleted file mode 100644 index 3127229..0000000 --- a/images/statusactive.png +++ /dev/null diff --git a/images/statusinactive.png b/images/statusinactive.png Binary files differdeleted file mode 100644 index 4c0438e..0000000 --- a/images/statusinactive.png +++ /dev/null @@ -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">✖</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> |