add comment

This commit is contained in:
2026-01-15 23:26:34 +09:00
parent 9980e596ca
commit aa35de397f
16 changed files with 784 additions and 135 deletions

View File

@@ -6,11 +6,15 @@ 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 { loadDiscussionPosts } from './components/discussion.js'
import { parseRoute, type Route } from './lib/router.js'
import { escapeHtml } from './lib/utils.js'
import type { AppConfig, Networks } from './types.js'
let authSession: AuthSession | null = null
let config: AppConfig
let networks: Networks = {}
let browserNetwork: string = '' // Network for AT Browser
// Browser state
let browserMode = false
@@ -42,6 +46,39 @@ function renderFooter(handle: string): string {
`
}
function renderPdsSelector(): string {
const networkKeys = Object.keys(networks)
const options = networkKeys.map(key => {
const isSelected = key === browserNetwork
return `<div class="pds-option ${isSelected ? 'selected' : ''}" data-network="${escapeHtml(key)}">
<span class="pds-name">${escapeHtml(key)}</span>
<span class="pds-check">✓</span>
</div>`
}).join('')
return `
<div class="pds-selector" id="pds-selector">
<button type="button" class="tab" id="pds-tab">PDS</button>
<div class="pds-dropdown" id="pds-dropdown">
${options}
</div>
</div>
`
}
function updatePdsSelector(): void {
const dropdown = document.getElementById('pds-dropdown')
if (!dropdown) return
const options = dropdown.querySelectorAll('.pds-option')
options.forEach(opt => {
const el = opt as HTMLElement
const network = el.dataset.network
const isSelected = network === browserNetwork
el.classList.toggle('selected', isSelected)
})
}
function renderTabs(activeTab: 'blog' | 'browser' | 'new', isLoggedIn: boolean): string {
let tabs = `
<a href="/" class="tab ${activeTab === 'blog' ? 'active' : ''}" id="blog-tab">Blog</a>
@@ -52,6 +89,8 @@ function renderTabs(activeTab: 'blog' | 'browser' | 'new', isLoggedIn: boolean):
tabs += `<a href="/post" class="tab ${activeTab === 'new' ? 'active' : ''}">Post</a>`
}
tabs += renderPdsSelector()
return `<div class="mode-tabs">${tabs}</div>`
}
@@ -123,6 +162,12 @@ async function loadBrowserContent(): Promise<void> {
const contentEl = document.getElementById('content')
if (!contentEl) return
// Set network config for browser
const browserNetworkConfig = networks[browserNetwork]
if (browserNetworkConfig) {
setNetworkConfig(browserNetworkConfig)
}
const loginDid = authSession?.did || null
await mountAtBrowser(
contentEl,
@@ -217,14 +262,6 @@ async function addEditButtonToStaticPost(collection: string, rkey: string, sessi
})
}
function escapeHtml(str: string): string {
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
}
// Refresh post list from API (for static pages)
async function refreshPostListFromAPI(): Promise<void> {
const contentEl = document.getElementById('content')
@@ -327,6 +364,57 @@ function setupEventHandlers(): void {
return
}
// PDS tab button - toggle dropdown
if (target.id === 'pds-tab' || target.closest('#pds-tab')) {
e.preventDefault()
e.stopPropagation()
const dropdown = document.getElementById('pds-dropdown')
if (dropdown) {
dropdown.classList.toggle('show')
}
return
}
// PDS option selection
const pdsOption = target.closest('.pds-option') as HTMLElement
if (pdsOption) {
e.preventDefault()
const selectedNetwork = pdsOption.dataset.network
if (selectedNetwork && selectedNetwork !== browserNetwork) {
browserNetwork = selectedNetwork
localStorage.setItem('browserNetwork', selectedNetwork)
// Update network config for API and Auth
const networkConfig = networks[selectedNetwork]
if (networkConfig) {
setNetworkConfig(networkConfig)
setAuthNetworkConfig(networkConfig)
}
// Update UI
updatePdsSelector()
// Reload browser if in browser mode
if (browserMode) {
loadBrowserContent()
}
}
// Close dropdown
const dropdown = document.getElementById('pds-dropdown')
if (dropdown) {
dropdown.classList.remove('show')
}
return
}
// Close PDS dropdown when clicking outside
if (!target.closest('#pds-selector')) {
const dropdown = document.getElementById('pds-dropdown')
if (dropdown) {
dropdown.classList.remove('show')
}
}
// JSON button click (on post detail page)
const jsonBtn = target.closest('.json-btn') as HTMLAnchorElement
if (jsonBtn) {
@@ -432,12 +520,17 @@ async function render(): Promise<void> {
}
}, true)
// Update tabs to show Post tab if logged in
if (isLoggedIn) {
const tabsEl = document.querySelector('.mode-tabs')
if (tabsEl && !tabsEl.querySelector('a[href="/post"]')) {
// Update tabs
const tabsEl = document.querySelector('.mode-tabs')
if (tabsEl) {
// Add Post tab if logged in
if (isLoggedIn && !tabsEl.querySelector('a[href="/post"]')) {
tabsEl.insertAdjacentHTML('beforeend', '<a href="/post" class="tab">Post</a>')
}
// Add PDS selector if not present
if (!tabsEl.querySelector('#pds-selector')) {
tabsEl.insertAdjacentHTML('beforeend', renderPdsSelector())
}
}
// For post pages, add edit button if logged in and can edit
@@ -445,6 +538,17 @@ async function render(): Promise<void> {
addEditButtonToStaticPost(config.collection, route.rkey, authSession!)
}
// For post pages, load discussion posts
if (route.type === 'post') {
const discussionContainer = document.getElementById('discussion-posts')
if (discussionContainer) {
const postUrl = discussionContainer.dataset.postUrl
if (postUrl) {
loadDiscussionPosts(discussionContainer.parentElement!, postUrl)
}
}
}
// For blog top page, check for new posts from API and merge
if (route.type === 'blog') {
refreshPostListFromAPI()
@@ -505,7 +609,7 @@ async function render(): Promise<void> {
const post = await getRecord(profile.did, config.collection, route.rkey!)
if (post) {
const canEdit = isLoggedIn && authSession?.did === profile.did
mountPostDetail(contentEl, post, config.handle, config.collection, canEdit)
mountPostDetail(contentEl, post, config.handle, config.collection, canEdit, config.siteUrl, config.network)
} else {
contentEl.innerHTML = '<p>Post not found</p>'
}
@@ -538,8 +642,9 @@ async function render(): Promise<void> {
}
async function init(): Promise<void> {
const [configData, networks] = await Promise.all([loadConfig(), loadNetworks()])
const [configData, networksData] = await Promise.all([loadConfig(), loadNetworks()])
config = configData
networks = networksData
// Set page title
document.title = config.title || 'ailog'
@@ -549,11 +654,14 @@ async function init(): Promise<void> {
document.documentElement.style.setProperty('--btn-color', config.color)
}
// Set network config
const networkConfig = networks[config.network]
if (networkConfig) {
setNetworkConfig(networkConfig)
setAuthNetworkConfig(networkConfig)
// Initialize browser network from localStorage or default to config.network
browserNetwork = localStorage.getItem('browserNetwork') || config.network
// Set network config based on selected browser network
const selectedNetworkConfig = networks[browserNetwork]
if (selectedNetworkConfig) {
setNetworkConfig(selectedNetworkConfig)
setAuthNetworkConfig(selectedNetworkConfig)
}
// Handle OAuth callback