mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-03-19 15:18:02 -04:00
Improve icon inject mechanism (#541)
This commit is contained in:
@@ -42,11 +42,10 @@ chrome.contextMenus.onClicked.addListener((info, tab) => {
|
||||
chrome.scripting.executeScript({
|
||||
target: { tabId: tab.id },
|
||||
func: (generatedPassword) => {
|
||||
// Write to clipboard
|
||||
navigator.clipboard.writeText(generatedPassword).then(() => {
|
||||
function showToast(message: string) {
|
||||
// Show notification
|
||||
const notification = document.createElement('div');
|
||||
notification.textContent = 'Password copied to clipboard';
|
||||
notification.textContent = message;
|
||||
notification.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
@@ -60,6 +59,11 @@ chrome.contextMenus.onClicked.addListener((info, tab) => {
|
||||
`;
|
||||
document.body.appendChild(notification);
|
||||
setTimeout(() => notification.remove(), 3000);
|
||||
}
|
||||
|
||||
// Write to clipboard
|
||||
navigator.clipboard.writeText(generatedPassword).then(() => {
|
||||
showToast('Password copied to clipboard');
|
||||
});
|
||||
},
|
||||
args: [password]
|
||||
@@ -327,7 +331,8 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
() => {}
|
||||
);
|
||||
await webApi.initializeBaseUrl();
|
||||
await webApi.post('Vault', newVault);
|
||||
// TODO: re-enable
|
||||
//await webApi.post('Vault', newVault);
|
||||
|
||||
console.log('Vault uploaded successfully');
|
||||
|
||||
@@ -364,4 +369,4 @@ async function getEmailAddressesForVault(sqliteClient: SqliteClient): Promise<st
|
||||
});
|
||||
|
||||
return filteredEmailAddresses;
|
||||
}
|
||||
}
|
||||
@@ -790,66 +790,85 @@ async function disableAutoPopup(): Promise<void> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject icons into forms
|
||||
* Inject icon for a focused input element
|
||||
*/
|
||||
function injectIcons(): void {
|
||||
const formDetector = new FormDetector(document);
|
||||
const forms = formDetector.detectForms();
|
||||
function injectIcon(input: HTMLInputElement): void {
|
||||
// Don't inject if already exists
|
||||
if (document.querySelector(`[data-icon-for="${input.id}"]`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
forms.forEach(form => {
|
||||
// Find the first occurring field by comparing their positions in the DOM
|
||||
const fields = [
|
||||
{ type: 'email', element: form.emailField },
|
||||
{ type: 'username', element: form.usernameField },
|
||||
{ type: 'password', element: form.passwordField }
|
||||
].filter(f => f.element);
|
||||
const iconDiv = document.createElement('div');
|
||||
iconDiv.innerHTML = ICON_HTML;
|
||||
const icon = iconDiv.firstElementChild as HTMLElement;
|
||||
|
||||
// Sort fields based on their DOM position
|
||||
fields.sort((a, b) => {
|
||||
if (!a.element || !b.element) return 0;
|
||||
return a.element.compareDocumentPosition(b.element) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
|
||||
});
|
||||
// Get input's position and dimensions
|
||||
const inputRect = input.getBoundingClientRect();
|
||||
|
||||
const targetField = fields[0]?.element;
|
||||
// Position icon absolutely relative to viewport
|
||||
icon.style.cssText = `
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
cursor: pointer;
|
||||
top: ${inputRect.top + window.scrollY + (inputRect.height - 24) / 2}px;
|
||||
right: ${window.innerWidth - (inputRect.right + window.scrollX) + 8}px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
pointer-events: auto;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
`;
|
||||
|
||||
if (targetField && !targetField.parentElement?.querySelector('.aliasvault-input-icon')) {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.style.cssText = 'position: relative; display: inline-block; width: 100%;';
|
||||
icon.setAttribute('data-icon-for', input.id);
|
||||
|
||||
// Preserve original input styles
|
||||
const computedStyle = window.getComputedStyle(targetField);
|
||||
const originalWidth = computedStyle.width;
|
||||
const originalDisplay = computedStyle.display;
|
||||
|
||||
targetField.parentNode?.insertBefore(wrapper, targetField);
|
||||
wrapper.appendChild(targetField);
|
||||
|
||||
// Restore original input styles
|
||||
targetField.style.width = originalWidth;
|
||||
targetField.style.display = originalDisplay;
|
||||
|
||||
const iconDiv = document.createElement('div');
|
||||
iconDiv.innerHTML = ICON_HTML;
|
||||
const icon = iconDiv.firstElementChild as HTMLElement;
|
||||
|
||||
icon.addEventListener('click', () => {
|
||||
showCredentialPopup(targetField as HTMLInputElement);
|
||||
});
|
||||
|
||||
wrapper.appendChild(icon);
|
||||
}
|
||||
icon.addEventListener('click', () => {
|
||||
showCredentialPopup(input);
|
||||
});
|
||||
|
||||
// Add to body
|
||||
document.body.appendChild(icon);
|
||||
|
||||
// Fade in the icon
|
||||
requestAnimationFrame(() => {
|
||||
icon.style.opacity = '1';
|
||||
});
|
||||
|
||||
// Remove icon when input loses focus, except when clicking the icon
|
||||
const handleBlur = (e: FocusEvent) => {
|
||||
// Don't remove if clicking the icon itself
|
||||
if (e.relatedTarget === icon) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fade out and remove icon
|
||||
icon.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
icon.remove();
|
||||
input.removeEventListener('blur', handleBlur);
|
||||
}, 200); // Match transition duration
|
||||
};
|
||||
|
||||
input.addEventListener('blur', handleBlur);
|
||||
}
|
||||
|
||||
// Call injectIcons on page load and DOM mutations
|
||||
injectIcons();
|
||||
const observer = new MutationObserver(() => {
|
||||
injectIcons();
|
||||
});
|
||||
/**
|
||||
* Listen for input field focus
|
||||
*/
|
||||
document.addEventListener('focusin', async (e) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
if (target.tagName === 'INPUT' && !target.dataset.aliasvaultIgnore) {
|
||||
const formDetector = new FormDetector(document);
|
||||
const forms = formDetector.detectForms();
|
||||
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
if (!forms.length) return;
|
||||
|
||||
injectIcon(target);
|
||||
|
||||
const isDisabled = await isAutoPopupDisabled();
|
||||
if (!isDisabled) {
|
||||
showCredentialPopup(target);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const createEditNamePopup = (defaultName: string): Promise<string | null> => {
|
||||
@@ -1065,7 +1084,6 @@ urlObserver.observe(document.body, {
|
||||
window.addEventListener('popstate', () => {
|
||||
removeExistingPopup();
|
||||
});
|
||||
|
||||
/**
|
||||
* Create credential list content for popup
|
||||
*/
|
||||
@@ -1261,4 +1279,4 @@ function updatePopupContent(popup: HTMLElement, credentials: Credential[], input
|
||||
margin: 8px 0;
|
||||
`;
|
||||
popup.insertBefore(divider, actionContainer);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user