このコミットが含まれているのは:
@@ -57,7 +57,8 @@ class PostImportPreviewer
|
||||
attributes['tags'] = merged_tags(tag_sources, provenance['tags'])
|
||||
{ source_row: row[:source_row], url: normal_url || url, attributes:, provenance:, tag_sources:,
|
||||
metadata_url: url,
|
||||
fetch_warnings:, validation_warnings:, warnings: fetch_warnings + validation_warnings, errors:,
|
||||
fetch_warnings:, validation_warnings:, warnings: fetch_warnings + validation_warnings,
|
||||
validation_errors: errors, errors:,
|
||||
status: errors.present? ? 'error' : ((fetch_warnings + validation_warnings).present? ? 'warning' : 'ready'),
|
||||
existing: normal_url.present? && Post.exists?(url: normal_url) }
|
||||
end
|
||||
|
||||
@@ -43,11 +43,18 @@ class PostImportSourceParser
|
||||
raise ArgumentError, 'JSON の投稿候補は配列にしてください.' unless value.is_a?(Array)
|
||||
|
||||
ensure_limits!(value)
|
||||
keys = value.grep(Hash).flat_map(&:keys).map(&:to_s).uniq
|
||||
unless value.all?(Hash)
|
||||
invalid = value.each_with_index.filter_map { |item, index| index + 1 unless item.is_a?(Hash) }
|
||||
raise ArgumentError, "JSON の投稿候補は object の配列にしてください: 行 #{ invalid.join(', ') }"
|
||||
end
|
||||
keys = value.flat_map(&:keys).map(&:to_s).uniq
|
||||
raise ArgumentError, "列数は #{ MAX_COLUMNS } 列までです." if keys.length > MAX_COLUMNS
|
||||
{ columns: keys.each_with_index.map { |key, index| { key:, label: "#{ column_name(index) }:#{ key }" } },
|
||||
rows: value.each_with_index.map { |item, index| { source_row: index + 1,
|
||||
values: keys.map { json_cell(item.is_a?(Hash) ? item[_1] || item[_1.to_sym] : nil) } } } }
|
||||
values: keys.map { |key|
|
||||
value = item.key?(key) ? item[key] : item[key.to_sym]
|
||||
json_cell(value)
|
||||
} } } }
|
||||
rescue JSON::ParserError, KeyError, ArgumentError => e
|
||||
raise ArgumentError, "JSON の形式が不正です: #{ e.message }"
|
||||
end
|
||||
|
||||
@@ -26,7 +26,8 @@ type ImportRow = {
|
||||
url: string
|
||||
attributes: Record<string, string>
|
||||
warnings: string[]
|
||||
errors: Record<string, string[]>
|
||||
validationErrors: Record<string, string[]>
|
||||
importErrors?: Record<string, string[]>
|
||||
provenance: Record<string, 'automatic' | 'mapped' | 'fixed' | 'manual'>
|
||||
tagSources?: Record<'automatic' | 'mapped' | 'fixed' | 'manual', string>
|
||||
status: 'ready' | 'warning' | 'error'
|
||||
@@ -127,19 +128,24 @@ const PostImportPage: FC<Props> = ({ user }) => {
|
||||
}
|
||||
|
||||
const mergeValidatedRows = (current: ImportRow[], validated: ImportRow[]): ImportRow[] =>
|
||||
current.filter (row => row.importStatus === 'created').concat (validated.map (row => {
|
||||
const previous = current.find (_1 => _1.sourceRow === row.sourceRow)
|
||||
return previous ? { ...row,
|
||||
importStatus: previous.importStatus,
|
||||
createdPostId: previous.createdPostId,
|
||||
errors: previous.importStatus === 'failed' ? previous.errors : row.errors } : row
|
||||
}))
|
||||
current.map (previous => {
|
||||
if (previous.importStatus === 'created')
|
||||
return previous
|
||||
const row = validated.find (_1 => _1.sourceRow === previous.sourceRow)
|
||||
return row ? { ...row,
|
||||
importStatus: previous.importStatus,
|
||||
createdPostId: previous.createdPostId,
|
||||
importErrors: previous.importErrors,
|
||||
validationErrors: row.validationErrors ?? row.errors ?? { } } : previous
|
||||
}).concat (validated.filter (row => !current.some (_1 => _1.sourceRow === row.sourceRow)))
|
||||
|
||||
const updateAttribute = async (index: number, field: string, value: string) => {
|
||||
const row = rows[index]
|
||||
const next = { ...row,
|
||||
attributes: { ...row.attributes, [field]: value },
|
||||
provenance: { ...row.provenance, [field]: 'manual' } }
|
||||
provenance: { ...row.provenance, [field]: 'manual' },
|
||||
importStatus: 'pending' as const,
|
||||
importErrors: undefined }
|
||||
const nextRows = rows.map ((currentRow, rowIndex) => rowIndex === index ? next : currentRow)
|
||||
setRows (nextRows)
|
||||
const generation = ++validationGeneration.current
|
||||
@@ -165,7 +171,8 @@ const PostImportPage: FC<Props> = ({ user }) => {
|
||||
|
||||
const updateURL = async (index: number, value: string) => {
|
||||
const row = rows[index]
|
||||
const next = { ...row, url: value, provenance: { ...row.provenance, url: 'manual' } }
|
||||
const next = { ...row, url: value, provenance: { ...row.provenance, url: 'manual' },
|
||||
importStatus: 'pending' as const, importErrors: undefined }
|
||||
const nextRows = rows.map ((currentRow, rowIndex) => rowIndex === index ? next : currentRow)
|
||||
setRows (nextRows)
|
||||
const generation = ++validationGeneration.current
|
||||
@@ -226,7 +233,9 @@ const PostImportPage: FC<Props> = ({ user }) => {
|
||||
const resultRow = result.rows.find (_1 => _1.sourceRow === row.sourceRow)
|
||||
return resultRow ? { ...row, importStatus: resultRow.status,
|
||||
createdPostId: resultRow.post?.id,
|
||||
errors: resultRow.errors ?? row.errors } : row
|
||||
importErrors: resultRow.errors,
|
||||
importStatus: resultRow.status,
|
||||
validationErrors: row.validationErrors } : row
|
||||
}))
|
||||
}
|
||||
catch
|
||||
@@ -334,7 +343,7 @@ const PostImportPage: FC<Props> = ({ user }) => {
|
||||
const RowCard = ({ row, index, update, updateURL }: { row: ImportRow, index: number, update: (index: number, field: string, value: string) => void, updateURL: (index: number, value: string) => void }) => (
|
||||
<article className="space-y-2 rounded border bg-white p-3 text-neutral-900 dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-100">
|
||||
<p>行 {row.sourceRow}・{row.status}</p>
|
||||
{row.importStatus === 'created' ? <p className="rounded bg-green-100 p-2 dark:bg-green-900">登録済み。この行は編集・再登録できません。</p> : <>
|
||||
{row.importStatus === 'created' ? <p className="rounded bg-green-100 p-2 dark:bg-green-900">登録済み。この行は編集・再登録できません。{row.createdPostId && <PrefetchLink to={`/posts/${row.createdPostId}`}>投稿を開く</PrefetchLink>}</p> : <>
|
||||
<EditableValue label="URL" value={row.url} origin={row.provenance.url} onCommit={value => updateURL (index, value)}/>
|
||||
<EditableValue label="タイトル" value={row.attributes.title ?? ''} origin={row.provenance.title} onCommit={value => update (index, 'title', value)}/>
|
||||
<EditableValue label="サムネール基底 URL" value={row.attributes.thumbnailBase ?? ''} origin={row.provenance.thumbnailBase} onCommit={value => update (index, 'thumbnailBase', value)}/>
|
||||
@@ -345,7 +354,9 @@ const RowCard = ({ row, index, update, updateURL }: { row: ImportRow, index: num
|
||||
<EditableValue label="タグ" value={row.attributes.tags ?? ''} origin={row.provenance.tags} onCommit={value => update (index, 'tags', value)}/>
|
||||
<EditableValue label="親投稿" value={row.attributes.parentPostIds ?? ''} origin={row.provenance.parentPostIds} onCommit={value => update (index, 'parentPostIds', value)}/>
|
||||
</>}
|
||||
<FieldError messages={[...row.warnings, ...Object.values (row.errors).flat ()]}/>
|
||||
<FieldError messages={[...row.warnings,
|
||||
...Object.values (row.validationErrors ?? { }).flat (),
|
||||
...Object.values (row.importErrors ?? { }).flat ()]}/>
|
||||
</article>)
|
||||
|
||||
const EditableValue = ({ label, value, origin, onCommit }: { label: string, value: string, origin: string | undefined, onCommit: (value: string) => void }) => {
|
||||
|
||||
新しい課題から参照
ユーザをブロックする