This commit is contained in:
2026-01-15 18:36:41 +09:00
commit 162072d980
24 changed files with 2153 additions and 0 deletions

171
src/main.ts Normal file
View File

@@ -0,0 +1,171 @@
import { getProfile, listRecords, getRecord, setNetworkConfig } from './lib/api.js'
import { renderServices } from './components/services.js'
import { login, logout, restoreSession, handleOAuthCallback, setAuthNetworkConfig, type AuthSession } from './lib/auth.js'
import { mountProfile } from './components/profile.js'
import { mountPostList, mountPostDetail } from './components/posts.js'
import { mountHeader } from './components/browser.js'
import { mountAtBrowser } from './components/atbrowser.js'
import { mountPostForm } from './components/postform.js'
import type { AppConfig, Networks } from './types.js'
let authSession: AuthSession | null = null
async function loadConfig(): Promise<AppConfig> {
const res = await fetch('/config.json')
return res.json()
}
async function loadNetworks(): Promise<Networks> {
const res = await fetch('/networks.json')
return res.json()
}
function renderFooter(handle: string): string {
const parts = handle.split('.')
const username = parts[0] || handle
return `
<footer class="site-footer">
<p>&copy; ${username}</p>
</footer>
`
}
function renderTabs(handle: string, mode: string | null, isLoggedIn: boolean): string {
const blogActive = !mode || mode === 'blog' ? 'active' : ''
const browserActive = mode === 'browser' ? 'active' : ''
const postActive = mode === 'post' ? 'active' : ''
let tabs = `
<a href="?handle=${handle}" class="tab ${blogActive}">Blog</a>
<a href="?mode=browser&handle=${handle}" class="tab ${browserActive}">Browser</a>
`
if (isLoggedIn) {
tabs += `<a href="?mode=post&handle=${handle}" class="tab ${postActive}">Post</a>`
}
return `<div class="mode-tabs">${tabs}</div>`
}
async function init(): Promise<void> {
const [config, networks] = await Promise.all([loadConfig(), loadNetworks()])
// Set page title
document.title = config.title || 'ailog'
// Set theme color
if (config.color) {
document.documentElement.style.setProperty('--btn-color', config.color)
}
// Set network config
const networkConfig = networks[config.network]
if (networkConfig) {
setNetworkConfig(networkConfig)
setAuthNetworkConfig(networkConfig)
}
// Handle OAuth callback
const callbackSession = await handleOAuthCallback()
if (callbackSession) {
authSession = callbackSession
} else {
// Try to restore existing session
authSession = await restoreSession()
}
const params = new URLSearchParams(window.location.search)
const mode = params.get('mode')
const rkey = params.get('rkey')
const collection = params.get('collection')
const service = params.get('service')
const handle = params.get('handle') || config.handle
const profileEl = document.getElementById('profile')
const contentEl = document.getElementById('content')
const headerEl = document.getElementById('header')
const footerEl = document.getElementById('footer')
if (!profileEl || !contentEl || !headerEl) return
// Footer
if (footerEl) {
footerEl.innerHTML = renderFooter(config.handle)
}
const isLoggedIn = !!authSession
// Header with login
mountHeader(headerEl, handle, isLoggedIn, authSession?.handle, {
onBrowse: (newHandle) => {
const currentMode = params.get('mode')
if (currentMode === 'browser') {
window.location.href = `?mode=browser&handle=${newHandle}`
} else {
window.location.href = `?handle=${newHandle}`
}
},
onLogin: async () => {
const inputHandle = (document.getElementById('header-input') as HTMLInputElement)?.value || handle
try {
await login(inputHandle)
} catch (err) {
console.error('Login error:', err)
alert('Login failed: ' + err)
}
},
onLogout: async () => {
await logout()
window.location.reload()
}
})
// Post mode (requires login)
if (mode === 'post' && isLoggedIn) {
profileEl.innerHTML = renderTabs(handle, mode, isLoggedIn)
mountPostForm(contentEl, config.collection, () => {
window.location.href = `?handle=${handle}`
})
return
}
// AT Browser mode
if (mode === 'browser') {
profileEl.innerHTML = renderTabs(handle, mode, isLoggedIn)
const loginDid = authSession?.did || null
await mountAtBrowser(contentEl, handle, collection, rkey, service, loginDid)
return
}
// Blog mode (default)
try {
const profile = await getProfile(handle)
profileEl.innerHTML = renderTabs(handle, mode, isLoggedIn)
const profileContentEl = document.createElement('div')
profileEl.appendChild(profileContentEl)
mountProfile(profileContentEl, profile)
// Add services
const servicesHtml = await renderServices(handle)
profileContentEl.insertAdjacentHTML('beforeend', servicesHtml)
if (rkey) {
const post = await getRecord(profile.did, config.collection, rkey)
if (post) {
const canEdit = isLoggedIn && authSession?.did === profile.did
mountPostDetail(contentEl, post, handle, config.collection, canEdit)
} else {
contentEl.innerHTML = '<p>Post not found</p>'
}
} else {
const posts = await listRecords(profile.did, config.collection)
mountPostList(contentEl, posts)
}
} catch (err) {
console.error(err)
contentEl.innerHTML = `<p class="error">Failed to load: ${err}</p>`
}
}
init()