このコミットが含まれているのは:
2026-07-17 23:14:37 +09:00
コミット 0ae41b6266
19個のファイルの変更455行の追加216行の削除
+131 -2
ファイルの表示
@@ -25,6 +25,31 @@ const ATTRIBUTE_KEYS = [
const PROVENANCE_KEYS = [...ATTRIBUTE_KEYS, 'url'] as const
const TAG_SOURCE_KEYS = ['automatic', 'manual'] as const
const WARNING_KEYS = [...ATTRIBUTE_KEYS, 'url'] as const
const ROW_KEYS = [
'sourceRow',
'url',
'attributes',
'fieldWarnings',
'baseWarnings',
'validationErrors',
'importErrors',
'provenance',
'tagSources',
'status',
'skipReason',
'existingPostId',
'existingPost',
'metadataUrl',
'resetSnapshot',
'createdPostId',
'importStatus',
'recoverable'] as const
const SESSION_KEYS = [
'version',
'expiresAt',
'source',
'rows',
'repairMode'] as const
const isPlainObject = (value: unknown): value is Record<string, unknown> =>
@@ -220,10 +245,110 @@ const sanitiseExistingPost = (value: unknown): PostImportExistingPost | undefine
thumbnailUrl: value.thumbnailUrl as string | undefined }
}
const recalculateThumbnailWarning = (row: PostImportRow): PostImportRow => {
const others = (row.fieldWarnings.thumbnailBase ?? []).filter (
key => key !== 'サムネールなし')
const thumbnailBasePresent =
typeof row.attributes.thumbnailBase === 'string'
&& row.attributes.thumbnailBase.trim () !== ''
return {
...row,
fieldWarnings: {
...row.fieldWarnings,
thumbnailBase:
thumbnailBasePresent
? others
: [...new Set ([...others, 'サムネールなし'])] } }
}
const serialiseExistingPost = (value: PostImportExistingPost | undefined) =>
value == null
? undefined
: {
id: value.id,
title: value.title,
url: value.url,
thumbnailUrl: value.thumbnailUrl }
const serialiseResetSnapshot = (value: PostImportResetSnapshot) => ({
url: value.url,
attributes: Object.fromEntries (
Object.entries (value.attributes).map (([key, entry]) => [key, entry])),
provenance: Object.fromEntries (
Object.entries (value.provenance).map (([key, entry]) => [key, entry])),
tagSources: {
automatic: value.tagSources.automatic,
manual: value.tagSources.manual },
fieldWarnings: Object.fromEntries (
Object.entries (value.fieldWarnings).map (([key, entry]) => [key, [...entry]])),
baseWarnings: [...value.baseWarnings],
metadataUrl: value.metadataUrl })
export const serialisePostImportRow = (row: PostImportRow) => {
const thumbnailBaseWarnings = (() => {
const others = (row.fieldWarnings.thumbnailBase ?? []).filter (
message => message !== 'サムネールなし')
const thumbnailBasePresent =
typeof row.attributes.thumbnailBase === 'string'
&& row.attributes.thumbnailBase.trim () !== ''
return thumbnailBasePresent
? others
: [...new Set ([...others, 'サムネールなし'])]
}) ()
return {
sourceRow: row.sourceRow,
url: row.url,
attributes: Object.fromEntries (
Object.entries (row.attributes).map (([key, entry]) => [key, entry])),
fieldWarnings: Object.fromEntries (
Object.entries ({
...row.fieldWarnings,
thumbnailBase: thumbnailBaseWarnings }).map (([key, entry]) => [key, [...entry]])),
baseWarnings: [...row.baseWarnings],
validationErrors: Object.fromEntries (
Object.entries (row.validationErrors).map (([key, entry]) => [key, [...entry]])),
importErrors:
row.importErrors == null
? undefined
: Object.fromEntries (
Object.entries (row.importErrors).map (([key, entry]) => [key, [...entry]])),
provenance: Object.fromEntries (
Object.entries (row.provenance).map (([key, entry]) => [key, entry])),
tagSources:
row.tagSources == null
? undefined
: {
automatic: row.tagSources.automatic,
manual: row.tagSources.manual },
status: row.status,
skipReason: row.skipReason,
existingPostId: row.existingPostId,
existingPost: serialiseExistingPost (row.existingPost),
metadataUrl: row.metadataUrl,
resetSnapshot: serialiseResetSnapshot (row.resetSnapshot),
createdPostId: row.createdPostId,
importStatus: row.importStatus,
recoverable: row.recoverable }
}
export const serialisePostImportRows = (rows: PostImportRow[]) =>
rows.map (row => serialisePostImportRow (row))
export const serialisedPostImportRowsEqual = (
left: PostImportRow[],
right: PostImportRow[],
): boolean =>
JSON.stringify (serialisePostImportRows (left))
=== JSON.stringify (serialisePostImportRows (right))
const sanitiseRow = (value: unknown): PostImportRow | null => {
if (!(isPlainObject (value)))
return null
if (!(hasOnlyKeys (value, ROW_KEYS)))
return null
if (!(Number.isInteger (value.sourceRow)) || Number (value.sourceRow) <= 0)
return null
if (typeof value.url !== 'string')
@@ -321,6 +446,8 @@ const sanitiseRow = (value: unknown): PostImportRow | null => {
const sanitiseSession = (value: unknown): PostImportSession | null => {
if (!(isPlainObject (value)))
return null
if (!(hasOnlyKeys (value, SESSION_KEYS)))
return null
if (value.version !== SESSION_VERSION || !(Array.isArray (value.rows)))
return null
if (typeof value.expiresAt !== 'string' || isExpiredSession (value.expiresAt))
@@ -334,7 +461,7 @@ const sanitiseSession = (value: unknown): PostImportSession | null => {
version: SESSION_VERSION,
expiresAt: value.expiresAt,
source: typeof value.source === 'string' ? value.source : '',
rows: rows as PostImportRow[],
rows: (rows as PostImportRow[]).map (row => recalculateThumbnailWarning (row)),
repairMode: value.repairMode === 'failed' ? 'failed' : 'all' }
}
@@ -438,7 +565,9 @@ export const savePostImportSession = (
return writeStorage (
sessionKey (validatedId),
JSON.stringify ({
...session,
source: session.source,
rows: serialisePostImportRows (session.rows),
repairMode: session.repairMode,
version: SESSION_VERSION,
expiresAt: new Date (Date.now () + SESSION_MAX_AGE_MS).toISOString () }),
onError)