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 + + + +
+

+

+

+ + +

+
+ + + +