このコミットが含まれているのは:
2026-07-18 02:29:55 +09:00
コミット e6c3c635b8
11個のファイルの変更134行の追加54行の削除
+84 -33
ファイルの表示
@@ -76,6 +76,7 @@ type PostMetadataResponse = {
fieldWarnings?: Record<string, string[]>
baseWarnings?: string[]
validationErrors?: Record<string, string[]>
existingPostId?: number
existingPost?: {
id: number
title: string
@@ -85,6 +86,7 @@ type PostMetadataResponse = {
type BulkApiRow = {
status: 'created' | 'skipped' | 'failed'
post?: { id: number }
existingPostId?: number
existingPost?: {
id: number
title: string
@@ -114,20 +116,20 @@ const rowMetadataPending = (row: PostImportRow): boolean =>
const rowOperationBusy = (
row: PostImportRow,
submitting: boolean,
loadingRow: number | null,
busyRowIds: Set<number>,
): boolean =>
submitting
|| loadingRow === row.sourceRow
|| busyRowIds.has (row.sourceRow)
|| rowMetadataPending (row)
const rowSkipBusy = (
row: PostImportRow,
submitting: boolean,
loadingRow: number | null,
busyRowIds: Set<number>,
): boolean =>
submitting
|| loadingRow === row.sourceRow
|| busyRowIds.has (row.sourceRow)
|| (rowMetadataPending (row) && !(isManualSkipRow (row)))
const shouldFetchMetadata = (row: PostImportRow): boolean =>
@@ -242,8 +244,11 @@ const mergeDryRunRow = (
hasWarnings
? 'warning'
: 'ready',
skipReason: result.existingPost?.id != null ? 'existing' : undefined,
existingPostId: result.existingPost?.id,
skipReason:
result.existingPostId != null || result.existingPost?.id != null
? 'existing'
: undefined,
existingPostId: result.existingPostId ?? result.existingPost?.id,
existingPost: result.existingPost ?? undefined,
importStatus:
currentRow.importStatus === 'created'
@@ -306,12 +311,16 @@ const indexedBulkResults = (
baseWarnings: result.baseWarnings,
errors }
}
if (result?.status === 'skipped' && result.existingPost != null)
if (result?.status === 'skipped')
{
return {
sourceRow: row.sourceRow,
status: 'skipped',
existingPostId: result.existingPost.id,
existingPostId:
result.existingPostId
?? result.existingPost?.id
?? row.existingPostId
?? row.sourceRow,
existingPost: result.existingPost,
fieldWarnings: result.fieldWarnings,
baseWarnings: result.baseWarnings,
@@ -350,10 +359,10 @@ const mergePreviewRow = (
fieldWarnings,
baseWarnings,
validationErrors,
existingPostId: preview.existingPost?.id,
existingPostId: preview.existingPostId ?? preview.existingPost?.id,
existingPost: preview.existingPost ?? undefined,
skipReason:
preview.existingPost?.id != null
preview.existingPostId != null || preview.existingPost?.id != null
? 'existing'
: currentRow.skipReason === 'manual'
? 'manual'
@@ -468,7 +477,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
const [session, setSession] = useState<PostImportSession | null> (null)
const [metadataLoading, setMetadataLoading] = useState (false)
const [submitting, setSubmitting] = useState (false)
const [loadingRow, setLoadingRow] = useState<number | null> (null)
const [busyRowIds, setBusyRowIds] = useState<Set<number>> (new Set ())
const [editingRow, setEditingRow] = useState<PostImportRow | null> (null)
const [showExistingRows, setShowExistingRows] = useState (false)
const sessionRef = useRef<PostImportSession | null> (null)
@@ -476,18 +485,31 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
const persistedSearchRef = useRef<string | null> (null)
const persistSequenceRef = useRef (0)
const previewSequenceRef = useRef (0)
const metadataSequenceRef = useRef (0)
const commitSessionState = (
nextSession: PostImportSession,
): PostImportSession => {
): {
session: PostImportSession
sessionRoundTripSucceeded: boolean } => {
const sessionId =
sessionIdRef.current ?? generatePostImportSessionId ()
sessionIdRef.current = sessionId
const persistedSession = buildSession (nextSession.rows)
savePostImportSession (sessionId, persistedSession)
const saved = savePostImportSession (sessionId, persistedSession, showStorageError)
const loadedSession =
saved
? loadPostImportSession (sessionId, showStorageError)
: null
sessionRef.current = persistedSession
setSession (persistedSession)
return persistedSession
return {
session: persistedSession,
sessionRoundTripSucceeded:
saved
&& serialisedPostImportRowsEqual (
loadedSession?.rows ?? [],
persistedSession.rows) }
}
const syncSessionUrl = async (
@@ -503,7 +525,16 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
if (persistSequenceRef.current !== sequence)
return null
const persistedSession = commitSessionState (nextSession)
const { session: persistedSession,
sessionRoundTripSucceeded } = commitSessionState (nextSession)
const canNavigate =
serialised.complete
|| sessionRoundTripSucceeded
if (!(canNavigate))
{
showStorageError ('ブラウザへ保存できませんでした.')
return null
}
const path = appendPostNewSessionId (serialised.path, sessionId)
persistedSearchRef.current = (new URL (path, window.location.origin)).search
@@ -530,6 +561,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
useEffect (() => {
let active = true
const previewSequence = ++previewSequenceRef.current
metadataSequenceRef.current = previewSequence
const controllers = new Set<AbortController> ()
if (sessionRef.current != null
@@ -637,7 +669,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
}
finally
{
if (active && previewSequenceRef.current === previewSequence)
if (active && metadataSequenceRef.current === previewSequence)
setMetadataLoading (false)
}
}
@@ -651,6 +683,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
return
if (parsed == null && loadedSession == null)
{
setMetadataLoading (false)
navigate ('/posts/new', { replace: true })
return
}
@@ -709,7 +742,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
const currentRow = currentRowBySource (sourceRow, currentSession)
if (currentRow == null)
return
if (rowSkipBusy (currentRow, submitting, loadingRow))
if (rowSkipBusy (currentRow, submitting, busyRowIds))
return
const nextRows = currentSession.rows.map (row => {
@@ -720,9 +753,12 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
return row
return {
...row,
skipReason: checked ? 'manual' : undefined,
existingPostId: checked ? undefined : row.existingPostId,
existingPost: checked ? undefined : row.existingPost }
skipReason:
checked
? 'manual'
: row.existingPostId != null
? 'existing'
: undefined }
})
const nextSession = buildSession (applyDuplicateUrlErrors (nextRows))
if (metadataLoading)
@@ -787,7 +823,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
replaceImportRow (latestSession.rows, mergedRow))
const nextSession =
metadataLoading
? commitSessionState (buildSession (mergedRows))
? commitSessionState (buildSession (mergedRows)).session
: await syncSessionUrl (buildSession (mergedRows))
if (nextSession == null)
return { saved: false, row: null }
@@ -852,7 +888,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
return
if (!(canEditReviewRow (currentRow)))
return
if (rowOperationBusy (currentRow, submitting, loadingRow))
if (rowOperationBusy (currentRow, submitting, busyRowIds))
return
setEditingRow (currentRow)
@@ -877,16 +913,16 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
const originalRow = initialSession.rows.find (row => row.sourceRow === sourceRow)
if (originalRow == null)
return
if (rowOperationBusy (originalRow, submitting, loadingRow))
if (rowOperationBusy (originalRow, submitting, busyRowIds))
return
setLoadingRow (sourceRow)
setBusyRowIds (current => new Set ([...current, sourceRow]))
try
{
const pendingRows = retryImportRow (initialSession.rows, sourceRow)
const pendingSession =
metadataLoading
? commitSessionState (buildSession (pendingRows))
? commitSessionState (buildSession (pendingRows)).session
: await syncSessionUrl (buildSession (pendingRows))
if (pendingSession == null)
return
@@ -915,7 +951,10 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
const nextRows = applyDuplicateUrlErrors (
mergeImportResults (latestAfterImport.rows, indexedResults))
.map (row => applyThumbnailWarning (row))
const persisted = await syncSessionUrl (buildSession (nextRows))
const persisted =
metadataLoading
? commitSessionState (buildSession (nextRows)).session
: await syncSessionUrl (buildSession (nextRows))
if (persisted != null
&& persisted.rows.every (row => isCompletedReviewRow (row)))
finishImport ()
@@ -932,13 +971,20 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
}
finally
{
setLoadingRow (null)
setBusyRowIds (current => {
const next = new Set (current)
next.delete (sourceRow)
return next
})
}
}
const submit = async () => {
const currentSession = sessionRef.current
if (currentSession == null || metadataLoading || submitting || loadingRow != null)
if (currentSession == null
|| metadataLoading
|| submitting
|| busyRowIds.size > 0)
return
if (currentSession.rows.every (row => isCompletedReviewRow (row)))
{
@@ -1043,10 +1089,10 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
row={row}
displayNumber={index + 1}
showSkipToggle={true}
editDisabled={rowOperationBusy (row, submitting, loadingRow)}
retryDisabled={rowOperationBusy (row, submitting, loadingRow)}
editDisabled={rowOperationBusy (row, submitting, busyRowIds)}
retryDisabled={rowOperationBusy (row, submitting, busyRowIds)}
skipDisabled={
rowSkipBusy (row, submitting, loadingRow)
rowSkipBusy (row, submitting, busyRowIds)
|| isExistingSkipRow (row)
|| row.importStatus === 'created'}
onEdit={() => editRow (row)}
@@ -1061,6 +1107,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
<PostImportFooter
metadataLoading={metadataLoading}
submitting={submitting}
busyRowsPresent={busyRowIds.size > 0}
canSubmit={canSubmit}
creatableCount={counts.creatable}
manualSkippedCount={counts.manualSkipped}
@@ -1074,6 +1121,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
const PostImportFooter = (
{ metadataLoading,
submitting,
busyRowsPresent,
canSubmit,
creatableCount,
manualSkippedCount,
@@ -1082,6 +1130,7 @@ const PostImportFooter = (
onBack,
onSubmit }: { metadataLoading: boolean
submitting: boolean
busyRowsPresent: boolean
canSubmit: boolean
creatableCount: number
manualSkippedCount: number
@@ -1101,18 +1150,20 @@ const PostImportFooter = (
<span>稿 {existingSkippedCount}</span>
<span> {pendingOrErrorCount}</span>
</div>
<div className="flex flex-col gap-2 sm:flex-row">
<div className="flex flex-col gap-2 md:flex-row">
<Button
type="button"
variant="outline"
className="w-full md:w-auto"
onClick={onBack}
disabled={submitting}>
URL
</Button>
<Button
type="button"
className="w-full md:w-auto"
onClick={onSubmit}
disabled={metadataLoading || submitting || !(canSubmit)}>
disabled={metadataLoading || submitting || busyRowsPresent || !(canSubmit)}>
{creatableCount > 1 ? '一括追加' : '追加'}
</Button>
</div>