1 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
17 * Interface to the Lunr search engines.
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
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';
32 let pagesIndex = null;
35 * Get the jsonFile that is generated when the component library is build.
37 * @param {String} jsonFile the URL to the json file.
40 const fetchJson = async(jsonFile) => {
41 const response = await fetch(jsonFile);
44 Log.debug(`Error getting Hugo index file: ${response.status}`);
47 return await response.json();
51 * Initiate lunr on the data in the jsonFile and add the jsondata to the pagesIndex
53 * @param {String} jsonFile the URL to the json file.
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() {
61 this.field('title', {boost: 10});
62 this.field('content');
63 this.field('tags', {boost: 5});
64 jsondata.forEach(p => {
69 }).catch(Notification.exception);
73 * Setup the eventlistener to listen on user input on the search field.
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');
83 renderResults(searchIndex(query));
85 searchInput.addEventListener('keydown', e => {
86 if (e.keyCode === enter) {
89 if (e.keyCode === escape) {
90 searchInput.value = '';
96 * Trigger a search in lunr and transform the result.
98 * @param {String} query
99 * @return {Array} results
101 const searchIndex = query => {
102 // Find the item in our index corresponding to the lunr one to have more info
104 // {ref: "/section/page1", score: 0.2725657778206127}
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;
116 * Display the 10 first results
118 * @param {Array} results to display
120 const renderResults = results => {
121 const dropdownMenu = document.querySelector(selectors.dropdownmenu);
122 if (!results.length) {
123 dropdownMenu.classList.remove('show');
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);
143 dropdownMenu.classList.add('show');
149 * @param {String} jsonFile Full path to the search DB json file.
151 export const search = jsonFile => {