Add user email lookup component for sharing UI tooltips
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* User Email Lookup Tool
|
||||
* Provides hover tooltips showing email addresses when hovering over user names
|
||||
*
|
||||
* @module UserEmailLookup
|
||||
* @description Standalone utility for looking up email addresses from the Associations
|
||||
* collection and displaying them as hover tooltips. Integrates with note/conversation
|
||||
* sharing UI to enhance user experience when managing shared access.
|
||||
* @usage Call window.setupUserEmailLookup(pbInstance, containerSelector) after page load.
|
||||
* Pass PocketBase instance and CSS selector for user capsule container.
|
||||
* @dependencies PocketBase instance (passed in). Browser APIs: DOM, tooltips via title attribute.
|
||||
* @integration Used in index.html note/conversation sharing forms to show email on hover
|
||||
* @gotchas Associations collection must have first_name field for lookups. Lookups are
|
||||
* performed on hover, so network latency may cause brief delay before tooltip appears.
|
||||
* Cache email lookups to minimize PocketBase queries.
|
||||
*/
|
||||
|
||||
// Cache for email lookups to minimize queries
|
||||
const emailLookupCache = {};
|
||||
|
||||
/**
|
||||
* Fetch email from Associations collection by first name
|
||||
* Uses cache to avoid duplicate queries
|
||||
*
|
||||
* @param {PocketBase} pb - PocketBase instance
|
||||
* @param {string} firstName - First name to look up
|
||||
* @returns {Promise<string>} Email address or fallback message
|
||||
* @example
|
||||
* const email = await getEmailByFirstName(pb, 'John');
|
||||
* console.log(email); // 'john@example.com' or 'Email not found'
|
||||
*/
|
||||
async function getEmailByFirstName(pb, firstName) {
|
||||
if (!firstName) return 'Email not found';
|
||||
|
||||
// Check cache first
|
||||
if (emailLookupCache[firstName]) {
|
||||
return emailLookupCache[firstName];
|
||||
}
|
||||
|
||||
try {
|
||||
const records = await pb.collection('Associations').getList(1, 50, {
|
||||
filter: `first_name = "${firstName}"`,
|
||||
});
|
||||
|
||||
if (records.items.length === 0) {
|
||||
emailLookupCache[firstName] = 'Email not found';
|
||||
return 'Email not found';
|
||||
}
|
||||
|
||||
// Use first match's email
|
||||
const email = records.items[0].email || 'Email not found';
|
||||
emailLookupCache[firstName] = email;
|
||||
return email;
|
||||
} catch (error) {
|
||||
console.error(`Failed to lookup email for "${firstName}":`, error);
|
||||
return 'Error loading email';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup email lookups for user capsules in a container
|
||||
* Attaches hover event listeners to show email in tooltip
|
||||
*
|
||||
* @param {PocketBase} pb - PocketBase instance
|
||||
* @param {string} containerSelector - CSS selector for container with user capsules
|
||||
* @example
|
||||
* window.setupUserEmailLookup(pb, '#selectedUsersContainer');
|
||||
*/
|
||||
window.setupUserEmailLookup = function(pb, containerSelector) {
|
||||
const container = document.querySelector(containerSelector);
|
||||
if (!container) {
|
||||
console.warn(`setupUserEmailLookup: Container not found: ${containerSelector}`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`✓ User Email Lookup initialized for: ${containerSelector}`);
|
||||
|
||||
// Observer: Watch for new capsule additions
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
mutation.addedNodes.forEach((node) => {
|
||||
if (node.nodeType === 1 && node.classList?.contains('inline-flex')) {
|
||||
attachEmailLookupTooltip(pb, node);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
observer.observe(container, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
|
||||
// Also process any existing capsules
|
||||
container.querySelectorAll('.inline-flex.items-center.gap-2').forEach((capsule) => {
|
||||
attachEmailLookupTooltip(pb, capsule);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Attach email lookup tooltip to a single user capsule
|
||||
* Shows email on hover (with caching to prevent redundant queries)
|
||||
*
|
||||
* @param {PocketBase} pb - PocketBase instance
|
||||
* @param {HTMLElement} capsuleEl - The user capsule element
|
||||
* @private
|
||||
*/
|
||||
function attachEmailLookupTooltip(pb, capsuleEl) {
|
||||
// Extract first name from capsule text
|
||||
const nameSpan = capsuleEl.querySelector('span:not([data-email-loaded])');
|
||||
if (!nameSpan || nameSpan.hasAttribute('data-email-loaded')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstName = nameSpan.textContent.trim();
|
||||
if (!firstName) return;
|
||||
|
||||
// Mark as processed
|
||||
nameSpan.setAttribute('data-email-loaded', 'true');
|
||||
|
||||
// Attach hover listener
|
||||
capsuleEl.addEventListener('mouseenter', async () => {
|
||||
// If email already in title, don't query again
|
||||
if (capsuleEl.title && !capsuleEl.title.startsWith('Loading')) {
|
||||
return;
|
||||
}
|
||||
|
||||
capsuleEl.title = 'Loading email...';
|
||||
const email = await getEmailByFirstName(pb, firstName);
|
||||
capsuleEl.title = email;
|
||||
capsuleEl.classList.add('cursor-help');
|
||||
});
|
||||
|
||||
capsuleEl.addEventListener('mouseleave', () => {
|
||||
// Title persists so user can still see it if they hover back quickly
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the email lookup cache
|
||||
* Useful if Associations collection is updated and you need fresh data
|
||||
*
|
||||
* @example
|
||||
* window.clearEmailLookupCache();
|
||||
*/
|
||||
window.clearEmailLookupCache = function() {
|
||||
Object.keys(emailLookupCache).forEach(key => delete emailLookupCache[key]);
|
||||
console.log('✓ Email lookup cache cleared');
|
||||
};
|
||||
|
||||
console.log('✓ User Email Lookup tool loaded - call window.setupUserEmailLookup(pb, containerSelector)');
|
||||
Reference in New Issue
Block a user