From 9d71c9163459c66903e295eee0d53cb2fff8fe7a Mon Sep 17 00:00:00 2001 From: FelisCatus Date: Tue, 14 Mar 2017 00:25:46 -0400 Subject: [PATCH] Add a mini-popup page with the menu only. This allows the popup to load faster for most cases. If advanced features are used, the user will be redirected to the full popup page. --- .../background.coffee | 8 + .../grunt/copy.coffee | 3 + .../grunt/watch.coffee | 3 + .../omega_target_popup.js | 81 ++++++ .../omega_target_web.coffee | 2 +- .../overlay/manifest.json | 2 +- .../src/external_api.coffee | 4 +- .../src/options.coffee | 12 +- omega-web/grunt/copy.coffee | 5 + omega-web/grunt/watch.coffee | 4 + omega-web/src/coffee/popup.coffee | 14 + omega-web/src/popup.jade | 6 +- omega-web/src/popup/css/dialog.css | 97 +++++++ omega-web/src/popup/css/index.css | 220 ++++++++++++++ omega-web/src/popup/index.html | 56 ++++ omega-web/src/popup/js/i18n.js | 10 + omega-web/src/popup/js/index.js | 49 ++++ omega-web/src/popup/js/keyboard.js | 129 +++++++++ omega-web/src/popup/js/keyboard_help.js | 32 +++ omega-web/src/popup/js/loader.js | 21 ++ omega-web/src/popup/js/profiles.js | 272 ++++++++++++++++++ .../src/popup/js/proxy_not_controllable.js | 29 ++ .../src/popup/proxy_not_controllable.html | 20 ++ 23 files changed, 1068 insertions(+), 11 deletions(-) create mode 100644 omega-target-chromium-extension/omega_target_popup.js create mode 100644 omega-web/src/popup/css/dialog.css create mode 100644 omega-web/src/popup/css/index.css create mode 100644 omega-web/src/popup/index.html create mode 100644 omega-web/src/popup/js/i18n.js create mode 100644 omega-web/src/popup/js/index.js create mode 100644 omega-web/src/popup/js/keyboard.js create mode 100644 omega-web/src/popup/js/keyboard_help.js create mode 100644 omega-web/src/popup/js/loader.js create mode 100644 omega-web/src/popup/js/profiles.js create mode 100644 omega-web/src/popup/js/proxy_not_controllable.js create mode 100644 omega-web/src/popup/proxy_not_controllable.html diff --git a/omega-target-chromium-extension/background.coffee b/omega-target-chromium-extension/background.coffee index 26b5e06..f13a112 100644 --- a/omega-target-chromium-extension/background.coffee +++ b/omega-target-chromium-extension/background.coffee @@ -285,6 +285,12 @@ encodeError = (obj) -> else obj +refreshActivePageIfEnabled = -> + return unless localStorage['omega.local.refreshOnProfileChange'] + chrome.tabs.query {active: true, lastFocusedWindow: true}, (tabs) -> + if tabs[0].url and tabs[0].url.substr(0, 6) != 'chrome' + chrome.tabs.reload(tabs[0].id, {bypassCache: true}) + chrome.runtime.onMessage.addListener (request, sender, respond) -> options.ready.then -> target = options @@ -298,6 +304,8 @@ chrome.runtime.onMessage.addListener (request, sender, respond) -> return promise = Promise.resolve().then -> method.apply(target, request.args) + if request.refreshActivePage + promise.then refreshActivePageIfEnabled return if request.noReply promise.then (result) -> diff --git a/omega-target-chromium-extension/grunt/copy.coffee b/omega-target-chromium-extension/grunt/copy.coffee index f816ed9..cd4afff 100644 --- a/omega-target-chromium-extension/grunt/copy.coffee +++ b/omega-target-chromium-extension/grunt/copy.coffee @@ -11,6 +11,9 @@ module.exports = target_self: src: 'omega_target_chromium_extension.min.js' dest: 'build/js/' + target_popup: + src: 'omega_target_popup.js' + dest: 'build/js/' overlay: expand: true cwd: 'overlay' diff --git a/omega-target-chromium-extension/grunt/watch.coffee b/omega-target-chromium-extension/grunt/watch.coffee index 06a73dc..cafeb37 100644 --- a/omega-target-chromium-extension/grunt/watch.coffee +++ b/omega-target-chromium-extension/grunt/watch.coffee @@ -17,6 +17,9 @@ module.exports = copy_overlay: files: ['overlay/**/*'] tasks: ['copy:overlay'] + copy_target_popup: + files: ['omega_target_popup.js'] + tasks: ['copy:target_popup'] src: files: ['src/**/*.coffee'] tasks: ['coffeelint:src', 'browserify', 'copy:target_self'] diff --git a/omega-target-chromium-extension/omega_target_popup.js b/omega-target-chromium-extension/omega_target_popup.js new file mode 100644 index 0000000..79b3d31 --- /dev/null +++ b/omega-target-chromium-extension/omega_target_popup.js @@ -0,0 +1,81 @@ +function callBackgroundNoReply(method, args, cb) { + chrome.runtime.sendMessage({ + method: method, + args: args, + noReply: true, + refreshActivePage: true, + }); + if (cb) return cb(); +} + +function callBackground(method, args, cb) { + chrome.runtime.sendMessage({ + method: method, + args: args, + }, function(response) { + if (chrome.runtime.lastError != null) + return cb && cb(chrome.runtime.lastError) + if (response.error) return cb && cb(response.error) + return cb && cb(null, response.result) + }); +} + +var requestInfoCallback = null; + +OmegaTargetPopup = { + getState: function (keys, cb) { + var results = {}; + keys.forEach(function(key) { + try { + results[key] = JSON.parse(localStorage['omega.local.' + key]); + } catch (_) { + return null; + } + }); + if (cb) cb(null, results); + }, + applyProfile: function (name, cb) { + callBackgroundNoReply('applyProfile', [name], cb); + }, + openOptions: function (hash, cb) { + var options_url = chrome.extension.getURL('options.html'); + + chrome.tabs.query({ + url: options_url + }, function(tabs) { + if (tabs.length > 0) { + var props = { + active: true + }; + if (hash) { + var url = options_url + hash; + props.url = url; + } + chrome.tabs.update(tabs[0].id, props); + } else { + chrome.tabs.create({ + url: options_url + }); + } + if (cb) return cb(); + }); + }, + getActivePageInfo: function(cb) { + chrome.tabs.query({active: true, lastFocusedWindow: true}, function (tabs) { + if (tabs.length === 0 || !tabs[0].url) return cb(); + var args = {tabId: tabs[0].id, url: tabs[0].url}; + callBackground('getPageInfo', [args], cb) + }); + }, + setDefaultProfile: function(profileName, defaultProfileName, cb) { + callBackgroundNoReply('setDefaultProfile', + [profileName, defaultProfileName], cb); + }, + addTempRule: function(domain, profileName, cb) { + callBackgroundNoReply('addTempRule', [domain, profileName], cb); + }, + openManage: function(domain, profileName, cb) { + chrome.tabs.create({url: 'chrome://extensions/?id=' + chrome.runtime.id}); + }, + getMessage: chrome.i18n.getMessage.bind(chrome.i18n), +}; diff --git a/omega-target-chromium-extension/omega_target_web.coffee b/omega-target-chromium-extension/omega_target_web.coffee index 4a6cf7a..0b88bf0 100644 --- a/omega-target-chromium-extension/omega_target_web.coffee +++ b/omega-target-chromium-extension/omega_target_web.coffee @@ -131,7 +131,7 @@ angular.module('omegaTarget', []).factory 'omegaTarget', ($q) -> connectBackground('tabRequestInfo', args, requestInfoCallback) d.resolve(callBackground('getPageInfo', args)) - return d.promise + return d.promise.then (info) -> if info?.url then info else null refreshActivePage: -> d = $q['defer']() chrome.tabs.query {active: true, lastFocusedWindow: true}, (tabs) -> diff --git a/omega-target-chromium-extension/overlay/manifest.json b/omega-target-chromium-extension/overlay/manifest.json index b4400cc..19d3180 100644 --- a/omega-target-chromium-extension/overlay/manifest.json +++ b/omega-target-chromium-extension/overlay/manifest.json @@ -21,7 +21,7 @@ "32": "img/icons/omega-action-32.png" }, "default_title": "__MSG_manifest_icon_default_title__", - "default_popup": "popup.html" + "default_popup": "popup/index.html" }, "background": { "page": "background.html" diff --git a/omega-target-chromium-extension/src/external_api.coffee b/omega-target-chromium-extension/src/external_api.coffee index 3c7a92e..a080718 100644 --- a/omega-target-chromium-extension/src/external_api.coffee +++ b/omega-target-chromium-extension/src/external_api.coffee @@ -21,7 +21,7 @@ module.exports = class ExternalApi return unless @disabled @options.setProxyNotControllable(null) - chrome.browserAction.setPopup({popup: 'popup.html'}) + chrome.browserAction.setPopup({popup: 'popup/index.html'}) @options.reloadQuickSwitch() @disabled = false @options.clearBadge() @@ -48,7 +48,7 @@ module.exports = class ExternalApi if @knownExts[port.sender.id] >= 32 reason = 'upgrade' @options.setProxyNotControllable reason, {text: 'X', color: '#5ab432'} - chrome.browserAction.setPopup({popup: 'popup.html'}) + chrome.browserAction.setPopup({popup: 'popup/index.html'}) port.postMessage({action: 'state', state: 'disabled'}) when 'enable' @reenable() diff --git a/omega-target-chromium-extension/src/options.coffee b/omega-target-chromium-extension/src/options.coffee index 1375e1a..d34651c 100644 --- a/omega-target-chromium-extension/src/options.coffee +++ b/omega-target-chromium-extension/src/options.coffee @@ -197,7 +197,7 @@ class ChromeOptions extends OmegaTarget.Options if tab.url and tab.url.indexOf('chrome') != 0 chrome.tabs.reload(tab.id) else - chrome.browserAction.setPopup({popup: 'popup.html'}) + chrome.browserAction.setPopup({popup: 'popup/index.html'}) Promise.resolve() setInspect: (settings) -> @@ -329,6 +329,8 @@ class ChromeOptions extends OmegaTarget.Options chrome.tabs.create url: chrome.extension.getURL('options.html') getPageInfo: ({tabId, url}) -> + errorCount = @_requestMonitor.tabInfo[tabId]?.errorCount + result = if errorCount then {errorCount: errorCount} else null getBadge = new Promise (resolve, reject) -> chrome.browserAction.getBadgeText {tabId: tabId}, (result) -> resolve(result) @@ -339,19 +341,21 @@ class ChromeOptions extends OmegaTarget.Options url = inspectUrl else @clearBadge() - return null if not url + return result if not url if url.substr(0, 6) == 'chrome' errorPagePrefix = 'chrome://errorpage/' if url.substr(0, errorPagePrefix.length) == errorPagePrefix url = querystring.parse(url.substr(url.indexOf('?') + 1)).lasturl - return null if not url + return result if not url else - return null + return result domain = OmegaPac.getBaseDomain(Url.parse(url).hostname) + return { url: url domain: domain tempRuleProfileName: @queryTempRule(domain) + errorCount: errorCount } module.exports = ChromeOptions diff --git a/omega-web/grunt/copy.coffee b/omega-web/grunt/copy.coffee index ba0cd18..64f74bc 100644 --- a/omega-web/grunt/copy.coffee +++ b/omega-web/grunt/copy.coffee @@ -12,3 +12,8 @@ module.exports = cwd: 'img' src: ['**/*'] dest: 'build/img/' + popup: + expand: true + cwd: 'src/popup' + src: ['**/*'] + dest: 'build/popup/' diff --git a/omega-web/grunt/watch.coffee b/omega-web/grunt/watch.coffee index 532f26f..f37edaa 100644 --- a/omega-web/grunt/watch.coffee +++ b/omega-web/grunt/watch.coffee @@ -17,6 +17,10 @@ module.exports = files: 'img/**/*' tasks: 'copy:img' + copy_popup: + files: + 'src/popup/**/*' + tasks: 'copy:popup' jade: files: ['src/**/*.jade'] tasks: 'jade' diff --git a/omega-web/src/coffee/popup.coffee b/omega-web/src/coffee/popup.coffee index 40ec0e3..d4a0dd8 100644 --- a/omega-web/src/coffee/popup.coffee +++ b/omega-web/src/coffee/popup.coffee @@ -184,8 +184,20 @@ module.controller 'PopupCtrl', ($scope, $window, $q, omegaTarget, omegaTarget.applyProfile(name).then -> refresh() + $scope.returnToMenu = -> + if location.hash.indexOf('!') >= 0 + location.href = 'popup/index.html' + return + $scope.showConditionForm = false + $scope.showRequestInfo = false + preselectedProfileNameForCondition = 'direct' + if $window.location.hash == '#!requestInfo' + $scope.showRequestInfo = true + else if $window.location.hash == '#!external' + $scope.nameExternal = {open: true} + omegaTarget.state([ 'availableProfiles', 'currentProfileName', 'isSystemProfile', 'validResultProfiles', 'refreshOnProfileChange', 'externalProfile', @@ -257,6 +269,8 @@ module.controller 'PopupCtrl', ($scope, $window, $q, omegaTarget, if $scope.currentTempRuleProfile preselectedProfileNameForCondition = $scope.currentTempRuleProfile $scope.currentDomain = info.domain + if $window.location.hash == '#!addRule' + $scope.prepareConditionForm() $scope.prepareConditionForm = -> currentDomain = $scope.currentDomain diff --git a/omega-web/src/popup.jade b/omega-web/src/popup.jade index 25a0861..5d9807f 100644 --- a/omega-web/src/popup.jade +++ b/omega-web/src/popup.jade @@ -1,6 +1,6 @@ doctype html // - Copyright (C) 2012-2013, The SwitchyOmega Authors. Please see the AUTHORS file + Copyright 2017 The SwitchyOmega Authors. Please see the AUTHORS file for details. This file is part of SwitchyOmega. @@ -111,7 +111,7 @@ html(lang='en' ng-app='omegaPopup' ng-controller='PopupCtrl' ng-csp) div(omega-profile-select='validResultProfiles' ng-model='rule.profileName' disp-name='dispNameFilter' options='availableProfiles') div.condition-controls - button.btn.btn-default(type='button' ng-click='showConditionForm = false') + button.btn.btn-default(type='button' ng-click='returnToMenu()') | {{'dialog_cancel' | tr}} button.btn.btn-primary(type='submit' ng-disabled='conditionForm.$invalid') {{'popup_addCondition' | tr}} div.proxy-not-controllable(ng-show='proxyNotControllable') @@ -148,7 +148,7 @@ html(lang='en' ng-app='omegaPopup' ng-controller='PopupCtrl' ng-csp) p.help-block(ng-show='!currentProfileCanAddRule') | {{'popup_requestErrorCannotAddCondition' | tr}} div.condition-controls - button.btn.btn-default(type='button' ng-click='showRequestInfo = false') + button.btn.btn-default(type='button' ng-click='returnToMenu()') | {{'dialog_cancel' | tr}} button.btn.btn-primary(type='submit' ng-show='!!currentProfileCanAddRule') {{'popup_addCondition' | tr}} button.btn.btn-default.pull-right(type='button' ng-show='!currentProfileCanAddRule' diff --git a/omega-web/src/popup/css/dialog.css b/omega-web/src/popup/css/dialog.css new file mode 100644 index 0000000..1d634f2 --- /dev/null +++ b/omega-web/src/popup/css/dialog.css @@ -0,0 +1,97 @@ +/*! + * Copyright 2017 The SwitchyOmega Authors. Please see the AUTHORS file + * for details. + * Based on Bootstrap v3.3.2 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) +*/ + +/* Dialog */ + +body, html { + margin: 0; + padding: 0; +} + +p { + margin: 0 0 1em 0; +} + +.om-dialog { + min-width: 400px; + padding: 10px 10px; + font-size: 14px; +} + + .om-text-danger { + color: #a94442; + } + + .om-dialog-help { + display: block; + margin-top: 5px; + margin-bottom: 10px; + color: #737373; + } + + .om-dialog-controls { + margin-bottom: 0; + } + + .om-dialog-controls .om-btn-primary { + float: right; + } + +/* Button */ + +.om-btn { + display: inline-block; + padding: 6px 12px; + margin-bottom: 0; + font-size: 14px; + font-weight: 400; + line-height: 1.42857143; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-image: none; + border: 1px solid transparent; + border-radius: 4px +} + +.om-btn.active, .om-btn:active { + background-image: none; + outline: 0; + -webkit-box-shadow: inset 0 3px 5px rgba(0,0,0,.125); + box-shadow: inset 0 3px 5px rgba(0,0,0,.125); +} + +.om-btn-default { + color: #333; + background-color: #fff; + border-color: #ccc; +} + +.om-btn-default:hover { + background-color: #e6e6e6; + border-color: #adadad; +} + +.om-btn-primary { + color: #fff; + background-color: #337ab7; + border-color: #2e6da4; +} + +.om-btn-primary:hover { + color: #fff; + background-color: #286090; + border-color: #204d74; +} diff --git a/omega-web/src/popup/css/index.css b/omega-web/src/popup/css/index.css new file mode 100644 index 0000000..ca63909 --- /dev/null +++ b/omega-web/src/popup/css/index.css @@ -0,0 +1,220 @@ +/*! + * Copyright 2017 The SwitchyOmega Authors. Please see the AUTHORS file + * for details. + * Based on Bootstrap v3.3.2 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) +*/ + +/* Layout */ + +html, body { + font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; + font-size: 14px; + padding: 0; + margin: 0; +} + +body { + min-width: 12em; +} + +.om-hidden { + display: none !important; +} + +/* Menu */ + +.om-nav { + list-style: none; + padding: 0; + margin: 0; +} + + .om-nav-item { + display: block; + margin: 2px 0; + } + + .om-nav-item > a > .glyphicon { + margin-right: 8px; + } + + .om-nav-item > a { + display: block; + padding: 5px 25px 5px 8px; + border-radius: 4px; + line-height: 1.5em; + font-size: 1em; + color: #337ab7; + text-decoration: none; + white-space: nowrap; + cursor: pointer; + } + .om-nav-item > a:hover { + text-decoration: none; + background-color: #eee; + } + .om-nav-item > a:hover { + color: #23527c; + } + .om-nav-item.om-effective > a { + background-color: #d9edf7; + } + .om-nav-item.om-active > a { + color: #fff; + background-color: #337ab7; + } + + .om-divider { + height: 1px; + overflow: hidden; + background-color: #E5E5E5; + } + + .om-reqinfo { + background-color: #fcf8e3; + } + + .glyphicon-warning-sign { + color: #8a6d3b; + } + +/* Dropdown */ + +.om-dropdown { + display: none; + list-style: none; + padding: 5px 0; + margin: 0 5px !important; + border: 1px solid rgba(0,0,0,.15); + border-radius: 4px; + box-shadow: 0 6px 12px rgba(0,0,0,.175); +} + +.om-open .om-dropdown { + display: block; +} + +.om-dropdown .om-nav-item { + margin: 0; +} + +.om-dropdown .om-nav-item > a { + padding: 3px 20px; + line-height: 1.3em; + margin: 0; +} + +.om-caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: 4px solid; + border-right: 4px solid transparent; + border-left: 4px solid transparent; +} + +.om-virtual-profile-icon { + border: dotted 1px; + margin: -1px; +} + +/* Default Edit */ + +.om-has-edit { + padding-right: 32px; + position: relative; +} + + .om-edit-toggle { + background-color: #fff; + border: 1px solid #ccc; + color: #337ab7; + display: inline-block; + margin: -5px 0; + text-align: center; + padding: 5px 8px 3px; + position: absolute; + right: 0; + } + + .om-edit-toggle:hover { + background-color: #e6e6e6; + border-color: #adadad; + } + +/* Keyboard */ + +.om-keyboard-help { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + border: solid 1px #000; + border-radius: 2px; + display: inline-block; + color: #000; + box-shadow: 1px 1px; + width: 1em; + height: 1em; + line-height: 1em; + text-align: center; + margin-top: -3px; +} + +/* Glyphicons */ + +@font-face { + font-family: 'Glyphicons Halflings'; + src: url(../../../lib/bootstrap/fonts/glyphicons-halflings-regular.eot); + src: url(../../../lib/bootstrap/fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../../../lib/bootstrap/fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../../../lib/bootstrap/fonts/glyphicons-halflings-regular.woff) format('woff'),url(../../../lib/bootstrap/fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../../../lib/bootstrap/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg') +} + +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: 400; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.glyphicon-transfer:before { + content: "\e178"; +} +.glyphicon-off:before { + content: "\e017"; +} +.glyphicon-warning-sign:before { + content: "\e107"; +} +.glyphicon-file:before { + content: "\e022" +} +.glyphicon-globe:before { + content: "\e135" +} +.glyphicon-question-sign:before { + content: "\e085" +} +.glyphicon-list:before { + content: "\e056" +} +.glyphicon-retweet:before { + content: "\e115" +} +.glyphicon-plus:before { + content: "\2b" +} +.glyphicon-filter:before { + content: "\e138" +} +.glyphicon-wrench:before { + content: "\e136" +} +.glyphicon-chevron-down:before { + content: "\e114"; +} diff --git a/omega-web/src/popup/index.html b/omega-web/src/popup/index.html new file mode 100644 index 0000000..9182af8 --- /dev/null +++ b/omega-web/src/popup/index.html @@ -0,0 +1,56 @@ + + + + + SwitchyOmega Popup + + + + + + + + diff --git a/omega-web/src/popup/js/i18n.js b/omega-web/src/popup/js/i18n.js new file mode 100644 index 0000000..7e3680e --- /dev/null +++ b/omega-web/src/popup/js/i18n.js @@ -0,0 +1,10 @@ +$script.ready('om-page-info', function() { + document.querySelector('#js-direct .om-profile-name').textContent = + OmegaTargetPopup.getMessage('profile_direct'); + document.querySelector('#js-system .om-profile-name').textContent = + OmegaTargetPopup.getMessage('profile_system'); + document.querySelector('#js-addrule-label').textContent = + OmegaTargetPopup.getMessage('popup_addCondition'); + document.querySelector('#js-option-label').textContent = + OmegaTargetPopup.getMessage('popup_showOptions'); +}); diff --git a/omega-web/src/popup/js/index.js b/omega-web/src/popup/js/index.js new file mode 100644 index 0000000..acc335b --- /dev/null +++ b/omega-web/src/popup/js/index.js @@ -0,0 +1,49 @@ +(function() { + handleClick('js-option', showOptions); + handleClick('js-temprule', showTempRuleDropdown); + handleClick('js-direct', applyProfile.bind(this, 'direct')); + handleClick('js-system', applyProfile.bind(this, 'system')); + OmegaPopup.addTempRule = addTempRule; + OmegaPopup.setDefaultProfile = setDefaultProfile; + OmegaPopup.applyProfile = applyProfile; + return; + + function handleClick(id, handler) { + document.getElementById(id).addEventListener('click', handler, false); + } + + function closePopup() { + window.close(); + } + + function showOptions() { + $script.ready('om-target', function() { + OmegaTargetPopup.openOptions(null, closePopup); + }); + } + + function applyProfile(profileName) { + $script.ready('om-target', function() { + OmegaTargetPopup.applyProfile(profileName, closePopup); + }); + } + + function setDefaultProfile(profileName, defaultProfileName) { + $script.ready('om-target', function() { + OmegaTargetPopup.setDefaultProfile(profileName, defaultProfileName, + closePopup); + }); + } + + function addTempRule(domain, profileName) { + $script.ready('om-target', function() { + OmegaTargetPopup.addTempRule(domain, profileName, closePopup); + }); + } + + function showTempRuleDropdown() { + $script.ready('om-dropdowns', function() { + OmegaPopup.showTempRuleDropdown(); + }); + } +})(); diff --git a/omega-web/src/popup/js/keyboard.js b/omega-web/src/popup/js/keyboard.js new file mode 100644 index 0000000..ce52abe --- /dev/null +++ b/omega-web/src/popup/js/keyboard.js @@ -0,0 +1,129 @@ +(function() { + var keyHandler = { + 38: moveUp, // Up + 40: moveDown, // Down + 37: closeDropdown, // Left + 39: openDropdown, // Right + + 72: closeDropdown, // h + 74: moveUp, // j + 75: moveDown, // k + 76: openDropdown, // l + + 191: showKeyboardHelp, // / + 63: showKeyboardHelp, // ? + + 48: 'js-direct', // 0 + 83: 'js-system', // s + 69: 'js-external', // e + 65: 'js-addrule', // a + 187: 'js-addrule', // +, = + 84: 'js-temprule', // t + 79: 'js-option', // o + 82: 'js-reqinfo', // r + }; + + for (i = 1; i <= 9; i++) { + keyHandler[48 + i] = 'js-profile-' + i; + } + + var walker; + return init(); + + function init() { + walker = document.createTreeWalker( + document.querySelector('.om-nav'), + NodeFilter.SHOW_ELEMENT, + {acceptNode: tabbableElementsOnly} + ); + + window.addEventListener('keydown', function(e) { + var handler = keyHandler[e.keyCode]; + if (!handler) console.log(e.keyCode); + if (handler == null) return; + if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return; + if (typeof handler === 'string') { + clickById(handler); + } else { + handler(); + } + }); + } + + function tabbableElementsOnly(node) { + if (node.classList.contains('om-hidden')) { + return NodeFilter.FILTER_REJECT; + } else if (node.classList.contains('om-dropdown') && + !node.parentElement.classList.contains('om-open')) { + return NodeFilter.FILTER_REJECT; + } else if (node.tabIndex >= 0) { + return NodeFilter.FILTER_ACCEPT; + } else { + return NodeFilter.FILTER_SKIP; + } + } + + function moveUp() { + walker.currentNode = document.activeElement; + var result = null; + if (walker.currentNode) { + result = walker.previousNode(); + } + if (!result) { + walker.currentNode = walker.root.lastElementChild; + walker.previousNode(); + walker.nextNode(); + } + walker.currentNode.focus(); + } + + function moveDown() { + walker.currentNode = document.activeElement; + var result = null; + if (walker.currentNode) { + result = walker.nextNode(); + } + if (!result) { + walker.currentNode = walker.root; + walker.nextNode(); + } + walker.currentNode.focus(); + } + + function openDropdown() { + var container = document.querySelector('.om-open'); + if (container) { + // Existing dropdown. Just move to it. + container.querySelector('a').focus(); + return; + } + var selectedItem = document.activeElement; + if (!selectedItem || !selectedItem.parentElement) return; + if (selectedItem.parentElement.classList.contains('om-has-dropdown')) { + var toggle = selectedItem.querySelector('.om-edit-toggle'); + if (toggle) { + toggle.click(); + } else { + selectedItem.click(); + } + } + } + + function closeDropdown() { + var container = document.querySelector('.om-open'); + if (container) { + container.classList.remove('om-open'); + container.querySelector('a').focus(); + } + } + + function showKeyboardHelp() { + $script('js/keyboard_help.js'); + } + + function clickById(id) { + var element = document.getElementById(id); + if (element) element.click(); + } + +})(); diff --git a/omega-web/src/popup/js/keyboard_help.js b/omega-web/src/popup/js/keyboard_help.js new file mode 100644 index 0000000..5f9a7e3 --- /dev/null +++ b/omega-web/src/popup/js/keyboard_help.js @@ -0,0 +1,32 @@ +(function() { + var keyForId = { + 'js-direct': '0', + 'js-system': 'S', + 'js-external': 'E', + 'js-addrule': 'A', + 'js-temprule': 'T', + 'js-option': 'O', + 'js-reqinfo': 'R' + } + Object.keys(keyForId).forEach(function (id) { + showHelp(id, keyForId[id]); + }); + + for (var i = 1; i <= 9; i++) { + showHelp('js-profile-' + i, '' + i); + } + + return; + + function showHelp(id, key) { + var element = document.getElementById(id); + if (!element) return; + if (!element.querySelector('.om-keyboard-help')) { + var span = document.createElement('span'); + span.classList.add('om-keyboard-help'); + span.textContent = key; + var reference = element.querySelector('.glyphicon'); + reference.parentNode.insertBefore(span, reference.nextSibling); + } + } +})(); diff --git a/omega-web/src/popup/js/loader.js b/omega-web/src/popup/js/loader.js new file mode 100644 index 0000000..e5c1237 --- /dev/null +++ b/omega-web/src/popup/js/loader.js @@ -0,0 +1,21 @@ +window.OmegaPopup = {}; +$script(['js/index.js', 'js/profiles.js', 'js/keyboard.js'], 'om-main'); +$script(['js/i18n.js']); +$script('../js/omega_target_popup.js', 'om-target', function() { + OmegaTargetPopup.getActivePageInfo(function(err, info) { + window.OmegaPopup.pageInfo = info; + $script.done('om-page-info'); + }); + OmegaTargetPopup.getState([ + 'availableProfiles', + 'currentProfileName', + 'validResultProfiles', + 'isSystemProfile', + 'currentProfileCanAddRule', + 'proxyNotControllable', + 'externalProfile', + ], function(err, state) { + window.OmegaPopup.state = state; + $script.done('om-state'); + }); +}); diff --git a/omega-web/src/popup/js/profiles.js b/omega-web/src/popup/js/profiles.js new file mode 100644 index 0000000..ef52f19 --- /dev/null +++ b/omega-web/src/popup/js/profiles.js @@ -0,0 +1,272 @@ +(function() { + $script.ready('om-state', updateMenuByState); + $script.ready('om-page-info', updateMenuByPageInfo); + $script.ready(['om-state', 'om-page-info'], updateMenuByStateAndPageInfo); + + var profileTemplate = document.getElementById('js-profile-tpl') + .cloneNode(true); + profileTemplate.classList.remove('om-profile-tpl'); + + var iconForProfileType = { + 'DirectProfile': 'glyphicon-transfer', + 'SystemProfile': 'glyphicon-off', + 'AutoDetectProfile': 'glyphicon-file', + 'FixedProfile': 'glyphicon-globe', + 'PacProfile': 'glyphicon-file', + 'VirtualProfile': 'glyphicon-question-sign', + 'RuleListProfile': 'glyphicon-list', + 'SwitchProfile': 'glyphicon-retweet', + }; + var orderForType = { + 'FixedProfile': -2000, + 'PacProfile': -1000, + 'VirtualProfile': 1000, + 'SwitchProfile': 2000, + 'RuleListProfile': 3000, + }; + + return; + + function updateMenuByState() { + var state = OmegaPopup.state; + if (state.proxyNotControllable) { + location.href = 'proxy_not_controllable.html'; + return; + } + addProfilesItems(state); + updateOtherItems(state); + } + + function compareProfile(a, b) { + var diff; + diff = (orderForType[a.profileType] | 0) - (orderForType[b.profileType] | 0); + if (diff !== 0) { + return diff; + } + if (a.name === b.name) { + return 0; + } else if (a.name < b.name) { + return -1; + } else { + return 1; + } + } + + function updateMenuByPageInfo() { + var info = OmegaPopup.pageInfo; + if (info && info.errorCount > 0) { + document.querySelector('.om-reqinfo').classList.remove('om-hidden'); + var text = OmegaTargetPopup.getMessage('popup_requestErrorCount', + [info.errorCount]); + document.querySelector('.om-reqinfo-text').textContent = text; + } + } + + function updateMenuByStateAndPageInfo() { + var state = OmegaPopup.state; + var info = OmegaPopup.pageInfo; + if (state.externalProfile && (!info || !info.errorCount)) { + showMenuForExternalProfile(state); + } + if (!info || !info.url) return updateOtherItems(null); + document.querySelector('.om-page-domain').textContent = info.domain; + OmegaPopup.showTempRuleDropdown = showTempRuleDropdown; + $script.done('om-dropdowns'); + } + + function showMenuForExternalProfile(state) { + var profile = state.externalProfile; + profile.name = OmegaTargetPopup.getMessage('popup_externalProfile') + var profileDisp = createMenuItemForProfile(profile); + + var link = profileDisp.querySelector('a'); + link.id = 'js-external'; + link.addEventListener('click', function() { + location.href = '../popup.html#!external'; + }); + + if (state.currentProfileName === '') { + profileDisp.classList.add('om-effective'); + } + + var reqInfo = document.querySelector('.om-reqinfo'); + reqInfo.parentElement.insertBefore(profileDisp, reqInfo); + } + + function showTempRuleDropdown() { + var tempRuleItem = document.querySelector('.om-nav-temprule'); + toggleDropdown(tempRuleItem, createTempRuleDropdown); + document.getElementById('js-temprule').focus(); + } + + function updateOtherItems(state) { + var hasValidResults = state && state.validResultProfiles && + state.validResultProfiles.length; + if (!hasValidResults || !state.currentProfileCanAddRule) { + document.querySelector('.om-nav-addrule').classList.add('om-hidden'); + document.getElementById('js-addrule').href = '#'; + } + if (!hasValidResults) { + document.querySelector('.om-nav-temprule').classList.add('om-hidden'); + document.getElementById('js-temprule').href = '#'; + } + } + + var isValidResultProfile = {}; + validResultProfiles.forEach(function(name) { + isValidResultProfile['+' + name] = true; + }); + + function addProfilesItems(state) { + var currentProfileClass = 'om-active'; + if (state.isSystemProfile) { + document.getElementById('js-system').parentElement + .classList.add('om-active'); + currentProfileClass = 'om-effective'; + } + if (state.currentProfileName === 'direct') { + document.getElementById('js-direct').parentElement + .classList.add(currentProfileClass); + } + + var profilesEnd = document.getElementById('js-profiles-end'); + var profilesContainer = profilesEnd.parentElement; + var profileCount = 0; + var charCodeUnderscore = '_'.charCodeAt(0) + var profiles = Object.keys(state.availableProfiles).map(function(key) { + return state.availableProfiles[key]; + }).sort(compareProfile); + profiles.forEach(function(profile) { + if (profile.builtin) return; + if (profile.name.charCodeAt(0) === charCodeUnderscore) return; + profileCount++; + + var profileDisp = createMenuItemForProfile(profile, + state.availableProfiles); + var link = profileDisp.querySelector('a'); + link.id = 'js-profile-' + profileCount; + link.addEventListener('click', function() { + $script.ready('om-main', function() { + OmegaPopup.applyProfile(profile.name); + }); + }); + + if (profile.name === state.currentProfileName) { + profileDisp.classList.add(currentProfileClass); + } + + if (profile.validResultProfiles) { + profileDisp.classList.add('om-has-dropdown'); + link.classList.add('om-has-edit'); + var toggle = document.createElement('div'); + toggle.classList.add('om-edit-toggle'); + var icon = document.createElement('span'); + icon.setAttribute('class', 'glyphicon glyphicon-chevron-down'); + toggle.appendChild(icon); + + toggle.addEventListener('click', function(e) { + e.stopPropagation(); + e.preventDefault(); + toggleDropdown(profileDisp, + createDefaultProfileDropdown.bind(profileDisp, profile)); + }); + + link.appendChild(toggle); + } + + profilesContainer.insertBefore(profileDisp, profilesEnd); + }); + } + + function createMenuItemForProfile(profile, profiles) { + var profileDisp = profileTemplate.cloneNode(true); + var text = profile.name; + if (profile.defaultProfileName) { + text += ' [' + profile.defaultProfileName + ']'; + } + profileDisp.querySelector('.om-profile-name').textContent = text; + + var targetProfile = profile; + if (profile.profileType === 'VirtualProfile') { + targetProfile = profiles['+' + profile.defaultProfileName]; + } + + var iconClass = iconForProfileType[targetProfile.profileType]; + var icon = profileDisp.querySelector('.glyphicon'); + icon.setAttribute('class', 'glyphicon ' + iconClass) + icon.style.color = targetProfile.color; + if (targetProfile !== profile) { + icon.classList.add('om-virtual-profile-icon'); + } + return profileDisp; + } + + function toggleDropdown(container, createDropdown) { + if (!container.classList.contains('om-dropdown-loaded')) { + var dropdown = createDropdown(); + dropdown.classList.add('om-dropdown'); + container.appendChild(dropdown); + container.classList.add('om-dropdown-loaded'); + } + if (container.classList.contains('om-open')) { + container.classList.remove('om-open'); + } else { + container.classList.add('om-open'); + } + } + + function createTempRuleDropdown() { + var ul = document.createElement('ul'); + var state = OmegaPopup.state; + var pageInfo = OmegaPopup.pageInfo; + + var profiles = state.validResultProfiles.map(function(name) { + return state.availableProfiles['+' + name]; + }).sort(compareProfile); + profiles.forEach(function(profile) { + if (profile.name.indexOf('__') === 0) return; + if ((profile.name === OmegaPopup.state.currentProfileName) && + (!pageInfo.tempRuleProfileName) && + (state.validResultProfiles.length > 1) + ) return; + var li = createMenuItemForProfile(profile, state.availableProfiles); + var link = li.querySelector('a'); + link.addEventListener('click', function() { + $script.ready('om-main', function() { + OmegaPopup.addTempRule(pageInfo.domain, profile.name); + }); + }); + if (profile.name === pageInfo.tempRuleProfileName) { + li.classList.add('om-active'); + } + ul.appendChild(li); + }); + return ul; + } + + function createDefaultProfileDropdown(profile) { + var ul = document.createElement('ul'); + var state = OmegaPopup.state; + var profiles = profile.validResultProfiles.map(function(name) { + return state.availableProfiles['+' + name]; + }).sort(compareProfile); + profiles.forEach(function(resultProfile) { + if (resultProfile.name.indexOf('__') === 0) return; + if ((resultProfile === profile.currentProfileName) && + (profile.validResultProfiles.length > 1) + ) return; + var li = createMenuItemForProfile(resultProfile, state.availableProfiles); + var link = li.querySelector('a'); + link.addEventListener('click', function() { + $script.ready('om-main', function() { + OmegaPopup.setDefaultProfile(profile.name, resultProfile.name); + }); + }); + if (resultProfile.name === profile.currentProfileName) { + li.classList.add('om-active'); + } + ul.appendChild(li); + }); + return ul; + } +})(); diff --git a/omega-web/src/popup/js/proxy_not_controllable.js b/omega-web/src/popup/js/proxy_not_controllable.js new file mode 100644 index 0000000..609c6c9 --- /dev/null +++ b/omega-web/src/popup/js/proxy_not_controllable.js @@ -0,0 +1,29 @@ +(function() { + var closeButton = document.getElementById('js-close'); + closeButton.addEventListener('click', window.close.bind(window), false); + + var manageButton = document.getElementById('js-manage-ext'); + manageButton.addEventListener('click', + OmegaTargetPopup.openManage.bind(OmegaTargetPopup), false); + + closeButton.textContent = OmegaTargetPopup.getMessage('dialog_cancel'); + manageButton.textContent = OmegaTargetPopup.getMessage( + 'popup_proxyNotControllableManage'); + + + OmegaTargetPopup.getState([ + 'proxyNotControllable', + ], function(err, state) { + var reason = state.proxyNotControllable; + var messageElement = document.getElementById('js-nc-text'); + var detailsElement = document.getElementById('js-nc-details'); + messageElement.textContent = OmegaTargetPopup.getMessage( + 'popup_proxyNotControllable_' + reason); + var detailsMessage = OmegaTargetPopup.getMessage( + 'popup_proxyNotControllableDetails_' + reason); + if (!detailsMessage) detailsMessage = OmegaTargetPopup.getMessage( + 'popup_proxyNotControllableDetails'); + + detailsElement.textContent = detailsMessage; + }); +})(); diff --git a/omega-web/src/popup/proxy_not_controllable.html b/omega-web/src/popup/proxy_not_controllable.html new file mode 100644 index 0000000..e9728d7 --- /dev/null +++ b/omega-web/src/popup/proxy_not_controllable.html @@ -0,0 +1,20 @@ + + + + + SwitchyOmega Popup + + + +
+

+

+

+ + +

+
+ + + +