このコミットが含まれているのは:
@@ -12,26 +12,23 @@ import { Button } from '@/components/ui/button'
|
||||
import { toast } from '@/components/ui/use-toast'
|
||||
import { SITE_TITLE } from '@/config'
|
||||
import {
|
||||
appendPostNewSessionId,
|
||||
serialisePostNewState,
|
||||
buildPostNewReviewPath,
|
||||
isPostNewReviewPathWithinLimit,
|
||||
} from '@/lib/postNewQueryState'
|
||||
import {
|
||||
countImportSourceLines,
|
||||
extractImportSourceUrls,
|
||||
validateImportSource,
|
||||
} from '@/lib/postImportSourceValidation'
|
||||
import {
|
||||
loadPostImportSourceDraft,
|
||||
savePostImportSourceDraft,
|
||||
} from '@/lib/postImportStorage'
|
||||
import { canEditContent } from '@/lib/users'
|
||||
import { countImportSourceLines,
|
||||
generatePostImportSessionId,
|
||||
cleanupExpiredPostImportSessions,
|
||||
initialisePreviewRows,
|
||||
loadPostImportSession,
|
||||
loadPostImportSourceDraft,
|
||||
resultRepairMode,
|
||||
serialisedPostImportRowsEqual,
|
||||
savePostImportSession,
|
||||
savePostImportSourceDraft,
|
||||
validateImportSource } from '@/lib/postImportSession'
|
||||
import Forbidden from '@/pages/Forbidden'
|
||||
|
||||
import type { FC } from 'react'
|
||||
|
||||
import type { PostImportRow } from '@/lib/postImportSession'
|
||||
import type { User } from '@/types'
|
||||
|
||||
type Props = { user: User | null }
|
||||
@@ -40,71 +37,6 @@ const MAX_ROWS = 100
|
||||
const SOURCE_ERROR_ID = 'post-import-source-error'
|
||||
const SOURCE_ISSUES_ID = 'post-import-source-issues'
|
||||
|
||||
const buildInitialRows = (source: string): PostImportRow[] =>
|
||||
source
|
||||
.split (/\r\n|\n|\r/)
|
||||
.map ((line, index) => ({
|
||||
sourceRow: index + 1,
|
||||
url: line.trim () }))
|
||||
.filter (row => row.url !== '')
|
||||
.map (row => ({
|
||||
sourceRow: row.sourceRow,
|
||||
url: row.url,
|
||||
attributes: {
|
||||
title: '',
|
||||
thumbnailBase: '',
|
||||
originalCreatedFrom: '',
|
||||
originalCreatedBefore: '',
|
||||
duration: '',
|
||||
videoMs: '',
|
||||
tags: '',
|
||||
parentPostIds: '' },
|
||||
fieldWarnings: { },
|
||||
baseWarnings: [],
|
||||
validationErrors: { },
|
||||
provenance: {
|
||||
url: 'manual',
|
||||
title: 'automatic',
|
||||
thumbnailBase: 'automatic',
|
||||
originalCreatedFrom: 'automatic',
|
||||
originalCreatedBefore: 'automatic',
|
||||
duration: 'automatic',
|
||||
videoMs: 'automatic',
|
||||
tags: 'automatic',
|
||||
parentPostIds: 'automatic' },
|
||||
tagSources: {
|
||||
automatic: '',
|
||||
manual: '' },
|
||||
status: 'pending' as const,
|
||||
displayTags: [],
|
||||
resetSnapshot: {
|
||||
url: row.url,
|
||||
attributes: {
|
||||
title: '',
|
||||
thumbnailBase: '',
|
||||
originalCreatedFrom: '',
|
||||
originalCreatedBefore: '',
|
||||
duration: '',
|
||||
videoMs: '',
|
||||
tags: '',
|
||||
parentPostIds: '' },
|
||||
provenance: {
|
||||
url: 'manual',
|
||||
title: 'automatic',
|
||||
thumbnailBase: 'automatic',
|
||||
originalCreatedFrom: 'automatic',
|
||||
originalCreatedBefore: 'automatic',
|
||||
duration: 'automatic',
|
||||
videoMs: 'automatic',
|
||||
tags: 'automatic',
|
||||
parentPostIds: 'automatic' },
|
||||
tagSources: {
|
||||
automatic: '',
|
||||
manual: '' },
|
||||
displayTags: [],
|
||||
fieldWarnings: { },
|
||||
baseWarnings: [] } }))
|
||||
|
||||
|
||||
const PostImportSourcePage: FC<Props> = ({ user }) => {
|
||||
const editable = canEditContent (user)
|
||||
@@ -117,6 +49,8 @@ const PostImportSourcePage: FC<Props> = ({ user }) => {
|
||||
const editedRef = useRef (false)
|
||||
|
||||
const lineCount = countImportSourceLines (source)
|
||||
const sourceUrls = extractImportSourceUrls (source)
|
||||
const withinPathLimit = isPostNewReviewPathWithinLimit (sourceUrls)
|
||||
const messages = sourceError != null ? [sourceError] : []
|
||||
const sourceDescribedBy =
|
||||
[sourceError != null ? SOURCE_ERROR_ID : null,
|
||||
@@ -124,13 +58,7 @@ const PostImportSourcePage: FC<Props> = ({ user }) => {
|
||||
.filter (value => value != null)
|
||||
.join (' ')
|
||||
|
||||
const showStorageError = (message: string) =>
|
||||
toast ({ description: message })
|
||||
|
||||
useEffect (() => {
|
||||
cleanupExpiredPostImportSessions (message =>
|
||||
toast ({ title: '保存済みデータを整理できませんでした', description: message }))
|
||||
|
||||
const draft = loadPostImportSourceDraft (message =>
|
||||
toast ({ title: '保存済み入力を復元できませんでした', description: message }))
|
||||
|
||||
@@ -173,73 +101,15 @@ const PostImportSourcePage: FC<Props> = ({ user }) => {
|
||||
setSourceError (null)
|
||||
return
|
||||
}
|
||||
if (!(withinPathLimit))
|
||||
return
|
||||
|
||||
setLoading (true)
|
||||
setSourceIssues ([])
|
||||
setSourceError (null)
|
||||
try
|
||||
{
|
||||
const nextRows = initialisePreviewRows (buildInitialRows (source))
|
||||
const serialised = await serialisePostNewState (nextRows)
|
||||
const sessionId = (() => {
|
||||
try
|
||||
{
|
||||
return generatePostImportSessionId ()
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null
|
||||
}
|
||||
}) ()
|
||||
const session = {
|
||||
source,
|
||||
rows: nextRows,
|
||||
repairMode: resultRepairMode (nextRows) }
|
||||
const saved =
|
||||
sessionId == null
|
||||
? false
|
||||
: savePostImportSession (sessionId, session, showStorageError)
|
||||
const loadedSession =
|
||||
sessionId == null
|
||||
? null
|
||||
: loadPostImportSession (sessionId, showStorageError)
|
||||
const sessionRoundTripSucceeded =
|
||||
saved
|
||||
&& serialisedPostImportRowsEqual (
|
||||
loadedSession?.rows ?? [],
|
||||
nextRows)
|
||||
const canNavigate =
|
||||
serialised?.complete === true
|
||||
|| sessionRoundTripSucceeded
|
||||
if (!(canNavigate))
|
||||
{
|
||||
showStorageError ('ブラウザへ保存できませんでした.')
|
||||
return
|
||||
}
|
||||
|
||||
const nextPath = (() => {
|
||||
if (serialised != null)
|
||||
{
|
||||
return sessionId == null
|
||||
? serialised.path
|
||||
: appendPostNewSessionId (serialised.path, sessionId)
|
||||
}
|
||||
if (sessionRoundTripSucceeded && sessionId != null)
|
||||
return `/posts/new?session_id=${ sessionId }`
|
||||
return null
|
||||
}) ()
|
||||
if (nextPath == null)
|
||||
{
|
||||
showStorageError ('ブラウザへ保存できませんでした.')
|
||||
return
|
||||
}
|
||||
|
||||
navigate (nextPath)
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
console.error (error)
|
||||
showStorageError ('ブラウザへ保存できませんでした.')
|
||||
navigate (buildPostNewReviewPath (sourceUrls))
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -268,8 +138,9 @@ const PostImportSourcePage: FC<Props> = ({ user }) => {
|
||||
className="h-80 font-mono text-sm"
|
||||
onBlur={() => {
|
||||
savePostImportSourceDraft (source, message =>
|
||||
toast ({ title: '入力内容を保存できませんでした',
|
||||
description: message }))
|
||||
toast ({
|
||||
title: '入力内容を保存できませんでした',
|
||||
description: message }))
|
||||
}}
|
||||
onChange={ev => {
|
||||
editedRef.current = true
|
||||
@@ -299,7 +170,7 @@ const PostImportSourcePage: FC<Props> = ({ user }) => {
|
||||
type="button"
|
||||
className="pointer-events-auto shrink-0"
|
||||
onClick={preview}
|
||||
disabled={loading}>
|
||||
disabled={loading || !(withinPathLimit)}>
|
||||
次へ
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
新しい課題から参照
ユーザをブロックする