@@ -2,7 +2,13 @@ import discourseConfigRef from "~/utils/discourseConfigRef";
22import React , { useCallback , useEffect , useMemo , useState } from "react" ;
33import AutocompleteInput from "roamjs-components/components/AutocompleteInput" ;
44import getAllPageNames from "roamjs-components/queries/getAllPageNames" ;
5- import { Button , Dialog , Collapse , InputGroup , Icon } from "@blueprintjs/core" ;
5+ import {
6+ Button ,
7+ ButtonGroup ,
8+ Collapse ,
9+ Dialog ,
10+ InputGroup ,
11+ } from "@blueprintjs/core" ;
612import createBlock from "roamjs-components/writes/createBlock" ;
713import deleteBlock from "roamjs-components/writes/deleteBlock" ;
814import type { RoamBasicNode } from "roamjs-components/types" ;
@@ -26,11 +32,19 @@ const SectionItem = memo(
2632 setSettingsDialogSectionUid,
2733 pageNames,
2834 setSections,
35+ index,
36+ isFirst,
37+ isLast,
38+ onMoveSection,
2939 } : {
3040 section : LeftSidebarPersonalSectionConfig ;
3141 setSections : Dispatch < SetStateAction < LeftSidebarPersonalSectionConfig [ ] > > ;
3242 setSettingsDialogSectionUid : ( uid : string | null ) => void ;
3343 pageNames : string [ ] ;
44+ index : number ;
45+ isFirst : boolean ;
46+ isLast : boolean ;
47+ onMoveSection : ( index : number , direction : "up" | "down" ) => void ;
3448 } ) => {
3549 const ref = extractRef ( section . text ) ;
3650 const blockText = getTextByBlockUid ( ref ) ;
@@ -203,6 +217,51 @@ const SectionItem = memo(
203217 [ setSections ] ,
204218 ) ;
205219
220+ const moveChild = useCallback (
221+ (
222+ section : LeftSidebarPersonalSectionConfig ,
223+ index : number ,
224+ direction : "up" | "down" ,
225+ ) => {
226+ if ( ! section . children ) return ;
227+ if ( direction === "up" && index === 0 ) return ;
228+ if ( direction === "down" && index === section . children . length - 1 )
229+ return ;
230+
231+ const newChildren = [ ...section . children ] ;
232+ const [ removed ] = newChildren . splice ( index , 1 ) ;
233+ const newIndex = direction === "up" ? index - 1 : index + 1 ;
234+ newChildren . splice ( newIndex , 0 , removed ) ;
235+
236+ setSections ( ( prev ) =>
237+ prev . map ( ( s ) => {
238+ if ( s . uid === section . uid ) {
239+ return {
240+ ...s ,
241+ children : newChildren ,
242+ } ;
243+ }
244+ return s ;
245+ } ) ,
246+ ) ;
247+
248+ if ( section . childrenUid ) {
249+ const order = direction === "down" ? newIndex + 1 : newIndex ;
250+
251+ void window . roamAlphaAPI
252+ /* eslint-disable @typescript-eslint/naming-convention */
253+ . moveBlock ( {
254+ location : { "parent-uid" : section . childrenUid , order } ,
255+ block : { uid : removed . uid } ,
256+ } )
257+ . then ( ( ) => {
258+ refreshAndNotify ( ) ;
259+ } ) ;
260+ }
261+ } ,
262+ [ setSections ] ,
263+ ) ;
264+
206265 const handleAddChild = useCallback ( async ( ) => {
207266 if ( childInput && section . childrenUid ) {
208267 await addChildToSection ( section , section . childrenUid , childInput ) ;
@@ -224,7 +283,7 @@ const SectionItem = memo(
224283 border : "1px solid rgba(51, 51, 51, 0.2)" ,
225284 } }
226285 >
227- < div className = "flex items-center" >
286+ < div className = "group flex items-center" >
228287 { ! sectionWithoutSettingsAndChildren && (
229288 < Button
230289 icon = { isExpanded ? "chevron-down" : "chevron-right" }
@@ -245,27 +304,43 @@ const SectionItem = memo(
245304 >
246305 < span className = "font-medium" > { originalName } </ span >
247306 </ div >
248- < Button
249- icon = { sectionWithoutSettingsAndChildren ? "plus" : "settings" }
250- minimal
251- title = {
252- sectionWithoutSettingsAndChildren
253- ? "Add children"
254- : "Edit section settings"
255- }
256- onClick = { ( ) =>
257- sectionWithoutSettingsAndChildren
258- ? void convertToComplexSection ( section )
259- : void setSettingsDialogSectionUid ( section . uid )
260- }
261- />
262- < Button
263- icon = "trash"
264- minimal
265- intent = "danger"
266- onClick = { ( ) => void removeSection ( section ) }
267- title = "Remove section"
268- />
307+ < ButtonGroup minimal >
308+ < Button
309+ icon = "arrow-up"
310+ small
311+ disabled = { isFirst }
312+ onClick = { ( ) => onMoveSection ( index , "up" ) }
313+ title = "Move section up"
314+ className = "opacity-0 transition-opacity group-hover:opacity-100"
315+ />
316+ < Button
317+ icon = "arrow-down"
318+ small
319+ disabled = { isLast }
320+ onClick = { ( ) => onMoveSection ( index , "down" ) }
321+ title = "Move section down"
322+ className = "opacity-0 transition-opacity group-hover:opacity-100"
323+ />
324+ < Button
325+ icon = { sectionWithoutSettingsAndChildren ? "plus" : "settings" }
326+ title = {
327+ sectionWithoutSettingsAndChildren
328+ ? "Add children"
329+ : "Edit section settings"
330+ }
331+ onClick = { ( ) =>
332+ sectionWithoutSettingsAndChildren
333+ ? void convertToComplexSection ( section )
334+ : void setSettingsDialogSectionUid ( section . uid )
335+ }
336+ />
337+ < Button
338+ icon = "trash"
339+ intent = "danger"
340+ onClick = { ( ) => void removeSection ( section ) }
341+ title = "Remove section"
342+ />
343+ </ ButtonGroup >
269344 </ div >
270345
271346 { ! sectionWithoutSettingsAndChildren && (
@@ -301,20 +376,41 @@ const SectionItem = memo(
301376
302377 { ( section . children || [ ] ) . length > 0 && (
303378 < div className = "space-y-1" >
304- { ( section . children || [ ] ) . map ( ( child ) => (
379+ { ( section . children || [ ] ) . map ( ( child , index ) => (
305380 < div
306381 key = { child . uid }
307- className = "flex items-center justify-between rounded bg-gray-50 p-2 hover:bg-gray-100"
382+ className = "group flex items-center justify-between rounded bg-gray-50 p-2 hover:bg-gray-100"
308383 >
309- < span className = "flex-grow" > { child . text } </ span >
310- < Button
311- icon = "trash"
312- minimal
313- small
314- intent = "danger"
315- onClick = { ( ) => void removeChild ( section , child ) }
316- title = "Remove child"
317- />
384+ < div className = "mr-2 min-w-0 flex-1 truncate" >
385+ { child . text }
386+ </ div >
387+ < ButtonGroup minimal className = "flex-shrink-0" >
388+ < Button
389+ icon = "arrow-up"
390+ small
391+ disabled = { index === 0 }
392+ onClick = { ( ) => moveChild ( section , index , "up" ) }
393+ title = "Move child up"
394+ className = "opacity-0 transition-opacity group-hover:opacity-100"
395+ />
396+ < Button
397+ icon = "arrow-down"
398+ small
399+ disabled = {
400+ index === ( section . children || [ ] ) . length - 1
401+ }
402+ onClick = { ( ) => moveChild ( section , index , "down" ) }
403+ title = "Move child down"
404+ className = "opacity-0 transition-opacity group-hover:opacity-100"
405+ />
406+ < Button
407+ icon = "trash"
408+ small
409+ intent = "danger"
410+ onClick = { ( ) => void removeChild ( section , child ) }
411+ title = "Remove child"
412+ />
413+ </ ButtonGroup >
318414 </ div >
319415 ) ) }
320416 </ div >
@@ -424,6 +520,35 @@ const LeftSidebarPersonalSectionsContent = ({
424520 setNewSectionInput ( value ) ;
425521 } , [ ] ) ;
426522
523+ const moveSection = useCallback (
524+ ( index : number , direction : "up" | "down" ) => {
525+ if ( direction === "up" && index === 0 ) return ;
526+ if ( direction === "down" && index === sections . length - 1 ) return ;
527+
528+ const newSections = [ ...sections ] ;
529+ const [ removed ] = newSections . splice ( index , 1 ) ;
530+ const newIndex = direction === "up" ? index - 1 : index + 1 ;
531+ newSections . splice ( newIndex , 0 , removed ) ;
532+
533+ setSections ( newSections ) ;
534+
535+ if ( personalSectionUid ) {
536+ const order = direction === "down" ? newIndex + 1 : newIndex ;
537+
538+ void window . roamAlphaAPI
539+ /* eslint-disable @typescript-eslint/naming-convention */
540+ . moveBlock ( {
541+ location : { "parent-uid" : personalSectionUid , order } ,
542+ block : { uid : removed . uid } ,
543+ } )
544+ . then ( ( ) => {
545+ refreshAndNotify ( ) ;
546+ } ) ;
547+ }
548+ } ,
549+ [ sections , personalSectionUid ] ,
550+ ) ;
551+
427552 const activeDialogSection = useMemo ( ( ) => {
428553 return sections . find ( ( s ) => s . uid === settingsDialogSectionUid ) || null ;
429554 } , [ sections , settingsDialogSectionUid ] ) ;
@@ -470,13 +595,17 @@ const LeftSidebarPersonalSectionsContent = ({
470595 </ div >
471596
472597 < div className = "mt-2 space-y-2" >
473- { sections . map ( ( section ) => (
598+ { sections . map ( ( section , index ) => (
474599 < div key = { section . uid } >
475600 < SectionItem
476601 section = { section }
477602 setSettingsDialogSectionUid = { setSettingsDialogSectionUid }
478603 pageNames = { pageNames }
479604 setSections = { setSections }
605+ index = { index }
606+ isFirst = { index === 0 }
607+ isLast = { index === sections . length - 1 }
608+ onMoveSection = { moveSection }
480609 />
481610 </ div >
482611 ) ) }
0 commit comments