このコミットが含まれているのは:
2026-07-18 01:28:22 +09:00
コミット 09ac2576bb
8個のファイルの変更196行の追加35行の削除
+3 -1
ファイルの表示
@@ -16,7 +16,9 @@ const hasWarnings = (row: PostImportRow): boolean =>
export const displayPostImportStatus = (
row: PostImportRow,
): PostImportDisplayStatus | null =>
(row.skipReason != null || row.importStatus === 'skipped')
row.status === 'pending'
? null
: (row.skipReason != null || row.importStatus === 'skipped')
? 'skipped'
: (row.importStatus === 'created')
? 'created'
+28 -3
ファイルの表示
@@ -73,8 +73,30 @@ export const validatePostImportSessionId = (value: unknown): string | null => {
}
export const generatePostImportSessionId = (): string =>
crypto.randomUUID ()
const generateUuidV4FromRandomValues = (): string => {
const bytes = new Uint8Array (16)
crypto.getRandomValues (bytes)
bytes[6] = (bytes[6] & 0x0f) | 0x40
bytes[8] = (bytes[8] & 0x3f) | 0x80
const hex = Array.from (
bytes,
byte => byte.toString (16).padStart (2, '0'))
return [
hex.slice (0, 4).join (''),
hex.slice (4, 6).join (''),
hex.slice (6, 8).join (''),
hex.slice (8, 10).join (''),
hex.slice (10, 16).join ('')].join ('-')
}
export const generatePostImportSessionId = (): string => {
if (typeof crypto.randomUUID === 'function')
return crypto.randomUUID ()
return generateUuidV4FromRandomValues ()
}
const sessionKey = (sessionId: string): string => `${ SESSION_PREFIX }${ sessionId }`
@@ -156,7 +178,10 @@ const ensureStringListRecord = (value: unknown): Record<string, string[]> | null
const isValidStatus = (
value: unknown,
): value is PostImportRow['status'] =>
value === 'ready' || value === 'warning' || value === 'error'
value === 'pending'
|| value === 'ready'
|| value === 'warning'
|| value === 'error'
const isValidImportStatus = (
+1 -1
ファイルの表示
@@ -34,7 +34,7 @@ export type PostImportRow = {
importErrors?: Record<string, string[]>
provenance: Record<string, PostImportOrigin>
tagSources?: Record<PostImportOrigin, string>
status: 'ready' | 'warning' | 'error'
status: 'pending' | 'ready' | 'warning' | 'error'
skipReason?: PostImportSkipReason
existingPostId?: number
existingPost?: PostImportExistingPost
+1 -1
ファイルの表示
@@ -344,7 +344,7 @@ const buildRow = (state: FullRowState): PostImportRow => {
validationErrors: { },
provenance,
tagSources,
status: 'ready',
status: 'pending',
skipReason: state.skip ?? undefined,
existingPostId: state.existing_post_id ?? undefined,
resetSnapshot: {
+58 -18
ファイルの表示
@@ -75,7 +75,7 @@ const buildInitialRows = (source: string): PostImportRow[] =>
tagSources: {
automatic: '',
manual: '' },
status: 'ready' as const,
status: 'pending' as const,
resetSnapshot: {
url: row.url,
attributes: {
@@ -122,6 +122,9 @@ const PostImportSourcePage: FC<Props> = ({ user }) => {
.filter (value => value != null)
.join (' ')
const showStorageError = (message: string) =>
toast ({ description: message })
useEffect (() => {
cleanupExpiredPostImportSessions (message =>
toast ({ title: '保存済みデータを整理できませんでした', description: message }))
@@ -176,28 +179,65 @@ const PostImportSourcePage: FC<Props> = ({ user }) => {
{
const nextRows = initialisePreviewRows (buildInitialRows (source))
const serialised = await serialisePostNewState (nextRows)
if (serialised == null)
return
const sessionId = generatePostImportSessionId ()
const sessionId = (() => {
try
{
return generatePostImportSessionId ()
}
catch
{
return null
}
}) ()
const session = {
source,
rows: nextRows,
repairMode: resultRepairMode (nextRows) }
const saved = savePostImportSession (sessionId, session)
const sessionLoaded = loadPostImportSession (sessionId)
if (!(serialised.complete)
&& (!(saved))
&& !(serialisedPostImportRowsEqual (sessionLoaded?.rows ?? [], nextRows)))
return
if (saved && !(serialisedPostImportRowsEqual (sessionLoaded?.rows ?? [], nextRows)))
return
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
}
navigate (appendPostNewSessionId (serialised.path, sessionId))
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
catch (error)
{
return
console.error (error)
showStorageError ('ブラウザへ保存できませんでした.')
}
finally
{
@@ -249,13 +289,13 @@ const PostImportSourcePage: FC<Props> = ({ user }) => {
</li>))}
</ul>
<div className="flex items-center justify-between gap-3">
<div className="relative z-10 flex items-center justify-between gap-3">
<div className="text-sm text-neutral-600 dark:text-neutral-300">
{lineCount} / {MAX_ROWS}
</div>
<Button
type="button"
className="shrink-0"
className="pointer-events-auto shrink-0"
onClick={preview}
disabled={loading}>