refact update
This commit is contained in:
@@ -86,16 +86,3 @@ export function mountHeader(
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Keep old function for compatibility
|
||||
export function mountBrowser(
|
||||
container: HTMLElement,
|
||||
currentHandle: string,
|
||||
onSubmit: (handle: string) => void
|
||||
): void {
|
||||
mountHeader(container, currentHandle, false, undefined, {
|
||||
onBrowse: onSubmit,
|
||||
onLogin: () => {},
|
||||
onLogout: () => {}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { searchPostsForUrl } from '../lib/api.js'
|
||||
import { escapeHtml } from '../lib/utils.js'
|
||||
import { escapeHtml, formatDate } from '../lib/utils.js'
|
||||
import { MAX_SEARCH_LENGTH, DISCUSSION_POST_LIMIT } from '../lib/constants.js'
|
||||
|
||||
// Map network to app URL
|
||||
export function getAppUrl(network: string): string {
|
||||
@@ -9,15 +10,6 @@ export function getAppUrl(network: string): string {
|
||||
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('/')
|
||||
@@ -29,8 +21,6 @@ function getPostUrl(uri: string, appUrl: string): string {
|
||||
|
||||
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)
|
||||
@@ -74,7 +64,7 @@ export async function loadDiscussionPosts(container: HTMLElement, postUrl: strin
|
||||
return
|
||||
}
|
||||
|
||||
const postsHtml = posts.slice(0, 10).map(post => {
|
||||
const postsHtml = posts.slice(0, DISCUSSION_POST_LIMIT).map(post => {
|
||||
const author = post.author
|
||||
const avatar = author.avatar || ''
|
||||
const displayName = author.displayName || author.handle
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
import type { BlogPost } from '../types.js'
|
||||
import { putRecord } from '../lib/auth.js'
|
||||
import { renderMarkdown } from '../lib/markdown.js'
|
||||
import { escapeHtml } from '../lib/utils.js'
|
||||
import { escapeHtml, formatDate } from '../lib/utils.js'
|
||||
import { renderDiscussionLink, loadDiscussionPosts, getAppUrl } from './discussion.js'
|
||||
|
||||
function formatDate(dateStr: string): string {
|
||||
const date = new Date(dateStr)
|
||||
return date.toLocaleDateString('ja-JP', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
})
|
||||
}
|
||||
|
||||
export function mountPostList(container: HTMLElement, posts: BlogPost[]): void {
|
||||
if (posts.length === 0) {
|
||||
container.innerHTML = '<p class="no-posts">No posts yet</p>'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { AtpAgent } from '@atproto/api'
|
||||
import type { Profile, BlogPost, NetworkConfig } from '../types.js'
|
||||
import { FALLBACK_PLCS, FALLBACK_BSKY_ENDPOINTS, SEARCH_TIMEOUT_MS } from './constants.js'
|
||||
|
||||
const agents: Map<string, AtpAgent> = new Map()
|
||||
|
||||
@@ -24,18 +25,6 @@ export function getAgent(service: string): AtpAgent {
|
||||
return agents.get(service)!
|
||||
}
|
||||
|
||||
// Fallback PLC directories
|
||||
const FALLBACK_PLCS = [
|
||||
'https://plc.directory',
|
||||
'https://plc.syu.is',
|
||||
]
|
||||
|
||||
// Fallback endpoints for handle/profile resolution
|
||||
const FALLBACK_ENDPOINTS = [
|
||||
'https://public.api.bsky.app',
|
||||
'https://bsky.syu.is',
|
||||
]
|
||||
|
||||
export async function resolvePds(did: string): Promise<string> {
|
||||
// Try current PLC first, then fallbacks
|
||||
const plcs = [getPlc(), ...FALLBACK_PLCS.filter(p => p !== getPlc())]
|
||||
@@ -64,7 +53,7 @@ export async function resolveHandle(handle: string): Promise<string> {
|
||||
return res.data.did
|
||||
} catch {
|
||||
// Try fallback endpoints
|
||||
for (const endpoint of FALLBACK_ENDPOINTS) {
|
||||
for (const endpoint of FALLBACK_BSKY_ENDPOINTS) {
|
||||
if (endpoint === getBsky()) continue // Skip if same as current
|
||||
try {
|
||||
const agent = getAgent(endpoint)
|
||||
@@ -80,7 +69,7 @@ export async function resolveHandle(handle: string): Promise<string> {
|
||||
|
||||
export async function getProfile(actor: string): Promise<Profile> {
|
||||
// Try current network first
|
||||
const endpoints = [getBsky(), ...FALLBACK_ENDPOINTS.filter(e => e !== getBsky())]
|
||||
const endpoints = [getBsky(), ...FALLBACK_BSKY_ENDPOINTS.filter(e => e !== getBsky())]
|
||||
|
||||
for (const endpoint of endpoints) {
|
||||
try {
|
||||
@@ -273,7 +262,7 @@ export async function searchPostsForUrl(url: string): Promise<any[]> {
|
||||
searchQueries.map(async query => {
|
||||
try {
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), 5000) // 5s timeout
|
||||
const timeoutId = setTimeout(() => controller.abort(), SEARCH_TIMEOUT_MS)
|
||||
|
||||
const res = await fetch(
|
||||
`${endpoint}/xrpc/app.bsky.feed.searchPosts?q=${encodeURIComponent(query)}&limit=20`,
|
||||
|
||||
19
src/lib/constants.ts
Normal file
19
src/lib/constants.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
// API limits
|
||||
export const API_RECORD_LIMIT = 100
|
||||
export const POST_LIST_LIMIT = 50
|
||||
export const DISCUSSION_POST_LIMIT = 10
|
||||
|
||||
// Search
|
||||
export const MAX_SEARCH_LENGTH = 20
|
||||
export const SEARCH_TIMEOUT_MS = 5000
|
||||
|
||||
// Fallback endpoints
|
||||
export const FALLBACK_PLCS = [
|
||||
'https://plc.directory',
|
||||
'https://plc.syu.is',
|
||||
]
|
||||
|
||||
export const FALLBACK_BSKY_ENDPOINTS = [
|
||||
'https://public.api.bsky.app',
|
||||
'https://bsky.syu.is',
|
||||
]
|
||||
17
src/lib/icons.ts
Normal file
17
src/lib/icons.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
// Shared icon definitions
|
||||
|
||||
export const LANG_ICON = `<svg viewBox="0 0 640 640" width="20" height="20" fill="currentColor"><path d="M192 64C209.7 64 224 78.3 224 96L224 128L352 128C369.7 128 384 142.3 384 160C384 177.7 369.7 192 352 192L342.4 192L334 215.1C317.6 260.3 292.9 301.6 261.8 337.1C276 345.9 290.8 353.7 306.2 360.6L356.6 383L418.8 243C423.9 231.4 435.4 224 448 224C460.6 224 472.1 231.4 477.2 243L605.2 531C612.4 547.2 605.1 566.1 589 573.2C572.9 580.3 553.9 573.1 546.8 557L526.8 512L369.3 512L349.3 557C342.1 573.2 323.2 580.4 307.1 573.2C291 566 283.7 547.1 290.9 531L330.7 441.5L280.3 419.1C257.3 408.9 235.3 396.7 214.5 382.7C193.2 399.9 169.9 414.9 145 427.4L110.3 444.6C94.5 452.5 75.3 446.1 67.4 430.3C59.5 414.5 65.9 395.3 81.7 387.4L116.2 370.1C132.5 361.9 148 352.4 162.6 341.8C148.8 329.1 135.8 315.4 123.7 300.9L113.6 288.7C102.3 275.1 104.1 254.9 117.7 243.6C131.3 232.3 151.5 234.1 162.8 247.7L173 259.9C184.5 273.8 197.1 286.7 210.4 298.6C237.9 268.2 259.6 232.5 273.9 193.2L274.4 192L64.1 192C46.3 192 32 177.7 32 160C32 142.3 46.3 128 64 128L160 128L160 96C160 78.3 174.3 64 192 64zM448 334.8L397.7 448L498.3 448L448 334.8z"/></svg>`
|
||||
|
||||
export const DISCUSS_ICON = `<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>`
|
||||
|
||||
export const LOGIN_ICON = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"></path><polyline points="10 17 15 12 10 7"></polyline><line x1="15" y1="12" x2="3" y2="12"></line></svg>`
|
||||
|
||||
export const LOGOUT_ICON = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line></svg>`
|
||||
|
||||
// Footer link icons
|
||||
export const BUILTIN_ICONS: Record<string, string> = {
|
||||
bluesky: `<svg viewBox="0 0 600 530" fill="currentColor"><path d="m135.72 44.03c66.496 49.921 138.02 151.14 164.28 205.46 26.262-54.316 97.782-155.54 164.28-205.46 47.98-36.021 125.72-63.892 125.72 24.795 0 17.712-10.155 148.79-16.111 170.07-20.703 73.984-96.144 92.854-163.25 81.433 117.3 19.964 147.14 86.092 82.697 152.22-122.39 125.59-175.91-31.511-189.63-71.766-2.514-7.3797-3.6904-10.832-3.7077-7.8964-0.0174-2.9357-1.1937 0.51669-3.7077 7.8964-13.72 40.255-67.24 197.36-189.63 71.766-64.444-66.128-34.605-132.26 82.697-152.22-67.108 11.421-142.55-7.4491-163.25-81.433-5.9562-21.282-16.111-152.36-16.111-170.07 0-88.687 77.742-60.816 125.72-24.795z"/></svg>`,
|
||||
github: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>`,
|
||||
ai: `<span class="icon-ai"></span>`,
|
||||
git: `<span class="icon-git"></span>`,
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { marked, Renderer } from 'marked'
|
||||
import hljs from 'highlight.js/lib/core'
|
||||
import { escapeHtml } from './utils.js'
|
||||
|
||||
// Import only common languages
|
||||
import javascript from 'highlight.js/lib/languages/javascript'
|
||||
@@ -53,13 +54,6 @@ renderer.code = function({ text, lang }: { text: string; lang?: string }) {
|
||||
return `<pre><code class="hljs">${highlighted}</code></pre>`
|
||||
}
|
||||
|
||||
function escapeHtml(str: string): string {
|
||||
return str
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
}
|
||||
|
||||
marked.setOptions({
|
||||
breaks: true,
|
||||
gfm: true,
|
||||
|
||||
@@ -5,3 +5,12 @@ export function escapeHtml(str: string): string {
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
}
|
||||
|
||||
export function formatDate(dateStr: string): string {
|
||||
const date = new Date(dateStr)
|
||||
return date.toLocaleDateString('ja-JP', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
})
|
||||
}
|
||||
|
||||
11
src/main.ts
11
src/main.ts
@@ -9,6 +9,7 @@ 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 { LANG_ICON, BUILTIN_ICONS } from './lib/icons.js'
|
||||
import type { AppConfig, Networks } from './types.js'
|
||||
|
||||
let authSession: AuthSession | null = null
|
||||
@@ -56,12 +57,6 @@ async function loadLinks(): Promise<FooterLink[]> {
|
||||
}
|
||||
}
|
||||
|
||||
const BUILTIN_ICONS: Record<string, string> = {
|
||||
bluesky: `<svg viewBox="0 0 600 530" fill="currentColor"><path d="m135.72 44.03c66.496 49.921 138.02 151.14 164.28 205.46 26.262-54.316 97.782-155.54 164.28-205.46 47.98-36.021 125.72-63.892 125.72 24.795 0 17.712-10.155 148.79-16.111 170.07-20.703 73.984-96.144 92.854-163.25 81.433 117.3 19.964 147.14 86.092 82.697 152.22-122.39 125.59-175.91-31.511-189.63-71.766-2.514-7.3797-3.6904-10.832-3.7077-7.8964-0.0174-2.9357-1.1937 0.51669-3.7077 7.8964-13.72 40.255-67.24 197.36-189.63 71.766-64.444-66.128-34.605-132.26 82.697-152.22-67.108 11.421-142.55-7.4491-163.25-81.433-5.9562-21.282-16.111-152.36-16.111-170.07 0-88.687 77.742-60.816 125.72-24.795z"/></svg>`,
|
||||
github: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>`,
|
||||
ai: `<span class="icon-ai"></span>`,
|
||||
git: `<span class="icon-git"></span>`,
|
||||
}
|
||||
|
||||
function renderFooterLinks(links: FooterLink[]): string {
|
||||
if (links.length === 0) return ''
|
||||
@@ -146,12 +141,10 @@ function renderLangSelector(): string {
|
||||
</div>`
|
||||
}).join('')
|
||||
|
||||
const langIcon = `<svg viewBox="0 0 640 640" width="20" height="20" fill="currentColor"><path d="M192 64C209.7 64 224 78.3 224 96L224 128L352 128C369.7 128 384 142.3 384 160C384 177.7 369.7 192 352 192L342.4 192L334 215.1C317.6 260.3 292.9 301.6 261.8 337.1C276 345.9 290.8 353.7 306.2 360.6L356.6 383L418.8 243C423.9 231.4 435.4 224 448 224C460.6 224 472.1 231.4 477.2 243L605.2 531C612.4 547.2 605.1 566.1 589 573.2C572.9 580.3 553.9 573.1 546.8 557L526.8 512L369.3 512L349.3 557C342.1 573.2 323.2 580.4 307.1 573.2C291 566 283.7 547.1 290.9 531L330.7 441.5L280.3 419.1C257.3 408.9 235.3 396.7 214.5 382.7C193.2 399.9 169.9 414.9 145 427.4L110.3 444.6C94.5 452.5 75.3 446.1 67.4 430.3C59.5 414.5 65.9 395.3 81.7 387.4L116.2 370.1C132.5 361.9 148 352.4 162.6 341.8C148.8 329.1 135.8 315.4 123.7 300.9L113.6 288.7C102.3 275.1 104.1 254.9 117.7 243.6C131.3 232.3 151.5 234.1 162.8 247.7L173 259.9C184.5 273.8 197.1 286.7 210.4 298.6C237.9 268.2 259.6 232.5 273.9 193.2L274.4 192L64.1 192C46.3 192 32 177.7 32 160C32 142.3 46.3 128 64 128L160 128L160 96C160 78.3 174.3 64 192 64zM448 334.8L397.7 448L498.3 448L448 334.8z"/></svg>`
|
||||
|
||||
return `
|
||||
<div class="lang-selector" id="lang-selector">
|
||||
<button type="button" class="lang-btn" id="lang-btn" title="Language">
|
||||
${langIcon}
|
||||
${LANG_ICON}
|
||||
</button>
|
||||
<div class="lang-dropdown" id="lang-dropdown">
|
||||
${options}
|
||||
|
||||
Reference in New Issue
Block a user