このコミットが含まれているのは:
@@ -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>
|
||||
|
||||
新しい課題から参照
ユーザをブロックする