your personal website on atproto - mirror blento.app
at next 128 lines 3.9 kB view raw
1import { user } from '$lib/atproto/auth.svelte'; 2import type { CardDefinition } from '../types'; 3import VCardCard from './VCardCard.svelte'; 4import VCardCardSettings from './VCardCardSettings.svelte'; 5 6// vCard spec: https://wikipedia.org/wiki/VCard 7 8export type VCardFields = { 9 firstName: string; 10 lastName: string; 11 org: string; 12 title: string; 13 email: string; 14 bday: string; // YYYY-MM-DD for input, stored as YYYYMMDD 15 website: string; 16 address: string; 17 note: string; 18}; 19 20export const emptyVCardFields: VCardFields = { 21 firstName: '', 22 lastName: '', 23 org: '', 24 title: '', 25 email: '', 26 bday: '', 27 website: '', 28 address: '', 29 note: '' 30}; 31 32// Convert YYYY-MM-DD to YYYYMMDD for vCard 33export function formatBdayToVCard(date: string): string { 34 return date.replace(/-/g, ''); 35} 36 37// Convert YYYYMMDD to YYYY-MM-DD for input 38export function formatBdayFromVCard(bday: string): string { 39 if (bday.length === 8) { 40 return `${bday.slice(0, 4)}-${bday.slice(4, 6)}-${bday.slice(6, 8)}`; 41 } 42 return bday; 43} 44 45// Generate vCard v4 string from fields 46export function generateVCard(f: VCardFields): string { 47 const lines = ['BEGIN:VCARD', 'VERSION:4.0']; 48 const fn = `${f.firstName} ${f.lastName}`.trim(); 49 if (fn) lines.push(`FN:${fn}`); 50 if (f.lastName || f.firstName) lines.push(`N:${f.lastName};${f.firstName};;;`); 51 if (f.org) lines.push(`ORG:${f.org}`); 52 if (f.title) lines.push(`TITLE:${f.title}`); 53 if (f.email) lines.push(`EMAIL:${f.email}`); 54 if (f.bday) lines.push(`BDAY:${formatBdayToVCard(f.bday)}`); 55 if (f.website) lines.push(`URL:${f.website}`); 56 if (f.address) lines.push(`ADR:;;${f.address};;;;`); 57 if (f.note) lines.push(`NOTE:${f.note}`); 58 lines.push('END:VCARD'); 59 return lines.join('\n'); 60} 61 62// Parse vCard string to fields (supports v3 & v4) 63export function parseVCard(vcard: string): VCardFields { 64 const get = (key: string) => { 65 const m = vcard.match(new RegExp(`^${key}[;:](.*)$`, 'im')); 66 return m?.[1]?.trim() || ''; 67 }; 68 69 const n = get('N').split(';'); 70 let lastName = n[0] || ''; 71 let firstName = n[1] || ''; 72 73 if (!lastName && !firstName) { 74 const fn = get('FN').split(' '); 75 firstName = fn[0] || ''; 76 lastName = fn.slice(1).join(' ') || ''; 77 } 78 79 const adr = get('ADR').split(';'); 80 81 return { 82 firstName, 83 lastName, 84 org: get('ORG').split(';')[0], 85 title: get('TITLE'), 86 email: get('EMAIL'), 87 bday: formatBdayFromVCard(get('BDAY')), 88 website: get('URL'), 89 address: adr[2] || '', 90 note: get('NOTE') 91 }; 92} 93 94// Parse FN (formatted name) or N from vCard 95export function parseVCardName(vcard: string): string { 96 const f = parseVCard(vcard); 97 return `${f.firstName} ${f.lastName}`.trim(); 98} 99 100// Parse ORG from vCard 101export function parseVCardOrg(vcard: string): string { 102 return parseVCard(vcard).org; 103} 104 105export const VCardCardDefinition = { 106 type: 'vcard', 107 contentComponent: VCardCard, 108 settingsComponent: VCardCardSettings, 109 110 createNew: (card) => { 111 card.w = 2; 112 card.h = 2; 113 card.mobileW = 4; 114 card.mobileH = 4; 115 const displayName = user.profile?.displayName || user.profile?.handle || ''; 116 card.cardData.vcard = generateVCard({ 117 ...emptyVCardFields, 118 lastName: displayName 119 }); 120 card.cardData.displayName = displayName; 121 }, 122 123 allowSetColor: true, 124 name: 'vCard Card', 125 keywords: ['contact', 'phone', 'email', 'address', 'business card'], 126 groups: ['Social'], 127 icon: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="size-4"><path stroke-linecap="round" stroke-linejoin="round" d="M15 9h3.75M15 12h3.75M15 15h3.75M4.5 19.5h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Zm6-10.125a1.875 1.875 0 1 1-3.75 0 1.875 1.875 0 0 1 3.75 0Zm1.294 6.336a6.721 6.721 0 0 1-3.17.789 6.721 6.721 0 0 1-3.168-.789 3.376 3.376 0 0 1 6.338 0Z" /></svg>` 128} as CardDefinition & { type: 'vcard' };