your personal website on atproto - mirror blento.app
at signup 126 lines 3.3 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 sidebarButtonText: 'vCard', 124 allowSetColor: true, 125 name: 'vCard Card' 126} as CardDefinition & { type: 'vcard' };