このコミットが含まれているのは:
2026-07-17 21:06:26 +09:00
コミット 3820d3d4d5
21個のファイルの変更949行の追加1270行の削除
+130 -7
ファイルの表示
@@ -11,13 +11,20 @@ import MainArea from '@/components/layout/MainArea'
import { Button } from '@/components/ui/button'
import { toast } from '@/components/ui/use-toast'
import { SITE_TITLE } from '@/config'
import { apiPost, isApiError } from '@/lib/api'
import { serialisePostNewState } from '@/lib/postNewQueryState'
import { apiGet, isApiError } from '@/lib/api'
import {
appendPostNewSessionId,
serialisePostNewState,
} from '@/lib/postNewQueryState'
import { canEditContent } from '@/lib/users'
import { countImportSourceLines,
generatePostImportSessionId,
cleanupExpiredPostImportSessions,
initialisePreviewRows,
loadPostImportSession,
loadPostImportSourceDraft,
resultRepairMode,
savePostImportSession,
savePostImportSourceDraft,
validateImportSource } from '@/lib/postImportSession'
import Forbidden from '@/pages/Forbidden'
@@ -29,6 +36,19 @@ import type { User } from '@/types'
type Props = { user: User | null }
type PreviewResponse = {
url: string
title?: string
thumbnailBase?: string
originalCreatedFrom?: string
originalCreatedBefore?: string
duration?: string
videoMs?: number
tags?: string
fieldWarnings?: Record<string, string[]>
baseWarnings?: string[]
existingPost?: { id: number } | null }
const MAX_ROWS = 100
const SOURCE_ERROR_ID = 'post-import-source-error'
const SOURCE_ISSUES_ID = 'post-import-source-issues'
@@ -44,6 +64,96 @@ const urlIssuesFromRows = (rows: PostImportRow[], source: string) => {
}
const previewRowFromResult = (
sourceRow: number,
result: PreviewResponse,
): PostImportRow => {
const fieldWarnings = result.fieldWarnings ?? { }
const baseWarnings = result.baseWarnings ?? []
const row: PostImportRow = {
sourceRow,
url: result.url,
attributes: {
title: result.title ?? '',
thumbnailBase: result.thumbnailBase ?? '',
originalCreatedFrom: result.originalCreatedFrom ?? '',
originalCreatedBefore: result.originalCreatedBefore ?? '',
duration: result.duration ?? '',
videoMs: result.videoMs ?? '',
tags: result.tags ?? '',
parentPostIds: '' },
fieldWarnings,
baseWarnings,
validationErrors: { },
provenance: {
url: 'manual',
title: 'automatic',
thumbnailBase: 'automatic',
originalCreatedFrom: 'automatic',
originalCreatedBefore: 'automatic',
duration: 'automatic',
videoMs: 'automatic',
tags: 'automatic',
parentPostIds: 'automatic' },
tagSources: {
automatic: result.tags ?? '',
manual: '' },
status:
Object.keys (fieldWarnings).length > 0 || baseWarnings.length > 0
? 'warning'
: 'ready',
skipReason: result.existingPost?.id != null ? 'existing' : undefined,
existingPostId: result.existingPost?.id,
resetSnapshot: {
url: result.url,
attributes: {
title: result.title ?? '',
thumbnailBase: result.thumbnailBase ?? '',
originalCreatedFrom: result.originalCreatedFrom ?? '',
originalCreatedBefore: result.originalCreatedBefore ?? '',
duration: result.duration ?? '',
videoMs: result.videoMs ?? '',
tags: result.tags ?? '',
parentPostIds: '' },
provenance: {
url: 'manual',
title: 'automatic',
thumbnailBase: 'automatic',
originalCreatedFrom: 'automatic',
originalCreatedBefore: 'automatic',
duration: 'automatic',
videoMs: 'automatic',
tags: 'automatic',
parentPostIds: 'automatic' },
tagSources: {
automatic: result.tags ?? '',
manual: '' },
fieldWarnings,
baseWarnings } }
return row
}
const fetchPreviewRows = async (urls: string[]): Promise<PostImportRow[]> => {
const results: PostImportRow[] = new Array (urls.length)
let nextIndex = 0
const worker = async () => {
while (nextIndex < urls.length)
{
const index = nextIndex
++nextIndex
const result = await apiGet<PreviewResponse> ('/posts/preview', {
params: { url: urls[index] } })
results[index] = previewRowFromResult (index + 1, result)
}
}
await Promise.all (
Array.from ({ length: Math.min (4, urls.length) }, () => worker ()))
return results
}
const PostImportSourcePage: FC<Props> = ({ user }) => {
const editable = canEditContent (user)
const navigate = useNavigate ()
@@ -114,17 +224,30 @@ const PostImportSourcePage: FC<Props> = ({ user }) => {
setSourceError (null)
try
{
const data = await apiPost<{ rows: PostImportRow[] }> ('/posts/import/preview', { source })
const urlIssues = urlIssuesFromRows (data.rows, source)
const urls = source.split (/\r\n|\n|\r/).map (line => line.trim ()).filter (line => line !== '')
const rows = await fetchPreviewRows (urls)
const urlIssues = urlIssuesFromRows (rows, source)
if (urlIssues.length > 0)
{
setSourceIssues (urlIssues)
return
}
const nextRows = initialisePreviewRows (data.rows)
const nextRows = initialisePreviewRows (rows)
const serialised = await serialisePostNewState (nextRows)
if (serialised != null)
navigate (serialised.path)
if (serialised == null)
return
const sessionId = generatePostImportSessionId ()
const session = {
source,
rows: nextRows,
repairMode: resultRepairMode (nextRows) }
savePostImportSession (sessionId, session)
const sessionLoaded = loadPostImportSession (sessionId)
if (!(serialised.complete) && sessionLoaded?.rows.length !== nextRows.length)
return
navigate (appendPostNewSessionId (serialised.path, sessionId))
}
catch (requestError)
{