diff --git a/backend/app/controllers/wiki_pages_controller.rb b/backend/app/controllers/wiki_pages_controller.rb index a778b48..abe29f0 100644 --- a/backend/app/controllers/wiki_pages_controller.rb +++ b/backend/app/controllers/wiki_pages_controller.rb @@ -13,6 +13,22 @@ class WikiPagesController < ApplicationController render_wiki_page_or_404 WikiPage.find_by(title: params[:title]) end + def exists + if WikiPage.exists?(params[:id]) + head :no_content + else + head :not_found + end + end + + def exists_by_title + if WikiPage.exists?(title: params[:title]) + head :no_content + else + head :not_found + end + end + def diff id = params[:id] from = params[:from] diff --git a/backend/app/models/nico_tag_relation.rb b/backend/app/models/nico_tag_relation.rb index ff4f3a6..d2c4a82 100644 --- a/backend/app/models/nico_tag_relation.rb +++ b/backend/app/models/nico_tag_relation.rb @@ -6,6 +6,7 @@ class NicoTagRelation < ApplicationRecord validates :tag_id, presence: true validate :nico_tag_must_be_nico + validate :tag_mustnt_be_nico private diff --git a/backend/config/routes.rb b/backend/config/routes.rb index 0733991..206d1a6 100644 --- a/backend/config/routes.rb +++ b/backend/config/routes.rb @@ -1,46 +1,60 @@ Rails.application.routes.draw do - get 'tags/nico', to: 'nico_tags#index' - put 'tags/nico/:id', to: 'nico_tags#update' - get 'tags/autocomplete', to: 'tags#autocomplete' - get 'tags/name/:name', to: 'tags#show_by_name' - get 'posts/random', to: 'posts#random' - get 'posts/changes', to: 'posts#changes' - post 'posts/:id/viewed', to: 'posts#viewed' - delete 'posts/:id/viewed', to: 'posts#unviewed' - get 'preview/title', to: 'preview#title' - get 'preview/thumbnail', to: 'preview#thumbnail' - get 'wiki/title/:title', to: 'wiki_pages#show_by_title' - get 'wiki/search', to: 'wiki_pages#search' - get 'wiki/changes', to: 'wiki_pages#changes' - get 'wiki/:id/diff', to: 'wiki_pages#diff' - get 'wiki/:id', to: 'wiki_pages#show' - get 'wiki', to: 'wiki_pages#index' - post 'wiki', to: 'wiki_pages#create' - put 'wiki/:id', to: 'wiki_pages#update' - post 'users/code/renew', to: 'users#renew' - - resources :posts - resources :ip_addresses - resources :nico_tag_relations - resources :post_tags - resources :settings - resources :tag_aliases - resources :tags - resources :user_ips - resources :user_post_views + resources :nico_tags, path: 'tags/nico', only: [:index, :update] + + resources :tags do + collection do + get :autocomplete + get 'name/:name', action: :show_by_name + end + end + + scope :preview, controller: :preview do + get :title + get :thumbnail + end + + resources :wiki_pages, path: 'wiki', only: [:index, :show, :create, :update] do + collection do + get :search + get :changes + + scope :title do + get ':title/exists', action: :exists_by_title + get ':title', action: :show_by_title + end + end + + member do + get :exists + get :diff + end + end + + resources :posts do + collection do + get :random + get :changes + end + + member do + post :viewed + delete :viewed, action: :unviewed + end + end + resources :users, only: [:create, :update] do collection do post :verify get :me + post 'code/renew', action: :renew end end - # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html - - # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. - # Can be used by load balancers and uptime monitors to verify that the app is live. - # get "up" => "rails/health#show", as: :rails_health_check - - # Defines the root path route ("/") - # root "posts#index" + resources :ip_addresses + resources :nico_tag_relations + resources :post_tags + resources :settings + resources :tag_aliases + resources :user_ips + resources :user_post_views end diff --git a/frontend/src/components/TagDetailSidebar.tsx b/frontend/src/components/TagDetailSidebar.tsx index 5d02358..a897fa8 100644 --- a/frontend/src/components/TagDetailSidebar.tsx +++ b/frontend/src/components/TagDetailSidebar.tsx @@ -1,5 +1,6 @@ import { AnimatePresence, motion } from 'framer-motion' import { useEffect, useState } from 'react' +import { Link } from 'react-router-dom' import TagLink from '@/components/TagLink' import TagSearch from '@/components/TagSearch' @@ -128,6 +129,9 @@ export default (({ post }: Props) => { && `${ (new Date (post.originalCreatedBefore)).toLocaleString () } より前`} )} +
  • + 履歴 +
  • )} diff --git a/frontend/src/components/TagLink.tsx b/frontend/src/components/TagLink.tsx index a77ca5f..ee673cc 100644 --- a/frontend/src/components/TagLink.tsx +++ b/frontend/src/components/TagLink.tsx @@ -1,5 +1,8 @@ +import axios from 'axios' +import { useEffect, useState } from 'react' import { Link } from 'react-router-dom' +import { API_BASE_URL } from '@/config' import { LIGHT_COLOUR_SHADE, DARK_COLOUR_SHADE, TAG_COLOUR } from '@/consts' import { cn } from '@/lib/utils' @@ -27,6 +30,27 @@ export default (({ tag, withWiki = true, withCount = true, ...props }: Props) => { + const [havingWiki, setHavingWiki] = useState (true) + + const wikiExists = async (tagName: string) => { + try + { + await axios.get (`${ API_BASE_URL }/wiki/title/${ encodeURIComponent (tagName) }/exists`) + setHavingWiki (true) + } + catch + { + setHavingWiki (false) + } + } + + useEffect (() => { + if (!(linkFlg) || !(withWiki)) + return + + wikiExists (tag.name) + }, [tag.name, linkFlg, withWiki]) + const spanClass = cn ( `text-${ TAG_COLOUR[tag.category] }-${ LIGHT_COLOUR_SHADE }`, `dark:text-${ TAG_COLOUR[tag.category] }-${ DARK_COLOUR_SHADE }`) @@ -39,10 +63,19 @@ export default (({ tag, <> {(linkFlg && withWiki) && ( - - ? - + {havingWiki + ? ( + + ? + ) + : ( + + ! + )} )} {nestLevel > 0 && ( { const page = Number (query.get ('page') ?? 1) const limit = Number (query.get ('limit') ?? 20) + // 投稿列の結合で使用 + let rowsCnt: number + useEffect (() => { void (async () => { const res = await axios.get (`${ API_BASE_URL }/posts/changes`, - { params: { ...(id && { id }), - ...(page && { page }), - ...(limit && { limit }) } }) + { params: { ...(id && { id }), page, limit } }) const data = toCamel (res.data as any, { deep: true }) as { changes: PostTagChange[] count: number } setChanges (data.changes) - setTotalPages (Math.trunc ((data.count - 1) / limit)) + setTotalPages (Math.ceil (data.count / limit)) }) () - }, [location.search]) + }, [id, page, limit]) return ( @@ -47,7 +48,7 @@ export default (() => { 耕作履歴 - {Boolean (id) && <>: 投稿 {#{id}}} + {id && <>: 投稿 {#{id}}} @@ -59,29 +60,42 @@ export default (() => { - {changes.map (change => ( - - - - - ))} + {changes.map ((change, i) => { + let withPost = i === 0 || change.post.id !== changes[i - 1].post.id + if (withPost) + { + rowsCnt = 1 + for (let j = i + 1; + (j < changes.length + && change.post.id === changes[j].post.id); + ++j) + ++rowsCnt + } + return ( + + {withPost && ( + )} + + + ) + })}
    - - {change.post.title - - - - {`を${ change.changeType === 'add' ? '追加' : '削除' }`} - - {change.user ? ( - - {change.user.name} - ) : 'bot 操作'} -
    - {change.timestamp} -
    + + {change.post.title + + + + {`を${ change.changeType === 'add' ? '追加' : '削除' }`} + + {change.user ? ( + + {change.user.name} + ) : 'bot 操作'} +
    + {change.timestamp} +
    diff --git a/frontend/src/pages/wiki/WikiDetailPage.tsx b/frontend/src/pages/wiki/WikiDetailPage.tsx index 058a5fc..b5ebd79 100644 --- a/frontend/src/pages/wiki/WikiDetailPage.tsx +++ b/frontend/src/pages/wiki/WikiDetailPage.tsx @@ -36,9 +36,16 @@ export default () => { if (/^\d+$/.test (title)) { void (async () => { - const res = await axios.get (`${ API_BASE_URL }/wiki/${ title }`) - const data = res.data as WikiPage - navigate (`/wiki/${ data.title }`, { replace: true }) + try + { + const res = await axios.get (`${ API_BASE_URL }/wiki/${ title }`) + const data = res.data as WikiPage + navigate (`/wiki/${ encodeURIComponent(data.title) }`, { replace: true }) + } + catch + { + ; + } }) () return @@ -51,6 +58,8 @@ export default () => { `${ API_BASE_URL }/wiki/title/${ encodeURIComponent (title) }`, { params: version ? { version } : { } }) const data = toCamel (res.data as any, { deep: true }) as WikiPage + if (data.title !== title) + navigate (`/wiki/${ encodeURIComponent(data.title) }`, { replace: true }) setWikiPage (data) WikiIdBus.set (data.id) }