diff --git a/keepassxc-browser/_locales/en/messages.json b/keepassxc-browser/_locales/en/messages.json index 6b063d1..970bbee 100644 --- a/keepassxc-browser/_locales/en/messages.json +++ b/keepassxc-browser/_locales/en/messages.json @@ -786,6 +786,10 @@ "message": "Use monochrome toolbar icon", "description": "Use monochrome toolbar icon checkbox text." }, + "optionsCheckboxUseCompactMode": { + "message": "Use compact mode", + "description": "Whether the compact mode (legacy dropdown) should be enabled." + }, "optionsSaveDomainOnly": { "message": "Save domain only", "description": "Save domain only checkbox text." @@ -915,7 +919,7 @@ "description": "Autocomplete Usernames option help text." }, "optionsShowGroupNameInAutocompleteHelpText": { - "message": "When credentials are returned from different groups, the group name will be shown.", + "message": "When credentials are returned from different groups, the group name will be shown. Only affects the compact mode.", "description": "Show group name in autocomplete help text." }, "optionsShowNotificationsHelpText": { @@ -926,6 +930,10 @@ "message": "Use a monochrome icon with colored badges in toolbar that adapts to light/dark browser theme.", "description": "Use monochrome toolbar icon option help text." }, + "optionsUseCompactModeHelpText": { + "message": "Use a compact version of the dropdown list to select credentials.", + "description": "Use compact (legacy) mode help text." + }, "optionsSaveDomainOnlyHelpText": { "message": "When saving new credentials, save only the domain instead of full URL.", "description": "Save domain only option help text." diff --git a/keepassxc-browser/background/page.js b/keepassxc-browser/background/page.js index 9ed6cc4..8a4504d 100755 --- a/keepassxc-browser/background/page.js +++ b/keepassxc-browser/background/page.js @@ -4,7 +4,6 @@ const defaultSettings = { afterFillSorting: SORT_BY_MATCHING_CREDENTIALS_SETTING, afterFillSortingTotp: SORT_BY_RELEVANT_ENTRY, autoCompleteUsernames: true, - showGroupNameInAutocomplete: true, autoFillAndSend: false, autoFillSingleEntry: false, autoFillSingleTotp: false, @@ -24,12 +23,14 @@ const defaultSettings = { redirectAllowance: 1, saveDomainOnly: true, showGettingStartedGuideAlert: true, - showTroubleshootingGuideAlert: true, + showGroupNameInAutocomplete: true, showLoginFormIcon: true, showLoginNotifications: true, showNotifications: true, - useMonochromeToolbarIcon: false, showOTPIcon: true, + showTroubleshootingGuideAlert: true, + useCompactMode: false, + useMonochromeToolbarIcon: false, useObserver: true, usePredefinedSites: true, usePasswordGeneratorIcons: false, diff --git a/keepassxc-browser/content/autocomplete.js b/keepassxc-browser/content/autocomplete.js index 2ae3384..541602b 100644 --- a/keepassxc-browser/content/autocomplete.js +++ b/keepassxc-browser/content/autocomplete.js @@ -16,6 +16,8 @@ class Autocomplete { this.elements = []; this.index = -1; this.input = undefined; + this.showGroup = true; + this.useCompactMode = false; this.wrapper = undefined; this.shadowRoot = undefined; this.container = undefined; @@ -25,25 +27,28 @@ class Autocomplete { this.elements = []; } - async click(e) { + async click(e) {} - } + async itemClick(e, input, uuid) {} - async itemClick(e, input, uuid) { + async itemEnter(index, item) {} - } - - async itemEnter(index, item) { - - } - - async create(input, showListInstantly = false, autoSubmit = false, afterFillSort = SORT_BY_MATCHING_CREDENTIALS_SETTING) { + async create( + input, + showListInstantly = false, + autoSubmit = false, + useCompactMode = false, + showGroup = true, + afterFillSort = SORT_BY_MATCHING_CREDENTIALS_SETTING, + ) { if (input.readOnly) { return; } this.autoSubmit = autoSubmit; this.afterFillSort = afterFillSort; + this.showGroup = showGroup; + this.useCompactMode = useCompactMode; if (!this.autocompleteList.includes(input)) { input.addEventListener('click', e => this.click(e)); @@ -63,9 +68,12 @@ class Autocomplete { return; } this.deselectItem(); - e.target.classList.add('kpxcAutocomplete-active'); + + const itemContainer = e.target.classList.contains('kpxcAutocomplete-item') ? e.target : e.target.parentElement; + itemContainer.classList.add('kpxcAutocomplete-item--active'); + const items = this.getAllItems(); - this.index = Array.from(items).indexOf(e.target); + this.index = Array.from(items).indexOf(itemContainer); } async showList(inputField) { @@ -89,14 +97,21 @@ class Autocomplete { this.wrapper = kpxcUI.createElement('div'); this.wrapper.style.all = 'unset'; this.wrapper.style.display = 'none'; - styleSheet.addEventListener('load', () => this.wrapper.style.display = 'block'); - this.container = kpxcUI.createElement('div', 'kpxcAutocomplete-container', { 'id': 'kpxcAutocomplete-container' }); + styleSheet.addEventListener('load', () => (this.wrapper.style.display = 'block')); + this.container = kpxcUI.createElement('div', 'kpxcAutocomplete-container', { + id: 'kpxcAutocomplete-container', + }); + + // Apply compact mode class + if (kpxc.settings.useCompactMode) { + this.container.classList.add('kpxcAutocomplete-container--compact'); + } this.shadowRoot = this.wrapper.attachShadow({ mode: 'closed' }); this.shadowRoot.append(colorStyleSheet); this.shadowRoot.append(styleSheet); - this.list = kpxcUI.createElement('div', 'kpxcAutocomplete-items', { 'id': 'kpxcAutocomplete-list' }); + this.list = kpxcUI.createElement('div', 'kpxcAutocomplete-items', { id: 'kpxcAutocomplete-list' }); initColorTheme(this.container); this.container.append(this.list); @@ -111,7 +126,7 @@ class Autocomplete { } this.updateList(); - this.container.style.display = 'block'; + this.container.classList.add('kpxcAutocomplete-container--visible'); this.updatePosition(); } @@ -131,45 +146,85 @@ class Autocomplete { // Update credentials to menu div for (const c of this.elements) { - const item = document.createElement('div'); - item.textContent = c.label; - item.setAttribute('uuid', c.uuid); + const item = this.generateItem(c); - const itemInput = kpxcUI.createElement('input', '', { 'type': 'hidden', 'value': c.value }); + const itemInput = kpxcUI.createElement('input', '', { type: 'hidden', value: c.value }); item.append(itemInput); item.addEventListener('click', e => this.itemClick(e, this.input, c.uuid)); // These events prevent the double hover effect if both keyboard and mouse are used item.addEventListener('mousemove', e => this.mouseMove(e)); - item.addEventListener('mousedown', e => e.stopPropagation()); item.addEventListener('mouseup', e => e.stopPropagation()); // If this page has an associated uuid and it matches this credential, then put it on top of the list - if (username === c.value - || (this.afterFillSort === SORT_BY_RELEVANT_ENTRY && c.uuid === pageUuid)) { + if (username === c.value || (this.afterFillSort === SORT_BY_RELEVANT_ENTRY && c.uuid === pageUuid)) { this.list.prepend(item); } else { this.list.appendChild(item); } } + // Use only two columns if groups are not shown + if (this.useCompactMode && !this.showGroup) { + this.list.style.gridTemplateColumns = 'repeat(2, auto)'; + } + this.selectItem(); } + // Add parentElement parameter + generateItem(credential) { + // Create the DOM element for the list item wrapper. + const item = kpxcUI.createElement('div', 'kpxcAutocomplete-item', { + title: `${credential.title} | ${credential.value} | ${credential.group}` + }); + item.setAttribute('uuid', credential.uuid); + + // Create the DOM element for the list item header. + const itemHeader = document.createElement('div'); + itemHeader.classList.add('kpxcAutocomplete-item__header'); + + // Create the DOM element for showing the group only when the group name is available. + if (!this.useCompactMode) { + const itemGroup = kpxcUI.createElement('div', 'kpxcAutocomplete-item__group', {}, credential.group); + itemHeader.append(itemGroup); + } + + // Create the DOM element for showing the credentials name. + const itemLabel = kpxcUI.createElement('div', 'kpxcAutocomplete-item__label', {}, credential.title); + itemHeader.append(itemLabel); + + // Append the header to the list item. + item.append(itemHeader); + + // Create the DOM element for showing the credentials username field. + const itemClassName = this.showGroup ? 'kpxcAutocomplete-item__value' : 'kpxcAutocomplete-item__value--last'; + const itemValue = kpxcUI.createElement('div', itemClassName, {}, credential.value); + item.append(itemValue); + + // If compact mode is enabled, append group to the end + if (this.useCompactMode && this.showGroup) { + const itemGroup = kpxcUI.createElement('div', 'kpxcAutocomplete-item__group', {}, credential.group); + item.append(itemGroup); + } + + return item; + } + selectItem() { this.deselectItem(); const items = this.getAllItems(); const item = items[this.index]; if (item !== undefined) { - item.classList.add('kpxcAutocomplete-active'); + item.classList.add('kpxcAutocomplete-item--active'); item.scrollIntoView({ block: 'nearest' }); } } deselectItem() { - const items = this.list.querySelectorAll('div.kpxcAutocomplete-active'); - items.forEach(item => item.classList.remove('kpxcAutocomplete-active')); + const items = this.list.querySelectorAll('div.kpxcAutocomplete-item--active'); + items.forEach(item => item.classList.remove('kpxcAutocomplete-item--active')); } closeList() { @@ -178,11 +233,11 @@ class Autocomplete { return; } - this.container.style.display = 'none'; + this.container.classList.remove('kpxcAutocomplete-container--visible'); } getAllItems() { - return this.list.getElementsByTagName('div'); + return this.list.getElementsByClassName('kpxcAutocomplete-item'); } /** @@ -191,7 +246,7 @@ class Autocomplete { * - ArrowUp selects item above, or the last item (first is active) * - Enter or Tab selects the item * - Backspace and Delete shows the list if input field is empty. First item is activated - */ + */ async keyDown(e) { if (!e.isTrusted) { return; @@ -211,7 +266,7 @@ class Autocomplete { } else { // Activate next item const items = this.getAllItems(); - this.index = (this.index+1) % items.length; + this.index = (this.index + 1) % items.length; this.selectItem(); } } else if (e.key === 'ArrowUp' && this.list) { @@ -274,23 +329,18 @@ class Autocomplete { return; } - const rect = this.input.getBoundingClientRect(); - this.container.style.minWidth = Pixels(this.input.offsetWidth); - // Calculate Y offset if menu does not fit to the bottom of the screen -> show it at the top of the input field + const rect = this.input.getBoundingClientRect(); const menuRect = this.container.getBoundingClientRect(); const totalHeight = menuRect.height + rect.height; - const menuOffset = (totalHeight + rect.y) > window.self.visualViewport.height ? totalHeight : 0; - if (menuOffset > 0) { - this.container.classList.add('kpxcAutocomplete-container-on-top'); - } else { - this.container.classList.remove('kpxcAutocomplete-container-on-top'); - } + const menuOffset = totalHeight + rect.y > window.self.visualViewport.height ? totalHeight : 0; const scrollTop = kpxcUI.getScrollTop(); const scrollLeft = kpxcUI.getScrollLeft(); if (kpxcUI.bodyStyle.position.toLowerCase() === 'relative') { - this.container.style.top = Pixels(rect.top - kpxcUI.bodyRect.top + scrollTop + this.input.offsetHeight - menuOffset); + this.container.style.top = Pixels( + rect.top - kpxcUI.bodyRect.top + scrollTop + this.input.offsetHeight - menuOffset, + ); this.container.style.left = Pixels(rect.left - kpxcUI.bodyRect.left + scrollLeft); } else { this.container.style.top = Pixels(rect.top + scrollTop + this.input.offsetHeight - menuOffset); diff --git a/keepassxc-browser/content/credential-autocomplete.js b/keepassxc-browser/content/credential-autocomplete.js index 800f362..5346619 100644 --- a/keepassxc-browser/content/credential-autocomplete.js +++ b/keepassxc-browser/content/credential-autocomplete.js @@ -23,7 +23,7 @@ CredentialAutocomplete.prototype.itemClick = async function(e, input, uuid) { e.stopPropagation(); const index = Array.prototype.indexOf.call(e.currentTarget.parentElement.childNodes, e.currentTarget); - const usernameValue = e.target.getElementsByTagName('input')[0].value; + const usernameValue = e.currentTarget.getElementsByTagName('input')[0]?.value; await this.fillPassword(usernameValue, index, uuid); this.closeList(); diff --git a/keepassxc-browser/content/keepassxc-browser.js b/keepassxc-browser/content/keepassxc-browser.js index 21dfc8d..6726045 100755 --- a/keepassxc-browser/content/keepassxc-browser.js +++ b/keepassxc-browser/content/keepassxc-browser.js @@ -269,14 +269,35 @@ kpxc.initAutocomplete = function() { for (const c of kpxc.combinations) { if (c.username) { - kpxcUserAutocomplete.create(c.username, false, kpxc.settings.autoSubmit, kpxc.settings.afterFillSorting); + kpxcUserAutocomplete.create( + c.username, + false, + kpxc.settings.autoSubmit, + kpxc.settings.useCompactMode, + kpxc.settings.showGroupNameInAutocomplete, + kpxc.settings.afterFillSorting, + ); } else if (!c.username && c.password) { // Single password field - kpxcUserAutocomplete.create(c.password, false, kpxc.settings.autoSubmit, kpxc.settings.afterFillSorting); + kpxcUserAutocomplete.create( + c.password, + false, + kpxc.settings.autoSubmit, + kpxc.settings.useCompactMode, + kpxc.settings.showGroupNameInAutocomplete, + kpxc.settings.afterFillSorting, + ); } if (c.totp) { - kpxcTOTPAutocomplete.create(c.totp, false, kpxc.settings.autoSubmit, kpxc.settings.afterFillSortingTotp); + kpxcTOTPAutocomplete.create( + c.totp, + false, + kpxc.settings.autoSubmit, + kpxc.settings.useCompactMode, + kpxc.settings.showGroupNameInAutocomplete, + kpxc.settings.afterFillSortingTotp, + ); } } }; @@ -382,7 +403,7 @@ kpxc.initLoginPopup = function() { return { title: title, - group: group, + group: credential.group, visibleLogin: visibleLogin, login: credential.login, loginId: loginId, @@ -440,6 +461,8 @@ kpxc.initLoginPopup = function() { popupLoginItems.push({ text: l.text, uuid: l.uuid }); kpxcUserAutocomplete.elements.push({ + group: l.group, + title: l.title, label: l.text, value: l.login, uuid: l.uuid, diff --git a/keepassxc-browser/css/autocomplete.css b/keepassxc-browser/css/autocomplete.css index 4710e20..a77a763 100644 --- a/keepassxc-browser/css/autocomplete.css +++ b/keepassxc-browser/css/autocomplete.css @@ -1,18 +1,22 @@ .kpxcAutocomplete-container { - display: none; - z-index: 2147483646; - position: absolute !important; + background-color: var(--kpxc-background-color); border: var(--kpxc-autocomplete-menu-border); - border-bottom-right-radius: 4px; - border-bottom-left-radius: 4px; - overflow: hidden; /* this fixes an issue with the border radius not showing up clearly */ + border-radius: 8px; + box-shadow: var(--kpxc-box-shadow); + box-sizing: border-box; + display: none; + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + font-size: 14px; + line-height: 1em; + min-width: 16rem; + overflow: hidden; /* Prevent the content from bleeding over the border radius. */ + padding: 6px; + position: absolute !important; + z-index: 2147483646; } -.kpxcAutocomplete-container-on-top { - border-bottom-right-radius: 0px; - border-bottom-left-radius: 0px; - border-top-right-radius: 4px; - border-top-left-radius: 4px; +.kpxcAutocomplete-container--visible { + display: block; } .kpxcAutocomplete-items { @@ -20,37 +24,138 @@ overflow-y: auto; } -.kpxcAutocomplete-items div { - padding: 5px; - cursor: pointer; +.kpxcAutocomplete-item { background-color: var(--kpxc-background-color); - border-bottom: 1px solid rgba(0,0,0,.125); - width: auto; + border-radius: 4px; color: var(--kpxc-text-color); - font-size: max(10px, .9em) !important; + cursor: pointer; letter-spacing: normal !important; - word-spacing: normal !important; - text-rendering: auto !important; + padding: 12px; text-align: start !important; + text-rendering: auto !important; + width: auto; + word-spacing: normal !important; } -.kpxcAutocomplete-items div:last-child { +.kpxcAutocomplete-container--compact .kpxcAutocomplete-items { + cursor: pointer; + display: grid; + grid-template-columns: repeat(3, auto); + max-height: 250px; + max-width: 600px; + overflow-y: auto; + overflow-x: hidden; +} + +.kpxcAutocomplete-container--compact .kpxcAutocomplete-item { + display: contents; +} + +.kpxcAutocomplete-container--compact .kpxcAutocomplete-item__header { + align-self: flex-start; + border-radius: 4px 0 0 4px; + display: grid; + height: 14px; + padding: 6px 24px 6px 6px; + pointer-events: auto; +} + +.kpxcAutocomplete-container--compact .kpxcAutocomplete-item__group { + align-self: center; + border-radius: 0 4px 4px 0; + display: flex; + height: 14px; + justify-content: flex-end; + padding: 6px 6px 6px 24px; + pointer-events: auto; +} + +.kpxcAutocomplete-container--compact .kpxcAutocomplete-item__value { + align-self: flex-end; + height: 14px; + justify-self: start; + margin: 0; + padding: 6px 0 6px 0; + pointer-events: auto; + width: 100%; +} + +/* Separate class for value if group is not shown */ +.kpxcAutocomplete-container--compact .kpxcAutocomplete-item__value--last { + align-self: center; + border-radius: 0 4px 4px 0; + color: var(--kpxc-lighter-text-color); + display: flex; + font-size: .85em; + height: 14px; + justify-content: flex-end; + overflow: hidden; + padding: 6px 6px 6px 24px; + pointer-events: auto; + text-overflow: ellipsis; + white-space: nowrap; +} + +.kpxcAutocomplete-item:last-child { border-bottom: none; } -.kpxcAutocomplete-active { +.kpxcAutocomplete-item__header { + align-items: center; + display: flex; + flex-direction: row-reverse; + gap: 6px; + justify-content: space-between; +} + +.kpxcAutocomplete-item__group { + color: var(--kpxc-light-text-color); + font-size: .7em; + font-weight: bold; + letter-spacing: 0.05em; + overflow: hidden; + pointer-events: none; + text-overflow: ellipsis; + text-transform: uppercase; + white-space: nowrap; +} + +.kpxcAutocomplete-item__label { + font-size: .9em; + font-weight: bold; + overflow: hidden; + pointer-events: none; + text-overflow: ellipsis; + white-space: nowrap +} + +.kpxcAutocomplete-item__value { + color: var(--kpxc-lighter-text-color); + font-size: .85em; + margin-top: 4px; + overflow: hidden; + pointer-events: none; + text-overflow: ellipsis; + white-space: nowrap; +} + +.kpxcAutocomplete-item--active { + background-color: #28a745 !important; + color: #ffffff !important; +} + +.kpxcAutocomplete-item--active * { background-color: #28a745 !important; color: #ffffff !important; } .kpxcAutocomplete-container footer { - padding: 5px; - background-color: #ddd; - border-top: 1px solid rgba(0,0,0,.125) !important; - width: auto; - color: #000; + background-color: var(--kpxc-autocomplete-footer-color); + border-radius: 0 0 4px 4px; + color: var(--kpxc-lighter-text-color); font-size: max(9px, .8em) !important; - font-style: italic; + padding: 5px; + width: auto; } @media (prefers-color-scheme: dark) { @@ -62,11 +167,4 @@ background: var(--kpxc-background-color); color: var(--kpxc-text-color); } - - .kpxc-pwgen-input { - background-color: var(--kpxc-input-background-color) !important; - border: var(--kpxc-input-border); - border-color: var(--kpxc-input-border-color); - color: var(--kpxc-text-color) !important; - } } diff --git a/keepassxc-browser/css/colors.css b/keepassxc-browser/css/colors.css index 905a55a..1e2dc38 100644 --- a/keepassxc-browser/css/colors.css +++ b/keepassxc-browser/css/colors.css @@ -1,8 +1,11 @@ -:root, :host { +:root, +:host { + --kpxc-autocomplete-footer-color: #f2f2f2; --kpxc-autocomplete-menu-border: 1px solid #ddd; --kpxc-background-color: #fff; + --kpxc-box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); --kpxc-card-background-color: #fff; - --kpxc-card-border-color: rgba(0,0,0,.125); + --kpxc-card-border-color: rgba(0, 0, 0, .125); --kpxc-card-header-color: #fafafa; --kpxc-checkbox-color: #5b5b5d; --kpxc-checkbox-background-color: #2b2a2a; @@ -11,7 +14,7 @@ --kpxc-input-background-color: #fff; --kpxc-input-border: 1px solid #2b4d1a; --kpxc-input-border-color: #292a2a; - --kpxc-input-main-border-color: rgba(0,0,0,.125); + --kpxc-input-main-border-color: rgba(0, 0, 0, .125); --kpxc-link-color: #3a8233; --kpxc-link-hover-color: #429f14; --kpxc-sidebar-background-color: #27272a; @@ -19,10 +22,15 @@ --kpxc-table-hover-color: #f2f2f2; --kpxc-table-odd-color: #f2f2f2; --kpxc-text-color: #000; + --kpxc-lighter-text-color: rgba(0, 0, 0, 0.8); + --kpxc-light-text-color: rgba(0, 0, 0, 0.5); } @media (prefers-color-scheme: dark) { - :root, :host { + + :root, + :host { + --kpxc-autocomplete-footer-color: #2b2a2a; --kpxc-autocomplete-menu-border: 1px solid #3b3b3d; --kpxc-background-color: #3b3b3d; --kpxc-card-background-color: #2b2a2a; @@ -42,11 +50,14 @@ --kpxc-table-hover-color: #474948; --kpxc-table-odd-color: #383a39; --kpxc-text-color: #cbcfcb; + --kpxc-lighter-text-color: rgba(203, 207, 203, 0.8); + --kpxc-light-text-color: rgba(203, 207, 203, 0.5); } } /* Same colors for manual switching */ [data-bs-theme='dark'] { + --kpxc-autocomplete-footer-color: #2b2a2a; --kpxc-autocomplete-menu-border: 1px solid #3b3b3d; --kpxc-background-color: #3b3b3d; --kpxc-card-background-color: #2b2a2a; @@ -66,13 +77,17 @@ --kpxc-table-hover-color: #474948; --kpxc-table-odd-color: #383a39; --kpxc-text-color: #cbcfcb; + --kpxc-lighter-text-color: rgba(203, 207, 203, 0.8); + --kpxc-light-text-color: rgba(203, 207, 203, 0.5); } [data-bs-theme='light'] { + --kpxc-autocomplete-footer-color: #f2f2f2; --kpxc-autocomplete-menu-border: 1px solid #ddd; --kpxc-background-color: #fff; + --kpxc-box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); --kpxc-card-background-color: #fff; - --kpxc-card-border-color: rgba(0,0,0,.125); + --kpxc-card-border-color: rgba(0, 0, 0, .125); --kpxc-card-header-color: #fafafa; --kpxc-checkbox-color: #5b5b5d; --kpxc-checkbox-background-color: #5b5b5d; @@ -81,11 +96,14 @@ --kpxc-input-background-color: #fff; --kpxc-input-border: 1px solid #2b4d1a; --kpxc-input-border-color: #292a2a; - --kpxc-input-main-border-color: rgba(0,0,0,.125); + --kpxc-input-main-border-color: rgba(0, 0, 0, .125); --kpxc-link-color: #3a8233; --kpxc-link-hover-color: #429f14; --kpxc-table-border-color: rgb(222, 226, 230); --kpxc-table-hover-color: #f2f2f2; --kpxc-table-odd-color: #f2f2f2; --kpxc-text-color: #000; + --kpxc-lighter-text-color: rgba(0, 0, 0, 0.8); + --kpxc-light-text-color: rgba(0, 0, 0, 0.5); +} } diff --git a/keepassxc-browser/options/options.html b/keepassxc-browser/options/options.html index 90c21dd..72090a8 100644 --- a/keepassxc-browser/options/options.html +++ b/keepassxc-browser/options/options.html @@ -136,6 +136,15 @@
+ + +
+
+ + +
+
+