diff --git a/keepassxc-browser/_locales/en/messages.json b/keepassxc-browser/_locales/en/messages.json index 408652a..4a47f0b 100644 --- a/keepassxc-browser/_locales/en/messages.json +++ b/keepassxc-browser/_locales/en/messages.json @@ -395,9 +395,9 @@ "message": "About", "description": "About page header." }, - "optionsButtonConfigureShortcuts": { - "message": "Configure shortcuts", - "description": "Keyboard shortcut button text." + "optionsConfigureShortcuts": { + "message": " Configure shortcuts", + "description": "Keyboard shortcut button and title text." }, "optionsButtonSave": { "message": "Save", @@ -426,6 +426,10 @@ "optionsButtonCancel": { "message": "Cancel", "description": "Cancel button text when removing database key or custom login fields." + }, + "optionsButtonReset": { + "message": "Reset", + "description": "Reset button text for keyboard shortcuts." }, "optionsLabelBlinkTime": { "message": "Blink Time:", @@ -579,6 +583,18 @@ "message": "Recommended: $1", "description": "Recommended setting text." }, + "optionsShortcutsSuccess": { + "message": "Shortcut for $1 has been successfully changed.", + "description": "Message for successful keyboard shortcut change." + }, + "optionsShortcutsInfo": { + "message": "Shortcut for $1 has been reset.", + "description": "Info message for changing keyboard shortcuts." + }, + "optionsShortcutsDanger": { + "message": "Error: Shortcut for $1 has not been changed!", + "description": "Error message for changing keyboard shortcuts." + }, "optionsConnectedDatabasesText": { "message": "The following KeePassXC databases are connected to KeePassXC-Browser.", "description": "Info text about connected databases." diff --git a/keepassxc-browser/options/options.html b/keepassxc-browser/options/options.html index 40eff33..389d259 100644 --- a/keepassxc-browser/options/options.html +++ b/keepassxc-browser/options/options.html @@ -35,7 +35,7 @@ : error
: error

-

+

diff --git a/keepassxc-browser/options/options.js b/keepassxc-browser/options/options.js index 8e20443..cc9c85a 100644 --- a/keepassxc-browser/options/options.js +++ b/keepassxc-browser/options/options.js @@ -126,8 +126,10 @@ options.initGeneralSettings = function() { }); }); - $('#configureCommands').click(function(){ - browser.tabs.create({ url: 'chrome://extensions/configureCommands' }); + $('#configureCommands').click(function() { + browser.tabs.create({ + url: isFirefox() ? browser.runtime.getURL("options/shortcuts.html") : 'chrome://extensions/configureCommands' + }); }); $('#blinkTimeoutButton').click(function(){ @@ -392,7 +394,9 @@ options.initSitePreferences = function() { options.initAbout = function() { $('#tab-about em.versionCIP').text(browser.runtime.getManifest().version); - if (isFirefox()) { + + // Hides keyboard shortcut configure button if Firefox version is < 60 (API is not compatible) + if (isFirefox() && Number(navigator.userAgent.substr(navigator.userAgent.lastIndexOf('/')+1, 2)) < 60) { $('#chrome-only').remove(); } }; diff --git a/keepassxc-browser/options/shortcuts.css b/keepassxc-browser/options/shortcuts.css new file mode 100644 index 0000000..40d852d --- /dev/null +++ b/keepassxc-browser/options/shortcuts.css @@ -0,0 +1,53 @@ +body { + padding-top: 20px; +} + +.conf-container { + margin: 0 auto; + width: 680px; + max-height: 315px; + background-color: white; + border: 1px solid #ccc; + border-radius: 4px; + box-shadow: 0 4px 6px 0 hsla(0, 0%, 0%, 0.2); +} + +.conf-container .conf-title { + margin: 10px 0 0 20px; + display: flex; + flex-direction: row; + align-items: center; +} + +.conf-title #icon { + width: 3em; +} + +.conf-container > hr { + margin: 10px 0; +} + +.conf-content { + margin: 10px 0 20px 40px; +} + +#conf-row { + display: flex; + align-items: center; + justify-content: flex-start; +} + +#conf-row div:first-child { + width: 200px; + margin: 10px; +} + +#conf-row button { + margin-left: 10px; +} + +.alert { + box-shadow: 0 4px 6px 0 hsla(0, 0%, 0%, 0.2); + margin: 20px auto; + width: 680px; +} \ No newline at end of file diff --git a/keepassxc-browser/options/shortcuts.html b/keepassxc-browser/options/shortcuts.html new file mode 100644 index 0000000..c516fca --- /dev/null +++ b/keepassxc-browser/options/shortcuts.html @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + +
+
+
+ logo +
+
+
+
+
+
+
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + diff --git a/keepassxc-browser/options/shortcuts.js b/keepassxc-browser/options/shortcuts.js new file mode 100644 index 0000000..e37dce1 --- /dev/null +++ b/keepassxc-browser/options/shortcuts.js @@ -0,0 +1,117 @@ +'use strict'; + +let tempArray = []; +let keyArray = []; + +document.querySelectorAll('input').forEach((b) => { + b.addEventListener('keydown', e => handleKeyDown(e)); + b.addEventListener('keyup', e => handleKeyUp(e)); +}); + +const saveButtons = document.querySelectorAll('.btn-primary'); +for (const b of saveButtons) { + b.addEventListener('click', e => { + updateShortcut(b.parentElement.children[1].getAttribute('id')) + }); +} + +const resetButtons = document.querySelectorAll('.btn-danger'); +for (const b of resetButtons) { + b.addEventListener('click', e => { + resetShortcut(b.parentElement.children[1].getAttribute('id')) + }); +} + +async function handleKeyDown(e) { + if (!e.repeat) { + e.currentTarget.value = ''; + + // Transform single keys to upper case for comparison + const key = e.key.length === 1 ? e.key.toUpperCase() : e.key; + tempArray.push(key); + keyArray.push(key); + } +}; + +async function handleKeyUp(e) { + if (!e.repeat) { + e.currentTarget.value = ''; + const index = tempArray.indexOf(e.key.length === 1 ? e.key.toUpperCase() : e.key); + if (index !== -1) { + tempArray.splice(index, 1); + } + + if (tempArray.length === 0) { + let text = ''; + for (let i = 0; i < keyArray.length; ++i) { + const currentText = keyArray[i] === 'Control' ? handleControl() : keyArray[i]; + text += currentText; + if (i !== keyArray.length - 1) { + text += '+'; + } + } + + keyArray = []; + e.currentTarget.value = text; + } + } +}; + +async function updateKeys() { + const commands = await browser.commands.getAll(); + for (const c of commands) { + const elem = document.getElementById(c.name); + if (elem) { + elem.value = c.shortcut; + } + } +}; + +async function updateShortcut(shortcut) { + try { + await browser.commands.update({ + name: shortcut, + shortcut: document.querySelector('#' + shortcut).value + }); + createBanner('success', shortcut); + } catch(e) { + console.log('Cannot change shortcut: ' + e); + createBanner('danger', shortcut); + } +}; + +async function resetShortcut(shortcut) { + await browser.commands.reset(shortcut); + createBanner('info', shortcut); + updateKeys(); +}; + +// Ctrl behaves differently on different OS's. macOS needs to return MacCtrl instead of Ctrl (which will be handled as Command) +function handleControl() { + return (navigator.platform === 'MacIntel') ? 'MacCtrl' : 'Ctrl'; +} + +// Possible types: success, info, danger +function createBanner(type, shortcut) { + const banner = document.createElement('div'); + banner.classList.add('alert', 'alert-dismissible', 'alert-' + type, 'fade', 'in'); + + if (type === 'success') { + banner.textContent = tr('optionsShortcutsSuccess', shortcut); + } else if (type === 'info') { + banner.textContent = tr('optionsShortcutsInfo', shortcut); + } else if (type === 'danger') { + banner.textContent = tr('optionsShortcutsDanger', shortcut); + } else { + return; + } + + document.body.appendChild(banner); + + // Destroy the banner after five seconds + setTimeout(() => { + document.body.removeChild(banner); + }, 5000); +}; + +document.addEventListener('DOMContentLoaded', updateKeys);