This commit is contained in:
@@ -177,8 +177,8 @@ class PostsController < ApplicationController
|
||||
merge = bool?(:merge)
|
||||
return head :bad_request if force && merge
|
||||
|
||||
base_version_no = nil
|
||||
base_version_no = parse_base_version_no unless force
|
||||
base_version_no = parse_base_version_no
|
||||
return head :bad_request if !(force) && !(base_version_no)
|
||||
|
||||
title = params[:title].presence
|
||||
tag_names = params[:tags].to_s.split
|
||||
@@ -442,9 +442,11 @@ class PostsController < ApplicationController
|
||||
|
||||
def parse_base_version_no
|
||||
version_no = Integer(params[:base_version_no], exception: false)
|
||||
raise ArgumentError, 'base_version_no は必須です.' unless version_no&.positive?
|
||||
|
||||
version_no
|
||||
if version_no&.positive?
|
||||
version_no
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def post_snapshot_from_version version
|
||||
|
||||
@@ -16,7 +16,7 @@ class VersionRecorder
|
||||
@record = record_class.unscoped.lock.find(@record.id)
|
||||
latest = latest_version
|
||||
|
||||
validate_version_sequence! latest
|
||||
validate_version_sequence!(latest)
|
||||
|
||||
attrs = snapshot_attributes
|
||||
|
||||
@@ -27,7 +27,7 @@ class VersionRecorder
|
||||
version = version_class.create!(
|
||||
base_attributes(latest).merge(record_key => @record).merge(attrs))
|
||||
|
||||
update_record_version_no! version.version_no
|
||||
update_record_version_no!(version.version_no)
|
||||
|
||||
version
|
||||
end
|
||||
@@ -47,7 +47,7 @@ class VersionRecorder
|
||||
end
|
||||
|
||||
def update_record_version_no! version_no
|
||||
@record.update_columns version_no: version_no
|
||||
@record.update_columns(version_no:)
|
||||
@record.version_no = version_no
|
||||
end
|
||||
|
||||
|
||||
@@ -103,10 +103,16 @@ export default (({ post, onSave }: Props) => {
|
||||
e.preventDefault ()
|
||||
|
||||
setDisabled (true)
|
||||
await update ({ id: post.id, title, tags, parentPostIds,
|
||||
originalCreatedFrom, originalCreatedBefore },
|
||||
{ baseVersionNo: post.versionNo })
|
||||
setDisabled (false)
|
||||
try
|
||||
{
|
||||
await update ({ id: post.id, title, tags, parentPostIds,
|
||||
originalCreatedFrom, originalCreatedBefore },
|
||||
{ baseVersionNo: post.versionNo })
|
||||
}
|
||||
finally
|
||||
{
|
||||
setDisabled (false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect (() => {
|
||||
@@ -114,7 +120,7 @@ export default (({ post, onSave }: Props) => {
|
||||
}, [post])
|
||||
|
||||
return (
|
||||
<div className="max-w-xl pt-2 space-y-4">
|
||||
<form onSubmit={handleSubmit} className="max-w-xl pt-2 space-y-4">
|
||||
{/* タイトル */}
|
||||
<div>
|
||||
<Label>タイトル</Label>
|
||||
@@ -154,10 +160,8 @@ export default (({ post, onSave }: Props) => {
|
||||
{/* 送信 */}
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={disabled}
|
||||
onClick={handleSubmit}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded disabled:bg-gray-400">
|
||||
disabled={disabled}>
|
||||
更新
|
||||
</Button>
|
||||
</div>)
|
||||
</form>)
|
||||
}) satisfies FC<Props>
|
||||
|
||||
@@ -31,13 +31,12 @@ const replaceToken = (value: string, start: number, end: number, text: string) =
|
||||
`${ value.slice (0, start) }${ text }${ value.slice (end) }`
|
||||
|
||||
|
||||
type Props =
|
||||
& { tags: string
|
||||
setTags: (tags: string) => void }
|
||||
& ComponentPropsWithoutRef<'textarea'>
|
||||
type Props = Omit<ComponentPropsWithoutRef<'textarea'>, 'value' | 'onChange' | 'onBlur'> & {
|
||||
tags: string
|
||||
setTags: (tags: string) => void }
|
||||
|
||||
|
||||
export default (({ tags, setTags, ...rest }: Props) => {
|
||||
export default (({ tags, setTags, onBlur, ...rest }: Props) => {
|
||||
const ref = useRef<HTMLTextAreaElement> (null)
|
||||
|
||||
const [bounds, setBounds] = useState<{ start: number; end: number }> ({ start: 0, end: 0 })
|
||||
@@ -77,6 +76,7 @@ export default (({ tags, setTags, ...rest }: Props) => {
|
||||
<div className="relative w-full">
|
||||
<Label>タグ</Label>
|
||||
<TextArea
|
||||
{...rest}
|
||||
ref={ref}
|
||||
value={tags}
|
||||
onChange={ev => setTags (ev.target.value)}
|
||||
@@ -85,11 +85,11 @@ export default (({ tags, setTags, ...rest }: Props) => {
|
||||
await recompute (pos)
|
||||
}}
|
||||
onFocus={() => setFocused (true)}
|
||||
onBlur={() => {
|
||||
onBlur={ev => {
|
||||
setFocused (false)
|
||||
setSuggestionsVsbl (false)
|
||||
}}
|
||||
{...rest}/>
|
||||
onBlur?.(ev)
|
||||
}}/>
|
||||
{focused && (
|
||||
<TagSearchBox
|
||||
suggestions={suggestionsVsbl && suggestions.length > 0
|
||||
|
||||
@@ -34,6 +34,7 @@ export default (({ value, onChange, className, onBlur, ...rest }: Props) => {
|
||||
|
||||
return (
|
||||
<input
|
||||
{...rest}
|
||||
className={cn ('border rounded p-2', className)}
|
||||
type="datetime-local"
|
||||
value={local}
|
||||
@@ -42,6 +43,5 @@ export default (({ value, onChange, className, onBlur, ...rest }: Props) => {
|
||||
setLocal (v)
|
||||
onChange?.(v ? (new Date (v)).toISOString () : null)
|
||||
}}
|
||||
onBlur={onBlur}
|
||||
{...rest}/>)
|
||||
onBlur={onBlur}/>)
|
||||
}) satisfies FC<Props>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle } from '@/components/ui/dialog'
|
||||
|
||||
import type { FC, ReactNode } from 'react'
|
||||
@@ -118,13 +119,15 @@ export default (({ children }: Props) => {
|
||||
closeActive (active?.kind !== 'confirm' && null)
|
||||
}}>
|
||||
{active && (
|
||||
<DialogContent>
|
||||
<DialogTitle>{active.options.title}</DialogTitle>
|
||||
<DialogContent className="px-6 pb-6 pt-7">
|
||||
<DialogHeader className="pl-8">
|
||||
<DialogTitle>{active.options.title}</DialogTitle>
|
||||
|
||||
{active.options.description && (
|
||||
<DialogDescription asChild>
|
||||
<div>{active.options.description}</div>
|
||||
</DialogDescription>)}
|
||||
{active.options.description && (
|
||||
<DialogDescription asChild>
|
||||
<div>{active.options.description}</div>
|
||||
</DialogDescription>)}
|
||||
</DialogHeader>
|
||||
|
||||
<DialogFooter>
|
||||
{active.kind === 'confirm' && (
|
||||
|
||||
@@ -4,34 +4,47 @@ import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
const buttonVariants = cva (
|
||||
[
|
||||
'inline-flex items-center justify-center gap-2 whitespace-nowrap',
|
||||
'rounded-md text-sm font-medium transition-colors',
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400',
|
||||
'disabled:pointer-events-none disabled:opacity-50',
|
||||
'[&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
||||
].join (' '),
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
default:
|
||||
'bg-slate-900 text-white hover:bg-slate-700 dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-slate-300',
|
||||
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
'bg-red-600 text-white hover:bg-red-700 dark:bg-red-700 dark:hover:bg-red-600',
|
||||
|
||||
outline:
|
||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
'border border-slate-300 bg-white text-slate-900 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800',
|
||||
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
'bg-slate-100 text-slate-900 hover:bg-slate-200 dark:bg-slate-800 dark:text-slate-100 dark:hover:bg-slate-700',
|
||||
|
||||
ghost:
|
||||
'text-slate-900 hover:bg-slate-100 dark:text-slate-100 dark:hover:bg-slate-800',
|
||||
|
||||
link:
|
||||
'text-blue-700 underline-offset-4 hover:underline dark:text-blue-300',
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
sm: "h-9 rounded-md px-3",
|
||||
lg: "h-11 rounded-md px-8",
|
||||
icon: "h-10 w-10",
|
||||
default: 'h-10 px-4 py-2',
|
||||
sm: 'h-9 rounded-md px-3',
|
||||
lg: 'h-11 rounded-md px-8',
|
||||
icon: 'h-10 w-10',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
|
||||
@@ -50,14 +50,16 @@ const DialogContent = React.forwardRef<
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
||||
<DialogPrimitive.Close
|
||||
className={cn (
|
||||
'absolute right-4 top-4 rounded-full p-1',
|
||||
'text-muted-foreground opacity-70 transition-opacity',
|
||||
'hover:bg-accent hover:text-accent-foreground hover:opacity-100',
|
||||
'focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2')}>
|
||||
'absolute left-4 top-4 rounded-full p-1',
|
||||
'text-slate-500 transition-colors',
|
||||
'hover:bg-slate-200 hover:text-slate-900',
|
||||
'dark:text-slate-400 dark:hover:bg-slate-700 dark:hover:text-slate-50',
|
||||
'focus:outline-none focus:ring-2 focus:ring-slate-400')}>
|
||||
<X className="h-4 w-4"/>
|
||||
<span className="sr-only">Close</span>
|
||||
<span className="sr-only">閉ぢる</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
|
||||
Reference in New Issue
Block a user