このコミットが含まれているのは:
@@ -47,7 +47,11 @@ type PreviewResponse = {
|
||||
tags?: string
|
||||
fieldWarnings?: Record<string, string[]>
|
||||
baseWarnings?: string[]
|
||||
existingPost?: { id: number } | null }
|
||||
existingPost?: {
|
||||
id: number
|
||||
title: string
|
||||
url: string
|
||||
thumbnailUrl?: string } | null }
|
||||
|
||||
const MAX_ROWS = 100
|
||||
const SOURCE_ERROR_ID = 'post-import-source-error'
|
||||
@@ -104,6 +108,7 @@ const previewRowFromResult = (
|
||||
: 'ready',
|
||||
skipReason: result.existingPost?.id != null ? 'existing' : undefined,
|
||||
existingPostId: result.existingPost?.id,
|
||||
existingPost: result.existingPost ?? undefined,
|
||||
resetSnapshot: {
|
||||
url: result.url,
|
||||
attributes: {
|
||||
@@ -134,25 +139,57 @@ const previewRowFromResult = (
|
||||
}
|
||||
|
||||
|
||||
const fetchPreviewRows = async (urls: string[]): Promise<PostImportRow[]> => {
|
||||
const results: PostImportRow[] = new Array (urls.length)
|
||||
const fetchPreviewRows = async (
|
||||
rows: Array<{ sourceRow: number
|
||||
url: string }>,
|
||||
signal: AbortSignal,
|
||||
): Promise<PostImportRow[]> => {
|
||||
const results: PostImportRow[] = new Array (rows.length)
|
||||
let nextIndex = 0
|
||||
const worker = async () => {
|
||||
while (nextIndex < urls.length)
|
||||
while (nextIndex < rows.length)
|
||||
{
|
||||
const index = nextIndex
|
||||
++nextIndex
|
||||
const row = rows[index]
|
||||
const result = await apiGet<PreviewResponse> ('/posts/preview', {
|
||||
params: { url: urls[index] } })
|
||||
results[index] = previewRowFromResult (index + 1, result)
|
||||
params: { url: row.url },
|
||||
signal })
|
||||
results[index] = previewRowFromResult (row.sourceRow, result)
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all (
|
||||
Array.from ({ length: Math.min (4, urls.length) }, () => worker ()))
|
||||
Array.from ({ length: Math.min (4, rows.length) }, () => worker ()))
|
||||
return results
|
||||
}
|
||||
|
||||
const serialisableRowJson = (row: PostImportRow): string =>
|
||||
JSON.stringify ({
|
||||
sourceRow: row.sourceRow,
|
||||
url: row.url,
|
||||
attributes: row.attributes,
|
||||
fieldWarnings: row.fieldWarnings,
|
||||
baseWarnings: row.baseWarnings,
|
||||
validationErrors: row.validationErrors,
|
||||
provenance: row.provenance,
|
||||
tagSources: row.tagSources,
|
||||
status: row.status,
|
||||
skipReason: row.skipReason,
|
||||
existingPostId: row.existingPostId,
|
||||
existingPost: row.existingPost,
|
||||
resetSnapshot: row.resetSnapshot,
|
||||
importStatus: row.importStatus,
|
||||
recoverable: row.recoverable })
|
||||
|
||||
const sameRows = (
|
||||
left: PostImportRow[],
|
||||
right: PostImportRow[],
|
||||
): boolean =>
|
||||
left.length === right.length
|
||||
&& left.every ((row, index) =>
|
||||
serialisableRowJson (row) === serialisableRowJson (right[index]))
|
||||
|
||||
|
||||
const PostImportSourcePage: FC<Props> = ({ user }) => {
|
||||
const editable = canEditContent (user)
|
||||
@@ -163,6 +200,7 @@ const PostImportSourcePage: FC<Props> = ({ user }) => {
|
||||
const [sourceError, setSourceError] = useState<string | null> (null)
|
||||
const saveTimer = useRef<number | null> (null)
|
||||
const editedRef = useRef (false)
|
||||
const previewController = useRef<AbortController | null> (null)
|
||||
|
||||
const lineCount = countImportSourceLines (source)
|
||||
const messages = sourceError != null ? [sourceError] : []
|
||||
@@ -203,6 +241,10 @@ const PostImportSourcePage: FC<Props> = ({ user }) => {
|
||||
}
|
||||
}, [source])
|
||||
|
||||
useEffect (() => () => {
|
||||
previewController.current?.abort ()
|
||||
}, [])
|
||||
|
||||
const preview = async () => {
|
||||
if (lineCount === 0)
|
||||
{
|
||||
@@ -224,8 +266,17 @@ const PostImportSourcePage: FC<Props> = ({ user }) => {
|
||||
setSourceError (null)
|
||||
try
|
||||
{
|
||||
const urls = source.split (/\r\n|\n|\r/).map (line => line.trim ()).filter (line => line !== '')
|
||||
const rows = await fetchPreviewRows (urls)
|
||||
previewController.current?.abort ()
|
||||
const controller = new AbortController ()
|
||||
previewController.current = controller
|
||||
const rows = await fetchPreviewRows (
|
||||
source
|
||||
.split (/\r\n|\n|\r/)
|
||||
.map ((line, index) => ({
|
||||
sourceRow: index + 1,
|
||||
url: line.trim () }))
|
||||
.filter (row => row.url !== ''),
|
||||
controller.signal)
|
||||
const urlIssues = urlIssuesFromRows (rows, source)
|
||||
if (urlIssues.length > 0)
|
||||
{
|
||||
@@ -242,9 +293,13 @@ const PostImportSourcePage: FC<Props> = ({ user }) => {
|
||||
source,
|
||||
rows: nextRows,
|
||||
repairMode: resultRepairMode (nextRows) }
|
||||
savePostImportSession (sessionId, session)
|
||||
const saved = savePostImportSession (sessionId, session)
|
||||
const sessionLoaded = loadPostImportSession (sessionId)
|
||||
if (!(serialised.complete) && sessionLoaded?.rows.length !== nextRows.length)
|
||||
if (!(serialised.complete)
|
||||
&& (!(saved))
|
||||
&& !(sameRows (sessionLoaded?.rows ?? [], nextRows)))
|
||||
return
|
||||
if (saved && !(sameRows (sessionLoaded?.rows ?? [], nextRows)))
|
||||
return
|
||||
|
||||
navigate (appendPostNewSessionId (serialised.path, sessionId))
|
||||
|
||||
新しい課題から参照
ユーザをブロックする