MDL-61460 tool_componentlibrary: removed white space.
[moodle.git] / admin / tool / componentlibrary / amd / src / search.js
1 // This file is part of Moodle - http://moodle.org/
2 //
3 // Moodle is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, either version 3 of the License, or
6 // (at your option) any later version.
7 //
8 // Moodle is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16 /**
17  * Interface to the Lunr search engines.
18  *
19  * @module     tool_componentlibrary/search
20  * @package    tool_componentlibrary
21  * @copyright  2021 Bas Brands <bas@moodle.com>
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 import lunrJs from 'tool_componentlibrary/lunr';
26 import selectors from 'tool_componentlibrary/selectors';
27 import Log from 'core/log';
28 import Notification from 'core/notification';
29 import {enter, escape} from 'core/key_codes';
31 let lunrIndex = null;
32 let pagesIndex = null;
34 /**
35  * Get the jsonFile that is generated when the component library is build.
36  *
37  * @param {String} jsonFile the URL to the json file.
38  * @retrun {Object}
39  */
40 const fetchJson = async(jsonFile) => {
41     const response = await fetch(jsonFile);
43     if (!response.ok) {
44         Log.debug(`Error getting Hugo index file: ${response.status}`);
45     }
47     return await response.json();
48 };
50 /**
51  * Initiate lunr on the data in the jsonFile and add the jsondata to the pagesIndex
52  *
53  * @param {String} jsonFile the URL to the json file.
54  */
55 const initLunr = jsonFile => {
56     fetchJson(jsonFile).then(jsondata => {
57         pagesIndex = jsondata;
58         // Using an arrow function here will break lunr on compile.
59         lunrIndex = lunrJs(function() {
60             this.ref('uri');
61             this.field('title', {boost: 10});
62             this.field('content');
63             this.field('tags', {boost: 5});
64             jsondata.forEach(p => {
65                 this.add(p);
66             });
67         });
68         return null;
69     }).catch(Notification.exception);
70 };
72 /**
73  * Setup the eventlistener to listen on user input on the search field.
74  */
75 const initUI = () => {
76     const searchInput = document.querySelector(selectors.searchinput);
77     searchInput.addEventListener('keyup', e => {
78         const query = e.currentTarget.value;
79         if (query.length < 2) {
80             document.querySelector(selectors.dropdownmenu).classList.remove('show');
81             return;
82         }
83         renderResults(searchIndex(query));
84     });
85     searchInput.addEventListener('keydown', e => {
86         if (e.keyCode === enter) {
87             e.preventDefault();
88         }
89         if (e.keyCode === escape) {
90             searchInput.value = '';
91         }
92     });
93 };
95 /**
96  * Trigger a search in lunr and transform the result.
97  *
98  * @param  {String} query
99  * @return {Array} results
100  */
101 const searchIndex = query => {
102     // Find the item in our index corresponding to the lunr one to have more info
103     // Lunr result:
104     //  {ref: "/section/page1", score: 0.2725657778206127}
105     // Our result:
106     //  {title:"Page1", href:"/section/page1", ...}
108     return lunrIndex.search(query + ' ' + query + '*').map(result => {
109         return pagesIndex.filter(page => {
110             return page.uri === result.ref;
111         })[0];
112     });
113 };
115 /**
116  * Display the 10 first results
117  *
118  * @param {Array} results to display
119  */
120 const renderResults = results => {
121     const dropdownMenu = document.querySelector(selectors.dropdownmenu);
122     if (!results.length) {
123         dropdownMenu.classList.remove('show');
124         return;
125     }
127     // Clear out the results.
128     dropdownMenu.innerHTML = '';
130     const baseUrl = M.cfg.wwwroot + '/admin/tool/componentlibrary/docspage.php';
132     // Only show the ten first results
133     results.slice(0, 10).forEach(function(result) {
134         const link = document.createElement("a");
135         const chapter = result.uri.split('/')[1];
136         link.appendChild(document.createTextNode(`${chapter} > ${result.title}`));
137         link.classList.add('dropdown-item');
138         link.href = baseUrl + result.uri;
140         dropdownMenu.appendChild(link);
141     });
143     dropdownMenu.classList.add('show');
144 };
146 /**
147  * Initialize module.
148  *
149  * @param {String} jsonFile Full path to the search DB json file.
150  */
151 export const search = jsonFile => {
152     initLunr(jsonFile);
153     initUI();
154 };