From ef455deb0a7baa824c6b35f6d9010caaa4d9b204 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Wed, 18 Jul 2018 18:00:55 -0400 Subject: [PATCH] fix https://github.com/uBlockOrigin/uBlock-issues/issues/106 --- src/_locales/en/messages.json | 8 ++ src/css/dashboard.css | 4 + src/css/shortcuts.css | 61 ++++++++++ src/dashboard.html | 2 + src/js/background.js | 2 + src/js/dashboard.js | 51 ++++---- src/js/messaging.js | 50 +++++++- src/js/shortcuts.js | 223 ++++++++++++++++++++++++++++++++++ src/js/start.js | 13 ++ src/shortcuts.html | 37 ++++++ 10 files changed, 425 insertions(+), 26 deletions(-) create mode 100644 src/css/shortcuts.css create mode 100644 src/js/shortcuts.js create mode 100644 src/shortcuts.html diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index 43d3ca5e0..d76595780 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -31,6 +31,10 @@ "message":"Whitelist", "description":"appears as tab name in dashboard" }, + "shortcutsPageName":{ + "message":"Shortcuts", + "description":"appears as tab name in dashboard" + }, "statsPageName":{ "message":"uBlock₀ — Logger", "description":"Title for the logger window" @@ -763,6 +767,10 @@ "message": "Temporarily allow large media elements", "description": "A context menu entry, present when large media elements have been blocked on the current site" }, + "shortcutCapturePlaceholder": { + "message": "Type a shortcut", + "description": "Placeholder string for input field used to capture a keyboard shortcut" + }, "dummy":{ "message":"This entry must be the last one", "description":"so we dont need to deal with comma for last entry" diff --git a/src/css/dashboard.css b/src/css/dashboard.css index ba9bd678d..b5e78a6bc 100644 --- a/src/css/dashboard.css +++ b/src/css/dashboard.css @@ -73,6 +73,10 @@ iframe { width: 100%; } +body:not(.canUpdateShortcuts) .tabButton[href="#shortcuts.html"] { + display: none; + } + @media (max-width: 640px) { #dashboard-nav { position: relative; diff --git a/src/css/shortcuts.css b/src/css/shortcuts.css new file mode 100644 index 000000000..d188c7f9b --- /dev/null +++ b/src/css/shortcuts.css @@ -0,0 +1,61 @@ +/** + uBlock Origin - a browser extension to block requests. + Copyright (C) 2018-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +.commandEntries { + margin: 2em; + } + +.commandEntries td { + padding: 0.5em 0.25em; + } + +.commandEntries td.commandDesc { + text-align: end; + } + +.commandEntries td.commandShortcut { + white-space: nowrap; + } + +.commandEntries td.commandShortcut input { + padding: 0.4em; + } + +.commandEntries td.commandShortcut input:focus { + outline: 2px solid blue; + } + +.commandEntries td.commandShortcut input ~ .commandReset { + color: #888; + cursor: pointer; + font-size: 150%; + padding: 0 0.2em; + vertical-align: middle; + } + +.commandEntries td.commandShortcut input ~ .commandReset:hover { + background-color: #eee; + color: black; + } + +.commandEntries td.commandShortcut input:placeholder-shown ~ .commandReset, +.commandEntries td.commandShortcut input:focus ~ .commandReset { + display: none; + } diff --git a/src/dashboard.html b/src/dashboard.html index d5eddc789..0c653a57e 100644 --- a/src/dashboard.html +++ b/src/dashboard.html @@ -18,6 +18,7 @@ --> @@ -26,6 +27,7 @@ + diff --git a/src/js/background.js b/src/js/background.js index 8739edd20..d711a615a 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -148,6 +148,8 @@ var µBlock = (function() { // jshint ignore:line lastBackupTime: 0 }, + commandShortcuts: new Map(), + // Allows to fully customize uBO's assets, typically set through admin // settings. The content of 'assets.json' will also tell which filter // lists to enable by default when uBO is first installed. diff --git a/src/js/dashboard.js b/src/js/dashboard.js index 052c838eb..d038c5d01 100644 --- a/src/js/dashboard.js +++ b/src/js/dashboard.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2014-2016 Raymond Hill + Copyright (C) 2014-present Raymond Hill This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -29,34 +29,34 @@ /******************************************************************************/ -var resizeFrame = function() { - var navRect = document.getElementById('dashboard-nav').getBoundingClientRect(); - var viewRect = document.documentElement.getBoundingClientRect(); - document.getElementById('iframe').style.setProperty('height', (viewRect.height - navRect.height) + 'px'); +let resizeFrame = function() { + let navRect = document.getElementById('dashboard-nav').getBoundingClientRect(); + let viewRect = document.documentElement.getBoundingClientRect(); + document.getElementById('iframe').style.setProperty( + 'height', + (viewRect.height - navRect.height) + 'px' + ); }; -/******************************************************************************/ - -var loadDashboardPanel = function() { - var pane = window.location.hash.slice(1); +let loadDashboardPanel = function() { + let pane = window.location.hash.slice(1); if ( pane === '' ) { - pane = vAPI.localStorage.getItem('dashboardLastVisitedPane') || 'settings.html'; + pane = vAPI.localStorage.getItem('dashboardLastVisitedPane'); + if ( pane === null ) { + pane = 'settings.html'; + } } else { vAPI.localStorage.setItem('dashboardLastVisitedPane', pane); } - var tabButton = uDom('[href="#' + pane + '"]'); - if ( !tabButton || tabButton.hasClass('selected') ) { - return; - } + let tabButton = uDom('[href="#' + pane + '"]'); + if ( !tabButton || tabButton.hasClass('selected') ) { return; } uDom('.tabButton.selected').toggleClass('selected', false); uDom('iframe').attr('src', pane); tabButton.toggleClass('selected', true); }; -/******************************************************************************/ - -var onTabClickHandler = function(e) { - var url = window.location.href, +let onTabClickHandler = function(e) { + let url = window.location.href, pos = url.indexOf('#'); if ( pos !== -1 ) { url = url.slice(0, pos); @@ -69,15 +69,16 @@ var onTabClickHandler = function(e) { e.preventDefault(); }; -/******************************************************************************/ - -uDom.onLoad(function() { - resizeFrame(); - window.addEventListener('resize', resizeFrame); - uDom('.tabButton').on('click', onTabClickHandler); - loadDashboardPanel(); +// https://github.com/uBlockOrigin/uBlock-issues/issues/106 +vAPI.messaging.send('dashboard', { what: 'canUpdateShortcuts' }, response => { + document.body.classList.toggle('canUpdateShortcuts', response === true); }); +resizeFrame(); +window.addEventListener('resize', resizeFrame); +uDom('.tabButton').on('click', onTabClickHandler); +loadDashboardPanel(); + /******************************************************************************/ })(); diff --git a/src/js/messaging.js b/src/js/messaging.js index a9359b1ef..bbe7fd89f 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2014-2018 Raymond Hill + Copyright (C) 2014-present Raymond Hill This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -914,6 +914,43 @@ var modifyRuleset = function(details) { /******************************************************************************/ +// Shortcuts pane + +let getShortcuts = function(callback) { + if ( vAPI.commands !== undefined ) { + vAPI.commands.getAll(commands => { + let response = []; + for ( let command of commands ) { + let desc = command.description; + let match = /^__MSG_(.+?)__$/.exec(desc); + if ( match !== null ) { + desc = vAPI.i18n(match[1]); + } + if ( desc === '' ) { continue; } + command.description = desc; + response.push(command); + } + callback(response); + }); + } else { + callback([]); + } +}; + +let setShortcut = function(details) { + if ( typeof vAPI.commands.update !== 'function' ) { return; } + if ( details.shortcut === undefined ) { + vAPI.commands.reset(details.name); + µb.commandShortcuts.delete(details.name); + } else { + vAPI.commands.update({ name: details.name, shortcut: details.shortcut }); + µb.commandShortcuts.set(details.name, details.shortcut); + } + vAPI.storage.set({ commandShortcuts: Array.from(µb.commandShortcuts) }); +}; + +/******************************************************************************/ + var onMessage = function(request, sender, callback) { // Async switch ( request.what ) { @@ -926,6 +963,9 @@ var onMessage = function(request, sender, callback) { case 'getLocalData': return getLocalData(callback); + case 'getShortcuts': + return getShortcuts(callback); + case 'readUserFilters': return µb.loadUserFilters(callback); @@ -940,6 +980,10 @@ var onMessage = function(request, sender, callback) { var response; switch ( request.what ) { + case 'canUpdateShortcuts': + response = typeof vAPI.commands.update === 'function'; + break; + case 'getRules': response = getRules(); break; @@ -981,6 +1025,10 @@ var onMessage = function(request, sender, callback) { resetUserData(); break; + case 'setShortcut': + setShortcut(request); + break; + case 'writeHiddenSettings': µb.changeHiddenSettings(µb.hiddenSettingsFromString(request.content)); break; diff --git a/src/js/shortcuts.js b/src/js/shortcuts.js new file mode 100644 index 000000000..3437ade1f --- /dev/null +++ b/src/js/shortcuts.js @@ -0,0 +1,223 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2018-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +'use strict'; + +(function() { + + // https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/commands#Shortcut_values + let validStatus0Codes = new Map([ + [ 'AltLeft', 'Alt' ], + [ 'ControlLeft', 'Ctrl' ], + [ 'ControlRight', 'Ctrl' ], + ]); + let validStatus1Codes = new Map([ + [ 'KeyA', 'A' ], + [ 'KeyB', 'B' ], + [ 'KeyC', 'C' ], + [ 'KeyD', 'D' ], + [ 'KeyE', 'E' ], + [ 'KeyF', 'F' ], + [ 'KeyG', 'G' ], + [ 'KeyH', 'H' ], + [ 'KeyI', 'I' ], + [ 'KeyJ', 'J' ], + [ 'KeyK', 'K' ], + [ 'KeyL', 'L' ], + [ 'KeyM', 'M' ], + [ 'KeyN', 'N' ], + [ 'KeyO', 'O' ], + [ 'KeyP', 'P' ], + [ 'KeyQ', 'Q' ], + [ 'KeyR', 'R' ], + [ 'KeyS', 'S' ], + [ 'KeyT', 'T' ], + [ 'KeyU', 'U' ], + [ 'KeyV', 'V' ], + [ 'KeyW', 'W' ], + [ 'KeyX', 'X' ], + [ 'KeyY', 'Y' ], + [ 'KeyZ', 'Z' ], + [ 'Digit0', '0' ], + [ 'Digit1', '1' ], + [ 'Digit2', '2' ], + [ 'Digit3', '3' ], + [ 'Digit4', '4' ], + [ 'Digit5', '5' ], + [ 'Digit6', '6' ], + [ 'Digit7', '7' ], + [ 'Digit8', '8' ], + [ 'Digit9', '9' ], + [ 'F1', 'F1' ], + [ 'F2', 'F2' ], + [ 'F3', 'F3' ], + [ 'F4', 'F4' ], + [ 'F5', 'F5' ], + [ 'F6', 'F6' ], + [ 'F7', 'F7' ], + [ 'F8', 'F8' ], + [ 'F9', 'F9' ], + [ 'F10', 'F10' ], + [ 'F11', 'F11' ], + [ 'F12', 'F12' ], + [ 'Comma', 'Comma' ], + [ 'Period', 'Period' ], + [ 'Home', 'Home' ], + [ 'End', 'End' ], + [ 'PageUp', 'PageUp' ], + [ 'PageDown', 'PageDown' ], + [ 'Space', 'Space' ], + [ 'Insert', 'Insert' ], + [ 'Delete', 'Delete' ], + [ 'ArrowUp', 'Up' ], + [ 'ArrowDown', 'Down' ], + [ 'ArrowLeft', 'Left' ], + [ 'ArrowRight', 'Right' ], + [ 'ShiftLeft', 'Shift' ], + [ 'ShiftRight', 'Shift' ], + ]); + + let commandNameFromElement = function(elem) { + while ( elem !== null ) { + let name = elem.getAttribute('data-name'); + if ( typeof name === 'string' && name !== '' ) { return name; } + elem = elem.parentElement; + } + }; + + let captureShortcut = function(ev) { + let input = ev.target; + let name = commandNameFromElement(input); + if ( name === undefined ) { return; } + + let before = input.value; + let after = new Set(); + let status = 0; + + let updateCapturedShortcut = function() { + return (input.value = Array.from(after).join('+')); + }; + + let blurHandler = function() { + input.removeEventListener('blur', blurHandler, true); + input.removeEventListener('keydown', keydownHandler, true); + input.removeEventListener('keyup', keyupHandler, true); + if ( status === 2 ) { + vAPI.messaging.send( + 'dashboard', + { what: 'setShortcut', name: name, shortcut: updateCapturedShortcut() } + ); + } else { + input.value = before; + } + }; + + let keydownHandler = function(ev) { + ev.preventDefault(); + ev.stopImmediatePropagation(); + if ( ev.code === 'Escape' ) { + input.blur(); + return; + } + if ( status === 0 ) { + let key = validStatus0Codes.get(ev.code); + if ( key !== undefined ) { + after.add(key); + updateCapturedShortcut(); + status = 1; + } + return; + } + /* status === 1 */ + let key = validStatus1Codes.get(ev.code); + if ( key === 'Shift' ) { + after.add('Shift'); + input.value = Array.from(after).join('+'); + return; + } + if ( key !== undefined ) { + after.add(key); + updateCapturedShortcut(); + status = 2; + input.blur(); + return; + } + }; + + let keyupHandler = function(ev) { + ev.preventDefault(); + ev.stopImmediatePropagation(); + if ( status !== 1 ) { return; } + let key = validStatus0Codes.get(ev.code); + if ( key !== undefined && after.has(key) ) { + after.clear(); + updateCapturedShortcut(); + status = 0; + return; + } + key = validStatus1Codes.get(ev.code); + if ( key === 'Shift' ) { + after.delete('Shift'); + updateCapturedShortcut(); + return; + } + }; + + input.value = ''; + input.addEventListener('blur', blurHandler, true); + input.addEventListener('keydown', keydownHandler, true); + input.addEventListener('keyup', keyupHandler, true); + }; + + let resetShortcut = function(ev) { + let name = commandNameFromElement(ev.target); + if ( name === undefined ) { return; } + + let input = document.querySelector('[data-name="' + name + '"] input'); + if ( input === null ) { return; } + input.value = ''; + vAPI.messaging.send( + 'dashboard', + { what: 'setShortcut', name: name } + ); + }; + + let onShortcutsReady = function(commands) { + if ( Array.isArray(commands) === false ) { return; } + let template = document.querySelector('#templates .commandEntry'); + let tbody = document.querySelector('.commandEntries tbody'); + for ( let command of commands ) { + if ( command.description === '' ) { continue; } + let tr = template.cloneNode(true); + tr.setAttribute('data-name', command.name); + tr.querySelector('.commandDesc').textContent = command.description; + let input = tr.querySelector('.commandShortcut input'); + input.setAttribute('data-name', command.name); + input.value = command.shortcut; + input.addEventListener('focus', captureShortcut); + tr.querySelector('.commandReset').addEventListener('click', resetShortcut); + tbody.appendChild(tr); + } + }; + + vAPI.messaging.send('dashboard', { what: 'getShortcuts' }, onShortcutsReady); + +})(); diff --git a/src/js/start.js b/src/js/start.js index ae5f018e6..d3a4f4ed1 100644 --- a/src/js/start.js +++ b/src/js/start.js @@ -103,6 +103,17 @@ var onPSLReady = function() { /******************************************************************************/ +var onCommandShortcutsReady = function(commandShortcuts) { + if ( Array.isArray(commandShortcuts) === false ) { return; } + µb.commandShortcuts = new Map(commandShortcuts); + if ( typeof vAPI.commands.update !== 'function' ) { return; } + for ( let entry of commandShortcuts ) { + vAPI.commands.update({ name: entry[0], shortcut: entry[1] }); + } +}; + +/******************************************************************************/ + // To bring older versions up to date var onVersionReady = function(lastVersion) { @@ -238,6 +249,7 @@ var onFirstFetchReady = function(fetched) { fromFetch(µb.restoreBackupSettings, fetched); onNetWhitelistReady(fetched.netWhitelist); onVersionReady(fetched.version); + onCommandShortcutsReady(fetched.commandShortcuts); µb.loadPublicSuffixList(onPSLReady); µb.loadRedirectResources(); @@ -270,6 +282,7 @@ var fromFetch = function(to, fetched) { var onSelectedFilterListsLoaded = function() { var fetchableProps = { + 'commandShortcuts': [], 'compiledMagic': '', 'dynamicFilteringString': [ 'behind-the-scene * * noop', diff --git a/src/shortcuts.html b/src/shortcuts.html new file mode 100644 index 000000000..22a72380d --- /dev/null +++ b/src/shortcuts.html @@ -0,0 +1,37 @@ + + + + + +uBlock Origin — Keyboard shortcuts + + + + + + + +
+
+
+ +