| @@ -18,7 +18,7 @@ class MaterialsController < ApplicationController | |||
| count = q.count | |||
| 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 | |||
| def show | |||
| @@ -29,7 +29,9 @@ class MaterialsController < ApplicationController | |||
| .find_by(id: params[:id]) | |||
| 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 | |||
| def create | |||
| @@ -50,7 +52,7 @@ class MaterialsController < ApplicationController | |||
| material.file.attach(file) | |||
| if material.save | |||
| render json: MaterialRepr.base(material), status: :created | |||
| render json: MaterialRepr.base(material, host: request.base_url), status: :created | |||
| else | |||
| render json: { errors: material.errors.full_messages }, status: :unprocessable_entity | |||
| end | |||
| @@ -80,7 +82,7 @@ class MaterialsController < ApplicationController | |||
| end | |||
| if material.save | |||
| render json: MaterialRepr.base(material) | |||
| render json: MaterialRepr.base(material, host: request.base_url) | |||
| else | |||
| render json: { errors: material.errors.full_messages }, status: :unprocessable_entity | |||
| end | |||
| @@ -33,7 +33,7 @@ class TagsController < ApplicationController | |||
| else | |||
| Tag.joins(:tag_name) | |||
| 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('tag_names.name LIKE ?', "%#{ name }%") if name | |||
| @@ -83,7 +83,7 @@ class TagsController < ApplicationController | |||
| tags = | |||
| Tag | |||
| .joins(:tag_name) | |||
| .includes(:tag_name, tag_name: :wiki_page) | |||
| .includes(:tag_name, tag_name: :wiki_page, :materials) | |||
| .where(category: [:meme, :character, :material]) | |||
| .where(id: tag_ids) | |||
| .order('tag_names.name') | |||
| @@ -128,7 +128,7 @@ class TagsController < ApplicationController | |||
| end | |||
| 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 | |||
| canonical_hit = | |||
| @@ -153,7 +153,7 @@ class TagsController < ApplicationController | |||
| def show | |||
| 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]) | |||
| if tag | |||
| render json: TagRepr.base(tag) | |||
| @@ -167,7 +167,7 @@ class TagsController < ApplicationController | |||
| return head :bad_request if name.blank? | |||
| 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: }) | |||
| if tag | |||
| render json: TagRepr.base(tag) | |||
| @@ -73,6 +73,8 @@ class Tag < ApplicationRecord | |||
| def has_wiki = wiki_page.present? | |||
| def material_id = materials.first&.id | |||
| def self.tagme | |||
| @tagme ||= find_or_create_by_tag_name!('タグ希望', category: :meta) | |||
| end | |||
| @@ -2,22 +2,23 @@ | |||
| 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 | |||
| module_function | |||
| def base(material) | |||
| def base material, host: | |||
| material.as_json(BASE).merge( | |||
| file: if material.file.attached? | |||
| 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 | |||
| def many(materials) | |||
| materials.map { |m| base(m) } | |||
| def many materials, host: | |||
| materials.map { |m| base(m, host) } | |||
| end | |||
| end | |||
| @@ -3,7 +3,7 @@ | |||
| module TagRepr | |||
| BASE = { only: [:id, :category, :post_count, :created_at, :updated_at], | |||
| methods: [:name, :has_wiki] }.freeze | |||
| methods: [:name, :has_wiki, :material_id] }.freeze | |||
| module_function | |||
| @@ -131,7 +131,7 @@ export default (() => { | |||
| <> | |||
| <RouteBlockerOverlay/> | |||
| <BrowserRouter> | |||
| <div className="flex flex-col h-screen w-screen"> | |||
| <div className="flex flex-col h-dvh w-screen"> | |||
| <TopNav user={user}/> | |||
| <RouteTransitionWrapper user={user} setUser={setUser}/> | |||
| </div> | |||
| @@ -1,8 +1,5 @@ | |||
| import { useEffect, useState } from 'react' | |||
| import PrefetchLink from '@/components/PrefetchLink' | |||
| import { LIGHT_COLOUR_SHADE, DARK_COLOUR_SHADE, TAG_COLOUR } from '@/consts' | |||
| import { apiGet } from '@/lib/api' | |||
| import { cn } from '@/lib/utils' | |||
| import type { ComponentProps, FC, HTMLAttributes } from 'react' | |||
| @@ -36,35 +33,6 @@ export default (({ tag, | |||
| withWiki = true, | |||
| withCount = true, | |||
| ...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 ( | |||
| `text-${ TAG_COLOUR[tag.category] }-${ LIGHT_COLOUR_SHADE }`, | |||
| `dark:text-${ TAG_COLOUR[tag.category] }-${ DARK_COLOUR_SHADE }`) | |||
| @@ -77,17 +45,27 @@ export default (({ tag, | |||
| <> | |||
| {(linkFlg && withWiki) && ( | |||
| <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>)} | |||
| </span>)} | |||
| @@ -6,7 +6,7 @@ import type { FC } from 'react' | |||
| export default (() => ( | |||
| <div className="md:flex md:flex-1"> | |||
| <div className="md:flex md:flex-1 md:h-[calc(100dvh-88px)]"> | |||
| <MaterialSidebar/> | |||
| <Outlet/> | |||
| </div>)) satisfies FC | |||
| @@ -3,6 +3,7 @@ import { Helmet } from 'react-helmet-async' | |||
| import { useParams } from 'react-router-dom' | |||
| import TagLink from '@/components/TagLink' | |||
| import WikiBody from '@/components/WikiBody' | |||
| import Label from '@/components/common/Label' | |||
| import PageTitle from '@/components/common/PageTitle' | |||
| import TabGroup, { Tab } from '@/components/common/TabGroup' | |||
| @@ -109,6 +110,12 @@ export default (() => { | |||
| <audio src={material.file} controls/>)))} | |||
| <TabGroup> | |||
| <Tab name="Wiki"> | |||
| <WikiBody | |||
| title={material.tag.name} | |||
| body={material.wikiPageBody ?? undefined}/> | |||
| </Tab> | |||
| <Tab name="編輯"> | |||
| <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') | |||
| return ( | |||
| <div className="md:flex md:flex-1"> | |||
| <div className="md:flex md:flex-1 md:h-[calc(100dvh-88px)]"> | |||
| <Helmet> | |||
| {(post?.thumbnail || post?.thumbnailBase) && ( | |||
| <meta name="thumbnail" content={post.thumbnail || post.thumbnailBase}/>)} | |||
| @@ -69,7 +69,7 @@ export default (() => { | |||
| }, [location.search]) | |||
| 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> | |||
| <title> | |||
| {tags.length | |||
| @@ -54,6 +54,7 @@ export type Material = { | |||
| tag: Tag | |||
| file: string | null | |||
| url: string | null | |||
| wikiPageBody?: string | null | |||
| contentType: string | null | |||
| createdAt: string | |||
| createdByUser: { id: number; name: string } | |||
| @@ -140,6 +141,7 @@ export type Tag = { | |||
| createdAt: string | |||
| updatedAt: string | |||
| hasWiki: boolean | |||
| materialId: number | |||
| children?: Tag[] | |||
| matchedAlias?: string | null } | |||