diff --git a/frontend/src/components/TagDetailSidebar.tsx b/frontend/src/components/TagDetailSidebar.tsx
index 57ef996..18256c7 100644
--- a/frontend/src/components/TagDetailSidebar.tsx
+++ b/frontend/src/components/TagDetailSidebar.tsx
@@ -1,4 +1,8 @@
-import { DndContext, PointerSensor, useSensor, useSensors } from '@dnd-kit/core'
+import { DndContext,
+ PointerSensor,
+ useDroppable,
+ useSensor,
+ useSensors } from '@dnd-kit/core'
import { AnimatePresence, motion } from 'framer-motion'
import { useEffect, useRef, useState } from 'react'
@@ -168,6 +172,44 @@ const findTag = (
}
+const detachEdge = (
+ nodes: Tag[],
+ parentId: number,
+ childId: number,
+): Tag[] => nodes.map (t => {
+ if (t.id === parentId)
+ {
+ const children = (t.children ?? []).filter (c => c.id !== childId)
+ return { ...t, children }
+ }
+ return t.children ? { ...t, children: detachEdge (t.children, parentId, childId) } : t
+})
+
+
+const insertRootAt = (
+ roots: Tag[],
+ index: number,
+ tag: Tag,
+): Tag[] => {
+ const without = roots.filter (t => t.id !== tag.id)
+ const next = without.slice ()
+ next.splice (Math.min (Math.max (index, 0), next.length), 0, tag)
+ return next
+}
+
+
+const DropSlot = ({ cat, index }: { cat: Category, index: number }) => {
+ const { setNodeRef, isOver: over } = useDroppable ({
+ id: `slot:${ cat }:${ index }`,
+ data: { kind: 'slot', cat, index } })
+
+ return (
+
+ {over && }
+ )
+}
+
+
type Props = { post: Post | null }
@@ -180,26 +222,58 @@ export default (({ post }: Props) => {
useSensor (PointerSensor, { activationConstraint: { distance: 6 } }))
const onDragEnd = async (e: DragEndEvent) => {
+ const activeKind = e.active.data.current?.kind
+ if (activeKind !== 'tag')
+ return
+
const childId: number | undefined = e.active.data.current?.tagId
- const parentId: number | undefined = e.over?.data.current?.tagId
+ const fromParentId: number | undefined = e.active.data.current?.parentTagId
- if (!(childId) || !(parentId))
- return
+ const overKind = e.over?.data.current?.kind
- if (childId === parentId)
+ if (!(childId) || !(overKind))
return
- setTags (prev => {
- const child = findTag (prev, childId)
- const parent = findTag (prev, parentId)
- if (!(child) || !(parent))
- return prev
+ if (overKind === 'tag')
+ {
+ const parentId: number | undefined = e.over?.data.current?.tagId
+ if (!(parentId) || childId === parentId)
+ return
+
+ setTags (prev => {
+ const child = findTag (prev, childId)
+ const parent = findTag (prev, parentId)
+ if (!(child) || !(parent) || isDescendant (child, parentId))
+ return prev
+
+ return attachChildOptimistic (prev, parentId, childId)
+ })
- if (isDescendant (child, parentId))
- return prev
+ return
+ }
- return attachChildOptimistic (prev, parentId, childId)
- })
+ if (overKind === 'slot')
+ {
+ const cat: Category | undefined = e.over?.data.current?.cat
+ const index: number | undefined = e.over?.data.current?.index
+ if (!(cat) || index == null)
+ return
+
+ setTags (prev => {
+ const child = findTag (prev, childId)
+ if (!(child))
+ return prev
+
+ const next: TagByCategory = { ...prev }
+
+ if (fromParentId)
+ next[cat] = detachEdge (next[cat], fromParentId, childId) as any
+
+ next[cat] = insertRootAt (next[cat], index, child)
+
+ return next
+ })
+ }
}
const categoryNames: Record = {
@@ -260,7 +334,10 @@ export default (({ post }: Props) => {
{tags[cat].map (tag => (
- renderTagTree (tag, 0, `cat-${ cat }`, suppressClickRef, undefined)))}
+ <>
+ {renderTagTree (tag, 0, `cat-${ cat }`, suppressClickRef, undefined)}
+ >))}
+
))}