add comment
This commit is contained in:
@@ -1,38 +1,8 @@
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import { marked, Renderer } from 'marked'
|
||||
|
||||
// Types
|
||||
interface AppConfig {
|
||||
title: string
|
||||
handle: string
|
||||
collection: string
|
||||
network: string
|
||||
color?: string
|
||||
}
|
||||
|
||||
interface Networks {
|
||||
[key: string]: {
|
||||
plc: string
|
||||
bsky: string
|
||||
}
|
||||
}
|
||||
|
||||
interface Profile {
|
||||
did: string
|
||||
handle: string
|
||||
displayName?: string
|
||||
description?: string
|
||||
avatar?: string
|
||||
}
|
||||
|
||||
interface BlogPost {
|
||||
uri: string
|
||||
cid: string
|
||||
title: string
|
||||
content: string
|
||||
createdAt: string
|
||||
}
|
||||
import type { AppConfig, Profile, BlogPost, Networks } from '../src/types.ts'
|
||||
import { escapeHtml } from '../src/lib/utils.ts'
|
||||
|
||||
// Highlight.js for syntax highlighting (core + common languages only)
|
||||
let hljs: typeof import('highlight.js/lib/core').default
|
||||
@@ -100,14 +70,6 @@ function setupMarked() {
|
||||
})
|
||||
}
|
||||
|
||||
function escapeHtml(str: string): string {
|
||||
return str
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
}
|
||||
|
||||
function formatDate(dateStr: string): string {
|
||||
const date = new Date(dateStr)
|
||||
return date.toLocaleDateString('ja-JP', {
|
||||
@@ -445,10 +407,23 @@ function generatePostListHtml(posts: BlogPost[]): string {
|
||||
return `<ul class="post-list">${items}</ul>`
|
||||
}
|
||||
|
||||
function generatePostDetailHtml(post: BlogPost, handle: string, collection: string): string {
|
||||
// Map network to app URL for discussion links
|
||||
function getAppUrl(network: string): string {
|
||||
if (network === 'syu.is') {
|
||||
return 'https://syu.is'
|
||||
}
|
||||
return 'https://bsky.app'
|
||||
}
|
||||
|
||||
function generatePostDetailHtml(post: BlogPost, handle: string, collection: string, network: string, siteUrl?: string): string {
|
||||
const rkey = post.uri.split('/').pop() || ''
|
||||
const jsonUrl = `/at/${handle}/${collection}/${rkey}/`
|
||||
const content = marked.parse(post.content) as string
|
||||
// Use siteUrl from config, or construct from handle
|
||||
const baseSiteUrl = siteUrl || `https://${handle}`
|
||||
const postUrl = `${baseSiteUrl}/post/${rkey}/`
|
||||
const appUrl = getAppUrl(network)
|
||||
const searchUrl = `${appUrl}/search?q=${encodeURIComponent(postUrl)}`
|
||||
|
||||
return `
|
||||
<article class="post-detail">
|
||||
@@ -461,6 +436,15 @@ function generatePostDetailHtml(post: BlogPost, handle: string, collection: stri
|
||||
</header>
|
||||
<div class="post-content">${content}</div>
|
||||
</article>
|
||||
<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-post-url="${escapeHtml(postUrl)}" data-app-url="${escapeHtml(appUrl)}"></div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
@@ -500,7 +484,7 @@ function generatePostPageContent(profile: Profile, post: BlogPost, config: AppCo
|
||||
${generateServicesHtml(profile.did, config.handle, collections)}
|
||||
</section>
|
||||
<section id="content">
|
||||
${generatePostDetailHtml(post, config.handle, config.collection)}
|
||||
${generatePostDetailHtml(post, config.handle, config.collection, config.network, config.siteUrl)}
|
||||
</section>
|
||||
</main>
|
||||
${generateFooterHtml(config.handle)}
|
||||
@@ -595,27 +579,20 @@ async function generate() {
|
||||
const localPosts = localDid ? loadPostsFromFiles(localDid, config.collection) : []
|
||||
console.log(`Found ${localPosts.length} posts from local`)
|
||||
|
||||
// Merge: API is the source of truth for what exists
|
||||
// - If post exists in API and local: use local (may have edits)
|
||||
// - If post exists in API only: use API
|
||||
// - If post exists in local only: skip (was deleted from API)
|
||||
// Merge: API is the source of truth
|
||||
// - If post exists in API: always use API (has latest edits)
|
||||
// - If post exists in local only: keep if not deleted (for posts beyond API limit)
|
||||
const apiRkeys = new Set(apiPosts.map(p => p.uri.split('/').pop()))
|
||||
const localRkeys = new Set(localPosts.map(p => p.uri.split('/').pop()))
|
||||
|
||||
// Local posts that still exist in API
|
||||
const validLocalPosts = localPosts.filter(p => apiRkeys.has(p.uri.split('/').pop()))
|
||||
// API posts that don't exist locally
|
||||
const newApiPosts = apiPosts.filter(p => !localRkeys.has(p.uri.split('/').pop()))
|
||||
// Local posts that don't exist in API (older posts beyond 100 limit)
|
||||
// Note: these might be deleted posts, so we keep them cautiously
|
||||
const oldLocalPosts = localPosts.filter(p => !apiRkeys.has(p.uri.split('/').pop()))
|
||||
|
||||
posts = [...validLocalPosts, ...newApiPosts].sort((a, b) =>
|
||||
posts = [...apiPosts, ...oldLocalPosts].sort((a, b) =>
|
||||
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
||||
)
|
||||
|
||||
const deletedCount = localPosts.length - validLocalPosts.length
|
||||
if (deletedCount > 0) {
|
||||
console.log(`Skipped ${deletedCount} deleted posts (exist locally but not in API)`)
|
||||
}
|
||||
console.log(`Total ${posts.length} posts (${validLocalPosts.length} local + ${newApiPosts.length} new from API)`)
|
||||
console.log(`Total ${posts.length} posts (${apiPosts.length} from API + ${oldLocalPosts.length} old local)`)
|
||||
|
||||
// Create output directory
|
||||
const distDir = path.join(process.cwd(), 'dist')
|
||||
|
||||
Reference in New Issue
Block a user