103 lines
3.7 KiB
TypeScript
103 lines
3.7 KiB
TypeScript
import { searchPostsForUrl } from '../lib/api.js'
|
|
import { escapeHtml } from '../lib/utils.js'
|
|
|
|
// Map network to app URL
|
|
export function getAppUrl(network: string): string {
|
|
if (network === 'syu.is') {
|
|
return 'https://syu.is'
|
|
}
|
|
return 'https://bsky.app'
|
|
}
|
|
|
|
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 renderDiscussionLink(postUrl: string, appUrl: string = 'https://bsky.app'): string {
|
|
// Convert full URL to search-friendly format (domain/post/rkey_prefix without https://)
|
|
// Keep total length around 20 chars to avoid URL truncation in posts
|
|
const MAX_SEARCH_LENGTH = 20
|
|
let searchQuery = postUrl
|
|
try {
|
|
const urlObj = new URL(postUrl)
|
|
const pathParts = urlObj.pathname.split('/').filter(Boolean)
|
|
const basePath = urlObj.host + '/' + (pathParts[0] || '') + '/'
|
|
const rkey = pathParts[1] || ''
|
|
const remainingLength = MAX_SEARCH_LENGTH - basePath.length
|
|
const rkeyPrefix = remainingLength > 0 ? rkey.slice(0, remainingLength) : ''
|
|
searchQuery = basePath + rkeyPrefix
|
|
} catch {
|
|
// Keep original if parsing fails
|
|
}
|
|
const searchUrl = `${appUrl}/search?q=${encodeURIComponent(searchQuery)}`
|
|
return `
|
|
<div class="discussion-section">
|
|
<a href="${searchUrl}" target="_blank" rel="noopener" class="discuss-link">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
|
<path d="M12 2C6.477 2 2 6.477 2 12c0 1.89.525 3.66 1.438 5.168L2.546 20.2A1.5 1.5 0 0 0 4 22h.5l2.83-.892A9.96 9.96 0 0 0 12 22c5.523 0 10-4.477 10-10S17.523 2 12 2z"/>
|
|
</svg>
|
|
Discuss on Bluesky
|
|
</a>
|
|
<div id="discussion-posts" class="discussion-posts" data-app-url="${escapeHtml(appUrl)}"></div>
|
|
</div>
|
|
`
|
|
}
|
|
|
|
export async function loadDiscussionPosts(container: HTMLElement, postUrl: string, appUrl: string = 'https://bsky.app'): Promise<void> {
|
|
const postsContainer = container.querySelector('#discussion-posts') as HTMLElement
|
|
if (!postsContainer) return
|
|
|
|
// Get appUrl from data attribute if available
|
|
const dataAppUrl = postsContainer.dataset.appUrl
|
|
const effectiveAppUrl = dataAppUrl || appUrl
|
|
|
|
postsContainer.innerHTML = '<div class="loading-small">Loading...</div>'
|
|
|
|
const posts = await searchPostsForUrl(postUrl)
|
|
|
|
if (posts.length === 0) {
|
|
postsContainer.innerHTML = ''
|
|
return
|
|
}
|
|
|
|
const postsHtml = posts.slice(0, 10).map(post => {
|
|
const author = post.author
|
|
const avatar = author.avatar || ''
|
|
const displayName = author.displayName || author.handle
|
|
const handle = author.handle
|
|
const text = post.record?.text || ''
|
|
const createdAt = post.record?.createdAt || ''
|
|
const postLink = getPostUrl(post.uri, effectiveAppUrl)
|
|
|
|
return `
|
|
<a href="${postLink}" target="_blank" rel="noopener" class="discussion-post">
|
|
<div class="discussion-author">
|
|
${avatar ? `<img src="${escapeHtml(avatar)}" class="discussion-avatar" alt="">` : ''}
|
|
<div class="discussion-author-info">
|
|
<span class="discussion-name">${escapeHtml(displayName)}</span>
|
|
<span class="discussion-handle">@${escapeHtml(handle)}</span>
|
|
</div>
|
|
<span class="discussion-date">${formatDate(createdAt)}</span>
|
|
</div>
|
|
<div class="discussion-text">${escapeHtml(text)}</div>
|
|
</a>
|
|
`
|
|
}).join('')
|
|
|
|
postsContainer.innerHTML = postsHtml
|
|
}
|