Browse Source

#46

feature/046
みてるぞ 5 days ago
parent
commit
fa8eb66535
9 changed files with 128 additions and 24 deletions
  1. +1
    -1
      backend/app/controllers/posts_controller.rb
  2. +13
    -5
      backend/app/models/post.rb
  3. +1
    -1
      backend/app/representations/post_repr.rb
  4. +32
    -11
      frontend/src/components/PostEditForm.tsx
  5. +5
    -2
      frontend/src/components/PostList.tsx
  6. +29
    -3
      frontend/src/pages/posts/PostDetailPage.tsx
  7. +27
    -0
      frontend/src/pages/posts/PostHistoryPage.tsx
  8. +12
    -0
      frontend/src/pages/posts/PostNewPage.tsx
  9. +8
    -1
      frontend/src/types.ts

+ 1
- 1
backend/app/controllers/posts_controller.rb View File

@@ -109,7 +109,7 @@ class PostsController < ApplicationController

render json: PostRepr.base(post, current_user)
.merge(tags: build_tag_tree_for(post.tags),
related: post.related(limit: 20))
related: PostRepr.many(post.related(limit: 20)))
end

def create


+ 13
- 5
backend/app/models/post.rb View File

@@ -37,18 +37,26 @@ class Post < ApplicationRecord

def parent_posts = parents

def child_posts = children

def sibling_posts
parent_post_ids = parent_posts.order(:id).pluck(:id)

parent_post_ids.to_h { [_1, PostImplication.where(parent_post: _1).map(&:post)] }
end

def as_json options = { }
super(options).merge({ thumbnail: thumbnail.attached? ?
Rails.application.routes.url_helpers.rails_blob_url(
thumbnail, only_path: false) :
nil })
super(options).merge(thumbnail: thumbnail.attached? ?
Rails.application.routes.url_helpers.rails_blob_url(
thumbnail, only_path: false) :
nil)
rescue
super(options).merge(thumbnail: nil)
end

def snapshot_tag_names = tags.joins(:tag_name).order('tag_names.name').pluck('tag_names.name')

def snapshot_parent_post_ids = parents.order(:parent_post_id).pulck(:parent_post_id)
def snapshot_parent_post_ids = parents.order(:parent_post_id).pluck(:parent_post_id)

def related limit: nil
ids = post_similarities.order(cos: :desc)


+ 1
- 1
backend/app/representations/post_repr.rb View File

@@ -3,7 +3,7 @@

module PostRepr
BASE = { include: { tags: TagRepr::BASE, uploaded_user: UserRepr::BASE },
methods: [:parent_posts] }.freeze
methods: [:parent_posts, :child_posts, :sibling_posts] }.freeze

module_function



+ 32
- 11
frontend/src/components/PostEditForm.tsx View File

@@ -4,6 +4,7 @@ import PostFormTagsArea from '@/components/PostFormTagsArea'
import PostOriginalCreatedTimeField from '@/components/PostOriginalCreatedTimeField'
import Label from '@/components/common/Label'
import { Button } from '@/components/ui/button'
import { toast } from '@/components/ui/use-toast'
import { apiPut } from '@/lib/api'

import type { FC } from 'react'
@@ -35,20 +36,30 @@ export default (({ post, onSave }: Props) => {
useState<string | null> (post.originalCreatedBefore)
const [originalCreatedFrom, setOriginalCreatedFrom] =
useState<string | null> (post.originalCreatedFrom)
const [title, setTitle] = useState (post.title)
const [parentPostIds, setParentPostIds] = useState (post.parentPosts!.map (p => p.id).join (' '))
const [tags, setTags] = useState<string> ('')
const [title, setTitle] = useState (post.title)

const handleSubmit = async () => {
const data = await apiPut<Post> (
`/posts/${ post.id }`,
{ title, tags, original_created_from: originalCreatedFrom,
original_created_before: originalCreatedBefore },
{ headers: { 'Content-Type': 'multipart/form-data' } })
onSave ({ ...post,
title: data.title,
tags: data.tags,
originalCreatedFrom: data.originalCreatedFrom,
originalCreatedBefore: data.originalCreatedBefore } as Post)
try
{
const data = await apiPut<Post> (
`/posts/${ post.id }`,
{ title, tags, parent_post_ids: parentPostIds,
original_created_from: originalCreatedFrom,
original_created_before: originalCreatedBefore },
{ headers: { 'Content-Type': 'multipart/form-data' } })
onSave ({ ...post,
title: data.title,
tags: data.tags,
originalCreatedFrom: data.originalCreatedFrom,
originalCreatedBefore: data.originalCreatedBefore } as Post)
toast ({ description: '更新しました.' })
}
catch
{
toast ({ description: '更新はできなかったよ……' })
}
}

useEffect (() => {
@@ -66,6 +77,16 @@ export default (({ post, onSave }: Props) => {
onChange={ev => setTitle (ev.target.value)}/>
</div>

{/* 親投稿 */}
<div>
<Label>親投稿</Label>
<input
type="text"
value={parentPostIds}
onChange={e => setParentPostIds (e.target.value)}
className="w-full border p-2 rounded"/>
</div>

{/* タグ */}
<PostFormTagsArea tags={tags} setTags={setTags}/>



+ 5
- 2
frontend/src/components/PostList.tsx View File

@@ -3,6 +3,7 @@ import { useRef } from 'react'
import { useLocation } from 'react-router-dom'

import PrefetchLink from '@/components/PrefetchLink'
import { cn } from '@/lib/utils'
import { useSharedTransitionStore } from '@/stores/sharedTransitionStore'

import type { FC, MouseEvent } from 'react'
@@ -39,8 +40,10 @@ export default (({ posts, onClick }: Props) => {
<motion.div
ref={cardRef}
layoutId={layoutId}
className="w-full h-full overflow-hidden rounded-xl shadow
transform-gpu will-change-transform"
className={cn ('w-full h-full overflow-hidden rounded-xl shadow',
'transform-gpu will-change-transform',
(post.childPosts ?? []).length > 0 && 'border-4 border-green-500',
(post.parentPosts ?? []).length > 0 && 'border-4 border-yellow-500')}
whileHover={{ scale: 1.02 }}
onLayoutAnimationStart={() => {
if (!(cardRef.current))


+ 29
- 3
frontend/src/pages/posts/PostDetailPage.tsx View File

@@ -21,7 +21,7 @@ import ServiceUnavailable from '@/pages/ServiceUnavailable'

import type { FC } from 'react'

import type { NiconicoViewerHandle, User } from '@/types'
import type { NiconicoViewerHandle, Post, User } from '@/types'

type Props = { user: User | null }

@@ -108,7 +108,34 @@ export default (({ user }: Props) => {
{post
? (
<>
{/* TODO: 親投稿リスト */}
{(post.childPosts ?? []).length > 0 && (
<div className="mb-4 bg-green-200 dark:bg-green-800 text-sm p-2 rounded-md">
<p>この投稿には {post.childPosts!.length} 件の子投稿があります.</p>
<PostList posts={[{ ...post, childPosts: [{ } as Post] },
...post.childPosts!.map (p => ({
...p, parentPosts: [{ } as Post] }))]}/>
</div>
)}
{(post.parentPosts ?? []).map (pp => {
const siblings = post.siblingPosts?.[String (pp.id) as `${ number }`]
if (!(siblings))
return

return (
<div
key={pp.id}
className="mb-4 bg-yellow-200 dark:bg-yellow-800 text-sm p-2 rounded-md">
<p>
この投稿には 1 件の親投稿{
siblings.length > 1
&& `と ${ siblings.length - 1 } 件の姉妹投稿`}があります.
</p>
<PostList posts={[{ ...pp, childPosts: [{ } as Post] },
...siblings.map (p => ({
...p, parentPosts: [{ } as Post] }))]}/>
</div>)
})}

{(post.thumbnail || post.thumbnailBase) && (
<motion.div
layoutId={`page-${ id }`}
@@ -147,7 +174,6 @@ export default (({ user }: Props) => {
(prev: any) => newPost ?? prev)
qc.invalidateQueries ({ queryKey: postsKeys.root })
qc.invalidateQueries ({ queryKey: tagsKeys.root })
toast ({ description: '更新しました.' })
}}/>
</Tab>)}
</TabGroup>


+ 27
- 0
frontend/src/pages/posts/PostHistoryPage.tsx View File

@@ -95,6 +95,8 @@ export default (() => {
<col className="w-96"/>
{/* タグ */}
<col className="w-[48rem]"/>
{/* 親投稿 */}
<col className="w-[48rem]"/>
{/* オリジナルの投稿日時 */}
<col className="w-96"/>
{/* 更新日時 */}
@@ -110,6 +112,7 @@ export default (() => {
<th className="p-2 text-left">タイトル</th>
<th className="p-2 text-left">URL</th>
<th className="p-2 text-left">タグ</th>
<th className="p-2 text-left">親投稿</th>
<th className="p-2 text-left">オリジナルの投稿日時</th>
<th className="p-2 text-left">更新日時</th>
<th className="p-2"/>
@@ -180,6 +183,28 @@ export default (() => {
{tag.name}
</span>))))}
</td>
<td className="p-2">
{change.parentPosts.map ((pp, i) => (
pp.type === 'added'
? (
<ins
key={i}
className="mr-2 text-green-600 dark:text-green-400">
{pp.title}
</ins>)
: (
pp.type === 'removed'
? (
<del
key={i}
className="mr-2 text-red-600 dark:text-red-400">
{pp.title}
</del>)
: (
<span key={i} className="mr-2">
{pp.title}
</span>))))}
</td>
<td className="p-2">
{change.versionNo === 1
? originalCreatedAtString (change.originalCreatedFrom.current,
@@ -225,6 +250,8 @@ export default (() => {
.map (t => t.name)
.filter (t => t.slice (0, 5) !== 'nico:')
.join (' '),
parent_post_ids:
change.parentPosts.map (p => p.id).join (' '),
original_created_from:
change.originalCreatedFrom.current,
original_created_before:


+ 12
- 0
frontend/src/pages/posts/PostNewPage.tsx View File

@@ -29,6 +29,7 @@ export default (({ user }: Props) => {

const [originalCreatedBefore, setOriginalCreatedBefore] = useState<string | null> (null)
const [originalCreatedFrom, setOriginalCreatedFrom] = useState<string | null> (null)
const [parentPostIds, setParentPostIds] = useState ('')
const [tags, setTags] = useState ('')
const [thumbnailAutoFlg, setThumbnailAutoFlg] = useState (true)
const [thumbnailFile, setThumbnailFile] = useState<File | null> (null)
@@ -46,6 +47,7 @@ export default (({ user }: Props) => {
formData.append ('title', title)
formData.append ('url', url)
formData.append ('tags', tags)
formData.append ('parent_post_ids', parentPostIds)
if (thumbnailFile)
formData.append ('thumbnail', thumbnailFile)
if (originalCreatedFrom)
@@ -177,6 +179,16 @@ export default (({ user }: Props) => {
className="mt-2 max-h-48 rounded border"/>)}
</div>

{/* 親投稿 */}
<div>
<Label>親投稿</Label>
<input
type="text"
value={parentPostIds}
onChange={e => setParentPostIds (e.target.value)}
className="w-full border p-2 rounded"/>
</div>

{/* タグ */}
<PostFormTagsArea tags={tags} setTags={setTags}/>



+ 8
- 1
frontend/src/types.ts View File

@@ -121,6 +121,9 @@ export type Post = {
thumbnail: string | null
thumbnailBase: string | null
tags: Tag[]
parentPosts?: Post[]
childPosts?: Post[]
siblingPosts?: Record<`${ number }`, Post[]>
viewed: boolean
related: Post[]
originalCreatedFrom: string | null
@@ -144,7 +147,11 @@ export type PostVersion = {
url: { current: string; prev: string | null }
thumbnail: { current: string | null; prev: string | null }
thumbnailBase: { current: string | null; prev: string | null }
tags: { name: string; type: 'context' | 'added' | 'removed' }[]
tags: { name: string
type: 'context' | 'added' | 'removed' }[]
parentPosts: { id: number
title: string
type: 'context' | 'added' | 'removed' }[]
originalCreatedFrom: { current: string | null; prev: string | null }
originalCreatedBefore: { current: string | null; prev: string | null }
createdAt: string


Loading…
Cancel
Save