// AT-Browser: Server info and collection hierarchy // Group collections by service domain function groupCollectionsByService(collections: string[]): Map { const groups = new Map() for (const collection of collections) { // Extract service from collection (e.g., "app.bsky.feed.post" -> "bsky.app") const parts = collection.split('.') let service: string if (parts.length >= 2) { // Reverse first two parts: app.bsky -> bsky.app, ai.syui -> syui.ai service = `${parts[1]}.${parts[0]}` } else { service = collection } if (!groups.has(service)) { groups.set(service, []) } groups.get(service)!.push(collection) } return groups } // Local favicon mappings const localFavicons: Record = { 'syui.ai': '/favicon/syui.ai.png', 'bsky.app': '/favicon/bsky.app.png', 'atproto.com': '/favicon/atproto.com.png', } // Get favicon URL for service function getFaviconUrl(service: string): string { if (localFavicons[service]) { return localFavicons[service] } return `https://www.google.com/s2/favicons?domain=${service}&sz=32` } // Render compact collection buttons for user page (horizontal) export function renderCollectionButtons(collections: string[], handle: string): string { if (collections.length === 0) { return '' } const groups = groupCollectionsByService(collections) const buttons = Array.from(groups.keys()).map(service => { const favicon = getFaviconUrl(service) return ` ${service} ` }).join('') return `
${buttons}
` } // Render server info section (for AT-Browser) export function renderServerInfo(did: string, pds: string | null): string { return `

Server

PDS
${pds || 'Unknown'}
DID
${did}
` } // Render service list (grouped collections) for AT-Browser export function renderServiceList(collections: string[], handle: string): string { if (collections.length === 0) { return '

No collections found.

' } const groups = groupCollectionsByService(collections) const items = Array.from(groups.entries()).map(([service, cols]) => { const favicon = getFaviconUrl(service) const count = cols.length return `
  • ${service} ${count}
  • ` }).join('') return `

    Collections

      ${items}
    ` } // Render collections for a specific service export function renderCollectionList( collections: string[], handle: string, service: string ): string { const favicon = getFaviconUrl(service) const items = collections.map(collection => { return `
  • ${collection}
  • ` }).join('') return `

    ${service}

      ${items}
    ` } // Render records list export function renderRecordList( records: { uri: string; cid: string; value: unknown }[], handle: string, collection: string ): string { if (records.length === 0) { return '

    No records found.

    ' } const items = records.map(record => { const rkey = record.uri.split('/').pop() || '' const value = record.value as Record const preview = getRecordPreview(value) return `
  • ${rkey} ${escapeHtml(preview)}
  • ` }).join('') return `

    ${collection}

    ${records.length} records

      ${items}
    ` } // Render single record detail export function renderRecordDetail( record: { uri: string; cid: string; value: unknown }, collection: string, isOwner: boolean = false ): string { const rkey = record.uri.split('/').pop() || '' const deleteBtn = isOwner ? ` ` : '' return `

    ${collection}

    URI: ${record.uri}

    CID: ${record.cid}

    ${deleteBtn}
    ${escapeHtml(JSON.stringify(record.value, null, 2))}
    ` } // Get preview text from record value function getRecordPreview(value: Record): string { if (typeof value.text === 'string') return value.text.slice(0, 60) if (typeof value.title === 'string') return value.title if (typeof value.name === 'string') return value.name if (typeof value.displayName === 'string') return value.displayName if (typeof value.handle === 'string') return value.handle if (typeof value.subject === 'string') return value.subject if (typeof value.description === 'string') return value.description.slice(0, 60) return '' } function escapeHtml(text: string): string { return text .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, ''') }