|
|
@@ -1,4 +1,4 @@ |
|
|
|
import React, { useEffect, useState } from 'react' |
|
|
|
import React, { useEffect, useState, useRef } from 'react' |
|
|
|
import { Link, useLocation, useParams, useNavigate } from 'react-router-dom' |
|
|
|
import axios from 'axios' |
|
|
|
import { API_BASE_URL, SITE_TITLE } from '../config' |
|
|
@@ -28,21 +28,22 @@ const PostNewPage = () => { |
|
|
|
|
|
|
|
const [title, setTitle] = useState ('') |
|
|
|
const [titleAutoFlg, setTitleAutoFlg] = useState (true) |
|
|
|
const [titleLoading, setTitleLoading] = useState (false) |
|
|
|
const [url, setURL] = useState ('') |
|
|
|
const [thumbnailFile, setThumbnailFile] = useState<File | null> (null) |
|
|
|
const [thumbnailPreview, setThumbnailPreview] = useState<string> ('') |
|
|
|
const [thumbnailAutoFlg, setThumbnailAutoFlg] = useState (true) |
|
|
|
const [thumbnailLoading, setThumbnailLoading] = useState (false) |
|
|
|
const [tags, setTags] = useState<Tag[]> ([]) |
|
|
|
const [tagIds, setTagIds] = useState<number[]> ([]) |
|
|
|
const previousURLRef = useRef ('') |
|
|
|
|
|
|
|
const handleSubmit = () => { |
|
|
|
const formData = new FormData () |
|
|
|
if (title) |
|
|
|
formData.append ('title', title) |
|
|
|
formData.append ('title', title) |
|
|
|
formData.append ('url', url) |
|
|
|
formData.append ('tags', JSON.stringify (tagIds)) |
|
|
|
if (!(thumbnailAutoFlg) && thumbnailFile) |
|
|
|
formData.append ('thumbnail', thumbnailFile) |
|
|
|
formData.append ('thumbnail', thumbnailFile) |
|
|
|
|
|
|
|
void (axios.post (`${ API_BASE_URL }/posts`, formData, { headers: { |
|
|
|
'Content-Type': 'multipart/form-data', |
|
|
@@ -63,6 +64,61 @@ const PostNewPage = () => { |
|
|
|
.catch (() => toast ({ title: 'タグ一覧の取得失敗' }))) |
|
|
|
}, []) |
|
|
|
|
|
|
|
useEffect (() => { |
|
|
|
if (titleAutoFlg && url) |
|
|
|
fetchTitle () |
|
|
|
}, [titleAutoFlg]) |
|
|
|
|
|
|
|
useEffect (() => { |
|
|
|
if (thumbnailAutoFlg && url) |
|
|
|
fetchThumbnail () |
|
|
|
}, [thumbnailAutoFlg]) |
|
|
|
|
|
|
|
const handleURLBlur = () => { |
|
|
|
if (!(url) || url === previousURLRef.current) |
|
|
|
return |
|
|
|
|
|
|
|
if (titleAutoFlg) |
|
|
|
fetchTitle () |
|
|
|
if (thumbnailAutoFlg) |
|
|
|
fetchThumbnail () |
|
|
|
previousURLRef.current = url |
|
|
|
} |
|
|
|
|
|
|
|
const fetchTitle = () => { |
|
|
|
setTitle ('') |
|
|
|
setTitleLoading (true) |
|
|
|
void (axios.get (`${ API_BASE_URL }/preview/title`, { |
|
|
|
params: { url }, |
|
|
|
headers: { 'X-Transfer-Code': localStorage.getItem ('user_code') || '' } }) |
|
|
|
.then (res => { |
|
|
|
setTitle (res.data.title || '') |
|
|
|
setTitleLoading (false) |
|
|
|
}) |
|
|
|
.finally (() => setTitleLoading (false))) |
|
|
|
} |
|
|
|
|
|
|
|
const fetchThumbnail = () => { |
|
|
|
setThumbnailPreview ('') |
|
|
|
setThumbnailFile (null) |
|
|
|
setThumbnailLoading (true) |
|
|
|
if (thumbnailPreview) |
|
|
|
URL.revokeObjectURL (thumbnailPreview) |
|
|
|
void (axios.get (`${ API_BASE_URL }/preview/thumbnail`, { |
|
|
|
params: { url }, |
|
|
|
headers: { 'X-Transfer-Code': localStorage.getItem ('user_code') || '' }, |
|
|
|
responseType: 'blob' }) |
|
|
|
.then (res => { |
|
|
|
const imageURL = URL.createObjectURL (res.data) |
|
|
|
setThumbnailPreview (imageURL) |
|
|
|
setThumbnailFile (new File ([res.data], |
|
|
|
'thumbnail.png', |
|
|
|
{ type: res.data.type || 'image/png' })) |
|
|
|
setThumbnailLoading (false) |
|
|
|
}) |
|
|
|
.finally (() => setThumbnailLoading (false))) |
|
|
|
} |
|
|
|
|
|
|
|
return ( |
|
|
|
<div className="max-w-xl mx-auto p-4 space-y-4"> |
|
|
|
<h1 className="text-2xl font-bold mb-2">広場に投稿を追加する</h1> |
|
|
@@ -74,7 +130,8 @@ const PostNewPage = () => { |
|
|
|
placeholder="例:https://www.nicovideo.jp/watch/..." |
|
|
|
value={url} |
|
|
|
onChange={e => setURL (e.target.value)} |
|
|
|
className="w-full border p-2 rounded" /> |
|
|
|
className="w-full border p-2 rounded" |
|
|
|
onBlur={handleURLBlur} /> |
|
|
|
</div> |
|
|
|
|
|
|
|
{/* タイトル */} |
|
|
@@ -91,6 +148,7 @@ const PostNewPage = () => { |
|
|
|
<input type="text" |
|
|
|
className="w-full border rounded p-2" |
|
|
|
value={title} |
|
|
|
placeholder={titleLoading ? 'Loading...' : ''} |
|
|
|
onChange={e => setTitle (e.target.value)} |
|
|
|
disabled={titleAutoFlg} /> |
|
|
|
</div> |
|
|
@@ -107,27 +165,27 @@ const PostNewPage = () => { |
|
|
|
</label> |
|
|
|
</div> |
|
|
|
{thumbnailAutoFlg |
|
|
|
? ( |
|
|
|
<p className="text-gray-500 text-sm"> |
|
|
|
URL から自動取得されます。 |
|
|
|
</p>) |
|
|
|
? (thumbnailLoading |
|
|
|
? <p className="text-gray-500 text-sm">Loading...</p> |
|
|
|
: !(thumbnailPreview) && ( |
|
|
|
<p className="text-gray-500 text-sm"> |
|
|
|
URL から自動取得されます。 |
|
|
|
</p>)) |
|
|
|
: ( |
|
|
|
<> |
|
|
|
<input type="file" |
|
|
|
accept="image/*" |
|
|
|
onChange={e => { |
|
|
|
const file = e.target.files?.[0] |
|
|
|
if (file) |
|
|
|
{ |
|
|
|
setThumbnailFile (file) |
|
|
|
setThumbnailPreview (URL.createObjectURL (file)) |
|
|
|
} |
|
|
|
}} /> |
|
|
|
{thumbnailPreview && ( |
|
|
|
<img src={thumbnailPreview} |
|
|
|
alt="preview" |
|
|
|
className="mt-2 max-h-48 rounded border" />)} |
|
|
|
</>)} |
|
|
|
<input type="file" |
|
|
|
accept="image/*" |
|
|
|
onChange={e => { |
|
|
|
const file = e.target.files?.[0] |
|
|
|
if (file) |
|
|
|
{ |
|
|
|
setThumbnailFile (file) |
|
|
|
setThumbnailPreview (URL.createObjectURL (file)) |
|
|
|
} |
|
|
|
}} />)} |
|
|
|
{thumbnailPreview && ( |
|
|
|
<img src={thumbnailPreview} |
|
|
|
alt="preview" |
|
|
|
className="mt-2 max-h-48 rounded border" />)} |
|
|
|
</div> |
|
|
|
|
|
|
|
{/* タグ */} |
|
|
@@ -149,7 +207,8 @@ const PostNewPage = () => { |
|
|
|
|
|
|
|
{/* 送信 */} |
|
|
|
<button onClick={handleSubmit} |
|
|
|
className="px-4 py-2 bg-blue-600 text-white rounded"> |
|
|
|
className="px-4 py-2 bg-blue-600 text-white rounded disabled:bg-gray-400" |
|
|
|
disabled={titleLoading || thumbnailLoading}> |
|
|
|
追加 |
|
|
|
</button> |
|
|
|
</div>) |
|
|
|