├── .eslintignore ├── src ├── utils │ ├── glob │ │ ├── index.js │ │ ├── src │ │ │ ├── index.js │ │ │ └── convert.js │ │ └── test │ │ │ └── index.js │ ├── i18n │ │ ├── index.js │ │ ├── src │ │ │ └── index.js │ │ └── test │ │ │ └── index.js │ ├── settings │ │ ├── index.js │ │ ├── src │ │ │ └── index.js │ │ └── test │ │ │ └── index.js │ ├── test │ │ ├── index.js │ │ ├── src │ │ │ └── index.js │ │ └── test │ │ │ └── index.js │ └── analytics │ │ ├── index.js │ │ ├── test │ │ └── index.js │ │ └── src │ │ └── index.js ├── components │ ├── app │ │ ├── index.js │ │ ├── src │ │ │ ├── index.scss │ │ │ └── index.js │ │ └── test │ │ │ └── index.js │ ├── blocked │ │ ├── index.js │ │ ├── src │ │ │ ├── index.scss │ │ │ └── index.js │ │ └── test │ │ │ └── index.js │ ├── layout │ │ ├── index.js │ │ ├── test │ │ │ └── index.js │ │ └── src │ │ │ ├── index.scss │ │ │ └── index.js │ ├── options │ │ ├── index.js │ │ ├── test │ │ │ └── index.js │ │ └── src │ │ │ ├── index.scss │ │ │ └── index.js │ └── about-dialog │ │ ├── index.js │ │ ├── src │ │ ├── index.scss │ │ └── index.js │ │ └── test │ │ └── index.js ├── assets │ ├── icon.ai │ ├── promo.ai │ ├── icon_16.png │ ├── icon_19.png │ ├── icon_32.png │ ├── icon_38.png │ ├── icon_48.png │ ├── icon_64.png │ ├── icon_96.png │ ├── icon_128.png │ ├── icon_256.png │ ├── icon_512.png │ ├── promo_440.png │ ├── promo_920.png │ ├── promo_1400.png │ ├── screenshot_chrome_1.png │ ├── screenshot_chrome_2.png │ ├── screenshot_opera_1.png │ ├── screenshot_opera_2.png │ ├── screenshot_small_1.png │ ├── screenshot_small_2.png │ └── icon.svg ├── background.html ├── options.html ├── optionsalt.html ├── blocked.html ├── platform │ ├── web │ │ └── utils │ │ │ └── platform.js │ ├── opera │ │ ├── manifest.json │ │ └── utils │ │ │ └── platform.js │ ├── chrome │ │ ├── manifest.json │ │ └── utils │ │ │ └── platform.js │ └── firefox │ │ ├── manifest.json │ │ └── utils │ │ └── platform.js ├── options.js ├── global.scss ├── blocked.js ├── optionsalt.js ├── _locales │ ├── en │ │ └── messages.json │ └── es │ │ └── messages.json └── background.js ├── .gitignore ├── loaders ├── disclosure.js └── template.js ├── webpack.config.chrome.js ├── webpack.config.opera.js ├── webpack.config.firefox.js ├── webpack.config.opera.min.js ├── webpack.config.chrome.min.js ├── webpack.config.firefox.min.js ├── .babelrc ├── bower.json ├── .eslintrc ├── tests.js ├── .travis.yml ├── karma.conf.js ├── README.md ├── package.json ├── webpack.config.factory.js ├── .csscomb.json └── LICENSE /.eslintignore: -------------------------------------------------------------------------------- 1 | /coverage/ 2 | /dist/ 3 | -------------------------------------------------------------------------------- /src/utils/glob/index.js: -------------------------------------------------------------------------------- 1 | export * from './src' 2 | -------------------------------------------------------------------------------- /src/utils/i18n/index.js: -------------------------------------------------------------------------------- 1 | export * from './src' 2 | -------------------------------------------------------------------------------- /src/utils/settings/index.js: -------------------------------------------------------------------------------- 1 | export * from './src' 2 | -------------------------------------------------------------------------------- /src/utils/test/index.js: -------------------------------------------------------------------------------- 1 | export * from './src' 2 | -------------------------------------------------------------------------------- /src/utils/analytics/index.js: -------------------------------------------------------------------------------- 1 | export * from './src' 2 | -------------------------------------------------------------------------------- /src/components/app/index.js: -------------------------------------------------------------------------------- 1 | export default from './src' 2 | -------------------------------------------------------------------------------- /src/components/blocked/index.js: -------------------------------------------------------------------------------- 1 | export default from './src' 2 | -------------------------------------------------------------------------------- /src/components/layout/index.js: -------------------------------------------------------------------------------- 1 | export default from './src' 2 | -------------------------------------------------------------------------------- /src/components/options/index.js: -------------------------------------------------------------------------------- 1 | export default from './src' 2 | -------------------------------------------------------------------------------- /src/components/about-dialog/index.js: -------------------------------------------------------------------------------- 1 | export default from './src' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /coverage/ 3 | /dist/ 4 | /node_modules/ 5 | -------------------------------------------------------------------------------- /src/utils/analytics/test/index.js: -------------------------------------------------------------------------------- 1 | describe('Analytics', function () { 2 | }) 3 | -------------------------------------------------------------------------------- /src/assets/icon.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unindented/allowlist-manager/HEAD/src/assets/icon.ai -------------------------------------------------------------------------------- /src/assets/promo.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unindented/allowlist-manager/HEAD/src/assets/promo.ai -------------------------------------------------------------------------------- /src/assets/icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unindented/allowlist-manager/HEAD/src/assets/icon_16.png -------------------------------------------------------------------------------- /src/assets/icon_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unindented/allowlist-manager/HEAD/src/assets/icon_19.png -------------------------------------------------------------------------------- /src/assets/icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unindented/allowlist-manager/HEAD/src/assets/icon_32.png -------------------------------------------------------------------------------- /src/assets/icon_38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unindented/allowlist-manager/HEAD/src/assets/icon_38.png -------------------------------------------------------------------------------- /src/assets/icon_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unindented/allowlist-manager/HEAD/src/assets/icon_48.png -------------------------------------------------------------------------------- /src/assets/icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unindented/allowlist-manager/HEAD/src/assets/icon_64.png -------------------------------------------------------------------------------- /src/assets/icon_96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unindented/allowlist-manager/HEAD/src/assets/icon_96.png -------------------------------------------------------------------------------- /src/assets/icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unindented/allowlist-manager/HEAD/src/assets/icon_128.png -------------------------------------------------------------------------------- /src/assets/icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unindented/allowlist-manager/HEAD/src/assets/icon_256.png -------------------------------------------------------------------------------- /src/assets/icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unindented/allowlist-manager/HEAD/src/assets/icon_512.png -------------------------------------------------------------------------------- /src/assets/promo_440.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unindented/allowlist-manager/HEAD/src/assets/promo_440.png -------------------------------------------------------------------------------- /src/assets/promo_920.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unindented/allowlist-manager/HEAD/src/assets/promo_920.png -------------------------------------------------------------------------------- /src/assets/promo_1400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unindented/allowlist-manager/HEAD/src/assets/promo_1400.png -------------------------------------------------------------------------------- /src/assets/screenshot_chrome_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unindented/allowlist-manager/HEAD/src/assets/screenshot_chrome_1.png -------------------------------------------------------------------------------- /src/assets/screenshot_chrome_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unindented/allowlist-manager/HEAD/src/assets/screenshot_chrome_2.png -------------------------------------------------------------------------------- /src/assets/screenshot_opera_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unindented/allowlist-manager/HEAD/src/assets/screenshot_opera_1.png -------------------------------------------------------------------------------- /src/assets/screenshot_opera_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unindented/allowlist-manager/HEAD/src/assets/screenshot_opera_2.png -------------------------------------------------------------------------------- /src/assets/screenshot_small_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unindented/allowlist-manager/HEAD/src/assets/screenshot_small_1.png -------------------------------------------------------------------------------- /src/assets/screenshot_small_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unindented/allowlist-manager/HEAD/src/assets/screenshot_small_2.png -------------------------------------------------------------------------------- /src/components/app/src/index.scss: -------------------------------------------------------------------------------- 1 | @import "~mdi/iconfont/material-icons.css"; 2 | @import "~mdl/src/material-design-lite.scss"; 3 | -------------------------------------------------------------------------------- /loaders/disclosure.js: -------------------------------------------------------------------------------- 1 | module.exports = function (source) { 2 | this.cacheable() 3 | 4 | return source.toString().replace(/\(\)\n$/g, '.call(window)\n') 5 | } 6 | -------------------------------------------------------------------------------- /webpack.config.chrome.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var webpackConfig = require('./webpack.config.factory') 4 | 5 | module.exports = webpackConfig({platform: 'chrome'}) 6 | -------------------------------------------------------------------------------- /webpack.config.opera.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var webpackConfig = require('./webpack.config.factory') 4 | 5 | module.exports = webpackConfig({platform: 'opera'}) 6 | -------------------------------------------------------------------------------- /webpack.config.firefox.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var webpackConfig = require('./webpack.config.factory') 4 | 5 | module.exports = webpackConfig({platform: 'firefox'}) 6 | -------------------------------------------------------------------------------- /src/background.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /webpack.config.opera.min.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var webpackConfig = require('./webpack.config.factory') 4 | 5 | module.exports = webpackConfig({platform: 'opera', environment: 'production'}) 6 | -------------------------------------------------------------------------------- /webpack.config.chrome.min.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var webpackConfig = require('./webpack.config.factory') 4 | 5 | module.exports = webpackConfig({platform: 'chrome', environment: 'production'}) 6 | -------------------------------------------------------------------------------- /webpack.config.firefox.min.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var webpackConfig = require('./webpack.config.factory') 4 | 5 | module.exports = webpackConfig({platform: 'firefox', environment: 'production'}) 6 | -------------------------------------------------------------------------------- /loaders/template.js: -------------------------------------------------------------------------------- 1 | var template = require('lodash/template') 2 | var pkg = require('../package.json') 3 | 4 | module.exports = function (source) { 5 | this.cacheable() 6 | 7 | return template(source.toString())(pkg) 8 | } 9 | -------------------------------------------------------------------------------- /src/components/about-dialog/src/index.scss: -------------------------------------------------------------------------------- 1 | .app-about-dialog.mdl-dialog { 2 | width: 384px; 3 | color: $text-color-primary; 4 | } 5 | 6 | .app-about-dialog .mdl-dialog__title { 7 | @include typo-title($usePreferred: false); 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/test/src/index.js: -------------------------------------------------------------------------------- 1 | import {findDOMNode} from 'react-dom' 2 | import {renderIntoDocument, Simulate} from 'react-addons-test-utils' 3 | 4 | export {Simulate} 5 | 6 | export function render (element) { 7 | return findDOMNode(renderIntoDocument(element)) 8 | } 9 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "react" 5 | ], 6 | "plugins": [ 7 | ["transform-class-properties"], 8 | ["transform-decorators-legacy"], 9 | ["transform-export-extensions"], 10 | ["transform-object-rest-spread"] 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "whitelist-manager", 3 | "description": "Browser extension that automatically blocks all pages from any website that is not in your list of allowed websites.", 4 | "license": "GPL-3.0", 5 | "private": true, 6 | "dependencies": { 7 | "chrome-platform-analytics": "=1.3.0" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ${productName} 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/utils/test/test/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {render} from 'utils/test' 3 | 4 | describe('Test', function () { 5 | describe('#render', function () { 6 | it('renders a component into a dettached node', function () { 7 | expect(render(
)).toEqual(jasmine.any(Node)) 8 | }) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /src/optionsalt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ${productName} 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/blocked.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ${productName} 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["standard", "standard-react"], 3 | "parser": "babel-eslint", 4 | "rules": { 5 | "no-unused-vars": [2, { 6 | "args": "after-used", 7 | "argsIgnorePattern": "^_", 8 | "varsIgnorePattern": "^_" 9 | }] 10 | }, 11 | "env": { 12 | "browser": true, 13 | "jasmine": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/assets/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/i18n/src/index.js: -------------------------------------------------------------------------------- 1 | import {getI18nMessage} from 'utils/platform' 2 | 3 | export function t () { 4 | const args = Array.prototype.map.call(arguments, String) 5 | return getI18nMessage(args[0], args.slice(1)) 6 | } 7 | 8 | export function dir (locale) { 9 | const main = locale.split('-')[0] 10 | return ['ar', 'he', 'fa', 'ps', 'ur'].indexOf(main) !== -1 ? 'rtl' : 'ltr' 11 | } 12 | -------------------------------------------------------------------------------- /src/components/blocked/src/index.scss: -------------------------------------------------------------------------------- 1 | .app-blocked { 2 | text-align: center; 3 | } 4 | 5 | .app-blocked__info > .material-icons { 6 | display: block; 7 | margin: 12px auto; 8 | color: $icon-color; 9 | 10 | @include typo-display-4($usePreferred: false); 11 | } 12 | 13 | .app-blocked__href { 14 | word-break: break-all; 15 | border: 1px solid $border-color; 16 | border-radius: 2px; 17 | } 18 | -------------------------------------------------------------------------------- /src/components/options/test/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Options from 'components/options' 3 | import {render} from 'utils/test' 4 | 5 | describe('Options', function () { 6 | beforeEach(function () { 7 | this.element = render() 8 | }) 9 | 10 | it('renders with the correct class name', function () { 11 | expect(this.element).toHaveClass('app-options') 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /tests.js: -------------------------------------------------------------------------------- 1 | import 'es6-shim' 2 | import * as jqueryMatchers from 'jasmine-jquery-matchers' 3 | 4 | beforeEach(function () { 5 | jasmine.addMatchers(jqueryMatchers) 6 | }) 7 | 8 | const requireAll = (context) => { 9 | context.keys().forEach(context) 10 | } 11 | 12 | requireAll(require.context('./src/components', true, /\/test\/.*\.jsx?$/)) 13 | requireAll(require.context('./src/utils', true, /\/test\/.*\.jsx?$/)) 14 | -------------------------------------------------------------------------------- /src/components/blocked/test/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Blocked from 'components/blocked' 3 | import {render} from 'utils/test' 4 | 5 | describe('Blocked', function () { 6 | beforeEach(function () { 7 | this.element = render() 8 | }) 9 | 10 | it('renders with the correct class name', function () { 11 | expect(this.element).toHaveClass('app-blocked') 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6' 4 | 5 | env: 6 | - CXX=g++-4.8 7 | 8 | addons: 9 | apt: 10 | sources: 11 | - ubuntu-toolchain-r-test 12 | packages: 13 | - g++-4.8 14 | 15 | script: 16 | - 'npm test' 17 | 18 | after_script: 19 | - 'find ./coverage -name lcov.info -exec cat {} \; | ./node_modules/.bin/coveralls' 20 | - 'npm run build' 21 | - 'sha256sum ./dist/chrome/*' 22 | - 'sha256sum ./dist/firefox/*' 23 | - 'sha256sum ./dist/opera/*' 24 | -------------------------------------------------------------------------------- /src/components/layout/test/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Layout from 'components/layout' 3 | import {render} from 'utils/test' 4 | 5 | describe('Layout', function () { 6 | beforeEach(function () { 7 | const location = {query: {details: 'eyJocmVmIjoiaHR0cHM6Ly91bmluZGVudGVkLm9yZy8iLCJob3N0bmFtZSI6InVuaW5kZW50ZWQub3JnIn0='}} 8 | this.element = render() 9 | }) 10 | 11 | it('renders with the correct class name', function () { 12 | expect(this.element).toHaveClass('app-layout') 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /src/components/about-dialog/test/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import AboutDialog from 'components/about-dialog' 3 | import {render} from 'utils/test' 4 | 5 | describe('AboutDialog', function () { 6 | beforeEach(function () { 7 | this.element = render() 8 | }) 9 | 10 | it('renders with the correct tag name', function () { 11 | expect(this.element).toHaveTag('dialog') 12 | }) 13 | 14 | it('renders with the correct class name', function () { 15 | expect(this.element).toHaveClass('app-about-dialog') 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /src/components/app/test/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {createMemoryHistory} from 'react-router' 3 | import App from 'components/app' 4 | import {render} from 'utils/test' 5 | 6 | describe('App', function () { 7 | beforeEach(function () { 8 | const history = createMemoryHistory('/blocked.html?details=eyJocmVmIjoiaHR0cHM6Ly91bmluZGVudGVkLm9yZy8iLCJob3N0bmFtZSI6InVuaW5kZW50ZWQub3JnIn0=') 9 | this.element = render() 10 | }) 11 | 12 | it('renders', function () { 13 | expect(this.element).toHaveTag('div') 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /src/utils/i18n/test/index.js: -------------------------------------------------------------------------------- 1 | import {t, dir} from 'utils/i18n' 2 | 3 | describe('I18n', function () { 4 | describe('t', function () { 5 | it('gets the corresponding translation', function () { 6 | expect(t('foo', 'bar', 42)).toBe('foo') 7 | }) 8 | }) 9 | 10 | describe('dir', function () { 11 | it('returns `rtl` for right-to-left languages', function () { 12 | expect(dir('he-IL')).toBe('rtl') 13 | }) 14 | 15 | it('returns `ltr` for left-to-right languages', function () { 16 | expect(dir('es-ES')).toBe('ltr') 17 | }) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /src/platform/web/utils/platform.js: -------------------------------------------------------------------------------- 1 | export function getExtensionManifest () { 2 | } 3 | 4 | export function getExtensionUrl () { 5 | } 6 | 7 | export function getI18nMessage (msg) { 8 | return msg 9 | } 10 | 11 | export function getItems (defaults, callback) { 12 | callback(defaults) 13 | } 14 | 15 | export function setItems (items, callback) { 16 | callback() 17 | } 18 | 19 | export function onChangeItems (callback) { 20 | callback() 21 | } 22 | 23 | export function onBeforeRequest (urls, callback) { 24 | callback() 25 | } 26 | 27 | export function updateTab () { 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/settings/src/index.js: -------------------------------------------------------------------------------- 1 | import {getItems, setItems, onChangeItems} from 'utils/platform' 2 | 3 | const defaults = { 4 | blockPages: false, 5 | blockOthers: false, 6 | whitelist: ['bing.*', 'google.*', 'paypal.me', 'wikimedia.org', 'wikipedia.org'] 7 | } 8 | 9 | export function defaultSettings () { 10 | return defaults 11 | } 12 | 13 | export function loadSettings (callback) { 14 | getItems(defaultSettings(), callback) 15 | } 16 | 17 | export function saveSettings (settings, callback) { 18 | setItems(settings, callback) 19 | } 20 | 21 | export function onChangeSettings (callback) { 22 | onChangeItems(callback) 23 | } 24 | -------------------------------------------------------------------------------- /src/utils/settings/test/index.js: -------------------------------------------------------------------------------- 1 | import {loadSettings, saveSettings} from 'utils/settings' 2 | 3 | describe('Settings', function () { 4 | describe('loadSettings', function () { 5 | it('invokes callback with settings', function (done) { 6 | loadSettings(function (settings) { 7 | expect(settings).toEqual({ 8 | blockPages: false, 9 | blockOthers: false, 10 | whitelist: jasmine.any(Array) 11 | }) 12 | done() 13 | }) 14 | }) 15 | }) 16 | 17 | describe('saveSettings', function () { 18 | it('invokes callback', function (done) { 19 | saveSettings({blockPages: true}, done) 20 | }) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /src/options.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {render} from 'react-dom' 3 | import Options from 'components/options' 4 | import {trackView, trackTiming, trackException} from 'utils/analytics' 5 | 6 | import _template from './options.html' 7 | 8 | const init = () => { 9 | render(, document.querySelector('#container')) 10 | 11 | trackView() 12 | trackTiming('Options', 'Load', Date.now() - window.performance.timing.navigationStart) 13 | } 14 | 15 | const error = (e) => { 16 | trackException(e.error) 17 | } 18 | 19 | /* ************************************************************************** */ 20 | 21 | window.addEventListener('load', init, true) 22 | window.addEventListener('error', error, true) 23 | -------------------------------------------------------------------------------- /src/global.scss: -------------------------------------------------------------------------------- 1 | @import "~mdl/src/color-definitions"; 2 | @import "~mdl/src/functions"; 3 | @import "~mdl/src/mixins"; 4 | 5 | $color-primary: $palette-deep-orange-500; 6 | $color-primary-dark: $palette-deep-orange-700; 7 | $color-primary-light: $palette-deep-orange-50; 8 | $color-accent: $palette-blue-grey-500; 9 | $color-primary-contrast: $color-dark-contrast; 10 | $color-accent-contrast: $color-dark-contrast; 11 | 12 | @import "~mdl/src/variables"; 13 | 14 | $layout-background-color: unquote("rgb(#{$color-primary-light})"); 15 | $card-supporting-text-text-color: unquote("rgba(#{$color-black}, 0.6)"); 16 | $border-color: unquote("rgba(#{$color-black}, 0.12)"); 17 | $icon-color: unquote("rgb(#{$color-primary})"); 18 | $error-text-color: unquote("rgb(#{$palette-red-A700})"); 19 | -------------------------------------------------------------------------------- /src/blocked.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {render} from 'react-dom' 3 | import {browserHistory} from 'react-router' 4 | import App from 'components/app' 5 | import {trackView, trackTiming, trackException} from 'utils/analytics' 6 | 7 | import _template from './blocked.html' 8 | 9 | const init = () => { 10 | render(, document.querySelector('#container')) 11 | 12 | trackView() 13 | trackTiming('Blocked', 'Load', Date.now() - window.performance.timing.navigationStart) 14 | } 15 | 16 | const error = (e) => { 17 | trackException(e.error) 18 | } 19 | 20 | /* ************************************************************************** */ 21 | 22 | window.addEventListener('load', init, true) 23 | window.addEventListener('error', error, true) 24 | -------------------------------------------------------------------------------- /src/optionsalt.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {render} from 'react-dom' 3 | import {browserHistory} from 'react-router' 4 | import App from 'components/app' 5 | import {trackView, trackTiming, trackException} from 'utils/analytics' 6 | 7 | import _template from './optionsalt.html' 8 | 9 | const init = () => { 10 | render(, document.querySelector('#container')) 11 | 12 | trackView() 13 | trackTiming('Options (Alt)', 'Load', Date.now() - window.performance.timing.navigationStart) 14 | } 15 | 16 | const error = (e) => { 17 | trackException(e.error) 18 | } 19 | 20 | /* ************************************************************************** */ 21 | 22 | window.addEventListener('load', init, true) 23 | window.addEventListener('error', error, true) 24 | -------------------------------------------------------------------------------- /src/components/app/src/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component, PropTypes} from 'react' 2 | import {Router, Route} from 'react-router' 3 | import Layout from 'components/layout' 4 | import Blocked from 'components/blocked' 5 | import Options from 'components/options' 6 | 7 | import _styles from './index.scss' 8 | 9 | export default class App extends Component { 10 | static propTypes = { 11 | history: PropTypes.any.isRequired 12 | } 13 | 14 | render () { 15 | const {history} = this.props 16 | 17 | return ( 18 | 19 | 20 | 21 | 22 | 23 | 24 | ) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/options/src/index.scss: -------------------------------------------------------------------------------- 1 | .app-options__option { 2 | display: inline-flex; 3 | padding-top: 7px; 4 | padding-bottom: 7px; 5 | } 6 | 7 | .app-options__option-hidden { 8 | position: absolute; 9 | width: 1px; 10 | height: 1px; 11 | padding: 0; 12 | margin: -1px; 13 | overflow: hidden; 14 | clip: rect(0 0 0 0); 15 | border: 0; 16 | } 17 | 18 | .app-options__label { 19 | display: block; 20 | margin-right: .6em; 21 | margin-left: .6em; 22 | } 23 | 24 | .app-options__textarea { 25 | display: block; 26 | width: 100%; 27 | height: 300px; 28 | box-sizing: border-box; 29 | padding: 7px; 30 | margin-top: 7px; 31 | margin-bottom: 7px; 32 | font-size: inherit; 33 | color: inherit; 34 | resize: none; 35 | border: 1px solid $border-color; 36 | border-radius: 2px; 37 | } 38 | -------------------------------------------------------------------------------- /src/platform/opera/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "default_locale": "en", 4 | "name": "__MSG_ext_name__", 5 | "description": "__MSG_ext_description__", 6 | "version": "${version}", 7 | "minimum_opera_version": "35.0", 8 | "icons": { 9 | "16": "assets/icon_16.png", 10 | "19": "assets/icon_19.png", 11 | "32": "assets/icon_32.png", 12 | "38": "assets/icon_38.png", 13 | "128": "assets/icon_128.png", 14 | "256": "assets/icon_256.png", 15 | "512": "assets/icon_512.png" 16 | }, 17 | "background": { 18 | "page": "background.html" 19 | }, 20 | "options_ui": { 21 | "page": "optionsalt.html" 22 | }, 23 | "permissions": [ 24 | "storage", 25 | "tabs", 26 | "webRequest", 27 | "webRequestBlocking", 28 | "http://*/*", 29 | "https://*/*" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /src/platform/chrome/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "default_locale": "en", 4 | "name": "__MSG_ext_name__", 5 | "description": "__MSG_ext_description__", 6 | "version": "${version}", 7 | "minimum_chrome_version": "45.0", 8 | "icons": { 9 | "16": "assets/icon_16.png", 10 | "19": "assets/icon_19.png", 11 | "32": "assets/icon_32.png", 12 | "38": "assets/icon_38.png", 13 | "128": "assets/icon_128.png", 14 | "256": "assets/icon_256.png", 15 | "512": "assets/icon_512.png" 16 | }, 17 | "background": { 18 | "page": "background.html" 19 | }, 20 | "options_ui": { 21 | "page": "options.html", 22 | "chrome_style": true 23 | }, 24 | "permissions": [ 25 | "storage", 26 | "tabs", 27 | "webRequest", 28 | "webRequestBlocking", 29 | "http://*/*", 30 | "https://*/*" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /src/components/blocked/src/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component, PropTypes} from 'react' 2 | import {Icon} from 'react-mdl' 3 | import {t} from 'utils/i18n' 4 | 5 | import _styles from './index.scss' 6 | 7 | export default class Blocked extends Component { 8 | static propTypes = { 9 | href: PropTypes.string.isRequired, 10 | hostname: PropTypes.string.isRequired 11 | } 12 | 13 | render () { 14 | const {href, hostname} = this.props 15 | 16 | return ( 17 |
18 |

19 | 20 | {t('msg_blocked_page_label')} 21 |

22 |

23 | {href} 24 |

25 |

26 | {t('msg_blocked_instructions_label', hostname)} 27 |

28 |
29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/platform/firefox/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "default_locale": "en", 4 | "name": "__MSG_ext_name__", 5 | "description": "__MSG_ext_description__", 6 | "version": "${version}", 7 | "applications": { 8 | "gecko": { 9 | "id": "${name}@mozilla.org", 10 | "strict_min_version": "45.0" 11 | } 12 | }, 13 | "icons": { 14 | "16": "assets/icon_16.png", 15 | "19": "assets/icon_19.png", 16 | "32": "assets/icon_32.png", 17 | "38": "assets/icon_38.png", 18 | "128": "assets/icon_128.png", 19 | "256": "assets/icon_256.png", 20 | "512": "assets/icon_512.png" 21 | }, 22 | "background": { 23 | "page": "background.html" 24 | }, 25 | "options_ui": { 26 | "page": "optionsalt.html" 27 | }, 28 | "permissions": [ 29 | "storage", 30 | "tabs", 31 | "webRequest", 32 | "webRequestBlocking", 33 | "http://*/*", 34 | "https://*/*" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /src/components/layout/src/index.scss: -------------------------------------------------------------------------------- 1 | .app-layout { 2 | position: absolute; 3 | width: 100%; 4 | height: 100%; 5 | } 6 | 7 | .app-layout__container { 8 | position: relative; 9 | display: flex; 10 | width: 100%; 11 | height: 100%; 12 | overflow-x: hidden; 13 | overflow-y: auto; 14 | flex-direction: column; 15 | background-color: $layout-background-color; 16 | } 17 | 18 | .noheader .app-layout__container .mdl-layout__header { 19 | display: none; 20 | } 21 | 22 | .app-layout__container .mdl-layout-title .material-icons { 23 | position: relative; 24 | top: 2px; 25 | margin-right: 8px; 26 | font-size: inherit; 27 | } 28 | 29 | .app-layout .mdl-layout__content { 30 | padding-top: 48px; 31 | } 32 | 33 | .app-layout .mdl-card { 34 | width: 480px; 35 | min-height: 0; 36 | margin: 0 auto 48px; 37 | overflow: visible; 38 | } 39 | 40 | .app-layout .mdl-card > .mdl-card__supporting-text { 41 | width: 100%; 42 | box-sizing: border-box; 43 | } 44 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var webpackConfig = require('./webpack.config.factory') 4 | 5 | module.exports = function (config) { 6 | var coverage = config.singleRun 7 | 8 | config.set({ 9 | webpackPort: 9874, 10 | runnerPort: 9875, 11 | port: 9876, 12 | 13 | basePath: '', 14 | 15 | files: ['tests.js'], 16 | 17 | browsers: ['PhantomJS'], 18 | frameworks: ['jasmine'], 19 | 20 | preprocessors: { 21 | 'tests.js': ['webpack', 'sourcemap'] 22 | }, 23 | 24 | webpack: webpackConfig({test: true, coverage: coverage, platform: 'web'}), 25 | 26 | webpackMiddleware: { 27 | noInfo: true 28 | }, 29 | 30 | reporters: (coverage ? ['dots', 'coverage'] : ['dots']), 31 | 32 | coverageReporter: { 33 | dir: 'coverage', 34 | 35 | reporters: [ 36 | {type: 'html', subdir: 'report-html'}, 37 | {type: 'lcov', subdir: 'report-lcov'}, 38 | {type: 'text'} 39 | ] 40 | } 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /src/utils/glob/src/index.js: -------------------------------------------------------------------------------- 1 | import constant from 'lodash/constant' 2 | import convert from './convert' 3 | 4 | const ipv4 = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/ 5 | const ipv6 = /^(?:[A-F0-9]{1,4}:){7}[A-F0-9]{1,4}$/ 6 | const localhost = /^localhost$/ 7 | const domain = /^.+\.[^.]+$/ 8 | const simpleDomain = /^[^.*]+\.[^.]+$/ 9 | const noop = constant(false) 10 | 11 | export function compile (glob) { 12 | // If not a valid glob, ignore it. 13 | if (!ipv4.test(glob) && 14 | !ipv6.test(glob) && 15 | !localhost.test(glob) && 16 | !domain.test(glob)) { 17 | return noop 18 | } 19 | 20 | let regexp = convert(glob) 21 | 22 | // Allow all subdomains if none was specified. 23 | if (simpleDomain.test(glob)) { 24 | regexp = `(.+\\.)?${regexp}` 25 | } 26 | 27 | // Force matching the entire input against the pattern. 28 | regexp = new RegExp(`^${regexp}$`, 'i') 29 | 30 | return (url) => { 31 | const {protocol, hostname} = url 32 | return (protocol !== 'http:' && protocol !== 'https:') || regexp.test(hostname) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/utils/analytics/src/index.js: -------------------------------------------------------------------------------- 1 | import {getService} from 'cpa' 2 | import pkg from 'package.json' 3 | 4 | let service = null 5 | let tracker = null 6 | 7 | try { 8 | service = getService(pkg.name) 9 | tracker = service.getTracker(pkg.analytics.code) 10 | } catch (e) {} 11 | 12 | export function getConfig (callback) { 13 | service && service.getConfig().addCallback(callback) 14 | } 15 | 16 | export function isTrackingPermitted (callback) { 17 | getConfig((config) => { 18 | callback(config.isTrackingPermitted()) 19 | }) 20 | } 21 | 22 | export function setTrackingPermitted (permitted) { 23 | getConfig((config) => { 24 | config.setTrackingPermitted(permitted) 25 | }) 26 | } 27 | 28 | export function trackView (view) { 29 | tracker && tracker.sendAppView(view || window.location.pathname) 30 | } 31 | 32 | export function trackEvent (category, action, label, value) { 33 | tracker && tracker.sendEvent(category, action, label, value) 34 | } 35 | 36 | export function trackTiming (category, variable, value, label, rate) { 37 | tracker && tracker.sendTiming(category, variable, value, label, rate) 38 | } 39 | 40 | export function trackException (err, fatal) { 41 | tracker && err && tracker.sendException(err.message, fatal) 42 | } 43 | -------------------------------------------------------------------------------- /src/platform/chrome/utils/platform.js: -------------------------------------------------------------------------------- 1 | require('../manifest.json') 2 | require.context('_locales', true, /.*\.json/) 3 | require.context('assets', false, /icon_.*\.png/) 4 | 5 | export function getExtensionManifest () { 6 | return window.chrome.runtime.getManifest() 7 | } 8 | 9 | export function getExtensionUrl () { 10 | return window.chrome.extension.getURL.apply(window.chrome.extension, arguments) 11 | } 12 | 13 | export function getI18nMessage () { 14 | return window.chrome.i18n.getMessage.apply(window.chrome.i18n, arguments) 15 | } 16 | 17 | function getStorage () { 18 | return window.chrome.storage.sync || window.chrome.storage.local 19 | } 20 | 21 | export function getItems (defaults, callback) { 22 | const storage = getStorage() 23 | storage.get(defaults, callback) 24 | } 25 | 26 | export function setItems (items, callback) { 27 | const storage = getStorage() 28 | storage.set(items, callback) 29 | } 30 | 31 | export function onChangeItems (callback) { 32 | window.chrome.storage.onChanged.addListener(callback) 33 | } 34 | 35 | export function onBeforeRequest (callback) { 36 | const urls = [''] 37 | window.chrome.webRequest.onBeforeRequest.addListener(callback, {urls}, ['blocking']) 38 | } 39 | 40 | export function updateTab (id, url) { 41 | url = getExtensionUrl(url) 42 | 43 | window.chrome.tabs.update(id, {url}) 44 | } 45 | -------------------------------------------------------------------------------- /src/platform/opera/utils/platform.js: -------------------------------------------------------------------------------- 1 | require('../manifest.json') 2 | require.context('_locales', true, /.*\.json/) 3 | require.context('assets', false, /icon_.*\.png/) 4 | 5 | export function getExtensionManifest () { 6 | return window.chrome.runtime.getManifest() 7 | } 8 | 9 | export function getExtensionUrl () { 10 | return window.chrome.extension.getURL.apply(window.chrome.extension, arguments) 11 | } 12 | 13 | export function getI18nMessage () { 14 | return window.chrome.i18n.getMessage.apply(window.chrome.i18n, arguments) 15 | } 16 | 17 | function getStorage () { 18 | return window.chrome.storage.sync || window.chrome.storage.local 19 | } 20 | 21 | export function getItems (defaults, callback) { 22 | const storage = getStorage() 23 | storage.get(defaults, callback) 24 | } 25 | 26 | export function setItems (items, callback) { 27 | const storage = getStorage() 28 | storage.set(items, callback) 29 | } 30 | 31 | export function onChangeItems (callback) { 32 | window.chrome.storage.onChanged.addListener(callback) 33 | } 34 | 35 | export function onBeforeRequest (callback) { 36 | const urls = [''] 37 | window.chrome.webRequest.onBeforeRequest.addListener(callback, {urls}, ['blocking']) 38 | } 39 | 40 | export function updateTab (id, url) { 41 | url = getExtensionUrl(url) 42 | 43 | window.chrome.tabs.update(id, {url}) 44 | } 45 | -------------------------------------------------------------------------------- /src/platform/firefox/utils/platform.js: -------------------------------------------------------------------------------- 1 | require('../manifest.json') 2 | require.context('_locales', true, /.*\.json/) 3 | require.context('assets', false, /icon_.*\.png/) 4 | 5 | export function getExtensionManifest () { 6 | return window.browser.runtime.getManifest() 7 | } 8 | 9 | export function getExtensionUrl () { 10 | return window.browser.extension.getURL.apply(window.browser.extension, arguments) 11 | } 12 | 13 | export function getI18nMessage () { 14 | return window.browser.i18n.getMessage.apply(window.browser.i18n, arguments) 15 | } 16 | 17 | function getStorage () { 18 | return window.browser.storage.sync || window.browser.storage.local 19 | } 20 | 21 | export function getItems (defaults, callback) { 22 | const storage = getStorage() 23 | storage.get(defaults, callback) 24 | } 25 | 26 | export function setItems (items, callback) { 27 | const storage = getStorage() 28 | storage.set(items, callback) 29 | } 30 | 31 | export function onChangeItems (callback) { 32 | window.browser.storage.onChanged.addListener(callback) 33 | } 34 | 35 | export function onBeforeRequest (callback) { 36 | const urls = [''] 37 | window.browser.webRequest.onBeforeRequest.addListener(callback, {urls}, ['blocking']) 38 | } 39 | 40 | export function updateTab (id, url) { 41 | url = getExtensionUrl(url) 42 | 43 | window.browser.tabs.update(id, {url}) 44 | } 45 | -------------------------------------------------------------------------------- /src/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "locale": { 3 | "message": "en" 4 | }, 5 | "ext_name": { 6 | "message": "Whitelist Manager" 7 | }, 8 | "ext_description": { 9 | "message": "Automatically blocks all pages from any website that is not in your list of allowed websites." 10 | }, 11 | 12 | "msg_header_about": { 13 | "message": "About..." 14 | }, 15 | 16 | "msg_blocked_page_label": { 17 | "message": "Whitelist Manager has prevented the following page from loading:" 18 | }, 19 | "msg_blocked_instructions_label": { 20 | "message": "If you wish to view pages from $HOSTNAME$, please add it to your list of allowed websites.", 21 | "placeholders": { 22 | "hostname": { 23 | "content": "$1" 24 | } 25 | } 26 | }, 27 | 28 | "msg_about_dialog_haiku_label": { 29 | "message": "Summer here again.\nMusic plays sweetly, drifting.\nAnd life is renewed." 30 | }, 31 | "msg_about_dialog_close_button": { 32 | "message": "Close" 33 | }, 34 | 35 | "msg_options_instructions_label": { 36 | "message": "In order to use this extension, activate the blocking options you need below, and edit your list of safe websites. This extension will automatically block access to any website that is not in that list." 37 | }, 38 | "msg_options_block_pages_label": { 39 | "message": "Block pages from websites not in my list" 40 | }, 41 | "msg_options_block_others_label": { 42 | "message": "Block other resources (images, scripts, etc.) from websites not in my list" 43 | }, 44 | "msg_options_whitelist_label": { 45 | "message": "List of allowed websites" 46 | }, 47 | "msg_options_analytics_label": { 48 | "message": "Send anonymized usage statistics and crash reports" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/background.js: -------------------------------------------------------------------------------- 1 | import {parse} from 'url' 2 | import {compile} from 'utils/glob' 3 | import {onBeforeRequest, updateTab} from 'utils/platform' 4 | import {loadSettings, onChangeSettings} from 'utils/settings' 5 | import {trackView, trackException} from 'utils/analytics' 6 | 7 | import _template from './background.html' 8 | 9 | const settings = {} 10 | 11 | const redirect = (details) => { 12 | const {tabId, type, url} = details 13 | const blockPages = settings.blockPages && type === 'main_frame' 14 | const blockOthers = settings.blockOthers && type !== 'main_frame' 15 | 16 | if (blockPages || blockOthers) { 17 | const parsed = parse(url) 18 | const match = settings.whitelist.find((entry) => entry(parsed)) 19 | 20 | if (!match) { 21 | if (blockPages) { 22 | updateTab(tabId, `blocked.html?details=${btoa(JSON.stringify(parsed))}`) 23 | } 24 | if (blockPages || blockOthers) { 25 | return {cancel: true} 26 | } 27 | } 28 | } 29 | } 30 | 31 | const reload = () => { 32 | loadSettings(({blockPages, blockOthers, whitelist}) => { 33 | settings.blockPages = blockPages 34 | settings.blockOthers = blockOthers 35 | settings.whitelist = whitelist.map(compile) 36 | }) 37 | } 38 | 39 | const init = () => { 40 | // Monitor every request. 41 | onBeforeRequest(redirect) 42 | 43 | // If settings change, reload. 44 | onChangeSettings(reload) 45 | 46 | // Load for the first time. 47 | reload() 48 | 49 | trackView() 50 | } 51 | 52 | const error = (e) => { 53 | trackException(e.error) 54 | } 55 | 56 | /* ************************************************************************** */ 57 | 58 | window.addEventListener('load', init, true) 59 | window.addEventListener('error', error, true) 60 | -------------------------------------------------------------------------------- /src/_locales/es/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "locale": { 3 | "message": "es" 4 | }, 5 | "ext_name": { 6 | "message": "Whitelist Manager" 7 | }, 8 | "ext_description": { 9 | "message": "Bloquea automáticamente las páginas de todo sitio que no esté en tu lista de sitios permitidos." 10 | }, 11 | 12 | "msg_header_about": { 13 | "message": "Información..." 14 | }, 15 | 16 | "msg_blocked_page_label": { 17 | "message": "Whitelist Manager ha impedido la carga de la página:" 18 | }, 19 | "msg_blocked_instructions_label": { 20 | "message": "Si deseas ver páginas de $HOSTNAME$, por favor añádelo a tu lista de sitios permitidos.", 21 | "placeholders": { 22 | "hostname": { 23 | "content": "$1" 24 | } 25 | } 26 | }, 27 | 28 | "msg_about_dialog_haiku_label": { 29 | "message": "Summer here again.\nMusic plays sweetly, drifting.\nAnd life is renewed." 30 | }, 31 | "msg_about_dialog_close_button": { 32 | "message": "Cerrar" 33 | }, 34 | 35 | "msg_options_instructions_label": { 36 | "message": "Para usar esta extensión, activa las opciones de bloqueo que desees debajo, y edita tu lista de sitios seguros. Esta extensión bloqueará automáticamente el acceso a todo sitio que no esté en esa lista." 37 | }, 38 | "msg_options_block_pages_label": { 39 | "message": "Bloquear páginas de sitios que no estén en mi lista" 40 | }, 41 | "msg_options_block_others_label": { 42 | "message": "Bloquear otros recursos (imágenes, scripts, etc.) de sitios que no estén en mi lista" 43 | }, 44 | "msg_options_whitelist_label": { 45 | "message": "Lista de sitios permitidos" 46 | }, 47 | "msg_options_analytics_label": { 48 | "message": "Enviar estadísticas de uso e informes sobre fallos anónimamente" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/components/about-dialog/src/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component, PropTypes} from 'react' 2 | import {Button, Dialog, DialogTitle, DialogContent, DialogActions} from 'react-mdl' 3 | import {autobind} from 'core-decorators' 4 | import dialogPolyfill from 'dialog-polyfill' 5 | import {t} from 'utils/i18n' 6 | import pkg from 'package.json' 7 | 8 | import _styles from './index.scss' 9 | 10 | export default class AboutDialog extends Component { 11 | static propTypes = { 12 | open: PropTypes.bool, 13 | onClose: PropTypes.func 14 | } 15 | 16 | @autobind 17 | handleDialogRef (dialog) { 18 | this._dialog = dialog 19 | } 20 | 21 | @autobind 22 | handleClose () { 23 | const {onClose} = this.props 24 | onClose && onClose() 25 | } 26 | 27 | hasDialog () { 28 | const dialog = this._dialog && this._dialog.dialogRef 29 | return (dialog instanceof Node) && document.contains(dialog) 30 | } 31 | 32 | componentDidMount () { 33 | dialogPolyfill.registerDialog(this._dialog.dialogRef) 34 | } 35 | 36 | render () { 37 | const {open} = this.props 38 | const isOpen = this.hasDialog() && !!open 39 | 40 | return ( 41 | 45 |
46 | 47 | {`${t('ext_name')} v${pkg.version}`} 48 | 49 | 50 |

51 | {t('msg_about_dialog_haiku_label')} 52 |

53 |
54 | 55 | 60 | 61 |
62 |
63 | ) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/utils/glob/src/convert.js: -------------------------------------------------------------------------------- 1 | function isGlobMetaCharacter (ch) { 2 | return '*?[]'.indexOf(ch) >= 0 3 | } 4 | 5 | function isRegExpMetaCharacter (ch) { 6 | return '\\^$.*+?()[]{}|'.indexOf(ch) >= 0 7 | } 8 | 9 | export default function convert (pattern, options = {}) { 10 | const {starCannotMatchZero, questionCanMatchZero} = options 11 | 12 | let buffer = '' 13 | let inCharSet = false 14 | 15 | for (let i = 0, l = pattern.length; i < l; ++i) { 16 | switch (pattern[i]) { 17 | case '*': 18 | if (inCharSet) { 19 | buffer += '*' 20 | } else { 21 | buffer += starCannotMatchZero ? '.+' : '.*' 22 | } 23 | break 24 | case '?': 25 | if (inCharSet) { 26 | buffer += '?' 27 | } else { 28 | buffer += questionCanMatchZero ? '.?' : '.' 29 | } 30 | break 31 | case '[': 32 | inCharSet = true 33 | buffer += pattern[i] 34 | if (i + 1 < l) { 35 | switch (pattern[i + 1]) { 36 | case '!': 37 | case '^': 38 | buffer += '^' 39 | ++i 40 | continue 41 | case ']': 42 | buffer += ']' 43 | ++i 44 | continue 45 | } 46 | } 47 | break 48 | case ']': 49 | inCharSet = false 50 | buffer += pattern[i] 51 | break 52 | case '\\': 53 | buffer += '\\' 54 | if (i === l - 1) { 55 | buffer += '\\' 56 | } else if (isGlobMetaCharacter(pattern[i + 1])) { 57 | buffer += pattern[++i] 58 | } else { 59 | buffer += '\\' 60 | } 61 | break 62 | default: 63 | if (!inCharSet && isRegExpMetaCharacter(pattern[i])) { 64 | buffer += '\\' 65 | } 66 | buffer += pattern[i] 67 | } 68 | } 69 | 70 | return buffer 71 | } 72 | -------------------------------------------------------------------------------- /src/components/layout/src/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component, PropTypes, cloneElement} from 'react' 2 | import {Header, Icon, Navigation, Content, Card, CardText} from 'react-mdl' 3 | import {autobind} from 'core-decorators' 4 | import AboutDialog from 'components/about-dialog' 5 | import {t} from 'utils/i18n' 6 | import {trackEvent} from 'utils/analytics' 7 | 8 | import _styles from './index.scss' 9 | 10 | export default class Layout extends Component { 11 | static propTypes = { 12 | location: PropTypes.any, 13 | children: PropTypes.oneOfType([ 14 | PropTypes.element, 15 | PropTypes.arrayOf(PropTypes.element) 16 | ]) 17 | } 18 | 19 | state = {} 20 | 21 | @autobind 22 | handleAboutClick () { 23 | trackEvent('About', 'Open') 24 | 25 | this.setState({about: true}) 26 | } 27 | 28 | @autobind 29 | handleAboutClose () { 30 | trackEvent('About', 'Close') 31 | 32 | this.setState({about: false}) 33 | } 34 | 35 | render () { 36 | const {location, children} = this.props 37 | const {about} = this.state 38 | const {details} = location.query 39 | const childProps = details != null ? JSON.parse(atob(details)) : {} 40 | 41 | const showAbout = !!about 42 | 43 | const title = ( 44 | 45 | 46 | {t('ext_name')} 47 | 48 | ) 49 | 50 | return ( 51 |
52 |
53 |
54 | 55 | 58 | {t('msg_header_about')} 59 | 60 | 61 |
62 | 63 | 64 | 65 | 66 | {children && cloneElement(children, childProps)} 67 | 68 | 69 | 70 |
71 | 72 | 76 |
77 | ) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Whitelist Manager](src/assets/promo_1400.png) 2 | 3 | # Whitelist Manager [![Build Status](https://img.shields.io/travis/unindented/whitelist-manager.svg)](http://travis-ci.org/unindented/whitelist-manager) [![Dependency Status](https://img.shields.io/gemnasium/unindented/whitelist-manager.svg)](https://gemnasium.com/unindented/whitelist-manager) [![Coverage Status](https://img.shields.io/coveralls/unindented/whitelist-manager.svg)](https://coveralls.io/r/unindented/whitelist-manager) 4 | 5 | Automatically blocks all pages from any website that is not in your list of allowed websites. 6 | 7 | ![Right-click on an image...](src/assets/screenshot_small_1.png) ![And get your palette of colors!](src/assets/screenshot_small_2.png) 8 | 9 | 10 | ## Description 11 | 12 | Do you have small children, and need to limit the pages they visit to a few known safe websites? Or maybe you just want to concentrate and eliminate all distractions? Then this extension is for you! 13 | 14 | Create your own _whitelist_ of safe websites, and this extension will automatically block all pages from any other website that is not in that list. 15 | 16 | When adding a website to your _whitelist_, consider the following: 17 | 18 | * You can use wildcards. For example, if you add _google.\*_, you WILL be able to access _google.com_, _google.de_, _google.es_, etc. 19 | * If you only specify the domain of the website, its subdomains will also be allowed. That is, if you add _google.com_, you WILL be able to access _mail.google.com_, _maps.google.com_, etc. 20 | * If you specify both the domain and the subdomain of the website, only that subdomain will be allowed. That is, if you add _www.google.com_, you WILL NOT be able to access _mail.google.com_, _maps.google.com_, etc. 21 | 22 | PS: When installing _Whitelist Manager_, your browser will warn you that this extension can access your data on all sites, and your browsing history. These permissions are required to block pages when you are browsing. No information is stored on your computer and no personal data is obtained from this extension. I promise! 23 | 24 | 25 | ## Install 26 | 27 | Go to the [Whitelist Manager home page](https://chrome.google.com/extensions/detail/pocjkchlmhkjafdpmkklknmjhokobgmh) and hit the _Install_ button. 28 | 29 | 30 | ## Meta 31 | 32 | * Code: `git clone git://github.com/unindented/whitelist-manager.git` 33 | * Home: 34 | 35 | 36 | ## Contributors 37 | 38 | * Daniel Perez Alvarez ([unindented@gmail.com](mailto:unindented@gmail.com)) 39 | * David Moreno Gomez ([dmgomez@gmail.com](mailto:dmgomez@gmail.com)) 40 | 41 | 42 | ## License 43 | 44 | Copyright (c) 2016 Daniel Perez Alvarez ([unindented.org](https://unindented.org/)). This is free software, and may be redistributed under the terms specified in the LICENSE file. 45 | -------------------------------------------------------------------------------- /src/components/options/src/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {autobind} from 'core-decorators' 3 | import {t} from 'utils/i18n' 4 | import {defaultSettings, loadSettings, saveSettings} from 'utils/settings' 5 | import {isTrackingPermitted, setTrackingPermitted} from 'utils/analytics' 6 | 7 | import _styles from './index.scss' 8 | 9 | export default class Options extends Component { 10 | constructor () { 11 | super() 12 | 13 | this.state = Object.assign({trackingPermitted: false}, defaultSettings()) 14 | 15 | loadSettings(function (settings) { 16 | this.setState(settings) 17 | }.bind(this)) 18 | 19 | isTrackingPermitted(function (permitted) { 20 | this.setState({trackingPermitted: permitted}) 21 | }.bind(this)) 22 | } 23 | 24 | @autobind 25 | handleChangeBlockPages (evt) { 26 | const {checked: blockPages} = evt.target 27 | saveSettings({blockPages}) 28 | this.setState({blockPages}) 29 | } 30 | 31 | @autobind 32 | handleChangeBlockOthers (evt) { 33 | const {checked: blockOthers} = evt.target 34 | saveSettings({blockOthers}) 35 | this.setState({blockOthers}) 36 | } 37 | 38 | @autobind 39 | handleChangeWhitelist (evt) { 40 | const {value} = evt.target 41 | const whitelist = value.split('\n') 42 | saveSettings({whitelist}) 43 | this.setState({whitelist}) 44 | } 45 | 46 | @autobind 47 | handleChangeAnalytics (evt) { 48 | const {checked: trackingPermitted} = evt.target 49 | setTrackingPermitted(trackingPermitted) 50 | this.setState({trackingPermitted}) 51 | } 52 | 53 | render () { 54 | const {blockPages, blockOthers, whitelist, trackingPermitted} = this.state 55 | 56 | return ( 57 |
58 |

59 | {t('msg_options_instructions_label')} 60 |

61 | 70 | 79 | 84 |