@@ -29,11 +29,13 @@ import {
2929 settingKeys ,
3030} from "~/components/settings/utils/settingsEmitter" ;
3131import {
32+ isQueryBlockRef ,
3233 type LeftSidebarConfig ,
3334 type LeftSidebarPersonalSectionConfig ,
3435 mergeGlobalSectionWithAccessor ,
3536 mergePersonalSectionsWithAccessor ,
3637} from "~/utils/getLeftSidebarSettings" ;
38+ import runQuery from "~/utils/runQuery" ;
3739import { sectionsToBlockProps } from "./settings/LeftSidebarPersonalSettings" ;
3840import discourseConfigRef , { notify } from "~/utils/discourseConfigRef" ;
3941import { getLeftSidebarSettings } from "~/utils/getLeftSidebarSettings" ;
@@ -347,6 +349,169 @@ const PersonalSectionItem = ({
347349 ) ;
348350} ;
349351
352+ const QuerySectionItem = ( {
353+ section,
354+ sectionIndex,
355+ dragHandle,
356+ onloadArgs,
357+ } : {
358+ section : LeftSidebarPersonalSectionConfig ;
359+ sectionIndex : number ;
360+ dragHandle : SortableHandle ;
361+ onloadArgs : OnloadArgs ;
362+ } ) => {
363+ const queryUid = extractRef ( section . text ) ;
364+ const alias = section . settings ?. alias ?. value ;
365+ const queryLabel = useMemo ( ( ) => getTextByBlockUid ( queryUid ) , [ queryUid ] ) ;
366+ const displayName = alias || queryLabel || section . text ;
367+ const truncateAt = section . settings ?. truncateResult . value ;
368+ const resultLimit = Math . max (
369+ 0 ,
370+ Math . trunc ( section . settings ?. resultLimit ?. value ?? 10 ) ,
371+ ) ;
372+
373+ const [ isOpen , setIsOpen ] = useState < boolean > (
374+ ! ! section . settings ?. folded . value ,
375+ ) ;
376+ const [ results , setResults ] = useState < ChildNode [ ] > ( [ ] ) ;
377+ const [ isLoading , setIsLoading ] = useState ( false ) ;
378+ const [ hasCompletedInitialLoad , setHasCompletedInitialLoad ] = useState ( false ) ;
379+ const [ error , setError ] = useState < string | null > ( null ) ;
380+ const [ isMenuOpen , setIsMenuOpen ] = useState ( false ) ;
381+ const isTogglingRef = useRef ( false ) ;
382+
383+ const loadResults = useCallback ( async ( ) => {
384+ setIsLoading ( true ) ;
385+ setError ( null ) ;
386+ try {
387+ const { allProcessedResults } = await runQuery ( {
388+ parentUid : queryUid ,
389+ extensionAPI : onloadArgs . extensionAPI ,
390+ } ) ;
391+ const children : ChildNode [ ] = allProcessedResults . map ( ( r ) => {
392+ const isPage = ! ! getPageTitleByPageUid ( r . uid ) ;
393+ return {
394+ uid : r . uid ,
395+ text : isPage ? r . uid : `((${ r . uid } ))` ,
396+ } ;
397+ } ) ;
398+ setResults ( children ) ;
399+ } catch ( e ) {
400+ console . error ( e ) ;
401+ setError ( "Query failed to run" ) ;
402+ } finally {
403+ setIsLoading ( false ) ;
404+ setHasCompletedInitialLoad ( true ) ;
405+ }
406+ } , [ queryUid , onloadArgs . extensionAPI ] ) ;
407+
408+ useEffect ( ( ) => {
409+ if ( isOpen && ! hasCompletedInitialLoad ) {
410+ void loadResults ( ) ;
411+ }
412+ } , [ isOpen , hasCompletedInitialLoad , loadResults ] ) ;
413+
414+ const handleChevronClick = async ( ) => {
415+ if ( ! section . settings ) return ;
416+ if ( isTogglingRef . current ) return ;
417+ isTogglingRef . current = true ;
418+ try {
419+ await toggleFoldedState ( {
420+ isOpen,
421+ setIsOpen,
422+ folded : section . settings . folded ,
423+ parentUid : section . settings . uid || "" ,
424+ sectionIndex,
425+ } ) ;
426+ } finally {
427+ isTogglingRef . current = false ;
428+ }
429+ } ;
430+
431+ const limitedResults =
432+ resultLimit > 0 ? results . slice ( 0 , resultLimit ) : results ;
433+
434+ let body : React . ReactNode = null ;
435+ if ( isLoading ) {
436+ body = < div className = "pl-8 pr-2.5 text-sm text-gray-500" > Loading…</ div > ;
437+ } else if ( error ) {
438+ body = < div className = "pl-8 pr-2.5 text-sm text-red-500" > { error } </ div > ;
439+ } else if ( limitedResults . length > 0 ) {
440+ body = limitedResults . map ( ( child ) => (
441+ < ChildRow
442+ key = { child . uid }
443+ child = { child }
444+ truncateAt = { truncateAt }
445+ onloadArgs = { onloadArgs }
446+ />
447+ ) ) ;
448+ } else if ( hasCompletedInitialLoad ) {
449+ body = < div className = "pl-8 pr-2.5 text-sm text-gray-500" > No results</ div > ;
450+ }
451+
452+ return (
453+ < >
454+ < div
455+ { ...dragHandle . attributes }
456+ { ...dragHandle . listeners }
457+ className = "sidebar-title-button flex w-full cursor-pointer items-center border-none bg-transparent pl-6 pr-2.5 font-semibold outline-none"
458+ >
459+ < div className = "flex w-full items-center justify-between" >
460+ < div
461+ className = "flex flex-1 items-center"
462+ onClick = { ( ) => void handleChevronClick ( ) }
463+ >
464+ { displayName . toUpperCase ( ) }
465+ </ div >
466+ < span
467+ className = "sidebar-title-button-chevron p-1"
468+ onClick = { ( ) => void handleChevronClick ( ) }
469+ >
470+ < Icon icon = { isOpen ? "chevron-down" : "chevron-right" } />
471+ </ span >
472+ < Popover
473+ interactionKind = { PopoverInteractionKind . CLICK }
474+ position = { Position . BOTTOM_RIGHT }
475+ autoFocus = { false }
476+ enforceFocus = { false }
477+ captureDismiss
478+ isOpen = { isMenuOpen }
479+ onInteraction = { ( next ) => setIsMenuOpen ( next ) }
480+ onClose = { ( ) => setIsMenuOpen ( false ) }
481+ popoverClassName = "dg-leftsidebar-popover"
482+ minimal
483+ content = {
484+ < Menu >
485+ < MenuItem
486+ icon = "refresh"
487+ text = "Refresh"
488+ onClick = { ( ) => {
489+ void loadResults ( ) ;
490+ setIsMenuOpen ( false ) ;
491+ } }
492+ />
493+ < MenuItem
494+ icon = "document-open"
495+ text = "Go to query block"
496+ onClick = { ( e ) => {
497+ void openTarget ( e , `((${ queryUid } ))` , onloadArgs ) ;
498+ setIsMenuOpen ( false ) ;
499+ } }
500+ />
501+ </ Menu >
502+ }
503+ >
504+ < span className = "sidebar-title-button-add p-1" >
505+ < Icon icon = "more" size = { 14 } />
506+ </ span >
507+ </ Popover >
508+ </ div >
509+ </ div >
510+ < Collapse isOpen = { isOpen } > { body } </ Collapse >
511+ </ >
512+ ) ;
513+ } ;
514+
350515const PersonalSections = ( {
351516 config,
352517 setConfig,
@@ -424,15 +589,28 @@ const PersonalSections = ({
424589 getId = { ( s ) => s . uid }
425590 onReorder = { reorderSections }
426591 className = "personal-left-sidebar-sections"
427- renderItem = { ( section , handle ) => (
428- < PersonalSectionItem
429- section = { section }
430- sectionIndex = { sections . findIndex ( ( s ) => s . uid === section . uid ) }
431- dragHandle = { handle }
432- onChildrenReorder = { reorderChildren }
433- onloadArgs = { onloadArgs }
434- />
435- ) }
592+ renderItem = { ( section , handle ) => {
593+ const sectionIndex = sections . findIndex ( ( s ) => s . uid === section . uid ) ;
594+ if ( isQueryBlockRef ( section . text ) && section . settings ?. uid ) {
595+ return (
596+ < QuerySectionItem
597+ section = { section }
598+ sectionIndex = { sectionIndex }
599+ dragHandle = { handle }
600+ onloadArgs = { onloadArgs }
601+ />
602+ ) ;
603+ }
604+ return (
605+ < PersonalSectionItem
606+ section = { section }
607+ sectionIndex = { sectionIndex }
608+ dragHandle = { handle }
609+ onChildrenReorder = { reorderChildren }
610+ onloadArgs = { onloadArgs }
611+ />
612+ ) ;
613+ } }
436614 />
437615 ) ;
438616} ;
0 commit comments