このコミットが含まれているのは:
@@ -49,6 +49,10 @@ const buildDraft = (row: PostImportRow): Draft => ({
|
||||
tags: String (row.attributes.tags ?? ''),
|
||||
parentPostIds: String (row.attributes.parentPostIds ?? '') })
|
||||
|
||||
const warningClass =
|
||||
'mt-1 rounded-md border border-amber-300 bg-amber-50 px-3 py-2 text-sm '
|
||||
+ 'text-amber-700 dark:border-amber-900 dark:bg-amber-950 dark:text-amber-200'
|
||||
|
||||
|
||||
const PostImportRowDialog: FC<Props> = (
|
||||
{ open, row, saving, onOpenChange, onSave },
|
||||
@@ -102,46 +106,81 @@ const PostImportRowDialog: FC<Props> = (
|
||||
label="URL"
|
||||
value={draft.url}
|
||||
origin={originOf (row, 'url')}
|
||||
warnings={row.fieldWarnings.url}
|
||||
errors={[
|
||||
...(row.validationErrors.url ?? []),
|
||||
...(row.importErrors?.url ?? [])]}
|
||||
onChange={value => update ('url', value)}/>
|
||||
<DialogField
|
||||
label="タイトル"
|
||||
value={draft.title}
|
||||
origin={originOf (row, 'title')}
|
||||
warnings={row.fieldWarnings.title}
|
||||
errors={[
|
||||
...(row.validationErrors.title ?? []),
|
||||
...(row.importErrors?.title ?? [])]}
|
||||
onChange={value => update ('title', value)}/>
|
||||
<DialogField
|
||||
label="サムネール基底 URL"
|
||||
value={draft.thumbnailBase}
|
||||
origin={originOf (row, 'thumbnailBase')}
|
||||
warnings={row.fieldWarnings.thumbnailBase}
|
||||
errors={[
|
||||
...(row.validationErrors.thumbnailBase ?? []),
|
||||
...(row.importErrors?.thumbnailBase ?? [])]}
|
||||
onChange={value => update ('thumbnailBase', value)}/>
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<DialogField
|
||||
label="作成日時 From"
|
||||
value={draft.originalCreatedFrom}
|
||||
origin={originOf (row, 'originalCreatedFrom')}
|
||||
errors={[
|
||||
...(row.validationErrors.originalCreatedFrom ?? []),
|
||||
...(row.importErrors?.originalCreatedFrom ?? [])]}
|
||||
onChange={value => update ('originalCreatedFrom', value)}/>
|
||||
<DialogField
|
||||
label="作成日時 Before"
|
||||
value={draft.originalCreatedBefore}
|
||||
origin={originOf (row, 'originalCreatedBefore')}
|
||||
errors={[
|
||||
...(row.validationErrors.originalCreatedBefore ?? []),
|
||||
...(row.importErrors?.originalCreatedBefore ?? [])]}
|
||||
onChange={value => update ('originalCreatedBefore', value)}/>
|
||||
</div>
|
||||
<DialogField
|
||||
label="動画時間"
|
||||
value={draft.duration}
|
||||
origin={originOf (row, 'duration')}
|
||||
errors={[
|
||||
...(row.validationErrors.duration ?? []),
|
||||
...(row.validationErrors.videoMs ?? []),
|
||||
...(row.importErrors?.duration ?? []),
|
||||
...(row.importErrors?.videoMs ?? [])]}
|
||||
onChange={value => update ('duration', value)}/>
|
||||
<DialogArea
|
||||
label="タグ"
|
||||
value={draft.tags}
|
||||
origin={originOf (row, 'tags')}
|
||||
warnings={row.fieldWarnings.tags}
|
||||
errors={[
|
||||
...(row.validationErrors.tags ?? []),
|
||||
...(row.importErrors?.tags ?? [])]}
|
||||
onChange={value => update ('tags', value)}/>
|
||||
<DialogField
|
||||
label="親投稿"
|
||||
value={draft.parentPostIds}
|
||||
origin={originOf (row, 'parentPostIds')}
|
||||
errors={[
|
||||
...(row.validationErrors.parentPostIds ?? []),
|
||||
...(row.importErrors?.parentPostIds ?? [])]}
|
||||
onChange={value => update ('parentPostIds', value)}/>
|
||||
<FieldError messages={Object.values (row.validationErrors ?? { }).flat ()}/>
|
||||
<FieldError messages={Object.values (row.importErrors ?? { }).flat ()}/>
|
||||
{row.baseWarnings.length > 0 && (
|
||||
<div className={warningClass}>
|
||||
{row.baseWarnings.map ((message, index) => (
|
||||
<div key={`${ message }-${ index }`}>{message}</div>))}
|
||||
</div>)}
|
||||
<FieldError messages={row.validationErrors.base}/>
|
||||
<FieldError messages={row.importErrors?.base}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -164,10 +203,12 @@ const PostImportRowDialog: FC<Props> = (
|
||||
}
|
||||
|
||||
const DialogField = (
|
||||
{ label, value, origin, onChange }: {
|
||||
{ label, value, origin, warnings, errors, onChange }: {
|
||||
label: string
|
||||
value: string
|
||||
origin: PostImportOrigin
|
||||
warnings?: string[]
|
||||
errors?: string[]
|
||||
onChange: (value: string) => void },
|
||||
) => (
|
||||
<label className="block space-y-1">
|
||||
@@ -179,13 +220,21 @@ const DialogField = (
|
||||
value={value}
|
||||
onChange={ev => onChange (ev.target.value)}
|
||||
className={inputClass (false)}/>
|
||||
{warnings && warnings.length > 0 && (
|
||||
<div className={warningClass}>
|
||||
{warnings.map ((message, index) => (
|
||||
<div key={`${ message }-${ index }`}>{message}</div>))}
|
||||
</div>)}
|
||||
<FieldError messages={errors}/>
|
||||
</label>)
|
||||
|
||||
const DialogArea = (
|
||||
{ label, value, origin, onChange }: {
|
||||
{ label, value, origin, warnings, errors, onChange }: {
|
||||
label: string
|
||||
value: string
|
||||
origin: PostImportOrigin
|
||||
warnings?: string[]
|
||||
errors?: string[]
|
||||
onChange: (value: string) => void },
|
||||
) => (
|
||||
<label className="block space-y-1">
|
||||
@@ -198,6 +247,12 @@ const DialogArea = (
|
||||
rows={4}
|
||||
onChange={ev => onChange (ev.target.value)}
|
||||
className={inputClass (false)}/>
|
||||
{warnings && warnings.length > 0 && (
|
||||
<div className={warningClass}>
|
||||
{warnings.map ((message, index) => (
|
||||
<div key={`${ message }-${ index }`}>{message}</div>))}
|
||||
</div>)}
|
||||
<FieldError messages={errors}/>
|
||||
</label>)
|
||||
|
||||
export default PostImportRowDialog
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
import PrefetchLink from '@/components/PrefetchLink'
|
||||
import PostImportStatusBadge from '@/components/posts/import/PostImportStatusBadge'
|
||||
import ThumbnailPreview from '@/components/posts/import/ThumbnailPreview'
|
||||
|
||||
@@ -48,6 +49,11 @@ const summaryError = (row: PostImportRow): string | null => {
|
||||
return Object.values (row.importErrors ?? { }).flat ()[0] ?? null
|
||||
}
|
||||
|
||||
const summaryWarning = (row: PostImportRow): string | null =>
|
||||
Object.values (row.fieldWarnings ?? { }).flat ()[0]
|
||||
?? row.baseWarnings?.[0]
|
||||
?? null
|
||||
|
||||
const summaryDate = (row: PostImportRow): string =>
|
||||
[row.attributes.originalCreatedFrom, row.attributes.originalCreatedBefore]
|
||||
.filter (_1 => typeof _1 === 'string' && _1 !== '')
|
||||
@@ -56,6 +62,7 @@ const summaryDate = (row: PostImportRow): string =>
|
||||
|
||||
const PostImportRowSummary: FC<Props> = ({ row, onEdit }) => {
|
||||
const error = summaryError (row)
|
||||
const warning = error == null ? summaryWarning (row) : null
|
||||
const importStatus = row.importStatus ?? 'pending'
|
||||
|
||||
return (
|
||||
@@ -89,6 +96,21 @@ const PostImportRowSummary: FC<Props> = ({ row, onEdit }) => {
|
||||
{summaryDate (row) || '日時未取得'}
|
||||
{row.attributes.duration ? ` / ${ row.attributes.duration }` : ''}
|
||||
</div>
|
||||
{row.createdPostId && (
|
||||
<PrefetchLink
|
||||
to={`/posts/${ row.createdPostId }`}
|
||||
className="inline-block text-xs text-sky-700 underline
|
||||
dark:text-sky-300">
|
||||
投稿 #{row.createdPostId}
|
||||
</PrefetchLink>)}
|
||||
{warning && (
|
||||
<div className="text-xs text-amber-700 dark:text-amber-200">
|
||||
{warning}
|
||||
</div>)}
|
||||
{error && (
|
||||
<div className="text-xs text-red-700 dark:text-red-200">
|
||||
{error}
|
||||
</div>)}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<PostImportStatusBadge value={importStatus}/>
|
||||
@@ -96,7 +118,7 @@ const PostImportRowSummary: FC<Props> = ({ row, onEdit }) => {
|
||||
<div className="space-y-1">
|
||||
{error
|
||||
? <PostImportStatusBadge value="error"/>
|
||||
: <PostImportStatusBadge value="ready"/>}
|
||||
: (warning ? <PostImportStatusBadge value="warning"/> : <PostImportStatusBadge value="ready"/>)}
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
@@ -128,11 +150,23 @@ const PostImportRowSummary: FC<Props> = ({ row, onEdit }) => {
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<PostImportStatusBadge value={row.status}/>
|
||||
<PostImportStatusBadge value={importStatus}/>
|
||||
{warning && <PostImportStatusBadge value="warning"/>}
|
||||
</div>
|
||||
{row.createdPostId && (
|
||||
<PrefetchLink
|
||||
to={`/posts/${ row.createdPostId }`}
|
||||
className="inline-block text-xs text-sky-700 underline
|
||||
dark:text-sky-300">
|
||||
投稿 #{row.createdPostId}
|
||||
</PrefetchLink>)}
|
||||
{error && (
|
||||
<div className="text-xs text-red-700 dark:text-red-200">
|
||||
{error}
|
||||
</div>)}
|
||||
{warning && (
|
||||
<div className="text-xs text-amber-700 dark:text-amber-200">
|
||||
{warning}
|
||||
</div>)}
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
|
||||
@@ -31,9 +31,10 @@ const ThumbnailPreview: FC<Props> = ({ url, alt = 'サムネール', className =
|
||||
return (
|
||||
<div
|
||||
className={`${ className } flex items-center justify-center rounded border
|
||||
border-red-300 bg-red-50 p-2 text-center text-xs text-red-700
|
||||
dark:border-red-900 dark:bg-red-950 dark:text-red-200`}>
|
||||
表示不可
|
||||
border-amber-300 bg-amber-50 p-2 text-center text-xs
|
||||
text-amber-700 dark:border-amber-900 dark:bg-amber-950
|
||||
dark:text-amber-200`}>
|
||||
サムネールを表示できません
|
||||
</div>)
|
||||
}
|
||||
|
||||
|
||||
新しい課題から参照
ユーザをブロックする