add old lexicon merge

This commit is contained in:
2026-02-19 08:18:02 +09:00
parent 24c5567f21
commit a97b10ba31
3 changed files with 93 additions and 11 deletions

View File

@@ -3,6 +3,17 @@ import { renderMarkdown } from '../lib/markdown'
import { renderDiscussion, loadDiscussionPosts } from './discussion' import { renderDiscussion, loadDiscussionPosts } from './discussion'
import { getCurrentLang } from './mode-tabs' import { getCurrentLang } from './mode-tabs'
// Extract text content from post content (handles both string and object formats)
function getContentText(content: unknown): string {
if (!content) return ''
if (typeof content === 'string') return content
if (typeof content === 'object' && content !== null) {
const obj = content as Record<string, unknown>
if (typeof obj.text === 'string') return obj.text
}
return ''
}
// Format date as yyyy/mm/dd // Format date as yyyy/mm/dd
function formatDate(dateStr: string): string { function formatDate(dateStr: string): string {
const d = new Date(dateStr) const d = new Date(dateStr)
@@ -52,7 +63,8 @@ export function renderPostDetail(
collection: string, collection: string,
isOwner: boolean = false, isOwner: boolean = false,
siteUrl?: string, siteUrl?: string,
appUrl: string = 'https://bsky.app' appUrl: string = 'https://bsky.app',
canMerge: boolean = false
): string { ): string {
const rkey = post.uri.split('/').pop() || '' const rkey = post.uri.split('/').pop() || ''
const date = formatDate(post.value.publishedAt) const date = formatDate(post.value.publishedAt)
@@ -61,29 +73,32 @@ export function renderPostDetail(
// Build post URL for discussion search // Build post URL for discussion search
const postUrl = siteUrl ? `${siteUrl}/@${handle}/${rkey}` : `${window.location.origin}/@${handle}/${rkey}` const postUrl = siteUrl ? `${siteUrl}/@${handle}/${rkey}` : `${window.location.origin}/@${handle}/${rkey}`
const rawContent = getContentText(post.value.content)
const editBtn = isOwner ? `<button type="button" class="post-edit-btn" id="post-edit-btn">Edit</button>` : '' const editBtn = isOwner ? `<button type="button" class="post-edit-btn" id="post-edit-btn">Edit</button>` : ''
const mergeBtn = canMerge ? `<button type="button" class="post-merge-btn" id="post-merge-btn" data-collection="${collection}" data-rkey="${rkey}" data-handle="${escapeHtml(handle)}" data-title="${escapeHtml(post.value.title || '')}" data-content="${escapeHtml(rawContent)}">Merge</button>` : ''
// Get current language and show appropriate content // Get current language and show appropriate content
const currentLang = getCurrentLang() const currentLang = getCurrentLang()
const translations = post.value.translations const translations = post.value.translations
const originalLang = post.value.langs?.[0] || 'ja' const originalLang = post.value.langs?.[0] || 'ja'
let displayTitle = post.value.title let displayTitle = post.value.title || ''
let displayContent = post.value.content.text let displayContent = rawContent
// Use translation if available and not original language // Use translation if available and not original language
if (translations && currentLang !== originalLang && translations[currentLang]) { if (translations && currentLang !== originalLang && translations[currentLang]) {
const trans = translations[currentLang] const trans = translations[currentLang]
displayTitle = trans.title || post.value.title displayTitle = trans.title || post.value.title || ''
displayContent = trans.content displayContent = trans.content || rawContent
} }
const content = renderMarkdown(displayContent) const content = displayContent ? renderMarkdown(displayContent) : ''
const editForm = isOwner ? ` const editForm = isOwner ? `
<div class="post-edit-form" id="post-edit-form" style="display: none;"> <div class="post-edit-form" id="post-edit-form" style="display: none;">
<input type="text" class="post-edit-title" id="post-edit-title" value="${escapeHtml(post.value.title)}" placeholder="Title"> <input type="text" class="post-edit-title" id="post-edit-title" value="${escapeHtml(post.value.title || '')}" placeholder="Title">
<textarea class="post-edit-content" id="post-edit-content" rows="15">${escapeHtml(post.value.content.text)}</textarea> <textarea class="post-edit-content" id="post-edit-content" rows="15">${escapeHtml(rawContent)}</textarea>
<div class="post-edit-actions"> <div class="post-edit-actions">
<button type="button" class="post-edit-cancel" id="post-edit-cancel">Cancel</button> <button type="button" class="post-edit-cancel" id="post-edit-cancel">Cancel</button>
<button type="button" class="post-edit-save" id="post-edit-save" data-collection="${collection}" data-rkey="${rkey}">Save</button> <button type="button" class="post-edit-save" id="post-edit-save" data-collection="${collection}" data-rkey="${rkey}">Save</button>
@@ -98,6 +113,7 @@ export function renderPostDetail(
<time class="post-date">${date}</time> <time class="post-date">${date}</time>
<a href="${jsonUrl}" class="json-btn">json</a> <a href="${jsonUrl}" class="json-btn">json</a>
${editBtn} ${editBtn}
${mergeBtn}
</div> </div>
</header> </header>
${editForm} ${editForm}

View File

@@ -3,7 +3,7 @@ import './styles/card.css'
import './styles/card-migrate.css' import './styles/card-migrate.css'
import { getConfig, resolveHandle, getProfile, getPosts, getPost, describeRepo, listRecords, getRecord, getPds, getNetworks, getChatMessages, getCards, getCardAdmin, getRse, getRseAdmin, getLinks } from './lib/api' import { getConfig, resolveHandle, getProfile, getPosts, getPost, describeRepo, listRecords, getRecord, getPds, getNetworks, getChatMessages, getCards, getCardAdmin, getRse, getRseAdmin, getLinks } from './lib/api'
import { parseRoute, onRouteChange, navigate, type Route } from './lib/router' import { parseRoute, onRouteChange, navigate, type Route } from './lib/router'
import { login, logout, handleCallback, restoreSession, isLoggedIn, getLoggedInHandle, getLoggedInDid, deleteRecord, updatePost, updateChat, updateLinks } from './lib/auth' import { login, logout, handleCallback, restoreSession, isLoggedIn, getLoggedInHandle, getLoggedInDid, deleteRecord, updatePost, updateChat, updateLinks, createPost } from './lib/auth'
import { validateRecord } from './lib/lexicon' import { validateRecord } from './lib/lexicon'
import { renderHeader } from './components/header' import { renderHeader } from './components/header'
import { renderProfile } from './components/profile' import { renderProfile } from './components/profile'
@@ -238,7 +238,8 @@ async function render(route: Route): Promise<void> {
const post = await getPost(did, config.collection, route.rkey, localOnly) const post = await getPost(did, config.collection, route.rkey, localOnly)
html += renderLangSelector(langList) html += renderLangSelector(langList)
if (post) { if (post) {
html += `<div id="content">${renderPostDetail(post, handle, config.collection, isOwner, config.siteUrl, webUrl)}</div>` const canMerge = isLoggedIn() && !isOwner
html += `<div id="content">${renderPostDetail(post, handle, config.collection, isOwner, config.siteUrl, webUrl, canMerge)}</div>`
} else { } else {
html += `<div id="content" class="error">Post not found</div>` html += `<div id="content" class="error">Post not found</div>`
} }
@@ -438,12 +439,13 @@ async function render(route: Route): Promise<void> {
setupValidateButton(currentRecord) setupValidateButton(currentRecord)
} }
// Setup post detail (translation toggle, discussion) // Setup post detail (translation toggle, discussion, merge)
if (route.type === 'post') { if (route.type === 'post') {
const contentEl = document.getElementById('content') const contentEl = document.getElementById('content')
if (contentEl) { if (contentEl) {
setupPostDetail(contentEl) setupPostDetail(contentEl)
} }
setupPostMerge(config.collection, handle)
} }
// Setup card migration button // Setup card migration button
@@ -629,6 +631,46 @@ function setupPostEdit(collection: string): void {
}) })
} }
// Setup merge button for post detail
function setupPostMerge(collection: string, _sourceHandle: string): void {
const mergeBtn = document.getElementById('post-merge-btn')
if (!mergeBtn) return
mergeBtn.addEventListener('click', async () => {
if (!confirm('Merge this post to your collection?')) return
const title = mergeBtn.getAttribute('data-title') || ''
const rawContent = mergeBtn.getAttribute('data-content') || ''
if (!rawContent) {
alert('No content to merge')
return
}
try {
mergeBtn.textContent = 'Merging...'
;(mergeBtn as HTMLButtonElement).disabled = true
await createPost(collection, title, rawContent)
mergeBtn.textContent = 'Merged!'
// Navigate to logged-in user's page
const loggedInHandle = getLoggedInHandle()
if (loggedInHandle) {
setTimeout(() => {
navigate({ type: 'user', handle: loggedInHandle })
}, 1000)
}
} catch (err) {
console.error('Merge failed:', err)
alert('Merge failed: ' + err)
mergeBtn.textContent = 'Merge'
;(mergeBtn as HTMLButtonElement).disabled = false
}
})
}
// Setup chat edit form // Setup chat edit form
function setupChatEdit(collection: string, handle: string): void { function setupChatEdit(collection: string, handle: string): void {
const form = document.getElementById('chat-edit-form') as HTMLFormElement const form = document.getElementById('chat-edit-form') as HTMLFormElement

View File

@@ -684,6 +684,30 @@ body {
opacity: 0.85; opacity: 0.85;
} }
.post-merge-btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 4px 10px;
margin-left: 8px;
background: #666;
color: #fff;
border: none;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
transition: opacity 0.2s;
}
.post-merge-btn:hover {
opacity: 0.85;
}
.post-merge-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Post Edit Form */ /* Post Edit Form */
.post-edit-form { .post-edit-form {
margin-top: 16px; margin-top: 16px;