| @@ -1,3 +1,7 @@ | |||
| /***********/ | |||
| /* GENERAL */ | |||
| /***********/ | |||
| html { | |||
| box-sizing: border-box; | |||
| font-size: 16px; | |||
| @@ -5,6 +9,7 @@ html { | |||
| *, *::before, *::after { | |||
| box-sizing: inherit; | |||
| } | |||
| body { | |||
| margin: 0 auto; | |||
| display: flex; | |||
| @@ -13,12 +18,21 @@ body { | |||
| padding: 10px 20px; | |||
| font-size: 1rem; | |||
| } | |||
| body.noscroll { | |||
| overflow: hidden; | |||
| } | |||
| body > div { | |||
| position: relative; | |||
| } | |||
| body > div + div { | |||
| margin: 2.5em 0 1em 0; | |||
| } | |||
| /************/ | |||
| /* HEADINGS */ | |||
| /************/ | |||
| h1 { | |||
| border-bottom: 1px solid #ddd; | |||
| margin: 0.5em 0; | |||
| @@ -26,11 +40,21 @@ h1 { | |||
| } | |||
| h2 { | |||
| margin: 0; | |||
| background-color: #fff; | |||
| border-bottom: 1px solid #ddd; | |||
| font-size: 1.625rem; | |||
| padding: 0 0 2px 0; | |||
| } | |||
| h3 { | |||
| font-size: 1.3125rem; | |||
| color: #666; | |||
| border-bottom: 1px dotted #eee; | |||
| margin: 1em 0 0.5em 0; | |||
| } | |||
| /*************/ | |||
| /* TEXTAREAS */ | |||
| /*************/ | |||
| textarea { | |||
| border: 1px solid #aaa; | |||
| font-family: Inconsolata, Courier, monospace; | |||
| @@ -45,14 +69,20 @@ textarea { | |||
| #exclusionPatterns textarea { | |||
| min-height: 150px; | |||
| } | |||
| form { | |||
| /******************/ | |||
| /* OTHER CONTROLS */ | |||
| /******************/ | |||
| .top-controls { | |||
| text-align: right; | |||
| margin: 0 0 2.5em 0; | |||
| display: flex; | |||
| flex-flow: row wrap; | |||
| align-items: center; | |||
| max-width: 640px; | |||
| } | |||
| form .buttons { | |||
| .top-controls .buttons { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: flex-end; | |||
| @@ -61,27 +91,32 @@ form .buttons { | |||
| button { | |||
| -webkit-appearance: none; | |||
| -moz-appearance: none; | |||
| border: none; | |||
| background-color: transparent; | |||
| } | |||
| button:active { | |||
| transform: scale(0.95); | |||
| } | |||
| button:focus:active { | |||
| outline: none; | |||
| } | |||
| .top-controls button { | |||
| border: 1px solid #bbb; | |||
| color: #fff; | |||
| padding: 10px 16px; | |||
| cursor: default; | |||
| margin: 0 0 0 0.5em; | |||
| background-color: #fff; | |||
| } | |||
| button.save-button { | |||
| .top-controls button.save-button { | |||
| background-color: #16e; | |||
| font-size: 1.5rem; | |||
| } | |||
| button.reset-button { | |||
| .top-controls button.reset-button { | |||
| background-color: #d3453d; | |||
| font-size: 1rem; | |||
| } | |||
| button:active { | |||
| transform: scale(0.95); | |||
| } | |||
| button:focus:active { | |||
| outline: none; | |||
| } | |||
| button:disabled { | |||
| .top-controls button:disabled { | |||
| background-color: #777; | |||
| opacity: 0.3; | |||
| } | |||
| @@ -137,13 +172,21 @@ input#whitelist-mode, | |||
| .mode-select-container span:not(.disabled):hover { | |||
| border-bottom: 1px solid currentColor; | |||
| } | |||
| form button:not(:disabled):hover { | |||
| .top-controls button:not(:disabled):hover { | |||
| text-shadow: | |||
| 0 0 1px #fff, | |||
| 0 0 3px #fff, | |||
| 0 0 5px #fff; | |||
| cursor: pointer; | |||
| } | |||
| p.note { | |||
| font-size: 0.875em; | |||
| } | |||
| /******************/ | |||
| /* WHITELIST MODE */ | |||
| /******************/ | |||
| #matchingPatterns.disabled h2 { | |||
| color: rgba(0,0,0,0.15); | |||
| } | |||
| @@ -166,9 +209,11 @@ form button:not(:disabled):hover { | |||
| font-family: Inconsolata, Courier, monospace; | |||
| font-size: 4em; | |||
| } | |||
| p.note { | |||
| font-size: 0.875em; | |||
| } | |||
| /***************/ | |||
| /* BOTTOM INFO */ | |||
| /***************/ | |||
| .bottom-info { | |||
| font-size: 0.875rem; | |||
| color: #aaa; | |||
| @@ -178,4 +223,120 @@ p.note { | |||
| } | |||
| .bottom-info p { | |||
| margin: 0; | |||
| } | |||
| } | |||
| /****************/ | |||
| /* HELP OVERLAY */ | |||
| /****************/ | |||
| .open-help-button-container { | |||
| position: absolute; | |||
| top: 0; | |||
| right: 0; | |||
| } | |||
| .help-overlay { | |||
| position: fixed; | |||
| background-color: #ffe; | |||
| top: 10px; | |||
| left: 10px; | |||
| margin: 0; | |||
| border: 1px solid #777; | |||
| box-shadow: | |||
| 1px 1px 4px 0 #aaa; | |||
| width: calc(100% - 20px); | |||
| height: calc(100% - 20px); | |||
| line-height: 1.4; | |||
| padding: 36px 0 6px 0; | |||
| } | |||
| #help { | |||
| visibility: hidden; | |||
| } | |||
| #help:target { | |||
| visibility: visible; | |||
| } | |||
| .help-container { | |||
| height: 100%; | |||
| padding: 0 16px; | |||
| overflow-y: scroll; | |||
| } | |||
| .help-container::-webkit-scrollbar { | |||
| background-color: transparent; | |||
| width: 15px; | |||
| } | |||
| .help-container::-webkit-scrollbar-thumb { | |||
| background-color: #c1c1c1; | |||
| box-shadow: 0 0 0 3px #ffe inset; | |||
| } | |||
| .help-container::-webkit-scrollbar-thumb:hover { | |||
| background-color: #888; | |||
| } | |||
| .help-overlay h1 { | |||
| margin: 0 0 0.5em 0; | |||
| line-height: 1; | |||
| padding: 0 0 4px 0; | |||
| } | |||
| .help-overlay .close-button, | |||
| .open-help-button-container .open-help-button { | |||
| display: block; | |||
| cursor: pointer; | |||
| color: #999; | |||
| white-space: nowrap; | |||
| } | |||
| .help-overlay .close-button:hover, | |||
| .open-help-button-container .open-help-button:hover { | |||
| color: #000; | |||
| } | |||
| .help-overlay .close-button::before, | |||
| .open-help-button-container .open-help-button::before { | |||
| font-family: Font Awesome; | |||
| padding: 4px; | |||
| display: inline-block; | |||
| } | |||
| .help-overlay .close-button { | |||
| font-size: 0.875rem; | |||
| position: absolute; | |||
| top: 0; | |||
| right: 0; | |||
| padding: 6px 12px; | |||
| } | |||
| .help-overlay .close-button::before { | |||
| content: "\F00D"; | |||
| font-weight: 300; | |||
| font-size: 1rem; | |||
| position: relative; | |||
| top: 1px; | |||
| } | |||
| .open-help-button-container .open-help-button { | |||
| font-size: 1.125rem; | |||
| padding: 8px 12px 10px 10px; | |||
| } | |||
| .open-help-button-container .open-help-button::before { | |||
| content: "\F059"; | |||
| font-weight: 900; | |||
| font-size: 1rem; | |||
| } | |||
| /********/ | |||
| /* MISC */ | |||
| /********/ | |||
| code { | |||
| font-family: Inconsolata, monospace; | |||
| } | |||
| .fa { | |||
| font-family: Font Awesome; | |||
| } | |||
| .fa-light { | |||
| font-weight: 300; | |||
| } | |||
| .fa-normal { | |||
| font-weight: 400; | |||
| } | |||
| .fa-heavy { | |||
| font-weight: 900; | |||
| } | |||
| @@ -1,15 +1,16 @@ | |||
| <!DOCTYPE html> | |||
| <html> | |||
| <head> | |||
| <title>Always Kill Sticky - Options</title> | |||
| <title>AlwaysKillSticky - Options</title> | |||
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |||
| <meta http-equiv='Content-Type' content='text/html; charset=utf-8' /> | |||
| <link rel='stylesheet' type='text/css' href='options.css' /> | |||
| <link rel='stylesheet' type='text/css' href='fonts.css' /> | |||
| <link rel="shortcut icon" type="image/png" href="images/ASK_on_32.png" /> | |||
| </head> | |||
| <body> | |||
| <h1>Always Kill Sticky</h1> | |||
| <form> | |||
| <h1>AlwaysKillSticky</h1> | |||
| <form class='top-controls'> | |||
| <span class='mode-select-container'> | |||
| <span class='blacklist-mode-label'>Blacklist mode</span> | |||
| <input type='checkbox' id='whitelist-mode'></input> | |||
| @@ -39,6 +40,43 @@ | |||
| <p class='bottom-info'>AlwaysKillSticky v<span class='version'>0.0</span></p> | |||
| <p class='bottom-info'>© Said Achmiz 2019–present</p> | |||
| </div> | |||
| <form class='open-help-button-container'> | |||
| <button type='button' class='open-help-button'>Help</button> | |||
| </form> | |||
| <div id='help' class='help-overlay'> | |||
| <form> | |||
| <button type='button' class='close-button'>Close</button> | |||
| </form> | |||
| <div class='help-container'> | |||
| <h1>AlwaysKillSticky Help</h1> | |||
| <p>On sites/pages where it’s enabled, AlwaysKillSticky finds all elements whose <code>position</code> CSS property has a computed value of either <code>sticky</code> or <code>fixed</code>, and removes those elements.</p> | |||
| <h2>Basic usage</h2> | |||
| <p>To enable AlwaysKillSticky on a site, click the AlwaysKillSticky icon in your browser toolbar, then click the big button (shaped like a thumbtack: <span class='fa fa-heavy'></span>). Stickies will immediately be killed, and will always be killed whenever you visit that page, or any page on the same site, in the future.</p> | |||
| <p>To stop killing stickies on a site, just click the big button again. Stickies will no longer be killed when you visit pages on that site. (After disabling sticky-killing for a page, you can click the Reload (<span class='fa fa-heavy'></span>) button to refresh the page if you want to restore killed sticky elements on that page.)</p> | |||
| <h2>Modes</h2> | |||
| <p>AlwaysKillSticky can work in either of two modes: <strong>blacklist mode</strong> (the default) and <strong>whitelist mode</strong>. (You can switch modes at any time, via the Options page.)</p> | |||
| <h3>Blacklist mode</h3> | |||
| <p>In <strong>blacklist mode</strong>, stickies will <em>only</em> be killed if:</p> | |||
| <ul> | |||
| <li>the page URL matches one of the patterns in the <strong>matching patterns</strong> list, <em>and</em>;</li> | |||
| <li>the page does <em>not</em> match any of the patterns in the <strong>exclusion patterns</strong> list.</li> | |||
| </ul> | |||
| <p>In blacklist mode, if you click the big button (<span class='fa fa-heavy'></span>) in the AlwaysKillSticky popup to enable the | |||
| <h3>Whitelist mode</h3> | |||
| <p>In <strong>whitelist mode</strong>, <em>the matching patterns list is ignored</em>. Stickies are <em>always</em> killed, <em>unless</em> the page matches one of the patterns in the <strong>exclusion patterns</strong> list.</p> | |||
| <p>(In whitelist mode, the behavior of the main button (<span class='fa fa-heavy'></span>) in the AlwaysKillSticky popup is reversed.)</p> | |||
| <h2>Advanced usage</h2> | |||
| <p>AlwaysKillSticky automatically manages the lists of matching and exclusion patterns. When you enable or disable sticky-killing for a site (by clicking the big button in the AlwaysKillSticky popup), the pattern lists are automatically modified appropriately. There is usually no need to edit the lists yourself.</p> | |||
| <p>However, if you prefer more fine-grained control (e.g., if you want to kill stickies on all sites on a domain, or if you want to exclude specific paths of a site), you can edit the pattern lists by hand. The matching and exclusion patterns are <a href='https://en.wikipedia.org/wiki/Regular_expression' rel='nofollow' target='_blank'>regular expressions</a> (a.k.a. “regexps”). (The site <a href='https://regexr.com/' rel='nofollow' target='_blank'>RegExr.com</a> is useful if you are not entirely familiar with how regexps work—or even if you are.)</p> | |||
| <p><strong>NOTE:</strong> Lines beginning with a pound sign (#) are comments; they are ignored.</p> | |||
| <h3>How automatic management of the patterns lists works</h3> | |||
| <p>The comments in the <a href='popup.js' target='_blank'><code>popup.js</code> script file</a> explain exactly what happens to the patterns lists when you use the main button (<span class='fa fa-heavy'></span>) in the AlwaysKillSticky popup to enable or disable sticky-killing on a site.</p> | |||
| <h2>License</h2> | |||
| <p>This extension is released under the GNU General Public License | |||
| as published by the Free Software Foundation; either version | |||
| 2 of the License, or (at your option) any later version.</p> | |||
| </div> | |||
| </div> | |||
| </body> | |||
| <script src="functions.js"></script> | |||
| <script src="options.js"></script> | |||
| @@ -3,12 +3,12 @@ | |||
| /**********/ | |||
| /* Triggered when a click is received by the mode selector switch, or by | |||
| either of the two labels (“Blacklist” and “Whitelist”). | |||
| either of the two labels ("Blacklist" and "Whitelist"). | |||
| */ | |||
| function modeSelectorInputReceived() { | |||
| toggleModeSelectorState(); | |||
| // Activate the “Reset” and “Save” buttons, since changes have been made. | |||
| // Activate the "Reset" and "Save" buttons, since changes have been made. | |||
| setButtonsActive(true); | |||
| } | |||
| @@ -28,7 +28,7 @@ function toggleModeSelectorState(newMode) { | |||
| container.classList.toggle("whitelist", (newMode == "whitelist")); | |||
| checkbox.checked = (newMode == "whitelist"); | |||
| /* Enable both spans (the “Blacklist” and “Whitelist” labels, then disable | |||
| /* Enable both spans (the "Blacklist" and "Whitelist" labels, then disable | |||
| just the label associated with the now-enabled mode. */ | |||
| container.querySelectorAll("span").forEach(span => { | |||
| span.classList.toggle("disabled", false); | |||
| @@ -37,13 +37,13 @@ function toggleModeSelectorState(newMode) { | |||
| /* In whitelist mode, the matching patterns textarea must be disabled, | |||
| since the matching patterns list is treated as containing only the | |||
| single pattern “.*”. An overlay is shown, to indicate this. */ | |||
| single pattern ".*". An overlay is shown, to indicate this. */ | |||
| document.querySelector("div#matchingPatterns").classList.toggle("disabled", (newMode == "whitelist")); | |||
| document.querySelector("div#matchingPatterns textarea").disabled = (newMode == "whitelist"); | |||
| } | |||
| /* Saves the entered state in storage. | |||
| De-activate the “Reset” and “Save” buttons. | |||
| De-activate the "Reset" and "Save" buttons. | |||
| */ | |||
| function saveChanges() { | |||
| let matchingPatterns = document.querySelector("#matchingPatterns textarea").value; | |||
| @@ -57,7 +57,7 @@ function saveChanges() { | |||
| } | |||
| /* Retrieves the saved state from storage and reset the UI to reflect it, | |||
| discarding the user’s changes. | |||
| discarding the user's changes. | |||
| */ | |||
| function resetChanges(callback) { | |||
| chrome.storage.sync.get([ "matchingPatterns", "exclusionPatterns", "mode" ], (result) => { | |||
| @@ -70,7 +70,7 @@ function resetChanges(callback) { | |||
| toggleModeSelectorState(AKS.mode); | |||
| // De-activate the “Reset” and “Save” buttons. | |||
| // De-activate the "Reset" and "Save" buttons. | |||
| setButtonsActive(false); | |||
| // Expand all textareas. | |||
| @@ -83,16 +83,22 @@ function resetChanges(callback) { | |||
| }); | |||
| } | |||
| /* Activates or de-activates the “Reset” and “Save” buttons. Called when | |||
| /* Activates or de-activates the "Reset" and "Save" buttons. Called when | |||
| changes are made (to activate) or when changes are saved or reset (to | |||
| de-activate). | |||
| */ | |||
| function setButtonsActive(active) { | |||
| document.querySelectorAll("button").forEach(button => { | |||
| document.querySelectorAll(".top-controls button").forEach(button => { | |||
| button.disabled = !active; | |||
| }); | |||
| } | |||
| /* Enables or disables scrolling on the page body, depending on whether the | |||
| help overlay is visible. */ | |||
| function setBodyScrollState() { | |||
| document.querySelector("body").classList.toggle("noscroll", location.hash == "#help"); | |||
| } | |||
| /* Expands a textarea to show all its contents (up to a maximum height which is | |||
| set via CSS). | |||
| */ | |||
| @@ -105,6 +111,11 @@ function expandTextarea(textarea) { | |||
| }); | |||
| } | |||
| function setHelpOverlayVisible(visible) { | |||
| location.hash = visible ? "help" : ""; | |||
| setBodyScrollState(); | |||
| } | |||
| /******************/ | |||
| /* INITIALIZATION */ | |||
| /******************/ | |||
| @@ -157,5 +168,24 @@ function initialize() { | |||
| modeSelectorInputReceived(); | |||
| }); | |||
| }); | |||
| // Listener for open help button. | |||
| document.querySelector(".open-help-button").addActivateEvent((event) => { | |||
| setHelpOverlayVisible(true); | |||
| }); | |||
| // Listener for close help button. | |||
| document.querySelector(".help-overlay .close-button").addActivateEvent((event) => { | |||
| setHelpOverlayVisible(false); | |||
| }); | |||
| // Disable scrolling on the document body, if need be (if help is open). | |||
| setBodyScrollState(); | |||
| // Listener for Escape key (closes the help overlay). | |||
| document.addEventListener("keyup", (event) => { | |||
| if (event.keyCode == 27 && location.hash == "#help") { | |||
| setHelpOverlayVisible(false); | |||
| } | |||
| }); | |||
| } | |||
| initialize(); | |||
| @@ -6,7 +6,7 @@ | |||
| happens until the UI state is updated, killSticky() is called (if needed), | |||
| and the new settings are saved in storage.) | |||
| What “toggle the current state” actually means depends on the current mode. | |||
| What "toggle the current state” actually means depends on the current mode. | |||
| [1] In blacklist mode, toggleState() does one of the following: | |||
| @@ -265,12 +265,12 @@ function initialize() { | |||
| // Listener for Options button. | |||
| document.querySelector("button.options-button").addActivateEvent(() => { | |||
| chrome.runtime.openOptionsPage(null); | |||
| window.open(chrome.runtime.getURL('options.html')) | |||
| }); | |||
| // Listener for the Help button. | |||
| document.querySelector("button.help-button").addActivateEvent(() => { | |||
| // TODO: code! | |||
| window.open(chrome.runtime.getURL('options.html') + "#help"); | |||
| }); | |||
| // Listener for reload button (appears when sticky-killing is toggled OFF). | |||