This commit is contained in:
@@ -6,7 +6,7 @@ class TheatreCommentsController < ApplicationController
|
||||
comments = TheatreComment
|
||||
.where(theatre_id: params[:theatre_id])
|
||||
.where('no > ?', no_gt)
|
||||
.order(:no)
|
||||
.order(no: :desc)
|
||||
|
||||
render json: comments.as_json(include: { user: { only: [:id, :name] } })
|
||||
end
|
||||
|
||||
@@ -51,14 +51,14 @@ RSpec.describe 'TheatreComments', type: :request do
|
||||
)
|
||||
end
|
||||
|
||||
it 'theatre_id で絞り込み、no_gt より大きいものを no 昇順で返す' do
|
||||
it 'theatre_id で絞り込み、no_gt より大きいものを no 降順で返す' do
|
||||
get "/theatres/#{theatre.id}/comments", params: { no_gt: 1 }
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response.parsed_body.map { |row| row['no'] }).to eq([2, 3])
|
||||
expect(response.parsed_body.map { |row| row['no'] }).to eq([3, 2])
|
||||
expect(response.parsed_body.map { |row| row['content'] }).to eq([
|
||||
'second comment',
|
||||
'third comment'
|
||||
'third comment',
|
||||
'second comment'
|
||||
])
|
||||
end
|
||||
|
||||
@@ -68,8 +68,8 @@ RSpec.describe 'TheatreComments', type: :request do
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
expect(response.parsed_body.first['user']).to eq({
|
||||
'id' => bob.id,
|
||||
'name' => 'Bob'
|
||||
'id' => alice.id,
|
||||
'name' => 'Alice'
|
||||
})
|
||||
expect(response.parsed_body.first['user'].keys).to contain_exactly('id', 'name')
|
||||
end
|
||||
@@ -78,7 +78,7 @@ RSpec.describe 'TheatreComments', type: :request do
|
||||
get "/theatres/#{theatre.id}/comments", params: { no_gt: -100 }
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response.parsed_body.map { |row| row['no'] }).to eq([1, 2, 3])
|
||||
expect(response.parsed_body.map { |row| row['no'] }).to eq([3, 2, 1])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -117,11 +117,18 @@ RSpec.describe 'Theatres API', type: :request do
|
||||
expect(theatre.host_user_id).to eq(member.id)
|
||||
expect(watch.expires_at).to be_within(1.second).of(30.seconds.from_now)
|
||||
|
||||
expect(json).to eq(
|
||||
expect(json).to include(
|
||||
'host_flg' => true,
|
||||
'post_id' => nil,
|
||||
'post_started_at' => nil
|
||||
)
|
||||
|
||||
expect(json.fetch('watching_users')).to contain_exactly(
|
||||
{
|
||||
'id' => member.id,
|
||||
'name' => 'member user'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -167,11 +174,22 @@ RSpec.describe 'Theatres API', type: :request do
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(theatre.reload.host_user_id).to eq(other_user.id)
|
||||
|
||||
expect(json).to eq(
|
||||
expect(json).to include(
|
||||
'host_flg' => false,
|
||||
'post_id' => nil,
|
||||
'post_started_at' => nil
|
||||
)
|
||||
|
||||
expect(json.fetch('watching_users')).to contain_exactly(
|
||||
{
|
||||
'id' => member.id,
|
||||
'name' => 'member user'
|
||||
},
|
||||
{
|
||||
'id' => other_user.id,
|
||||
'name' => 'other user'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from 'react'
|
||||
import { Helmet } from 'react-helmet-async'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
import ErrorScreen from '@/components/ErrorScreen'
|
||||
import PostEmbed from '@/components/PostEmbed'
|
||||
import PrefetchLink from '@/components/PrefetchLink'
|
||||
import TagDetailSidebar from '@/components/TagDetailSidebar'
|
||||
@@ -46,6 +47,7 @@ export default (() => {
|
||||
const [content, setContent] = useState ('')
|
||||
const [loading, setLoading] = useState (false)
|
||||
const [sending, setSending] = useState (false)
|
||||
const [status, setStatus] = useState (200)
|
||||
const [theatre, setTheatre] = useState<Theatre | null> (null)
|
||||
const [theatreInfo, setTheatreInfo] = useState<TheatreInfo> (INITIAL_THEATRE_INFO)
|
||||
const [post, setPost] = useState<Post | null> (null)
|
||||
@@ -60,7 +62,7 @@ export default (() => {
|
||||
}, [videoLength])
|
||||
|
||||
useEffect (() => {
|
||||
lastCommentNoRef.current = comments.at (-1)?.no ?? 0
|
||||
lastCommentNoRef.current = comments[0]?.no ?? 0
|
||||
}, [comments])
|
||||
|
||||
useEffect (() => {
|
||||
@@ -85,7 +87,7 @@ export default (() => {
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
console.error (error)
|
||||
setStatus ((error as any)?.response.status ?? 200)
|
||||
}
|
||||
}) ()
|
||||
|
||||
@@ -94,12 +96,6 @@ export default (() => {
|
||||
}
|
||||
}, [id])
|
||||
|
||||
useEffect (() => {
|
||||
commentsRef.current?.scrollTo ({
|
||||
top: commentsRef.current.scrollHeight,
|
||||
behavior: 'smooth' })
|
||||
}, [comments])
|
||||
|
||||
useEffect (() => {
|
||||
if (!(id))
|
||||
return
|
||||
@@ -122,7 +118,7 @@ export default (() => {
|
||||
if (!(cancelled) && newComments.length > 0)
|
||||
{
|
||||
lastCommentNoRef.current = newComments[newComments.length - 1].no
|
||||
setComments (prev => [...prev, ...newComments])
|
||||
setComments (prev => [...newComments, ...prev])
|
||||
}
|
||||
|
||||
const currentInfo = theatreInfoRef.current
|
||||
@@ -232,6 +228,9 @@ export default (() => {
|
||||
embedRef.current?.seek (targetTime)
|
||||
}
|
||||
|
||||
if (status >= 400)
|
||||
return <ErrorScreen status={status}/>
|
||||
|
||||
return (
|
||||
<div className="md:flex md:flex-1">
|
||||
<Helmet>
|
||||
@@ -270,7 +269,7 @@ export default (() => {
|
||||
|
||||
<SidebarComponent>
|
||||
<form
|
||||
className="w-full h-auto border border-black dark:border-white rounded"
|
||||
className="w-auto h-auto border border-black dark:border-white rounded mx-2"
|
||||
onSubmit={async e => {
|
||||
e.preventDefault ()
|
||||
|
||||
@@ -282,28 +281,20 @@ export default (() => {
|
||||
setSending (true)
|
||||
await apiPost (`/theatres/${ id }/comments`, { content })
|
||||
setContent ('')
|
||||
commentsRef.current?.scrollTo ({
|
||||
top: commentsRef.current.scrollHeight,
|
||||
behavior: 'smooth' })
|
||||
commentsRef.current?.scrollTo ({ top: 0, behavior: 'smooth' })
|
||||
}
|
||||
finally
|
||||
{
|
||||
setSending (false)
|
||||
}
|
||||
}}>
|
||||
<div className="p-2">
|
||||
現在の同接数:{theatreInfo.watchingUsers.length}
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-hidden overflow-y-scroll text-wrap w-full h-32
|
||||
border rounded">
|
||||
<ul className="list-inside list-disc">
|
||||
{theatreInfo.watchingUsers.map (user => (
|
||||
<li key={user.id} className="px-4 py-1 text-sm">
|
||||
{user.name || `名もなきニジラー(#${ user.id })`}
|
||||
</li>))}
|
||||
</ul>
|
||||
</div>
|
||||
<input
|
||||
className="w-full p-2 border rounded"
|
||||
type="text"
|
||||
placeholder="ここにコメントを入力"
|
||||
value={content}
|
||||
onChange={e => setContent (e.target.value)}
|
||||
disabled={sending}/>
|
||||
|
||||
<div
|
||||
ref={commentsRef}
|
||||
@@ -324,14 +315,23 @@ export default (() => {
|
||||
</div>
|
||||
</div>))}
|
||||
</div>
|
||||
|
||||
<input
|
||||
className="w-full p-2 border rounded"
|
||||
type="text"
|
||||
value={content}
|
||||
onChange={e => setContent (e.target.value)}
|
||||
disabled={sending}/>
|
||||
</form>
|
||||
|
||||
<div className="w-auto h-auto border border-black dark:border-white rounded mx-2 mt-4">
|
||||
<div className="p-2">
|
||||
現在の同接数:{theatreInfo.watchingUsers.length}
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-hidden overflow-y-scroll text-wrap w-full h-32
|
||||
border rounded">
|
||||
<ul className="list-inside list-disc">
|
||||
{theatreInfo.watchingUsers.map (user => (
|
||||
<li key={user.id} className="px-4 py-1 text-sm">
|
||||
{user.name || `名もなきニジラー(#${ user.id })`}
|
||||
</li>))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</SidebarComponent>
|
||||
|
||||
<div className="md:hidden">
|
||||
|
||||
Reference in New Issue
Block a user