@@ -6,6 +6,26 @@ const FETCH_RESULTS_DELAY = 250;
6
6
const CLEAR_RESULTS_DELAY = 300 ;
7
7
const RTD_SEARCH_PARAMETER = "rtd_search" ;
8
8
9
+ function parseQuery ( search_query ) {
10
+ let query = [ ] ;
11
+ let options = { } ;
12
+ search_query . split ( / \s + / ) . forEach ( function ( term ) {
13
+ let result = term . split ( ":" , 2 ) ;
14
+ if ( result . length < 2 ) {
15
+ query . push ( term ) ;
16
+ } else {
17
+ if ( result [ 0 ] in options ) {
18
+ options [ result [ 0 ] ] . push ( result [ 1 ] ) ;
19
+ } else {
20
+ options [ result [ 0 ] ] = [ result [ 1 ] ] ;
21
+ }
22
+ }
23
+ } ) ;
24
+ query = query . join ( " " ) ;
25
+ return [ query , options ] ;
26
+ }
27
+
28
+
9
29
/**
10
30
* Debounce the function.
11
31
* Usage::
@@ -111,6 +131,12 @@ const updateSearchBar = () => {
111
131
} ;
112
132
113
133
134
+ function setSearchQuery ( query ) {
135
+ let search_outer_input = document . querySelector ( ".search__outer__input" ) ;
136
+ search_outer_input . value = query ;
137
+ }
138
+
139
+
114
140
/*
115
141
* Returns true if the modal window is visible.
116
142
*/
@@ -273,9 +299,10 @@ const generateSingleResult = (resultData, projectName, id) => {
273
299
274
300
// If the result is not from the same project,
275
301
// then it's from a subproject.
276
- if ( projectName !== resultData . project ) {
302
+ const project_slug = resultData . project . slug
303
+ if ( projectName !== project_slug ) {
277
304
let subtitle = createDomNode ( "small" , { class : "rtd_ui_search_subtitle" } ) ;
278
- subtitle . innerText = `(from project ${ resultData . project } )` ;
305
+ subtitle . innerText = ` (from project ${ project_slug } )` ;
279
306
h2_element . appendChild ( subtitle ) ;
280
307
}
281
308
h2_element . appendChild ( createDomNode ( "br" ) )
@@ -489,7 +516,7 @@ const getErrorDiv = err_msg => {
489
516
* @param {String } projectName: name (slug) of the project
490
517
* @return {Function } debounced function with debounce time of 500ms
491
518
*/
492
- const fetchAndGenerateResults = ( api_endpoint , parameters , projectName ) => {
519
+ const fetchAndGenerateResults = ( api_endpoint , parameters ) => {
493
520
let search_outer = document . querySelector ( ".search__outer" ) ;
494
521
495
522
// Removes all results (if there is any),
@@ -518,7 +545,7 @@ const fetchAndGenerateResults = (api_endpoint, parameters, projectName) => {
518
545
if ( data . results . length > 0 ) {
519
546
let search_result_box = generateSuggestionsList (
520
547
data ,
521
- projectName
548
+ parameters . project
522
549
) ;
523
550
removeResults ( ) ;
524
551
search_outer . appendChild ( search_result_box ) ;
@@ -550,28 +577,56 @@ const fetchAndGenerateResults = (api_endpoint, parameters, projectName) => {
550
577
* This html structure will serve as the boilerplate
551
578
* to show our search results.
552
579
*
580
+ * @param {Array } filters: filters to be applied to the search
553
581
* @return {String } initial html structure
554
582
*/
555
- const generateAndReturnInitialHtml = ( ) => {
556
- let innerHTML =
557
- '<div class="search__outer"> \
558
- <div class="search__cross" title="Close"> \
559
- <!--?xml version="1.0" encoding="UTF-8"?--> \
560
- <svg class="search__cross__img" width="15px" height="15px" enable-background="new 0 0 612 612" version="1.1" viewBox="0 0 612 612" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"> \
561
- <polygon points="612 36.004 576.52 0.603 306 270.61 35.478 0.603 0 36.004 270.52 306.01 0 576 35.478 611.4 306 341.41 576.52 611.4 612 576 341.46 306.01"></polygon> \
562
- </svg> \
563
- </div> \
564
- <input class="search__outer__input" placeholder="Search ..."> \
565
- <span class="bar"></span> \
566
- </div> \
567
- <div class="rtd__search__credits"> \
568
- Search by <a href="https://readthedocs.org/">Read the Docs</a> & <a href="https://readthedocs-sphinx-search.readthedocs.io/en/latest/">readthedocs-sphinx-search</a> \
569
- </div>' ;
583
+ const generateAndReturnInitialHtml = ( filters ) => {
584
+ let innerHTML = `
585
+ <div class="search__outer">
586
+ <div class="search__cross" title="Close">
587
+ <!--?xml version="1.0" encoding="UTF-8"?-->
588
+ <svg class="search__cross__img" width="15px" height="15px" enable-background="new 0 0 612 612" version="1.1" viewBox="0 0 612 612" xml:space="preserve" xmlns="http://www.w3.org/2000/svg">
589
+ <polygon points="612 36.004 576.52 0.603 306 270.61 35.478 0.603 0 36.004 270.52 306.01 0 576 35.478 611.4 306 341.41 576.52 611.4 612 576 341.46 306.01"></polygon>
590
+ </svg>
591
+ </div>
592
+ <input class="search__outer__input" placeholder="Search...">
593
+ <div class="bar"></div>
594
+ <div class="search__filters">
595
+ <ul>
596
+ </ul>
597
+ </div>
598
+ </div>
599
+ <div class="rtd__search__credits">
600
+ Search by <a href="https://readthedocs.org/">Read the Docs</a> & <a href="https://readthedocs-sphinx-search.readthedocs.io/en/latest/">readthedocs-sphinx-search</a>
601
+ </div>
602
+ ` ;
570
603
571
604
let div = createDomNode ( "div" , {
572
605
class : "search__outer__wrapper search__backdrop" ,
573
606
} ) ;
574
607
div . innerHTML = innerHTML ;
608
+ let filters_list = div . querySelector ( ".search__filters ul" ) ;
609
+ const config = getConfig ( ) ;
610
+ for ( const [ filter , value ] of filters ) {
611
+ let li = createDomNode ( "li" ) ;
612
+ let button = createDomNode ( "button" ) ;
613
+ button . innerText = filter ;
614
+ button . value = value ;
615
+ button . addEventListener ( "click" , event => {
616
+ event . preventDefault ( ) ;
617
+ let search_query = getSearchTerm ( ) ;
618
+ let [ query , _ ] = parseQuery ( search_query ) ;
619
+ setSearchQuery ( event . target . value + " " + query ) ;
620
+ const search_params = {
621
+ q : search_query ,
622
+ project : config . project ,
623
+ version : config . version ,
624
+ } ;
625
+ fetchAndGenerateResults ( config . api_endpoint , search_params ) ( ) ;
626
+ } ) ;
627
+ li . appendChild ( button ) ;
628
+ filters_list . appendChild ( li ) ;
629
+ }
575
630
return div ;
576
631
} ;
577
632
@@ -609,7 +664,7 @@ const showSearchModal = custom_query => {
609
664
search_outer_input . focus ( ) ;
610
665
}
611
666
} ;
612
-
667
+
613
668
if ( window . jQuery ) {
614
669
$ ( ".search__outer__wrapper" ) . fadeIn ( ANIMATION_TIME , show_modal ) ;
615
670
} else {
@@ -650,15 +705,43 @@ const removeSearchModal = () => {
650
705
}
651
706
} ;
652
707
708
+
709
+ /**
710
+ * Get the config used by the search.
711
+ *
712
+ * This configiration is extracted from the global variable
713
+ * READTHEDOCS_DATA, which is defined by Read the Docs,
714
+ * and the global variable RTD_SEARCH_CONFIG, which is defined
715
+ * by the sphinx extension.
716
+ *
717
+ * @return {Object } config
718
+ */
719
+ function getConfig ( ) {
720
+ const project = READTHEDOCS_DATA . project ;
721
+ const version = READTHEDOCS_DATA . version ;
722
+ const api_host = READTHEDOCS_DATA . proxied_api_host || '/_' ;
723
+ // This variable is defined in another file.
724
+ // that is loaded before this file,
725
+ // containing settings from the sphinx extension.
726
+ const search_config = RTD_SEARCH_CONFIG || { } ;
727
+ const api_endpoint = api_host + "/api/v3/search/" ;
728
+ return {
729
+ project : project ,
730
+ version : version ,
731
+ api_endpoint : api_endpoint ,
732
+ filters : search_config . filters ,
733
+ default_filter : search_config . default_filter ,
734
+ }
735
+ }
736
+
737
+
653
738
window . addEventListener ( "DOMContentLoaded" , ( ) => {
654
739
// only add event listeners if READTHEDOCS_DATA global
655
740
// variable is found.
656
741
if ( window . hasOwnProperty ( "READTHEDOCS_DATA" ) ) {
657
- const project = READTHEDOCS_DATA . project ;
658
- const version = READTHEDOCS_DATA . version ;
659
- const api_host = READTHEDOCS_DATA . proxied_api_host || '/_' ;
742
+ const config = getConfig ( ) ;
660
743
661
- let initialHtml = generateAndReturnInitialHtml ( ) ;
744
+ let initialHtml = generateAndReturnInitialHtml ( config . filters ) ;
662
745
document . body . appendChild ( initialHtml ) ;
663
746
664
747
let search_outer_wrapper = document . querySelector (
@@ -684,13 +767,16 @@ window.addEventListener("DOMContentLoaded", () => {
684
767
// cancel previous ajax request.
685
768
current_request . cancel ( ) ;
686
769
}
687
- const search_endpoint = api_host + "/api/v2/search/" ;
770
+ let [ query , options ] = parseQuery ( search_query ) ;
771
+ if ( Object . keys ( options ) . length == 0 ) {
772
+ search_query = config . default_filter + " " + query ;
773
+ }
688
774
const search_params = {
689
775
q : search_query ,
690
- project : project ,
691
- version : version ,
776
+ project : config . project ,
777
+ version : config . version ,
692
778
} ;
693
- current_request = fetchAndGenerateResults ( search_endpoint , search_params , project ) ;
779
+ current_request = fetchAndGenerateResults ( config . api_endpoint , search_params ) ;
694
780
current_request ( ) ;
695
781
} else {
696
782
// if the last request returns the results,
0 commit comments