Browse Source

Merge remote-tracking branch 'origin/main' into feature/103

feature/103
みてるぞ 1 week ago
parent
commit
d7c51258a4
9 changed files with 115 additions and 64 deletions
  1. +3
    -5
      backend/app/controllers/nico_tags_controller.rb
  2. +6
    -14
      backend/app/controllers/posts_controller.rb
  3. +5
    -6
      backend/app/controllers/tags_controller.rb
  4. +4
    -7
      backend/app/controllers/wiki_pages_controller.rb
  5. +16
    -0
      backend/app/representations/WikiPageRepr.rb
  6. +16
    -0
      backend/app/representations/post_repr.rb
  7. +16
    -0
      backend/app/representations/tag_repr.rb
  8. +47
    -28
      frontend/src/components/PostOriginalCreatedTimeField.tsx
  9. +2
    -4
      frontend/src/components/common/DateTimeField.tsx

+ 3
- 5
backend/app/controllers/nico_tags_controller.rb View File

@@ -1,6 +1,4 @@
class NicoTagsController < ApplicationController class NicoTagsController < ApplicationController
TAG_JSON = { only: [:id, :category, :post_count], methods: [:name, :has_wiki] }.freeze

def index def index
limit = (params[:limit] || 20).to_i limit = (params[:limit] || 20).to_i
cursor = params[:cursor].presence cursor = params[:cursor].presence
@@ -19,8 +17,8 @@ class NicoTagsController < ApplicationController
end end


render json: { tags: tags.map { |tag| render json: { tags: tags.map { |tag|
tag.as_json(TAG_JSON).merge(linked_tags: tag.linked_tags.map { |lt|
lt.as_json(TAG_JSON)
TagRepr.base(tag).merge(linked_tags: tag.linked_tags.map { |lt|
TagRepr.base(lt)
}) })
}, next_cursor: } }, next_cursor: }
end end
@@ -41,6 +39,6 @@ class NicoTagsController < ApplicationController
tag.linked_tags = linked_tags tag.linked_tags = linked_tags
tag.save! tag.save!


render json: tag.linked_tags.map { |t| t.as_json(TAG_JSON) }, status: :ok
render json: tag.linked_tags.map { |t| TagRepr.base(t) }, status: :ok
end end
end end

+ 6
- 14
backend/app/controllers/posts_controller.rb View File

@@ -36,8 +36,7 @@ class PostsController < ApplicationController
end end


render json: { posts: posts.map { |post| render json: { posts: posts.map { |post|
post.as_json(include: { tags: { only: [:id, :category, :post_count],
methods: [:name, :has_wiki] } }).tap do |json|
PostRepr.base(post).tap do |json|
json['thumbnail'] = json['thumbnail'] =
if post.thumbnail.attached? if post.thumbnail.attached?
rails_storage_proxy_url(post.thumbnail, only_path: false) rails_storage_proxy_url(post.thumbnail, only_path: false)
@@ -60,10 +59,7 @@ class PostsController < ApplicationController


viewed = current_user&.viewed?(post) || false viewed = current_user&.viewed?(post) || false


render json: (post
.as_json(include: { tags: { only: [:id, :category, :post_count],
methods: [:name, :has_wiki] } })
.merge(viewed:))
render json: PostRepr.base(post).merge(viewed:)
end end


def show def show
@@ -102,9 +98,7 @@ class PostsController < ApplicationController
sync_post_tags!(post, tags) sync_post_tags!(post, tags)


post.reload post.reload
render json: post.as_json(include: { tags: { only: [:id, :category, :post_count],
methods: [:name, :has_wiki] } }),
status: :created
render json: PostRepr.base(post), status: :created
else else
render json: { errors: post.errors.full_messages }, status: :unprocessable_entity render json: { errors: post.errors.full_messages }, status: :unprocessable_entity
end end
@@ -170,7 +164,7 @@ class PostsController < ApplicationController


events = [] events = []
pts.each do |pt| pts.each do |pt|
tag = pt.tag.as_json(only: [:id, :category], methods: [:name, :has_wiki])
tag = TagRepr.base(pt.tag)
post = pt.post post = pt.post


events << Event.new( events << Event.new(
@@ -269,8 +263,7 @@ class PostsController < ApplicationController
return nil unless tag return nil unless tag


if path.include?(tag_id) if path.include?(tag_id)
return tag.as_json(only: [:id, :category, :post_count],
methods: [:name, :has_wiki]).merge(children: [])
return TagRepr.base(tag).merge(children: [])
end end


if memo.key?(tag_id) if memo.key?(tag_id)
@@ -282,8 +275,7 @@ class PostsController < ApplicationController


children = child_ids.filter_map { |cid| build_node.(cid, new_path) } children = child_ids.filter_map { |cid| build_node.(cid, new_path) }


memo[tag_id] = tag.as_json(only: [:id, :category, :post_count],
methods: [:name, :has_wiki]).merge(children:)
memo[tag_id] = TagRepr.base(tag).merge(children:)
end end


root_ids.filter_map { |id| build_node.call(id, []) } root_ids.filter_map { |id| build_node.call(id, []) }


+ 5
- 6
backend/app/controllers/tags_controller.rb View File

@@ -13,7 +13,7 @@ class TagsController < ApplicationController
tags = tags.where(posts: { id: post_id }) tags = tags.where(posts: { id: post_id })
end end


render json: tags.as_json(only: [:id, :category, :post_count], methods: [:name, :has_wiki])
render json: TagRepr.base(tags)
end end


def autocomplete def autocomplete
@@ -57,8 +57,7 @@ class TagsController < ApplicationController
tags = tags.order(Arel.sql('post_count DESC, tag_names.name')).limit(20).to_a tags = tags.order(Arel.sql('post_count DESC, tag_names.name')).limit(20).to_a


render json: tags.map { |tag| render json: tags.map { |tag|
tag.as_json(only: [:id, :category, :post_count], methods: [:name, :has_wiki])
.merge(matched_alias: matched_alias_by_tag_name_id[tag.tag_name_id])
TagRepr.base(tag).merge(matched_alias: matched_alias_by_tag_name_id[tag.tag_name_id])
} }
end end


@@ -67,7 +66,7 @@ class TagsController < ApplicationController
.includes(:tag_name, tag_name: :wiki_page) .includes(:tag_name, tag_name: :wiki_page)
.find_by(id: params[:id]) .find_by(id: params[:id])
if tag if tag
render json: tag.as_json(only: [:id, :category, :post_count], methods: [:name, :has_wiki])
render json: TagRepr.base(tag)
else else
head :not_found head :not_found
end end
@@ -81,7 +80,7 @@ class TagsController < ApplicationController
.includes(:tag_name, tag_name: :wiki_page) .includes(:tag_name, tag_name: :wiki_page)
.find_by(tag_names: { name: }) .find_by(tag_names: { name: })
if tag if tag
render json: tag.as_json(only: [:id, :category, :post_count], methods: [:name, :has_wiki])
render json: TagRepr.base(tag)
else else
head :not_found head :not_found
end end
@@ -104,6 +103,6 @@ class TagsController < ApplicationController
tag.update!(category:) tag.update!(category:)
end end


render json: tag.as_json(methods: [:name])
render json: TagRepr.base(tag)
end end
end end

+ 4
- 7
backend/app/controllers/wiki_pages_controller.rb View File

@@ -4,14 +4,12 @@ class WikiPagesController < ApplicationController
def index def index
title = params[:title].to_s.strip title = params[:title].to_s.strip
if title.blank? if title.blank?
return render json: WikiPage.joins(:tag_name)
.includes(:tag_name)
.as_json(methods: [:title])
return render json: WikiPageRepr.base(WikiPage.joins(:tag_name).includes(:tag_name))
end end


q = WikiPage.joins(:tag_name).includes(:tag_name) q = WikiPage.joins(:tag_name).includes(:tag_name)
.where('tag_names.name LIKE ?', "%#{ WikiPage.sanitize_sql_like(title) }%") .where('tag_names.name LIKE ?', "%#{ WikiPage.sanitize_sql_like(title) }%")
render json: q.limit(20).as_json(methods: [:title])
render json: WikiPageRepr.base(q.limit(20))
end end


def show def show
@@ -98,7 +96,7 @@ class WikiPagesController < ApplicationController
message = params[:message].presence message = params[:message].presence
Wiki::Commit.content!(page:, body:, created_user: current_user, message:) Wiki::Commit.content!(page:, body:, created_user: current_user, message:)


render json: page.as_json(methods: [:title]), status: :created
render json: WikiPageRepr.base(page), status: :created
else else
render json: { errors: page.errors.full_messages }, render json: { errors: page.errors.full_messages },
status: :unprocessable_entity status: :unprocessable_entity
@@ -174,8 +172,7 @@ class WikiPagesController < ApplicationController
succ = page.succ_revision_id(revision_id) succ = page.succ_revision_id(revision_id)
updated_at = rev.created_at updated_at = rev.created_at


render json: page.as_json(methods: [:title])
.merge(body:, revision_id:, pred:, succ:, updated_at:)
render json: WikiPageRepr.base(page).merge(body:, revision_id:, pred:, succ:, updated_at:)
end end


def find_revision page def find_revision page


+ 16
- 0
backend/app/representations/WikiPageRepr.rb View File

@@ -0,0 +1,16 @@
# frozen_string_literal: true


module WikiPageRepr
BASE = { methods: [:title] }.freeze

module_function

def base wiki_page
wiki_page.as_json(BASE)
end

def many wiki_pages
wiki_pages.map { |p| base(p) }
end
end

+ 16
- 0
backend/app/representations/post_repr.rb View File

@@ -0,0 +1,16 @@
# frozen_string_literal: true


module PostRepr
BASE = { include: { tags: TagRepr::BASE } }.freeze

module_function

def base post
post.as_json(BASE)
end

def many posts
posts.map { |p| base(p) }
end
end

+ 16
- 0
backend/app/representations/tag_repr.rb View File

@@ -0,0 +1,16 @@
# frozen_string_literal: true


module TagRepr
BASE = { only: [:id, :category, :post_count], methods: [:name, :has_wiki] }.freeze

module_function

def base tag
tag.as_json(BASE)
end

def many tags
tags.map { |t| base(t) }
end
end

+ 47
- 28
frontend/src/components/PostOriginalCreatedTimeField.tsx View File

@@ -1,5 +1,6 @@
import DateTimeField from '@/components/common/DateTimeField' import DateTimeField from '@/components/common/DateTimeField'
import Label from '@/components/common/Label' import Label from '@/components/common/Label'
import { Button } from '@/components/ui/button'


import type { FC } from 'react' import type { FC } from 'react'


@@ -16,34 +17,52 @@ export default (({ originalCreatedFrom,
setOriginalCreatedBefore }: Props) => ( setOriginalCreatedBefore }: Props) => (
<div> <div>
<Label>オリジナルの作成日時</Label> <Label>オリジナルの作成日時</Label>
<div className="my-1">
<DateTimeField
className="mr-2"
value={originalCreatedFrom ?? undefined}
onChange={setOriginalCreatedFrom}
onBlur={ev => {
const v = ev.target.value
if (!(v))
return
const d = new Date (v)
if (d.getSeconds () === 0)
{
if (d.getMinutes () === 0 && d.getHours () === 0)
d.setDate (d.getDate () + 1)
else
d.setMinutes (d.getMinutes () + 1)
}
else
d.setSeconds (d.getSeconds () + 1)
setOriginalCreatedBefore (d.toISOString ())
}}/>
以降
<div className="my-1 flex">
<div className="w-80">
<DateTimeField
className="mr-2"
value={originalCreatedFrom ?? undefined}
onChange={setOriginalCreatedFrom}
onBlur={ev => {
const v = ev.target.value
if (!(v))
return

const d = new Date (v)
if (d.getMinutes () === 0 && d.getHours () === 0)
d.setDate (d.getDate () + 1)
else
d.setMinutes (d.getMinutes () + 1)
setOriginalCreatedBefore (d.toISOString ())
}}/>
以降
</div>
<div>
<Button
className="bg-gray-600 text-white rounded"
onClick={() => {
setOriginalCreatedFrom (null)
}}>
リセット
</Button>
</div>
</div> </div>
<div className="my-1">
<DateTimeField
className="mr-2"
value={originalCreatedBefore ?? undefined}
onChange={setOriginalCreatedBefore}/>
より前
<div className="my-1 flex">
<div className="w-80">
<DateTimeField
className="mr-2"
value={originalCreatedBefore ?? undefined}
onChange={setOriginalCreatedBefore}/>
より前
</div>
<div>
<Button
className="bg-gray-600 text-white rounded"
onClick={() => {
setOriginalCreatedBefore (null)
}}>
リセット
</Button>
</div>
</div> </div>
</div>)) satisfies FC<Props> </div>)) satisfies FC<Props>

+ 2
- 4
frontend/src/components/common/DateTimeField.tsx View File

@@ -5,7 +5,7 @@ import { cn } from '@/lib/utils'
import type { FC, FocusEvent } from 'react' import type { FC, FocusEvent } from 'react'




const pad = (n: number) => n.toString ().padStart (2, '0')
const pad = (n: number): string => n.toString ().padStart (2, '0')




const toDateTimeLocalValue = (d: Date) => { const toDateTimeLocalValue = (d: Date) => {
@@ -14,8 +14,7 @@ const toDateTimeLocalValue = (d: Date) => {
const day = pad (d.getDate ()) const day = pad (d.getDate ())
const h = pad (d.getHours ()) const h = pad (d.getHours ())
const min = pad (d.getMinutes ()) const min = pad (d.getMinutes ())
const s = pad (d.getSeconds ())
return `${ y }-${ m }-${ day }T${ h }:${ min }:${ s }`
return `${ y }-${ m }-${ day }T${ h }:${ min }:00`
} }




@@ -37,7 +36,6 @@ export default (({ value, onChange, className, onBlur }: Props) => {
<input <input
className={cn ('border rounded p-2', className)} className={cn ('border rounded p-2', className)}
type="datetime-local" type="datetime-local"
step={1}
value={local} value={local}
onChange={ev => { onChange={ev => {
const v = ev.target.value const v = ev.target.value


Loading…
Cancel
Save