mirror of
https://github.com/keepassxreboot/keepassxc-browser.git
synced 2026-03-11 08:54:43 +00:00
Refactor Site Preferences options (#2666)
Refactor Site Preferences options
This commit is contained in:
parent
39c86225e3
commit
a6d11091d8
9 changed files with 224 additions and 51 deletions
|
|
@ -175,6 +175,7 @@
|
|||
"sendMessage": "readonly",
|
||||
"showNotification": "readonly",
|
||||
"siteMatch": "readonly",
|
||||
"SitePreferences": "readonly",
|
||||
"slashNeededForUrl": "readonly",
|
||||
"SORT_BY_GROUP_AND_TITLE": "readonly",
|
||||
"SORT_BY_GROUP_AND_USERNAME": "readonly",
|
||||
|
|
|
|||
|
|
@ -678,6 +678,10 @@
|
|||
"message": "Site Preferences",
|
||||
"description": "Site Preferences page header."
|
||||
},
|
||||
"optionsSitePreferencesSettings": {
|
||||
"message": "Settings",
|
||||
"description": "Site Preferences settings button text."
|
||||
},
|
||||
"optionsMenuAbout": {
|
||||
"message": "About",
|
||||
"description": "About page header."
|
||||
|
|
|
|||
|
|
@ -61,6 +61,17 @@ const ManualFill = {
|
|||
BOTH: 2
|
||||
};
|
||||
|
||||
const SitePreferences = {
|
||||
ALLOW_IFRAMES: 'allowIframes',
|
||||
IMPROVED_FIELD_DETECTION: 'improvedFieldDetection',
|
||||
USERNAME_ONLY: 'usernameOnly',
|
||||
};
|
||||
|
||||
// Returns a string with 'px' for CSS styles
|
||||
const Pixels = function(value) {
|
||||
return String(value) + 'px';
|
||||
};
|
||||
|
||||
const compareVersion = function(minimum, current, canBeEqual = true) {
|
||||
if (!minimum || !current || minimum?.indexOf('.') === -1 || current?.indexOf('.') === -1) {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -62,10 +62,10 @@ kpxc.addToSitePreferences = async function(optionName, addWildcard = false) {
|
|||
|
||||
await sendMessage('save_settings', kpxc.settings);
|
||||
|
||||
if (optionName === 'allowIframes') {
|
||||
if (optionName === SitePreferences.ALLOW_IFRAMES) {
|
||||
await sendMessage('page_set_allow_iframes', [ true, site ]);
|
||||
await sendMessage('iframe_detected', false);
|
||||
} else if (optionName === 'usernameOnly') {
|
||||
} else if (optionName === SitePreferences.USERNAME_ONLY) {
|
||||
await sendMessage('username_field_detected', false);
|
||||
}
|
||||
};
|
||||
|
|
@ -952,9 +952,9 @@ browser.runtime.onMessage.addListener(async function(req, sender) {
|
|||
if (req.action === 'activated_tab') {
|
||||
kpxc.triggerActivatedTab();
|
||||
} else if (req.action === 'add_allow_iframes_option') {
|
||||
kpxc.addToSitePreferences('allowIframes');
|
||||
kpxc.addToSitePreferences(SitePreferences.ALLOW_IFRAMES);
|
||||
} else if (req.action === 'add_username_only_option') {
|
||||
kpxc.addToSitePreferences('usernameOnly', true);
|
||||
kpxc.addToSitePreferences(SitePreferences.USERNAME_ONLY, true);
|
||||
} else if (req.action === 'check_database_hash' && 'hash' in req) {
|
||||
kpxc.detectDatabaseChange(req);
|
||||
} else if (req.action === 'choose_credential_fields') {
|
||||
|
|
|
|||
|
|
@ -32,11 +32,6 @@ const $ = function(elem) {
|
|||
return document.querySelector(elem);
|
||||
};
|
||||
|
||||
// Returns a string with 'px' for CSS styles
|
||||
const Pixels = function(value) {
|
||||
return String(value) + 'px';
|
||||
};
|
||||
|
||||
// Basic icon class
|
||||
class Icon {
|
||||
constructor(field, databaseState = DatabaseState.DISCONNECTED, segmented = false) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
--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-box-shadow-strong: 0 20px 25px -5px rgb(0 0 0 / 0.8), 0 8px 10px -6px rgb(0 0 0 / 0.8);
|
||||
--kpxc-card-background-color: #fff;
|
||||
--kpxc-card-border-color: rgba(0, 0, 0, .125);
|
||||
--kpxc-card-header-color: #fafafa;
|
||||
|
|
@ -29,7 +30,6 @@
|
|||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
|
||||
:root,
|
||||
:host {
|
||||
--kpxc-autocomplete-footer-color: #2b2a2a;
|
||||
|
|
@ -114,4 +114,3 @@
|
|||
--kpxc-lighter-text-color: rgba(0, 0, 0, 0.8);
|
||||
--kpxc-light-text-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,12 +105,6 @@ tbody {
|
|||
color: var(--kpxc-text-color);
|
||||
}
|
||||
|
||||
.table-striped > tbody > tr:nth-of-type(odd) input[type="checkbox"] {
|
||||
background-color: var(--kpxc-card-background-color) !important;
|
||||
border-color: var(--kpxc-input-main-border-color) !important;
|
||||
color: var(--kpxc-text-color) !important;
|
||||
}
|
||||
|
||||
.table-striped > tbody > tr:nth-of-type(odd) .form-check-input:checked {
|
||||
background-color: var(--kpxc-checkbox-background-color) !important;
|
||||
}
|
||||
|
|
@ -136,14 +130,14 @@ table tbody tr.empty:not(:nth-last-child(2)) {
|
|||
display: none;
|
||||
}
|
||||
|
||||
#tab-site-preferences td:nth-of-type(2),
|
||||
#tab-site-preferences td:nth-of-type(2) select {
|
||||
#tab-site-preferences td:nth-of-type(3),
|
||||
#tab-site-preferences td:nth-of-type(3) select {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
#tab-site-preferences td:nth-of-type(2),
|
||||
#tab-site-preferences td:nth-of-type(3),
|
||||
#tab-site-preferences td:nth-of-type(4),
|
||||
#tab-site-preferences td:nth-of-type(5),
|
||||
table td:last-of-type {
|
||||
width: 1px;
|
||||
}
|
||||
|
|
@ -233,6 +227,26 @@ table td:last-of-type {
|
|||
color: var(--kpxc-text-color);
|
||||
}
|
||||
|
||||
.non-clickable {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.settings-dropdown {
|
||||
box-shadow: var(--kpxc-box-shadow-strong);
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.settings-dropdown-body {
|
||||
background-color: var(--bs-table-bg);
|
||||
}
|
||||
|
||||
.dropdown-divider {
|
||||
border-top: 1px solid var(--kpxc-table-odd-color);
|
||||
height: 0.5rem;
|
||||
margin-top: 0.3rem;
|
||||
}
|
||||
|
||||
.site-preferences-input {
|
||||
border: 0;
|
||||
border-bottom-right-radius: 4px !important;
|
||||
|
|
|
|||
|
|
@ -260,7 +260,7 @@
|
|||
<input class="form-check-input" type="checkbox" name="autoFillSingleEntry" id="autoFillSingleEntry" value="true">
|
||||
<label class="form-check-label" for="autoFillSingleEntry" data-i18n="optionsCheckboxAutoFillSingleEntry"></label>
|
||||
<div class="form-text" data-i18n="optionsAutoFillSingleEntryHelpText"></div>
|
||||
|
||||
|
||||
<div class="alert alert-warning mt-3 col-lg-9" role="alert">
|
||||
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
|
||||
<span data-i18n="optionsAutoFillSingleEntryWarning"></span>
|
||||
|
|
@ -761,16 +761,14 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<th scope="col"><span data-i18n="optionsColumnPageURL"></span></th>
|
||||
<th scope="col"></th>
|
||||
<th scope="col"><span data-i18n="optionsColumnIgnore"></span></th>
|
||||
<th scope="col"><span data-i18n="optionsColumnUsernameOnly"></span></th>
|
||||
<th scope="col"><span data-i18n="optionsColumnImprovedInputFieldDetection"></span></th>
|
||||
<th scope="col"><span data-i18n="optionsColumnAllowIframes"></span></th>
|
||||
<th scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="empty">
|
||||
<td colspan="5"><span data-i18n="optionsSitePreferencesNotFound"></span></td>
|
||||
<td colspan="4"><span data-i18n="optionsSitePreferencesNotFound"></span></td>
|
||||
</tr>
|
||||
<tr class="clone d-none">
|
||||
<td class="text-nowrap">
|
||||
|
|
@ -790,17 +788,19 @@
|
|||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<select class="form-select form-select-sm" name="ignore" data-i18n="[title]optionsSitePreferencesSelect">
|
||||
<td class="text-nowrap">
|
||||
<button type="button" class="btn btn-sm btn-primary container-fluid" id="settings-button">
|
||||
<span class="non-clickable" data-i18n="optionsSitePreferencesSettings"></span>
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<select class="form-select form-select-sm" id="ignore-select" name="ignore" data-i18n="[title]optionsSitePreferencesSelect">
|
||||
<option value="ignoreNothing" data-i18n="optionsSelectionNothing"></option>
|
||||
<option value="ignoreNormal" data-i18n="optionsSelectionNormal"></option>
|
||||
<option value="ignoreAutoSubmit" data-i18n="optionsSelectionAutoSubmit"></option>
|
||||
<option value="ignoreFull" data-i18n="optionsSelectionFull"></option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input class="form-check-input" type="checkbox" name="usernameOnly" value="false" data-i18n="[title]optionsColumnUsernameOnly"></td>
|
||||
<td><input class="form-check-input" type="checkbox" name="improvedFieldDetection" value="false" data-i18n="[title]optionsColumnImprovedInputFieldDetection"></td>
|
||||
<td><input class="form-check-input" type="checkbox" name="allowIframes" value="false" data-i18n="[title]optionsColumnAllowIframes"></td>
|
||||
<td class="text-nowrap">
|
||||
<button class="btn btn-sm delete btn-danger" data-i18n="[title]removeSitePreferencesButtonTitle">
|
||||
<i class="fa fa-remove" aria-hidden="true"></i>
|
||||
|
|
@ -838,6 +838,32 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Settings dropdown -->
|
||||
<div class="card settings-dropdown" style="display: none;">
|
||||
<div class="card-body settings-dropdown-body">
|
||||
<!-- Username only detection -->
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="usernameOnly" name="usernameOnly" value="false" data-i18n="[title]optionsColumnUsernameOnly">
|
||||
<label class="form-check-label" for="usernameOnly" data-i18n="optionsColumnUsernameOnly"></label>
|
||||
</div>
|
||||
|
||||
<!-- Improved input field detection -->
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="improvedFieldDetection" name="improvedFieldDetection" value="false" data-i18n="[title]optionsColumnImprovedInputFieldDetection">
|
||||
<label class="form-check-label" for="improvedFieldDetection" data-i18n="optionsColumnImprovedInputFieldDetection"></label>
|
||||
</div>
|
||||
|
||||
<!-- Allow iFrames -->
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="allowIframes" name="allowIframes" value="false" data-i18n="[title]optionsColumnAllowIframes">
|
||||
<label class="form-check-label" for="allowIframes" data-i18n="optionsColumnAllowIframes"></label>
|
||||
</div>
|
||||
|
||||
<!-- Add new options below -->
|
||||
<!--<hr class="dropdown-divider">-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- About -->
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const options = {};
|
||||
options.dropdownButton = null;
|
||||
|
||||
const $ = function(elem) {
|
||||
return document.querySelector(elem);
|
||||
|
|
@ -536,6 +537,43 @@ options.initSitePreferences = function() {
|
|||
modalEvent.currentTarget.querySelector('.modal-footer button.yes').focus();
|
||||
});
|
||||
|
||||
const settingsButtonClicked = function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const closestTr = e.target.closest('tr');
|
||||
const url = closestTr.getAttribute('url');
|
||||
const sitePreferences = options.settings['sitePreferences']?.find((pref) => pref?.url === url);
|
||||
const usernameOnly = sitePreferences.usernameOnly;
|
||||
const improvedFieldDetection = sitePreferences.improvedFieldDetection;
|
||||
const allowIframes = sitePreferences.allowIframes;
|
||||
|
||||
const dropdown = $('.settings-dropdown');
|
||||
if (dropdown?.style?.display !== 'block'
|
||||
|| (dropdown?.style?.display === 'block' && e.target !== options.dropdownButton)) {
|
||||
if (options.dropdownButton) {
|
||||
options.dropdownButton.classList.remove('active');
|
||||
// Update number of enabled settings to the old Settings button
|
||||
const checkboxValues = Array.from(dropdown?.querySelectorAll('input[type=checkbox]')).map(c => c.checked);
|
||||
updateSettingsButtonText(options.dropdownButton, checkboxValues);
|
||||
}
|
||||
|
||||
// Apply current settings to the dropdown
|
||||
dropdown.querySelector('#usernameOnly').checked = usernameOnly;
|
||||
dropdown.querySelector('#improvedFieldDetection').checked = improvedFieldDetection;
|
||||
dropdown.querySelector('#allowIframes').checked = allowIframes;
|
||||
|
||||
dropdown?.show();
|
||||
updateDropdownPosition(e, dropdown);
|
||||
options.dropdownButton = e.target;
|
||||
options.dropdownButton.classList.add('active');
|
||||
} else {
|
||||
dropdown?.hide();
|
||||
options.dropdownButton.classList.remove('active');
|
||||
updateSettingsButtonText(options.dropdownButton, [ usernameOnly, improvedFieldDetection, allowIframes ]);
|
||||
options.dropdownButton = null;
|
||||
}
|
||||
};
|
||||
|
||||
const removeButtonClicked = function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
|
|
@ -601,18 +639,18 @@ options.initSitePreferences = function() {
|
|||
}
|
||||
};
|
||||
|
||||
const checkboxClicked = function() {
|
||||
const closestTr = this.closest('tr');
|
||||
const checkboxClicked = async function(e) {
|
||||
const closestTr = options?.dropdownButton?.closest('tr');
|
||||
const url = closestTr.getAttribute('url');
|
||||
|
||||
for (const site of options.settings['sitePreferences']) {
|
||||
if (site.url === url) {
|
||||
if (this.name === 'usernameOnly') {
|
||||
site.usernameOnly = this.checked;
|
||||
} else if (this.name === 'improvedFieldDetection') {
|
||||
site.improvedFieldDetection = this.checked;
|
||||
} else if (this.name === 'allowIframes') {
|
||||
site.allowIframes = this.checked;
|
||||
if (e.target.name === SitePreferences.USERNAME_ONLY) {
|
||||
site.usernameOnly = e.target.checked;
|
||||
} else if (e.target.name === SitePreferences.IMPROVED_FIELD_DETECTION) {
|
||||
site.improvedFieldDetection = e.target.checked;
|
||||
} else if (e.target.name === SitePreferences.ALLOW_IFRAMES) {
|
||||
site.allowIframes = e.target.checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -633,6 +671,11 @@ options.initSitePreferences = function() {
|
|||
options.saveSettings();
|
||||
};
|
||||
|
||||
const dropdown = $('.settings-dropdown');
|
||||
dropdown.querySelector('#usernameOnly').addEventListener('change', checkboxClicked);
|
||||
dropdown.querySelector('#improvedFieldDetection').addEventListener('change',checkboxClicked);
|
||||
dropdown.querySelector('#allowIframes').addEventListener('change', checkboxClicked);
|
||||
|
||||
const addNewRow = function(rowClone, newIndex, url, ignore, usernameOnly, improvedFieldDetection, allowIframes) {
|
||||
const row = rowClone.cloneNode(true);
|
||||
row.setAttribute('url', url);
|
||||
|
|
@ -662,19 +705,25 @@ options.initSitePreferences = function() {
|
|||
saveModifiedUrl(e, row, inputField, editButton, cancelButton, saveButton)
|
||||
);
|
||||
|
||||
// Page URL
|
||||
row.children[0].children[0].children[0].value = url;
|
||||
row.children[0].children[0]?.addEventListener('dblclick', (e) =>
|
||||
enterEditMode(e, row, inputField, editButton, cancelButton, saveButton)
|
||||
);
|
||||
row.children[1].children[0].value = ignore;
|
||||
row.children[1].children[0].addEventListener('change', selectionChanged);
|
||||
row.children[2].children['usernameOnly'].checked = usernameOnly;
|
||||
row.children[2].children['usernameOnly'].addEventListener('change', checkboxClicked);
|
||||
row.children[3].children['improvedFieldDetection'].checked = improvedFieldDetection;
|
||||
row.children[3].children['improvedFieldDetection'].addEventListener('change', checkboxClicked);
|
||||
row.children[4].children['allowIframes'].checked = allowIframes;
|
||||
row.children[4].children['allowIframes'].addEventListener('change', checkboxClicked);
|
||||
row.children[5].addEventListener('click', removeButtonClicked);
|
||||
|
||||
// Settings
|
||||
const settings = row.children[1];
|
||||
updateSettingsButtonText(settings.querySelector('#settings-button'),
|
||||
[ usernameOnly, improvedFieldDetection, allowIframes ]);
|
||||
settings.querySelector('#settings-button').addEventListener('click', (e) => settingsButtonClicked(e));
|
||||
|
||||
// Ignore
|
||||
const ignoreSelect = row.children[2];
|
||||
ignoreSelect.querySelector('#ignore-select').value = ignore;
|
||||
ignoreSelect.querySelector('#ignore-select').addEventListener('change', selectionChanged);
|
||||
|
||||
// Remove button
|
||||
row.children[3].addEventListener('click', removeButtonClicked);
|
||||
|
||||
$('#tab-site-preferences table tbody').append(row);
|
||||
};
|
||||
|
|
@ -738,11 +787,11 @@ options.initSitePreferences = function() {
|
|||
$('#tab-site-preferences table tbody tr.empty').hide();
|
||||
|
||||
options.settings['sitePreferences'].push({
|
||||
url: value,
|
||||
ignore: IGNORE_NOTHING,
|
||||
usernameOnly: false,
|
||||
improvedFieldDetection: false,
|
||||
allowIframes: false,
|
||||
ignore: IGNORE_NOTHING,
|
||||
improvedFieldDetection: false,
|
||||
url: value,
|
||||
usernameOnly: false,
|
||||
});
|
||||
options.saveSettings();
|
||||
manualUrl.value = '';
|
||||
|
|
@ -823,6 +872,80 @@ const getBrowserId = function() {
|
|||
return 'Other/Unknown';
|
||||
};
|
||||
|
||||
// Update the number of enabled settings to the button text
|
||||
const updateSettingsButtonText = function(buttonElement, enabledOptions = []) {
|
||||
const numberOfEnabledOptions = enabledOptions.filter(o => o === true).length;
|
||||
const buttonText = buttonElement.querySelector('span');
|
||||
|
||||
if (numberOfEnabledOptions > 0) {
|
||||
buttonText.textContent =
|
||||
`${browser.i18n.getMessage('optionsSitePreferencesSettings')} (${numberOfEnabledOptions})`;
|
||||
} else {
|
||||
buttonText.textContent = browser.i18n.getMessage('optionsSitePreferencesSettings');
|
||||
}
|
||||
};
|
||||
|
||||
// Updates settings dropdown menu position
|
||||
const updateDropdownPosition = function(e, dropdown) {
|
||||
if (!dropdown) {
|
||||
dropdown = $('.settings-dropdown');
|
||||
}
|
||||
|
||||
const settingsButton = e?.target ?? options.dropdownButton;
|
||||
const rect = settingsButton?.getClientRects()?.[0];
|
||||
if (!rect) {
|
||||
return;
|
||||
}
|
||||
|
||||
const zoom = getComputedStyle(document.body).zoom || 1;
|
||||
const scrollTop = document.defaultView.scrollY / zoom;
|
||||
const scrollLeft = document.defaultView?.scrollX / zoom;
|
||||
|
||||
// If dropdown does not fit to the bottom of the screen -> show it at the top of the settings button
|
||||
const dropdownRect = dropdown.getBoundingClientRect();
|
||||
const totalHeight = dropdownRect.height + rect.height;
|
||||
const offset = (totalHeight + rect.y) / zoom > window.self.visualViewport.height ? totalHeight / zoom : 0;
|
||||
|
||||
dropdown.style.left = Pixels(rect.left / zoom + scrollLeft);
|
||||
dropdown.style.top = Pixels(rect.bottom / zoom + scrollTop - offset);
|
||||
};
|
||||
|
||||
// Hides the settings dropdown when clicked outside of it
|
||||
document.addEventListener('mouseup', function(e) {
|
||||
if (!e.isTrusted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dropdown = $('.settings-dropdown');
|
||||
if (dropdown?.style?.display !== 'block') {
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = dropdown?.getClientRects()?.[0];
|
||||
if (!rect) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((e.x > rect.right || e.x < rect.x) || (e.y > rect.bottom || e.y < rect.y)
|
||||
&& e.target.nodeName !== 'BUTTON') {
|
||||
dropdown?.hide();
|
||||
const checkboxValues = Array.from(dropdown?.querySelectorAll('input[type=checkbox]')).map(c => c.checked);
|
||||
updateSettingsButtonText(options.dropdownButton, checkboxValues);
|
||||
options.dropdownButton.classList.remove('active');
|
||||
options.dropdownButton = null;
|
||||
}
|
||||
});
|
||||
|
||||
// Handle dropdown position on window resize
|
||||
window.addEventListener('resize', function() {
|
||||
updateDropdownPosition();
|
||||
});
|
||||
|
||||
// Handle dropdown position on scroll
|
||||
window.addEventListener('scroll', function() {
|
||||
updateDropdownPosition();
|
||||
});
|
||||
|
||||
(async() => {
|
||||
try {
|
||||
// We eagerly load the theme here to avoid a white flash
|
||||
|
|
|
|||
Loading…
Reference in a new issue