このコミットが含まれているのは:
2026-07-15 20:25:51 +09:00
コミット d2f1b1ed48
13個のファイルの変更85行の追加196行の削除
+4 -19
ファイルの表示
@@ -9,8 +9,6 @@ import PrefetchLink from '@/components/PrefetchLink'
import MainArea from '@/components/layout/MainArea'
import PostImportStatusBadge from '@/components/posts/import/PostImportStatusBadge'
import { displayPostImportStatus } from '@/components/posts/import/postImportRowStatus'
import { importPanelToneClass } from '@/components/posts/import/postImportTone'
import PostImportSummaryChip from '@/components/posts/import/PostImportSummaryChip'
import { Button } from '@/components/ui/button'
import { toast } from '@/components/ui/use-toast'
import { SITE_TITLE } from '@/config'
@@ -71,7 +69,7 @@ const PostImportResultPage: FC<Props> = ({ user }) => {
[session])
const retry = async (sourceRow: number) => {
if (session == null)
if (session == null || sessionId == null)
return
setLoadingRow (sourceRow)
@@ -177,13 +175,8 @@ const PostImportResultPage: FC<Props> = ({ user }) => {
<div className="mx-auto max-w-5xl space-y-4 p-4">
<PageTitle></PageTitle>
<div className="rounded-lg border bg-white p-4 dark:border-neutral-700
dark:bg-neutral-900">
<div className="flex flex-wrap gap-2">
<PostImportSummaryChip label="登録成功" value={counts.created}/>
<PostImportSummaryChip label="スキップ" value={counts.skipped} badge="skipped"/>
<PostImportSummaryChip label="失敗" value={counts.failed}/>
</div>
<div className="text-sm text-neutral-700 dark:text-neutral-200">
{counts.created}  {counts.skipped}  {counts.failed}
</div>
<div className="space-y-3">
@@ -193,18 +186,10 @@ const PostImportResultPage: FC<Props> = ({ user }) => {
const canRetry =
row.importStatus === 'failed'
&& Object.keys (row.validationErrors).length === 0
const panelStatus =
row.importStatus === 'created'
? 'created'
: (row.importStatus === 'failed'
? 'failed'
: displayStatus ?? 'ready')
return (
<div
key={row.sourceRow}
className={[
'rounded-lg border p-4',
...importPanelToneClass (panelStatus)].join (' ')}>
className="rounded-lg border p-4 transition-shadow hover:shadow-sm">
<div className="flex flex-col gap-3 md:flex-row md:items-start
md:justify-between">
<div className="space-y-2">
+8 -23
ファイルの表示
@@ -8,8 +8,6 @@ import PageTitle from '@/components/common/PageTitle'
import MainArea from '@/components/layout/MainArea'
import PostImportRowDialog from '@/components/posts/import/PostImportRowDialog'
import PostImportRowSummary from '@/components/posts/import/PostImportRowSummary'
import PostImportStatusBadge from '@/components/posts/import/PostImportStatusBadge'
import PostImportSummaryChip from '@/components/posts/import/PostImportSummaryChip'
import { Button } from '@/components/ui/button'
import { toast } from '@/components/ui/use-toast'
import { SITE_TITLE } from '@/config'
@@ -130,8 +128,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
if (editingRow == null)
return false
const urlChanged = draft.url !== editingRow.url
const nextRow =
const baseRow =
resetRequested
? {
...editingRow,
@@ -139,10 +136,10 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
attributes: { ...resetSnapshot.attributes },
provenance: { ...resetSnapshot.provenance },
tagSources: { ...resetSnapshot.tagSources },
metadataUrl: resetSnapshot.metadataUrl,
importStatus: editingRow.importStatus === 'created' ? 'created' : 'pending',
importErrors: undefined }
: buildNextEditedRow (editingRow, draft, urlChanged)
metadataUrl: resetSnapshot.metadataUrl }
: editingRow
const urlChanged = draft.url !== baseRow.url
const nextRow = buildNextEditedRow (baseRow, draft, urlChanged)
setSavingRow (editingRow.sourceRow)
const nextRows = session.rows.map (row =>
@@ -272,16 +269,6 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
<div className="mx-auto max-w-6xl space-y-4 p-4">
<PageTitle>稿</PageTitle>
<div className="rounded-lg border bg-white p-4 dark:border-neutral-700
dark:bg-neutral-900">
<div className="flex flex-wrap items-center gap-2">
<PostImportSummaryChip label="全件" value={counts.total}/>
<PostImportSummaryChip label="登録対象" value={counts.submittable} badge="ready"/>
<PostImportSummaryChip label="スキップ予定" value={counts.skipPlanned}
badge="skipped"/>
</div>
</div>
<div className="space-y-3">
{reviewRows.map (row => (
<div key={row.sourceRow} id={`post-import-row-${ row.sourceRow }`}>
@@ -336,11 +323,9 @@ const PostImportFooter = (
<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> {processableCount} </span>
<PostImportStatusBadge value="ready"/>
<span> {creatableCount} </span>
<PostImportStatusBadge value="skipped"/>
<span> {skipPlannedCount} </span>
<span> {processableCount}</span>
<span> {creatableCount}</span>
<span> {skipPlannedCount}</span>
</div>
<div className="flex flex-col gap-2 sm:flex-row">
<Button type="button" variant="outline" onClick={onBack}>
+9 -7
ファイルの表示
@@ -34,12 +34,15 @@ const MAX_ROWS = 100
const SOURCE_ERROR_ID = 'post-import-source-error'
const SOURCE_ISSUES_ID = 'post-import-source-issues'
const urlIssuesFromRows = (rows: PostImportRow[]) =>
rows.flatMap (row =>
const urlIssuesFromRows = (rows: PostImportRow[], source: string) => {
const sourceLines = source.split (/\r\n|\n|\r/)
return rows.flatMap (row =>
(row.validationErrors.url ?? []).map (message => ({
sourceRow: row.sourceRow,
message,
url: row.url })))
url: sourceLines[row.sourceRow - 1]?.trim () ?? row.url })))
}
const PostImportSourcePage: FC<Props> = ({ user }) => {
@@ -108,7 +111,7 @@ const PostImportSourcePage: FC<Props> = ({ user }) => {
{
const data = await apiPost<{ rows: PostImportRow[] }> ('/posts/import/preview', {
source })
const urlIssues = urlIssuesFromRows (data.rows)
const urlIssues = urlIssuesFromRows (data.rows, source)
if (urlIssues.length > 0)
{
setSourceIssues (urlIssues)
@@ -151,7 +154,7 @@ const PostImportSourcePage: FC<Props> = ({ user }) => {
<title>{`投稿インポート | ${ SITE_TITLE }`}</title>
</Helmet>
<Form className="max-w-4xl p-0">
<Form className="max-w-4xl">
<PageTitle>稿</PageTitle>
<FormField label="URL リスト">
{() => (
@@ -162,8 +165,7 @@ const PostImportSourcePage: FC<Props> = ({ user }) => {
aria-describedby={sourceDescribedBy || undefined}
className={inputClass (
messages.length > 0 || sourceIssues.length > 0,
'font-mono text-sm',
)}
'font-mono text-sm')}
onBlur={() => {
savePostImportSourceDraft (source, message =>
toast ({
+7 -12
ファイルの表示
@@ -25,13 +25,8 @@ import type { User } from '@/types'
type Props = { user: User | null }
type PostFormField =
'url'
| 'title'
| 'tags'
| 'parentPostIds'
| 'videoMs'
| 'originalCreatedAt'
| 'thumbnail'
'url' | 'title' | 'tags' | 'parentPostIds'
| 'videoMs' | 'originalCreatedAt' | 'thumbnail'
const PostNewPage: FC<Props> = ({ user }) => {
@@ -42,8 +37,10 @@ const PostNewPage: FC<Props> = ({ user }) => {
const { baseErrors, fieldErrors, clearValidationErrors, applyValidationError } =
useValidationErrors<PostFormField> ()
const [originalCreatedBefore, setOriginalCreatedBefore] = useState<string | null> (null)
const [originalCreatedFrom, setOriginalCreatedFrom] = useState<string | null> (null)
const [originalCreatedBefore, setOriginalCreatedBefore] =
useState<string | null> (null)
const [originalCreatedFrom, setOriginalCreatedFrom] =
useState<string | null> (null)
const [parentPostIds, setParentPostIds] = useState ('')
const [tags, setTags] = useState ('')
const [duration, setDuration] = useState ('')
@@ -56,9 +53,7 @@ const PostNewPage: FC<Props> = ({ user }) => {
const thumbnailPreviewRef = useRef ('')
const videoFlg =
useMemo (() =>
tags.split (/\s+/).some (
tag => tag.replace (/\[.*\]$/, '') === '動画'),
useMemo (() => tags.split (/\s+/).some (tag => tag.replace (/\[.*\]$/, '') === '動画'),
[tags])
const handleSubmit = async () => {