| /***********/ | |||||
| /* GENERAL */ | |||||
| /***********/ | |||||
| html { | html { | ||||
| box-sizing: border-box; | box-sizing: border-box; | ||||
| font-size: 16px; | font-size: 16px; | ||||
| *, *::before, *::after { | *, *::before, *::after { | ||||
| box-sizing: inherit; | box-sizing: inherit; | ||||
| } | } | ||||
| body { | body { | ||||
| margin: 0 auto; | margin: 0 auto; | ||||
| display: flex; | display: flex; | ||||
| padding: 10px 20px; | padding: 10px 20px; | ||||
| font-size: 1rem; | font-size: 1rem; | ||||
| } | } | ||||
| body.noscroll { | |||||
| overflow: hidden; | |||||
| } | |||||
| body > div { | body > div { | ||||
| position: relative; | position: relative; | ||||
| } | } | ||||
| body > div + div { | body > div + div { | ||||
| margin: 2.5em 0 1em 0; | margin: 2.5em 0 1em 0; | ||||
| } | } | ||||
| /************/ | |||||
| /* HEADINGS */ | |||||
| /************/ | |||||
| h1 { | h1 { | ||||
| border-bottom: 1px solid #ddd; | border-bottom: 1px solid #ddd; | ||||
| margin: 0.5em 0; | margin: 0.5em 0; | ||||
| } | } | ||||
| h2 { | h2 { | ||||
| margin: 0; | margin: 0; | ||||
| background-color: #fff; | |||||
| border-bottom: 1px solid #ddd; | border-bottom: 1px solid #ddd; | ||||
| font-size: 1.625rem; | font-size: 1.625rem; | ||||
| padding: 0 0 2px 0; | 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 { | textarea { | ||||
| border: 1px solid #aaa; | border: 1px solid #aaa; | ||||
| font-family: Inconsolata, Courier, monospace; | font-family: Inconsolata, Courier, monospace; | ||||
| #exclusionPatterns textarea { | #exclusionPatterns textarea { | ||||
| min-height: 150px; | min-height: 150px; | ||||
| } | } | ||||
| form { | |||||
| /******************/ | |||||
| /* OTHER CONTROLS */ | |||||
| /******************/ | |||||
| .top-controls { | |||||
| text-align: right; | text-align: right; | ||||
| margin: 0 0 2.5em 0; | margin: 0 0 2.5em 0; | ||||
| display: flex; | display: flex; | ||||
| flex-flow: row wrap; | |||||
| align-items: center; | align-items: center; | ||||
| max-width: 640px; | max-width: 640px; | ||||
| } | } | ||||
| form .buttons { | |||||
| .top-controls .buttons { | |||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| justify-content: flex-end; | justify-content: flex-end; | ||||
| button { | button { | ||||
| -webkit-appearance: none; | -webkit-appearance: none; | ||||
| -moz-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; | border: 1px solid #bbb; | ||||
| color: #fff; | color: #fff; | ||||
| padding: 10px 16px; | padding: 10px 16px; | ||||
| cursor: default; | cursor: default; | ||||
| margin: 0 0 0 0.5em; | margin: 0 0 0 0.5em; | ||||
| background-color: #fff; | |||||
| } | } | ||||
| button.save-button { | |||||
| .top-controls button.save-button { | |||||
| background-color: #16e; | background-color: #16e; | ||||
| font-size: 1.5rem; | font-size: 1.5rem; | ||||
| } | } | ||||
| button.reset-button { | |||||
| .top-controls button.reset-button { | |||||
| background-color: #d3453d; | background-color: #d3453d; | ||||
| font-size: 1rem; | font-size: 1rem; | ||||
| } | } | ||||
| button:active { | |||||
| transform: scale(0.95); | |||||
| } | |||||
| button:focus:active { | |||||
| outline: none; | |||||
| } | |||||
| button:disabled { | |||||
| .top-controls button:disabled { | |||||
| background-color: #777; | background-color: #777; | ||||
| opacity: 0.3; | opacity: 0.3; | ||||
| } | } | ||||
| .mode-select-container span:not(.disabled):hover { | .mode-select-container span:not(.disabled):hover { | ||||
| border-bottom: 1px solid currentColor; | border-bottom: 1px solid currentColor; | ||||
| } | } | ||||
| form button:not(:disabled):hover { | |||||
| .top-controls button:not(:disabled):hover { | |||||
| text-shadow: | text-shadow: | ||||
| 0 0 1px #fff, | 0 0 1px #fff, | ||||
| 0 0 3px #fff, | 0 0 3px #fff, | ||||
| 0 0 5px #fff; | 0 0 5px #fff; | ||||
| cursor: pointer; | cursor: pointer; | ||||
| } | } | ||||
| p.note { | |||||
| font-size: 0.875em; | |||||
| } | |||||
| /******************/ | |||||
| /* WHITELIST MODE */ | |||||
| /******************/ | |||||
| #matchingPatterns.disabled h2 { | #matchingPatterns.disabled h2 { | ||||
| color: rgba(0,0,0,0.15); | color: rgba(0,0,0,0.15); | ||||
| } | } | ||||
| font-family: Inconsolata, Courier, monospace; | font-family: Inconsolata, Courier, monospace; | ||||
| font-size: 4em; | font-size: 4em; | ||||
| } | } | ||||
| p.note { | |||||
| font-size: 0.875em; | |||||
| } | |||||
| /***************/ | |||||
| /* BOTTOM INFO */ | |||||
| /***************/ | |||||
| .bottom-info { | .bottom-info { | ||||
| font-size: 0.875rem; | font-size: 0.875rem; | ||||
| color: #aaa; | color: #aaa; | ||||
| } | } | ||||
| .bottom-info p { | .bottom-info p { | ||||
| margin: 0; | 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; | |||||
| } |
| <!DOCTYPE html> | <!DOCTYPE html> | ||||
| <html> | <html> | ||||
| <head> | <head> | ||||
| <title>Always Kill Sticky - Options</title> | |||||
| <title>AlwaysKillSticky - Options</title> | |||||
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
| <meta http-equiv='Content-Type' content='text/html; charset=utf-8' /> | <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='options.css' /> | ||||
| <link rel='stylesheet' type='text/css' href='fonts.css' /> | |||||
| <link rel="shortcut icon" type="image/png" href="images/ASK_on_32.png" /> | <link rel="shortcut icon" type="image/png" href="images/ASK_on_32.png" /> | ||||
| </head> | </head> | ||||
| <body> | <body> | ||||
| <h1>Always Kill Sticky</h1> | |||||
| <form> | |||||
| <h1>AlwaysKillSticky</h1> | |||||
| <form class='top-controls'> | |||||
| <span class='mode-select-container'> | <span class='mode-select-container'> | ||||
| <span class='blacklist-mode-label'>Blacklist mode</span> | <span class='blacklist-mode-label'>Blacklist mode</span> | ||||
| <input type='checkbox' id='whitelist-mode'></input> | <input type='checkbox' id='whitelist-mode'></input> | ||||
| <p class='bottom-info'>AlwaysKillSticky v<span class='version'>0.0</span></p> | <p class='bottom-info'>AlwaysKillSticky v<span class='version'>0.0</span></p> | ||||
| <p class='bottom-info'>© Said Achmiz 2019–present</p> | <p class='bottom-info'>© Said Achmiz 2019–present</p> | ||||
| </div> | </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> | </body> | ||||
| <script src="functions.js"></script> | <script src="functions.js"></script> | ||||
| <script src="options.js"></script> | <script src="options.js"></script> |
| /**********/ | /**********/ | ||||
| /* Triggered when a click is received by the mode selector switch, or by | /* 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() { | function modeSelectorInputReceived() { | ||||
| toggleModeSelectorState(); | 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); | setButtonsActive(true); | ||||
| } | } | ||||
| container.classList.toggle("whitelist", (newMode == "whitelist")); | container.classList.toggle("whitelist", (newMode == "whitelist")); | ||||
| checkbox.checked = (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. */ | just the label associated with the now-enabled mode. */ | ||||
| container.querySelectorAll("span").forEach(span => { | container.querySelectorAll("span").forEach(span => { | ||||
| span.classList.toggle("disabled", false); | span.classList.toggle("disabled", false); | ||||
| /* In whitelist mode, the matching patterns textarea must be disabled, | /* In whitelist mode, the matching patterns textarea must be disabled, | ||||
| since the matching patterns list is treated as containing only the | 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").classList.toggle("disabled", (newMode == "whitelist")); | ||||
| document.querySelector("div#matchingPatterns textarea").disabled = (newMode == "whitelist"); | document.querySelector("div#matchingPatterns textarea").disabled = (newMode == "whitelist"); | ||||
| } | } | ||||
| /* Saves the entered state in storage. | /* Saves the entered state in storage. | ||||
| De-activate the “Reset” and “Save” buttons. | |||||
| De-activate the "Reset" and "Save" buttons. | |||||
| */ | */ | ||||
| function saveChanges() { | function saveChanges() { | ||||
| let matchingPatterns = document.querySelector("#matchingPatterns textarea").value; | let matchingPatterns = document.querySelector("#matchingPatterns textarea").value; | ||||
| } | } | ||||
| /* Retrieves the saved state from storage and reset the UI to reflect it, | /* 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) { | function resetChanges(callback) { | ||||
| chrome.storage.sync.get([ "matchingPatterns", "exclusionPatterns", "mode" ], (result) => { | chrome.storage.sync.get([ "matchingPatterns", "exclusionPatterns", "mode" ], (result) => { | ||||
| toggleModeSelectorState(AKS.mode); | toggleModeSelectorState(AKS.mode); | ||||
| // De-activate the “Reset” and “Save” buttons. | |||||
| // De-activate the "Reset" and "Save" buttons. | |||||
| setButtonsActive(false); | setButtonsActive(false); | ||||
| // Expand all textareas. | // Expand all textareas. | ||||
| }); | }); | ||||
| } | } | ||||
| /* 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 | changes are made (to activate) or when changes are saved or reset (to | ||||
| de-activate). | de-activate). | ||||
| */ | */ | ||||
| function setButtonsActive(active) { | function setButtonsActive(active) { | ||||
| document.querySelectorAll("button").forEach(button => { | |||||
| document.querySelectorAll(".top-controls button").forEach(button => { | |||||
| button.disabled = !active; | 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 | /* Expands a textarea to show all its contents (up to a maximum height which is | ||||
| set via CSS). | set via CSS). | ||||
| */ | */ | ||||
| }); | }); | ||||
| } | } | ||||
| function setHelpOverlayVisible(visible) { | |||||
| location.hash = visible ? "help" : ""; | |||||
| setBodyScrollState(); | |||||
| } | |||||
| /******************/ | /******************/ | ||||
| /* INITIALIZATION */ | /* INITIALIZATION */ | ||||
| /******************/ | /******************/ | ||||
| modeSelectorInputReceived(); | 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(); | initialize(); |
| happens until the UI state is updated, killSticky() is called (if needed), | happens until the UI state is updated, killSticky() is called (if needed), | ||||
| and the new settings are saved in storage.) | 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: | [1] In blacklist mode, toggleState() does one of the following: | ||||
| // Listener for Options button. | // Listener for Options button. | ||||
| document.querySelector("button.options-button").addActivateEvent(() => { | document.querySelector("button.options-button").addActivateEvent(() => { | ||||
| chrome.runtime.openOptionsPage(null); | |||||
| window.open(chrome.runtime.getURL('options.html')) | |||||
| }); | }); | ||||
| // Listener for the Help button. | // Listener for the Help button. | ||||
| document.querySelector("button.help-button").addActivateEvent(() => { | 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). | // Listener for reload button (appears when sticky-killing is toggled OFF). |