99 Button ,
1010 Dialog ,
1111 InputGroup ,
12- NonIdealState ,
1312 Spinner ,
1413 SpinnerSize ,
1514 Tag ,
@@ -21,6 +20,7 @@ import getPageTitleByPageUid from "roamjs-components/queries/getPageTitleByPageU
2120import renderOverlay , {
2221 RoamOverlayProps ,
2322} from "roamjs-components/util/renderOverlay" ;
23+ import { createBlock } from "roamjs-components/writes" ;
2424import {
2525 insertPageRefAtRange ,
2626 snapshotInsertTarget ,
@@ -37,14 +37,12 @@ import {
3737 type SearchResult ,
3838 type SortConfig ,
3939 buildSearchIndex ,
40- formatMetadataDate ,
4140 searchIndexedNodes ,
4241 sortSearchResults ,
4342 splitWithHighlights ,
4443 stripTypePrefix ,
4544} from "./utils" ;
4645import { DiscourseNodeTypeFilter } from "~/components/AdvancedNodeSearchDialog/DiscourseNodeTypeFilter" ;
47- import { RenderRoamBlock , RenderRoamPage } from "~/utils/roamReactComponents" ;
4846import { AdvancedSearchFooter } from "./AdvancedSearchFooter" ;
4947
5048type Props = Record < string , unknown > ;
@@ -110,43 +108,6 @@ const ResultRow = ({
110108 </ Button >
111109) ;
112110
113- const PreviewPane = ( { result } : { result : SearchResult | null } ) => {
114- if ( ! result ) {
115- return (
116- < div className = "flex min-h-0 flex-1 items-center justify-center overflow-hidden" >
117- < NonIdealState
118- icon = "search"
119- title = "Search DG nodes"
120- description = "Type a keyword to preview matching discourse graph nodes."
121- />
122- </ div >
123- ) ;
124- }
125- const isPage = ! ! getPageTitleByPageUid ( result . uid ) ;
126-
127- return (
128- < div className = "flex min-h-0 flex-1 flex-col overflow-hidden" >
129- < div className = "flex-none flex-row gap-2 border-b border-gray-200 px-5 py-3 text-xs text-gray-500" >
130- Created: { formatMetadataDate ( result . createdAt ) } · Last modified:{ " " }
131- { formatMetadataDate ( result . lastModified ) } · Author:{ " " }
132- { result . authorName || "Unknown" }
133- </ div >
134- < div
135- className = "min-h-0 flex-1 overflow-y-auto border-t border-gray-200 px-5 py-3"
136- onMouseDown = { ( event ) => event . preventDefault ( ) }
137- >
138- < div className = "pointer-events-none" >
139- { isPage ? (
140- < RenderRoamPage hideMentions key = { result . uid } uid = { result . uid } />
141- ) : (
142- < RenderRoamBlock key = { result . uid } uid = { result . uid } zoomPath />
143- ) }
144- </ div >
145- </ div >
146- </ div >
147- ) ;
148- } ;
149-
150111const AdvancedNodeSearchDialog = ( {
151112 isOpen,
152113 onClose,
@@ -319,6 +280,51 @@ const AdvancedNodeSearchDialog = ({
319280 : ! results . length
320281 ? "empty"
321282 : "results" ;
283+
284+ const onOpenSearchSidebar = useCallback ( async ( ) => {
285+ if ( contentState !== "results" || ! results . length ) return ;
286+
287+ try {
288+ const parentUid =
289+ ( await window . roamAlphaAPI . ui . mainWindow . getOpenPageOrBlockUid ( ) ) ||
290+ window . roamAlphaAPI . util . dateToPageUid ( new Date ( ) ) ;
291+
292+ const sidebarBlockTitle = `Advanced search results: "${ debouncedSearchTerm || "(empty query)" } "` ;
293+ const sidebarChildren = results . map ( ( result ) => ( {
294+ text : `[[${ result . title } ]]` ,
295+ } ) ) ;
296+
297+ const sidebarBlockUid = await createBlock ( {
298+ parentUid,
299+ order : Number . MAX_VALUE ,
300+ node : { text : sidebarBlockTitle , children : sidebarChildren } ,
301+ } ) ;
302+
303+ await window . roamAlphaAPI . ui . rightSidebar . addWindow ( {
304+ window : {
305+ type : "outline" ,
306+ // @ts -expect-error - block-uid is valid for outline sidebar windows
307+ // eslint-disable-next-line @typescript-eslint/naming-convention
308+ "block-uid" : sidebarBlockUid ,
309+ } ,
310+ } ) ;
311+
312+ posthog . capture ( "Advanced Node Search: Open search sidebar" , {
313+ resultCount : results . length ,
314+ searchTerm : debouncedSearchTerm ,
315+ sortDirection : sort . direction ,
316+ sortField : sort . field ,
317+ } ) ;
318+ onClose ( ) ;
319+ } catch ( error ) {
320+ console . error ( "Failed to open search sidebar results block:" , error ) ;
321+ renderToast ( {
322+ id : "advanced-node-search-sidebar-open-error" ,
323+ content : "Could not render search results in the right sidebar." ,
324+ intent : "danger" ,
325+ } ) ;
326+ }
327+ } , [ contentState , debouncedSearchTerm , onClose , results , sort ] ) ;
322328 const handleSortChange = useCallback ( ( nextSort : SortConfig ) : void => {
323329 setSort ( nextSort ) ;
324330 } , [ ] ) ;
@@ -357,6 +363,14 @@ const AdvancedNodeSearchDialog = ({
357363 } else if ( event . key === "ArrowUp" && results . length ) {
358364 event . preventDefault ( ) ;
359365 setActiveIndex ( ( index ) => Math . max ( index - 1 , 0 ) ) ;
366+ } else if (
367+ event . key === "Enter" &&
368+ event . altKey &&
369+ contentState === "results" &&
370+ results . length
371+ ) {
372+ event . preventDefault ( ) ;
373+ void onOpenSearchSidebar ( ) ;
360374 } else if (
361375 event . key === "Enter" &&
362376 ! event . metaKey &&
@@ -386,15 +400,14 @@ const AdvancedNodeSearchDialog = ({
386400 contentState ,
387401 insertTarget ,
388402 onClose ,
403+ onOpenSearchSidebar ,
389404 onInsert ,
390405 onOpen ,
391406 onOpenInSidebar ,
392407 results . length ,
393408 ] ,
394409 ) ;
395410
396- const showSplitView = contentState === "results" ;
397-
398411 return (
399412 < Dialog
400413 autoFocus = { false }
@@ -446,30 +459,25 @@ const AdvancedNodeSearchDialog = ({
446459 />
447460 </ div >
448461 < div className = "flex min-h-0 w-full flex-1 overflow-hidden" >
449- { showSplitView ? (
450- < >
451- < div
452- aria-label = "Search results"
453- className = "w-1/3 shrink-0 overflow-y-auto border-r border-gray-200 py-1"
454- ref = { resultsPanelRef }
455- role = "listbox"
456- >
457- { results . map ( ( result , index ) => (
458- < ResultRow
459- active = { index === activeIndex }
460- key = { result . uid }
461- keywords = { keywords }
462- nodeConfig = { nodeConfigByType [ result . type ] }
463- onClick = { ( ) => setActiveIndex ( index ) }
464- onMouseEnter = { ( ) => setActiveIndex ( index ) }
465- result = { result }
466- />
467- ) ) }
468- </ div >
469- < div className = "flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden" >
470- < PreviewPane result = { activeResult } />
471- </ div >
472- </ >
462+ { contentState === "results" ? (
463+ < div
464+ aria-label = "Search results"
465+ className = "w-full overflow-y-auto py-1"
466+ ref = { resultsPanelRef }
467+ role = "listbox"
468+ >
469+ { results . map ( ( result , index ) => (
470+ < ResultRow
471+ active = { index === activeIndex }
472+ key = { result . uid }
473+ keywords = { keywords }
474+ nodeConfig = { nodeConfigByType [ result . type ] }
475+ onClick = { ( ) => setActiveIndex ( index ) }
476+ onMouseEnter = { ( ) => setActiveIndex ( index ) }
477+ result = { result }
478+ />
479+ ) ) }
480+ </ div >
473481 ) : (
474482 < div className = "flex min-h-0 w-full flex-1 items-center justify-center px-4 py-8 text-center text-sm text-gray-500" >
475483 { contentState === "indexing" && (
@@ -489,19 +497,24 @@ const AdvancedNodeSearchDialog = ({
489497 < AdvancedSearchFooter
490498 contentState = { contentState }
491499 hasActiveResult = { ! ! activeResult }
500+ hasResults = { results . length > 0 }
492501 insertTarget = { insertTarget }
493502 onInsert = { ( ) => void onInsert ( ) }
494503 onOpen = { ( ) => void onOpen ( ) }
495504 onOpenInSidebar = { ( ) => void onOpenInSidebar ( ) }
505+ onOpenSearchSidebar = { ( ) => void onOpenSearchSidebar ( ) }
496506 />
497507 </ div >
498508 </ Dialog >
499509 ) ;
500510} ;
501511
502- export const renderAdvancedNodeSearchDialog = ( ) =>
512+ export const renderAdvancedNodeSearchSidebar = ( ) =>
503513 renderOverlay ( {
504514 // eslint-disable-next-line @typescript-eslint/naming-convention
505515 Overlay : AdvancedNodeSearchDialog ,
506516 props : { } ,
507517 } ) ;
518+
519+ export const renderAdvancedNodeSearchDialog = ( ) =>
520+ renderAdvancedNodeSearchSidebar ( ) ;
0 commit comments