├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── bug_report_site_issue.md │ └── feature_request.md ├── .gitignore ├── .gitmodules ├── CONTRIBUTING.md ├── CONTRIBUTORS.md ├── LICENSE.md ├── README.md ├── assets ├── amotags.txt ├── icon-base.svg ├── icon-dark-org.svg ├── icon-final.png ├── icon-final.svg ├── icon-light-org.svg ├── screencasts │ └── .gitkeep ├── screenshots │ ├── disabled-light-mode.png │ ├── enabled-dark-style.png │ └── enabled-light-style.png └── texts │ ├── de │ ├── Changelog │ │ ├── 1.0.html │ │ ├── 1.0.md │ │ ├── 1.1.html │ │ ├── 1.1.md │ │ ├── 1.2.html │ │ ├── 1.2.md │ │ ├── 1.3.html │ │ ├── 1.3.md │ │ ├── 2.0.html │ │ └── 2.0.md │ ├── amoDescription.html │ ├── amoScreenshots.csv │ ├── amoSummary.txt │ ├── permissions.md │ └── privacy.txt │ └── en │ ├── Changelog │ ├── 1.0.html │ ├── 1.0.md │ ├── 1.1.html │ ├── 1.1.md │ ├── 1.2.html │ ├── 1.2.md │ ├── 1.3.html │ ├── 1.3.md │ ├── 2.0.html │ ├── 2.0.md │ └── next.md │ ├── amoDescription.html │ ├── amoScreenshots.csv │ ├── amoSummary.txt │ ├── permissions.md │ └── privacy.txt ├── jsconfig.json ├── package-lock.json ├── package.json ├── scripts ├── make.sh └── manifests │ ├── dev.json │ └── firefox.json ├── src ├── _locales │ ├── da │ │ └── messages.json │ ├── de │ │ └── messages.json │ └── en │ │ └── messages.json ├── background │ ├── background.html │ ├── background.js │ └── modules │ │ ├── ActionButton.js │ │ └── InstallUpgrade.js ├── common │ ├── common.css │ ├── common.js │ ├── img │ │ ├── check.svg │ │ ├── close-white.svg │ │ ├── close.svg │ │ ├── error-white.svg │ │ ├── info-dark.svg │ │ ├── info-light.svg │ │ ├── open-in-new.svg │ │ └── warning-dark.svg │ └── modules │ │ ├── BrowserSettings │ │ └── BrowserSettings.js │ │ ├── DarkModeLogic.js │ │ ├── data │ │ ├── BrowserCommunicationTypes.js │ │ ├── DefaultSettings.js │ │ └── MessageLevel.js │ │ └── lodash │ │ ├── .internal │ │ ├── baseGetTag.js │ │ ├── freeGlobal.js │ │ ├── getTag.js │ │ └── root.js │ │ ├── debounce.js │ │ ├── isFunction.js │ │ ├── isObject.js │ │ ├── isObjectLike.js │ │ ├── isPlainObject.js │ │ ├── isString.js │ │ └── throttle.js ├── icons │ ├── icon-dark.svg │ └── icon-light.svg ├── manifest.json ├── options │ ├── fastLoad.js │ ├── modules │ │ └── CustomOptionTriggers.js │ ├── options.css │ ├── options.html │ └── options.js └── tests │ ├── .eslintrc │ ├── index.css │ ├── index.html │ ├── module.test.js │ ├── run.js │ └── setup.js └── tests /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | # Unix style files 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.sh] 13 | indent_style = space 14 | indent_size = 4 15 | 16 | [*.json] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.html] 21 | indent_style = tab 22 | 23 | [*.css] 24 | indent_style = space 25 | indent_size = 2 26 | 27 | [*.js] 28 | indent_style = space 29 | indent_size = 4 30 | 31 | [*.eslintrc] 32 | indent_style = space 33 | indent_size = 4 34 | 35 | [*.{md,markdown}] 36 | indent_style = space 37 | indent_size = 2 38 | trim_trailing_whitespace = false 39 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | src/common/lib/* 2 | src/common/modules/lib/* 3 | src/common/modules/lodash/* 4 | src/popup/lib/* 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parserOptions": { 4 | "ecmaVersion": 2017, 5 | "sourceType": "module", 6 | "ecmaFeatures": { 7 | "impliedStrict": true 8 | } 9 | }, 10 | "env": { 11 | "browser": true, 12 | "webextensions": true, 13 | "es6": true 14 | }, 15 | "extends": "eslint:recommended", 16 | "rules": { 17 | // basic rules 18 | "semi": 1, 19 | "semi-style": 2, 20 | "semi-spacing": 1, 21 | "camelcase": 2, 22 | "quotes": ["warn", "double", { 23 | "avoidEscape": true, 24 | "allowTemplateLiterals": false 25 | }], 26 | "brace-style": 2, 27 | 28 | // just to make sure (are defaults) 29 | "indent": ["error", 4], 30 | 31 | // allow console output for errors in browsers 32 | "no-console": 0, 33 | 34 | // technically required, because of CSP 35 | "no-eval": 2, 36 | "no-implied-eval": 2, 37 | 38 | // great new EcmaScript features 39 | "prefer-const": ["error", { 40 | "destructuring": "all" 41 | }], 42 | "no-var": 1, 43 | "prefer-arrow-callback": 1, 44 | "implicit-arrow-linebreak": 1, 45 | "arrow-parens": 1, 46 | "arrow-spacing": 1, 47 | "no-confusing-arrow": 1, 48 | "prefer-rest-params": 2, 49 | "prefer-spread": 2, 50 | "prefer-template": 1, 51 | "template-curly-spacing": 1, 52 | "symbol-description": 2, 53 | "object-shorthand": ["warn", "consistent-as-needed"], 54 | "prefer-promise-reject-errors": 2, 55 | /* "prefer-destructuring": 1, */ // https://github.com/eslint/eslint/issues/10250 56 | "prefer-numeric-literals": 1, 57 | 58 | // additional rules 59 | "no-new-object": 2, 60 | "eqeqeq": ["error", "smart"], 61 | "curly": ["error", "all"], 62 | "dot-location": ["error", "property"], 63 | "dot-notation": 2, 64 | "no-array-constructor": 2, 65 | "no-throw-literal": 2, 66 | "no-self-compare": 2, 67 | "no-useless-call": 1, 68 | /* "no-use-before-define": 1, */ 69 | "consistent-return": 2, 70 | "spaced-comment": 1, 71 | "no-multi-spaces": 1, 72 | "no-new-wrappers": 2, 73 | "no-script-url": 2, 74 | "no-void": 1, 75 | "vars-on-top": 1, 76 | "yoda": ["error", "never"], 77 | /* "no-warning-comments": 1, */ // should be enabled later 78 | "require-await": 1, 79 | "require-jsdoc": ["error", { 80 | "require": { 81 | "FunctionDeclaration": true, 82 | "MethodDefinition": false, 83 | "ClassDeclaration": false, 84 | "ArrowFunctionExpression": false 85 | } 86 | }], 87 | "valid-jsdoc": ["error", { 88 | "prefer": { 89 | "return": "returns" 90 | }, 91 | "preferType": { 92 | "Boolean": "boolean", 93 | "Number": "number", 94 | "object": "Object", 95 | "String": "string", 96 | "HtmlElement": "HTMLElement" 97 | }, 98 | "requireReturnType": true, 99 | "matchDescription": ".+", 100 | "requireParamDescription": false, 101 | "requireReturnDescription": false 102 | }], 103 | "wrap-iife": ["error", "inside"], 104 | "no-loop-func": 2, 105 | "no-unused-expressions": 2, 106 | // "linebreak-style": 1, // cannot be used when contributing on Windows 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Bug description 11 | 12 | 13 | ## Steps to reproduce 14 | 15 | 16 | 1. 17 | 2. 18 | 3. 19 | 20 | ### Actual behavior 21 | 22 | 23 | ### Expected behavior 24 | 25 | 26 | ## System 27 | 28 | 29 | Operating system and version: 30 | Browser and version: Firefox 31 | Add-on version: 32 | 33 | ## Possible solution 34 | 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report_site_issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Problem with a specific website 3 | about: Create a bug report for a problem on a specific website 4 | title: '' 5 | labels: bug, site-issue 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Affected website:** 11 | 12 | ## Bug description 13 | 14 | 15 | ## Steps to reproduce 16 | 17 | 18 | 1. Go to https://example.com/replace-with-url 19 | 2. 20 | 3. 21 | 22 | ### Screencast/Screenshots 23 | 24 | 25 | ### Actual behavior 26 | 27 | 28 | ### Expected behavior 29 | 30 | 31 | ## System 32 | 33 | 34 | Operating system and version: 35 | Browser and version: Firefox 36 | Add-on version: 37 | 38 | ## Possible solution 39 | 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Background 11 | 12 | 13 | 14 | ## Proposed solution 15 | 16 | 17 | 18 | ## Alternatives 19 | 20 | 21 | 22 | ## Additional context 23 | 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | node_modules/ 3 | .vscode 4 | 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/common/modules/Localizer"] 2 | path = src/common/modules/Localizer 3 | url = https://github.com/TinyWebEx/Localizer/ 4 | [submodule "src/tests/helper"] 5 | path = src/tests/helper 6 | url = https://github.com/TinyWebEx/TestHelper 7 | [submodule "src/common/modules/AddonSettings"] 8 | path = src/common/modules/AddonSettings 9 | url = https://github.com/TinyWebEx/AddonSettings 10 | [submodule "src/common/modules/Logger"] 11 | path = src/common/modules/Logger 12 | url = https://github.com/TinyWebEx/Logger 13 | [submodule "src/common/modules/MessageHandler"] 14 | path = src/common/modules/MessageHandler 15 | url = https://github.com/TinyWebEx/MessageHandler 16 | [submodule "src/common/modules/BrowserCommunication"] 17 | path = src/common/modules/BrowserCommunication 18 | url = https://github.com/TinyWebEx/BrowserCommunication 19 | [submodule "src/common/modules/AutomaticSettings"] 20 | path = src/common/modules/AutomaticSettings 21 | url = https://github.com/TinyWebEx/AutomaticSettings 22 | [submodule "src/common/modules/CommonCss"] 23 | path = src/common/modules/CommonCss 24 | url = https://github.com/TinyWebEx/CommonCss 25 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Nice to see you want to contribute to this project! :+1: :tada: 2 | Please have a look at this guide to know what you are changing. 3 | 4 | As I do not want to duplicate the instructions all over, please **find the common contributors docs here**: https://github.com/TinyWebEx/common/blob/master/CONTRIBUTING.md 5 | 6 | Some links and potential special rules for this repo only are listed below. 7 | 8 | ## Support us! 9 | 10 | You like this add-on, but have no idea how to support us? 11 | 12 | Here are some easy things you can always do: 13 | 14 | * Spread the word and recommend it to others! 🤗😍 15 | * Leave a rating [at addons.mozilla.org](https://addons.mozilla.org/firefox/addon/dark-mode-website-switcher/reviews/) if you like it! 16 | Also consider writing some text and not only leaving stars there. It's always nice to hear some warm words. ☺️ 17 | * Star this project [on GitHub](https://github.com/rugk/website-dark-mode-switcher) by clicking the "star" icon! 18 | 19 | ## Translations 20 | 21 | It would be great if you can contribute your translations! You can either translate the JSON files directly or use [this online translator service](https://lusito.github.io/web-ext-translator/?gh=https://github.com/rugk/website-dark-mode-switcher). 22 | 23 | **Manually:** To translate it manually, go to [`src/_locales/en`](src/_locales/en) and copy the English (or any other existing language) `messages.json` file. (You could also use another source language if you want, but usually English is the best.) Create a new dir at `src/_locales` with the abbreviation of the language you want to translate. 24 | **Web-ext-translator:** Go to [this page](https://lusito.github.io/web-ext-translator/) and translate it online. Download the result by clicking on "Export to ZIP" at the bottom. 25 | 26 | At the end, just submit a Pull Request with your changed files. 27 | Of course, you can (and should) improve existing translations. 28 | 29 | For more details, [see the official docs](https://developer.mozilla.org/Add-ons/WebExtensions/Internationalization#Providing_localized_strings_in__locales). 30 | 31 | ### Other items to translate 32 | 33 | * Text assets to translate: [`assets/texts`](assets/texts) 34 | * Screenshots: [`assets/screenshots`](assets/screenshots) 35 | * Wiki to translate: [wiki](../../wiki) 36 | * Sidebar file for adding language: [`_Sidebar` file](../../wiki/_Sidebar/_edit) 37 | 38 | For more information, see the whole [contributing doc](https://github.com/TinyWebEx/common/blob/master/CONTRIBUTING.md#translations). 39 | 40 | ## Coding 41 | 42 | See the **common guide** on how to [start coding](https://github.com/TinyWebEx/common/blob/master/CONTRIBUTING.md#coding) and what rules to follow. 43 | 44 | **Attention:** For this add-on, you need to execute [`scripts/downloadEmojiImages.sh`](scripts/downloadEmojiImages.sh) to download the bundled emoji sheets if you use anything else than the "native emojis" ("emojis from your OS") in the settings of this add-on. The reason is just, that these big files are not bundled/distributed in this repo. 45 | 46 | ### Tests 47 | 48 | * Test dir: [`src/tests/`](src/tests/) 49 | * EsLint config for tests: [`src/tests/.eslintrc`](src/tests/.eslintrc) 50 | 51 | ## Need ideas? 52 | 53 | Don't have any idea what to take up? [Here you can find a list of good issues for starters](../../contribute), e.g. if you want to start with this project or a (programming) language in general. 54 | However, of course, feel free to take on any issue (that is not claimed or assigned to someone else). 55 | 56 | Also, there are other add-on's, which are very similar and may also need work: 57 | 58 | * [Awesome Emoji Picker](https://github.com/rugk/how-did-i-get-here/contribute) 59 | * [Mastodon Simplified Federation](https://github.com/rugk/mastodon-simplified-federation/contribute) 60 | * [Offline QR Code Generator](https://github.com/rugk/offline-qr-code/contribute) 61 | * [How did I get here?](https://github.com/rugk/how-did-i-get-here/contribute) 62 | 63 | There is also [an overview over all good first issues in other add-on repos](https://github.com/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue+archived%3Afalse+user%3Arugk+user%3ATinyWebEx+label%3A%22good+first+issue%22). Also [check out the libraries used by this project](https://github.com/TinyWebEx). 64 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # Contributors 2 | 3 | * @tartpvule 4 | * @ENT8R 5 | * Kyle Copperfield (@kmcopper) 6 | 7 | ## Translators 8 | 9 | ### Danish 10 | 11 | - [@Grooty12](https://github.com/Grooty12) 12 | 13 | ### German 14 | 15 | - [@rugk](https://github.com/rugk) 16 | 17 | ### Turkish 18 | 19 | - Ömür Turan ([@omurturan](https://github.com/omurturan)) 20 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ## Dark Mode Website Switcher license 2 | 3 | Copyright (c) 2019 rugk and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | 11 | ## [lodash](https://github.com/lodash/lodash) 12 | 13 | This project includes some source code from lodash. 14 | 15 | The MIT License 16 | 17 | Copyright JS Foundation and other contributors 18 | 19 | Based on Underscore.js, copyright Jeremy Ashkenas, 20 | DocumentCloud and Investigative Reporters & Editors 21 | 22 | This software consists of voluntary contributions made by many 23 | individuals. For exact contribution history, see the revision history 24 | available at https://github.com/lodash/lodash 25 | 26 | The following license applies to all parts of this software except as 27 | documented below: 28 | 29 | ==== 30 | 31 | Permission is hereby granted, free of charge, to any person obtaining 32 | a copy of this software and associated documentation files (the 33 | "Software"), to deal in the Software without restriction, including 34 | without limitation the rights to use, copy, modify, merge, publish, 35 | distribute, sublicense, and/or sell copies of the Software, and to 36 | permit persons to whom the Software is furnished to do so, subject to 37 | the following conditions: 38 | 39 | The above copyright notice and this permission notice shall be 40 | included in all copies or substantial portions of the Software. 41 | 42 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 43 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 44 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 45 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 46 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 47 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 48 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 49 | 50 | ==== 51 | 52 | Copyright and related rights for sample code are waived via CC0. Sample 53 | code is defined as all source code displayed within the prose of the 54 | documentation. 55 | 56 | CC0: http://creativecommons.org/publicdomain/zero/1.0/ 57 | 58 | ==== 59 | 60 | Files located in the node_modules and vendor directories are externally 61 | maintained libraries used by this software which have their own 62 | licenses; we recommend you read them, as their terms may differ from the 63 | terms above. 64 | 65 | ## [Mozilla Photon icons](https://design.firefox.com/icons/viewer/) 66 | 67 | * [`src/icons/icon-dark.svg`](src/icons/icon-dark.svg) is based on the [window icon](https://design.firefox.com/icons/viewer/#win) by Mozilla, license: [MPL v2.0](https://www.mozilla.org/en-US/MPL/2.0/) 68 | 69 | The MPLv2.0 license is reproduced below: 70 | 71 | Mozilla Public License, version 2.0 1. Definitions 1.1. “Contributor” means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 72 | 73 | 1.2. “Contributor Version” means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor’s Contribution. 74 | 75 | 1.3. “Contribution” means Covered Software of a particular Contributor. 76 | 77 | 1.4. “Covered Software” means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 78 | 79 | 1.5. “Incompatible With Secondary Licenses” means 80 | 81 | that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or 82 | 83 | that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 84 | 85 | 1.6. “Executable Form” means any form of the work other than Source Code Form. 86 | 87 | 1.7. “Larger Work” means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 88 | 89 | 1.8. “License” means this document. 90 | 91 | 1.9. “Licensable” means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 92 | 93 | 1.10. “Modifications” means any of the following: 94 | 95 | any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or 96 | 97 | any new file in Source Code Form that contains any Covered Software. 98 | 99 | 1.11. “Patent Claims” of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 100 | 101 | 1.12. “Secondary License” means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 102 | 103 | 1.13. “Source Code Form” means the form of the work preferred for making modifications. 104 | 105 | 1.14. “You” (or “Your”) means an individual or a legal entity exercising rights under this License. For legal entities, “You” includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, “control” means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 106 | 107 | 2. License Grants and Conditions 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: 108 | 109 | under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and 110 | 111 | under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 112 | 113 | 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 114 | 115 | 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: 116 | 117 | for any code that a Contributor has removed from Covered Software; or 118 | 119 | for infringements caused by: (i) Your and any other third party’s modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or 120 | 121 | under Patent Claims infringed by Covered Software in the absence of its Contributions. 122 | 123 | This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 124 | 125 | 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 126 | 127 | 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 128 | 129 | 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 130 | 131 | 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 132 | 133 | 3. Responsibilities 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients’ rights in the Source Code Form. 134 | 135 | 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: 136 | 137 | such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and 138 | 139 | You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients’ rights in the Source Code Form under this License. 140 | 141 | 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 142 | 143 | 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 144 | 145 | 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 146 | 147 | 4. Inability to Comply Due to Statute or Regulation If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 148 | 149 | 5. Termination 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 150 | 151 | 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 152 | 153 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. 154 | 155 | 6. Disclaimer of Warranty Covered Software is provided under this License on an “as is” basis, without warranty of any kind, either expressed, implied, or statutory, including, without limitation, warranties that the Covered Software is free of defects, merchantable, fit for a particular purpose or non-infringing. The entire risk as to the quality and performance of the Covered Software is with You. Should any Covered Software prove defective in any respect, You (not any Contributor) assume the cost of any necessary servicing, repair, or correction. This disclaimer of warranty constitutes an essential part of this License. No use of any Covered Software is authorized under this License except under this disclaimer. 156 | 157 | 7. Limitation of Liability Under no circumstances and under no legal theory, whether tort (including negligence), contract, or otherwise, shall any Contributor, or anyone who distributes Covered Software as permitted above, be liable to You for any direct, indirect, special, incidental, or consequential damages of any character including, without limitation, damages for lost profits, loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses, even if such party shall have been informed of the possibility of such damages. This limitation of liability shall not apply to liability for death or personal injury resulting from such party’s negligence to the extent applicable law prohibits such limitation. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, so this exclusion and limitation may not apply to You. 158 | 159 | 8. Litigation Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party’s ability to bring cross-claims or counter-claims. 160 | 161 | 9. Miscellaneous This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 162 | 163 | 10. Versions of the License 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 164 | 165 | 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 166 | 167 | 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 168 | 169 | 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. 170 | 171 | Exhibit A - Source Code Form License Notice This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 172 | 173 | If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. 174 | 175 | You may add additional accurate notices of copyright ownership. 176 | 177 | Exhibit B - “Incompatible With Secondary Licenses” Notice This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0. 178 | 179 | ## Special thanks 180 | * to [SVGO](https://jakearchibald.github.io/svgomg/) for their SVG minimisation tool 181 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dark Mode Website Switcher 2 | 3 | [![Mozilla Add-on version](https://img.shields.io/amo/v/dark-mode-website-switcher.svg)](https://addons.mozilla.org/firefox/addon/dark-mode-website-switcher/?utm_source=github.com&utm_medium=git&utm_content=badge-version&campaign=github) 4 | [![Mozilla Add-on downloads](https://img.shields.io/amo/d/dark-mode-website-switcher.svg)](https://addons.mozilla.org/firefox/addon/dark-mode-website-switcher/?utm_source=github.com&utm_medium=git&utm_content=badge-downloads&campaign=github) 5 | [![Mozilla Add-on users](https://img.shields.io/amo/users/dark-mode-website-switcher.svg)](https://addons.mozilla.org/firefox/addon/dark-mode-website-switcher/?utm_source=github.com&utm_medium=git&utm_content=badge-users&campaign=github) 6 | [![Mozilla Add-on stars](https://img.shields.io/amo/stars/dark-mode-website-switcher.svg)](https://addons.mozilla.org/firefox/addon/dark-mode-website-switcher/reviews/?utm_source=github.com&utm_medium=git&utm_content=badge-stars&campaign=github) 7 | 8 | This is a (Firefox) add-on (WebExtension) that lets you invert the website's color scheme by inverting/changing the [`prefers-color-scheme`](https://developer.mozilla.org/docs/Web/CSS/@media/prefers-color-scheme) media feature of CSS without requiring you to change the whole system style setting. 9 | 10 | Test websites: 11 | * https://stuffandnonsense.co.uk/blog/redesigning-your-product-and-website-for-dark-mode 12 | * https://webkit.org/ 13 | * https://pinafore.social/ 14 | * https://s.codepen.io/aardrian/debug/NmoQdN 15 | * http://adrianroselli.com/ 16 | * https://emojipedia.org/ 17 | * https://bugzilla.mozilla.org/ 18 | 19 | This extension only works with modern Firefox v95 or higher. Versions [before v1.0](../../releases) do support older browser versions, but use a more error-prone way of darkening websites. 20 | 21 | ## Download 22 | 23 | **[![Get it for Firefox!](https://extensionworkshop.com/assets/img/documentation/publish/get-the-addon-178x60px.dad84b42.png)](https://addons.mozilla.org/firefox/addon/dark-mode-website-switcher/?utm_source=github.com&utm_medium=git&utm_content=download-button&campaign=github)** 24 | 25 | ## Contribute 26 | 27 | You can easily get involved in this FLOSS project and any help is certainly appreciated. Here are some ideas: 28 | 29 | * 📃 [Translate this add-on into multiple languages!](./CONTRIBUTING.md#translations) 30 | * 🐛 [Fix some easy issues and get started in add-on development](CONTRIBUTING.md#coding) (or just try out the development version) 31 | * 💡 [Or check out some other add-on issues](CONTRIBUTING.md#need-ideas) (or translate them). 32 | 33 | Or, in any case, [support us by spreading the word!](./CONTRIBUTING.md#support-us) ❤️ 34 | 35 | If you want to find out how this add-on currently works on a technical level, [have a look at this Stackoverflow answer](https://stackoverflow.com/a/55910185/5008962). I've explained it there in detail. 36 | -------------------------------------------------------------------------------- /assets/amotags.txt: -------------------------------------------------------------------------------- 1 | prefers-color-scheme, css, webdev, development, website, dark, light, mode, dark-theme, dark-style, dark-website, website-design 2 | -------------------------------------------------------------------------------- /assets/icon-base.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 21 | 23 | 24 | 26 | image/svg+xml 27 | 29 | 30 | 31 | 32 | 33 | 35 | 38 | 46 | 47 | 50 | 56 | 57 | 60 | 66 | 67 | 70 | 76 | 77 | 80 | 86 | 87 | 88 | 110 | 118 | 123 | 131 | 132 | 136 | 140 | 145 | 153 | 154 | 159 | 163 | 168 | 176 | 177 | 181 | 185 | 193 | 194 | 201 | 208 | 212 | 216 | 224 | 225 | 230 | 236 | 237 | -------------------------------------------------------------------------------- /assets/icon-dark-org.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | image/svg+xml 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /assets/icon-final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rugk/website-dark-mode-switcher/f07e3f233b23f72175d39f1a8c9feee625ca5f6f/assets/icon-final.png -------------------------------------------------------------------------------- /assets/icon-final.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 21 | 23 | 24 | 26 | image/svg+xml 27 | 29 | 30 | 31 | 32 | 33 | 35 | 38 | 46 | 47 | 50 | 56 | 57 | 60 | 66 | 67 | 70 | 76 | 77 | 80 | 86 | 87 | 88 | 110 | 118 | 123 | 131 | 132 | 136 | 140 | 145 | 153 | 154 | 159 | 163 | 168 | 176 | 177 | 182 | 186 | 194 | 195 | 202 | 209 | 213 | 217 | 225 | 226 | 231 | 237 | 238 | -------------------------------------------------------------------------------- /assets/icon-light-org.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | image/svg+xml 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /assets/screencasts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rugk/website-dark-mode-switcher/f07e3f233b23f72175d39f1a8c9feee625ca5f6f/assets/screencasts/.gitkeep -------------------------------------------------------------------------------- /assets/screenshots/disabled-light-mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rugk/website-dark-mode-switcher/f07e3f233b23f72175d39f1a8c9feee625ca5f6f/assets/screenshots/disabled-light-mode.png -------------------------------------------------------------------------------- /assets/screenshots/enabled-dark-style.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rugk/website-dark-mode-switcher/f07e3f233b23f72175d39f1a8c9feee625ca5f6f/assets/screenshots/enabled-dark-style.png -------------------------------------------------------------------------------- /assets/screenshots/enabled-light-style.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rugk/website-dark-mode-switcher/f07e3f233b23f72175d39f1a8c9feee625ca5f6f/assets/screenshots/enabled-light-style.png -------------------------------------------------------------------------------- /assets/texts/de/Changelog/1.0.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | Mehr Informationen auf GitHub. 7 | -------------------------------------------------------------------------------- /assets/texts/de/Changelog/1.0.md: -------------------------------------------------------------------------------- 1 | * **Neu:** Kann Webseiten das dunkle Design erzwingen. 2 | * **Verbessert:** Der dunkle Modus kann mit einem Klick auf den Toolbar-Button angepasst werden. 3 | -------------------------------------------------------------------------------- /assets/texts/de/Changelog/1.1.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | Mehr Informationen auf GitHub. 7 | -------------------------------------------------------------------------------- /assets/texts/de/Changelog/1.1.md: -------------------------------------------------------------------------------- 1 | * **Improved:** Local files and other URLs are now also supported ([#8](https://github.com/rugk/website-dark-mode-switcher/issues/8)) 2 | * **Improved:** Whitespace characters in media queries are ignored ([#7](https://github.com/rugk/website-dark-mode-switcher/issues/7)) 3 | -------------------------------------------------------------------------------- /assets/texts/de/Changelog/1.2.html: -------------------------------------------------------------------------------- 1 | 9 | 10 | Mehr Informationen auf GitHub. 11 | -------------------------------------------------------------------------------- /assets/texts/de/Changelog/1.2.md: -------------------------------------------------------------------------------- 1 | * **Neu:** Du kannst den dunklen Modus nun (standardmäßig) mit der Tastenkombination Ctrl+Shift+B wechseln ([#10](https://github.com/rugk/website-dark-mode-switcher/issues/10)) 2 | * **Verbessert:** Verringere Anzahl der Log-Messages, die in die Konsole geschrieben werden. ([#11](https://github.com/rugk/website-dark-mode-switcher/issues/11)) 3 | * **Behoben:** Das gefälschte Media-Query-JavaScript-Resultat ist nun realistischer. ([#13](https://github.com/rugk/website-dark-mode-switcher/issues/13)) 4 | * **Behoben:** `@import` wird nun bei CSS Stylesheets unterstützt. ([#28](https://github.com/rugk/website-dark-mode-switcher/issues/28)) 5 | * **Behoben:** Eine „race condition“ verhinderte das Aktivieren des dunklen Modus auf einigen Seite und wurde nun behoben. ([#9](https://github.com/rugk/website-dark-mode-switcher/issues/9)) 6 | * **Intern:** Die Abhängigkeiten wurden aktualisiert, um die Sicherheit und die Verlässlichkeit des Add-on's zu gewährleisten. 7 | -------------------------------------------------------------------------------- /assets/texts/de/Changelog/1.3.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | Mehr Informationen auf GitHub. 7 | -------------------------------------------------------------------------------- /assets/texts/de/Changelog/1.3.md: -------------------------------------------------------------------------------- 1 | * **Verbessert:** Verbesserte Kompatibilität mit GitHub, Twitter und weiteren Seiten durch ein Umschreiben der Funktion der JavaScript-Fälschung. ([#30](https://github.com/rugk/website-dark-mode-switcher/issues/30), [#35](https://github.com/rugk/website-dark-mode-switcher/pull/35), danke an [@tartpvule](https://github.com/tartpvule)) 2 | * **Verbessert:** Verbesserter Initialisierungsprozess der Inhaltsskripte. ([#36](https://github.com/rugk/website-dark-mode-switcher/pull/36), danke an [@tartpvule](https://github.com/tartpvule)) 3 | -------------------------------------------------------------------------------- /assets/texts/de/Changelog/2.0.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | Mehr Informationen auf GitHub. 7 | -------------------------------------------------------------------------------- /assets/texts/de/Changelog/2.0.md: -------------------------------------------------------------------------------- 1 | * **Verbessert:** Durch den Wechsel auf eine komplett neue API von Firefox verbessert sich die Webseiten-Kompatibilität drastisch und es wurden dadurch viele Probleme behoben. ([#5](https://github.com/rugk/website-dark-mode-switcher/issues/5)) 2 | * **Neu:** Du kannst nun in den Einstellungen auswählen, zwischen welchen Stilen der Toolbar-Knopf wechseln soll. 3 | -------------------------------------------------------------------------------- /assets/texts/de/amoDescription.html: -------------------------------------------------------------------------------- 1 | Dieses Add-on ändert das Webseiten-Farbschema in dem es das prefers-color-scheme Media-Query-Feature von CSS invertiert, sodass du nicht die ganze Systemeinstellung anpassen musst. 2 | 3 | Du kannst den dunklen Modus auch mit der Tastenkombination Ctrl+Shift+B umschalten (dies ist in den Firefox-Add-on-Einstellungen anpassbar). 4 | 5 | Diese Erweiterung funktioniert nur mit Firefox v95 oder höher. 6 | 7 | 8 | ✍️ Benutzung ✍️ 9 | 10 | Du kannst einen Toolbar-Button nutzen, um das Erzwingen eines dunklen Modus aktivieren oder zu deaktivieren. Es zeigt ein gelbes Mondsymbol, wenn es aktiviert ist und Webseiten auf ein dunkles Design erzwingt. 11 | 12 | 13 | ⛱️ Test-Webseiten ⛱️ 14 | Hier einige bekannte Webseiten, die den dunklen System-Stil benutzen, sodass du diese mit diesem Add-on testen kannst: 15 | 22 | 23 | 24 | 📝 Entwicklung 📝 25 | Dieses Add-on ist freie Open-Source-Software und wird auf GitHub entwickelt. Fork' es auf GitHub and trage zu dem Projekt bei. 26 | Es gibt einige leichte Issues zum Starten. 27 | 28 | 29 | 💬 Berechtigungen 💬 30 | Dieses Add-on verlangt so wenig Berechtigungen, wie möglich. 31 | Eine Erklärung aller Berechtigungen, die dieses Add-on erfragt, kann auf dieser Seite gefunden werden. 32 | -------------------------------------------------------------------------------- /assets/texts/de/amoScreenshots.csv: -------------------------------------------------------------------------------- 1 | disabled-light-mode.png; "So sieht deine Webseite aus, wenn das Add-on deaktiviert ist."; 2 | enabled-dark-style.png; "Ist das Add-on aktiviert, so erzwingt es einen dunklen Webseiten-Stil."; 3 | enabled-light-style.png; "Ist das Add-on aktiviert, so erzwingt es einen dunklen Webseiten-Stil, unabhängig von der System-Einstellung."; 4 | -------------------------------------------------------------------------------- /assets/texts/de/amoSummary.txt: -------------------------------------------------------------------------------- 1 | Passt das Farbdesign aller Webseiten an, sodass diese standardmäßig alle dunkel sind, wenn es die Seite ein spezielles Design dafür hat. Es lässt Webseiten dunkel erscheinen, auch wenn ein helles Systemdesign genutzt wird. 2 | -------------------------------------------------------------------------------- /assets/texts/de/permissions.md: -------------------------------------------------------------------------------- 1 | # Erfragte Berechtigungen 2 | 3 | Für eine allgemeine Erklärung von Add-on-Berechtigungen siehe [diesen Support-Artikel]https://support.mozilla.org/de/kb/berechtigungsdialoge-der-firefox-erweiterungen). 4 | 5 | ## Berechtigungen bei Installation 6 | 7 | | Internal Id | Permission | Explanation | 8 | |:------------|:-------------------------------------------|:----------------------------------------------------------------------------| 9 | | `tabs` | Auf Browsertabs zugreifen | Benötigt, um das (dunkle) Design in die existierenden Webseiten einzufügen. | 10 | | `` | Auf Ihre Daten für alle Websites zugreifen | Benötigt, um das (dunkle) Design in die Webseiten einzufügen. | 11 | 12 | ## Versteckte Berechtigungen 13 | 14 | Zusätzlich verlangt dieses Add-on folgende Berechtigungen, welche in Firefox aber nicht abgefragt werden, da sie keine tiefgreifenden Berechtigungen sind. 15 | 16 | | Interne ID | Berechtigung | Erklärung | 17 | |:-------------|:---------------------------------------|:--------------------------------------------------------------| 18 | | `storage` | Zugriff auf lokalen Speicher | Benötigt um Einstellungen abzuspeichern | 19 | -------------------------------------------------------------------------------- /assets/texts/de/privacy.txt: -------------------------------------------------------------------------------- 1 | Dieses Add-on sendet keine Informationen zum Add-on-Autor oder einer Drittpartei. 2 | 3 | Eine Erklärung aller Berechtigungen, die dieses Add-on erfragt, kann auf https://github.com/rugk/website-dark-mode-switcher/blob/master/assets/texts/de/permissions.md gefunden werden. 4 | 5 | == DIENSTE VON DRITTEN == 6 | 7 | Dieses ADD-ON nutzt den „Sync storage” des Browsers, um die Einstellungen zu speichern. Wenn der NUTZER „Sync” im Browser aktiviert, werden die Einstellungen des Add-ons hochgeladen und zwischen den Geräten, die mit dem (Mozilla) Account des Browsers verbunden sind, synchronisiert. Wenn dies nicht gemacht wird, werden die Daten lokal auf dem Gerät gespeichert. 8 | In Mozilla Firefox werden die Daten Ende-zu-Ende-verschlüsselt, bevor sie hochgeladen und auf den Servern von Mozilla gespeichert werden. 9 | Siehe https://accounts.firefox.com/legal/privacy und https://www.mozilla.org/privacy/firefox/#c-privacy-topic-8 für Mozilla's Datenschutzerklärung über dieses Thema. 10 | 11 | -------------------------------------------------------------------------------- /assets/texts/en/Changelog/1.0.html: -------------------------------------------------------------------------------- 1 |
    2 |
  • New: Can force a website to be dark.
  • 3 |
  • Improved: You can trigger the dark mode with a click on the toolbar button.
  • 4 |
5 | 6 | More information on GitHub. 7 | -------------------------------------------------------------------------------- /assets/texts/en/Changelog/1.0.md: -------------------------------------------------------------------------------- 1 | * **New:** Can force a website to be dark. 2 | * **Improved:** You can trigger the dark mode with a click on the toolbar button. 3 | -------------------------------------------------------------------------------- /assets/texts/en/Changelog/1.1.html: -------------------------------------------------------------------------------- 1 |
    2 |
  • Improved: Local files and other URLs are now also supported (#8)
  • 3 |
  • Improved: Whitespace characters in media queries are ignored (#7)
  • 4 |
5 | 6 | More information on GitHub. 7 | -------------------------------------------------------------------------------- /assets/texts/en/Changelog/1.1.md: -------------------------------------------------------------------------------- 1 | * **Improved:** Local files and other URLs are now also supported ([#8](https://github.com/rugk/website-dark-mode-switcher/issues/8)) 2 | * **Improved:** Whitespace characters in media queries are ignored ([#7](https://github.com/rugk/website-dark-mode-switcher/issues/7)) 3 | -------------------------------------------------------------------------------- /assets/texts/en/Changelog/1.2.html: -------------------------------------------------------------------------------- 1 |
    2 |
  • New: You can now toggle the dark mode with the hot key Ctrl+Shift+B by default (#10)
  • 3 |
  • Improved: Reduced amount of log messages written to console (#11)
  • 4 |
  • Improved: The settings page was expanded and is now much more useful than just being
  • 5 |
  • Fixed: The JavaScript faking of the media query result is more realistic now. (#13)
  • 6 |
  • Fixed: @import is now supported for CSS stylesheets. (#28)
  • 7 |
  • Fixed: Fixed a race condition leading some sites to not enable the dark mode. (#9)
  • 8 |
  • Internal: Dependencies were updated to ensure the security and reliability of this add-on.
  • 9 |
10 | 11 | More information on GitHub. 12 | -------------------------------------------------------------------------------- /assets/texts/en/Changelog/1.2.md: -------------------------------------------------------------------------------- 1 | * **New:** You can now toggle the dark mode with the hot key Ctrl+Shift+B by default ([#10](https://github.com/rugk/website-dark-mode-switcher/issues/10)) 2 | * **Improved:** Reduced amount of log messages written to console ([#11](https://github.com/rugk/website-dark-mode-switcher/issues/11)) 3 | * **Improved:** The settings page was expanded and is now much more useful than just being 4 | * **Fixed:** The JavaScript faking of the media query result is more realistic now. ([#13](https://github.com/rugk/website-dark-mode-switcher/issues/13)) 5 | * **Fixed:** `@import` is now supported for CSS stylesheets. ([#28](https://github.com/rugk/website-dark-mode-switcher/issues/28)) 6 | * **Fixed:** Fixed a race condition leading some sites to not enable the dark mode. ([#9](https://github.com/rugk/website-dark-mode-switcher/issues/9)) 7 | * **Internal:** Dependencies were updated to ensure the security and reliability of this add-on. 8 | -------------------------------------------------------------------------------- /assets/texts/en/Changelog/1.3.html: -------------------------------------------------------------------------------- 1 |
    2 |
  • Improved: Improved compatibility with GitHub, Twitter and more sites due to rewrite of JavaScript faking (#30, #35, thanks @tartpvule)
  • 3 |
  • Improved: Improve initialization process of content scripts. (#36, thanks @tartpvule)
  • 4 |
5 | 6 | More information on GitHub. 7 | -------------------------------------------------------------------------------- /assets/texts/en/Changelog/1.3.md: -------------------------------------------------------------------------------- 1 | * **Improved:** Improved compatibility with GitHub, Twitter and more sites due to rewrite of JavaScript faking ([#30](https://github.com/rugk/website-dark-mode-switcher/issues/30), [#35](https://github.com/rugk/website-dark-mode-switcher/pull/35), thanks [@tartpvule](https://github.com/tartpvule)) 2 | * **Improved:** Improve initialization process of content scripts. ([#36](https://github.com/rugk/website-dark-mode-switcher/pull/36), thanks [@tartpvule](https://github.com/tartpvule)) 3 | -------------------------------------------------------------------------------- /assets/texts/en/Changelog/2.0.html: -------------------------------------------------------------------------------- 1 |
    2 |
  • Improved: Switched to a completely new API of Firefox, drastically improving the compatibility and fixing many issues. (#5)
  • 3 |
  • New: You can now adjust between which styles the toolbar button should toggle in the settings.
  • 4 |
5 | 6 | More information on GitHub. 7 | -------------------------------------------------------------------------------- /assets/texts/en/Changelog/2.0.md: -------------------------------------------------------------------------------- 1 | * **Improved:** Switched to a completely new API of Firefox, drastically improving the compatibility and fixing many issues. ([#5](https://github.com/rugk/website-dark-mode-switcher/issues/5)) 2 | * **New:** You can now adjust between which styles the toolbar button should toggle in the settings. 3 | -------------------------------------------------------------------------------- /assets/texts/en/Changelog/next.md: -------------------------------------------------------------------------------- 1 | * **New:** The translation for Danish has been added. (, thanks to [@Grooty12](https://github.com/Grooty12). 2 | -------------------------------------------------------------------------------- /assets/texts/en/amoDescription.html: -------------------------------------------------------------------------------- 1 | This is an add-on that lets you invert the website's color scheme by inverting/changing the prefers-color-scheme media feature of CSS without requiring you to change the whole system style setting. 2 | 3 | You can also trigger toggling the dark mode with the hot key Ctrl+Shift+B (customizable in the Firefox add-on settings). 4 | 5 | This extension only works with Firefox v95 or higher. 6 | 7 | 8 | ✍️ Usage ✍️ 9 | 10 | You can use a toolbar button to toggle whether it should force a dark mode or not. It displays a yellow moon icon, if it is enabled and forces the website to be dark. 11 | 12 | 13 | ⛱️ Test websites ⛱️ 14 | Here are some well-known websites that support the dark system style, so you can test them with this add-on: 15 | 22 | 23 | 24 | 📝 Development 📝 25 | The add-on is free/libre open-source software and developed on GitHub. Fork it on GitHub and contribute. 26 | There are some easy issues to start with. 27 | 28 | 29 | 💬 Permissions 💬 30 | This add-on requires as few permissions as possible. 31 | An explanation of all permissions, this add-on requests, can be found on this site. 32 | -------------------------------------------------------------------------------- /assets/texts/en/amoScreenshots.csv: -------------------------------------------------------------------------------- 1 | disabled-light-mode.png; "This is how your website looks, if the add-on is disabled."; 2 | enabled-dark-style.png; "If the add-on is enabled, it forces a dark style."; 3 | enabled-light-style.png; "If the add-on is enabled, it forces a dark style, independent of the system setting."; 4 | -------------------------------------------------------------------------------- /assets/texts/en/amoSummary.txt: -------------------------------------------------------------------------------- 1 | Adjusts the website's color scheme, so that all websites are dark by default, if they have a special design for that. It makes websites look dark even with a light system style. 2 | -------------------------------------------------------------------------------- /assets/texts/en/permissions.md: -------------------------------------------------------------------------------- 1 | # Requested permissions 2 | 3 | For a general explanation of add-on permission see [this support article](https://support.mozilla.org/kb/permission-request-messages-firefox-extensions). 4 | 5 | ## Installation permissions 6 | 7 | | Internal Id | Permission | Explanation | 8 | |:-------------|:----------------------------------|:--------------------------------------------------------------| 9 | | `` | Access your data for all websites | Needed injecting the new (dark) style into tabs. | 10 | | `tabs` | Access browser tabs | Needed injecting the new (dark) style into all existing tabs. | 11 | 12 | ## Hidden permissions 13 | 14 | Additionally, it requests these permissions, which are not requested in Firefox when the add-on is installed, as they are not a serious permission. 15 | 16 | | Internal Id | Permission | Explanation | 17 | |:-------------|:----------------------------------|:-------------------------------------------------| 18 | | `storage` | Access local storage | Needed for saving options | 19 | -------------------------------------------------------------------------------- /assets/texts/en/privacy.txt: -------------------------------------------------------------------------------- 1 | This add-on does not send any information to the add-on author or any third-party. 2 | 3 | An explanation of all permissions, this add-on requests, can be found at https://github.com/rugk/website-dark-mode-switcher/blob/master/assets/texts/en/permissions.md. 4 | 5 | == THIRD-PARTY SERVICES == 6 | 7 | This ADD-ON uses the “sync storage” of your browser to store the settings. If the USER enables “Sync” in the browser, the settings are uploaded and synchronized across your devices connected to your account. If you do not do, the data is only stored locally on your device. 8 | In Mozilla Firefox the data is end-to-end encrypted before getting uploaded and stored on servers by Mozilla. 9 | See https://accounts.firefox.com/legal/privacy and https://www.mozilla.org/privacy/firefox/#c-privacy-topic-8 for Mozilla's privacy policies on that topic. 10 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "baseUrl": "./src", 5 | "checkJs": true, 6 | "paths": { 7 | "/*": ["./*"], 8 | } 9 | }, 10 | "exclude": [ 11 | "node_modules", 12 | "**/node_modules/*" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "eslint": "^8.3.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /scripts/make.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Makes a release ZIP of the add-on. 4 | # 5 | # IMPORTANT: This is only useful for building release versions of the add-on. 6 | # For development, please rather follow the guidance in the contributing doc. 7 | # 8 | 9 | EXTENSION_NAME="dark-mode-website-switcher@rugk.github.io" 10 | 11 | mkdir -p "build" 12 | 13 | # license should be in add-on 14 | mv LICENSE.md src/LICENSE.md 15 | 16 | # make sure we are using the stable manifest 17 | # as the dev edition manifest.json allows mocha.css and mocha.js in the CSP 18 | cp "./scripts/manifests/firefox.json" "./src/manifest.json" || exit 19 | 20 | # create zip 21 | cd src || exit 22 | zip -r -FS "../build/$EXTENSION_NAME.xpi" ./* -x "tests/*" -x "**/tests/*" \ 23 | -x "docs/*" -x "**/docs/*" \ 24 | -x "examples/*" -x "**/examples/*" -x "**/*.example" \ 25 | -x "**/README.md" -x "**/CONTRIBUTING.md" -x "**/manifest.json" \ 26 | -x "**/.git" -x "**/.gitignore" -x "**/.gitmodules" -x "**/.eslintrc" \ 27 | -x "**/.editorconfig" \ 28 | -x "**/.github/*" 29 | 30 | # revert changes 31 | mv LICENSE.md ../LICENSE.md 32 | cp "../scripts/manifests/dev.json" "../src/manifest.json" 33 | 34 | cd .. 35 | -------------------------------------------------------------------------------- /scripts/manifests/dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "DarkModeSwitcher DEV VERSION", 4 | "short_name": "__MSG_extensionNameShort__", 5 | "version": "2.0", 6 | "author": "rugk", 7 | 8 | "description": "__MSG_extensionDescription__", 9 | "homepage_url": "https://github.com/rugk/website-dark-mode-switcher/", 10 | 11 | "browser_action": { 12 | "browser_style": true, 13 | "default_icon": "icons/icon-dark.svg", 14 | "default_title": "__MSG_browserActionButtonTitle__", 15 | "theme_icons": [ 16 | { 17 | "dark": "icons/icon-dark.svg", 18 | "light": "icons/icon-light.svg", 19 | "size": 32 20 | } 21 | ] 22 | }, 23 | 24 | "options_ui": { 25 | "page": "options/options.html", 26 | "browser_style": true 27 | }, 28 | 29 | "background": { 30 | "page": "background/background.html" 31 | }, 32 | 33 | "commands": { 34 | "_execute_browser_action": { 35 | "suggested_key": { 36 | "default": "Ctrl+Shift+B" 37 | }, 38 | "description": "__MSG_commandToggleDarkMode__" 39 | } 40 | }, 41 | 42 | // testing version allows loading unit test libraries from CDNs 43 | "content_security_policy": "default-src 'self'; img-src data:; style-src 'self' https://unpkg.com; script-src 'self' https://unpkg.com", 44 | "icons": { 45 | "16": "icons/icon-dark.svg", 46 | "32": "icons/icon-dark.svg", 47 | "48": "icons/icon-dark.svg", 48 | "96": "icons/icon-dark.svg" 49 | }, 50 | "default_locale": "en", 51 | 52 | "permissions": [ 53 | "storage", 54 | "browserSettings" 55 | ], 56 | 57 | "applications": { 58 | "gecko": { 59 | "id": "dark-mode-website-switcher@rugk.github.io", 60 | "strict_min_version": "95.0" 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /scripts/manifests/firefox.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "__MSG_extensionName__", 4 | "short_name": "__MSG_extensionNameShort__", 5 | "version": "2.0", 6 | "author": "rugk", 7 | 8 | "description": "__MSG_extensionDescription__", 9 | "homepage_url": "https://github.com/rugk/website-dark-mode-switcher/", 10 | 11 | "browser_action": { 12 | "browser_style": true, 13 | "default_icon": "icons/icon-dark.svg", 14 | "default_title": "__MSG_browserActionButtonTitle__", 15 | "theme_icons": [ 16 | { 17 | "dark": "icons/icon-dark.svg", 18 | "light": "icons/icon-light.svg", 19 | "size": 32 20 | } 21 | ] 22 | }, 23 | 24 | "options_ui": { 25 | "page": "options/options.html", 26 | "browser_style": true 27 | }, 28 | 29 | "background": { 30 | "page": "background/background.html" 31 | }, 32 | 33 | "commands": { 34 | "_execute_browser_action": { 35 | "suggested_key": { 36 | "default": "Ctrl+Shift+B" 37 | }, 38 | "description": "__MSG_commandToggleDarkMode__" 39 | } 40 | }, 41 | 42 | "content_security_policy": "default-src 'self'", 43 | "icons": { 44 | "16": "icons/icon-dark.svg", 45 | "32": "icons/icon-dark.svg", 46 | "48": "icons/icon-dark.svg", 47 | "96": "icons/icon-dark.svg" 48 | }, 49 | "default_locale": "en", 50 | 51 | "permissions": [ 52 | "storage", 53 | "browserSettings" 54 | ], 55 | 56 | "applications": { 57 | "gecko": { 58 | "id": "dark-mode-website-switcher@rugk.github.io", 59 | "strict_min_version": "95.0" 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/_locales/da/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | // manifest.json 3 | "extensionName": { 4 | "message": "Mørk Tilstand Hjemmeside skifter", 5 | "description": "Name of the extension.", 6 | "hash": "1c347b2756cb4cd138c4e8c84731ca51" 7 | }, 8 | "extensionNameShort": { 9 | "message": "Mørk Tilstand Hjemmeside skifter", 10 | "description": "Name of the extension.", 11 | "hash": "1c347b2756cb4cd138c4e8c84731ca51" 12 | }, 13 | "extensionDescription": { 14 | "message": "Skifter hjemmesider til deres mørke tilstand, hvis de understøtter det.", 15 | "description": "Description of the extension.", 16 | "hash": "ac715a72fd13ff2b45081c96d554483e" 17 | }, 18 | "browserActionButtonTitle": { 19 | "message": "Tving Mørk Tilstand", 20 | "description": "The title for the button, which opens the popup.", 21 | "hash": "0c27fa654612c56f14537e6a6c9ef141" 22 | }, 23 | "commandToggleDarkMode": { 24 | "message": "Skift tvingning af mørk tilstand", 25 | "description": "Description of the hot key command to toggle the dark mode just like clicking on the browser action (by default with Ctrl+Shift+B).", 26 | "hash": "ec5231cdc07a35b5a30865fb18ef368c" 27 | }, 28 | 29 | // errors or other messages (mostly for settings) 30 | 31 | // browser setting - list of levels of control 32 | 33 | // badges 34 | 35 | // options 36 | 37 | // ARIA labels/descriptions 38 | 39 | "__WET_LOCALE__": { "message": "da" } 40 | } 41 | -------------------------------------------------------------------------------- /src/_locales/de/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | // manifest.json 3 | "extensionName": { 4 | "message": "Dark Mode Website Switcher", 5 | "description": "Name of the extension." 6 | }, 7 | "extensionNameShort": { 8 | "message": "Dark Mode Website Switcher", 9 | "description": "Name of the extension." 10 | }, 11 | "extensionDescription": { 12 | "message": "Wechselt Webseiten auf eine dunkle Version, wenn diese dies unterstützen.", 13 | "description": "Description of the extension." 14 | }, 15 | "browserActionButtonTitle": { 16 | "message": "Dark Website Forcer", 17 | "description": "The title for the button, which opens the popup." 18 | }, 19 | "commandToggleDarkMode": { 20 | "message": "Erzwingen des dunklen Modi umschalten", 21 | "description": "Description of the hot key command to toggle the dark mode just like clicking on the browser action (by default with Ctrl+Shift+B)." 22 | }, 23 | 24 | // errors or other messages (mostly for settings) 25 | "errorShowingMessage": { 26 | "message": "Konnte Nachricht nicht anzeigen.", 27 | "description": "When there is an error when showing the error/info/…." 28 | }, 29 | "couldNotLoadOptions": { 30 | "message": "Konnte Einstellungen nicht laden.", 31 | "description": "When one or all settings could not be loaded." 32 | }, 33 | "couldNotSaveOption": { 34 | "message": "Konnte diese Einstellung nicht speichern.", 35 | "description": "When a setting could not be saved." 36 | }, 37 | "messageUndoButton": { 38 | "message": "Rückgängig machen", 39 | "description": "The text of a button that undoes the last action." 40 | }, 41 | "couldNotUndoAction": { 42 | "message": "Konnte Aktion nicht rückgängig machen.", 43 | "description": "Shown when an action cannot be undone." 44 | }, 45 | "resettingOptionsWorked": { 46 | "message": "Alle Einstellungen benutzen nun wieder die Standardwerte!", 47 | "description": "The message shown, when the options of the settings were reset." 48 | }, 49 | "resettingOptionsFailed": { 50 | "message": "Konnte Optionen nicht zurück setzen!", 51 | "description": "The message shown, when the options of the settings could not have been reset." 52 | }, 53 | 54 | "warningAdvancedSettings": { 55 | "message": "Achtung: Dies sind erweiterte Einstellungen und sollten nur geändert werden, wenn du weist, was du tust! Im Fehlerfall bitte die Änderungen zurücksetzen.", 56 | "description": "The message shown in the options discouraging the user from changing the settings." 57 | }, 58 | "optionWarningRareStyleSetting": { 59 | "message": "Durch die Art wie die meisten Webseiten funktionieren, wird diese Option wahrscheinlich auf vielen Webseiten nicht funktionieren.", 60 | "description": "The message shown, when the website style option is set to a rarely used value we cannot properly toggle/enforce." 61 | }, 62 | 63 | // options 64 | "someSettingsAreManaged": { 65 | "message": "Einige Einstellung werden von deinem Systemadministrator festgelegt und können nicht geändert werden.", 66 | "description": "The message, which appears, when settings are pre-defined (as managed options) by administrators." 67 | }, 68 | "optionIsDisabledBecauseManaged": { 69 | "message": "Diese Option ist deaktiviert, weil sie von deinem Systemadministrator festgelegt wurde.", 70 | "description": "The title (tooltip) shown, when hovering over a disabled, managed option." 71 | }, 72 | "optionLearnMore": { 73 | "message": "Weitere Informationen", 74 | "description": "When a link to an explainer needs to be added, this is the link text." 75 | }, 76 | "optionsResetButton": { 77 | "message": "Setze alle Einstellungen auf Standardwerte zurück", 78 | "description": "The button to delete all current settings and load the defaults, shown in the add-on settings." 79 | }, 80 | 81 | "titleInstructions": { 82 | "message": "Anleitung", 83 | "description": "The title for a settings group." 84 | }, 85 | "titleAdvancedOptions": { 86 | "message": "Erweiterte Einstellungen", 87 | "description": "The title for a settings group." 88 | }, 89 | 90 | "settingIntroHelp": { 91 | "message": "Um das Add-on zu aktivieren oder zu deaktivieren und so eine Webseite in den dunklen Modus zu verwandeln, klicke auf das Toolbar-Symbol oder presse die Tastenkombination (anpassbar in den Tastenkombinationen-Add-on-Einstellungen von Firefox). Beachte, dass du in einigen Fällen die Webseite eventuell neu laden musst.", 92 | "description": "A description in the settings explaining the usage of the add-on." 93 | }, 94 | "settingIntroHelpRestrictions": { 95 | "message": "Bitte beachte, dass diese Erweiterung die Webseiten nur in einem dunklen Modus anzeigen kann, wenn diese dies unterstützen.", 96 | "description": "A description in the settings explaining the restrictions of the add-on." 97 | }, 98 | "settingIntroBehaviour": { 99 | "message": "Mit den folgenden Optionen kannst du den aktuellen Stil und die beiden Zustände ändern, in die der Knopf des Add-ons wechselt, wenn dieser angeklickt wird.", 100 | "description": "A description in the settings explaining what can be adjusted in the behaviour section." 101 | }, 102 | 103 | "optionsWebsiteStyle": { 104 | "message": "Webseitenstil:", 105 | "description": "An add-on option." 106 | }, 107 | "optionsWebsiteStyleLight": { 108 | "message": "Hell", 109 | "description": "The add-on option for optionsWebsiteStyle." 110 | }, 111 | "optionsWebsiteStyleDark": { 112 | "message": "Dunkel", 113 | "description": "The add-on option for optionsWebsiteStyle." 114 | }, 115 | "optionsWebsiteStyleNoPreference": { 116 | "message": "Keine spezielle Preferenz", 117 | "description": "The add-on option for optionsWebsiteStyle. Explicitly opt-in into not preferring any style." 118 | }, 119 | "optionsWebsiteStyleSystem": { 120 | "message": "System (nicht überschreiben)", 121 | "description": "The add-on option for optionsWebsiteStyle. It basically just disables the add-on." 122 | }, 123 | 124 | "optionFunctionalMode": { 125 | "message": "Benutze die funktionale Implementierung, um die Stylesheets zu parsen.", 126 | "description": "The add-on option for optionFunctionalMode. It changes the way the add-on works internally." 127 | }, 128 | "optionFunctionalModeDescr": { 129 | "message": "Diese Option ändert die interne CSS-Abarbeitungsmethode für die Webseiten. Es wird aktuell nicht empfohlen sie zu verwenden, da die funktionale Implementierung aufgrund von Arbeitsspeicherbeschränkungen nicht auf allen Seiten funktionieren könnte.", 130 | "description": "The add-on option description for optionFunctionalMode. It should discourage changing the option." 131 | }, 132 | 133 | "translatorCredit": { 134 | "message": "Dieses Add-on wurde von $TRANSLATORS$ ins Deutsche übersetzt.", 135 | "description": "The credit text for the translator. See https://github.com/TinyWebEx/common/blob/master/CONTRIBUTING.md#translator-credit-inside-of-add-on for how to translate this.", 136 | "placeholders": { 137 | "translators": { 138 | "content": "$1", 139 | "example": "@rugk" 140 | } 141 | } 142 | }, 143 | "translatorLink": { 144 | "message": "https://github.com/rugk", 145 | "description": "The link to the translator's GitHub profile." 146 | }, 147 | "translatorUsername": { 148 | "message": "rugk", 149 | "description": "The username that the translator wants to be referred to." 150 | }, 151 | 152 | "contributorsThanks": { 153 | "message": "Ebenfalls vielen Dank an $CONTRIBUTORS$.", 154 | "description": "Text thanking all contributors and linking to the contributors file.", 155 | "placeholders": { 156 | "contributors": { 157 | "content": "$1", 158 | "example": "all other contributors" 159 | } 160 | } 161 | }, 162 | "contributorsThanksLink": { 163 | "message": "https://github.com/rugk/website-dark-mode-switcher/blob/master/CONTRIBUTORS.md", 164 | "description": "The link to the CONTRIBUTORS file." 165 | }, 166 | "contributorsThanksLinkText": { 167 | "message": "alle anderen Beitragenden", 168 | "description": "The link text linking to the contributors file. See contributorsThanks." 169 | }, 170 | 171 | // ARIA labels/descriptions 172 | "dismissIconDescription": { 173 | "message": "Diese Nachricht schließen", 174 | "description": "the aria label for the close button of the message box" 175 | }, 176 | "ariaMessageLoading": { 177 | "message": "Ladenachricht", 178 | "description": "the aria label to label the message box as an info message box" 179 | }, 180 | "ariaMessageInfo": { 181 | "message": "Info", 182 | "description": "the aria label to label the message box as an info message box" 183 | }, 184 | "ariaMessageSuccess": { 185 | "message": "Erfolg", 186 | "description": "the aria label to label the message box as an success message box" 187 | }, 188 | "ariaMessageError": { 189 | "message": "Fehlermeldung", 190 | "description": "the aria label to label the message box as an error message box" 191 | }, 192 | "ariaMessageWarning": { 193 | "message": "Warnung", 194 | "description": "the aria label to label the message box as an warning message box" 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | // manifest.json 3 | "extensionName": { 4 | "message": "Dark Mode Website Switcher", 5 | "description": "Name of the extension." 6 | }, 7 | "extensionNameShort": { 8 | "message": "Dark Mode Website Switcher", 9 | "description": "Name of the extension." 10 | }, 11 | "extensionDescription": { 12 | "message": "Switches websites to the dark version, if they support it.", 13 | "description": "Description of the extension." 14 | }, 15 | "browserActionButtonTitle": { 16 | "message": "Dark Website Forcer", 17 | "description": "The title for the button, which opens the popup." 18 | }, 19 | "commandToggleDarkMode": { 20 | "message": "Toggle forcing the dark mode", 21 | "description": "Description of the hot key command to toggle the dark mode just like clicking on the browser action (by default with Ctrl+Shift+B)." 22 | }, 23 | 24 | // errors or other messages (mostly for settings) 25 | "errorShowingMessage": { 26 | "message": "Could not show this message.", 27 | "description": "When there is an error when showing the error/info/…." 28 | }, 29 | "couldNotLoadOptions": { 30 | "message": "Could not load settings.", 31 | "description": "When one or all settings could not be loaded." 32 | }, 33 | "couldNotSaveOption": { 34 | "message": "Could not save this setting.", 35 | "description": "When a setting could not be saved." 36 | }, 37 | "messageUndoButton": { 38 | "message": "Undo", 39 | "description": "The text of a button that undoes the last action." 40 | }, 41 | "couldNotUndoAction": { 42 | "message": "Could not undo action.", 43 | "description": "Shown when an action cannot be undone." 44 | }, 45 | "resettingOptionsWorked": { 46 | "message": "All settings are now back to defaults again!", 47 | "description": "The message shown, when the options of the settings were reset." 48 | }, 49 | "resettingOptionsFailed": { 50 | "message": "Could not reset options!", 51 | "description": "The message shown, when the options of the settings could not have been reset." 52 | }, 53 | 54 | "optionWarningRareStyleSetting": { 55 | "message": "Due to the way most websites implement their styling, this option may not work on many websites.", 56 | "description": "The message shown, when the website style option is set to a rarely used value we cannot properly toggle/enforce." 57 | }, 58 | "warningAdvancedSettings": { 59 | "message": "Attention: These are advanced settings and should only be changed if you know what you are doing! In case of error, please reset the changes.", 60 | "description": "The message shown in the options discouraging the user from changing the settings." 61 | }, 62 | 63 | // browser setting - list of levels of control 64 | "browserSettingLevelOfControlNotControllable": { 65 | "message": "This browser setting cannot be controlled by the extension.", 66 | "description": "The mexplanation for a level of control of a browser setting that an extension has over such a setting. See https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/types/BrowserSetting/get" 67 | }, 68 | "browserSettingLevelOfControlControlledByOtherExtensions": { 69 | "message": "This browser setting cannot be controlled by this extension, because another extension already controls it.", 70 | "description": "The mexplanation for a level of control of a browser setting that an extension has over such a setting. See https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/types/BrowserSetting/get" 71 | }, 72 | "browserSettingLevelOfControlControllableByThisExtension": { 73 | "message": "This browser setting can be controlled by this extension.", 74 | "description": "The mexplanation for a level of control of a browser setting that an extension has over such a setting. See https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/types/BrowserSetting/get" 75 | }, 76 | "browserSettingLevelOfControlControlledByThisExtensionExplanation": { 77 | "message": "This browser setting is currently being controlled by this extension.", 78 | "description": "The mexplanation for a level of control of a browser setting that an extension has over such a setting. See https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/types/BrowserSetting/get" 79 | }, 80 | 81 | // badges 82 | "badgeLight": { 83 | "message": "", 84 | "description": "The badge text shown, when the style is set to a light. You usually shoulod not translate it. (You do not even need to copy it.)" 85 | }, 86 | "badgeDark": { 87 | "message": "🌔", 88 | "description": "The badge text shown, when the style is set to a dark. You usually shoulod not translate it. (You do not even need to copy it.)" 89 | }, 90 | 91 | // options 92 | "someSettingsAreManaged": { 93 | "message": "Some settings are managed by your system administrator and cannot be changed.", 94 | "description": "The message, which appears, when settings are pre-defined (as managed options) by administrators." 95 | }, 96 | "optionIsDisabledBecauseManaged": { 97 | "message": "This option is disabled, because it has been configured by your system administrator.", 98 | "description": "The title (tooltip) shown, when hovering over a disabled, managed option." 99 | }, 100 | "optionLearnMore": { 101 | "message": "Learn more", 102 | "description": "When a link to an explainer needs to be added, this is the link text." 103 | }, 104 | "optionsResetButton": { 105 | "message": "Reset all settings to defaults", 106 | "description": "The button to delete all current settings and load the defaults, shown in the add-on settings." 107 | }, 108 | 109 | "titleInstructions": { 110 | "message": "Instructions", 111 | "description": "The title for a settings group." 112 | }, 113 | "titleAdvancedOptions": { 114 | "message": "Advanced options", 115 | "description": "The title for a settings group." 116 | }, 117 | 118 | "settingIntroHelp": { 119 | "message": "To enable and disable the add-on and turn a website into dark or the default settings, please click on the toolbar icon of this extension or use the hot key (configurable in the extension shortcut configuration of Firefox). Note that in some cases you may need to reload the site.", 120 | "description": "A description in the settings explaining the usage of the add-on." 121 | }, 122 | "settingIntroHelpRestrictions": { 123 | "message": "Please be aware that this extension only works on websites that support the dark styling.", 124 | "description": "A description in the settings explaining the restrictions of the add-on." 125 | }, 126 | "settingIntroBehaviour": { 127 | "message": "The following options allow you to change the current style and the two states the add-on button toggles when you click on it.", 128 | "description": "A description in the settings explaining what can be adjusted in the behaviour section." 129 | }, 130 | 131 | "optionsWebsiteStyle": { 132 | "message": "Website style:", 133 | "description": "An add-on option." 134 | }, 135 | "optionsWebsiteStyleLight": { 136 | "message": "Light", 137 | "description": "The add-on option for optionsWebsiteStyle." 138 | }, 139 | "optionsWebsiteStyleDark": { 140 | "message": "Dark", 141 | "description": "The add-on option for optionsWebsiteStyle." 142 | }, 143 | "optionsWebsiteStyleSystem": { 144 | "message": "Follow the system/device's theme", 145 | "description": "The add-on option for optionsWebsiteStyle. The style is adopted from the system/device here." 146 | }, 147 | "optionsWebsiteStyleBrowser": { 148 | "message": "Follow the browser's theme", 149 | "description": "The add-on option for optionsWebsiteStyle. The style is adopted from the browser theme here." 150 | }, 151 | "optionsWebsiteStyleNull": { 152 | "message": "Do not overwrite (Disables this add-on)", 153 | "description": "The add-on option for optionsWebsiteStyle. It basically just disables the add-on." 154 | }, 155 | 156 | "optionFunctionalMode": { 157 | "message": "Use the functional implementation for parsing style sheets.", 158 | "description": "The add-on option for optionFunctionalMode. It changes the way the add-on works internally." 159 | }, 160 | "optionFunctionalModeDescr": { 161 | "message": "This option changes the internal CSS processing method for the websites. It is currently not recommened to use, as the functional implementation may not work on some sites due to memory constraints. Only change it for debugging.", 162 | "description": "The add-on option description for optionFunctionalMode. It should discourage changing the option." 163 | }, 164 | 165 | "translatorCredit": { 166 | "message": "This add-on has been translated into English by $TRANSLATORS$.", 167 | "description": "The credit text for the translator. See https://github.com/TinyWebEx/common/blob/master/CONTRIBUTING.md#translator-credit-inside-of-add-on for how to translate this.", 168 | "placeholders": { 169 | "translators": { 170 | "content": "$1", 171 | "example": "@rugk" 172 | } 173 | } 174 | }, 175 | "translatorLink": { 176 | "message": "https://github.com/rugk", 177 | "description": "The link to the translator's GitHub profile." 178 | }, 179 | "translatorUsername": { 180 | "message": "rugk", 181 | "description": "The username that the translator wants to be referred to." 182 | }, 183 | 184 | "contributorsThanks": { 185 | "message": "Also thanks to $CONTRIBUTORS$.", 186 | "description": "Text thanking all contributors and linking to the contributors file.", 187 | "placeholders": { 188 | "contributors": { 189 | "content": "$1", 190 | "example": "all other contributors" 191 | } 192 | } 193 | }, 194 | "contributorsThanksLink": { 195 | "message": "https://github.com/rugk/website-dark-mode-switcher/blob/master/CONTRIBUTORS.md", 196 | "description": "The link to the CONTRIBUTORS file." 197 | }, 198 | "contributorsThanksLinkText": { 199 | "message": "all other contributors", 200 | "description": "The link text linking to the contributors file. See contributorsThanks." 201 | }, 202 | 203 | // ARIA labels/descriptions 204 | "dismissIconDescription": { 205 | "message": "Close this message", 206 | "description": "the aria label for the close button of the message box" 207 | }, 208 | "ariaMessageLoading": { 209 | "message": "loading message", 210 | "description": "the aria label to label the message box as an info message box" 211 | }, 212 | "ariaMessageInfo": { 213 | "message": "info message", 214 | "description": "the aria label to label the message box as an info message box" 215 | }, 216 | "ariaMessageSuccess": { 217 | "message": "success message", 218 | "description": "the aria label to label the message box as an success message box" 219 | }, 220 | "ariaMessageError": { 221 | "message": "error message", 222 | "description": "the aria label to label the message box as an error message box" 223 | }, 224 | "ariaMessageWarning": { 225 | "message": "warning message", 226 | "description": "the aria label to label the message box as an warning message box" 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/background/background.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/background/background.js: -------------------------------------------------------------------------------- 1 | import * as ActionButton from "./modules/ActionButton.js"; 2 | import "./modules/InstallUpgrade.js"; 3 | 4 | // init modules 5 | ActionButton.init(); 6 | -------------------------------------------------------------------------------- /src/background/modules/ActionButton.js: -------------------------------------------------------------------------------- 1 | import * as DarkModeLogic from "/common/modules/DarkModeLogic.js"; 2 | 3 | const BADGE_BACKGROUND_COLOR = "rgba(48, 48, 48, 0)"; 4 | const BADGE_COLOR = null; // = "auto" 5 | 6 | /** 7 | * Adjust the indicator that shows to the user whether the dark mode is enabled 8 | * or not. 9 | * 10 | * @function 11 | * @private 12 | * @param {string} newColorSetting 13 | * @returns {void} 14 | */ 15 | export function adjustUserIndicator(newColorSetting) { 16 | let badgeText; 17 | if (newColorSetting === "dark") { 18 | badgeText = browser.i18n.getMessage("badgeDark"); 19 | } else { // if = light 20 | badgeText = browser.i18n.getMessage("badgeLight"); 21 | } 22 | 23 | browser.browserAction.setBadgeText({ 24 | text: badgeText 25 | }); 26 | } 27 | 28 | /** 29 | * Init module. 30 | * 31 | * And bind to clicks on toolbar button, so you can quickly trigger the dark mode. 32 | * 33 | * @function 34 | * @returns {void} 35 | */ 36 | export function init() { 37 | DarkModeLogic.getCurrentState().then((newSetting) => { 38 | adjustUserIndicator(newSetting); 39 | }); 40 | 41 | DarkModeLogic.registerChangeListener((currentValue) => { 42 | adjustUserIndicator(currentValue); 43 | }); 44 | 45 | browser.browserAction.onClicked.addListener(() => { 46 | return DarkModeLogic.toggleDarkMode().catch(console.error); 47 | }); 48 | 49 | // static design 50 | browser.browserAction.setBadgeTextColor({ 51 | color: BADGE_COLOR 52 | }); 53 | browser.browserAction.setBadgeBackgroundColor({ 54 | color: BADGE_BACKGROUND_COLOR 55 | }); 56 | } 57 | -------------------------------------------------------------------------------- /src/background/modules/InstallUpgrade.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Upgrades user data on installation of new updates. 3 | * 4 | * Attention: Currently you must not include this script asyncronously. See 5 | * https://bugzilla.mozilla.org/show_bug.cgi?id=1506464 for details. 6 | * 7 | * @module InstallUpgrade 8 | */ 9 | 10 | import { COLOR_OVERRIDE } from "/common/modules/DarkModeLogic.js"; 11 | 12 | /** 13 | * Upgrades the user data if required. 14 | * 15 | * @see {@link https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onInstalled} 16 | * @private 17 | * @param {Object} details 18 | * @returns {Promise} 19 | */ 20 | async function handleInstalled(details) { 21 | // only trigger for usual addon updates 22 | if (details.reason !== "update") { 23 | return; 24 | } 25 | 26 | switch (details.previousVersion) { 27 | case "1.2": 28 | case "1.3": { 29 | console.log(`Doing upgrade from ${details.previousVersion}.`, details); 30 | 31 | const oldData = await browser.storage.sync.get(); 32 | 33 | let oldColorScheme = oldData.fakedColorStatus; 34 | switch (oldColorScheme) { 35 | case "no_preference": 36 | case "no_overwrite": 37 | oldColorScheme = COLOR_OVERRIDE.SYSTEM; 38 | break; 39 | } 40 | 41 | await browser.storage.sync.set({ 42 | prefersColorSchemeOverride: oldColorScheme, 43 | }); 44 | await browser.storage.sync.remove(["fakedColorStatus", "functionalMode"]); 45 | 46 | console.info("Data upgrade successful.", await browser.storage.sync.get()); 47 | break; 48 | } 49 | default: 50 | console.log(`Addon upgrade from ${details.previousVersion}. No data upgrade needed.`, details); 51 | } 52 | } 53 | 54 | /** 55 | * Inits module. 56 | * 57 | * @private 58 | * @returns {void} 59 | */ 60 | function init() { 61 | browser.runtime.onInstalled.addListener(handleInstalled); 62 | } 63 | 64 | init(); 65 | -------------------------------------------------------------------------------- /src/common/common.css: -------------------------------------------------------------------------------- 1 | @import url("./modules/CommonCss/common.css"); 2 | 3 | -------------------------------------------------------------------------------- /src/common/common.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Just load/does common stuff. 3 | * 4 | * @module common 5 | * @requires modules/Localizer 6 | */ 7 | 8 | // by default translate whole site 9 | import "/common/modules/Localizer/Localizer.js"; 10 | -------------------------------------------------------------------------------- /src/common/img/check.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/common/img/close-white.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/common/img/close.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/common/img/error-white.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/common/img/info-dark.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/common/img/info-light.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/common/img/open-in-new.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/common/img/warning-dark.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/common/modules/BrowserSettings/BrowserSettings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * All available communication types 3 | * 4 | * @module data/BrowserSettings 5 | */ 6 | 7 | /** 8 | * An array of all sources for requests. 9 | * 10 | * @const 11 | * @type {Object.} 12 | */ 13 | export const LEVEL_OF_CONTROL = Object.freeze({ 14 | NOT_CONTROLLABLE: "not_controllable", 15 | CONTROLLED_BY_OTHER_EXTENSION: "controlled_by_other_extensions", 16 | CONTROLLABLE_BY_THIS_EXTENSION: "controllable_by_this_extension", 17 | CONTROLLED_BY_THIS_EXTENSION: "controlled_by_this_extension" 18 | }); 19 | 20 | /** 21 | * The localisation strings for explaining the current level of the control. 22 | * 23 | * You can e.g. use it to explain why {@see isControllable} returns false. 24 | * 25 | * @const 26 | * @type {Object.} 27 | */ 28 | export const LEVEL_OF_CONTROL_EXPLANATION = Object.freeze({ 29 | [LEVEL_OF_CONTROL.NOT_CONTROLLABLE]: "browserSettingLevelOfControlNotControllable", 30 | [LEVEL_OF_CONTROL.CONTROLLED_BY_OTHER_EXTENSION]: "browserSettingLevelOfControlControlledByOtherExtensions", 31 | [LEVEL_OF_CONTROL.CONTROLLABLE_BY_THIS_EXTENSION]: "browserSettingLevelOfControlControllableByThisExtension", 32 | [LEVEL_OF_CONTROL.CONTROLLED_BY_THIS_EXTENSION]: "browserSettingLevelOfControlControlledByThisExtensionExplanation" 33 | }); 34 | 35 | /** 36 | * Returns whether the extension is controllable by the extension. 37 | * 38 | * @private 39 | * @param {LEVEL_OF_CONTROL} levelOfControl 40 | * @see {@link https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/types/BrowserSetting/get} 41 | */ 42 | export function isControllable(levelOfControl) { 43 | switch (levelOfControl) { 44 | case LEVEL_OF_CONTROL.CONTROLLABLE_BY_THIS_EXTENSION: 45 | case LEVEL_OF_CONTROL.CONTROLLED_BY_THIS_EXTENSION: 46 | case LEVEL_OF_CONTROL.CONTROLLED_BY_OTHER_EXTENSION: // I guess we can still try to override it? https://github.com/mdn/content/issues/10838 47 | return true; 48 | case LEVEL_OF_CONTROL.NOT_CONTROLLABLE: 49 | case LEVEL_OF_CONTROL.CONTROLLED_BY_THIS_EXTENSION: 50 | return false; 51 | default: 52 | throw new TypeError(`Unexpected error: Invalid value for levelOfControl passed: ${levelOfControl}.`); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/common/modules/DarkModeLogic.js: -------------------------------------------------------------------------------- 1 | import * as AddonSettings from "/common/modules/AddonSettings/AddonSettings.js"; 2 | import { isControllable } from "/common/modules/BrowserSettings/BrowserSettings.js"; 3 | 4 | /** 5 | * A simplified API callback providing the result of the about:config setting. 6 | * 7 | * @async 8 | * @callback changeTrigger 9 | * @param {string} value the new value 10 | * @return {void} 11 | */ 12 | 13 | /** 14 | * A map of all possible color schemes supported by the API. 15 | * 16 | * @const 17 | * @type {Object.} 18 | * @see {@link https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/browserSettings/overrideContentColorScheme} 19 | */ 20 | export const COLOR_OVERRIDE = Object.freeze({ 21 | LIGHT: "light", 22 | DARK: "dark", 23 | SYSTEM: "system", 24 | BROWSER: "browser", 25 | NULL: null 26 | }); 27 | 28 | /** 29 | * @type {changeTrigger[]} 30 | */ 31 | const onChangeCallbacks = []; 32 | 33 | /** 34 | * Switch the dark mode from on to off or inverse. 35 | * 36 | * @returns {Promise} 37 | */ 38 | export async function toggleDarkMode() { 39 | AddonSettings.loadOptions(); 40 | const darkModeVariant = await AddonSettings.get("darkModeVariant"); 41 | const lightModeVariant = await AddonSettings.get("lightModeVariant"); 42 | 43 | const currentBrowserSetting = await getCurrentState(); 44 | let newBrowserSetting = ""; 45 | if (currentBrowserSetting === COLOR_OVERRIDE.DARK || 46 | currentBrowserSetting === darkModeVariant) { 47 | newBrowserSetting = lightModeVariant; 48 | } else { // if = COLOR_OVERRIDE.LIGHT 49 | newBrowserSetting = darkModeVariant; 50 | } 51 | 52 | await applySetting(newBrowserSetting); 53 | } 54 | 55 | /** 56 | * Return the currently used option. 57 | * 58 | * @returns {Promise} 59 | */ 60 | export async function getCurrentState() { 61 | // TODO: implement optional syncing! 62 | // const shouldBeSynced = await AddonSettings.get("shouldBeSynced"); 63 | 64 | // if (shouldBeSynced) { 65 | // const currentBrowserSetting = (await browser.browserSettings.overrideContentColorScheme.get({})).value; 66 | // const syncedSetting = await AddonSettings.get("prefersColorSchemeOverride"); 67 | // if (currentBrowserSetting !== syncedSetting) { 68 | // await applySetting(syncedSetting); 69 | // } 70 | // } 71 | 72 | // reload the setting from browser again as that is the safer value to rely on 73 | return (await browser.browserSettings.overrideContentColorScheme.get({})).value; 74 | } 75 | 76 | /** 77 | * Apply the provided style. 78 | * 79 | * A value of "null", given as a string or literal, resets the setting, so other extensions or similar can override it. 80 | * 81 | * @param {string|null} newOption of COLOR_OVERRIDE 82 | * @returns {Promise} 83 | * @throws {Error} 84 | */ 85 | export async function applySetting(newOption) { 86 | const currentBrowserSetting = await browser.browserSettings.overrideContentColorScheme.get({}); 87 | console.log("current browser setting for overrideContentColorScheme:", currentBrowserSetting); 88 | 89 | if (!isControllable(currentBrowserSetting.levelOfControl)) { 90 | throw Error("Browser setting is not controllable."); 91 | } 92 | 93 | let couldBeModified; 94 | if (newOption === "null" || newOption === null) { 95 | couldBeModified = await browser.browserSettings.overrideContentColorScheme.clear({}); 96 | } else { 97 | couldBeModified = await browser.browserSettings.overrideContentColorScheme.set({ 98 | value: newOption 99 | }); 100 | } 101 | 102 | if (!couldBeModified) { 103 | throw Error("Browser setting could not be modified."); 104 | } 105 | 106 | await AddonSettings.set("prefersColorSchemeOverride", newOption); 107 | } 108 | 109 | /** 110 | * Register a callback for any changed value. 111 | * 112 | * @param {changeTrigger} callback 113 | * @returns {void} 114 | */ 115 | export function registerChangeListener(callback) { 116 | onChangeCallbacks.push(callback); 117 | } 118 | 119 | /** 120 | * Handle any change to the browser setting value and trigger registered callbacks. 121 | * 122 | * @private 123 | * @param {Object} details 124 | * @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/types/BrowserSetting/onChange} 125 | * @returns {void} 126 | */ 127 | function internalTriggerHandler(details) { 128 | const currentValue = details.value; 129 | onChangeCallbacks.forEach((callback) => callback(currentValue)); 130 | } 131 | 132 | /** 133 | * Register the internal handler ({@link internalTriggerHandler)}) for handling changes to the browser setting. 134 | * 135 | * @private 136 | * @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/types/BrowserSetting/onChange} 137 | * @returns {void} 138 | */ 139 | function registerBrowserListener() { 140 | browser.browserSettings.overrideContentColorScheme.onChange.addListener(internalTriggerHandler); 141 | } 142 | 143 | /** 144 | * Unregister the internal handler ({@link internalTriggerHandler)}) to ignore any changes to the browser setting. 145 | * 146 | * @private 147 | * @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/types/BrowserSetting/onChange} 148 | * @returns {void} 149 | */ 150 | function unregisterBrowserListener() { 151 | browser.browserSettings.overrideContentColorScheme.onChange.removeListener(internalTriggerHandler); 152 | } 153 | 154 | // automatically init itself as fast as possible 155 | registerBrowserListener(); 156 | -------------------------------------------------------------------------------- /src/common/modules/data/BrowserCommunicationTypes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * All available communication types 3 | * 4 | * @module data/browserCommunicationTypes 5 | */ 6 | 7 | /** 8 | * An array of all communcation types. 9 | * 10 | * @const 11 | * @type {Object.} 12 | */ 13 | export const COMMUNICATION_MESSAGE_TYPE = Object.freeze({ 14 | INSERT_CSS: "insertCss", 15 | REMOVE_CSS: "removeCss", 16 | NEW_SETTING: "newSetting", 17 | NEW_ADDIONAL_SETTINGS: "newSettingAdditional" 18 | }); 19 | 20 | /** 21 | * An array of all sources for requests. 22 | * 23 | * @const 24 | * @type {Object.} 25 | */ 26 | export const COMMUNICATION_MESSAGE_SOURCE = Object.freeze({ 27 | BROWSER_ACTION: "browserAction", 28 | SETTINGS_PAGE: "settingsPage" 29 | }); 30 | -------------------------------------------------------------------------------- /src/common/modules/data/DefaultSettings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Specifies the default settings of the add-on. 3 | * 4 | * @module data/DefaultSettings 5 | */ 6 | 7 | export const DEFAULT_SETTINGS = Object.freeze({ 8 | cleverDarkMode: true, 9 | prefersColorSchemeOverride: "dark", 10 | functionalMode: false, 11 | lightModeVariant: "light", 12 | darkModeVariant: "dark" 13 | }); 14 | -------------------------------------------------------------------------------- /src/common/modules/data/MessageLevel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains a static object for messages. 3 | * 4 | * @module /common/modules/data/MessageLevel 5 | */ 6 | 7 | /** 8 | * Specifies the message level to use, 9 | * 10 | * @readonly 11 | * @enum {int} 12 | * @default 13 | */ 14 | export const MESSAGE_LEVEL = Object.freeze({ 15 | "ERROR": 3, 16 | "WARN": 2, 17 | "INFO": 1, 18 | "LOADING": -2, 19 | "SUCCESS": -3 20 | }); 21 | -------------------------------------------------------------------------------- /src/common/modules/lodash/.internal/baseGetTag.js: -------------------------------------------------------------------------------- 1 | const objectProto = Object.prototype 2 | const hasOwnProperty = objectProto.hasOwnProperty 3 | const toString = objectProto.toString 4 | const symToStringTag = typeof Symbol != 'undefined' ? Symbol.toStringTag : undefined 5 | 6 | /** 7 | * The base implementation of `getTag` without fallbacks for buggy environments. 8 | * 9 | * @private 10 | * @param {*} value The value to query. 11 | * @returns {string} Returns the `toStringTag`. 12 | */ 13 | function baseGetTag(value) { 14 | if (value == null) { 15 | return value === undefined ? '[object Undefined]' : '[object Null]' 16 | } 17 | if (!(symToStringTag && symToStringTag in Object(value))) { 18 | return toString.call(value) 19 | } 20 | const isOwn = hasOwnProperty.call(value, symToStringTag) 21 | const tag = value[symToStringTag] 22 | let unmasked = false 23 | try { 24 | value[symToStringTag] = undefined 25 | unmasked = true 26 | } catch (e) {} 27 | 28 | const result = toString.call(value) 29 | if (unmasked) { 30 | if (isOwn) { 31 | value[symToStringTag] = tag 32 | } else { 33 | delete value[symToStringTag] 34 | } 35 | } 36 | return result 37 | } 38 | 39 | export default baseGetTag 40 | -------------------------------------------------------------------------------- /src/common/modules/lodash/.internal/freeGlobal.js: -------------------------------------------------------------------------------- 1 | /** Detect free variable `global` from Node.js. */ 2 | const freeGlobal = typeof global == 'object' && global !== null && global.Object === Object && global 3 | 4 | export default freeGlobal 5 | -------------------------------------------------------------------------------- /src/common/modules/lodash/.internal/getTag.js: -------------------------------------------------------------------------------- 1 | import baseGetTag from './baseGetTag.js' 2 | 3 | /** `Object#toString` result references. */ 4 | const dataViewTag = '[object DataView]' 5 | const mapTag = '[object Map]' 6 | const objectTag = '[object Object]' 7 | const promiseTag = '[object Promise]' 8 | const setTag = '[object Set]' 9 | const weakMapTag = '[object WeakMap]' 10 | 11 | /** Used to detect maps, sets, and weakmaps. */ 12 | const dataViewCtorString = `${DataView}` 13 | const mapCtorString = `${Map}` 14 | const promiseCtorString = `${Promise}` 15 | const setCtorString = `${Set}` 16 | const weakMapCtorString = `${WeakMap}` 17 | 18 | /** 19 | * Gets the `toStringTag` of `value`. 20 | * 21 | * @private 22 | * @param {*} value The value to query. 23 | * @returns {string} Returns the `toStringTag`. 24 | */ 25 | let getTag = baseGetTag 26 | 27 | // Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6. 28 | if ((DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag) || 29 | (getTag(new Map) != mapTag) || 30 | (getTag(Promise.resolve()) != promiseTag) || 31 | (getTag(new Set) != setTag) || 32 | (getTag(new WeakMap) != weakMapTag)) { 33 | getTag = (value) => { 34 | const result = baseGetTag(value) 35 | const Ctor = result == objectTag ? value.constructor : undefined 36 | const ctorString = Ctor ? `${Ctor}` : '' 37 | 38 | if (ctorString) { 39 | switch (ctorString) { 40 | case dataViewCtorString: return dataViewTag 41 | case mapCtorString: return mapTag 42 | case promiseCtorString: return promiseTag 43 | case setCtorString: return setTag 44 | case weakMapCtorString: return weakMapTag 45 | } 46 | } 47 | return result 48 | } 49 | } 50 | 51 | export default getTag 52 | -------------------------------------------------------------------------------- /src/common/modules/lodash/.internal/root.js: -------------------------------------------------------------------------------- 1 | import freeGlobal from './freeGlobal.js' 2 | 3 | /** Detect free variable `self`. */ 4 | const freeSelf = typeof self == 'object' && self !== null && self.Object === Object && self 5 | 6 | /** Used as a reference to the global object. */ 7 | const root = freeGlobal || freeSelf 8 | 9 | export default root 10 | -------------------------------------------------------------------------------- /src/common/modules/lodash/debounce.js: -------------------------------------------------------------------------------- 1 | import isObject from './isObject.js' 2 | import root from './.internal/root.js' 3 | 4 | /** 5 | * Creates a debounced function that delays invoking `func` until after `wait` 6 | * milliseconds have elapsed since the last time the debounced function was 7 | * invoked, or until the next browser frame is drawn. The debounced function 8 | * comes with a `cancel` method to cancel delayed `func` invocations and a 9 | * `flush` method to immediately invoke them. Provide `options` to indicate 10 | * whether `func` should be invoked on the leading and/or trailing edge of the 11 | * `wait` timeout. The `func` is invoked with the last arguments provided to the 12 | * debounced function. Subsequent calls to the debounced function return the 13 | * result of the last `func` invocation. 14 | * 15 | * **Note:** If `leading` and `trailing` options are `true`, `func` is 16 | * invoked on the trailing edge of the timeout only if the debounced function 17 | * is invoked more than once during the `wait` timeout. 18 | * 19 | * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred 20 | * until the next tick, similar to `setTimeout` with a timeout of `0`. 21 | * 22 | * If `wait` is omitted in an environment with `requestAnimationFrame`, `func` 23 | * invocation will be deferred until the next frame is drawn (typically about 24 | * 16ms). 25 | * 26 | * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) 27 | * for details over the differences between `debounce` and `throttle`. 28 | * 29 | * @since 0.1.0 30 | * @category Function 31 | * @param {Function} func The function to debounce. 32 | * @param {number} [wait=0] 33 | * The number of milliseconds to delay; if omitted, `requestAnimationFrame` is 34 | * used (if available). 35 | * @param {Object} [options={}] The options object. 36 | * @param {boolean} [options.leading=false] 37 | * Specify invoking on the leading edge of the timeout. 38 | * @param {number} [options.maxWait] 39 | * The maximum time `func` is allowed to be delayed before it's invoked. 40 | * @param {boolean} [options.trailing=true] 41 | * Specify invoking on the trailing edge of the timeout. 42 | * @returns {Function} Returns the new debounced function. 43 | * @example 44 | * 45 | * // Avoid costly calculations while the window size is in flux. 46 | * jQuery(window).on('resize', debounce(calculateLayout, 150)) 47 | * 48 | * // Invoke `sendMail` when clicked, debouncing subsequent calls. 49 | * jQuery(element).on('click', debounce(sendMail, 300, { 50 | * 'leading': true, 51 | * 'trailing': false 52 | * })) 53 | * 54 | * // Ensure `batchLog` is invoked once after 1 second of debounced calls. 55 | * const debounced = debounce(batchLog, 250, { 'maxWait': 1000 }) 56 | * const source = new EventSource('/stream') 57 | * jQuery(source).on('message', debounced) 58 | * 59 | * // Cancel the trailing debounced invocation. 60 | * jQuery(window).on('popstate', debounced.cancel) 61 | * 62 | * // Check for pending invocations. 63 | * const status = debounced.pending() ? "Pending..." : "Ready" 64 | */ 65 | function debounce(func, wait, options) { 66 | let lastArgs, 67 | lastThis, 68 | maxWait, 69 | result, 70 | timerId, 71 | lastCallTime 72 | 73 | let lastInvokeTime = 0 74 | let leading = false 75 | let maxing = false 76 | let trailing = true 77 | 78 | // Bypass `requestAnimationFrame` by explicitly setting `wait=0`. 79 | const useRAF = (!wait && wait !== 0 && typeof root.requestAnimationFrame === 'function') 80 | 81 | if (typeof func !== 'function') { 82 | throw new TypeError('Expected a function') 83 | } 84 | wait = +wait || 0 85 | if (isObject(options)) { 86 | leading = !!options.leading 87 | maxing = 'maxWait' in options 88 | maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait 89 | trailing = 'trailing' in options ? !!options.trailing : trailing 90 | } 91 | 92 | function invokeFunc(time) { 93 | const args = lastArgs 94 | const thisArg = lastThis 95 | 96 | lastArgs = lastThis = undefined 97 | lastInvokeTime = time 98 | result = func.apply(thisArg, args) 99 | return result 100 | } 101 | 102 | function startTimer(pendingFunc, wait) { 103 | if (useRAF) { 104 | root.cancelAnimationFrame(timerId) 105 | return root.requestAnimationFrame(pendingFunc) 106 | } 107 | return setTimeout(pendingFunc, wait) 108 | } 109 | 110 | function cancelTimer(id) { 111 | if (useRAF) { 112 | return root.cancelAnimationFrame(id) 113 | } 114 | clearTimeout(id) 115 | } 116 | 117 | function leadingEdge(time) { 118 | // Reset any `maxWait` timer. 119 | lastInvokeTime = time 120 | // Start the timer for the trailing edge. 121 | timerId = startTimer(timerExpired, wait) 122 | // Invoke the leading edge. 123 | return leading ? invokeFunc(time) : result 124 | } 125 | 126 | function remainingWait(time) { 127 | const timeSinceLastCall = time - lastCallTime 128 | const timeSinceLastInvoke = time - lastInvokeTime 129 | const timeWaiting = wait - timeSinceLastCall 130 | 131 | return maxing 132 | ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke) 133 | : timeWaiting 134 | } 135 | 136 | function shouldInvoke(time) { 137 | const timeSinceLastCall = time - lastCallTime 138 | const timeSinceLastInvoke = time - lastInvokeTime 139 | 140 | // Either this is the first call, activity has stopped and we're at the 141 | // trailing edge, the system time has gone backwards and we're treating 142 | // it as the trailing edge, or we've hit the `maxWait` limit. 143 | return (lastCallTime === undefined || (timeSinceLastCall >= wait) || 144 | (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait)) 145 | } 146 | 147 | function timerExpired() { 148 | const time = Date.now() 149 | if (shouldInvoke(time)) { 150 | return trailingEdge(time) 151 | } 152 | // Restart the timer. 153 | timerId = startTimer(timerExpired, remainingWait(time)) 154 | } 155 | 156 | function trailingEdge(time) { 157 | timerId = undefined 158 | 159 | // Only invoke if we have `lastArgs` which means `func` has been 160 | // debounced at least once. 161 | if (trailing && lastArgs) { 162 | return invokeFunc(time) 163 | } 164 | lastArgs = lastThis = undefined 165 | return result 166 | } 167 | 168 | function cancel() { 169 | if (timerId !== undefined) { 170 | cancelTimer(timerId) 171 | } 172 | lastInvokeTime = 0 173 | lastArgs = lastCallTime = lastThis = timerId = undefined 174 | } 175 | 176 | function flush() { 177 | return timerId === undefined ? result : trailingEdge(Date.now()) 178 | } 179 | 180 | function pending() { 181 | return timerId !== undefined 182 | } 183 | 184 | function debounced(...args) { 185 | const time = Date.now() 186 | const isInvoking = shouldInvoke(time) 187 | 188 | lastArgs = args 189 | lastThis = this 190 | lastCallTime = time 191 | 192 | if (isInvoking) { 193 | if (timerId === undefined) { 194 | return leadingEdge(lastCallTime) 195 | } 196 | if (maxing) { 197 | // Handle invocations in a tight loop. 198 | timerId = startTimer(timerExpired, wait) 199 | return invokeFunc(lastCallTime) 200 | } 201 | } 202 | if (timerId === undefined) { 203 | timerId = startTimer(timerExpired, wait) 204 | } 205 | return result 206 | } 207 | debounced.cancel = cancel 208 | debounced.flush = flush 209 | debounced.pending = pending 210 | return debounced 211 | } 212 | 213 | export default debounce 214 | -------------------------------------------------------------------------------- /src/common/modules/lodash/isFunction.js: -------------------------------------------------------------------------------- 1 | import getTag from './.internal/getTag.js' 2 | import isObject from './isObject.js' 3 | 4 | /** 5 | * Checks if `value` is classified as a `Function` object. 6 | * 7 | * @since 0.1.0 8 | * @category Lang 9 | * @param {*} value The value to check. 10 | * @returns {boolean} Returns `true` if `value` is a function, else `false`. 11 | * @example 12 | * 13 | * isFunction(_) 14 | * // => true 15 | * 16 | * isFunction(/abc/) 17 | * // => false 18 | */ 19 | function isFunction(value) { 20 | if (!isObject(value)) { 21 | return false 22 | } 23 | // The use of `Object#toString` avoids issues with the `typeof` operator 24 | // in Safari 9 which returns 'object' for typed arrays and other constructors. 25 | const tag = getTag(value) 26 | return tag == '[object Function]' || tag == '[object AsyncFunction]' || 27 | tag == '[object GeneratorFunction]' || tag == '[object Proxy]' 28 | } 29 | 30 | export default isFunction 31 | -------------------------------------------------------------------------------- /src/common/modules/lodash/isObject.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if `value` is the 3 | * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) 4 | * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) 5 | * 6 | * @since 0.1.0 7 | * @category Lang 8 | * @param {*} value The value to check. 9 | * @returns {boolean} Returns `true` if `value` is an object, else `false`. 10 | * @example 11 | * 12 | * isObject({}) 13 | * // => true 14 | * 15 | * isObject([1, 2, 3]) 16 | * // => true 17 | * 18 | * isObject(Function) 19 | * // => true 20 | * 21 | * isObject(null) 22 | * // => false 23 | */ 24 | function isObject(value) { 25 | const type = typeof value 26 | return value != null && (type == 'object' || type == 'function') 27 | } 28 | 29 | export default isObject 30 | -------------------------------------------------------------------------------- /src/common/modules/lodash/isObjectLike.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if `value` is object-like. A value is object-like if it's not `null` 3 | * and has a `typeof` result of "object". 4 | * 5 | * @since 4.0.0 6 | * @category Lang 7 | * @param {*} value The value to check. 8 | * @returns {boolean} Returns `true` if `value` is object-like, else `false`. 9 | * @example 10 | * 11 | * isObjectLike({}) 12 | * // => true 13 | * 14 | * isObjectLike([1, 2, 3]) 15 | * // => true 16 | * 17 | * isObjectLike(Function) 18 | * // => false 19 | * 20 | * isObjectLike(null) 21 | * // => false 22 | */ 23 | function isObjectLike(value) { 24 | return typeof value == 'object' && value !== null 25 | } 26 | 27 | export default isObjectLike 28 | -------------------------------------------------------------------------------- /src/common/modules/lodash/isPlainObject.js: -------------------------------------------------------------------------------- 1 | import getTag from './.internal/getTag.js' 2 | import isObjectLike from './isObjectLike.js' 3 | 4 | /** 5 | * Checks if `value` is a plain object, that is, an object created by the 6 | * `Object` constructor or one with a `[[Prototype]]` of `null`. 7 | * 8 | * @since 0.8.0 9 | * @category Lang 10 | * @param {*} value The value to check. 11 | * @returns {boolean} Returns `true` if `value` is a plain object, else `false`. 12 | * @example 13 | * 14 | * function Foo() { 15 | * this.a = 1 16 | * } 17 | * 18 | * isPlainObject(new Foo) 19 | * // => false 20 | * 21 | * isPlainObject([1, 2, 3]) 22 | * // => false 23 | * 24 | * isPlainObject({ 'x': 0, 'y': 0 }) 25 | * // => true 26 | * 27 | * isPlainObject(Object.create(null)) 28 | * // => true 29 | */ 30 | function isPlainObject(value) { 31 | if (!isObjectLike(value) || getTag(value) != '[object Object]') { 32 | return false 33 | } 34 | if (Object.getPrototypeOf(value) === null) { 35 | return true 36 | } 37 | let proto = value 38 | while (Object.getPrototypeOf(proto) !== null) { 39 | proto = Object.getPrototypeOf(proto) 40 | } 41 | return Object.getPrototypeOf(value) === proto 42 | } 43 | 44 | export default isPlainObject 45 | -------------------------------------------------------------------------------- /src/common/modules/lodash/isString.js: -------------------------------------------------------------------------------- 1 | import getTag from './.internal/getTag.js' 2 | 3 | /** 4 | * Checks if `value` is classified as a `String` primitive or object. 5 | * 6 | * @since 0.1.0 7 | * @category Lang 8 | * @param {*} value The value to check. 9 | * @returns {boolean} Returns `true` if `value` is a string, else `false`. 10 | * @example 11 | * 12 | * isString('abc') 13 | * // => true 14 | * 15 | * isString(1) 16 | * // => false 17 | */ 18 | function isString(value) { 19 | const type = typeof value 20 | return type == 'string' || (type == 'object' && value != null && !Array.isArray(value) && getTag(value) == '[object String]') 21 | } 22 | 23 | export default isString 24 | -------------------------------------------------------------------------------- /src/common/modules/lodash/throttle.js: -------------------------------------------------------------------------------- 1 | import debounce from './debounce.js' 2 | import isObject from './isObject.js' 3 | 4 | /** 5 | * Creates a throttled function that only invokes `func` at most once per 6 | * every `wait` milliseconds (or once per browser frame). The throttled function 7 | * comes with a `cancel` method to cancel delayed `func` invocations and a 8 | * `flush` method to immediately invoke them. Provide `options` to indicate 9 | * whether `func` should be invoked on the leading and/or trailing edge of the 10 | * `wait` timeout. The `func` is invoked with the last arguments provided to the 11 | * throttled function. Subsequent calls to the throttled function return the 12 | * result of the last `func` invocation. 13 | * 14 | * **Note:** If `leading` and `trailing` options are `true`, `func` is 15 | * invoked on the trailing edge of the timeout only if the throttled function 16 | * is invoked more than once during the `wait` timeout. 17 | * 18 | * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred 19 | * until the next tick, similar to `setTimeout` with a timeout of `0`. 20 | * 21 | * If `wait` is omitted in an environment with `requestAnimationFrame`, `func` 22 | * invocation will be deferred until the next frame is drawn (typically about 23 | * 16ms). 24 | * 25 | * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) 26 | * for details over the differences between `throttle` and `debounce`. 27 | * 28 | * @since 0.1.0 29 | * @category Function 30 | * @param {Function} func The function to throttle. 31 | * @param {number} [wait=0] 32 | * The number of milliseconds to throttle invocations to; if omitted, 33 | * `requestAnimationFrame` is used (if available). 34 | * @param {Object} [options={}] The options object. 35 | * @param {boolean} [options.leading=true] 36 | * Specify invoking on the leading edge of the timeout. 37 | * @param {boolean} [options.trailing=true] 38 | * Specify invoking on the trailing edge of the timeout. 39 | * @returns {Function} Returns the new throttled function. 40 | * @example 41 | * 42 | * // Avoid excessively updating the position while scrolling. 43 | * jQuery(window).on('scroll', throttle(updatePosition, 100)) 44 | * 45 | * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes. 46 | * const throttled = throttle(renewToken, 300000, { 'trailing': false }) 47 | * jQuery(element).on('click', throttled) 48 | * 49 | * // Cancel the trailing throttled invocation. 50 | * jQuery(window).on('popstate', throttled.cancel) 51 | */ 52 | function throttle(func, wait, options) { 53 | let leading = true 54 | let trailing = true 55 | 56 | if (typeof func !== 'function') { 57 | throw new TypeError('Expected a function') 58 | } 59 | if (isObject(options)) { 60 | leading = 'leading' in options ? !!options.leading : leading 61 | trailing = 'trailing' in options ? !!options.trailing : trailing 62 | } 63 | return debounce(func, wait, { 64 | leading, 65 | trailing, 66 | 'maxWait': wait 67 | }) 68 | } 69 | 70 | export default throttle 71 | -------------------------------------------------------------------------------- /src/icons/icon-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon-light.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "DarkModeSwitcher DEV VERSION", 4 | "short_name": "__MSG_extensionNameShort__", 5 | "version": "2.0", 6 | "author": "rugk", 7 | 8 | "description": "__MSG_extensionDescription__", 9 | "homepage_url": "https://github.com/rugk/website-dark-mode-switcher/", 10 | 11 | "browser_action": { 12 | "browser_style": true, 13 | "default_icon": "icons/icon-dark.svg", 14 | "default_title": "__MSG_browserActionButtonTitle__", 15 | "theme_icons": [ 16 | { 17 | "dark": "icons/icon-dark.svg", 18 | "light": "icons/icon-light.svg", 19 | "size": 32 20 | } 21 | ] 22 | }, 23 | 24 | "options_ui": { 25 | "page": "options/options.html", 26 | "browser_style": true 27 | }, 28 | 29 | "background": { 30 | "page": "background/background.html" 31 | }, 32 | 33 | "commands": { 34 | "_execute_browser_action": { 35 | "suggested_key": { 36 | "default": "Ctrl+Shift+B" 37 | }, 38 | "description": "__MSG_commandToggleDarkMode__" 39 | } 40 | }, 41 | 42 | // testing version allows loading unit test libraries from CDNs 43 | "content_security_policy": "default-src 'self'; img-src data:; style-src 'self' https://unpkg.com; script-src 'self' https://unpkg.com", 44 | "icons": { 45 | "16": "icons/icon-dark.svg", 46 | "32": "icons/icon-dark.svg", 47 | "48": "icons/icon-dark.svg", 48 | "96": "icons/icon-dark.svg" 49 | }, 50 | "default_locale": "en", 51 | 52 | "permissions": [ 53 | "storage", 54 | "browserSettings" 55 | ], 56 | 57 | "applications": { 58 | "gecko": { 59 | "id": "dark-mode-website-switcher@rugk.github.io", 60 | "strict_min_version": "95.0" 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/options/fastLoad.js: -------------------------------------------------------------------------------- 1 | import * as EnvironmentalOptions from "/common/modules/AutomaticSettings/EnvironmentalOptions.js"; 2 | 3 | // hide not mobile compatible settings 4 | EnvironmentalOptions.init(); 5 | -------------------------------------------------------------------------------- /src/options/modules/CustomOptionTriggers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This modules contains the custom triggers for some options that are added. 3 | * 4 | * @module modules/CustomOptionTriggers 5 | */ 6 | 7 | import * as AutomaticSettings from "/common/modules/AutomaticSettings/AutomaticSettings.js"; 8 | import * as DarkModeLogic from "/common/modules/DarkModeLogic.js"; 9 | 10 | /** 11 | * Applies the dark mode setting. 12 | * 13 | * @function 14 | * @private 15 | * @param {string} optionValue 16 | * @param {string} [option] 17 | * @returns {Promise} 18 | */ 19 | function applyPrefersColorSchemeOverride(optionValue) { 20 | return DarkModeLogic.applySetting(optionValue); 21 | } 22 | 23 | /** 24 | * Apply the setting and show it as the used one. 25 | * 26 | * @param {string} currentSetting The setting to show as the currently selected one. 27 | * @returns {void} 28 | */ 29 | function applySetting(currentSetting) { 30 | const newColorSettingInput = document.getElementById("prefersColorSchemeOverride"); 31 | newColorSettingInput.value = currentSetting; 32 | } 33 | 34 | /** 35 | * Binds the triggers. 36 | * 37 | * This is basically the "init" method. 38 | * 39 | * @function 40 | * @returns {void} 41 | */ 42 | export function registerTrigger() { 43 | AutomaticSettings.Trigger.registerSave("prefersColorSchemeOverride", applyPrefersColorSchemeOverride); 44 | 45 | // handle loading of options correctly 46 | AutomaticSettings.Trigger.registerAfterLoad(AutomaticSettings.Trigger.RUN_ALL_SAVE_TRIGGER); 47 | 48 | DarkModeLogic.registerChangeListener(applySetting); 49 | DarkModeLogic.getCurrentState().then((currentSetting) => { 50 | applySetting(currentSetting); 51 | }); 52 | } 53 | -------------------------------------------------------------------------------- /src/options/options.css: -------------------------------------------------------------------------------- 1 | @import "/common/modules/AutomaticSettings/css/photonOptions.css"; 2 | 3 | .alignoption { 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | margin-top: 8px; 8 | } 9 | -------------------------------------------------------------------------------- /src/options/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 24 | 31 | 38 | 45 |
46 |
47 |
48 |

Instructions

49 | 50 |

51 | 52 | To enable and disable the add-on and turn a website into dark or the default settings, please click on the toolbar icon of this extension or use the hot key (configurable in the extension shortcut configuration of Firefox). Note that in some cases you may need to reload the site. 53 |
54 | 55 | Please be aware that this extension only works on websites that support the dark styling. 56 | 57 |

58 |
59 | 60 |
61 |

Behaviour

62 | 63 |

64 |

65 | 66 | The following options allow you to change the current style and the two states the add-on button toggles when you click on it. 67 | 68 |
69 |

70 |
    71 | 80 |
  • 81 | 82 |
  • 83 |
  • 84 | 91 |
  • 92 | 93 |
  • 94 |
    95 | 96 | 103 |
    104 | 105 |
    106 | This setting will be considered as being the dark mode when toggeling the mode. 107 |
    108 |
  • 109 | 110 |
  • 111 |
    112 | 113 | 120 |
    121 | 122 |
    123 | This setting will be considered as being the light mode when toggeling the mode. 124 |
    125 |
  • 126 |
127 |
128 |
129 |

130 | 131 |
132 |
133 | 134 |

135 | 136 | This add-on has been translated into English by 137 | somone. 138 | 139 | 140 | Also thanks to 141 | all other contributors. 142 | 143 |

144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /src/options/options.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Starter module for addon settings site. 3 | * 4 | * @requires modules/OptionHandler 5 | */ 6 | 7 | import * as AddonSettings from "/common/modules/AddonSettings/AddonSettings.js"; 8 | import * as AutomaticSettings from "/common/modules/AutomaticSettings/AutomaticSettings.js"; 9 | import * as CustomOptionTriggers from "./modules/CustomOptionTriggers.js"; 10 | 11 | // init modules 12 | CustomOptionTriggers.registerTrigger(); 13 | AutomaticSettings.setDefaultOptionProvider(AddonSettings.getDefaultValue); 14 | AutomaticSettings.init(); 15 | 16 | -------------------------------------------------------------------------------- /src/tests/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | // EsLint config for mocha tests 3 | // requires EsLint mocha plugin: https://github.com/lo1tuma/eslint-plugin-mocha 4 | "root": false, 5 | "env": { 6 | "browser": true, 7 | "webextensions": true, 8 | "es6": true, 9 | "mocha": true 10 | }, 11 | "plugins": [ 12 | "mocha" 13 | ], 14 | 15 | "rules": { 16 | // arrows are not allowed 17 | "prefer-arrow-callback": 0, 18 | // "mocha/prefer-arrow-callback": 2, 19 | "mocha/no-mocha-arrows": 2, 20 | 21 | // style 22 | "mocha/max-top-level-suites": 1, 23 | "mocha/valid-suite-description": ["warn", "^([a-zA-Z.]+\\(.*\\)|[[A-Z0-9_]+|[\\w\\s:]+)( – .*)?$", ["describe", "context", "suite"]], // function name or function call or header with : 24 | // RegEx:for "valid-suite-description": https://regex101.com/r/MKJw59/7 25 | "mocha/valid-test-description": ["warn", "^((?!should).)*$", ["it", "specify", "test"]], // must not contain "should", but should be third-person present tense 26 | // RegEx for "valid-test-description": https://regex101.com/r/9gYH8f/1, thanks to https://stackoverflow.com/a/406408/5008962 27 | 28 | // hooks 29 | "mocha/no-hooks-for-single-case": 1, 30 | // "mocha/no-hooks": 2, 31 | 32 | // assyncronous tests 33 | // "mocha/no-synchronous-tests": 2, // only 34 | 35 | // common errors when writing tests 36 | "mocha/no-global-tests": 2, 37 | "mocha/handle-done-callback": 2, 38 | "mocha/no-identical-title": 2, 39 | "mocha/no-nested-tests": 2, 40 | "mocha/no-return-and-callback": 2, 41 | "mocha/no-setup-in-describe": 2, 42 | "mocha/no-sibling-hooks": 2, 43 | "mocha/no-top-level-hooks": 2, 44 | 45 | // warnings to remind devs that there is something to do (debugging/…) 46 | "mocha/no-skipped-tests": 1, 47 | "mocha/no-exclusive-tests": 1, 48 | // "mocha/no-pending-tests": 1, 49 | 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/tests/index.css: -------------------------------------------------------------------------------- 1 | #testArea { 2 | visibility: hidden; 3 | } 4 | -------------------------------------------------------------------------------- /src/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Dark Mode Website Switcher ‒ Mocha Tests 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/tests/module.test.js: -------------------------------------------------------------------------------- 1 | import "https://unpkg.com/mocha@5.2.0/mocha.js"; /* globals mocha */ 2 | import "https://unpkg.com/chai@4.1.2/chai.js"; /* globals chai */ 3 | 4 | import * as Module from "/common/modules/Module.js"; 5 | 6 | describe("common module: Module", function () { 7 | describe("CONSTANT", function () { 8 | it("is there", function () { 9 | chai.assert.exists(Module.CONSTANT); 10 | chai.assert.isNotEmpty(Module.CONSTANT); 11 | }); 12 | 13 | it("is frozen", function () { 14 | chai.assert.isFrozen(Module.CONSTANT); 15 | }); 16 | }); 17 | 18 | describe("function()", function () { 19 | it("does something useful", function () { 20 | chai.assert.strictEqual(Module.function(1, 2), 3); 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/tests/run.js: -------------------------------------------------------------------------------- 1 | import "https://unpkg.com/mocha@5.2.0/mocha.js"; /* globals mocha */ 2 | 3 | /* tests */ 4 | import "./module.test.js"; 5 | 6 | mocha.checkLeaks(); 7 | mocha.run(); 8 | -------------------------------------------------------------------------------- /src/tests/setup.js: -------------------------------------------------------------------------------- 1 | import "https://unpkg.com/mocha@5.2.0/mocha.js"; /* globals mocha */ 2 | 3 | mocha.setup("bdd"); 4 | -------------------------------------------------------------------------------- /tests: -------------------------------------------------------------------------------- 1 | src/tests --------------------------------------------------------------------------------