import { searchPostsForUrl, getCurrentNetwork, type SearchPost } from '../lib/api' const DISCUSSION_POST_LIMIT = 10 function escapeHtml(str: string): string { return str .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') } function formatDate(dateStr: string): string { const date = new Date(dateStr) return date.toLocaleDateString('ja-JP', { year: 'numeric', month: '2-digit', day: '2-digit', }) } function getPostUrl(uri: string, appUrl: string): string { // at://did:plc:xxx/app.bsky.feed.post/rkey -> {appUrl}/profile/did:plc:xxx/post/rkey const parts = uri.replace('at://', '').split('/') if (parts.length >= 3) { return `${appUrl}/profile/${parts[0]}/post/${parts[2]}` } return '#' } export function renderDiscussion(postUrl: string, appUrl: string = 'https://bsky.app'): string { // Build search URL with host/@username only let searchQuery = postUrl try { const urlObj = new URL(postUrl) const pathParts = urlObj.pathname.split('/').filter(Boolean) // pathParts[0] = @username.domain (e.g., @syui.syui.ai) // Extract just @username if (pathParts[0]?.startsWith('@')) { const handlePart = pathParts[0].slice(1) // remove @ const username = handlePart.split('.')[0] // get first part before . searchQuery = `${urlObj.host}/@${username}` } else { searchQuery = urlObj.host } } catch { // Keep original } const searchUrl = `${appUrl}/search?q=${encodeURIComponent(searchQuery)}` return `
Discuss on Bluesky
` } export async function loadDiscussionPosts(container: HTMLElement, postUrl: string, appUrl: string = 'https://bsky.app'): Promise { const postsContainer = container.querySelector('#discussion-posts') as HTMLElement if (!postsContainer) return // Get appUrl from network config (overrides default) const network = await getCurrentNetwork() const dataAppUrl = network.web || postsContainer.dataset.appUrl || appUrl postsContainer.innerHTML = '
Loading comments...
' const posts = await searchPostsForUrl(postUrl) if (posts.length === 0) { postsContainer.innerHTML = '' return } const postsHtml = posts.slice(0, DISCUSSION_POST_LIMIT).map((post: SearchPost) => { const author = post.author const avatar = author.avatar || '' const displayName = author.displayName || author.handle const handle = author.handle const record = post.record as { text?: string; createdAt?: string } const text = record?.text || '' const createdAt = record?.createdAt || '' const postLink = getPostUrl(post.uri, dataAppUrl) // Truncate text const truncatedText = text.length > 200 ? text.slice(0, 200) + '...' : text return `
${avatar ? `` : '
'}
${escapeHtml(displayName)} @${escapeHtml(handle)}
${formatDate(createdAt)}
${escapeHtml(truncatedText)}
` }).join('') postsContainer.innerHTML = postsHtml }