/** * GoingsOn - Contacts Render Module * Contact card rendering, detail modal rendering */ (function() { 'use strict'; const esc = GoingsOn.utils.escapeHtml; const escAttr = GoingsOn.utils.escapeAttr; // ============ Helpers ============ /** * Extract up to 2 initials from a display name. * @param {string} name - Full display name * @returns {string} Uppercase initials (e.g. "JD") */ function getInitials(name) { return name.split(/\s+/).filter(Boolean).map(w => w[0]).slice(0, 2).join('').toUpperCase(); } // ============ Card Rendering ============ /** * Render a contact card for the contacts grid. * @param {Object} c - Contact object * @returns {string} HTML string for the contact card */ function renderCard(c) { const initials = c.initials || getInitials(c.displayName); const primaryEmail = c.primaryEmail || c.emails?.find(e => e.isPrimary)?.address || c.emails?.[0]?.address || ''; const nickname = c.nickname ? `"${esc(c.nickname)}"` : ''; const company = c.company ? `${esc(c.company)}` : ''; const emailLine = primaryEmail ? `${esc(primaryEmail)}` : ''; const tagPills = (c.tags || []).map(t => `${esc(t)}` ).join(''); return `
${esc(initials)}

${esc(c.displayName)}

${nickname} ${company}
${emailLine ? `

${emailLine}

` : ''} ${tagPills ? `
${tagPills}
` : ''}
`; } // ============ Detail Modal Rendering ============ /** * Show the full contact detail modal with all sub-collections. * @param {Object} contact - Full contact object with emails, phones, socialHandles, customFields */ function showDetailModal(contact) { const initials = contact.initials || getInitials(contact.displayName); // Build info section let info = ''; if (contact.nickname) info += `
Nickname: ${esc(contact.nickname)}
`; if (contact.company) info += `
Company: ${esc(contact.company)}
`; if (contact.title) info += `
Title: ${esc(contact.title)}
`; if (contact.birthday) info += `
Birthday: ${esc(contact.birthday)}
`; if (contact.timezone) info += `
Timezone: ${esc(contact.timezone)}
`; // Tags const tagPills = (contact.tags || []).map(t => `${esc(t)}` ).join(' '); // Emails const emailRows = (contact.emails || []).map(e => `
${esc(e.address)} ${e.label ? `(${esc(e.label)})` : ''} ${e.isPrimary ? 'Primary' : ''}
`).join(''); // Phones const phoneRows = (contact.phones || []).map(p => `
${esc(p.number)} ${p.label ? `(${esc(p.label)})` : ''} ${p.isPrimary ? 'Primary' : ''}
`).join(''); // Social handles const socialRows = (contact.socialHandles || []).map(s => `
${esc(s.platform)}: ${s.url ? `${esc(s.handle)}` : esc(s.handle)}
`).join(''); // Custom fields const customFieldRows = (contact.customFields || []).map(f => `
${esc(f.label)}: ${f.url ? `${esc(f.value)}` : esc(f.value)}
`).join(''); const content = `
${esc(initials)}

${esc(contact.displayName)}

${contact.company ? `
${esc(contact.company)}${contact.title ? ` - ${esc(contact.title)}` : ''}
` : ''}
${info ? `
${info}
` : ''} ${tagPills ? `
${tagPills}
` : ''} ${contact.notes ? `
Notes:

${esc(contact.notes)}

` : ''}

Email Addresses

${emailRows || '
No email addresses
'}

Phone Numbers

${phoneRows || '
No phone numbers
'}

Social Handles

${socialRows || '
No social handles
'}

Custom Fields

${customFieldRows || '
No custom fields
'}
`; GoingsOn.ui.openModal(contact.displayName, content); } // ============ Populate Namespace ============ GoingsOn.contactsRender = { getInitials, renderCard, showDetailModal, }; })();