このコミットが含まれているのは:
@@ -148,7 +148,8 @@ class PostsController < ApplicationController
|
|||||||
original_created_from: params[:original_created_from],
|
original_created_from: params[:original_created_from],
|
||||||
original_created_before: params[:original_created_before],
|
original_created_before: params[:original_created_before],
|
||||||
parent_post_ids: parse_parent_post_ids,
|
parent_post_ids: parse_parent_post_ids,
|
||||||
video_ms: params[:video_ms], duration: params[:duration] }).create!
|
video_ms: params[:video_ms],
|
||||||
|
duration: params[:duration] }).create!
|
||||||
|
|
||||||
post.reload
|
post.reload
|
||||||
render json: PostRepr.base(post), status: :created
|
render json: PostRepr.base(post), status: :created
|
||||||
|
|||||||
@@ -54,20 +54,25 @@ class PostImportPreviewer
|
|||||||
add_field_warning!(field_warnings, 'url', EXISTING_SKIP_WARNING)
|
add_field_warning!(field_warnings, 'url', EXISTING_SKIP_WARNING)
|
||||||
attributes['tags'] = merged_tags(tag_sources, provenance['tags'])
|
attributes['tags'] = merged_tags(tag_sources, provenance['tags'])
|
||||||
warnings_present = field_warnings.values.any?(&:present?) || base_warnings.present?
|
warnings_present = field_warnings.values.any?(&:present?) || base_warnings.present?
|
||||||
return {
|
return { source_row: row[:source_row],
|
||||||
source_row: row[:source_row],
|
url: normal_url,
|
||||||
url: normal_url,
|
attributes:,
|
||||||
attributes:,
|
provenance:,
|
||||||
provenance:,
|
tag_sources:,
|
||||||
tag_sources:,
|
metadata_url: url_for_metadata,
|
||||||
metadata_url: url_for_metadata,
|
skip_reason: 'existing',
|
||||||
skip_reason: 'existing',
|
existing_post_id: existing_post.id,
|
||||||
existing_post_id: existing_post.id,
|
field_warnings:,
|
||||||
field_warnings:,
|
base_warnings:,
|
||||||
base_warnings:,
|
validation_errors:,
|
||||||
validation_errors:,
|
status:
|
||||||
status: validation_errors.present? ? 'error' : (warnings_present ? 'warning' : 'ready'),
|
if validation_errors.present?
|
||||||
}
|
'error'
|
||||||
|
elsif warnings_present
|
||||||
|
'warning'
|
||||||
|
else
|
||||||
|
'ready'
|
||||||
|
end }
|
||||||
end
|
end
|
||||||
|
|
||||||
should_fetch = should_fetch_metadata?(fetch_metadata, row[:source_row].to_i)
|
should_fetch = should_fetch_metadata?(fetch_metadata, row[:source_row].to_i)
|
||||||
@@ -88,8 +93,7 @@ class PostImportPreviewer
|
|||||||
attributes['tags'] = merged_tags(tag_sources, provenance['tags'])
|
attributes['tags'] = merged_tags(tag_sources, provenance['tags'])
|
||||||
|
|
||||||
warnings_present = field_warnings.values.any?(&:present?) || base_warnings.present?
|
warnings_present = field_warnings.values.any?(&:present?) || base_warnings.present?
|
||||||
{
|
{ source_row: row[:source_row],
|
||||||
source_row: row[:source_row],
|
|
||||||
url: normal_url || url,
|
url: normal_url || url,
|
||||||
attributes:,
|
attributes:,
|
||||||
provenance:,
|
provenance:,
|
||||||
@@ -100,8 +104,7 @@ class PostImportPreviewer
|
|||||||
field_warnings:,
|
field_warnings:,
|
||||||
base_warnings:,
|
base_warnings:,
|
||||||
validation_errors:,
|
validation_errors:,
|
||||||
status: validation_errors.present? ? 'error' : (warnings_present ? 'warning' : 'ready'),
|
status: validation_errors.present? ? 'error' : (warnings_present ? 'warning' : 'ready') }
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def should_fetch_metadata? fetch_metadata, source_row
|
def should_fetch_metadata? fetch_metadata, source_row
|
||||||
@@ -175,9 +178,8 @@ class PostImportPreviewer
|
|||||||
Preview::HttpFetcher::FetchFailed,
|
Preview::HttpFetcher::FetchFailed,
|
||||||
Preview::HttpFetcher::ResponseTooLarge => e
|
Preview::HttpFetcher::ResponseTooLarge => e
|
||||||
Rails.logger.info(
|
Rails.logger.info(
|
||||||
"post_import_metadata_fetch_failure "\
|
"post_import_metadata_fetch_failure "\
|
||||||
"#{ { error: e.class.name, message: e.message }.to_json }",
|
"#{ { error: e.class.name, message: e.message }.to_json }")
|
||||||
)
|
|
||||||
{ data: { }, warnings: { 'url' => [METADATA_FETCH_WARNING] } }
|
{ data: { }, warnings: { 'url' => [METADATA_FETCH_WARNING] } }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -24,11 +24,10 @@ class PostImportRunner
|
|||||||
|
|
||||||
def run_row row, preview
|
def run_row row, preview
|
||||||
attributes = row.fetch('attributes', { }).transform_keys { _1.to_s.underscore }
|
attributes = row.fetch('attributes', { }).transform_keys { _1.to_s.underscore }
|
||||||
return row.slice('source_row').merge(status: 'skipped') if preview[:skip_reason] == 'existing'
|
|
||||||
|
|
||||||
return { source_row: row['source_row'],
|
return { source_row: row['source_row'],
|
||||||
status: 'failed',
|
status: 'failed',
|
||||||
errors: preview[:validation_errors] } if preview[:validation_errors].present?
|
errors: preview[:validation_errors] } if preview[:validation_errors].present?
|
||||||
|
return row.slice('source_row').merge(status: 'skipped') if preview[:skip_reason] == 'existing'
|
||||||
|
|
||||||
attributes['tags'] = preview[:attributes]['tags']
|
attributes['tags'] = preview[:attributes]['tags']
|
||||||
attributes['url'] = row['url']
|
attributes['url'] = row['url']
|
||||||
@@ -44,7 +43,7 @@ class PostImportRunner
|
|||||||
{ source_row: row['source_row'],
|
{ source_row: row['source_row'],
|
||||||
status: 'failed',
|
status: 'failed',
|
||||||
errors: { tags: ['廃止済みタグは付与できません.'] } }
|
errors: { tags: ['廃止済みタグは付与できません.'] } }
|
||||||
rescue ArgumentError, PostCreator::VideoMsParseError
|
rescue ArgumentError
|
||||||
{ source_row: row['source_row'],
|
{ source_row: row['source_row'],
|
||||||
status: 'failed',
|
status: 'failed',
|
||||||
errors: { base: ['入力値が不正です.'] } }
|
errors: { base: ['入力値が不正です.'] } }
|
||||||
|
|||||||
@@ -2,9 +2,8 @@ class PostMetadataFetcher
|
|||||||
def self.fetch raw_url
|
def self.fetch raw_url
|
||||||
uri, = Preview::UrlSafety.validate(raw_url)
|
uri, = Preview::UrlSafety.validate(raw_url)
|
||||||
response = Preview::HttpFetcher.fetch(
|
response = Preview::HttpFetcher.fetch(
|
||||||
uri.to_s,
|
uri.to_s,
|
||||||
max_bytes: Preview::ThumbnailFetcher::HTML_MAX_BYTES,
|
max_bytes: Preview::ThumbnailFetcher::HTML_MAX_BYTES)
|
||||||
)
|
|
||||||
metadata = Preview::HtmlMetadataExtractor.extract(response)
|
metadata = Preview::HtmlMetadataExtractor.extract(response)
|
||||||
document = Nokogiri::HTML.parse(response.body)
|
document = Nokogiri::HTML.parse(response.body)
|
||||||
content = lambda { |name|
|
content = lambda { |name|
|
||||||
@@ -19,7 +18,8 @@ class PostMetadataFetcher
|
|||||||
created_range = original_created_range(published)
|
created_range = original_created_range(published)
|
||||||
platform_tags = platform_tags(uri)
|
platform_tags = platform_tags(uri)
|
||||||
{ title: metadata[:title],
|
{ title: metadata[:title],
|
||||||
thumbnail_base: Preview::KnownSiteExtractor.thumbnail_url(uri) || metadata[:image_url],
|
thumbnail_base:
|
||||||
|
Preview::KnownSiteExtractor.thumbnail_url(uri) || metadata[:image_url],
|
||||||
original_created_from: created_range&.first&.iso8601,
|
original_created_from: created_range&.first&.iso8601,
|
||||||
original_created_before: created_range&.last&.iso8601,
|
original_created_before: created_range&.last&.iso8601,
|
||||||
duration: duration&.to_f&.then { _1.positive? ? (_1 * 1_000).round : nil },
|
duration: duration&.to_f&.then { _1.positive? ? (_1 * 1_000).round : nil },
|
||||||
@@ -56,5 +56,6 @@ class PostMetadataFetcher
|
|||||||
rescue ArgumentError, TypeError
|
rescue ArgumentError, TypeError
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
private_class_method :platform_tags, :original_created_range
|
private_class_method :platform_tags, :original_created_range
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ class PostUrlNormaliser
|
|||||||
|
|
||||||
uri.host = uri.host.downcase
|
uri.host = uri.host.downcase
|
||||||
uri.path = uri.path.sub(/\/\z/, '') if uri.path.present?
|
uri.path = uri.path.sub(/\/\z/, '') if uri.path.present?
|
||||||
uri.to_s
|
PostUrlSanitisationRule.sanitise(uri.to_s)
|
||||||
rescue URI::InvalidURIError
|
rescue URI::InvalidURIError
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ module Preview
|
|||||||
|
|
||||||
Response = Data.define(:body, :content_type, :url)
|
Response = Data.define(:body, :content_type, :url)
|
||||||
|
|
||||||
def self.fetch(raw_url, max_bytes: DEFAULT_MAX_BYTES, redirects: MAX_REDIRECTS,
|
def self.fetch(raw_url,
|
||||||
|
max_bytes: DEFAULT_MAX_BYTES,
|
||||||
|
redirects: MAX_REDIRECTS,
|
||||||
allowed_hosts: nil)
|
allowed_hosts: nil)
|
||||||
uri, addresses = UrlSafety.validate(raw_url)
|
uri, addresses = UrlSafety.validate(raw_url)
|
||||||
if allowed_hosts && !allowed_hosts.include?(uri.host.downcase)
|
if allowed_hosts && !allowed_hosts.include?(uri.host.downcase)
|
||||||
|
|||||||
@@ -193,6 +193,9 @@ const isValidSkipReason = (
|
|||||||
): value is PostImportSkipReason =>
|
): value is PostImportSkipReason =>
|
||||||
value === 'existing'
|
value === 'existing'
|
||||||
|
|
||||||
|
const isPositiveInteger = (value: unknown): value is number =>
|
||||||
|
Number.isInteger (value) && Number (value) > 0
|
||||||
|
|
||||||
|
|
||||||
const sanitiseRow = (value: unknown): PostImportRow | null => {
|
const sanitiseRow = (value: unknown): PostImportRow | null => {
|
||||||
if (!(isPlainObject (value)))
|
if (!(isPlainObject (value)))
|
||||||
@@ -211,6 +214,12 @@ const sanitiseRow = (value: unknown): PostImportRow | null => {
|
|||||||
return null
|
return null
|
||||||
if (value.skipReason != null && !(isValidSkipReason (value.skipReason)))
|
if (value.skipReason != null && !(isValidSkipReason (value.skipReason)))
|
||||||
return null
|
return null
|
||||||
|
if (value.skipReason === 'existing' && !(isPositiveInteger (value.existingPostId)))
|
||||||
|
return null
|
||||||
|
if (value.skipReason !== 'existing' && value.existingPostId != null)
|
||||||
|
return null
|
||||||
|
if (value.createdPostId != null && !(isPositiveInteger (value.createdPostId)))
|
||||||
|
return null
|
||||||
|
|
||||||
const validationErrors = ensureStringListRecord (value.validationErrors)
|
const validationErrors = ensureStringListRecord (value.validationErrors)
|
||||||
const fieldWarnings = ensureStringListRecord (value.fieldWarnings)
|
const fieldWarnings = ensureStringListRecord (value.fieldWarnings)
|
||||||
@@ -250,13 +259,13 @@ const sanitiseRow = (value: unknown): PostImportRow | null => {
|
|||||||
provenance: value.provenance as Record<string, PostImportOrigin>,
|
provenance: value.provenance as Record<string, PostImportOrigin>,
|
||||||
tagSources: value.tagSources as Record<PostImportOrigin, string> | undefined,
|
tagSources: value.tagSources as Record<PostImportOrigin, string> | undefined,
|
||||||
status: value.status,
|
status: value.status,
|
||||||
skipReason: value.skipReason,
|
skipReason: value.skipReason,
|
||||||
existingPostId:
|
existingPostId:
|
||||||
Number.isInteger (value.existingPostId) ? Number (value.existingPostId) : undefined,
|
isPositiveInteger (value.existingPostId) ? Number (value.existingPostId) : undefined,
|
||||||
metadataUrl: typeof value.metadataUrl === 'string' ? value.metadataUrl : undefined,
|
metadataUrl: typeof value.metadataUrl === 'string' ? value.metadataUrl : undefined,
|
||||||
createdPostId:
|
createdPostId:
|
||||||
Number.isInteger (value.createdPostId) ? Number (value.createdPostId) : undefined,
|
isPositiveInteger (value.createdPostId) ? Number (value.createdPostId) : undefined,
|
||||||
importStatus: value.importStatus }
|
importStatus: value.importStatus }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -605,6 +614,9 @@ export const validateImportSource = (
|
|||||||
const hasSkipReason = (row: PostImportRow): boolean =>
|
const hasSkipReason = (row: PostImportRow): boolean =>
|
||||||
row.skipReason === 'existing'
|
row.skipReason === 'existing'
|
||||||
|
|
||||||
|
const hasValidationErrors = (row: PostImportRow): boolean =>
|
||||||
|
Object.keys (row.validationErrors ?? { }).length > 0
|
||||||
|
|
||||||
|
|
||||||
export const submittableImportRows = (rows: PostImportRow[]): PostImportRow[] =>
|
export const submittableImportRows = (rows: PostImportRow[]): PostImportRow[] =>
|
||||||
rows.filter (row => {
|
rows.filter (row => {
|
||||||
@@ -612,10 +624,12 @@ export const submittableImportRows = (rows: PostImportRow[]): PostImportRow[] =>
|
|||||||
return false
|
return false
|
||||||
if (row.importStatus === 'skipped')
|
if (row.importStatus === 'skipped')
|
||||||
return false
|
return false
|
||||||
if (hasSkipReason (row))
|
if (hasSkipReason (row) && !(hasValidationErrors (row)))
|
||||||
return false
|
return false
|
||||||
if (row.importStatus === 'failed')
|
if (row.importStatus === 'failed')
|
||||||
return false
|
return false
|
||||||
|
if (hasValidationErrors (row))
|
||||||
|
return false
|
||||||
return row.importStatus == null || row.importStatus === 'pending'
|
return row.importStatus == null || row.importStatus === 'pending'
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -624,9 +638,11 @@ export const reviewSummaryCounts = (rows: PostImportRow[]) => ({
|
|||||||
total: rows.length,
|
total: rows.length,
|
||||||
submittable: rows.filter (row =>
|
submittable: rows.filter (row =>
|
||||||
submittableImportRows ([row]).length > 0
|
submittableImportRows ([row]).length > 0
|
||||||
&& Object.keys (row.validationErrors ?? { }).length === 0).length,
|
&& !(hasValidationErrors (row))).length,
|
||||||
invalid: rows.filter (row => Object.keys (row.validationErrors ?? { }).length > 0).length,
|
invalid: rows.filter (row => hasValidationErrors (row)).length,
|
||||||
skipPlanned: rows.filter (row =>
|
skipPlanned: rows.filter (row =>
|
||||||
hasSkipReason (row) && row.importStatus !== 'created').length,
|
hasSkipReason (row)
|
||||||
|
&& !(hasValidationErrors (row))
|
||||||
|
&& row.importStatus !== 'created').length,
|
||||||
created: rows.filter (row => row.importStatus === 'created').length,
|
created: rows.filter (row => row.importStatus === 'created').length,
|
||||||
failed: rows.filter (row => row.importStatus === 'failed').length })
|
failed: rows.filter (row => row.importStatus === 'failed').length })
|
||||||
|
|||||||
@@ -16,9 +16,11 @@ import { canEditContent } from '@/lib/users'
|
|||||||
import { clearPostImportSourceDraft,
|
import { clearPostImportSourceDraft,
|
||||||
loadPostImportSession,
|
loadPostImportSession,
|
||||||
mergeImportResults,
|
mergeImportResults,
|
||||||
|
mergeValidatedImportRows,
|
||||||
retryImportRow,
|
retryImportRow,
|
||||||
savePostImportSession,
|
savePostImportSession,
|
||||||
type PostImportResultRow,
|
type PostImportResultRow,
|
||||||
|
type PostImportRow,
|
||||||
type PostImportSession } from '@/lib/postImportSession'
|
type PostImportSession } from '@/lib/postImportSession'
|
||||||
import Forbidden from '@/pages/Forbidden'
|
import Forbidden from '@/pages/Forbidden'
|
||||||
|
|
||||||
@@ -92,9 +94,31 @@ const PostImportResultPage: FC<Props> = ({ user }) => {
|
|||||||
{
|
{
|
||||||
const pendingRows = retryImportRow (session.rows, sourceRow)
|
const pendingRows = retryImportRow (session.rows, sourceRow)
|
||||||
setSession ({ ...session, rows: pendingRows })
|
setSession ({ ...session, rows: pendingRows })
|
||||||
const target = pendingRows.find (_1 => _1.sourceRow === sourceRow)
|
const validated = await apiPost<{ rows: PostImportRow[] }> ('/posts/import/validate', {
|
||||||
|
rows: pendingRows
|
||||||
|
.filter (row => row.importStatus !== 'created')
|
||||||
|
.map (row => ({
|
||||||
|
sourceRow: row.sourceRow,
|
||||||
|
url: row.url,
|
||||||
|
attributes: row.attributes,
|
||||||
|
provenance: row.provenance,
|
||||||
|
tagSources: row.tagSources,
|
||||||
|
metadataUrl: row.metadataUrl })),
|
||||||
|
changed_row: -1 })
|
||||||
|
const validatedRows = mergeValidatedImportRows (pendingRows, validated.rows).map (row =>
|
||||||
|
row.sourceRow === sourceRow
|
||||||
|
&& Object.keys (row.validationErrors ?? { }).length > 0
|
||||||
|
? { ...row, importStatus: 'failed' as const }
|
||||||
|
: row)
|
||||||
|
setSession (current =>
|
||||||
|
current
|
||||||
|
? { ...current, rows: validatedRows }
|
||||||
|
: current)
|
||||||
|
const target = validatedRows.find (_1 => _1.sourceRow === sourceRow)
|
||||||
if (target == null)
|
if (target == null)
|
||||||
return
|
return
|
||||||
|
if (Object.keys (target.validationErrors ?? { }).length > 0)
|
||||||
|
return
|
||||||
|
|
||||||
const result = await apiPost<{
|
const result = await apiPost<{
|
||||||
created: number
|
created: number
|
||||||
@@ -125,15 +149,15 @@ const PostImportResultPage: FC<Props> = ({ user }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const openRepair = (sourceRow: number) => {
|
const openRepair = (sourceRow: number) => {
|
||||||
|
if (session == null || sessionId == null)
|
||||||
|
return
|
||||||
|
|
||||||
const nextSession = { ...session, repairMode: 'failed' as const }
|
const nextSession = { ...session, repairMode: 'failed' as const }
|
||||||
setSession (nextSession)
|
setSession (nextSession)
|
||||||
if (sessionId)
|
const saved = savePostImportSession (sessionId, nextSession, message =>
|
||||||
{
|
toast ({ title: '取込状態を保存できませんでした', description: message }))
|
||||||
const saved = savePostImportSession (sessionId, nextSession, message =>
|
if (!(saved))
|
||||||
toast ({ title: '取込状態を保存できませんでした', description: message }))
|
return
|
||||||
if (!(saved))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
navigate (`/posts/import/${ sessionId }/review?edit=${ sourceRow }`)
|
navigate (`/posts/import/${ sessionId }/review?edit=${ sourceRow }`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ import type { User } from '@/types'
|
|||||||
|
|
||||||
type Props = { user: User | null }
|
type Props = { user: User | null }
|
||||||
|
|
||||||
type PostFormField =
|
type PostFormField = 'url'
|
||||||
'url'
|
|
||||||
| 'title'
|
| 'title'
|
||||||
| 'tags'
|
| 'tags'
|
||||||
| 'parentPostIds'
|
| 'parentPostIds'
|
||||||
@@ -42,10 +41,8 @@ const PostNewPage: FC<Props> = ({ user }) => {
|
|||||||
const { baseErrors, fieldErrors, clearValidationErrors, applyValidationError } =
|
const { baseErrors, fieldErrors, clearValidationErrors, applyValidationError } =
|
||||||
useValidationErrors<PostFormField> ()
|
useValidationErrors<PostFormField> ()
|
||||||
|
|
||||||
const [originalCreatedBefore, setOriginalCreatedBefore] =
|
const [originalCreatedBefore, setOriginalCreatedBefore] = useState<string | null> (null)
|
||||||
useState<string | null> (null)
|
const [originalCreatedFrom, setOriginalCreatedFrom] = useState<string | null> (null)
|
||||||
const [originalCreatedFrom, setOriginalCreatedFrom] =
|
|
||||||
useState<string | null> (null)
|
|
||||||
const [parentPostIds, setParentPostIds] = useState ('')
|
const [parentPostIds, setParentPostIds] = useState ('')
|
||||||
const [tags, setTags] = useState ('')
|
const [tags, setTags] = useState ('')
|
||||||
const [duration, setDuration] = useState ('')
|
const [duration, setDuration] = useState ('')
|
||||||
@@ -57,11 +54,9 @@ const PostNewPage: FC<Props> = ({ user }) => {
|
|||||||
const [url, setURL] = useState ('')
|
const [url, setURL] = useState ('')
|
||||||
|
|
||||||
const thumbnailPreviewRef = useRef ('')
|
const thumbnailPreviewRef = useRef ('')
|
||||||
const videoFlg =
|
const videoFlg = useMemo (() =>
|
||||||
useMemo (() =>
|
tags.split (/\s+/).some (
|
||||||
tags.split (/\s+/).some (
|
tag => tag.replace (/\[.*\]$/, '') === '動画'), [tags])
|
||||||
tag => tag.replace (/\[.*\]$/, '') === '動画'),
|
|
||||||
[tags])
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
clearValidationErrors ()
|
clearValidationErrors ()
|
||||||
@@ -82,8 +77,7 @@ const PostNewPage: FC<Props> = ({ user }) => {
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await apiPost ('/posts', formData,
|
await apiPost ('/posts', formData, { headers: { 'Content-Type': 'multipart/form-data' } })
|
||||||
{ headers: { 'Content-Type': 'multipart/form-data' } })
|
|
||||||
toast ({ title: '投稿成功!' })
|
toast ({ title: '投稿成功!' })
|
||||||
navigate ('/posts')
|
navigate ('/posts')
|
||||||
}
|
}
|
||||||
@@ -98,8 +92,7 @@ const PostNewPage: FC<Props> = ({ user }) => {
|
|||||||
setTitleLoading (true)
|
setTitleLoading (true)
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
const data = await apiGet<{ title: string }> ('/preview/title',
|
const data = await apiGet<{ title: string }> ('/preview/title', { params: { url } })
|
||||||
{ params: { url } })
|
|
||||||
setTitle (data.title || '')
|
setTitle (data.title || '')
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -116,13 +109,11 @@ const PostNewPage: FC<Props> = ({ user }) => {
|
|||||||
URL.revokeObjectURL (thumbnailPreviewRef.current)
|
URL.revokeObjectURL (thumbnailPreviewRef.current)
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
const data = await apiGet<Blob> ('/preview/thumbnail',
|
const data = await apiGet<Blob> ('/preview/thumbnail', { params: { url },
|
||||||
{ params: { url },
|
responseType: 'blob' })
|
||||||
responseType: 'blob' })
|
|
||||||
const imageURL = URL.createObjectURL (data)
|
const imageURL = URL.createObjectURL (data)
|
||||||
setThumbnailPreview (imageURL)
|
setThumbnailPreview (imageURL)
|
||||||
setThumbnailFile (new File ([data],
|
setThumbnailFile (new File ([data], 'thumbnail.png',
|
||||||
'thumbnail.png',
|
|
||||||
{ type: data.type || 'image/png' }))
|
{ type: data.type || 'image/png' }))
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -176,7 +167,7 @@ const PostNewPage: FC<Props> = ({ user }) => {
|
|||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => void fetchTitle()}
|
onClick={() => void fetchTitle ()}
|
||||||
disabled={!(url) || titleLoading}>
|
disabled={!(url) || titleLoading}>
|
||||||
取得
|
取得
|
||||||
</Button>
|
</Button>
|
||||||
@@ -193,7 +184,7 @@ const PostNewPage: FC<Props> = ({ user }) => {
|
|||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => void fetchThumbnail()}
|
onClick={() => void fetchThumbnail ()}
|
||||||
disabled={!(url) || thumbnailLoading}>
|
disabled={!(url) || thumbnailLoading}>
|
||||||
取得
|
取得
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
新しい課題から参照
ユーザをブロックする