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

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