Add user email lookup component for sharing UI tooltips

This commit is contained in:
2026-01-14 12:59:28 +00:00
parent 534016e9d5
commit efbe5d6573
4 changed files with 378 additions and 0 deletions
+194
View File
@@ -0,0 +1,194 @@
# User Email Lookup Tool
A standalone utility for looking up email addresses from the Associations collection and displaying them as hover tooltips over user names in the sharing interface.
## Features
- 🔍 Email lookup via Associations collection by first name
- 💬 Hover tooltips show email addresses automatically
- 🚀 Request caching to minimize PocketBase queries
- 👁️ Automatic tooltip persistence during hover
- 📍 MutationObserver integration for dynamic capsule additions
- ⚡ Graceful fallback for missing emails
## Installation
Copy the `user-email-lookup` folder to your project:
```bash
cp -r user-email-lookup /path/to/project/tools/
```
## Usage
After the tool is loaded and PocketBase is initialized, call:
```javascript
// Initialize email lookups for note sharing
window.setupUserEmailLookup(pb, '#selectedUsersContainer');
// Or for conversation sharing
window.setupUserEmailLookup(pb, '#selectedConversationUsersContainer');
```
### Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| `pb` | PocketBase | Initialized PocketBase client instance |
| `containerSelector` | string | CSS selector for container with user capsules (e.g., `#selectedUsersContainer`) |
## How It Works
1. **Initial Setup**: Scans container for existing user capsules and attaches hover listeners
2. **Dynamic Monitoring**: Uses MutationObserver to detect newly added capsules
3. **Email Lookup**: On hover, queries Associations collection for `first_name` match
4. **Caching**: Stores results in memory to prevent redundant queries
5. **Tooltip Display**: Shows email in native browser tooltip (`title` attribute)
### Example User Experience
```
User types: "John, Sarah, Mike"
System creates capsules with their first names
User hovers over "John" capsule
→ Lookup queries Associations for first_name = "John"
→ Finds: john.smith@company.com
→ Tooltip shows: "john.smith@company.com"
User hovers over "Sarah" capsule
→ Cache hit (if already looked up Sarah recently)
→ Tooltip shows: "sarah.jones@company.com"
```
## API
### `setupUserEmailLookup(pb, containerSelector)`
Initialize email lookups for a container of user capsules.
```javascript
window.setupUserEmailLookup(pb, '#selectedUsersContainer');
```
### `getEmailByFirstName(pb, firstName)`
Manually lookup an email by first name. Used internally but available for custom extensions.
```javascript
const email = await getEmailByFirstName(pb, 'John');
// Returns: 'john@example.com' or 'Email not found' or 'Error loading email'
```
### `clearEmailLookupCache()`
Clear the in-memory email cache. Useful if Associations collection is updated and you need fresh data.
```javascript
window.clearEmailLookupCache();
```
## Integration
This tool is designed for Prism Notes integration. Include it in `index.html`:
```html
<script src="tools/user-email-lookup/index.js"></script>
```
Then initialize after PocketBase auth completes:
```javascript
// In the user auth flow
if (pb.authStore.isValid) {
window.setupUserEmailLookup(pb, '#selectedUsersContainer');
}
```
## PocketBase Requirements
Your **Associations** collection must have:
- `first_name` field (string) — used for lookups
- `email` field (string) — displayed in tooltip
### Example Associations Record
```json
{
"id": "abc123",
"first_name": "John",
"email": "john.smith@company.com",
"last_name": "Smith",
"created": "2026-01-14T...",
"updated": "2026-01-14T..."
}
```
## Caching Behavior
**Cache Storage**: In-memory JavaScript object (lost on page refresh)
**When to Clear**:
- After Associations collection updates
- If user updates their profile
- Before bulk operations with many lookups
**Manual Clear**:
```javascript
window.clearEmailLookupCache();
```
## Browser Compatibility
- Works in all modern browsers (Chrome 90+, Firefox 88+, Safari 14+, Edge 90+)
- Requires MutationObserver API (widely supported)
- Uses native `title` attribute for tooltips (no dependencies)
## Performance Notes
- First lookup for a name: ~100-200ms (network + PocketBase query)
- Subsequent lookups: <1ms (cache hit)
- Memory usage: ~100 bytes per cached name
- MutationObserver overhead: Negligible
## Troubleshooting
### "Email not found" tooltip
Check that:
1. Associations collection exists in PocketBase
2. `first_name` field is spelled correctly
3. User's record has the `first_name` populated
4. PocketBase read rules allow access to Associations collection
### Slow tooltips on first hover
This is expected—first lookup queries PocketBase. Subsequent hovers are instant (cached).
### "Error loading email" tooltip
Check PocketBase console for errors. Possible causes:
- Network connectivity issues
- Insufficient permissions to read Associations
- PocketBase server downtime
### Cache not clearing
Cache is in-memory only. Refresh the page to clear, or call:
```javascript
window.clearEmailLookupCache();
```
## Future Enhancements
- [ ] Persistent storage (IndexedDB) for longer-lived cache
- [ ] Configurable tooltip styling (custom CSS instead of native title)
- [ ] Bulk email lookup for performance optimization
- [ ] Email suggestion/autocomplete during input
- [ ] Fallback email patterns (first.last@company.com)
## Dependencies
- `pocketbase` (passed in as parameter) — for Associations queries
- Browser APIs: DOM, MutationObserver, title attribute
## Notes
- Tooltips use native browser `title` attribute for simplicity and accessibility
- Lookup is by `first_name` only (not full name) to match how names are entered in the UI
- Cache is intentionally in-memory to support real-time Associations updates
+151
View File
@@ -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)');