このコミットが含まれているのは:
@@ -270,7 +270,6 @@ describe ('post import row state', () => {
|
||||
thumbnailBase: '',
|
||||
originalCreatedFrom: '',
|
||||
originalCreatedBefore: '',
|
||||
duration: '2.5',
|
||||
tags: 'edited-tag',
|
||||
parentPostIds: '' },
|
||||
true)
|
||||
@@ -279,7 +278,7 @@ describe ('post import row state', () => {
|
||||
expect (nextRow.importErrors).toBeUndefined ()
|
||||
expect (nextRow.url).toBe ('https://example.com/edited')
|
||||
expect (nextRow.attributes.title).toBe ('edited title')
|
||||
expect (nextRow.attributes.duration).toBe ('2.5')
|
||||
expect (nextRow.attributes.duration).toBe ('2')
|
||||
expect (nextRow.attributes.tags).toBe ('edited-tag')
|
||||
expect (nextRow.provenance.title).toBe ('manual')
|
||||
expect (nextRow.provenance.url).toBe ('manual')
|
||||
|
||||
@@ -128,7 +128,6 @@ export const buildNextEditedRow = (
|
||||
['thumbnailBase', draft.thumbnailBase],
|
||||
['originalCreatedFrom', draft.originalCreatedFrom],
|
||||
['originalCreatedBefore', draft.originalCreatedBefore],
|
||||
['duration', draft.duration],
|
||||
['parentPostIds', draft.parentPostIds]] as const
|
||||
draftFields.forEach (([field, value]) => {
|
||||
nextAttributes[field] = value
|
||||
|
||||
@@ -25,11 +25,11 @@ describe ('post import storage', () => {
|
||||
skipReason: 'existing',
|
||||
existingPostId: 10 })
|
||||
|
||||
expect (savePostImportSession ('session', {
|
||||
expect (savePostImportSession ({
|
||||
source: skipped.url,
|
||||
rows: [row, skipped],
|
||||
repairMode: 'all' })).toBe (true)
|
||||
expect (loadPostImportSession ('session')).toMatchObject ({
|
||||
expect (loadPostImportSession ()).toMatchObject ({
|
||||
version: 2,
|
||||
source: skipped.url,
|
||||
rows: [{
|
||||
@@ -63,12 +63,13 @@ describe ('post import storage', () => {
|
||||
{ ...session.rows[0], importStatus: 'skipped', recoverable: true },
|
||||
{ ...session.rows[0], recoverable: true }]
|
||||
|
||||
for (const [index, row] of invalidRows.entries ())
|
||||
for (const row of invalidRows)
|
||||
{
|
||||
sessionStorage.setItem (`post-import-session:invalid-${ index }`, JSON.stringify ({
|
||||
sessionStorage.setItem ('post-import-session:current', JSON.stringify ({
|
||||
...session,
|
||||
rows: [row] }))
|
||||
expect (loadPostImportSession (`invalid-${ index }`)).toBeNull ()
|
||||
expect (loadPostImportSession ()).toBeNull ()
|
||||
sessionStorage.clear ()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -82,18 +83,19 @@ describe ('post import storage', () => {
|
||||
{ ...row, resetSnapshot: { ...row.resetSnapshot,
|
||||
fieldWarnings: { title: 'warning' } } }]
|
||||
|
||||
invalidRows.forEach ((invalidRow, index) => {
|
||||
sessionStorage.setItem (`post-import-session:shape-${ index }`, JSON.stringify ({
|
||||
invalidRows.forEach (invalidRow => {
|
||||
sessionStorage.setItem ('post-import-session:current', JSON.stringify ({
|
||||
version: 2,
|
||||
savedAt: new Date ().toISOString (),
|
||||
source: '',
|
||||
rows: [invalidRow],
|
||||
repairMode: 'all' }))
|
||||
expect (loadPostImportSession (`shape-${ index }`)).toBeNull ()
|
||||
expect (loadPostImportSession ()).toBeNull ()
|
||||
sessionStorage.clear ()
|
||||
})
|
||||
})
|
||||
|
||||
it ('removes expired and malformed sessions without touching current sessions', () => {
|
||||
it ('keeps only the current fixed-key session and removes legacy prefixed entries', () => {
|
||||
const current = {
|
||||
version: 2,
|
||||
savedAt: new Date ().toISOString (),
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { PostImportOrigin,
|
||||
|
||||
const SESSION_VERSION = 2
|
||||
const SESSION_PREFIX = 'post-import-session:'
|
||||
const CURRENT_SESSION_KEY = `${ SESSION_PREFIX }current`
|
||||
const SOURCE_DRAFT_KEY = 'post-import-source-draft'
|
||||
const SESSION_MAX_AGE_MS = 24 * 60 * 60 * 1000
|
||||
const ATTRIBUTE_KEYS = [
|
||||
@@ -28,7 +29,7 @@ const isPlainObject = (value: unknown): value is Record<string, unknown> =>
|
||||
typeof value === 'object' && value != null && !(Array.isArray (value))
|
||||
|
||||
|
||||
const sessionKey = (sessionId: string): string => `${ SESSION_PREFIX }${ sessionId }`
|
||||
const sessionKey = (): string => CURRENT_SESSION_KEY
|
||||
|
||||
|
||||
const readStorage = (
|
||||
@@ -288,12 +289,6 @@ const isExpiredSession = (savedAt: string): boolean => {
|
||||
}
|
||||
|
||||
|
||||
export const createPostImportSessionId = (): string =>
|
||||
typeof crypto !== 'undefined' && 'randomUUID' in crypto
|
||||
? crypto.randomUUID ()
|
||||
: `${ Date.now () }-${ Math.random ().toString (36).slice (2) }`
|
||||
|
||||
|
||||
export const cleanupExpiredPostImportSessions = (
|
||||
onError?: StorageErrorHandler,
|
||||
) => {
|
||||
@@ -311,6 +306,13 @@ export const cleanupExpiredPostImportSessions = (
|
||||
const raw = sessionStorage.getItem (key)
|
||||
if (raw == null)
|
||||
continue
|
||||
|
||||
if (key !== CURRENT_SESSION_KEY)
|
||||
{
|
||||
sessionStorage.removeItem (key)
|
||||
--i
|
||||
continue
|
||||
}
|
||||
try
|
||||
{
|
||||
const value = JSON.parse (raw) as { savedAt?: string }
|
||||
@@ -368,24 +370,38 @@ export const clearPostImportSourceDraft = (
|
||||
|
||||
|
||||
export const savePostImportSession = (
|
||||
sessionId: string,
|
||||
session: Omit<PostImportSession, 'version' | 'savedAt'>,
|
||||
sessionOrLegacyId: string | Omit<PostImportSession, 'version' | 'savedAt'>,
|
||||
sessionOrOnError?: Omit<PostImportSession, 'version' | 'savedAt'> | StorageErrorHandler,
|
||||
onError?: StorageErrorHandler,
|
||||
): boolean =>
|
||||
writeStorage (
|
||||
sessionKey (sessionId),
|
||||
): boolean => {
|
||||
const session =
|
||||
typeof sessionOrLegacyId === 'string'
|
||||
? sessionOrOnError as Omit<PostImportSession, 'version' | 'savedAt'>
|
||||
: sessionOrLegacyId
|
||||
const errorHandler =
|
||||
typeof sessionOrLegacyId === 'string'
|
||||
? onError
|
||||
: sessionOrOnError as StorageErrorHandler | undefined
|
||||
|
||||
return writeStorage (
|
||||
sessionKey (),
|
||||
JSON.stringify ({
|
||||
...session,
|
||||
version: SESSION_VERSION,
|
||||
savedAt: new Date ().toISOString () }),
|
||||
onError)
|
||||
errorHandler)
|
||||
}
|
||||
|
||||
|
||||
export const loadPostImportSession = (
|
||||
sessionId: string,
|
||||
legacyIdOrOnError?: string | StorageErrorHandler,
|
||||
onError?: StorageErrorHandler,
|
||||
): PostImportSession | null => {
|
||||
const raw = readStorage (sessionKey (sessionId), onError)
|
||||
const errorHandler =
|
||||
typeof legacyIdOrOnError === 'string'
|
||||
? onError
|
||||
: legacyIdOrOnError
|
||||
const raw = readStorage (sessionKey (), errorHandler)
|
||||
if (raw == null)
|
||||
return null
|
||||
|
||||
@@ -396,7 +412,7 @@ export const loadPostImportSession = (
|
||||
return null
|
||||
if (typeof value.savedAt !== 'string' || isExpiredSession (value.savedAt))
|
||||
{
|
||||
removeStorage (sessionKey (sessionId), onError)
|
||||
removeStorage (sessionKey (), errorHandler)
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -416,3 +432,10 @@ export const loadPostImportSession = (
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const clearPostImportSession = (
|
||||
onError?: StorageErrorHandler,
|
||||
) => {
|
||||
removeStorage (sessionKey (), onError)
|
||||
}
|
||||
|
||||
@@ -78,7 +78,6 @@ export type PostImportEditableDraft = {
|
||||
thumbnailBase: string
|
||||
originalCreatedFrom: string
|
||||
originalCreatedBefore: string
|
||||
duration: string
|
||||
tags: string
|
||||
parentPostIds: string }
|
||||
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする