コミットを比較

...

4 コミット

作成者 SHA1 メッセージ 日付
みてるぞ f8e8d8fbb0 #268 2026-07-07 22:35:09 +09:00
みてるぞ 33f9c6602f #268 2026-07-07 22:34:58 +09:00
みてるぞ 2d6af0aa5b #268 2026-07-07 21:01:57 +09:00
みてるぞ c0879ac117 #268 2026-07-07 20:57:12 +09:00
2個のファイルの変更82行の追加27行の削除
+16 -9
ファイルの表示
@@ -57,7 +57,8 @@ const DraggableDroppableTagRow: FC<Props> = ({ tag, nestLevel, pathKey, parentTa
isDragging: dragging } = useDraggable ({ id: dndId, isDragging: dragging } = useDraggable ({ id: dndId,
data: { kind: 'tag', data: { kind: 'tag',
tagId: tag.id, tagId: tag.id,
parentTagId } }) parentTagId,
nestLevel } })
const { setNodeRef: setDropRef, isOver: over } = useDroppable ({ const { setNodeRef: setDropRef, isOver: over } = useDroppable ({
id: dndId, id: dndId,
@@ -77,12 +78,16 @@ const DraggableDroppableTagRow: FC<Props> = ({ tag, nestLevel, pathKey, parentTa
return return
const dx = e.clientX - p.x const dx = e.clientX - p.x
const dy = e.clientY - p.y const dy = e.clientY - p.y
if (dx * dx + dy * dy >= 9) if (dx * dx + dy * dy >= 9)
armEatNextClick () armEatNextClick ()
}} }}
onPointerUpCapture={() => { onPointerUpCapture={() => {
downPosRef.current = null downPosRef.current = null
}} }}
onPointerCancelCapture={() => {
downPosRef.current = null
}}
onClickCapture={e => { onClickCapture={e => {
if (suppressClickRef.current) if (suppressClickRef.current)
{ {
@@ -90,18 +95,20 @@ const DraggableDroppableTagRow: FC<Props> = ({ tag, nestLevel, pathKey, parentTa
e.stopPropagation () e.stopPropagation ()
} }
}} }}
ref={node => { ref={setDropRef}
setDragRef (node)
setDropRef (node)
}}
style={style}
className={cn ( className={cn (
'min-w-0 max-w-full overflow-hidden rounded select-none', 'min-w-0 max-w-full overflow-hidden rounded select-none',
sp && 'touch-pan-y',
over && 'ring-2 ring-offset-2')} over && 'ring-2 ring-offset-2')}
{...attributes} >
{...listeners}>
<motion.div <motion.div
className="flex min-w-0 max-w-full items-baseline overflow-hidden" ref={setDragRef}
style={style}
className={cn (
'inline-flex min-w-0 max-w-full items-baseline overflow-hidden',
sp && 'touch-pan-y')}
{...attributes}
{...listeners}
transition={{ layout: layoutTransition }} transition={{ layout: layoutTransition }}
layoutId={animationMode === 'off' layoutId={animationMode === 'off'
? undefined ? undefined
+66 -18
ファイルの表示
@@ -1,7 +1,9 @@
import { DndContext, import { DndContext,
DragOverlay, DragOverlay,
MeasuringStrategy,
MouseSensor, MouseSensor,
TouchSensor, TouchSensor,
pointerWithin,
useDroppable, useDroppable,
useSensor, useSensor,
useSensors } from '@dnd-kit/core' useSensors } from '@dnd-kit/core'
@@ -25,13 +27,20 @@ import { postsKeys, tagsKeys } from '@/lib/queryKeys'
import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings' import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings'
import { dateString, originalCreatedAtString } from '@/lib/utils' import { dateString, originalCreatedAtString } from '@/lib/utils'
import type { DragEndEvent } from '@dnd-kit/core' import type { CollisionDetection, DragEndEvent } from '@dnd-kit/core'
import type { FC, MutableRefObject, ReactNode } from 'react' import type { FC, MutableRefObject, ReactNode } from 'react'
import type { Category, Post, TagWithSections } from '@/types' import type { Category, Post, TagWithSections } from '@/types'
type TagByCategory = { [key in Category]: TagWithSections[] } type TagByCategory = { [key in Category]: TagWithSections[] }
const tagCollisionDetection: CollisionDetection = args => {
return pointerWithin (args)
}
const alwaysMeasureDroppables = {
droppable: { strategy: MeasuringStrategy.Always } } as const
const renderTagTree = ( const renderTagTree = (
tag: TagWithSections, tag: TagWithSections,
@@ -177,12 +186,34 @@ const DropSlot = ({ cat }: { cat: Category }) => {
</li>) </li>)
} }
const EmptyCategoryDropSection = (
{ cat, children }: { cat: Category
children: ReactNode }) => {
const { setNodeRef, isOver: over } = useDroppable ({
id: `slot:${ cat }`,
data: { kind: 'slot', cat } })
return (
<div ref={setNodeRef} className="my-3">
{children}
<ul>
<li className="h-1">
{over && <div className="h-0.5 w-full rounded bg-sky-400"/>}
</li>
</ul>
</div>)
}
type Props = { type Props = {
className?: string className?: string
post: Post post: Post
sp?: boolean } sp?: boolean }
type ActiveTagDrag = {
tagId: number
nestLevel: number }
const TagDetailSidebar: FC<Props> = ({ className, post, sp }) => { const TagDetailSidebar: FC<Props> = ({ className, post, sp }) => {
sp = Boolean (sp) sp = Boolean (sp)
@@ -212,7 +243,7 @@ const TagDetailSidebar: FC<Props> = ({ className, post, sp }) => {
return tagsTmp return tagsTmp
}, [post]) }, [post])
const [activeTagId, setActiveTagId] = useState<number | null> (null) const [activeTagDrag, setActiveTagDrag] = useState<ActiveTagDrag | null> (null)
const [dragging, setDragging] = useState (false) const [dragging, setDragging] = useState (false)
const [saving, setSaving] = useState (false) const [saving, setSaving] = useState (false)
const [tags, setTags] = useState (baseTags) const [tags, setTags] = useState (baseTags)
@@ -328,9 +359,18 @@ const TagDetailSidebar: FC<Props> = ({ className, post, sp }) => {
<TagSearch/> <TagSearch/>
<DndContext <DndContext
sensors={sensors} sensors={sensors}
collisionDetection={sp ? tagCollisionDetection : undefined}
measuring={sp ? alwaysMeasureDroppables : undefined}
onDragStart={e => { onDragStart={e => {
if (e.active.data.current?.kind === 'tag') if (e.active.data.current?.kind === 'tag')
setActiveTagId (e.active.data.current?.tagId ?? null) {
const tagId = e.active.data.current?.tagId
const nestLevel = e.active.data.current?.nestLevel
setActiveTagDrag (
tagId == null || nestLevel == null
? null
: { tagId, nestLevel })
}
setDragging (true) setDragging (true)
suppressClickRef.current = true suppressClickRef.current = true
document.body.style.userSelect = 'none' document.body.style.userSelect = 'none'
@@ -341,13 +381,13 @@ const TagDetailSidebar: FC<Props> = ({ className, post, sp }) => {
suppressClickRef.current = false}, { capture: true, once: true }) suppressClickRef.current = false}, { capture: true, once: true })
}} }}
onDragCancel={() => { onDragCancel={() => {
setActiveTagId (null) setActiveTagDrag (null)
setDragging (false) setDragging (false)
document.body.style.userSelect = '' document.body.style.userSelect = ''
suppressClickRef.current = false suppressClickRef.current = false
}} }}
onDragEnd={async e => { onDragEnd={async e => {
setActiveTagId (null) setActiveTagDrag (null)
setDragging (false) setDragging (false)
await onDragEnd (e) await onDragEnd (e)
document.body.style.userSelect = '' document.body.style.userSelect = ''
@@ -362,18 +402,26 @@ const TagDetailSidebar: FC<Props> = ({ className, post, sp }) => {
if (!(categoryTags.length > 0 || dragging)) if (!(categoryTags.length > 0 || dragging))
return null return null
const sectionTitle = (
<SubsectionTitle>
<motion.div
layoutId={animationMode === 'off'
? undefined
: `tag-${ sp ? 'sp-' : '' }${ cat }`}
transition={{ layout: layoutTransition }}>
{CATEGORY_NAMES[cat]}
</motion.div>
</SubsectionTitle>)
if (!(categoryTags.length > 0))
return (
<EmptyCategoryDropSection cat={cat} key={cat}>
{sectionTitle}
</EmptyCategoryDropSection>)
return ( return (
<div className="my-3" key={cat}> <div className="my-3" key={cat}>
<SubsectionTitle> {sectionTitle}
<motion.div
layoutId={animationMode === 'off'
? undefined
: `tag-${ sp ? 'sp-' : '' }${ cat }`}
transition={{ layout: layoutTransition }}>
{CATEGORY_NAMES[cat]}
</motion.div>
</SubsectionTitle>
<ul> <ul>
{(tagRelationDisplay === 'grouped' {(tagRelationDisplay === 'grouped'
? (tags[cat] ?? []).flatMap (tag => ? (tags[cat] ?? []).flatMap (tag =>
@@ -443,9 +491,9 @@ const TagDetailSidebar: FC<Props> = ({ className, post, sp }) => {
<DragOverlay adjustScale={false}> <DragOverlay adjustScale={false}>
<div className="pointer-events-none"> <div className="pointer-events-none">
{activeTagId != null && (() => { {activeTagDrag != null && (() => {
const tag = findTag (tags, activeTagId) const tag = findTag (tags, activeTagDrag.tagId)
return tag && <TagLink tag={tag}/> return tag && <TagLink tag={tag} nestLevel={activeTagDrag.nestLevel}/>
}) ()} }) ()}
</div> </div>
</DragOverlay> </DragOverlay>