diff --git a/css/help.css b/css/help.css index e104f93..f98a95b 100644 --- a/css/help.css +++ b/css/help.css @@ -1,106 +1,123 @@ body { - font-family:Arial, sans-serif; - font-size:16px; - line-height: 20px; - max-width:740px; - margin:20px auto; - border:solid 1px #bbb; - padding:50px; - background: white; - border-radius: 4px; - color: #2b2626; + font-family: Arial, sans-serif; + font-size: 1rem; + line-height: 20px; + width: auto; + border: solid 1px #bbb; + background: white; + border-radius: 4px; + color: #2b2626; } -a, a:visited { - color:rgb(21,90,233); - text-decoration: none; +a, +a:visited { + color: rgb(21, 90, 233); + text-decoration: none; } a:hover { - color:black; - text-decoration: underline; + color: black; + text-decoration: underline; } html { - background: #f8f8f8; + background: #f8f8f8; } h1 { - text-align: center; - font-size:38px; - margin-bottom:80px; + font-size: 3rem; + line-height: 3rem; + margin-bottom: 80px; + letter-spacing: 2.1px; +} + +h3{ + font-size: 1.5rem; + line-height: 3rem; } h4 { - font-size:24px; - margin-bottom:0px; - padding-bottom:0px; + font-size: 1.2rem; + margin-bottom: 0px; + padding-bottom: 0px; } li { - margin:2px; + margin: 2px; } .fields li { - margin:10px 4px; + margin: 10px 4px; } .url { - font-style: italic; + font-style: italic; } td.pattern { - font-weight: normal; - background:transparent; + font-weight: normal; + background: transparent; } th { - text-align:right; + text-align: right; } table { - margin:10px; - border:solid 1px #bbb; - padding:8px; - margin-bottom:30px; - border-radius: 3px; + margin: 10px; + border: solid 1px #bbb; + padding: 8px; + margin-bottom: 30px; + border-radius: 3px; } .pattern { - color:black; - font-weight: bold; - display: inline-block; - padding-left:2px; - padding-right:2px; - border-radius:3px; - background: #eee; + color: black; + font-weight: bold; + display: inline-block; + padding-left: 2px; + padding-right: 2px; + border-radius: 3px; + background: #eee; } /* Dark mode support */ @media (prefers-color-scheme: dark) { - - html { - background: rgb(32,33,36); - } - body { - background: rgb(42,43,46); - color: #ddd; - border: solid 1px #888; - } - th { - color: white; - font-weight: normal; - } - a, a:visited, a:hover { - color: rgb(138,179,241); - } - - h1, h2, h3, h4, strong { - color: white; - } - - tr .pattern { - color: rgb(53,180,75); - } - } \ No newline at end of file + html { + background: rgb(32, 33, 36); + } + body { + background: rgb(42, 43, 46); + color: #ddd; + border: solid 1px #888; + } + th { + color: white; + font-weight: normal; + } + a, + a:visited, + a:hover { + color: rgb(138, 179, 241); + } + + h1, + h2, + h3, + h4 { + text-align: center; + } + + h1, + h2, + h3, + h4, + strong { + color: white; + } + + tr .pattern { + color: rgb(53, 180, 75); + } +} diff --git a/css/popup.css b/css/popup.css index 573d18f..26834d9 100644 --- a/css/popup.css +++ b/css/popup.css @@ -1,88 +1,89 @@ - body { - width: 180px; - text-align: center; - font-family: Arial, sans-serif; + width: auto; + text-align: center; + font-family: Arial, sans-serif; } h1 { - margin-top: 4px; - font-size: 22px; - font-weight: bold; - letter-spacing: 1.5px; + margin-top: 4px; + font-size: 3rem; + font-weight: bold; + letter-spacing: 2.1px; } h1 span { - position: relative; - top: 1px; - font-size: 38px; + position: relative; + top: 1px; + font-size: 38px; } button { - margin: 5px auto !important; - display: block; - width: 90%; + margin: 5px auto !important; + font-size: 1.4rem; + display: block; + width: 90%; + min-height: 32px; + line-height: 29px; } .disabled { - margin-top: -16px; - height: 13px; - color: red; - font-size: 12px; + margin-top: -1.5rem; + color: red; + font-size: 2.5rem; + letter-spacing: 2.1px; } label { - margin: 6px auto; - display: block; - width: 85%; - text-align: left; - font-size: 12px !important; + margin: 6px auto; + display: block; + width: 85%; + text-align: left; + font-size: 12px !important; } /* Firefox only */ -@supports (-moz-appearance:none) { - label input { - position: relative; - top: 1px; - } +@supports (-moz-appearance: none) { + label input { + position: relative; + top: 1px; + } } button { - height: 20px; - border: solid 1px #aaa !important; - border-radius: 3px; - color: #333; + height: 20px; + border: solid 1px #aaa !important; + border-radius: 3px; + color: #333; } label span { - position: relative; - top: 1px; + position: relative; + top: 1px; } @media (prefers-color-scheme: dark) { + html { + background: rgb(52, 53, 56); + color: #ddd; + } - html { - background: rgb(52,53,56); - color: #ddd; - } - - h1, - label { - color: #eee; - } + h1, + label { + color: #eee; + } - th { - color: #eee; - font-weight: normal; - } + th { + color: #eee; + font-weight: normal; + } - .disabled span { - color: rgb(240,85,82); - } + .disabled span { + color: rgb(240, 85, 82); + } - button { - border: solid 1px #777 !important; - background-color: rgb(32,33,36) !important; - color: #ddd; - } -} + button { + border: solid 1px #777 !important; + background-color: rgb(32, 33, 36) !important; + color: #ddd; + } +} \ No newline at end of file diff --git a/css/redirector.css b/css/redirector.css index 63ee0a7..51bdca5 100644 --- a/css/redirector.css +++ b/css/redirector.css @@ -1,122 +1,124 @@ - /* Basic element styles */ body { font-family: Arial, sans-serif; - font-size:14px; - background:white; - color:rgb(43, 38, 38); + font-size: 14px; + background: white; + color: rgb(43, 38, 38); } -h1, h2, h3, h5, h6 { +h1, +h2, +h3, +h5, +h6 { text-align: center; } h1 { - font-size:55px; - margin-bottom:0px; - cursor:default; - padding:0px; + font-size: 3rem; + margin-bottom: 0px; + cursor: default; + padding: 0px; letter-spacing: 2.1px; } h3 { - font-size:18px; - padding:0px; + font-size: 1.5rem; + padding: 0px; } h4 { text-align: left; - font-size:14px; - padding:0px; - margin:4px; + font-size: 1.2rem; + padding: 0px; + margin: 4px; } h5 { - font-size:20px; - margin:-6px 0 0 0; - color:#5e6163; + font-size: 1.1rem; + margin: -6px 0 0 0; + color: #5e6163; } -input[type='text'] { - border-radius:2px; - padding:3px; - border:solid 1px #bbb; +input[type="text"] { + border-radius: 2px; + padding: 3px; + border: solid 1px #bbb; } input[type="radio"] { - margin-right:5px; + margin-right: 5px; } /* Classes for buttons and other stuff */ .btn { - background:white; - border:solid 1px #bbb; - border-radius:3px; + background: white; + border: solid 1px #bbb; + border-radius: 3px; cursor: pointer; font-weight: bold; - display:inline-block; + display: inline-block; text-align: center; text-decoration: none; + text-shadow: none; } .btn.large { - font-size:18px; - padding:4px 8px; - + font-size: 18px; + padding: 4px 8px; } .btn.medium { - font-size:14px; - padding:2px 6px; + font-size: 14px; + padding: 2px 6px; } .btn:hover { - color:white !important; + color: white !important; text-decoration: none; } - .btn.grey { - color:#333; + color: #333; } .btn.grey:hover { - background:#333; - border:solid 1px #333; + background: #333; + border: solid 1px #333; } .btn.red { - color:rgb(208,52,37); + color: rgb(208, 52, 37); } .btn.red:hover { - background:rgb(208,52,37); - border:solid 1px rgb(208,52,37); + background: rgb(208, 52, 37); + border: solid 1px rgb(208, 52, 37); } .btn.blue { - color:rgb(21,90,233); + color: rgb(21, 90, 233); } .btn.blue:hover { - background:rgb(21,90,233); - border:solid 1px rgb(21,90,233); + background: rgb(21, 90, 233); + border: solid 1px rgb(21, 90, 233); } .btn.green { - color:green; + color: green; } .btn.green:hover { - background:green; - border:solid 1px green; + background: green; + border: solid 1px green; } .btn.blue.active { color: white; - background:rgb(21,90,233); + background: rgb(21, 90, 233); } #redirect-row-template { @@ -126,33 +128,34 @@ input[type="radio"] { /* Main menu with buttons */ #menu { - margin: 30px auto 8px auto; - max-width:90%; + margin: 30px auto; + max-width: 90%; text-align: center; } #import-file { - display:none; + display: none; } /* Message box for success/failure messages */ #message-box { - margin:10px auto; - width:90%; - color:white; + margin: 10px auto; + width: 100%; + color: white; max-width: 800px; - border-radius:3px; - height:0px; - line-height: 30px; - font-size:16px; - text-align:center; - overflow:hidden; + border-radius: 3px; + height: 0px; + line-height: 2rem; + font-size: 1rem; + text-align: center; + overflow: hidden; + white-space: normal; transition: height 0.2s ease-out; position: relative; } #message-box.visible { - height:30px; + height: auto; transition: height 0.2s ease-out; } @@ -161,53 +164,54 @@ input[type="radio"] { } #message-box.error { - background-color: rgb(208,52,37);; + background-color: rgb(208, 52, 37); } #message-box a { - color:white; - font-size:20px; + color: white; + font-size: 1.5rem; position: absolute; - right:6px; - top:0px; - cursor:pointer; + right: 6px; + top: 50%; + transform: translateY(-50%); + cursor: pointer; } /* Table of redirects */ .redirect-table { - max-width:800px; - border:solid 1px #bbb; - margin:0px auto; - border-radius:3px; + max-width: 800px; + border: solid 1px #bbb; + margin: 0px auto; + border-radius: 3px; } .redirect-row { position: relative; - font-size:14px; - padding:8px; - line-height:18px; - border-bottom:solid 1px #bbb; + font-size: 14px; + padding: 8px; + line-height: 18px; + border-bottom: solid 1px #bbb; } .redirect-info { - display:table; + display: table; } .redirect-info div { - display:table-row; + display: table-row; } .redirect-info div label { - display:table-cell; - padding:3px 5px; + display: table-cell; + padding: 3px 5px; white-space: nowrap; } .redirect-info div p { - display:table-cell; - word-wrap:anywhere; - max-width:700px; + display: table-cell; + word-wrap: anywhere; + max-width: 700px; } .redirect-row:last-child { @@ -215,55 +219,65 @@ input[type="radio"] { } .redirect-row:nth-child(odd) { - background:#f8f8f8; + background: #f8f8f8; } -.redirect-info.disabled label, .redirect-info.disabled span, span.disabled, .redirect-info.disabled p, a.disabled, button[disabled] { - color:#bbb !important; +.redirect-info.disabled label, +.redirect-info.disabled span, +span.disabled, +.redirect-info.disabled p, +a.disabled, +button[disabled] { + color: #bbb !important; } button span { - pointer-events:none; + pointer-events: none; } /* Edit, Delete, Disable buttons */ .redirect-row button { - font-size:13px; - margin-top:5px; - padding:2px; - width:80px; + font-size: 13px; + margin-top: 5px; + padding: 2px; + width: 80px; display: inline-block; } .redirect-row h4 span.disabled-marker { - color:red !important; + color: red !important; } /* nav btns */ -.move-up-btn, .move-down-btn, .move-downbottom-btn, .move-uptop-btn { - width:45px !important; +.move-up-btn, +.move-down-btn, +.move-downbottom-btn, +.move-uptop-btn { + width: 45px !important; } -.move-downbottom-btn, .move-uptop-btn { - height:25px !important; +.move-downbottom-btn, +.move-uptop-btn { + height: 25px !important; } .redirect-row label { - display:inline-block; - width:80px; - font-weight:bold; + display: inline-block; + width: 80px; + font-weight: bold; text-align: right; } .arrow { - font-size:18px; + font-size: 18px; } -a.disabled:hover, button[disabled]:hover { - cursor:default; - color:#bbb !important; - border:solid 1px #bbb !important; - background:white !important; +a.disabled:hover, +button[disabled]:hover { + cursor: default; + color: #bbb !important; + border: solid 1px #bbb !important; + background: white !important; } /* Toggle Grouping Checkbox */ @@ -300,7 +314,7 @@ a.disabled:hover, button[disabled]:hover { } .toggle-container input:checked ~ .checkMarked { - background-color: #2196F3; + background-color: #2196f3; } .checkmark:after { @@ -329,49 +343,45 @@ a.disabled:hover, button[disabled]:hover { #delete-redirect-form { position: fixed; - padding:10px; - width:500px; - background:white; - border:solid 1px #bbb; - border-radius:3px; + padding: 10px; + width: 45%; + background: white; + border: solid 1px #bbb; + border-radius: 3px; z-index: 6000; - left:50%; - margin-left:-260px; - top:50%; - margin-top:-220px; - height:255px; - display:none; + left: 50%; + top: 50%; + display: none; + transform: translate(-50%, -50%); } #delete-redirect-form div{ - margin-bottom:7px; + margin-bottom: 7px; } #delete-redirect-form div label:first-child { - width:130px; - font-weight:bold; + font-weight: bold; text-align: right; - display:inline-block; + display: inline-block; vertical-align: top; } #delete-redirect-form div span { - display:inline-block; - width: 330px; + display: inline-block; + width: 70%; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; - vertical-align: top; } /* Edit form */ #cover { position: fixed; - top:0px; - bottom:0px; - left:0px; - right:0px; + top: 0px; + bottom: 0px; + left: 0px; + right: 0px; z-index: 5000; background: #333; opacity: 0.5; @@ -379,116 +389,115 @@ a.disabled:hover, button[disabled]:hover { } .blur { - -webkit-filter:blur(3px); - filter:blur(3px); + -webkit-filter: blur(3px); + filter: blur(3px); } #edit-redirect-form { position: fixed; - display:table; - display:none; - width:700px; - background:white; - border:solid 1px #bbb; - border-radius:3px; + padding: 10px; + background: white; + border: solid 1px #bbb; + border-radius: 3px; z-index: 6000; - left:50%; - margin-left:-350px; - top:50%; - transform: translateY(-50%); - max-height: 96vh; + display: none; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); overflow: auto; } .form-grid { - display:table; + display: table; } -.form-grid > div{ - display:table-row; +.form-grid > div { + display: table-row; } .form-grid > div > label { - display:table-cell; - font-weight:bold; + display: table-cell; + font-weight: bold; text-align: right; - padding:6px; + padding: 6px; white-space: nowrap; - width:140px; - vertical-align:top; + width: 140px; + vertical-align: top; } .input-cell { - padding-top:1px; + padding-top: 1px; } -.form-grid div input[type='text'] { - width:510px; - font-size:14px; +.form-grid div input[type="text"] { + width: 510px; + font-size: 0.9rem; } .example-result { - width:500px; - display:inline-block; - word-wrap:break-word; - margin-top:5px; + width: 500px; + display: inline-block; + word-wrap: break-word; + margin-top: 5px; } .example-result-error { - margin-top:5px; - display:inline-block; + margin-top: 5px; + display: inline-block; } -#unescape-matches, #escape-matches { - margin-top:7px; - margin-left:0px; +#unescape-matches, +#escape-matches { + margin-top: 7px; + margin-left: 0px; } .input-cell label { - display:block; + display: block; } #apply-to { - padding-top:3px; + padding-top: 3px; } #apply-to label span { position: relative; - top:1px; + top: 1px; } .input-cell label input { - margin-left:0px; + margin-left: 0px; } .error { - color:red; + color: red; } -.placeholder { - color:#c0c0c0; - font-size:11px; +.placeholder { + color: #c0c0c0; + font-size: 11px; } -::-moz-placeholder { /* Firefox 19+ */ - color: #c0c0c0; +::-moz-placeholder { + /* Firefox 19+ */ + color: #c0c0c0; } .advanced { - margin-top:8px; + margin-top: 8px; } .hidden { - display:none; + display: none; } #advanced-toggle { - padding-top:3px; + padding-top: 3px; text-align: center; } #advanced-toggle a { - color:rgb(21,90,233); + color: rgb(21, 90, 233); cursor: pointer; } @@ -497,44 +506,46 @@ a.disabled:hover, button[disabled]:hover { } .advanced div .input-cell label:first-child { - margin-top:2px; + margin-top: 2px; } .advanced div .input-cell select { - margin-top:4px; - width:160px; + margin-top: 4px; + width: 160px; } a[ng-click] { - cursor:pointer; + cursor: pointer; } -#wildcardtype, #regextype { - margin-right:10px; - display:inline-block; - margin-top:4px; +#wildcardtype, +#regextype { + margin-right: 10px; + display: inline-block; + margin-top: 4px; } .button-container { - margin-top:20px; + margin-top: 20px; text-align: center; - padding-bottom:10px; + padding-bottom: 10px; } /* Footer with link */ footer { - margin-top:30px; + margin-top: 30px; text-align: center; } footer small { - font-size:10px; - color:#555; + font-size: 10px; + color: #555; } -footer small a, footer small a:visited { +footer small a, +footer small a:visited { text-decoration: none; - color:rgb(21,90,233); + color: rgb(21, 90, 233); } footer small a:hover { @@ -547,22 +558,69 @@ footer small a:hover { } #storage-sync-option input { - margin:10px; + margin: 10px; +} + +/* Popup form for importing redirects */ +#import-popup { + position: fixed; + padding: 10px; + width: 35%; + background: white; + border: solid 1px #bbb; + border-radius: 3px; + z-index: 6000; + left: 50%; + top: 50%; + display: none; + transform: translate(-50%, -50%); +} + +#import-popup input { + font-weight: bold; + text-align: center; + display: inline-block; + vertical-align: top; + margin-bottom: 0.5rem; + padding: 4px 8px; +} + +.popup-content { + text-align: center; + margin-bottom: 7px; +} + +@media only screen and (max-width: 767px) { + #delete-redirect-form, + #edit-redirect-form, + #import-popup { + width: 85vw; + } } /* Dark mode support */ @media (prefers-color-scheme: dark) { - body { - background: rgb(32,33,36); + background: rgb(32, 33, 36); color: #bbb; } + #import-popup { + background: rgb(41, 42, 45); + border: solid 1px #888; + } + + #import-popup label { + color: white; + font-weight: normal; + } + h1 { color: #eee; } - h5, footer small { + h5, + footer small { color: #aaa; } @@ -570,19 +628,20 @@ footer small a:hover { color: white; } - footer small a, footer small a:visited { - color: rgb(138,179,241); + footer small a, + footer small a:visited { + color: rgb(138, 179, 241); } .redirect-row:nth-child(odd) { - background: rgb(31,32,35); + background: rgb(31, 32, 35); } .redirect-row:nth-child(even) { - background: rgb(41,42,45); + background: rgb(41, 42, 45); } .btn { - background-color: rgb(32,33,36); + background-color: rgb(32, 33, 36); border: solid 1px #777; } @@ -591,7 +650,7 @@ footer small a:hover { } .btn.green { - color: rgb(53,180,75); + color: rgb(53, 180, 75); } .toggle { @@ -599,29 +658,30 @@ footer small a:hover { } #message-box.success { - background-color: rgb(53,203,75);; + background-color: rgb(53, 203, 75); } #message-box.error { - background-color: rgb(252,87,84); + background-color: rgb(252, 87, 84); } - .redirect-table, .redirect-row { + .redirect-table, + .redirect-row { border-color: #555 !important; } .redirect-row h4 span.disabled-marker { - color:rgb(252,87,84) !important; + color: rgb(252, 87, 84) !important; } .btn.red { - color: rgb(252,87,84); + color: rgb(252, 87, 84); } .btn.grey[disabled]:hover { border: solid 1px #777 !important; - background: rgb(32,33,36) !important; - color:#555 !important;; + background: rgb(32, 33, 36) !important; + color: #555 !important; } .btn.grey:hover { @@ -631,43 +691,51 @@ footer small a:hover { } .btn.blue { - color: rgb(138,179,241); + color: rgb(138, 179, 241); } .redirect-row [data-bind="description"] { color: #eee; } - .redirect-info.disabled label, .redirect-info.disabled span, span.disabled, .redirect-info.disabled p, a.disabled, button[disabled] { - color:#555 !important; + .redirect-info.disabled label, + .redirect-info.disabled span, + span.disabled, + .redirect-info.disabled p, + a.disabled, + button[disabled] { + color: #555 !important; } - #edit-redirect-form, #delete-redirect-form { - background:rgb(41,42,45); + #edit-redirect-form, + #delete-redirect-form { + background: rgb(41, 42, 45); border: solid 1px #888; } - #advanced-toggle a, #advanced-toggle a:visited { - color: rgb(138,179,241); + #advanced-toggle a, + #advanced-toggle a:visited { + color: rgb(138, 179, 241); } h3 { color: #eee; } - #edit-redirect-form label, #delete-redirect-form label { + #edit-redirect-form label, + #delete-redirect-form label { color: white; font-weight: normal; } .example-result-error { - color:rgb(252,87,84) !important; + color: rgb(252, 87, 84) !important; } #edit-redirect-form input { - background: rgb(68,68,68); + background: rgb(68, 68, 68); color: #ddd; - border-color: rgb(68,68,68) !important ; + border-color: rgb(68, 68, 68) !important ; border-radius: 2px; } } diff --git a/help.html b/help.html index ec5c5b9..40a3f65 100644 --- a/help.html +++ b/help.html @@ -1,318 +1,341 @@ - - REDIRECTOR HELP - - - - - - -

REDIRECTOR HELP

-

Table of contents

- - - - -

What is Redirector?

- -

Redirector is a browser extension that allows you to automatically redirect from - one webpage to another. For example, every time you visit http://abc.com you will automatically - load http://def.com instead. This can be useful for instance to always redirect articles to printer friendly - versions, redirect http:// to https:// for sites that support both, bypass advertising pages that appear before - being able to view certain pages and more.

-

A new feature in v3.0 is that the result of a redirect will never be redirected again, even if it matches another include pattern. This is to prevent endless loops, for example if you have a pattern that redirects from a -> b and another that redirects from b -> a. This also removes the annoying message that the include pattern matches the result and therefore you can't create the redirect. That doesn't matter anymore because the result will never be redirected, even if it matches the include pattern again, so this should make it simpler for people to create redirects.

- - -

Basic usage

-

To add a new redirect you press the Redirector icon next to your address bar, and in the popup that comes up you choose the Edit Redirects button. - On the settings page you can add, edit and delete redirect. Redirects will be checked in the same order as they are shown on that page, so you can move them - up or down to give them higher or lower priority. The edit form will guide you by showing you an example result as you're typing in your patterns. A redirect - contains the following fields: -

- - - -

Wildcards

- -

Wildcards are the simplest way to specify include and exclude patterns. When you create a wildcard pattern there - is just one special character, the asterisk *. An asterisk in your pattern will match zero or more characters and you can - have more than one star in your pattern. Some examples: -

- $1, $2, $3 in the redirect urls will match the text that the stars matched. Examples: - -

- - -

Regular expressions

- -

Regular expressions allow for more complicated patterns but they are a lot harder to learn than wildcards. I'm not gonna - create a regex tutorial here but normal javascript regex syntax works, look at Regular-Expressions.info for - an introduction to regular expressions. $1,$2 etc. can be used in the redirect url and will be replaced with contents of captures in - the regular expressions. Captures are specified with parentheses. Example: http://example.com/index.asp\?id=(\d+) will match the url - http://example.com/index.asp?id=12345 and $1 will be replaced by 12345. (A common mistake in regex patterns is to forget to escape - the ? sign in the querystring of the url. ? is a special character in regular expressions so if you want to match an url with a querystring - you should escape it as \?). To test your regular expressions, you may use any website or service. For example, RegExr.

- - -

Storage Area (Sync vs Local)

- -

Storage Area, by default, is set to Local. If you wish to sync your redirector rules across devices, you may choose to enable Sync from Settings page. - When you toggle to Sync, data will be copied over to Sync storage and local storage will be deleted. - Similary, sync storage will be deleted if you disable sync and data will be moved to Local storage. -

- Note:
  1. Google Chrome Sync and Mozilla Firefox Sync limits the storage size as per below. - This limit is decided by browser vendors and Redirector addon cannot do anything about changing the below.
  2. -
  3. You need to use chrome/firefox settings to setup a sync account for syncing to work. - If that is not completed, Sync will just act like local storage - take note of the storage sizes below. - If sync account is not setup in chrome/firefox browser settings, leave the storage area to LOCAL as it has much larger size than Sync storage size. -
- - -
-

- - -

Examples

- -
    -
  1. - Static redirect
    - - - - - - - - - - - - - - - - - - - - - - -
    Example URL:http://example.com/foo
    Include pattern:http://example.com/foo
    Redirect to:http://example.com/bar
    Pattern type:Wildcard
    Example result:http://example.com/bar
    -
  2. -
  3. - Redirect using query string parameter and wildcards
    - - - - - - - - - - - - - - - - - - - - - -
    Example URL:http://example.com/index.php?id=12345&a=b
    Include pattern:http://example.com/index.php?id=*&a=b
    Redirect to:http://example.com/printerfriendly.php?id=$1&a=b
    Pattern type:Wildcard
    Example result:http://example.com/printerfriendly.php?id=12345&a=b
    -
  4. -
  5. - Redirect using query string parameter and regular expressions
    - - - - - - - - - - - - - - - - - - - - - - -
    Example URL:http://example.com/index.php?id=12345&a=b
    Include pattern:http://example.com/index.php\?id=(\d+)&a=b
    Redirect to:http://example.com/printerfriendly.php?id=$1&a=b
    Pattern type:Regular Expression
    Example result:http://example.com/printerfriendly.php?id=12345&a=b
    -
  6. -
  7. - Redirect to a different folder using wildcards
    - The exclude pattern makes sure that there is only one folder there, so for instance - http://example.com/category/fish/cat/mouse/index.php would not match. - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Example URL:http://example.com/category/fish/index.php
    Include pattern:http://example.com/category/*/index.php
    Exclude pattern:http://example.com/category/*/*/index.php
    Redirect to:http://example.com/category/cat/index.php
    Pattern type:Wildcard
    Example result:http://example.com/category/cat/index.php
    -
  8. -
  9. - Redirect http to https using wildcards
    - - - - - - - - - - - - - - - - - - - - - -
    Example URL:http://mail.google.com/randomcharacters
    Include pattern:http://mail.google.com*
    Redirect to:https://mail.google.com$1
    Pattern type:Wildcard
    Example result:https://mail.google.com/randomcharacters
    -
  10. -
- + + REDIRECTOR HELP + + + + + + +

REDIRECTOR HELP

+

Table of contents

+ + +

What is Redirector?

+

Redirector is a browser extension that allows you to automatically redirect from one webpage to another. For example, every time you visit https://abc.com you will automatically load https://def.com instead. This can be useful for instance to always redirect articles to printer friendly versions, redirect https:// to https:// for sites that support both, bypass advertising pages that appear before being able to view certain pages and more.

+

A new feature in v3.0 is that the result of a redirect will never be redirected again, even if it matches another include pattern. This is to prevent endless loops, for example if you have a pattern that redirects from a -> b and another that redirects from b -> a. This also removes the annoying message that the include pattern matches the result and therefore you can't create the redirect. That doesn't matter anymore because the result will never be redirected, even if it matches the include pattern again, so this should make it simpler for people to create redirects.

+ +

Basic usage

+

To add a new redirect you press the Redirector icon next to your address bar, and in the popup that comes up you choose the Edit Redirects button. On the settings page you can add, edit and delete redirect. Redirects will be checked in the same order as they are shown on that page, so you can move them up or down to give them higher or lower priority. The edit form will guide you by showing you an example result as you're typing in your patterns. A redirect contains the following fields:

+ + +

Wildcards

+

Wildcards are the simplest way to specify include and exclude patterns. When you create a wildcard pattern there is just one special character, the asterisk *. An asterisk in your pattern will match zero or more characters and you can have more than one star in your pattern. Some examples: +

$1, $2, $3 in the redirect urls will match the text that the stars matched. Examples: +

+ +

Regular expressions

+

Regular expressions allow for more complicated patterns but they are a lot harder to learn than wildcards. I'm not gonna create a regex tutorial here but normal javascript regex syntax works, look at Regular-Expressions.info for an introduction to regular expressions. $1,$2 etc. can be used in the redirect url and will be replaced with contents of captures in the regular expressions. Captures are specified with parentheses. Example: https://example.com/index.asp\?id=(\d+) will match the url https://example.com/index.asp?id=12345 and $1 will be replaced by 12345. (A common mistake in regex patterns is to forget to escape the ? sign in the querystring of the url. ? is a special character in regular expressions so if you want to match an url with a querystring you should escape it as \?). To test your regular expressions, you may use any website or service. For example, RegExr.

+ +

Storage Area (Sync vs Local)

+

Storage Area, by default, is set to Local. If you wish to sync your redirector rules across devices, you may choose to enable Sync from Settings page. When you toggle to Sync, data will be copied over to Sync storage and local storage will be deleted. Similary, sync storage will be deleted if you disable sync and data will be moved to Local storage. +

+ Note: +
    +
  1. Google Chrome Sync and Mozilla Firefox Sync limits the storage size as per below. This limit is decided by browser vendors and Redirector addon cannot do anything about changing the below.
  2. +
  3. You need to use chrome/firefox settings to setup a sync account for syncing to work. If that is not completed, Sync will just act like local storage - take note of the storage sizes below. If sync account is not setup in chrome/firefox browser settings, leave the storage area to LOCAL as it has much larger size than Sync storage size.
  4. +
+ +
+

+ +

Examples

+
    +
  1. + + Static redirect +
    + + + + + + + + + + + + + + + + + + + + + +
    Example URL:https://example.com/foo
    Include pattern:https://example.com/foo
    Redirect to:https://example.com/bar
    Pattern type:Wildcard
    Example result:https://example.com/bar
    +
  2. +
  3. + + Redirect using query string parameter and wildcards +
    + + + + + + + + + + + + + + + + + + + + + +
    Example URL:https://example.com/index.php?id=12345&a=b
    Include pattern:https://example.com/index.php?id=*&a=b
    Redirect to:https://example.com/printerfriendly.php?id=$1&a=b
    Pattern type:Wildcard
    Example result:https://example.com/printerfriendly.php?id=12345&a=b
    +
  4. +
  5. + + Redirect using query string parameter and regular expressions +
    + + + + + + + + + + + + + + + + + + + + + +
    Example URL:https://example.com/index.php?id=12345&a=b
    Include pattern:https://example.com/index.php\?id=(\d+)&a=b
    Redirect to:https://example.com/printerfriendly.php?id=$1&a=b
    Pattern type:Regular Expression
    Example result:https://example.com/printerfriendly.php?id=12345&a=b
    +
  6. +
  7. + + Redirect to a different folder using wildcards +
    The exclude pattern makes sure that there is only one folder there, so for instance https://example.com/category/fish/cat/mouse/index.php would not match. + + + + + + + + + + + + + + + + + + + + + + + + +
    Example URL:https://example.com/category/fish/index.php
    Include pattern:https://example.com/category/*/index.php
    Exclude pattern:https://example.com/category/*/*/index.php
    Redirect to:https://example.com/category/cat/index.php
    Pattern type:Wildcard
    Example result:https://example.com/category/cat/index.php
    +
  8. +
  9. + + Redirect http to https using wildcards +
    + + + + + + + + + + + + + + + + + + + + + +
    Example URL:https://mail.google.com/randomcharacters
    Include pattern:https://mail.google.com*
    Redirect to:https://mail.google.com$1
    Pattern type:Wildcard
    Example result:https://mail.google.com/randomcharacters
    +
  10. +
+ diff --git a/icon.html b/icon.html index 3ab7760..ea7c7db 100644 --- a/icon.html +++ b/icon.html @@ -1,59 +1,54 @@ - - - - - - - - - \ No newline at end of file + img { + margin: 0; + padding: 0; + border: solid 1px green; + } + + + + + + diff --git a/js/background.js b/js/background.js index 4bdba26..94e06c9 100644 --- a/js/background.js +++ b/js/background.js @@ -1,13 +1,12 @@ - //This is the background script. It is responsible for actually redirecting requests, //as well as monitoring changes in the redirects and the disabled status and reacting to them. function log(msg, force) { if (log.enabled || force) { - console.log('REDIRECTOR: ' + msg); + console.log(`REDIRECTOR: ${msg}`); } } log.enabled = false; -var enableNotifications=false; +var enableNotifications = false; function isDarkMode() { return window.matchMedia('(prefers-color-scheme: dark)').matches; @@ -22,22 +21,18 @@ var partitionedRedirects = {}; //Cache of urls that have just been redirected to. They will not be redirected again, to //stop recursive redirects, and endless redirect chains. //Key is url, value is timestamp of redirect. -var ignoreNextRequest = { - -}; +var ignoreNextRequest = {}; //url => { timestamp:ms, count:1...n}; -var justRedirected = { - -}; +var justRedirected = {}; var redirectThreshold = 3; function setIcon(image) { - var data = { + var data = { path: {} }; - for (let nr of [16,19,32,38,48,64,128]) { + for (let nr of [16, 19, 32, 38, 48, 64, 128]) { data.path[nr] = `images/${image}-${nr}.png`; } @@ -45,9 +40,9 @@ function setIcon(image) { var err = chrome.runtime.lastError; if (err) { //If not checked we will get unchecked errors in the background page console... - log('Error in SetIcon: ' + err.message); + log(`Error in SetIcon: ${err.message}`); } - }); + }); } //This is the actual function that gets called for each request and must @@ -59,56 +54,59 @@ function checkRedirects(details) { if (details.method != 'GET') { return {}; } - log('Checking: ' + details.type + ': ' + details.url); + log(`Checking: ${details.type}: ${details.url}`); var list = partitionedRedirects[details.type]; if (!list) { - log('No list for type: ' + details.type); + log(`No list for type: ${details.type}`); return {}; } var timestamp = ignoreNextRequest[details.url]; if (timestamp) { - log('Ignoring ' + details.url + ', was just redirected ' + (new Date().getTime()-timestamp) + 'ms ago'); + log(`Ignoring ${details.url}, was just redirected ${(new Date().getTime() - timestamp)}ms ago`); delete ignoreNextRequest[details.url]; return {}; } + for (const rule of list) { + const result = rule.getMatch(details.url); - for (var i = 0; i < list.length; i++) { - var r = list[i]; - var result = r.getMatch(details.url); - if (result.isMatch) { - - //Check if we're stuck in a loop where we keep redirecting this, in that - //case ignore! - var data = justRedirected[details.url]; - - var threshold = 3000; - if(!data || ((new Date().getTime()-data.timestamp) > threshold)) { //Obsolete after 3 seconds - justRedirected[details.url] = { timestamp : new Date().getTime(), count: 1}; + // Check if we're stuck in a loop where we keep redirecting this, in that case ignore! + const data = justRedirected[details.url]; + const threshold = 3000; + + if (!data || (new Date().getTime() - data.timestamp > threshold)) { + justRedirected[details.url] = { + timestamp: new Date().getTime(), + count: 1, + }; } else { data.count++; justRedirected[details.url] = data; + if (data.count >= redirectThreshold) { - log('Ignoring ' + details.url + ' because we have redirected it ' + data.count + ' times in the last ' + threshold + 'ms'); + log(`Ignoring ${details.url} because we have redirected it ${data.count} times in the last ${threshold}ms`); return {}; - } + } } - - - log('Redirecting ' + details.url + ' ===> ' + result.redirectTo + ', type: ' + details.type + ', pattern: ' + r.includePattern + ' which is in Rule : ' + r.description); - if(enableNotifications){ - sendNotifications(r, details.url, result.redirectTo); + + log(`Redirecting ${details.url} ===> ${result.redirectTo}, type: ${details.type}, pattern: ${rule.includePattern} which is in Rule: ${rule.description}`); + + if (enableNotifications) { + sendNotifications(rule, details.url, result.redirectTo); } - ignoreNextRequest[result.redirectTo] = new Date().getTime(); - return { redirectUrl: result.redirectTo }; + ignoreNextRequest[result.redirectTo] = new Date().getTime(); + + return { + redirectUrl: result.redirectTo, + }; } } - - return {}; + + return {}; } //Monitor changes in data, and setup everything again. @@ -131,14 +129,14 @@ function monitorChanges(changes, namespace) { if (changes.redirects) { log('Redirects have changed, setting up listener again'); setUpRedirectListener(); - } + } - if (changes.logging) { + if (changes.logging) { log.enabled = changes.logging.newValue; - log('Logging settings have changed to ' + changes.logging.newValue, true); //Always want this to be logged... + log(`Logging settings have changed to ${changes.logging.newValue}`, true); //Always want this to be logged... } - if (changes.enableNotifications){ - log('notifications setting changed to ' + changes.enableNotifications.newValue); + if (changes.enableNotifications) { + log(`notifications setting changed to ${changes.enableNotifications.newValue}`); enableNotifications = changes.enableNotifications.newValue; } } @@ -149,22 +147,22 @@ chrome.storage.onChanged.addListener(monitorChanges); function createFilter(redirects) { var types = []; for (var i = 0; i < redirects.length; i++) { - redirects[i].appliesTo.forEach(function(type) { + redirects[i].appliesTo.forEach(function(type) { // Added this condition below as part of fix for issue 115 https://github.com/einaregilsson/Redirector/issues/115 // Firefox considers responsive web images request as imageset. Chrome doesn't. // Chrome throws an error for imageset type, so let's add to 'types' only for the values that chrome or firefox supports - if(chrome.webRequest.ResourceType[type.toUpperCase()]!== undefined){ - if (types.indexOf(type) == -1) { - types.push(type); + if (chrome.webRequest.ResourceType[type.toUpperCase()] !== undefined) { + if (types.indexOf(type) == -1) { + types.push(type); + } } - } }); } types.sort(); return { urls: ["https://*/*", "http://*/*"], - types : types + types: types }; } @@ -174,16 +172,16 @@ function createPartitionedRedirects(redirects) { for (var i = 0; i < redirects.length; i++) { var redirect = new Redirect(redirects[i]); redirect.compile(); - for (var j=0; j storageArea.QUOTA_BYTES_PER_ITEM) { - log("size of redirects " + size + " is greater than allowed for Sync which is " + storageArea.QUOTA_BYTES_PER_ITEM); + log(`size of redirects ${size} is greater than allowed for Sync which is ${storageArea.QUOTA_BYTES_PER_ITEM}`); // Setting storageArea back to Local. - storageArea = chrome.storage.local; + storageArea = chrome.storage.local; sendResponse({ message: "Sync Not Possible - size of Redirects larger than what's allowed by Sync. Refer Help page" }); } else { chrome.storage.local.get({ redirects: [] - }, function (obj) { + }, function(obj) { //check if at least one rule is there. - if (obj.redirects.length>0) { - chrome.storage.sync.set(obj, function (a) { + if (obj.redirects.length > 0) { + chrome.storage.sync.set(obj, function(a) { log('redirects moved from Local to Sync Storage Area'); //Remove Redirects from Local storage chrome.storage.local.remove("redirects"); @@ -340,14 +359,14 @@ chrome.runtime.onMessage.addListener( }); } }); - } else { + } else { storageArea = chrome.storage.local; - log('storageArea size for local is ' + storageArea.QUOTA_BYTES / 1000000 + ' MB, that is .. ' + storageArea.QUOTA_BYTES + " bytes"); + log(`storageArea size for local is ${storageArea.QUOTA_BYTES / 1000000} MB, that is .. ${storageArea.QUOTA_BYTES} bytes`); chrome.storage.sync.get({ redirects: [] - }, function (obj) { - if (obj.redirects.length>0) { - chrome.storage.local.set(obj, function (a) { + }, function(obj) { + if (obj.redirects.length > 0) { + chrome.storage.local.set(obj, function(a) { log('redirects moved from Sync to Local Storage Area'); //Remove Redirects from sync storage chrome.storage.sync.remove("redirects"); @@ -367,7 +386,7 @@ chrome.runtime.onMessage.addListener( }); } else { - log('Unexpected message: ' + JSON.stringify(request)); + log(`Unexpected message: ${JSON.stringify(request)}`); return false; } @@ -376,36 +395,39 @@ chrome.runtime.onMessage.addListener( } ); - //First time setup updateIcon(); -chrome.storage.local.get({logging:false}, function(obj) { +chrome.storage.local.get({ + logging: false +}, function(obj) { log.enabled = obj.logging; }); chrome.storage.local.get({ isSyncEnabled: false -}, function (obj) { +}, function(obj) { if (obj.isSyncEnabled) { storageArea = chrome.storage.sync; } else { storageArea = chrome.storage.local; } // Now we know which storageArea to use, call setupInitial function - setupInitial(); + setupInitial(); }); //wrapped the below inside a function so that we can call this once we know the value of storageArea from above. function setupInitial() { - chrome.storage.local.get({enableNotifications:false},function(obj){ + chrome.storage.local.get({ + enableNotifications: false + }, function(obj) { enableNotifications = obj.enableNotifications; }); chrome.storage.local.get({ disabled: false - }, function (obj) { + }, function(obj) { if (!obj.disabled) { setUpRedirectListener(); } else { @@ -415,15 +437,14 @@ function setupInitial() { } log('Redirector starting up...'); - // Below is a feature request by an user who wished to see visual indication for an Redirect rule being applied on URL // https://github.com/einaregilsson/Redirector/issues/72 // By default, we will have it as false. If user wishes to enable it from settings page, we can make it true until user disables it (or browser is restarted) // Upon browser startup, just set enableNotifications to false. // Listen to a message from Settings page to change this to true. -function sendNotifications(redirect, originalUrl, redirectedUrl ){ - //var message = "Applied rule : " + redirect.description + " and redirected original page " + originalUrl + " to " + redirectedUrl; +function sendNotifications(redirect, originalUrl, redirectedUrl) { + //var message = `Applied rule: ${redirect.description} and redirected original page ${originalUrl} to ${redirectedUrl}`; log("Showing redirect success notification"); //Firefox and other browsers does not yet support "list" type notification like in Chrome. // Console.log(JSON.stringify(chrome.notifications)); -- This will still show "list" as one option but it just won't work as it's not implemented by Firefox yet @@ -431,35 +452,41 @@ function sendNotifications(redirect, originalUrl, redirectedUrl ){ // So let's use useragent. // Opera UA has both chrome and OPR. So check against that ( Only chrome which supports list) - other browsers to get BASIC type notifications. - let icon = isDarkMode() ? "images/icon-dark-theme-48.png": "images/icon-light-theme-48.png"; + let icon = isDarkMode() ? "images/icon-dark-theme-48.png" : "images/icon-light-theme-48.png"; - if(navigator.userAgent.toLowerCase().indexOf("chrome") > -1 && navigator.userAgent.toLowerCase().indexOf("opr")<0){ - - var items = [{title:"Original page: ", message: originalUrl},{title:"Redirected to: ",message: redirectedUrl}]; - var head = "Redirector - Applied rule : " + redirect.description; + if (navigator.userAgent.toLowerCase().indexOf("chrome") > -1 && navigator.userAgent.toLowerCase().indexOf("opr") < 0) { + + var items = [{ + title: "Original page: ", + message: originalUrl + }, { + title: "Redirected to: ", + message: redirectedUrl + }]; + var head = `Redirector - Applied rule: ${redirect.description}`; chrome.notifications.create({ - type : "list", - items : items, - title : head, - message : head, - iconUrl : icon - }); - } - else{ - var message = "Applied rule : " + redirect.description + " and redirected original page " + originalUrl + " to " + redirectedUrl; + type: "list", + items: items, + title: head, + message: head, + iconUrl: icon + }); + } else { + var message = `Applied rule: ${redirect.description} and redirected original page ${originalUrl} to ${redirectedUrl}`; chrome.notifications.create({ - type : "basic", - title : "Redirector", - message : message, - iconUrl : icon + type: "basic", + title: "Redirector", + message: message, + iconUrl: icon }); } } chrome.runtime.onStartup.addListener(handleStartup); -function handleStartup(){ - enableNotifications=false; + +function handleStartup() { + enableNotifications = false; chrome.storage.local.set({ enableNotifications: false }); diff --git a/js/editredirect.js b/js/editredirect.js index ca4ea07..1881afc 100644 --- a/js/editredirect.js +++ b/js/editredirect.js @@ -36,6 +36,7 @@ function saveRedirect() { updateBindings(); saveChanges(); + updateExportLink(); hideForm('#edit-redirect-form'); } @@ -51,7 +52,6 @@ function toggleAdvancedOptions(ev) { } } - function editFormChange() { //Now read values back from the form... for (let input of el('#edit-redirect-form').querySelectorAll('input[type="text"][data-bind]')) { @@ -71,13 +71,12 @@ function editFormChange() { dataBind('#edit-redirect-form', activeRedirect); } - - var deleteIndex; + function confirmDeleteRedirect(index) { deleteIndex = index; - let redirect = REDIRECTS[deleteIndex]; - showForm('#delete-redirect-form', redirect); + let redirect = REDIRECTS[deleteIndex]; + showForm('#delete-redirect-form', redirect); } function deleteRedirect() { @@ -93,7 +92,6 @@ function cancelDelete() { hideForm('#delete-redirect-form'); } - function setupEditAndDeleteEventListeners() { el('#btn-save-redirect').addEventListener('click', saveRedirect); @@ -109,5 +107,4 @@ function setupEditAndDeleteEventListeners() { el('#edit-redirect-form').addEventListener('input', editFormChange); } - setupEditAndDeleteEventListeners(); \ No newline at end of file diff --git a/js/importexport.js b/js/importexport.js index c3e79dd..57f0147 100644 --- a/js/importexport.js +++ b/js/importexport.js @@ -1,96 +1,201 @@ -// Shows a message explaining how many redirects were imported. -function showImportedMessage(imported, existing) { - if (imported == 0 && existing == 0) { - showMessage('No redirects existed in the file.'); - } - if (imported > 0 && existing == 0) { - showMessage('Successfully imported ' + imported + ' redirect' + (imported > 1 ? 's.' : '.'), true); - } - if (imported == 0 && existing > 0) { - 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.'; - } - showMessage(m, true); - } +function showImportedMessage(imported, existing, updated, exRuleDel) { + let message = ''; + + if (imported === 0 && existing === 0 && updated === 0) { + message = 'No redirects existed in the file.'; + } else { + if (imported > 0) { + message += `Successfully imported ${imported} redirect${imported > 1 ? 's' : ''}. `; + } + + if (existing > 0) { + message += `${existing} redirect${existing > 1 ? 's' : ''} already existed and were ignored. `; + } + + if (updated > 0) { + message += `${updated} redirect${updated > 1 ? 's were' : ' was'} updated. `; + } + } + + if (exRuleDel) { + message += ' Removed the example rule.'; + } + closeImportPopup(); + showMessage(message, true); } -function importRedirects(ev) { - - let file = ev.target.files[0]; - if (!file) { - return; - } - var reader = new FileReader(); - - reader.onload = function(e) { - var data; - try { - data = JSON.parse(reader.result); - } catch(e) { - showMessage('Failed to parse JSON data, invalid JSON: ' + (e.message||'').substr(0,100)); - return; - } - - if (!data.redirects) { - showMessage('Invalid JSON, missing "redirects" property'); - return; - } - - var imported = 0, existing = 0; - for (var i = 0; i < data.redirects.length; i++) { - var r = new Redirect(data.redirects[i]); - r.updateExampleResult(); - if (REDIRECTS.some(function(i) { return new Redirect(i).equals(r);})) { - existing++; - } else { - REDIRECTS.push(r.toObject()); - imported++; - } - } - - showImportedMessage(imported, existing); - - saveChanges(); - renderRedirects(); - }; - - try { - reader.readAsText(file, 'utf-8'); - } catch(e) { - showMessage('Failed to read import file'); - } +function importRedirects(source) { + // If the source is an Event, it's from file input + if (source instanceof Event) { + const file = source.target.files[0]; + if (!file) { + return; + } + + const reader = new FileReader(); + reader.onload = function (e) { + processImportedData(e.target.result); + }; + + reader.onerror = function (error) { + showMessage('Failed to read import file'); + }; + + reader.readAsText(file, 'utf-8'); + } + // If the source is a string, it's from a URL fetch + else if (typeof source === 'string') { + processImportedData(source); + } } -function updateExportLink() { - var redirects = REDIRECTS.map(function(r) { - return new Redirect(r).toObject(); - }); +function processImportedData(data) { + try { + const jsonData = JSON.parse(data); - let version = chrome.runtime.getManifest().version; + if (!jsonData.redirects) { + showMessage('Invalid JSON, missing "redirects" property'); + return; + } - var exportObj = { - createdBy : 'Redirector v' + version, - createdAt : new Date(), - redirects : redirects - }; + const { redirects } = jsonData; + let imported = 0; + let existing = 0; + let updated = 0; - var json = JSON.stringify(exportObj, null, 4); + redirects.forEach(redirectData => { + const importedRedirect = new Redirect(redirectData); + importedRedirect.updateExampleResult(); - //Using encodeURIComponent here instead of base64 because base64 always messed up our encoding for some reason... - el('#export-link').href = 'data:text/plain;charset=utf-8,' + encodeURIComponent(json); + const existingRedirectIndex = REDIRECTS.findIndex( + r => r.id === importedRedirect.id + ); + + if (importedRedirect.id = undefined) { + importedRedirect.id = nanoid(); + //console.log("Assigned new ID:", importedRedirect.id); + return importedRedirect.id; + } + + if (existingRedirectIndex !== -1) { + // Check if the imported redirect is different + if (!new Redirect(REDIRECTS[existingRedirectIndex]).equals(importedRedirect)) { + // Update existing redirect + REDIRECTS[existingRedirectIndex] = importedRedirect; + updated++; + } else { + // If not different, consider it as existing + existing++; + } + } else { + // Add new redirect + REDIRECTS.push(importedRedirect); + imported++; + } + }); + + // If more than one rule was imported, remove the example rule + let exRuleDel = false; + if (imported > 1) { + const exampleRuleIndex = REDIRECTS.findIndex( + r => r.description === 'Example redirect, try going to https://example.com/anywordhere' + ); + if (exampleRuleIndex !== -1) { + REDIRECTS.splice(exampleRuleIndex, 1); + exRuleDel = true; + } + } + + showImportedMessage(imported, existing, updated, exRuleDel); + + saveChanges(); + renderRedirects(); + updateExportLink(); + } catch (error) { + showMessage(`Failed to parse JSON data, invalid JSON: ${(error.message || '').substr(0, 100)}`); + } +} + +function updateExportLink() { + const redirects = REDIRECTS.map(r => new Redirect(r).toObject()); + const version = chrome.runtime.getManifest().version; + + const exportObj = { + createdBy: `Redirector v${version}`, + createdAt: new Date(), + redirects: redirects + }; + + const json = JSON.stringify(exportObj, null, 4); + + el('#export-link').href = `data:text/plain;charset=utf-8,${encodeURIComponent(json)}`; } updateExportLink(); +var blurWrapper = document.getElementById("blur-wrapper"); + +// Function to open the import popup +function showImportPopup() { + document.getElementById('import-popup').style.display = 'block'; + document.getElementById('cover').style.display = 'block'; + blurWrapper.classList.add("blur"); + } + +// Function to close the import popup +function closeImportPopup() { + document.getElementById('import-popup').style.display = 'none'; + document.getElementById('cover').style.display = 'none'; + blurWrapper.classList.remove("blur"); +} + +// Setup event listeners for import functionality function setupImportExportEventListeners() { - el("#import-file").addEventListener('change', importRedirects); - el("#export-link").addEventListener('mousedown', updateExportLink); + + document.querySelector('label[for="import-file"]').addEventListener('click', function(ev) { + ev.preventDefault(); + showImportPopup(); + }); + + const importFileInputPopup = document.getElementById("import-file-popup"); + importFileInputPopup.addEventListener('change', importRedirects); + + document.querySelector('label[for="import-file"]').addEventListener('click', function(ev) { + ev.preventDefault(); + showImportPopup(); + }); + + document.getElementById('import-url-button').addEventListener('click', function() { + const url = document.getElementById('import-url-popup').value; + fetchAndImportFromURL(url); + }); + + document.getElementById('import-file-popup-cb').addEventListener('click', function () { + document.getElementById('import-file-popup').click(); + }); + + document.getElementById('cancel-import').addEventListener('click', closeImportPopup); +} + +function fetchAndImportFromURL(url) { + if (url) { + fetch(url) + .then(response => { + if (response.ok) { + return response.text() || response.json(); + } + throw new Error('Invalid response.'); + }) + .then(data => { + importRedirects(data); + //console.log("Redirects imported:", data); + }) + .catch(error => { + console.error('Problem fetching the URL:', error); + }); + } else { + alert("Please enter a URL."); + } } setupImportExportEventListeners(); \ No newline at end of file diff --git a/js/nanoid.js b/js/nanoid.js new file mode 100644 index 0000000..434c9e0 --- /dev/null +++ b/js/nanoid.js @@ -0,0 +1,2 @@ +// From https://raw.githubusercontent.com/ai/nanoid/main/nanoid.js +export let nanoid=(t=21)=>crypto.getRandomValues(new Uint8Array(t)).reduce(((t,e)=>t+=(e&=63)<36?e.toString(36):e<62?(e-26).toString(36).toUpperCase():e>62?"-":"_"),""); \ No newline at end of file diff --git a/js/organizemode.js b/js/organizemode.js index f550d1b..5044c38 100644 --- a/js/organizemode.js +++ b/js/organizemode.js @@ -1,38 +1,37 @@ - function displayOrganizeModeMessage() { - if(el('#message-box').classList.contains('visible')) { - hideMessage(); - } else { - showMessage("Use ⟱ to move a redirect to the bottom, ⟰ to move to the top, and use the checkboxes to select multiple redirects.", true) - } + if (el('#message-box').classList.contains('visible')) { + hideMessage(); + } else { + showMessage("Use ⟱ to move a redirect to the bottom, ⟰ to move to the top, and use the checkboxes to select multiple redirects.", true) + } } function organizeModeToggle(ev) { - ev.preventDefault(); - let organizeModes = ['.groupings', '.arrows'] - for (let mode of organizeModes) { - let organizeModeElms = document.querySelectorAll(mode); - for (i = 0; i < organizeModeElms.length; ++i) { - let elm = organizeModeElms[i]; - let isHidden = ''; - if(mode === '.arrows') { - // targeting parent span for arrows - elm = elm.parentElement; - } - isHidden = elm.classList.contains('hidden'); - isHidden ? elm.classList.remove('hidden') : elm.classList.add('hidden'); - } - } + ev.preventDefault(); + let organizeModes = ['.groupings', '.arrows'] + for (let mode of organizeModes) { + let organizeModeElms = document.querySelectorAll(mode); + for (i = 0; i < organizeModeElms.length; ++i) { + let elm = organizeModeElms[i]; + let isHidden = ''; + if (mode === '.arrows') { + // targeting parent span for arrows + elm = elm.parentElement; + } + isHidden = elm.classList.contains('hidden'); + isHidden ? elm.classList.remove('hidden') : elm.classList.add('hidden'); + } + } - let buttonClasses = el('#organize-mode').classList; - !buttonClasses.contains('active') ? el('#organize-mode').classList.add('active') : el('#organize-mode').classList.remove('active'); + let buttonClasses = el('#organize-mode').classList; + !buttonClasses.contains('active') ? el('#organize-mode').classList.add('active') : el('#organize-mode').classList.remove('active'); - displayOrganizeModeMessage(); + displayOrganizeModeMessage(); } function setupOrganizeModeToggleEventListener() { - el('#organize-mode').addEventListener('click', organizeModeToggle); + el('#organize-mode').addEventListener('click', organizeModeToggle); } setupOrganizeModeToggleEventListener(); \ No newline at end of file diff --git a/js/popup.js b/js/popup.js index 8ac707c..4697922 100644 --- a/js/popup.js +++ b/js/popup.js @@ -1,5 +1,3 @@ - - var storage = chrome.storage.local; var viewModel = {}; //Just an object for the databinding @@ -8,40 +6,49 @@ function applyBinding() { } function toggle(prop) { - storage.get({[prop]: false}, function(obj) { - storage.set({[prop] : !obj[prop]}); + storage.get({ + [prop]: false + }, function(obj) { + storage.set({ + [prop]: !obj[prop] + }); viewModel[prop] = !obj[prop]; applyBinding(); }); } - - function openRedirectorSettings() { - - //switch to open one if we have it to minimize conflicts var url = chrome.extension.getURL('redirector.html'); - - //FIREFOXBUG: Firefox chokes on url:url filter if the url is a moz-extension:// url - //so we don't use that, do it the more manual way instead. - chrome.tabs.query({currentWindow:true}, function(tabs) { - for (var i=0; i < tabs.length; i++) { + + chrome.tabs.query({ + currentWindow: true + }, function(tabs) { + for (var i = 0; i < tabs.length; i++) { if (tabs[i].url == url) { - chrome.tabs.update(tabs[i].id, {active:true}, function(tab) { + chrome.tabs.update(tabs[i].id, { + active: true + }, function() { close(); }); return; } } - chrome.tabs.create({url:url, active:true}); + chrome.tabs.create({ + url: url, + active: true + }, function() { + close(); + }); }); - return; -}; - +} function pageLoad() { - storage.get({logging:false, enableNotifications:false, disabled: false}, function(obj) { + storage.get({ + logging: false, + enableNotifications: false, + disabled: false + }, function(obj) { viewModel = obj; applyBinding(); }) @@ -53,4 +60,4 @@ function pageLoad() { } pageLoad(); -//Setup page... +//Setup page... \ No newline at end of file diff --git a/js/redirect.js b/js/redirect.js index eff4d2c..fac8017 100644 --- a/js/redirect.js +++ b/js/redirect.js @@ -1,4 +1,3 @@ - function Redirect(o) { this._init(o); } @@ -15,36 +14,36 @@ Redirect.REGEX = 'R'; Redirect.requestTypes = { main_frame: "Main window (address bar)", sub_frame: "IFrames", - stylesheet : "Stylesheets", + stylesheet: "Stylesheets", font: "Fonts", - script : "Scripts", - image : "Images", + script: "Scripts", + image: "Images", imageset: "Responsive Images in Firefox", - object : "Objects (e.g. Flash content, Java applets)", - object_subrequest : "Object subrequests", - xmlhttprequest : "XMLHttpRequests (Ajax)", - history : "HistoryState", - other : "Other" + object: "Objects (e.g. Flash content, Java applets)", + object_subrequest: "Object subrequests", + xmlhttprequest: "XMLHttpRequests (Ajax)", + history: "HistoryState", + other: "Other" }; - Redirect.prototype = { //attributes - description : '', - exampleUrl : '', - exampleResult : '', - error : null, - includePattern : '', - excludePattern : '', - patternDesc:'', - redirectUrl : '', - patternType : '', - processMatches : 'noProcessing', - disabled : false, + description: '', + id: '', + exampleUrl: '', + exampleResult: '', + error: null, + includePattern: '', + excludePattern: '', + patternDesc: '', + redirectUrl: '', + patternType: '', + processMatches: 'noProcessing', + disabled: false, grouped: false, - compile : function() { + compile: function() { var incPattern = this._preparePattern(this.includePattern); var excPattern = this._preparePattern(this.excludePattern); @@ -57,33 +56,35 @@ Redirect.prototype = { } }, - equals : function(redirect) { - return this.description == redirect.description - && this.exampleUrl == redirect.exampleUrl - && this.includePattern == redirect.includePattern - && this.excludePattern == redirect.excludePattern - && this.patternDesc == redirect.patternDesc - && this.redirectUrl == redirect.redirectUrl - && this.patternType == redirect.patternType - && this.processMatches == redirect.processMatches - && this.appliesTo.toString() == redirect.appliesTo.toString(); + equals: function(redirect) { + return this.description == redirect.description && + this.id == redirect.id && + this.exampleUrl == redirect.exampleUrl && + this.includePattern == redirect.includePattern && + this.excludePattern == redirect.excludePattern && + this.patternDesc == redirect.patternDesc && + this.redirectUrl == redirect.redirectUrl && + this.patternType == redirect.patternType && + this.processMatches == redirect.processMatches && + this.appliesTo.toString() == redirect.appliesTo.toString(); }, - toObject : function() { + toObject: function() { return { - description : this.description, - exampleUrl : this.exampleUrl, - exampleResult : this.exampleResult, - error : this.error, - includePattern : this.includePattern, - excludePattern : this.excludePattern, - patternDesc : this.patternDesc, - redirectUrl : this.redirectUrl, - patternType : this.patternType, - processMatches : this.processMatches, - disabled : this.disabled, + description: this.description, + id: this.id, + exampleUrl: this.exampleUrl, + exampleResult: this.exampleResult, + error: this.error, + includePattern: this.includePattern, + excludePattern: this.excludePattern, + patternDesc: this.patternDesc, + redirectUrl: this.redirectUrl, + patternType: this.patternType, + processMatches: this.processMatches, + disabled: this.disabled, grouped: this.grouped, - appliesTo : this.appliesTo.slice(0) + appliesTo: this.appliesTo.slice(0) }; }, @@ -92,11 +93,13 @@ Redirect.prototype = { this.compile(); } var result = { - isMatch : false, - isExcludeMatch : false, - isDisabledMatch : false, - redirectTo : '', - toString : function() { return JSON.stringify(this); } + isMatch: false, + isExcludeMatch: false, + isDisabledMatch: false, + redirectTo: '', + toString: function() { + return JSON.stringify(this); + } }; var redirectTo = this._includeMatch(url); @@ -116,7 +119,7 @@ Redirect.prototype = { //Updates the .exampleResult field or the .error //field depending on if the example url and patterns match //and make a good redirect - updateExampleResult : function() { + updateExampleResult: function() { //Default values this.error = null; @@ -131,7 +134,7 @@ Redirect.prototype = { if (this.patternType == Redirect.REGEX && this.includePattern) { try { new RegExp(this.includePattern, 'gi'); - } catch(e) { + } catch (e) { this.error = 'Invalid regular expression in Include pattern.'; return; } @@ -140,7 +143,7 @@ Redirect.prototype = { if (this.patternType == Redirect.REGEX && this.excludePattern) { try { new RegExp(this.excludePattern, 'gi'); - } catch(e) { + } catch (e) { this.error = 'Invalid regular expression in Exclude pattern.'; return; } @@ -166,7 +169,7 @@ Redirect.prototype = { // return; //} - if (!match.isMatch) { + if (!match.isMatch) { this.error = 'The include pattern does not match the example url.'; return; } @@ -178,19 +181,19 @@ Redirect.prototype = { return this.patternType == Redirect.REGEX; }, - isWildcard : function() { + isWildcard: function() { return this.patternType == Redirect.WILDCARD; }, - test : function() { + test: function() { return this.getMatch(this.exampleUrl); }, //Private functions below - _rxInclude : null, - _rxExclude : null, + _rxInclude: null, + _rxExclude: null, - _preparePattern : function(pattern) { + _preparePattern: function(pattern) { if (!pattern) { return null; } @@ -213,9 +216,10 @@ Redirect.prototype = { } }, - _init : function(o) { + _init: function(o) { o = o || {}; this.description = o.description || ''; + this.id = o.id || ''; this.exampleUrl = o.exampleUrl || ''; this.exampleResult = o.exampleResult || ''; this.error = o.error || null; @@ -249,21 +253,21 @@ Redirect.prototype = { get processMatchesExampleText() { let examples = { - noProcessing : 'Use matches as they are', - urlEncode : 'E.g. turn /bar/foo?x=2 into %2Fbar%2Ffoo%3Fx%3D2', - urlDecode : 'E.g. turn %2Fbar%2Ffoo%3Fx%3D2 into /bar/foo?x=2', - doubleUrlDecode : 'E.g. turn %252Fbar%252Ffoo%253Fx%253D2 into /bar/foo?x=2', - base64Decode : 'E.g. turn aHR0cDovL2Nubi5jb20= into http://cnn.com' - }; + noProcessing: 'Use matches as they are', + urlEncode: 'E.g. turn /bar/foo?x=2 into %2Fbar%2Ffoo%3Fx%3D2', + urlDecode: 'E.g. turn %2Fbar%2Ffoo%3Fx%3D2 into /bar/foo?x=2', + doubleUrlDecode: 'E.g. turn %252Fbar%252Ffoo%253Fx%253D2 into /bar/foo?x=2', + base64Decode: 'E.g. turn aHR0cHM6Ly9nb29nbGUuY29tLw== into https://google.com/' + }; return examples[this.processMatches]; }, - toString : function() { + toString: function() { return JSON.stringify(this.toObject(), null, 2); }, - _includeMatch : function(url) { + _includeMatch: function(url) { if (!this._rxInclude) { return null; } @@ -275,24 +279,27 @@ Redirect.prototype = { for (var i = matches.length - 1; i > 0; i--) { var repl = matches[i] || ''; if (this.processMatches == 'urlDecode') { - repl = unescape(repl); + repl = decodeURIComponent(repl); } else if (this.processMatches == 'doubleUrlDecode') { - repl = unescape(unescape(repl)); + repl = decodeURIComponent(decodeURIComponent(repl)); } else if (this.processMatches == 'urlEncode') { repl = encodeURIComponent(repl); } else if (this.processMatches == 'base64decode') { - if (repl.indexOf('%') > -1) { - repl = unescape(repl); + if (/^[A-Za-z0-9+/]*={0,2}$/.test(repl)) { // Check if it's valid base64 + repl = atob(repl); + } else { + // Handle invalid base64 encoding here + console.error('Invalid base64 encoding:', repl); + repl = ''; // You can set it to an empty string or handle the error as needed } - repl = atob(repl); } resultUrl = resultUrl.replace(new RegExp('\\$' + i, 'gi'), repl); } this._rxInclude.lastIndex = 0; return resultUrl; - }, + }, - _excludeMatch : function(url) { + _excludeMatch: function(url) { if (!this._rxExclude) { return false; } @@ -300,4 +307,4 @@ Redirect.prototype = { this._rxExclude.lastIndex = 0; return shouldExclude; } -}; +}; \ No newline at end of file diff --git a/js/redirectorpage.js b/js/redirectorpage.js index 38e1331..ace0a0e 100644 --- a/js/redirectorpage.js +++ b/js/redirectorpage.js @@ -1,11 +1,17 @@ var REDIRECTS = []; // The global redirects list... var options = { - isSyncEnabled : false + isSyncEnabled: false }; var template; function normalize(r) { - return new Redirect(r).toObject(); //Cleans out any extra props, and adds default values for missing ones. + let normalizedRedirect = new Redirect(r).toObject(); + // Check if the redirect already has an ID, assign a new one only if it doesn't + if (normalizedRedirect.id === undefined) { + normalizedRedirect.id = nanoid(); + console.log("Assigned new ID:", normalizedRedirect.id); + } + return normalizedRedirect; } // Saves the entire list of redirects to storage. @@ -14,49 +20,54 @@ function saveChanges() { // Clean them up so angular $$hash things and stuff don't get serialized. let arr = REDIRECTS.map(normalize); - chrome.runtime.sendMessage({type:"save-redirects", redirects:arr}, function(response) { - console.log(response.message); - if(response.message.indexOf("Redirects failed to save") > -1){ + chrome.runtime.sendMessage({ + type: "save-redirects", + redirects: arr + }, function(response) { + if (response.message.indexOf("Redirects failed to save") > -1) { showMessage(response.message, false); - } else{ - console.log('Saved ' + arr.length + ' redirects at ' + new Date() + '. Message from background page:' + response.message); + } else { + console.log(`Saved ${arr.length} redirects at ${new Date()}. Message from background page: ${response.message}`); } }); } function toggleSyncSetting() { - chrome.runtime.sendMessage({type:"toggle-sync", isSyncEnabled: !options.isSyncEnabled}, function(response) { - if(response.message === "sync-enabled"){ + chrome.runtime.sendMessage({ + type: "toggle-sync", + isSyncEnabled: !options.isSyncEnabled + }, function(response) { + if (response.message === "sync-enabled") { options.isSyncEnabled = true; showMessage('Sync is enabled!', true); - } else if(response.message === "sync-disabled"){ + } else if (response.message === "sync-disabled") { options.isSyncEnabled = false; showMessage('Sync is disabled - local storage will be used!', true); - } else if(response.message.indexOf("Sync Not Possible")>-1){ + } else if (response.message.indexOf("Sync Not Possible") > -1) { options.isSyncEnabled = false; - chrome.storage.local.set({isSyncEnabled: $s.isSyncEnabled}, function(){ - // console.log("set back to false"); + chrome.storage.local.set({ + isSyncEnabled: $s.isSyncEnabled + }, function() { + //console.log("set back to false"); }); showMessage(response.message, false); - } - else { + } else { alert(response.message) - showMessage('Error occured when trying to change Sync settings. Look at the logs and raise an issue',false); + showMessage('Error occured when trying to change Sync settings. Look at the logs and raise an issue', false); } el('#storage-sync-option input').checked = options.isSyncEnabled; }); } function renderRedirects() { - el('.redirect-rows').textContent = ''; - for (let i=0; i < REDIRECTS.length; i++) { - let r = REDIRECTS[i]; - let node = template.cloneNode(true); - node.removeAttribute('id'); - - renderSingleRedirect(node, r, i); - el('.redirect-rows').appendChild(node); - } + const fragment = document.createDocumentFragment(); + REDIRECTS.forEach((redirect, index) => { + let node = template.cloneNode(true); + node.removeAttribute('id'); + renderSingleRedirect(node, redirect, index); + fragment.appendChild(node); + }); + el('.redirect-rows').appendChild(fragment); } function renderSingleRedirect(node, redirect, index) { @@ -79,7 +90,7 @@ function renderSingleRedirect(node, redirect, index) { let checkmark = node.querySelectorAll('.checkmark'); - if(checkmark.length == 1) { + if (checkmark.length == 1) { checkmark[0].setAttribute('data-index', index); } @@ -89,7 +100,6 @@ function renderSingleRedirect(node, redirect, index) { delete redirect.$index; } - function updateBindings() { let nodes = document.querySelectorAll('.redirect-row'); @@ -98,7 +108,7 @@ function updateBindings() { throw new Error('Mismatch in lengths, Redirects are ' + REDIRECTS.length + ', nodes are ' + nodes.length) } - for (let i=0; i < nodes.length; i++) { + for (let i = 0; i < nodes.length; i++) { let node = nodes[i]; let redirect = REDIRECTS[i]; renderSingleRedirect(node, redirect, i); @@ -106,27 +116,58 @@ function updateBindings() { } function duplicateRedirect(index) { - let redirect = new Redirect(REDIRECTS[index]); - REDIRECTS.splice(index, 0, redirect); + let originalRedirect = new Redirect(REDIRECTS[index]); + + // Create a new Redirect instance for the duplicate + let duplicatedRedirect = new Redirect({ + ...originalRedirect, // Copy properties from the original redirect + description: incrementDescription(originalRedirect.description) + }); + + // Ensure the new description is unique across the entire list + while (descriptionExists(duplicatedRedirect.description)) { + duplicatedRedirect.description = incrementDescription(duplicatedRedirect.description); + } + + REDIRECTS.splice(index + 1, 0, duplicatedRedirect); + + let newNode = template.cloneNode(true); + newNode.removeAttribute('id'); + el('.redirect-rows').appendChild(newNode); + updateBindings(); + saveChanges(); +} - let newNode = template.cloneNode(true); - newNode.removeAttribute('id'); - el('.redirect-rows').appendChild(newNode); - updateBindings(); - saveChanges(); +function incrementDescription(description) { + // Check if the description contains a number at the end + let match = description.match(/(\d+)$/); + let count = match ? parseInt(match[1]) + 1 : 2; + + // Increment number in the description + return description.replace(/(\d+)?$/, ' ' + count); +} + +function descriptionExists(description) { + // Check if the given description already exists in the list + return REDIRECTS.some(redirect => redirect.description === description); } function checkIfGroupingExists() { - let grouping = REDIRECTS.map((row, i) => { return { row, index: i}}) - .filter(result => result.row.grouped) - .sort((a, b) => a.index - b.index); + let grouping = REDIRECTS.map((row, i) => { + return { + row, + index: i + } + }) + .filter(result => result.row.grouped) + .sort((a, b) => a.index - b.index); return grouping; } function toggleDisabled(index) { let grouping = checkIfGroupingExists(); - if(grouping && grouping.length > 1) { + if (grouping && grouping.length > 1) { for (let redirect of grouping) { let redirectDom = REDIRECTS[redirect.index]; redirectDom.disabled = !redirectDom.disabled @@ -152,33 +193,33 @@ function clearGrouping(elm) { } function swap(node1, node2) { - const afterNode2 = node2.nextElementSibling; - const parent = node2.parentNode; - node1.replaceWith(node2); - parent.insertBefore(node1, afterNode2); + const afterNode2 = node2.nextElementSibling; + const parent = node2.parentNode; + node1.replaceWith(node2); + parent.insertBefore(node1, afterNode2); } function groupedMoveDown(group) { - var jumpLength = 1; + var jumpLength = 1; - if(isGroupAdjacent(group)) { - jumpLength = group.length; - } + if (isGroupAdjacent(group)) { + jumpLength = group.length; + } - for(let rule of group) { - let elm = document.querySelector("[data-index='" + (rule.index).toString() + "']"); - let prev = document.querySelector("[data-index='" + (rule.index + jumpLength).toString() + "']"); - clearGrouping(elm); - clearGrouping(prev); - swap(elm,prev); - } + for (let rule of group) { + let elm = document.querySelector("[data-index='" + (rule.index).toString() + "']"); + let prev = document.querySelector("[data-index='" + (rule.index + jumpLength).toString() + "']"); + clearGrouping(elm); + clearGrouping(prev); + swap(elm, prev); + } - for(let rule of group) { - rule.row.grouped = false; - let prevRedir = REDIRECTS[rule.index + jumpLength]; - REDIRECTS[rule.index + jumpLength] = REDIRECTS[rule.index]; - REDIRECTS[rule.index] = prevRedir; - } + for (let rule of group) { + rule.row.grouped = false; + let prevRedir = REDIRECTS[rule.index + jumpLength]; + REDIRECTS[rule.index + jumpLength] = REDIRECTS[rule.index]; + REDIRECTS[rule.index] = prevRedir; + } updateBindings(); saveChanges(); @@ -186,8 +227,8 @@ function groupedMoveDown(group) { function isGroupAdjacent(grouping) { let distances = []; - for(let i = grouping.length - 1; i >= 0; i--) { - if(i != 0) { + for (let i = grouping.length - 1; i >= 0; i--) { + if (i != 0) { distances.push(grouping[i].index - grouping[i - 1].index); } } @@ -197,22 +238,22 @@ function isGroupAdjacent(grouping) { function groupedMoveUp(group) { var jumpLength = 1; - if(isGroupAdjacent(group)) { + if (isGroupAdjacent(group)) { jumpLength = group.length; } - for(let rule of group) { + for (let rule of group) { let elm = document.querySelector("[data-index='" + (rule.index).toString() + "']"); let prev = document.querySelector("[data-index='" + (rule.index - jumpLength).toString() + "']"); clearGrouping(elm); clearGrouping(prev); - if(jumpLength > 1) { - swap(elm,prev); + if (jumpLength > 1) { + swap(elm, prev); } } - for(let rule of group) { + for (let rule of group) { rule.row.grouped = false; let prevRedir = REDIRECTS[rule.index - jumpLength]; REDIRECTS[rule.index - jumpLength] = REDIRECTS[rule.index]; @@ -222,14 +263,15 @@ function groupedMoveUp(group) { updateBindings(); saveChanges(); } + function moveUp(index) { let grouping = checkIfGroupingExists(); - if(grouping.length > 1) { + if (grouping.length > 1) { groupedMoveUp(grouping); } else { - let prev = REDIRECTS[index-1]; - REDIRECTS[index-1] = REDIRECTS[index]; + let prev = REDIRECTS[index - 1]; + REDIRECTS[index - 1] = REDIRECTS[index]; REDIRECTS[index] = prev; } @@ -240,11 +282,11 @@ function moveUp(index) { function moveDown(index) { let grouping = checkIfGroupingExists(); - if(grouping.length > 1) { + if (grouping.length > 1) { groupedMoveDown(grouping); } else { - let next = REDIRECTS[index+1]; - REDIRECTS[index+1] = REDIRECTS[index]; + let next = REDIRECTS[index + 1]; + REDIRECTS[index + 1] = REDIRECTS[index]; REDIRECTS[index] = next; } updateBindings(); @@ -272,42 +314,47 @@ function pageLoad() { //Need to proxy this through the background page, because Firefox gives us dead objects //nonsense when accessing chrome.storage directly. - chrome.runtime.sendMessage({type: "get-redirects"}, function(response) { - console.log('Received redirects message, count=' + response.redirects.length); - for (var i=0; i < response.redirects.length; i++) { + chrome.runtime.sendMessage({ + type: "get-redirects" + }, function(response) { + //console.log(`Received redirects message, count=${response.redirects.length}`); + for (var i = 0; i < response.redirects.length; i++) { REDIRECTS.push(new Redirect(response.redirects[i])); - } + } if (response.redirects.length === 0) { - //Add example redirect for first time users... - REDIRECTS.push(new Redirect( - { - "description": "Example redirect, try going to http://example.com/anywordhere", - "exampleUrl": "http://example.com/some-word-that-matches-wildcard", - "exampleResult": "https://google.com/search?q=some-word-that-matches-wildcard", - "error": null, - "includePattern": "http://example.com/*", - "excludePattern": "", - "patternDesc": "Any word after example.com leads to google search for that word.", - "redirectUrl": "https://google.com/search?q=$1", - "patternType": "W", - "processMatches": "noProcessing", - "disabled": false, - "appliesTo": [ - "main_frame" - ] - } - )); + //console.log("First run, adding example rule") + REDIRECTS.push(new Redirect({ + "description": "Example redirect, try going to https://example.com/anywordhere", + "exampleUrl": "https://example.com/some-word-that-matches-wildcard", + "exampleResult": "https://google.com/search?q=some-word-that-matches-wildcard", + "error": null, + "id": "NEixgGiEr3XHcj6_K_kKl", + "includePattern": "https://example.com/*", + "excludePattern": "", + "patternDesc": "Any word after example.com leads to google search for that word.", + "redirectUrl": "https://google.com/search?q=$1", + "patternType": "W", + "processMatches": "noProcessing", + "disabled": false, + "appliesTo": [ + "main_frame" + ] + })); } renderRedirects(); + saveChanges(); + updateExportLink(); }); - chrome.storage.local.get({isSyncEnabled:false}, function(obj){ + chrome.storage.local.get({ + isSyncEnabled: false + }, function(obj) { options.isSyncEnabled = obj.isSyncEnabled; el('#storage-sync-option').checked = options.isSyncEnabled; }); - if(navigator.userAgent.toLowerCase().indexOf("chrome") > -1){ + if (navigator.userAgent.toLowerCase().indexOf("chrome") > -1) { show('#storage-sync-option'); } @@ -316,7 +363,7 @@ function pageLoad() { el('#hide-message').addEventListener('click', hideMessage); el('#storage-sync-option input').addEventListener('click', toggleSyncSetting); el('.redirect-rows').addEventListener('click', function(ev) { - if(ev.target.type == 'checkbox') { + if (ev.target.type == 'checkbox') { ev.target.nextElementSibling.classList.add("checkMarked"); ev.target.parentElement.parentElement.classList.add('grouped'); toggleGrouping(ev.target.index); @@ -341,7 +388,9 @@ function pageLoad() { function updateFavicon(e) { let type = e.matches ? 'dark' : 'light' el('link[rel="shortcut icon"]').href = `images/icon-${type}-theme-32.png`; - chrome.runtime.sendMessage({type: "update-icon"}); //Only works if this page is open, but still, better than nothing... + chrome.runtime.sendMessage({ + type: "update-icon" + }); //Only works if this page is open, but still, better than nothing... } let mql = window.matchMedia('(prefers-color-scheme:dark)'); @@ -350,9 +399,9 @@ mql.onchange = updateFavicon; updateFavicon(mql); function toggleGrouping(index) { - if(REDIRECTS[index]) { + if (REDIRECTS[index]) { REDIRECTS[index].grouped = !REDIRECTS[index].grouped; } } -pageLoad(); +pageLoad(); \ No newline at end of file diff --git a/js/stub.js b/js/stub.js index 59b2da9..b3b5013 100644 --- a/js/stub.js +++ b/js/stub.js @@ -1,112 +1,117 @@ - //Dummy file to use while developing the UI. This way we can just develop it on a local fileserver, and don't have to reload //an extension for every tiny change! -if (!chrome || !chrome.storage || !chrome.storage.local) { +if (!chrome || !chrome.storage || !chrome.storage.local) { + + let testData = { + "createdBy": "Redirector v3.2", + "createdAt": "2019-12-09T12:54:13.391Z", + "redirects": [{ + "description": "Mbl test", + "exampleUrl": "https://mbl.is", + "exampleResult": "http://foo.is", + "error": null, + "includePattern": "*mbl*", + "excludePattern": "", + "patternDesc": "My description", + "redirectUrl": "http://foo.is", + "patternType": "R", + "processMatches": "noProcessing", + "disabled": false, + "appliesTo": [ + "main_frame", + "script" + ] + }, + { + "description": "Msdfsdfbl test", + "exampleUrl": "https://mbssfdsl.is", + "exampleResult": "http://foo.is", + "error": null, + "includePattern": "*mbl*", + "excludePattern": "", + "patternDesc": "My description", + "redirectUrl": "http://foo.is", + "patternType": "W", + "processMatches": "urlEncode", + "disabled": false, + "appliesTo": [ + "main_frame", + "sub_frame" + ] + }, { + "description": "https://foo.is?s=joh", + "exampleUrl": "https://foo.is?s=joh", + "exampleResult": "https://foo.is", + "error": null, + "includePattern": "(.*)(\\?s=)(.*)", + "excludePattern": "", + "patternDesc": "Test error", + "redirectUrl": "$1", + "patternType": "R", + "processMatches": "noProcessing", + "disabled": false, + "appliesTo": [ + "main_frame" + ] + } + ] + }; - let testData = { - "createdBy": "Redirector v3.2", - "createdAt": "2019-12-09T12:54:13.391Z", - "redirects": [ - { - "description": "Mbl test", - "exampleUrl": "https://mbl.is", - "exampleResult": "http://foo.is", - "error": null, - "includePattern": "*mbl*", - "excludePattern": "", - "patternDesc": "My description", - "redirectUrl": "http://foo.is", - "patternType": "R", - "processMatches": "noProcessing", - "disabled": false, - "appliesTo": [ - "main_frame", - "script" - ] - }, - { - "description": "Msdfsdfbl test", - "exampleUrl": "https://mbssfdsl.is", - "exampleResult": "http://foo.is", - "error": null, - "includePattern": "*mbl*", - "excludePattern": "", - "patternDesc": "My description", - "redirectUrl": "http://foo.is", - "patternType": "W", - "processMatches": "urlEncode", - "disabled": false, - "appliesTo": [ - "main_frame", - "sub_frame" - ] - }, { - "description": "https://foo.is?s=joh", - "exampleUrl": "https://foo.is?s=joh", - "exampleResult": "https://foo.is", - "error": null, - "includePattern": "(.*)(\\?s=)(.*)", - "excludePattern": "", - "patternDesc": "Test error", - "redirectUrl": "$1", - "patternType": "R", - "processMatches": "noProcessing", - "disabled": false, - "appliesTo": [ - "main_frame" - ] - } - ] - }; + localStorage.redirector = JSON.stringify(testData); - localStorage.redirector = JSON.stringify(testData); + //Make dummy for testing... + window.chrome = window.chrome || {}; + chrome.storage = { + local: { + get: function(defaults, callback) { + let data = JSON.parse(localStorage.redirector || '{}'); + let result = {}; + for (let key in defaults) { + if (typeof data[key] !== 'undefined') { + result[key] = data[key]; + } else { + result[key] = defaults[key]; + } + } + callback(result); + }, - //Make dummy for testing... - window.chrome = window.chrome || {}; - chrome.storage = { - local : { - get : function(defaults, callback) { - let data = JSON.parse(localStorage.redirector || '{}'); - - let result = {}; - for (let key in defaults) { - if (typeof data[key] !== 'undefined') { - result[key] = data[key]; - } else { - result[key] = defaults[key]; - } - } - callback(result); - }, + set: function(obj) { + let data = JSON.parse(localStorage.redirector || '{}'); - set : function(obj) { - let data = JSON.parse(localStorage.redirector || '{}'); - - for (let k in obj) { - data[k] = obj[k]; - } - localStorage.redirector = JSON.stringify(data); - } - } - }; + for (let k in obj) { + data[k] = obj[k]; + } + localStorage.redirector = JSON.stringify(data); + } + } + }; - chrome.runtime = { - sendMessage : function(params, callback) { - let data = JSON.parse(localStorage.redirector || '{}'); - if (params.type === 'get-redirects') { - chrome.storage.local.get({redirects:[]}, callback); - } else if (params.type === 'toggle-sync') { - if (params.isSyncEnabled) { - callback({message:'sync-enabled'}); - } else { - callback({message:'sync-disabled'}); - } - } - }, - getManifest : function() { - return { version: '0-dev' }; - } - }; + chrome.runtime = { + sendMessage: function(params, callback) { + let data = JSON.parse(localStorage.redirector || '{}'); + if (params.type === 'get-redirects') { + chrome.storage.local.get({ + redirects: [] + }, callback); + } else if (params.type === 'toggle-sync') { + if (params.isSyncEnabled) { + callback({ + message: 'sync-enabled' + }); + } else { + callback({ + message: 'sync-disabled' + }); + } + } + }, + getManifest: function() { + return { + version: '0-dev' + }; + } + }; } \ No newline at end of file diff --git a/js/util.js b/js/util.js index 4249acc..bccf065 100644 --- a/js/util.js +++ b/js/util.js @@ -4,22 +4,22 @@ function dataBind(el, dataObject) { return prop.charAt(0) === '!' ? !dataObject[prop.substr(1)] : dataObject[prop]; } - if (typeof el === 'string') { + if (typeof el === 'string') { el = document.querySelector(el) } for (let tag of el.querySelectorAll('[data-bind]')) { - let prop = tag.getAttribute('data-bind'); + let prop = tag.getAttribute('data-bind'); if (tag.tagName.toLowerCase() === 'input') { if (tag.getAttribute('type').toLowerCase() === 'radio') { tag.checked = dataObject[prop] === tag.getAttribute('value'); - } else if (tag.getAttribute('type').toLowerCase() === 'checkbox') { - tag.checked = dataObject[prop]; - } else { - tag.value = dataObject[prop]; + } else if (tag.getAttribute('type').toLowerCase() === 'checkbox') { + tag.checked = dataObject[prop]; + } else { + tag.value = dataObject[prop]; } } else if (tag.tagName.toLowerCase() === 'select') { for (let opt of tag.querySelectorAll('option')) { - if (opt.getAttribute('value') === dataObject[prop]) { + if (opt.getAttribute('value') === dataObject[prop]) { opt.setAttribute('selected', 'selected'); } else { opt.removeAttribute('selected'); @@ -53,7 +53,7 @@ function dataBind(el, dataObject) { for (let tag of el.querySelectorAll('[data-class]')) { let [className, prop] = tag.getAttribute('data-class').split(':'); let shouldHaveClass = boolValue(prop); - if (shouldHaveClass) { + if (shouldHaveClass) { tag.classList.add(className); } else { tag.classList.remove(className); @@ -83,7 +83,7 @@ function showForm(selector, dataObject) { } function move(arr, from, to) { - arr.splice(to, 0, arr.splice(from, 1)[0]); + arr.splice(to, 0, arr.splice(from, 1)[0]); } function hideForm(selector) { @@ -95,7 +95,9 @@ function hideForm(selector) { // Shows a message bar above the list of redirects. function showMessage(message, success) { let messageBox = document.getElementById('message-box'); - dataBind('#message-box', {message}); + dataBind('#message-box', { + message + }); if (success) { messageBox.className = 'visible success'; } else { diff --git a/manifest.json b/manifest.json index 19b88bb..6c18f06 100644 --- a/manifest.json +++ b/manifest.json @@ -36,8 +36,8 @@ "persistent": true }, "options_ui": { - "page": "popup.html", - "chrome_style": true + "page": "redirector.html", + "open_in_tab": true }, "browser_action": { "default_icon": { @@ -89,4 +89,4 @@ } ] } -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..effbc76 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,29 @@ +{ + "name": "Redirector-master", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "nanoid": "^3.3.7" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..16ed9b3 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "nanoid": "^3.3.7" + } +} diff --git a/popup.html b/popup.html index b37c1de..940a652 100644 --- a/popup.html +++ b/popup.html @@ -1,20 +1,31 @@ - - REDIRECTOR - - - - - -

REDIRECTOR

-
Disabled
- - - - - - - - + + REDIRECTOR + + + + + +

REDIRECTOR

+
+ Disabled +
+ + + + + + + + diff --git a/redirector.html b/redirector.html index e0f6f4c..9d663f7 100644 --- a/redirector.html +++ b/redirector.html @@ -1,218 +1,288 @@ - - REDIRECTOR - - - - - - - -
-
- - -
-

Are you sure you want to delete this redirect?

-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - - -
-

Create Redirect

-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
- - -
-
-
- -
-
-
- -
-
-
- - -
- - -
-
- - -
- -

REDIRECTOR

-
Go where YOU want!
- - - - -
- - -
- - -
-
-
-

[Disabled]

-
-
- -

-
-
-

-
-
-

-
-
-

-
-
-

-
-
-

-
-
-
- - - - - - - - - -
- -
-
- -
- - - -
- - - - - - - - - - + + REDIRECTOR + + + + + + +
+ +
+

Are you sure you want to delete this redirect?

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+

Create Redirect

+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + +
+
+
+ +
+ +
+
+
+ +
+ + +
+
+
+ + +
+ + +
+
+ +
+ +
+
+

REDIRECTOR

+
Go where YOU want!
+ +
+ + +
+ +
+
+
+

+ [Disabled] + +

+
+
+ +

+
+
+ +

+
+
+ +

+
+
+ +

+
+
+ +

+ + + + +

+
+
+ +

+
+
+
+ + + + + + + + +
+ +
+
+ +
+ +
+ + + + + + + + + \ No newline at end of file