Reviewed-on: #433 Co-authored-by: miteruzo <miteruzo@naver.com>
このコミットはプルリクエスト #433 でマージされました。
このコミットが含まれているのは:
@@ -1,19 +1,22 @@
|
||||
import { AnimatePresence, motion } from 'framer-motion'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Helmet } from 'react-helmet-async'
|
||||
import { ChevronRight } from 'lucide-react'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
import { useLocale, useLocaleNavigate } from '@/lib/useLocale'
|
||||
import PageTitle from '@/components/common/PageTitle'
|
||||
import MainArea from '@/components/layout/MainArea'
|
||||
import PrefetchLink from '@/components/PrefetchLink'
|
||||
import PostThumbnailPreview from '@/components/posts/PostThumbnailPreview'
|
||||
import PostImportRowForm from '@/components/posts/import/PostImportRowForm'
|
||||
import PostImportRowSummary from '@/components/posts/import/PostImportRowSummary'
|
||||
import { renderPostImportMessage } from '@/components/posts/import/postImportRowStatus'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { toast } from '@/components/ui/use-toast'
|
||||
import { SITE_TITLE } from '@/config'
|
||||
import PageHead from '@/components/common/PageHead'
|
||||
import { apiGet, apiPost, isApiError } from '@/lib/api'
|
||||
import { translateUIMessage } from '@/lib/i18n'
|
||||
import { useUITranslation } from '@/lib/useUITranslation'
|
||||
import useDialogue from '@/lib/dialogues/useDialogue'
|
||||
import { parsePostNewReviewUrls } from '@/lib/postNewQueryState'
|
||||
import { validateImportSource } from '@/lib/postImportSourceValidation'
|
||||
@@ -99,8 +102,9 @@ type ExistingSkippedRowsProps = {
|
||||
id: string
|
||||
rows: PostImportRow[] }
|
||||
|
||||
const DUPLICATE_URL_MESSAGE = 'URL が重複しています.'
|
||||
const BULK_PROCESSING_FAILED_MESSAGE = '登録中にエラーが発生しました.'
|
||||
const DUPLICATE_URL_MESSAGE = translateUIMessage ('validation.duplicateUrl', 'imports', 'ja').text
|
||||
const BULK_PROCESSING_FAILED_MESSAGE =
|
||||
translateUIMessage ('validation.processingFailed', 'imports', 'ja').text
|
||||
|
||||
const emptyAttributes = () => ({
|
||||
title: '',
|
||||
@@ -595,10 +599,12 @@ const editableRowDirty = (row: PostImportRow): boolean =>
|
||||
|
||||
|
||||
const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
const locale = useLocale ()
|
||||
const { message, label } = useUITranslation ('imports')
|
||||
const editable = canEditContent (user)
|
||||
const dialogue = useDialogue ()
|
||||
const location = useLocation ()
|
||||
const navigate = useNavigate ()
|
||||
const navigate = useLocaleNavigate ()
|
||||
const behaviourSettings = useClientBehaviourSettings ()
|
||||
const animationMode = behaviourSettings.animation ?? 'normal'
|
||||
const existingRowsTransition =
|
||||
@@ -608,7 +614,8 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
? { duration: .08, ease: 'linear' as const }
|
||||
: { duration: .2, ease: 'easeOut' as const }
|
||||
|
||||
const [rows, setRows] = useState<PostImportRow[] | null> (null)
|
||||
const [rows, setRows] = useState<PostImportRow[] | null> (
|
||||
() => buildInitialRows (parsePostNewReviewUrls (location.search)))
|
||||
const [metadataLoading, setMetadataLoading] = useState (false)
|
||||
const [submitting, setSubmitting] = useState (false)
|
||||
const [busyRowIds, setBusyRowIds] = useState<Set<number>> (new Set ())
|
||||
@@ -649,9 +656,10 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
const finishImport = useCallback (() => {
|
||||
allowNextNavigation ()
|
||||
clearPostImportSourceDraft (message =>
|
||||
toast ({ title: '入力内容を削除できませんでした', description: message }))
|
||||
toast ({ title: label ('review.clearFailed'),
|
||||
description: renderPostImportMessage (message, locale) }))
|
||||
navigate ('/posts')
|
||||
}, [allowNextNavigation, navigate])
|
||||
}, [allowNextNavigation, navigate, label, locale])
|
||||
|
||||
useEffect (() => {
|
||||
const urls = parsePostNewReviewUrls (location.search)
|
||||
@@ -696,7 +704,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
|
||||
try
|
||||
{
|
||||
const preview = await apiGet<PostMetadataResponse> ('/posts/metadata', {
|
||||
const preview = await apiGet<PostMetadataResponse> ('/posts/metadata', { locale,
|
||||
params: { url: currentRow.url },
|
||||
signal: controller.signal })
|
||||
if (!(active) || metadataSequenceRef.current !== sequence)
|
||||
@@ -770,7 +778,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
active = false
|
||||
controllers.forEach (controller => controller.abort ())
|
||||
}
|
||||
}, [commitRows, currentRowBySource, location.search, updateRows])
|
||||
}, [commitRows, currentRowBySource, location.search, updateRows, locale])
|
||||
|
||||
useEffect (() => {
|
||||
if (editingRow == null || resultRepairMode (rowsRef.current) !== 'failed')
|
||||
@@ -871,12 +879,12 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
urlChanged
|
||||
? mergePreviewRow (
|
||||
nextRow,
|
||||
await apiGet<PostMetadataResponse> ('/posts/metadata', {
|
||||
await apiGet<PostMetadataResponse> ('/posts/metadata', { locale,
|
||||
params: { url: nextRow.url } }))
|
||||
: nextRow
|
||||
const dryRun = await apiPost<PostMetadataResponse> (
|
||||
'/posts?dry=1',
|
||||
buildDryRunFormData (candidateRow))
|
||||
buildDryRunFormData (candidateRow), { locale })
|
||||
const mergedRow = mergeDryRunRow (candidateRow, dryRun)
|
||||
const mergedRows = updateRows (currentRows =>
|
||||
replaceImportRow (currentRows, mergedRow))
|
||||
@@ -910,7 +918,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
return { saved: false, row: applyThumbnailWarning (errorRow) }
|
||||
}
|
||||
|
||||
toast ({ title: '行の再検証に失敗しました' })
|
||||
toast ({ title: label ('review.validateFailed') })
|
||||
return { saved: false, row: null }
|
||||
}
|
||||
}
|
||||
@@ -924,8 +932,8 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
saveDraft (row, { draft, resetRequested })
|
||||
|
||||
await dialogue.form ({
|
||||
title: '投稿を編輯',
|
||||
cancelText: '取消',
|
||||
title: label ('review.editPost'),
|
||||
cancelText: label ('shared:actions.cancel'),
|
||||
size: 'large',
|
||||
body: controls => (
|
||||
<PostImportRowForm
|
||||
@@ -974,11 +982,11 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
|
||||
const result = await apiPost<{ results: BulkApiRow[] }>(
|
||||
'/posts/bulk',
|
||||
buildBulkFormData ([target]))
|
||||
buildBulkFormData ([target]), { locale })
|
||||
if (result.results.length !== 1)
|
||||
{
|
||||
updateRows (currentRows => replaceImportRow (currentRows, originalRow))
|
||||
toast ({ title: '登録結果が不完全でした' })
|
||||
toast ({ title: label ('review.incomplete') })
|
||||
return
|
||||
}
|
||||
|
||||
@@ -993,7 +1001,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
catch
|
||||
{
|
||||
updateRows (currentRows => replaceImportRow (currentRows, originalRow))
|
||||
toast ({ title: '再試行に失敗しました' })
|
||||
toast ({ title: label ('review.retryFailed') })
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -1018,10 +1026,10 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
{
|
||||
const result = await apiPost<{ results: BulkApiRow[] }>(
|
||||
'/posts/bulk',
|
||||
buildBulkFormData (latestProcessingRows))
|
||||
buildBulkFormData (latestProcessingRows), { locale })
|
||||
if (result.results.length !== latestProcessingRows.length)
|
||||
{
|
||||
toast ({ title: '登録結果が不完全でした' })
|
||||
toast ({ title: label ('review.incomplete') })
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1035,7 +1043,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
}
|
||||
catch
|
||||
{
|
||||
toast ({ title: '登録に失敗しました' })
|
||||
toast ({ title: label ('review.submitFailed') })
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -1051,13 +1059,11 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{`追加内容確認 | ${ SITE_TITLE }`}</title>
|
||||
</Helmet>
|
||||
<PageHead title={message ('review.title')}/>
|
||||
|
||||
<MainArea className="pb-40">
|
||||
<div className="mx-auto max-w-6xl space-y-6">
|
||||
<PageTitle>追加内容確認</PageTitle>
|
||||
<PageTitle>{label ('review.title')}</PageTitle>
|
||||
|
||||
{existingRows.length > 0 && (
|
||||
<div className="rounded-lg border p-4">
|
||||
@@ -1076,7 +1082,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
? ''
|
||||
: 'transition-transform')}/>
|
||||
</span>
|
||||
既存投稿による自動スキップ {counts.existingSkipped}件
|
||||
{label ('review.existingSkipped', { count: counts.existingSkipped })}
|
||||
</button>
|
||||
<AnimatePresence initial={false}>
|
||||
{showExistingRows && (
|
||||
@@ -1154,36 +1160,39 @@ const PostImportFooter = (
|
||||
pendingOrErrorCount: number
|
||||
onBack: () => void
|
||||
onSubmit: () => void },
|
||||
) => (
|
||||
<div
|
||||
className="shrink-0 border-t bg-white/95 p-4 backdrop-blur
|
||||
dark:border-neutral-700 dark:bg-neutral-950/95">
|
||||
<div className="mx-auto flex max-w-6xl flex-col gap-3 md:flex-row
|
||||
md:items-center md:justify-between">
|
||||
<div className="flex flex-wrap items-center gap-2 text-sm">
|
||||
<span>作成対象 {creatableCount}件</span>
|
||||
<span>手動スキップ {manualSkippedCount}件</span>
|
||||
<span>既存投稿による自動スキップ {existingSkippedCount}件</span>
|
||||
<span>未処理 {pendingOrErrorCount}件</span>
|
||||
) => {
|
||||
const { label } = useUITranslation ('imports')
|
||||
return (
|
||||
<div
|
||||
className="shrink-0 border-t bg-white/95 p-4 backdrop-blur
|
||||
dark:border-neutral-700 dark:bg-neutral-950/95">
|
||||
<div className="mx-auto flex max-w-6xl flex-col gap-3 md:flex-row
|
||||
md:items-center md:justify-between">
|
||||
<div className="flex flex-wrap items-center gap-2 text-sm">
|
||||
<span>{label ('review.creatable', { count: creatableCount })}</span>
|
||||
<span>{label ('review.manualSkipped', { count: manualSkippedCount })}</span>
|
||||
<span>{label ('review.existingSkipped', { count: existingSkippedCount })}</span>
|
||||
<span>{label ('review.pending', { count: pendingOrErrorCount })}</span>
|
||||
</div>
|
||||
<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}>
|
||||
{label ('review.back')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
className="w-full md:w-auto"
|
||||
onClick={onSubmit}
|
||||
disabled={metadataLoading || submitting || busyRowsPresent || !(canSubmit)}>
|
||||
{creatableCount > 1 ? label ('review.addAll') : label ('shared:actions.add')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<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}>
|
||||
戻る
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
className="w-full md:w-auto"
|
||||
onClick={onSubmit}
|
||||
disabled={metadataLoading || submitting || busyRowsPresent || !(canSubmit)}>
|
||||
{creatableCount > 1 ? '一括追加' : '追加'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>)
|
||||
</div>)
|
||||
}
|
||||
|
||||
export default PostImportReviewPage
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする