simple new blog
This commit is contained in:
140
src/components/atbrowser.ts
Normal file
140
src/components/atbrowser.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import { describeRepo, listRecordsRaw, getRecordRaw, fetchLexicon, resolveHandle, getServiceInfo } from '../lib/api.js'
|
||||
|
||||
function extractRkey(uri: string): string {
|
||||
const parts = uri.split('/')
|
||||
return parts[parts.length - 1]
|
||||
}
|
||||
|
||||
function formatDate(dateStr: string): string {
|
||||
const date = new Date(dateStr)
|
||||
return date.toLocaleDateString('ja-JP', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
})
|
||||
}
|
||||
|
||||
function escapeHtml(str: string): string {
|
||||
return str
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
}
|
||||
|
||||
async function renderCollections(did: string, handle: string): Promise<string> {
|
||||
const collections = await describeRepo(did)
|
||||
|
||||
if (collections.length === 0) {
|
||||
return '<p class="no-data">No collections found</p>'
|
||||
}
|
||||
|
||||
const items = collections.map(col => {
|
||||
const service = getServiceInfo(col)
|
||||
const favicon = service ? `<img src="${service.favicon}" class="collection-favicon" alt="" onerror="this.style.display='none'">` : ''
|
||||
const serviceName = service ? `<span class="collection-service">${service.name}</span>` : ''
|
||||
|
||||
return `
|
||||
<li class="collection-item">
|
||||
<a href="?mode=browser&handle=${handle}&collection=${encodeURIComponent(col)}" class="collection-link">
|
||||
${favicon}
|
||||
<span class="collection-nsid">${col}</span>
|
||||
${serviceName}
|
||||
</a>
|
||||
</li>
|
||||
`
|
||||
}).join('')
|
||||
|
||||
return `
|
||||
<div class="collections">
|
||||
<h3>Collections</h3>
|
||||
<ul class="collection-list">${items}</ul>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
async function renderRecordList(did: string, handle: string, collection: string): Promise<string> {
|
||||
const records = await listRecordsRaw(did, collection)
|
||||
|
||||
if (records.length === 0) {
|
||||
return '<p class="no-data">No records found</p>'
|
||||
}
|
||||
|
||||
const items = records.map(rec => {
|
||||
const rkey = extractRkey(rec.uri)
|
||||
const preview = rec.value.title || rec.value.text?.slice(0, 50) || rkey
|
||||
return `
|
||||
<li class="record-item">
|
||||
<a href="?mode=browser&handle=${handle}&collection=${encodeURIComponent(collection)}&rkey=${rkey}" class="record-link">
|
||||
<span class="record-rkey">${rkey}</span>
|
||||
<span class="record-preview">${preview}</span>
|
||||
</a>
|
||||
</li>
|
||||
`
|
||||
}).join('')
|
||||
|
||||
return `
|
||||
<div class="records">
|
||||
<h3>${collection}</h3>
|
||||
<p class="record-count">${records.length} records</p>
|
||||
<ul class="record-list">${items}</ul>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
async function renderRecordDetail(did: string, handle: string, collection: string, rkey: string): Promise<string> {
|
||||
const record = await getRecordRaw(did, collection, rkey)
|
||||
|
||||
if (!record) {
|
||||
return '<p class="error">Record not found</p>'
|
||||
}
|
||||
|
||||
const lexicon = await fetchLexicon(collection)
|
||||
const schemaStatus = lexicon ? 'verified' : 'none'
|
||||
const schemaLabel = lexicon ? '✓ Schema' : '○ No schema'
|
||||
const json = JSON.stringify(record, null, 2)
|
||||
|
||||
return `
|
||||
<div class="record-detail">
|
||||
<div class="record-header">
|
||||
<h3>${collection}</h3>
|
||||
<p class="record-uri">${record.uri}</p>
|
||||
<p class="record-cid">CID: ${record.cid}</p>
|
||||
<span class="schema-status schema-${schemaStatus}">${schemaLabel}</span>
|
||||
</div>
|
||||
<div class="json-view">
|
||||
<pre><code>${escapeHtml(json)}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
export async function mountAtBrowser(
|
||||
container: HTMLElement,
|
||||
handle: string,
|
||||
collection: string | null,
|
||||
rkey: string | null
|
||||
): Promise<void> {
|
||||
container.innerHTML = '<p class="loading">Loading...</p>'
|
||||
|
||||
try {
|
||||
const did = handle.startsWith('did:') ? handle : await resolveHandle(handle)
|
||||
|
||||
let content: string
|
||||
let nav = ''
|
||||
|
||||
if (collection && rkey) {
|
||||
nav = `<a href="?mode=browser&handle=${handle}&collection=${encodeURIComponent(collection)}" class="back-link">← Back</a>`
|
||||
content = await renderRecordDetail(did, handle, collection, rkey)
|
||||
} else if (collection) {
|
||||
nav = `<a href="?mode=browser&handle=${handle}" class="back-link">← Collections</a>`
|
||||
content = await renderRecordList(did, handle, collection)
|
||||
} else {
|
||||
content = await renderCollections(did, handle)
|
||||
}
|
||||
|
||||
container.innerHTML = nav + content
|
||||
} catch (err) {
|
||||
container.innerHTML = `<p class="error">Failed to load: ${err}</p>`
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user