diff --git a/backend/app/services/post_import_previewer.rb b/backend/app/services/post_import_previewer.rb index 7052f96..7fa55db 100644 --- a/backend/app/services/post_import_previewer.rb +++ b/backend/app/services/post_import_previewer.rb @@ -126,7 +126,7 @@ class PostImportPreviewer warnings_present = field_warnings.values.any?(&:present?) || base_warnings.present? { source_row: row[:source_row], - url: normal_url || url, + url: validation_errors[:url].present? ? url : (normal_url || url), attributes:, provenance:, tag_sources:, @@ -199,7 +199,7 @@ class PostImportPreviewer def fetch_metadata url return { data: { }, warnings: { 'url' => ['URL が空です.'] } } if url.blank? - data = PostMetadataFetcher.fetch(url).stringify_keys.compact + data = sanitise_metadata(PostMetadataFetcher.fetch(url).stringify_keys.compact) warnings = { } add_field_warning!(warnings, 'title', TITLE_FETCH_WARNING) if data['title'].blank? if data['thumbnail_base'].blank? @@ -215,6 +215,40 @@ class PostImportPreviewer { data: { }, warnings: { 'url' => [METADATA_FETCH_WARNING] } } end + def sanitise_metadata metadata + { + 'title' => sanitise_metadata_title(metadata['title']), + 'thumbnail_base' => sanitise_metadata_url(metadata['thumbnail_base']), + 'original_created_from' => sanitise_metadata_time(metadata['original_created_from']), + 'original_created_before' => sanitise_metadata_time(metadata['original_created_before']), + 'duration' => sanitise_metadata_duration(metadata['duration']), + 'tags' => metadata['tags'].to_s.presence }.compact + end + + def sanitise_metadata_title value + value.is_a?(String) ? value.presence : nil + end + + def sanitise_metadata_url value + return nil unless value.is_a?(String) + + PostUrlNormaliser.normalise(value) + end + + def sanitise_metadata_time value + return nil unless value.is_a?(String) + + Time.zone.parse(value)&.iso8601 + rescue ArgumentError, TypeError + nil + end + + def sanitise_metadata_duration value + return nil unless value.is_a?(Numeric) + + value.positive? ? value.to_i : nil + end + def preload_metadata! prepared_rows, fetch_metadata, metadata_cache, existing_posts, url_counts urls = prepared_rows.filter_map { |row| next unless row[:normal_url].present? diff --git a/frontend/src/components/posts/import/PostImportRowDialog.tsx b/frontend/src/components/posts/import/PostImportRowDialog.tsx index 4224f66..644b68d 100644 --- a/frontend/src/components/posts/import/PostImportRowDialog.tsx +++ b/frontend/src/components/posts/import/PostImportRowDialog.tsx @@ -4,7 +4,7 @@ import FieldWarning from '@/components/common/FieldWarning' import FormField from '@/components/common/FormField' import PostImportStatusBadge from '@/components/posts/import/PostImportStatusBadge' import ThumbnailPreview from '@/components/posts/import/ThumbnailPreview' -import { effectivePostImportStatus } from '@/components/posts/import/postImportRowStatus' +import { displayPostImportStatus } from '@/components/posts/import/postImportRowStatus' import { Button } from '@/components/ui/button' import { Dialog, DialogContent, @@ -17,8 +17,7 @@ import { inputClass } from '@/lib/utils' import type { FC } from 'react' import { useEffect, useState } from 'react' -import type { PostImportOrigin, - PostImportRow } from '@/lib/postImportSession' +import type { PostImportRow } from '@/lib/postImportSession' type Draft = { url: string @@ -31,41 +30,12 @@ type Draft = { parentPostIds: string } type Props = { - open: boolean - row: PostImportRow | null - saving: boolean - onOpenChange: (open: boolean) => void - onSave: (draft: Draft) => Promise } - -const originOf = ( - row: PostImportRow, - field: string, -): PostImportOrigin => - row.provenance[field] ?? 'automatic' - -const changedOrigin = ( - changed: boolean, - row: PostImportRow, - field: string, -): PostImportOrigin => - changed ? 'manual' : originOf (row, field) - -const originalCreatedOrigin = ( - row: PostImportRow, - originalDraft: Draft, - draft: Draft, -): PostImportOrigin => { - const changed = - originalDraft.originalCreatedFrom !== draft.originalCreatedFrom - || originalDraft.originalCreatedBefore !== draft.originalCreatedBefore - if (changed) - return 'manual' - - const manualOrigin = - originOf (row, 'originalCreatedFrom') === 'manual' - || originOf (row, 'originalCreatedBefore') === 'manual' - return manualOrigin ? 'manual' : 'automatic' -} + open: boolean + row: PostImportRow | null + messageRow?: PostImportRow | null + saving: boolean + onOpenChange: (open: boolean) => void + onSave: (draft: Draft) => Promise } const buildDraft = (row: PostImportRow): Draft => ({ url: row.url, @@ -77,6 +47,16 @@ const buildDraft = (row: PostImportRow): Draft => ({ tags: String (row.attributes.tags ?? ''), parentPostIds: String (row.attributes.parentPostIds ?? '') }) +const buildResetDraft = (row: PostImportRow): Draft => ({ + url: row.resetSnapshot.url, + title: String (row.resetSnapshot.attributes.title ?? ''), + thumbnailBase: String (row.resetSnapshot.attributes.thumbnailBase ?? ''), + originalCreatedFrom: String (row.resetSnapshot.attributes.originalCreatedFrom ?? ''), + originalCreatedBefore: String (row.resetSnapshot.attributes.originalCreatedBefore ?? ''), + duration: String (row.resetSnapshot.attributes.duration ?? ''), + tags: String (row.resetSnapshot.attributes.tags ?? ''), + parentPostIds: String (row.resetSnapshot.attributes.parentPostIds ?? '') }) + const groupedMessages = ( ...values: Array ): string[] => @@ -84,7 +64,7 @@ const groupedMessages = ( const PostImportRowDialog: FC = ( - { open, row, saving, onOpenChange, onSave }, + { open, row, messageRow, saving, onOpenChange, onSave }, ) => { const [draft, setDraft] = useState (null) @@ -96,9 +76,7 @@ const PostImportRowDialog: FC = ( if (row == null || draft == null) return null - const originalDraft = buildDraft (row) - const fieldOrigin = (field: keyof Draft): PostImportOrigin => - changedOrigin (draft[field] !== originalDraft[field], row, field) + const displayRow = messageRow ?? row const update = ( key: Key, @@ -131,7 +109,7 @@ const PostImportRowDialog: FC = ( url={draft.thumbnailBase} className="h-28 w-28"/>
- +
@@ -139,79 +117,78 @@ const PostImportRowDialog: FC = ( update ('url', value)}/> update ('title', value)}/> update ('thumbnailBase', value)}/> - } originalCreatedFrom={draft.originalCreatedFrom || null} setOriginalCreatedFrom={value => update ('originalCreatedFrom', value ?? '')} originalCreatedBefore={draft.originalCreatedBefore || null} setOriginalCreatedBefore={value => update ('originalCreatedBefore', value ?? '')} errors={groupedMessages ( - row.validationErrors.originalCreatedAt, - row.validationErrors.originalCreatedFrom, - row.validationErrors.originalCreatedBefore, - row.importErrors?.originalCreatedAt, - row.importErrors?.originalCreatedFrom, - row.importErrors?.originalCreatedBefore, + displayRow.validationErrors.originalCreatedAt, + displayRow.validationErrors.originalCreatedFrom, + displayRow.validationErrors.originalCreatedBefore, + displayRow.importErrors?.originalCreatedAt, + displayRow.importErrors?.originalCreatedFrom, + displayRow.importErrors?.originalCreatedBefore, )}/> update ('duration', value)}/> update ('tags', value)}/> update ('parentPostIds', value)}/> - - - + + + +