| @@ -18,7 +18,7 @@ class MaterialsController < ApplicationController | |||||
| count = q.count | count = q.count | ||||
| materials = q.order(created_at: :desc, id: :desc).limit(limit).offset(offset) | materials = q.order(created_at: :desc, id: :desc).limit(limit).offset(offset) | ||||
| render json: { materials: MaterialRepr.many(materials), count: count } | |||||
| render json: { materials: MaterialRepr.many(materials, host: request.base_url), count: count } | |||||
| end | end | ||||
| def show | def show | ||||
| @@ -29,7 +29,9 @@ class MaterialsController < ApplicationController | |||||
| .find_by(id: params[:id]) | .find_by(id: params[:id]) | ||||
| return head :not_found unless material | return head :not_found unless material | ||||
| render json: MaterialRepr.base(material) | |||||
| wiki_page_body = material.tag.tag_name.wiki_page&.current_revision&.body | |||||
| render json: MaterialRepr.base(material, host: request.base_url).merge(wiki_page_body:) | |||||
| end | end | ||||
| def create | def create | ||||
| @@ -50,7 +52,7 @@ class MaterialsController < ApplicationController | |||||
| material.file.attach(file) | material.file.attach(file) | ||||
| if material.save | if material.save | ||||
| render json: MaterialRepr.base(material), status: :created | |||||
| render json: MaterialRepr.base(material, host: request.base_url), status: :created | |||||
| else | else | ||||
| render json: { errors: material.errors.full_messages }, status: :unprocessable_entity | render json: { errors: material.errors.full_messages }, status: :unprocessable_entity | ||||
| end | end | ||||
| @@ -80,7 +82,7 @@ class MaterialsController < ApplicationController | |||||
| end | end | ||||
| if material.save | if material.save | ||||
| render json: MaterialRepr.base(material) | |||||
| render json: MaterialRepr.base(material, host: request.base_url) | |||||
| else | else | ||||
| render json: { errors: material.errors.full_messages }, status: :unprocessable_entity | render json: { errors: material.errors.full_messages }, status: :unprocessable_entity | ||||
| end | end | ||||
| @@ -33,7 +33,7 @@ class TagsController < ApplicationController | |||||
| else | else | ||||
| Tag.joins(:tag_name) | Tag.joins(:tag_name) | ||||
| end | end | ||||
| .includes(:tag_name, tag_name: :wiki_page) | |||||
| .includes(:tag_name, tag_name: :wiki_page, :materials) | |||||
| q = q.where(posts: { id: post_id }) if post_id.present? | q = q.where(posts: { id: post_id }) if post_id.present? | ||||
| q = q.where('tag_names.name LIKE ?', "%#{ name }%") if name | q = q.where('tag_names.name LIKE ?', "%#{ name }%") if name | ||||
| @@ -83,7 +83,7 @@ class TagsController < ApplicationController | |||||
| tags = | tags = | ||||
| Tag | Tag | ||||
| .joins(:tag_name) | .joins(:tag_name) | ||||
| .includes(:tag_name, tag_name: :wiki_page) | |||||
| .includes(:tag_name, tag_name: :wiki_page, :materials) | |||||
| .where(category: [:meme, :character, :material]) | .where(category: [:meme, :character, :material]) | ||||
| .where(id: tag_ids) | .where(id: tag_ids) | ||||
| .order('tag_names.name') | .order('tag_names.name') | ||||
| @@ -128,7 +128,7 @@ class TagsController < ApplicationController | |||||
| end | end | ||||
| base = Tag.joins(:tag_name) | base = Tag.joins(:tag_name) | ||||
| .includes(:tag_name, tag_name: :wiki_page) | |||||
| .includes(:tag_name, tag_name: :wiki_page, :materials) | |||||
| base = base.where('tags.post_count > 0') if present_only | base = base.where('tags.post_count > 0') if present_only | ||||
| canonical_hit = | canonical_hit = | ||||
| @@ -153,7 +153,7 @@ class TagsController < ApplicationController | |||||
| def show | def show | ||||
| tag = Tag.joins(:tag_name) | tag = Tag.joins(:tag_name) | ||||
| .includes(:tag_name, tag_name: :wiki_page) | |||||
| .includes(:tag_name, tag_name: :wiki_page, :materials) | |||||
| .find_by(id: params[:id]) | .find_by(id: params[:id]) | ||||
| if tag | if tag | ||||
| render json: TagRepr.base(tag) | render json: TagRepr.base(tag) | ||||
| @@ -167,7 +167,7 @@ class TagsController < ApplicationController | |||||
| return head :bad_request if name.blank? | return head :bad_request if name.blank? | ||||
| tag = Tag.joins(:tag_name) | tag = Tag.joins(:tag_name) | ||||
| .includes(:tag_name, tag_name: :wiki_page) | |||||
| .includes(:tag_name, tag_name: :wiki_page, :materials) | |||||
| .find_by(tag_names: { name: }) | .find_by(tag_names: { name: }) | ||||
| if tag | if tag | ||||
| render json: TagRepr.base(tag) | render json: TagRepr.base(tag) | ||||
| @@ -73,6 +73,8 @@ class Tag < ApplicationRecord | |||||
| def has_wiki = wiki_page.present? | def has_wiki = wiki_page.present? | ||||
| def material_id = materials.first&.id | |||||
| def self.tagme | def self.tagme | ||||
| @tagme ||= find_or_create_by_tag_name!('タグ希望', category: :meta) | @tagme ||= find_or_create_by_tag_name!('タグ希望', category: :meta) | ||||
| end | end | ||||
| @@ -2,22 +2,23 @@ | |||||
| module MaterialRepr | module MaterialRepr | ||||
| BASE = { methods: [:content_type], | |||||
| include: { created_by_user: UserRepr::BASE, | |||||
| BASE = { only: [:id, :url, :created_at, :updated_at], | |||||
| methods: [:content_type], | |||||
| include: { tag: TagRepr::BASE, | |||||
| created_by_user: UserRepr::BASE, | |||||
| updated_by_user: UserRepr::BASE } }.freeze | updated_by_user: UserRepr::BASE } }.freeze | ||||
| module_function | module_function | ||||
| def base(material) | |||||
| def base material, host: | |||||
| material.as_json(BASE).merge( | material.as_json(BASE).merge( | ||||
| file: if material.file.attached? | file: if material.file.attached? | ||||
| Rails.application.routes.url_helpers.rails_storage_proxy_url( | Rails.application.routes.url_helpers.rails_storage_proxy_url( | ||||
| material.file, only_path: false) | |||||
| end, | |||||
| tag: TagRepr.base(material.tag)) | |||||
| material.file, host:) | |||||
| end) | |||||
| end | end | ||||
| def many(materials) | |||||
| materials.map { |m| base(m) } | |||||
| def many materials, host: | |||||
| materials.map { |m| base(m, host) } | |||||
| end | end | ||||
| end | end | ||||
| @@ -3,7 +3,7 @@ | |||||
| module TagRepr | module TagRepr | ||||
| BASE = { only: [:id, :category, :post_count, :created_at, :updated_at], | BASE = { only: [:id, :category, :post_count, :created_at, :updated_at], | ||||
| methods: [:name, :has_wiki] }.freeze | |||||
| methods: [:name, :has_wiki, :material_id] }.freeze | |||||
| module_function | module_function | ||||
| @@ -131,7 +131,7 @@ export default (() => { | |||||
| <> | <> | ||||
| <RouteBlockerOverlay/> | <RouteBlockerOverlay/> | ||||
| <BrowserRouter> | <BrowserRouter> | ||||
| <div className="flex flex-col h-screen w-screen"> | |||||
| <div className="flex flex-col h-dvh w-screen"> | |||||
| <TopNav user={user}/> | <TopNav user={user}/> | ||||
| <RouteTransitionWrapper user={user} setUser={setUser}/> | <RouteTransitionWrapper user={user} setUser={setUser}/> | ||||
| </div> | </div> | ||||
| @@ -1,8 +1,5 @@ | |||||
| import { useEffect, useState } from 'react' | |||||
| import PrefetchLink from '@/components/PrefetchLink' | import PrefetchLink from '@/components/PrefetchLink' | ||||
| import { LIGHT_COLOUR_SHADE, DARK_COLOUR_SHADE, TAG_COLOUR } from '@/consts' | import { LIGHT_COLOUR_SHADE, DARK_COLOUR_SHADE, TAG_COLOUR } from '@/consts' | ||||
| import { apiGet } from '@/lib/api' | |||||
| import { cn } from '@/lib/utils' | import { cn } from '@/lib/utils' | ||||
| import type { ComponentProps, FC, HTMLAttributes } from 'react' | import type { ComponentProps, FC, HTMLAttributes } from 'react' | ||||
| @@ -36,35 +33,6 @@ export default (({ tag, | |||||
| withWiki = true, | withWiki = true, | ||||
| withCount = true, | withCount = true, | ||||
| ...props }: Props) => { | ...props }: Props) => { | ||||
| const [havingWiki, setHavingWiki] = useState (true) | |||||
| const wikiExists = async (tag: Tag) => { | |||||
| if ('hasWiki' in tag) | |||||
| { | |||||
| setHavingWiki (tag.hasWiki) | |||||
| return | |||||
| } | |||||
| const tagName = (tag as Tag).name | |||||
| try | |||||
| { | |||||
| await apiGet (`/wiki/title/${ encodeURIComponent (tagName) }/exists`) | |||||
| setHavingWiki (true) | |||||
| } | |||||
| catch | |||||
| { | |||||
| setHavingWiki (false) | |||||
| } | |||||
| } | |||||
| useEffect (() => { | |||||
| if (!(linkFlg) || !(withWiki)) | |||||
| return | |||||
| wikiExists (tag) | |||||
| }, [tag.name, linkFlg, withWiki]) | |||||
| const spanClass = cn ( | const spanClass = cn ( | ||||
| `text-${ TAG_COLOUR[tag.category] }-${ LIGHT_COLOUR_SHADE }`, | `text-${ TAG_COLOUR[tag.category] }-${ LIGHT_COLOUR_SHADE }`, | ||||
| `dark:text-${ TAG_COLOUR[tag.category] }-${ DARK_COLOUR_SHADE }`) | `dark:text-${ TAG_COLOUR[tag.category] }-${ DARK_COLOUR_SHADE }`) | ||||
| @@ -77,17 +45,27 @@ export default (({ tag, | |||||
| <> | <> | ||||
| {(linkFlg && withWiki) && ( | {(linkFlg && withWiki) && ( | ||||
| <span className="mr-1"> | <span className="mr-1"> | ||||
| {havingWiki | |||||
| {(tag.materialId != null || tag.hasWiki) | |||||
| ? ( | ? ( | ||||
| <PrefetchLink to={`/wiki/${ encodeURIComponent (tag.name) }`} | |||||
| className={linkClass}> | |||||
| ? | |||||
| </PrefetchLink>) | |||||
| tag.materialId == null | |||||
| ? ( | |||||
| <PrefetchLink | |||||
| to={`/wiki/${ encodeURIComponent (tag.name) }`} | |||||
| className={linkClass}> | |||||
| ? | |||||
| </PrefetchLink>) | |||||
| : ( | |||||
| <PrefetchLink | |||||
| to={`/materials/${ tag.materialId }`} | |||||
| className={linkClass}> | |||||
| ? | |||||
| </PrefetchLink>)) | |||||
| : ( | : ( | ||||
| <PrefetchLink to={`/wiki/${ encodeURIComponent (tag.name) }`} | |||||
| className="animate-[wiki-blink_.25s_steps(2,end)_infinite] | |||||
| dark:animate-[wiki-blink-dark_.25s_steps(2,end)_infinite]" | |||||
| title={`${ tag.name } Wiki が存在しません.`}> | |||||
| <PrefetchLink | |||||
| to={`/wiki/${ encodeURIComponent (tag.name) }`} | |||||
| className="animate-[wiki-blink_.25s_steps(2,end)_infinite] | |||||
| dark:animate-[wiki-blink-dark_.25s_steps(2,end)_infinite]" | |||||
| title={`${ tag.name } Wiki が存在しません.`}> | |||||
| ! | ! | ||||
| </PrefetchLink>)} | </PrefetchLink>)} | ||||
| </span>)} | </span>)} | ||||
| @@ -6,7 +6,7 @@ import type { FC } from 'react' | |||||
| export default (() => ( | export default (() => ( | ||||
| <div className="md:flex md:flex-1"> | |||||
| <div className="md:flex md:flex-1 md:h-[calc(100dvh-88px)]"> | |||||
| <MaterialSidebar/> | <MaterialSidebar/> | ||||
| <Outlet/> | <Outlet/> | ||||
| </div>)) satisfies FC | </div>)) satisfies FC | ||||
| @@ -3,6 +3,7 @@ import { Helmet } from 'react-helmet-async' | |||||
| import { useParams } from 'react-router-dom' | import { useParams } from 'react-router-dom' | ||||
| import TagLink from '@/components/TagLink' | import TagLink from '@/components/TagLink' | ||||
| import WikiBody from '@/components/WikiBody' | |||||
| import Label from '@/components/common/Label' | import Label from '@/components/common/Label' | ||||
| import PageTitle from '@/components/common/PageTitle' | import PageTitle from '@/components/common/PageTitle' | ||||
| import TabGroup, { Tab } from '@/components/common/TabGroup' | import TabGroup, { Tab } from '@/components/common/TabGroup' | ||||
| @@ -109,6 +110,12 @@ export default (() => { | |||||
| <audio src={material.file} controls/>)))} | <audio src={material.file} controls/>)))} | ||||
| <TabGroup> | <TabGroup> | ||||
| <Tab name="Wiki"> | |||||
| <WikiBody | |||||
| title={material.tag.name} | |||||
| body={material.wikiPageBody ?? undefined}/> | |||||
| </Tab> | |||||
| <Tab name="編輯"> | <Tab name="編輯"> | ||||
| <div className="max-w-wl pt-2 space-y-4"> | <div className="max-w-wl pt-2 space-y-4"> | ||||
| {/* タグ */} | {/* タグ */} | ||||
| @@ -93,7 +93,7 @@ export default (({ user }: Props) => { | |||||
| : 'bg-gray-500 hover:bg-gray-600') | : 'bg-gray-500 hover:bg-gray-600') | ||||
| return ( | return ( | ||||
| <div className="md:flex md:flex-1"> | |||||
| <div className="md:flex md:flex-1 md:h-[calc(100dvh-88px)]"> | |||||
| <Helmet> | <Helmet> | ||||
| {(post?.thumbnail || post?.thumbnailBase) && ( | {(post?.thumbnail || post?.thumbnailBase) && ( | ||||
| <meta name="thumbnail" content={post.thumbnail || post.thumbnailBase}/>)} | <meta name="thumbnail" content={post.thumbnail || post.thumbnailBase}/>)} | ||||
| @@ -69,7 +69,7 @@ export default (() => { | |||||
| }, [location.search]) | }, [location.search]) | ||||
| return ( | return ( | ||||
| <div className="md:flex md:flex-1" ref={containerRef}> | |||||
| <div className="md:flex md:flex-1 md:h-[calc(100dvh-88px)]" ref={containerRef}> | |||||
| <Helmet> | <Helmet> | ||||
| <title> | <title> | ||||
| {tags.length | {tags.length | ||||
| @@ -54,6 +54,7 @@ export type Material = { | |||||
| tag: Tag | tag: Tag | ||||
| file: string | null | file: string | null | ||||
| url: string | null | url: string | null | ||||
| wikiPageBody?: string | null | |||||
| contentType: string | null | contentType: string | null | ||||
| createdAt: string | createdAt: string | ||||
| createdByUser: { id: number; name: string } | createdByUser: { id: number; name: string } | ||||
| @@ -140,6 +141,7 @@ export type Tag = { | |||||
| createdAt: string | createdAt: string | ||||
| updatedAt: string | updatedAt: string | ||||
| hasWiki: boolean | hasWiki: boolean | ||||
| materialId: number | |||||
| children?: Tag[] | children?: Tag[] | ||||
| matchedAlias?: string | null } | matchedAlias?: string | null } | ||||