広場投稿追加画面の刷新 (#399) #413

マージ済み
みてるぞ が 103 個のコミットを feature/399 から main へマージ 2026-07-19 00:03:11 +09:00
9個のファイルの変更198行の追加147行の削除
コミット a847d93d2f の変更だけを表示してゐます - すべてのコミットを表示
+54 -22
ファイルの表示
@@ -124,7 +124,7 @@ npm run preview
- For arrays, never put whitespace or a line break immediately before `]`. - For arrays, never put whitespace or a line break immediately before `]`.
- Keep the first element on the same line as `[` by default. - Keep the first element on the same line as `[` by default.
- If an array would exceed the line limit, break after `[` and indent - If an array would exceed the line limit, break after `[` and indent
elements by 4 spaces. elements 4 spaces deeper than the statement's base indentation.
### Ruby delimiter and wrapping rules ### Ruby delimiter and wrapping rules
@@ -140,6 +140,12 @@ npm run preview
99 文字を超えるなら block 形式へ切り替へるか、message 定数化などで縮める。 99 文字を超えるなら block 形式へ切り替へるか、message 定数化などで縮める。
- Ruby の method chain や call argument を折り返す際、call-site の `)` - Ruby の method chain や call argument を折り返す際、call-site の `)`
block close のやうに独立させない。 block close のやうに独立させない。
- Ruby の block body は、その基準位置から 2 空白深くする。
- Ruby の wrapped expression、method argument、array element、hash pair などの
continuation indentation は、その statement の基準位置から 4 空白深くする。
- Ruby の continuation indentation を、行頭からの絶対空白数として扱はない。
- 例へば class body 内の array は、class body の 2 空白を基準に、更に 4 空白
深くするため、結果として行頭から 6 空白になる。
Bad: Bad:
@@ -225,20 +231,37 @@ records.each {
formatting as the local reference shape: component and callback bodies use formatting as the local reference shape: component and callback bodies use
2-space block indentation, single-line bodies do not gain unnecessary 2-space block indentation, single-line bodies do not gain unnecessary
braces, wrapped expressions use 4-space continuation indentation, multi- braces, wrapped expressions use 4-space continuation indentation, multi-
stage ternaries use explicit parentheses, and only complete leading runs stage ternaries use explicit parentheses, and TypeScript / TSX indentation
of 8 spaces are compressed to tabs. and alignment whitespace compress every complete run of 8 spaces to tabs.
- In TypeScript and TSX only, tabs are for leading 8-column compression only.
- A tab does not represent one indentation level. - A tab does not represent one indentation level.
- Do not replace 2-space or 4-space indentation with tabs. - In TypeScript and TSX, first determine visible indentation using 2-space
- First determine visible indentation using 2-space block indentation and block indentation and 4-space continuation indentation, then compress every
4-space continuation indentation, then compress only complete leading runs complete run of 8 spaces used for indentation or column alignment to tabs.
of 8 spaces into tabs. - In TypeScript and TSX, 8-space compression is mandatory, not optional.
- Tabs are only for leading indentation, never for spaces after non-space - In TypeScript and TSX, this applies both to leading indentation and to
text. alignment whitespace after non-space text, such as aligned inline type
- Keep residual leading 2, 4, or 6 spaces after any tab compression. columns.
- In TypeScript and TSX, keep residual 2, 4, or 6 spaces after each tab
compression.
- In TypeScript and TSX, do not treat a tab as one logical indentation level.
- In TypeScript and TSX, do not alter string literals, template-literal
contents, regular expressions, or user-facing text merely to apply tab
compression.
- Examples: 2 columns = 2 spaces, 4 columns = 4 spaces, 6 columns = 6 - Examples: 2 columns = 2 spaces, 4 columns = 4 spaces, 6 columns = 6
spaces, 8 columns = 1 tab, 10 columns = 1 tab + 2 spaces, 12 columns = 1 spaces, 8 columns = 1 tab, 10 columns = 1 tab + 2 spaces, 12 columns = 1
tab + 4 spaces, 16 columns = 2 tabs. tab + 4 spaces, 14 columns = 1 tab + 6 spaces, 16 columns = 2 tabs.
- When TypeScript or TSX code already uses column alignment, apply the same
8-space compression rule to that alignment whitespace.
- Example:
```ts
type Props = {
row: PostImportRow
displayNumber?: number
onEdit?: () => void
onRetry?: () => void
onToggleSkip?: (checked: boolean) => void }
```
- TypeScript and TSX imports may stay on one line if they remain within the - TypeScript and TSX imports may stay on one line if they remain within the
line limit; do not expand short type-only imports mechanically. line limit; do not expand short type-only imports mechanically.
- Keep runtime value imports and type imports in separate declarations. - Keep runtime value imports and type imports in separate declarations.
@@ -598,10 +621,10 @@ and layout reuse, follow `frontend/AGENTS.md`.
beginning of a line. beginning of a line.
- The TSX-specific self-review must confirm JSX closing markers and closing - The TSX-specific self-review must confirm JSX closing markers and closing
parentheses keep the surrounding compact style. parentheses keep the surrounding compact style.
- The TypeScript/TSX self-review must confirm leading block indentation uses - The TypeScript/TSX self-review must confirm block indentation uses 2 spaces
2 spaces per level, wrapped continuations use the repository's 4-space per level, wrapped continuations use the repository's 4-space continuation
continuation alignment, and complete leading runs of 8 spaces may be alignment, and every complete run of 8 spaces used for indentation or
compressed to tabs. alignment has been compressed to tabs.
- Prefer `const` arrow functions for TypeScript/TSX component and helper declarations. - Prefer `const` arrow functions for TypeScript/TSX component and helper declarations.
- Put two blank lines before and after top-level `const` function - Put two blank lines before and after top-level `const` function
declarations, unless imports, exports, or file boundaries make that awkward. declarations, unless imports, exports, or file boundaries make that awkward.
@@ -614,9 +637,18 @@ and layout reuse, follow `frontend/AGENTS.md`.
- Indent the block body 2 spaces deeper than the keyword and opening brace. - Indent the block body 2 spaces deeper than the keyword and opening brace.
- Put the closing `}` on its own line at the same indentation as the keyword. - Put the closing `}` on its own line at the same indentation as the keyword.
- Do not write `try {`, `catch {`, or `finally {`. - Do not write `try {`, `catch {`, or `finally {`.
- In TypeScript and TSX, convert every complete leading run of 8 spaces to a - In TypeScript and TSX, convert every complete run of 8 spaces used for
tab character. indentation or alignment to a tab character.
- A leading tab is exactly equivalent to 8 leading spaces. - In TypeScript and TSX, a tab is exactly equivalent to 8 columns, whether it
appears at the beginning of a line or in alignment whitespace after
non-space text.
- In TSX, JSX nesting uses 2-space block indentation and wrapped JSX
attributes use 4-space continuation indentation; after visible columns are
determined, compress every complete run of 8 spaces in the resulting
indentation or alignment to tabs.
- In TSX, do not leave JSX subtree indentation at 8, 10, 12, 14, or 16
columns as spaces alone; convert each complete run of 8 spaces to tabs and
keep only the residual 2, 4, or 6 spaces.
- In TypeScript and TSX function declarations, including `const` arrow - In TypeScript and TSX function declarations, including `const` arrow
function declarations, classify the parameter list before placing the closing function declarations, classify the parameter list before placing the closing
`)`. `)`.
@@ -1259,9 +1291,9 @@ to `.ts` and `.tsx`:
7. JSX `>` and `/>` stay with the final prop unless nearby code proves 7. JSX `>` and `/>` stay with the final prop unless nearby code proves
otherwise. otherwise.
8. JSX closing parentheses keep the compact local style. 8. JSX closing parentheses keep the compact local style.
9. Leading block indentation uses 2 spaces per level, wrapped continuations 9. Block indentation uses 2 spaces per level, wrapped continuations use the
use the repository's 4-space continuation alignment, and complete leading repository's 4-space continuation alignment, and every complete run of 8
runs of 8 spaces may be compressed to tabs. spaces used for indentation or alignment has been compressed to tabs.
10. No line has trailing whitespace. 10. No line has trailing whitespace.
Preferred: Preferred:
+17 -4
ファイルの表示
@@ -138,7 +138,8 @@ class Post < ApplicationRecord
end end
def self.section_literal section def self.section_literal section
"[#{ Post.ms_to_time(section.begin_ms) }-#{ section.end_ms ? Post.ms_to_time(section.end_ms) : '' }]" "[#{ Post.ms_to_time(section.begin_ms) }-"\
"#{ section.end_ms ? Post.ms_to_time(section.end_ms) : '' }]"
end end
def self.ms_to_time ms def self.ms_to_time ms
@@ -323,7 +324,9 @@ class Post < ApplicationRecord
end end
def self.external_svg_reference?(attribute_name, attribute_value) def self.external_svg_reference?(attribute_name, attribute_value)
return external_svg_url?(attribute_value) if ['href', 'xlink:href', 'src'].include?(attribute_name) if ['href', 'xlink:href', 'src'].include?(attribute_name)
return external_svg_url?(attribute_value)
end
return style_contains_disallowed_urls?(attribute_value) if attribute_name == 'style' return style_contains_disallowed_urls?(attribute_value) if attribute_name == 'style'
return svg_url_function_disallowed?(attribute_value) if attribute_value.match?(/url\s*\(/i) return svg_url_function_disallowed?(attribute_value) if attribute_value.match?(/url\s*\(/i)
@@ -344,7 +347,12 @@ class Post < ApplicationRecord
def self.svg_url_function_disallowed?(value) def self.svg_url_function_disallowed?(value)
value.to_s.scan(/url\s*\(([^)]*)\)/i).flatten.any? do |entry| value.to_s.scan(/url\s*\(([^)]*)\)/i).flatten.any? do |entry|
reference = entry.to_s.strip.delete_prefix("'").delete_prefix('"').delete_suffix("'").delete_suffix('"') reference =
entry.to_s.strip
.delete_prefix("'")
.delete_prefix('"')
.delete_suffix("'")
.delete_suffix('"')
reference.present? && !(reference.start_with?('#')) reference.present? && !(reference.start_with?('#'))
end end
end end
@@ -433,7 +441,12 @@ class Post < ApplicationRecord
match = match =
value.match( value.match(
/\A(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?(Z|[+-]\d{2}:?\d{2})?\z/) /
\A(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})
(?::(\d{2})(?:\.(\d+))?)?
(Z|[+-]\d{2}:?\d{2})?
\z
/x)
return nil if match.nil? return nil if match.nil?
year = match[1].to_i year = match[1].to_i
+3 -1
ファイルの表示
@@ -85,7 +85,9 @@ class PostCreatePlan
end_raw: match[3], end_raw: match[3],
tag_name: name) tag_name: name)
end end
raise Tag::SectionLiteralParseError.new(raw_name, raw_name) if name.include?('[') || name.include?(']') if name.include?('[') || name.include?(']')
raise Tag::SectionLiteralParseError.new(raw_name, raw_name)
end
[resolved_tag_name(name), category&.to_sym, sections] [resolved_tag_name(name), category&.to_sym, sections]
end end
+11 -10
ファイルの表示
@@ -124,7 +124,7 @@ RSpec.describe 'Posts API', type: :request do
end end
end end
it "returns posts with tag name in JSON" do it 'returns posts with tag name in JSON' do
get "/posts" get "/posts"
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
@@ -161,9 +161,9 @@ RSpec.describe 'Posts API', type: :request do
]) ])
end end
context "when q is provided" do context 'when q is provided' do
it "filters posts by q (hit case)" do it 'filters posts by q (hit case)' do
get "/posts", params: { tags: "spec_tag" } get '/posts', params: { tags: 'spec_tag' }
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
posts = json.fetch('posts') posts = json.fetch('posts')
@@ -181,8 +181,8 @@ RSpec.describe 'Posts API', type: :request do
end end
end end
it "filters posts by q (hit case by alias)" do it 'filters posts by q (hit case by alias)' do
get "/posts", params: { tags: "manko" } get '/posts', params: { tags: 'manko' }
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
posts = json.fetch('posts') posts = json.fetch('posts')
@@ -200,7 +200,7 @@ RSpec.describe 'Posts API', type: :request do
end end
end end
it "returns empty posts when nothing matches" do it 'returns empty posts when nothing matches' do
get "/posts", params: { tags: "no_such_keyword_12345" } get "/posts", params: { tags: "no_such_keyword_12345" }
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
@@ -1161,7 +1161,7 @@ RSpec.describe 'Posts API', type: :request do
) )
end end
context "when nico tag already exists in tags" do context 'when nico tag already exists in tags' do
before do before do
Tag.find_undiscard_or_create_by!( Tag.find_undiscard_or_create_by!(
tag_name: TagName.find_undiscard_or_create_by!(name: 'nico:nico_tag'), tag_name: TagName.find_undiscard_or_create_by!(name: 'nico:nico_tag'),
@@ -1455,7 +1455,7 @@ RSpec.describe 'Posts API', type: :request do
) )
end end
context "when nico tag already exists in tags" do context 'when nico tag already exists in tags' do
before do before do
Tag.find_undiscard_or_create_by!( Tag.find_undiscard_or_create_by!(
tag_name: TagName.find_undiscard_or_create_by!(name: 'nico:nico_tag'), tag_name: TagName.find_undiscard_or_create_by!(name: 'nico:nico_tag'),
@@ -1712,7 +1712,8 @@ RSpec.describe 'Posts API', type: :request do
expect(post_record.reload.title).to eq('updated by other user') expect(post_record.reload.title).to eq('updated by other user')
end end
it 'returns 409 with mergeable true when stale tag changes do not conflict but merge is not requested' do it 'returns 409 with mergeable true when stale tag changes'\
' do not conflict but merge is not requested' do
sign_in_as(member) sign_in_as(member)
base_version = create_post_version_for!(post_record.reload) base_version = create_post_version_for!(post_record.reload)
+6 -6
ファイルの表示
@@ -16,11 +16,11 @@ RSpec.describe "nico:sync" do
NicoTagRelation.create!(nico_tag_id: nico_tag.id, tag_id: tag.id) NicoTagRelation.create!(nico_tag_id: nico_tag.id, tag_id: tag.id)
end end
it "既存 post を見つけて、nico tag と linked tag を追加し、差分が出たら bot を付ける" do it '既存 post を見つけて、nico tag と linked tag を追加し、差分が出たら bot を付ける' do
# 既存 post(正規表現で拾われるURL) # 既存 post(正規表現で拾われるURL)
post = Post.create!( post = Post.create!(
title: "old", title: 'old',
url: "https://www.nicovideo.jp/watch/sm9", url: 'https://www.nicovideo.jp/watch/sm9',
uploaded_user: nil uploaded_user: nil
) )
@@ -108,10 +108,10 @@ RSpec.describe "nico:sync" do
expect(calls).to eq(2) expect(calls).to eq(2)
end end
it "既存 post にあった古い nico tag は active から外され、履歴として discard される" do it '既存 post にあった古い nico tag は active から外され、履歴として discard される' do
post = Post.create!( post = Post.create!(
title: "old", title: 'old',
url: "https://www.nicovideo.jp/watch/sm9", url: 'https://www.nicovideo.jp/watch/sm9',
uploaded_user: nil uploaded_user: nil
) )
+4 -2
ファイルの表示
@@ -92,7 +92,9 @@ describe ('PostEditForm', () => {
.toHaveValue ('180.5') .toHaveValue ('180.5')
}) })
it ('shows deduplicated original-created endpoint errors on the shared datetime field', async () => { it (
'shows deduplicated original-created endpoint errors on the shared datetime field',
async () => {
const post = buildPost () const post = buildPost ()
api.isApiError.mockReturnValue (true) api.isApiError.mockReturnValue (true)
postsApi.updatePost.mockRejectedValueOnce ({ postsApi.updatePost.mockRejectedValueOnce ({
@@ -114,5 +116,5 @@ describe ('PostEditForm', () => {
expect (await screen.findByText ('日時を確認してください.')).toBeInTheDocument () expect (await screen.findByText ('日時を確認してください.')).toBeInTheDocument ()
expect (screen.getByText ('終了を確認してください.')).toBeInTheDocument () expect (screen.getByText ('終了を確認してください.')).toBeInTheDocument ()
expect (screen.getAllByText ('日時を確認してください.')).toHaveLength (1) expect (screen.getAllByText ('日時を確認してください.')).toHaveLength (1)
}) })
}) })
+4 -3
ファイルの表示
@@ -75,8 +75,6 @@ const TagLink: FC<Props> = ({ tag,
const spanClass = 'tag-link-colour' const spanClass = 'tag-link-colour'
const linkClass = 'tag-link-colour tag-link-hover-colour' const linkClass = 'tag-link-colour tag-link-hover-colour'
const textClass = 'group min-w-0 max-w-full overflow-hidden align-bottom' const textClass = 'group min-w-0 max-w-full overflow-hidden align-bottom'
const rootClass =
'inline-flex min-w-0 max-w-full flex-nowrap items-stretch align-baseline gap-x-1 md:items-baseline'
const markerWrapClass = 'shrink-0 self-start md:self-auto' const markerWrapClass = 'shrink-0 self-start md:self-auto'
const countClass = 'shrink-0 self-end md:self-auto' const countClass = 'shrink-0 self-end md:self-auto'
const matchedAlias = isFullTag (tag) ? tag.matchedAlias : null const matchedAlias = isFullTag (tag) ? tag.matchedAlias : null
@@ -84,7 +82,10 @@ const TagLink: FC<Props> = ({ tag,
?? (matchedAlias == null ? tag.name : `${ matchedAlias }${ tag.name }`) ?? (matchedAlias == null ? tag.name : `${ matchedAlias }${ tag.name }`)
return ( return (
<span className={rootClass}> <span
className={cn (
'inline-flex min-w-0 max-w-full flex-nowrap items-stretch align-baseline',
'gap-x-1 md:items-baseline')}>
{(linkFlg && withWiki && isFullTag (tag)) && ( {(linkFlg && withWiki && isFullTag (tag)) && (
<span className={markerWrapClass}> <span className={markerWrapClass}>
{(tag.materialId != null || tag.hasWiki || tag.hasDeerjikists) {(tag.materialId != null || tag.hasWiki || tag.hasDeerjikists)
+95 -97
ファイルの表示
@@ -1,10 +1,10 @@
"use client" 'use client'
import * as React from "react" import * as DialogPrimitive from '@radix-ui/react-dialog'
import * as DialogPrimitive from "@radix-ui/react-dialog" import { X } from 'lucide-react'
import { X } from "lucide-react" import * as React from 'react'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
const Dialog = DialogPrimitive.Root const Dialog = DialogPrimitive.Root
@@ -15,111 +15,109 @@ const DialogPortal = DialogPrimitive.Portal
const DialogClose = DialogPrimitive.Close const DialogClose = DialogPrimitive.Close
const DialogOverlay = React.forwardRef< const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>, React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => ( >(
<DialogPrimitive.Overlay ({ className, ...props }, ref) => (
ref={ref} <DialogPrimitive.Overlay
className={cn( ref={ref}
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", className={cn (
className)} 'fixed inset-0 z-50 bg-black/80',
{...props} 'data-[state=open]:animate-in data-[state=closed]:animate-out',
/>)) 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
className)}
{...props}/>))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef< const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>, React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => ( >(
<DialogPortal> ({ className, children, ...props }, ref) => (
<DialogOverlay /> <DialogPortal>
<DialogPrimitive.Content <DialogOverlay />
ref={ref} <DialogPrimitive.Content
className={cn ( ref={ref}
'fixed left-[50%] top-[50%] z-50 grid w-[calc(100%-2rem)] max-w-lg', className={cn (
'translate-x-[-50%] translate-y-[-50%]', 'fixed left-[50%] top-[50%] z-50 grid w-[calc(100%-2rem)] max-w-lg',
'gap-5 rounded-2xl border border-border', 'translate-x-[-50%] translate-y-[-50%]',
'bg-background p-6 text-foreground shadow-2xl', 'gap-5 rounded-2xl border border-border',
'duration-200', 'bg-background p-6 text-foreground shadow-2xl',
'data-[state=open]:animate-in data-[state=closed]:animate-out', 'duration-200',
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0', 'data-[state=open]:animate-in data-[state=closed]:animate-out',
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95', 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
className)} 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
{...props} className)}
> {...props}>
{children} {children}
<DialogPrimitive.Close <DialogPrimitive.Close
className={cn ( className={cn (
'absolute left-4 top-4 rounded-full p-1', 'absolute left-4 top-4 rounded-full p-1',
'text-slate-500 transition-colors', 'text-slate-500 transition-colors',
'hover:bg-slate-200 hover:text-slate-900', 'hover:bg-slate-200 hover:text-slate-900',
'dark:text-slate-400 dark:hover:bg-slate-700 dark:hover:text-slate-50', 'dark:text-slate-400 dark:hover:bg-slate-700 dark:hover:text-slate-50',
'focus:outline-none focus:ring-2 focus:ring-slate-400')}> 'focus:outline-none focus:ring-2 focus:ring-slate-400')}>
<X className="h-4 w-4"/> <X className="h-4 w-4"/>
<span className="sr-only"></span> <span className="sr-only"></span>
</DialogPrimitive.Close> </DialogPrimitive.Close>
</DialogPrimitive.Content> </DialogPrimitive.Content>
</DialogPortal>)) </DialogPortal>))
DialogContent.displayName = DialogPrimitive.Content.displayName DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({ const DialogHeader = (
className, { className, ...props }: React.HTMLAttributes<HTMLDivElement>,
...props ) => (
}: React.HTMLAttributes<HTMLDivElement>) => ( <div
<div className={cn (
className={cn( 'flex flex-col space-y-1.5 text-center md:text-left',
"flex flex-col space-y-1.5 text-center md:text-left", className)}
className)} {...props}/>)
{...props} DialogHeader.displayName = 'DialogHeader'
/>)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({ const DialogFooter = (
className, { className, ...props }: React.HTMLAttributes<HTMLDivElement>,
...props ) => (
}: React.HTMLAttributes<HTMLDivElement>) => ( <div
<div className={cn (
className={cn( 'flex flex-col-reverse md:flex-row md:justify-end md:space-x-2',
"flex flex-col-reverse md:flex-row md:justify-end md:space-x-2", className)}
className)} {...props}/>)
{...props} DialogFooter.displayName = 'DialogFooter'
/>)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef< const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>, React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title> React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => ( >(
<DialogPrimitive.Title ({ className, ...props }, ref) => (
ref={ref} <DialogPrimitive.Title
className={cn( ref={ref}
"text-lg font-semibold leading-none tracking-tight", className={cn (
className)} 'text-lg font-semibold leading-none tracking-tight',
{...props} className)}
/>)) {...props}/>))
DialogTitle.displayName = DialogPrimitive.Title.displayName DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef< const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>, React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description> React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => ( >(
<DialogPrimitive.Description ({ className, ...props }, ref) => (
ref={ref} <DialogPrimitive.Description
className={cn("text-sm text-muted-foreground", className)} ref={ref}
{...props} className={cn ('text-sm text-muted-foreground', className)}
/>)) {...props}/>))
DialogDescription.displayName = DialogPrimitive.Description.displayName DialogDescription.displayName = DialogPrimitive.Description.displayName
export { export {
Dialog, Dialog,
DialogPortal, DialogPortal,
DialogOverlay, DialogOverlay,
DialogClose, DialogClose,
DialogTrigger, DialogTrigger,
DialogContent, DialogContent,
DialogHeader, DialogHeader,
DialogFooter, DialogFooter,
DialogTitle, DialogTitle,
DialogDescription, DialogDescription,
} }
+4 -2
ファイルの表示
@@ -262,7 +262,9 @@ describe ('TheatreDetailPage', () => {
expect (postEmbed.seek).not.toHaveBeenCalledWith (0) expect (postEmbed.seek).not.toHaveBeenCalledWith (0)
}) })
it ('shows child tags from the post tag tree in both vertical and horizontal layouts', async () => { it (
'shows child tags from the post tag tree in both vertical and horizontal layouts',
async () => {
const childTag = buildTag ({ id: 12, name: '子タグ', category: 'general' }) const childTag = buildTag ({ id: 12, name: '子タグ', category: 'general' })
const parentTag = buildTag ({ const parentTag = buildTag ({
id: 11, id: 11,
@@ -307,7 +309,7 @@ describe ('TheatreDetailPage', () => {
expect (within (tagSection ()).getAllByRole ('link', { name: '子タグ' })) expect (within (tagSection ()).getAllByRole ('link', { name: '子タグ' }))
.toHaveLength (1) .toHaveLength (1)
}) })
}) })
it ('does not advance host post while video length is unknown', async () => { it ('does not advance host post while video length is unknown', async () => {
api.apiPut.mockImplementation ((path: string) => { api.apiPut.mockImplementation ((path: string) => {