@@ -276,6 +276,8 @@ let Structr = {
276276 } ,
277277 init : ( ) => {
278278 _Helpers . fastRemoveAllChildren ( document . querySelector ( '#errorText' ) ) ;
279+
280+ Structr . globalSearch . init ( ) ;
279281 } ,
280282 clearMain : ( ) => {
281283
@@ -1917,6 +1919,151 @@ let Structr = {
19171919 } ) ;
19181920 } ,
19191921
1922+ globalSearch : {
1923+ init : ( ) => {
1924+ let form = document . querySelector ( '#global-search-node-form' ) ;
1925+ let searchField = form . querySelector ( '[name="queryString"]' ) ;
1926+
1927+ form . addEventListener ( 'submit' , e => {
1928+ e . preventDefault ( ) ;
1929+
1930+ let data = Structr . globalSearch . getBasicFormData ( form ) ;
1931+
1932+ if ( data . queryString . length > 0 ) {
1933+ Structr . globalSearch . doSearch ( data ) ;
1934+ } else {
1935+ form . reportValidity ( ) ;
1936+ }
1937+ } ) ;
1938+
1939+ searchField . addEventListener ( 'search' , ( ) => {
1940+ if ( searchField . value === '' ) {
1941+ Structr . globalSearch . clear ( ) ;
1942+ }
1943+ } ) ;
1944+ } ,
1945+ clear : ( ) => {
1946+ let resultsElement = document . querySelector ( '#global-search-results' ) ;
1947+ for ( let oldResult of resultsElement . querySelectorAll ( '[data-id]' ) ) {
1948+ _Helpers . fastRemoveElement ( oldResult ) ;
1949+ }
1950+ } ,
1951+ doSearch : async ( data ) => {
1952+
1953+ let results = await Command . searchNodes ( data ) ;
1954+
1955+ Structr . globalSearch . clear ( ) ;
1956+
1957+ let resultsElement = document . querySelector ( '#global-search-results' ) ;
1958+
1959+ for ( let result of results ) {
1960+
1961+ for ( let key of result . keys ) {
1962+
1963+ let el = _Helpers . createSingleDOMElementFromHTML ( Structr . globalSearch . templates . result ( result , key ) ) ;
1964+
1965+ resultsElement . appendChild ( el ) ;
1966+
1967+ el . querySelector ( 'button' ) . addEventListener ( 'click' , Structr . globalSearch . goToResultButtonClicked ) ;
1968+ }
1969+ }
1970+ } ,
1971+ goToResultButtonClicked : e => {
1972+
1973+ let id = e . target . closest ( '[data-id]' ) . dataset . id ;
1974+ let key = e . target . closest ( '[data-id]' ) . dataset . key ;
1975+
1976+ let matchingTabUrlHash = '#pages:' + _Pages . search . getTabForKey ( key ) ;
1977+ let link = document . querySelector ( `[href="${ matchingTabUrlHash } "]` )
1978+
1979+ if ( link && id === _Pages . centerPane . dataset [ 'elementId' ] ) {
1980+
1981+ _Pages . activateCenterPane ( link ) ;
1982+
1983+ } else {
1984+
1985+ // preselect tab for that element
1986+ _Pages . saveActiveCenterTab ( id , matchingTabUrlHash ) ;
1987+
1988+ _Pages . selectAndShowArbitraryDOMElement ( id ) ;
1989+ }
1990+ } ,
1991+ getBasicFormData : ( form ) => {
1992+ let data = { } ;
1993+
1994+ for ( let el of form . elements ) {
1995+ switch ( el . type ) {
1996+ case 'checkbox' :
1997+ data [ el . name ] = el . checked ;
1998+ break ;
1999+ default :
2000+ data [ el . name ] = el . value ;
2001+ break ;
2002+ }
2003+ }
2004+
2005+ return data ;
2006+ } ,
2007+ templates : {
2008+ popover : config => `
2009+ <div popover id="global-search-popover" class="absolute" style="
2010+ position-anchor: --global-search;
2011+ position-area: x-start y-end;
2012+ width: 500px;
2013+ height: 700px;
2014+ overflow: auto;
2015+ border: 1px solid var(--input-field-border);
2016+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
2017+ border-radius: .25rem;
2018+ font-size: 1rem;
2019+ resize: both;
2020+ ">
2021+
2022+ <div class="overflow-y-auto max-h-full h-full">
2023+ <div class="mx-4 my-4">
2024+ <form id="global-search-node-form" class="flex flex-col gap-2">
2025+ <div class="flex gap-2">
2026+ <input type="search" name="queryString" required placeholder="Search term...">
2027+ <button type="submit" class="action button btn focus:border-gray-666 active:border-green">Search</button>
2028+ </div>
2029+ <div>
2030+ <label class="flex items-center"><input type="checkbox" checked name="searchDOM">Search Page Elements</label>
2031+ <label class="flex items-center"><input type="checkbox" checked name="searchSchema">Search Schema Code</label>
2032+ <label class="flex items-center"><input type="checkbox" checked name="searchFlow">Search Flow Nodes</label>
2033+ </div>
2034+
2035+ <div>
2036+ <label class="flex items-center"><input type="checkbox" name="caseInsensitive">Case Insensitive</label>
2037+ </div>
2038+ </form>
2039+
2040+ <div id="global-search-results" class="grid items-center gap-x-2 gap-y-3 mt-6" style="grid-template-columns: [ name ] minmax(0, 1fr) [ keys ] minmax(10%, max-content) [ id ] 4rem [ actions ] minmax(2rem, max-content)">
2041+ <div class="contents font-bold">
2042+ <div>Name/Type</div>
2043+ <div>Key</div>
2044+ <div>ID</div>
2045+ <div></div>
2046+ </div>
2047+ </div>
2048+ </div>
2049+ </div>
2050+ </div>
2051+ ` ,
2052+ result : ( result , key ) => `
2053+ <div class="contents" data-id="${ result . id } " data-key="${ key } ">
2054+ <div>${ result . name ? `${ result . name } [${ result . type } ]` : result . type } </div>
2055+ <div>${ key } </div>
2056+ <div class="truncate">${ result . id } </div>
2057+ <div>
2058+ <button class="flex items-center hover:bg-gray-100 focus:border-gray-666 active:border-green p-2 mr-0" title="Go to element">
2059+ ${ _Icons . getSvgIcon ( _Icons . iconOpenInNewPage , 16 , 16 , [ ..._Icons . getSvgIconClassesNonColorIcon ( ) , 'pointer-events-none' ] ) }
2060+ </button>
2061+ </div>
2062+ </div>
2063+ `
2064+ }
2065+ } ,
2066+
19202067 /* basically only exists to get rid of repeating strings. is also used to filter out internal keys from dialogs */
19212068 internalKeys : {
19222069 name : 'name' ,
@@ -1978,14 +2125,29 @@ let Structr = {
19782125 </div>
19792126 </div>
19802127
1981- <div class="flex gap-4 ml-2 mr-6">
2128+ <div class="flex gap-4 items-center mr-6">
2129+
2130+ <div style="target-name: --global-search;">
2131+
2132+ <button class="m-0 p-0 border-0" popovertarget="global-search-popover" style="anchor-name: --global-search;">
2133+ ${ _Icons . getSvgIcon ( _Icons . iconSearch , 24 , 24 , _Icons . getSvgIconClassesForColoredIcon ( [ 'text-white' , 'mt-1' ] ) , 'Global Search' ) }
2134+ </button>
19822135
1983- <a target="_blank" href="${ _Helpers . getPrefixedRootUrl ( '/structr/config' ) } ">${ _Icons . getSvgIconWithID ( 'settings-icon' , _Icons . iconSettingsWrench , 20 , 20 , _Icons . getSvgIconClassesForColoredIcon ( [ 'text-white' , 'mt-1.5' ] ) , 'System Settings' ) } </a>
2136+ ${ Structr . globalSearch . templates . popover ( config ) }
2137+ </div>
19842138
1985- ${ _Icons . getSvgIconWithID ( 'terminal-icon' , _Icons . iconTerminal , 26 , 26 , _Icons . getSvgIconClassesForColoredIcon ( [ 'text-white' ] ) , 'Toggle Console' ) }
2139+ <div>
2140+ <a target="_blank" href="${ _Helpers . getPrefixedRootUrl ( '/structr/config' ) } ">
2141+ ${ _Icons . getSvgIconWithID ( 'settings-icon' , _Icons . iconSettingsWrench , 20 , 20 , _Icons . getSvgIconClassesForColoredIcon ( [ 'text-white' , 'mt-1' ] ) , 'System Settings' ) }
2142+ </a>
2143+ </div>
2144+
2145+ <div>
2146+ ${ _Icons . getSvgIconWithID ( 'terminal-icon' , _Icons . iconTerminal , 26 , 26 , _Icons . getSvgIconClassesForColoredIcon ( [ 'text-white' ] ) , 'Toggle Console' ) }
2147+ </div>
19862148
19872149 <div id="${ Structr . notificationIconId } " class="relative">
1988- ${ _Icons . getSvgIcon ( _Icons . iconNotificationBell , 20 , 20 , _Icons . getSvgIconClassesForColoredIcon ( [ 'text-white' , 'mt-1' ] ) , 'Show notifications' ) }
2150+ ${ _Icons . getSvgIcon ( _Icons . iconNotificationBell , 20 , 20 , _Icons . getSvgIconClassesForColoredIcon ( [ 'text-white' , 'mt-1' ] ) , 'Show notifications' ) }
19892151 <div class="absolute flex items-center rounded-full h-4 -top-1 -right-3 text-white bg-red">
19902152 <div data-notification-count class="px-2 text-xs empty:hidden"></div>
19912153 </div>
0 commit comments