Compare commits
1 Commits
main
...
feature/302
| Author | SHA1 | Date | |
|---|---|---|---|
| 09763982b5 |
@@ -44,7 +44,8 @@ class PostsController < ApplicationController
|
|||||||
filtered_posts
|
filtered_posts
|
||||||
.joins("LEFT JOIN (#{ pt_max_sql }) pt_max ON pt_max.post_id = posts.id")
|
.joins("LEFT JOIN (#{ pt_max_sql }) pt_max ON pt_max.post_id = posts.id")
|
||||||
.reselect('posts.*', Arel.sql("#{ updated_at_all_sql } AS updated_at_all"))
|
.reselect('posts.*', Arel.sql("#{ updated_at_all_sql } AS updated_at_all"))
|
||||||
.preload(tags: [:deerjikists, :materials, { tag_name: :wiki_page }])
|
.preload(:parents, :children,
|
||||||
|
tags: [:deerjikists, :materials, { tag_name: :wiki_page }])
|
||||||
.with_attached_thumbnail
|
.with_attached_thumbnail
|
||||||
|
|
||||||
q = q.where('posts.url LIKE ?', "%#{ url }%") if url
|
q = q.where('posts.url LIKE ?', "%#{ url }%") if url
|
||||||
@@ -95,7 +96,8 @@ class PostsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def random
|
def random
|
||||||
post = filtered_posts.preload(tags: [:deerjikists, :materials, { tag_name: :wiki_page }])
|
post = filtered_posts.preload(:parents, :childern,
|
||||||
|
tags: [:deerjikists, :materials, { tag_name: :wiki_page }])
|
||||||
.order('RAND()')
|
.order('RAND()')
|
||||||
.first
|
.first
|
||||||
return head :not_found unless post
|
return head :not_found unless post
|
||||||
@@ -104,7 +106,11 @@ class PostsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
post = Post.includes(tags: [:deerjikists, :materials, { tag_name: :wiki_page }]).find_by(id: params[:id])
|
post = Post
|
||||||
|
.preload(:parents, :children)
|
||||||
|
.includes(:parents, :children,
|
||||||
|
tags: [:deerjikists, :materials, { tag_name: :wiki_page }])
|
||||||
|
.find_by(id: params[:id])
|
||||||
return head :not_found unless post
|
return head :not_found unless post
|
||||||
|
|
||||||
render json: PostRepr.base(post, current_user)
|
render json: PostRepr.base(post, current_user)
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
class TheatreCommentsController < ApplicationController
|
class TheatreCommentsController < ApplicationController
|
||||||
def index
|
def index
|
||||||
|
limit = params[:limit].to_i
|
||||||
|
limit = 20 if limit <= 0
|
||||||
|
|
||||||
no_gt = params[:no_gt].to_i
|
no_gt = params[:no_gt].to_i
|
||||||
no_gt = 0 if no_gt.negative?
|
no_gt = 0 if no_gt < 0
|
||||||
|
|
||||||
comments = TheatreComment
|
comments = TheatreComment
|
||||||
.where(theatre_id: params[:theatre_id])
|
.where(theatre_id: params[:theatre_id])
|
||||||
.where('no > ?', no_gt)
|
.where('no > ?', no_gt)
|
||||||
.order(no: :desc)
|
.order(no: :desc)
|
||||||
|
.limit(limit)
|
||||||
|
|
||||||
render json: comments.as_json(include: { user: { only: [:id, :name] } })
|
render json: comments.map {
|
||||||
|
_1.as_json(include: { user: { only: [:id, :name] } })
|
||||||
|
.merge(content: _1.discarded? ? nil : _1.content, deleted: _1.discarded?)
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@@ -29,4 +36,18 @@ class TheatreCommentsController < ApplicationController
|
|||||||
|
|
||||||
render json: comment, status: :created
|
render json: comment, status: :created
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
return head :unauthorized unless current_user
|
||||||
|
|
||||||
|
theatre_id = params[:theatre_id].to_i
|
||||||
|
no = params[:id].to_i
|
||||||
|
|
||||||
|
comment = TheatreComment.find_by(theatre_id:, no:)
|
||||||
|
return head :not_found unless comment
|
||||||
|
|
||||||
|
comment.discard!
|
||||||
|
|
||||||
|
head :no_content
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
class TheatreProgrammesController < ApplicationController
|
||||||
|
def index
|
||||||
|
limit = params[:limit].to_i
|
||||||
|
limit = 100 if limit <= 0
|
||||||
|
|
||||||
|
position_gt = params[:position_gt].to_i
|
||||||
|
position_gt = 0 if position_gt < 0
|
||||||
|
|
||||||
|
programmes = TheatreProgramme
|
||||||
|
.where(theatre_id: params[:theatre_id])
|
||||||
|
.where('position > ?', position_gt)
|
||||||
|
.order(position: :desc).limit(100)
|
||||||
|
.limit(limit)
|
||||||
|
|
||||||
|
render json: programmes.as_json(include: { post: PostRepr::BASE })
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -43,11 +43,14 @@ class TheatresController < ApplicationController
|
|||||||
return head :not_found unless theatre
|
return head :not_found unless theatre
|
||||||
return head :forbidden if theatre.host_user != current_user
|
return head :forbidden if theatre.host_user != current_user
|
||||||
|
|
||||||
post = Post.where("url LIKE '%nicovideo.jp%'")
|
ApplicationRecord.transaction do
|
||||||
.or(Post.where("url LIKE '%youtube.com%'"))
|
post = Post.where("url LIKE '%nicovideo.jp%'")
|
||||||
.order('RAND()')
|
.order('RAND()')
|
||||||
.first
|
.first
|
||||||
theatre.update!(current_post: post, current_post_started_at: Time.current)
|
theatre.update!(current_post: post, current_post_started_at: Time.current)
|
||||||
|
position = (theatre.programmes.maximum(:position) || 0) + 1
|
||||||
|
theatre.programmes.create!(position:, post:)
|
||||||
|
end
|
||||||
|
|
||||||
head :no_content
|
head :no_content
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ class Tag < ApplicationRecord
|
|||||||
|
|
||||||
def material_id = materials.first&.id
|
def material_id = materials.first&.id
|
||||||
|
|
||||||
def has_deerjikists = deerjikists.present?
|
def has_deerjikists = deerjikists.exists?
|
||||||
|
|
||||||
def self.tagme = find_or_create_by_tag_name!('タグ希望', category: :meta)
|
def self.tagme = find_or_create_by_tag_name!('タグ希望', category: :meta)
|
||||||
def self.bot = find_or_create_by_tag_name!('bot操作', category: :meta)
|
def self.bot = find_or_create_by_tag_name!('bot操作', category: :meta)
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ class Theatre < ApplicationRecord
|
|||||||
class_name: 'TheatreWatchingUser', inverse_of: :theatre
|
class_name: 'TheatreWatchingUser', inverse_of: :theatre
|
||||||
has_many :watching_users, through: :active_theatre_watching_users, source: :user
|
has_many :watching_users, through: :active_theatre_watching_users, source: :user
|
||||||
|
|
||||||
|
has_many :programmes, class_name: 'TheatreProgramme'
|
||||||
|
|
||||||
belongs_to :host_user, class_name: 'User', optional: true
|
belongs_to :host_user, class_name: 'User', optional: true
|
||||||
belongs_to :current_post, class_name: 'Post', optional: true
|
belongs_to :current_post, class_name: 'Post', optional: true
|
||||||
belongs_to :created_by_user, class_name: 'User'
|
belongs_to :created_by_user, class_name: 'User'
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
class TheatreProgramme < ApplicationRecord
|
||||||
|
self.primary_key = :theatre_id, :position
|
||||||
|
|
||||||
|
belongs_to :theatre
|
||||||
|
belongs_to :post
|
||||||
|
end
|
||||||
@@ -87,7 +87,8 @@ Rails.application.routes.draw do
|
|||||||
patch :next_post
|
patch :next_post
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :comments, controller: :theatre_comments, only: [:index, :create]
|
resources :comments, controller: :theatre_comments, only: [:index, :create, :destroy]
|
||||||
|
resources :programmes, controller: :theatre_programmes, only: [:index]
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :materials, only: [:index, :show, :create, :update, :destroy]
|
resources :materials, only: [:index, :show, :create, :update, :destroy]
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
class CreateTheatreProgrammes < ActiveRecord::Migration[8.0]
|
||||||
|
def change
|
||||||
|
create_table :theatre_programmes, primary_key: [:theatre_id, :position] do |t|
|
||||||
|
t.references :theatre, null: false, foreign_key: true
|
||||||
|
t.integer :position, null: false
|
||||||
|
t.references :post, null: false, foreign_key: true
|
||||||
|
t.datetime :created_at, null: false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Generated
+13
-1
@@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[8.0].define(version: 2026_05_07_213300) do
|
ActiveRecord::Schema[8.0].define(version: 2026_05_14_221900) do
|
||||||
create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||||
t.string "name", null: false
|
t.string "name", null: false
|
||||||
t.string "record_type", null: false
|
t.string "record_type", null: false
|
||||||
@@ -283,6 +283,15 @@ ActiveRecord::Schema[8.0].define(version: 2026_05_07_213300) do
|
|||||||
t.index ["user_id"], name: "index_theatre_comments_on_user_id"
|
t.index ["user_id"], name: "index_theatre_comments_on_user_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "theatre_programmes", primary_key: ["theatre_id", "position"], charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||||
|
t.bigint "theatre_id", null: false
|
||||||
|
t.integer "position", null: false
|
||||||
|
t.bigint "post_id", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.index ["post_id"], name: "index_theatre_programmes_on_post_id"
|
||||||
|
t.index ["theatre_id"], name: "index_theatre_programmes_on_theatre_id"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "theatre_watching_users", primary_key: ["theatre_id", "user_id"], charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
create_table "theatre_watching_users", primary_key: ["theatre_id", "user_id"], charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||||
t.bigint "theatre_id", null: false
|
t.bigint "theatre_id", null: false
|
||||||
t.bigint "user_id", null: false
|
t.bigint "user_id", null: false
|
||||||
@@ -290,6 +299,7 @@ ActiveRecord::Schema[8.0].define(version: 2026_05_07_213300) do
|
|||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.index ["expires_at"], name: "index_theatre_watching_users_on_expires_at"
|
t.index ["expires_at"], name: "index_theatre_watching_users_on_expires_at"
|
||||||
|
t.index ["theatre_id", "expires_at"], name: "idx_on_theatre_id_skip_expires_at_4c8de1dd42"
|
||||||
t.index ["theatre_id", "expires_at"], name: "index_theatre_watching_users_on_theatre_id_and_expires_at"
|
t.index ["theatre_id", "expires_at"], name: "index_theatre_watching_users_on_theatre_id_and_expires_at"
|
||||||
t.index ["theatre_id"], name: "index_theatre_watching_users_on_theatre_id"
|
t.index ["theatre_id"], name: "index_theatre_watching_users_on_theatre_id"
|
||||||
t.index ["user_id"], name: "index_theatre_watching_users_on_user_id"
|
t.index ["user_id"], name: "index_theatre_watching_users_on_user_id"
|
||||||
@@ -464,6 +474,8 @@ ActiveRecord::Schema[8.0].define(version: 2026_05_07_213300) do
|
|||||||
add_foreign_key "tags", "tag_names"
|
add_foreign_key "tags", "tag_names"
|
||||||
add_foreign_key "theatre_comments", "theatres"
|
add_foreign_key "theatre_comments", "theatres"
|
||||||
add_foreign_key "theatre_comments", "users"
|
add_foreign_key "theatre_comments", "users"
|
||||||
|
add_foreign_key "theatre_programmes", "posts"
|
||||||
|
add_foreign_key "theatre_programmes", "theatres"
|
||||||
add_foreign_key "theatre_watching_users", "theatres"
|
add_foreign_key "theatre_watching_users", "theatres"
|
||||||
add_foreign_key "theatre_watching_users", "users"
|
add_foreign_key "theatre_watching_users", "users"
|
||||||
add_foreign_key "theatres", "posts", column: "current_post_id"
|
add_foreign_key "theatres", "posts", column: "current_post_id"
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ const RouteTransitionWrapper = ({ user, setUser }: {
|
|||||||
<Route path="/tags/:id/deerjikists" element={<DeerjikistDetailPage/>}/>
|
<Route path="/tags/:id/deerjikists" element={<DeerjikistDetailPage/>}/>
|
||||||
<Route path="/tags/nico" element={<NicoTagListPage user={user}/>}/>
|
<Route path="/tags/nico" element={<NicoTagListPage user={user}/>}/>
|
||||||
<Route path="/tags/changes" element={<TagHistoryPage/>}/>
|
<Route path="/tags/changes" element={<TagHistoryPage/>}/>
|
||||||
<Route path="/theatres/:id" element={<TheatreDetailPage/>}/>
|
<Route path="/theatres/:id" element={<TheatreDetailPage user={user}/>}/>
|
||||||
<Route path="/materials" element={<MaterialBasePage/>}>
|
<Route path="/materials" element={<MaterialBasePage/>}>
|
||||||
<Route index element={<MaterialListPage/>}/>
|
<Route index element={<MaterialListPage/>}/>
|
||||||
<Route path="new" element={<MaterialNewPage/>}/>
|
<Route path="new" element={<MaterialNewPage/>}/>
|
||||||
|
|||||||
@@ -55,12 +55,6 @@ export const menuOutline = ({ tag, wikiId, user, pathName }: {
|
|||||||
{ name: '追加', to: '/materials/new' },
|
{ name: '追加', to: '/materials/new' },
|
||||||
{ name: '全体履歴', to: '/materials/changes', visible: false },
|
{ name: '全体履歴', to: '/materials/changes', visible: false },
|
||||||
{ name: 'ヘルプ', to: '/wiki/ヘルプ:素材集' }] },
|
{ name: 'ヘルプ', to: '/wiki/ヘルプ:素材集' }] },
|
||||||
{ name: '上映会', to: '/theatres/1', base: '/theatres', subMenu: [
|
|
||||||
{ name: <>第 1 会場</>, to: '/theatres/1' },
|
|
||||||
{ name: 'CyTube', to: '//cytube.mm428.net/r/deernijika' },
|
|
||||||
{ name: <>ニジカ放送局第 1 チャンネル</>,
|
|
||||||
to: '//www.youtube.com/watch?v=DCU3hL4Uu6A' },
|
|
||||||
{ name: 'ヘルプ', to: '/wiki/ヘルプ:上映会' }] },
|
|
||||||
{ name: 'Wiki', to: '/wiki/ヘルプ:ホーム', base: '/wiki', subMenu: [
|
{ name: 'Wiki', to: '/wiki/ヘルプ:ホーム', base: '/wiki', subMenu: [
|
||||||
{ name: '検索', to: '/wiki' },
|
{ name: '検索', to: '/wiki' },
|
||||||
{ name: '新規', to: '/wiki/new' },
|
{ name: '新規', to: '/wiki/new' },
|
||||||
@@ -71,6 +65,8 @@ export const menuOutline = ({ tag, wikiId, user, pathName }: {
|
|||||||
visible: wikiPageFlg },
|
visible: wikiPageFlg },
|
||||||
{ name: '履歴', to: `/wiki/changes?id=${ wikiId }`, visible: wikiPageFlg },
|
{ name: '履歴', to: `/wiki/changes?id=${ wikiId }`, visible: wikiPageFlg },
|
||||||
{ name: '編輯', to: `/wiki/${ wikiId || wikiTitle }/edit`, visible: wikiPageFlg }] },
|
{ name: '編輯', to: `/wiki/${ wikiId || wikiTitle }/edit`, visible: wikiPageFlg }] },
|
||||||
|
{ name: 'おたのしみ', visible: false, subMenu: [
|
||||||
|
{ name: '上映会 (β)', to: '/theatres/1' }] },
|
||||||
{ name: 'ユーザ', to: '/users/settings', visible: false, subMenu: [
|
{ name: 'ユーザ', to: '/users/settings', visible: false, subMenu: [
|
||||||
{ name: '一覧', to: '/users', visible: false },
|
{ name: '一覧', to: '/users', visible: false },
|
||||||
{ name: 'お前', to: `/users/${ user?.id }`, visible: false },
|
{ name: 'お前', to: `/users/${ user?.id }`, visible: false },
|
||||||
@@ -267,7 +263,7 @@ const TopNav: FC<Props> = ({ user }) => {
|
|||||||
: { initial: { x: 40, y: -40, opacity: 0 },
|
: { initial: { x: 40, y: -40, opacity: 0 },
|
||||||
animate: { x: 0, y: 0, opacity: 1 },
|
animate: { x: 0, y: 0, opacity: 1 },
|
||||||
exit: { x: 40, y: -40, opacity: 0 } })}
|
exit: { x: 40, y: -40, opacity: 0 } })}
|
||||||
className="z-10 h-full flex items-center px-3 font-bold w-24">
|
className="z-10 h-full flex items-center px-3 font-bold w-28">
|
||||||
<h2>{item.name}</h2>
|
<h2>{item.name}</h2>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
{item.subMenu
|
{item.subMenu
|
||||||
|
|||||||
@@ -6,20 +6,24 @@ import ErrorScreen from '@/components/ErrorScreen'
|
|||||||
import PostEmbed from '@/components/PostEmbed'
|
import PostEmbed from '@/components/PostEmbed'
|
||||||
import PrefetchLink from '@/components/PrefetchLink'
|
import PrefetchLink from '@/components/PrefetchLink'
|
||||||
import TagDetailSidebar from '@/components/TagDetailSidebar'
|
import TagDetailSidebar from '@/components/TagDetailSidebar'
|
||||||
|
import SectionTitle from '@/components/common/SectionTitle'
|
||||||
|
import { useDialogue } from '@/components/dialogues/DialogueProvider'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
import MainArea from '@/components/layout/MainArea'
|
import MainArea from '@/components/layout/MainArea'
|
||||||
import SidebarComponent from '@/components/layout/SidebarComponent'
|
import SidebarComponent from '@/components/layout/SidebarComponent'
|
||||||
import { SITE_TITLE } from '@/config'
|
import { SITE_TITLE } from '@/config'
|
||||||
import { apiGet, apiPatch, apiPost, apiPut, isApiError } from '@/lib/api'
|
import { apiGet, apiDelete, apiPatch, apiPost, apiPut, isApiError } from '@/lib/api'
|
||||||
import { fetchPost } from '@/lib/posts'
|
import { fetchPost } from '@/lib/posts'
|
||||||
import { dateString } from '@/lib/utils'
|
import { dateString } from '@/lib/utils'
|
||||||
|
|
||||||
import type { FC } from 'react'
|
import type { FC, User } from 'react'
|
||||||
|
|
||||||
import type { NiconicoMetadata,
|
import type { NiconicoMetadata,
|
||||||
NiconicoViewerHandle,
|
NiconicoViewerHandle,
|
||||||
Post,
|
Post,
|
||||||
Theatre,
|
Theatre,
|
||||||
TheatreComment } from '@/types'
|
TheatreComment,
|
||||||
|
TheatreProgramme } from '@/types'
|
||||||
|
|
||||||
type TheatreInfo = {
|
type TheatreInfo = {
|
||||||
hostFlg: boolean
|
hostFlg: boolean
|
||||||
@@ -34,9 +38,36 @@ const INITIAL_THEATRE_INFO =
|
|||||||
watchingUsers: [] as { id: number; name: string }[] } as const
|
watchingUsers: [] as { id: number; name: string }[] } as const
|
||||||
|
|
||||||
|
|
||||||
const TheatreDetailPage: FC = () => {
|
const commentBox: ReactNode[] = (comment: TheatreComment) =>
|
||||||
|
[(
|
||||||
|
<div key={`${ comment.no }-content`} className="w-full">
|
||||||
|
{comment.deleted
|
||||||
|
? (
|
||||||
|
<span className="text-sm font-bold">
|
||||||
|
削除されました.
|
||||||
|
</span>)
|
||||||
|
: comment.content}
|
||||||
|
</div>),
|
||||||
|
(
|
||||||
|
<div key={`${ comment.no }-user`} className="w-full text-sm text-right">
|
||||||
|
by {comment.user
|
||||||
|
? (comment.user.name || `名もなきニジラー(#${ comment.user.id })`)
|
||||||
|
: '運営'}
|
||||||
|
</div>),
|
||||||
|
(
|
||||||
|
<div key={`${ comment.no }-createdAt`} className="w-full text-sm text-right">
|
||||||
|
{dateString (comment.createdAt)}
|
||||||
|
</div>)]
|
||||||
|
|
||||||
|
|
||||||
|
type Props = { user: User }
|
||||||
|
|
||||||
|
|
||||||
|
const TheatreDetailPage: FC<Props> = ({ user }: Props) => {
|
||||||
const { id } = useParams ()
|
const { id } = useParams ()
|
||||||
|
|
||||||
|
const dialogue = useDialogue ()
|
||||||
|
|
||||||
const commentsRef = useRef<HTMLDivElement> (null)
|
const commentsRef = useRef<HTMLDivElement> (null)
|
||||||
const embedRef = useRef<NiconicoViewerHandle> (null)
|
const embedRef = useRef<NiconicoViewerHandle> (null)
|
||||||
const loadingRef = useRef (false)
|
const loadingRef = useRef (false)
|
||||||
@@ -52,6 +83,7 @@ const TheatreDetailPage: FC = () => {
|
|||||||
const [theatre, setTheatre] = useState<Theatre | null> (null)
|
const [theatre, setTheatre] = useState<Theatre | null> (null)
|
||||||
const [theatreInfo, setTheatreInfo] = useState<TheatreInfo> (INITIAL_THEATRE_INFO)
|
const [theatreInfo, setTheatreInfo] = useState<TheatreInfo> (INITIAL_THEATRE_INFO)
|
||||||
const [post, setPost] = useState<Post | null> (null)
|
const [post, setPost] = useState<Post | null> (null)
|
||||||
|
const [programmes, setProgrammes] = useState<TheatreProgramme[]> ([])
|
||||||
const [videoLength, setVideoLength] = useState (0)
|
const [videoLength, setVideoLength] = useState (0)
|
||||||
|
|
||||||
useEffect (() => {
|
useEffect (() => {
|
||||||
@@ -118,7 +150,7 @@ const TheatreDetailPage: FC = () => {
|
|||||||
{
|
{
|
||||||
const newComments = await apiGet<TheatreComment[]> (
|
const newComments = await apiGet<TheatreComment[]> (
|
||||||
`/theatres/${ id }/comments`,
|
`/theatres/${ id }/comments`,
|
||||||
{ params: { no_gt: lastCommentNoRef.current } })
|
{ params: { no_gt: lastCommentNoRef.current, limit: '20' } })
|
||||||
|
|
||||||
if (!(cancelled) && newComments.length > 0)
|
if (!(cancelled) && newComments.length > 0)
|
||||||
{
|
{
|
||||||
@@ -214,10 +246,24 @@ const TheatreDetailPage: FC = () => {
|
|||||||
}
|
}
|
||||||
}) ()
|
}) ()
|
||||||
|
|
||||||
|
void (async () => {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const data = await apiGet<TheatreProgramme[]> (
|
||||||
|
`/theatres/${ id }/programmes`, { params: { limit: '100' } })
|
||||||
|
if (!(cancelled))
|
||||||
|
setProgrammes (data)
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
console.error (e)
|
||||||
|
}
|
||||||
|
}) ()
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
cancelled = true
|
cancelled = true
|
||||||
}
|
}
|
||||||
}, [theatreInfo.postId])
|
}, [id, theatreInfo.postId])
|
||||||
|
|
||||||
const syncPlayback = (meta: NiconicoMetadata) => {
|
const syncPlayback = (meta: NiconicoMetadata) => {
|
||||||
if (!(theatreInfo.postStartedAt))
|
if (!(theatreInfo.postStartedAt))
|
||||||
@@ -233,12 +279,30 @@ const TheatreDetailPage: FC = () => {
|
|||||||
embedRef.current?.seek (targetTime)
|
embedRef.current?.seek (targetTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDelete = async (commentNo: number) => {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await apiDelete (`/theatres/${ id }/comments/${ commentNo }`)
|
||||||
|
setComments (prev => {
|
||||||
|
const rtn = [...prev]
|
||||||
|
const idx = rtn.findIndex (x => x.no === commentNo)
|
||||||
|
rtn[idx] = { ...rtn[idx], deleted: true }
|
||||||
|
return rtn
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (status >= 400)
|
if (status >= 400)
|
||||||
return <ErrorScreen status={status}/>
|
return <ErrorScreen status={status}/>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="md:flex md:flex-1 overflow-y-auto md:overflow-y-hidden">
|
<div className="md:flex md:flex-1 overflow-y-auto md:overflow-y-hidden">
|
||||||
<Helmet>
|
<Helmet>
|
||||||
|
<meta name="robots" content="noindex"/>
|
||||||
{theatre && (
|
{theatre && (
|
||||||
<title>
|
<title>
|
||||||
{'上映会場'
|
{'上映会場'
|
||||||
@@ -270,6 +334,22 @@ const TheatreDetailPage: FC = () => {
|
|||||||
</PrefetchLink>
|
</PrefetchLink>
|
||||||
</div>
|
</div>
|
||||||
</>) : 'Loading...'}
|
</>) : 'Loading...'}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<SectionTitle>
|
||||||
|
上映履歴
|
||||||
|
</SectionTitle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{programmes.map ((programme, i) => (
|
||||||
|
<div key={i}>
|
||||||
|
<PrefetchLink to={`/posts/${ programme.post.id }`}>
|
||||||
|
{programme.post.title}
|
||||||
|
</PrefetchLink>
|
||||||
|
({dateString (programme.createdAt)})
|
||||||
|
</div>))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</MainArea>
|
</MainArea>
|
||||||
|
|
||||||
<SidebarComponent>
|
<SidebarComponent>
|
||||||
@@ -306,18 +386,36 @@ const TheatreDetailPage: FC = () => {
|
|||||||
className="overflow-x-hidden overflow-y-scroll text-wrap w-full
|
className="overflow-x-hidden overflow-y-scroll text-wrap w-full
|
||||||
h-[32vh] md:h-[64vh] border rounded">
|
h-[32vh] md:h-[64vh] border rounded">
|
||||||
{comments.map (comment => (
|
{comments.map (comment => (
|
||||||
<div key={comment.no} className="p-2">
|
<div
|
||||||
<div className="w-full">
|
key={comment.no}
|
||||||
{comment.content}
|
className="p-2 group relative rounded py-1 hover:bg-gray-100
|
||||||
</div>
|
dark:hover:bg-gray-800">
|
||||||
<div className="w-full text-sm text-right">
|
{(user && comment.user?.id === user.id && !(comment.deleted)) && (
|
||||||
by {comment.user
|
<button
|
||||||
? (comment.user.name || `名もなきニジラー(#${ comment.user.id })`)
|
type="button"
|
||||||
: '運営'}
|
className="absolute left-1 top-1 hidden rounded text-md text-red-600
|
||||||
</div>
|
hover:bg-red-100 group-hover:inline-block
|
||||||
<div className="w-full text-sm text-right">
|
dark:text-red-300 dark:hover:bg-red-950"
|
||||||
{dateString (comment.createdAt)}
|
aria-label="コメントを削除"
|
||||||
</div>
|
onClick={async e => {
|
||||||
|
e.stopPropagation ()
|
||||||
|
|
||||||
|
if (!(await dialogue.confirm ({
|
||||||
|
title: 'このコメントを削除しますか?',
|
||||||
|
description: (
|
||||||
|
<div className="border border-black dark:border-white rounded
|
||||||
|
my-3 p-2 w-64">
|
||||||
|
{commentBox (comment)}
|
||||||
|
</div>),
|
||||||
|
confirmText: '削除',
|
||||||
|
variant: 'danger' })))
|
||||||
|
return
|
||||||
|
|
||||||
|
await handleDelete (comment.no)
|
||||||
|
}}>
|
||||||
|
×
|
||||||
|
</button>)}
|
||||||
|
{commentBox (comment)}
|
||||||
</div>))}
|
</div>))}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -345,4 +443,5 @@ const TheatreDetailPage: FC = () => {
|
|||||||
</div>)
|
</div>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default TheatreDetailPage
|
export default TheatreDetailPage
|
||||||
|
|||||||
+18
-5
@@ -210,11 +210,24 @@ export type Theatre = {
|
|||||||
createdAt: string
|
createdAt: string
|
||||||
updatedAt: string }
|
updatedAt: string }
|
||||||
|
|
||||||
export type TheatreComment = {
|
export type TheatreComment =
|
||||||
theatreId: number,
|
| { theatreId: number
|
||||||
no: number,
|
no: number
|
||||||
user: { id: number, name: string } | null
|
deteled: false
|
||||||
content: string
|
user: { id: number, name: string } | null
|
||||||
|
content: string
|
||||||
|
createdAt: string }
|
||||||
|
| { theatreId: number
|
||||||
|
no: number
|
||||||
|
deleted: true
|
||||||
|
user: { id: number, name: string } | null
|
||||||
|
content null,
|
||||||
|
createdAt: string }
|
||||||
|
|
||||||
|
export type TheatreProgramme = {
|
||||||
|
theatreId: number
|
||||||
|
position: number
|
||||||
|
post: Post
|
||||||
createdAt: string }
|
createdAt: string }
|
||||||
|
|
||||||
export type User = {
|
export type User = {
|
||||||
|
|||||||
Reference in New Issue
Block a user