26 |
rodolico |
1 |
// Search module for phpDocumentor
|
|
|
2 |
//
|
|
|
3 |
// This module is a wrapper around fuse.js that will use a given index and attach itself to a
|
|
|
4 |
// search form and to a search results pane identified by the following data attributes:
|
|
|
5 |
//
|
|
|
6 |
// 1. data-search-form
|
|
|
7 |
// 2. data-search-results
|
|
|
8 |
//
|
|
|
9 |
// The data-search-form is expected to have a single input element of type 'search' that will trigger searching for
|
|
|
10 |
// a series of results, were the data-search-results pane is expected to have a direct UL child that will be populated
|
|
|
11 |
// with rendered results.
|
|
|
12 |
//
|
|
|
13 |
// The search has various stages, upon loading this stage the data-search-form receives the CSS class
|
|
|
14 |
// 'phpdocumentor-search--enabled'; this indicates that JS is allowed and indices are being loaded. It is recommended
|
|
|
15 |
// to hide the form by default and show it when it receives this class to achieve progressive enhancement for this
|
|
|
16 |
// feature.
|
|
|
17 |
//
|
|
|
18 |
// After loading this module, it is expected to load a search index asynchronously, for example:
|
|
|
19 |
//
|
|
|
20 |
// <script defer src="js/searchIndex.js"></script>
|
|
|
21 |
//
|
|
|
22 |
// In this script the generated index should attach itself to the search module using the `appendIndex` function. By
|
|
|
23 |
// doing it like this the page will continue loading, unhindered by the loading of the search.
|
|
|
24 |
//
|
|
|
25 |
// After the page has fully loaded, and all these deferred indexes loaded, the initialization of the search module will
|
|
|
26 |
// be called and the form will receive the class 'phpdocumentor-search--active', indicating search is ready. At this
|
|
|
27 |
// point, the input field will also have it's 'disabled' attribute removed.
|
|
|
28 |
var Search = (function () {
|
|
|
29 |
var fuse;
|
|
|
30 |
var index = [];
|
|
|
31 |
var options = {
|
|
|
32 |
shouldSort: true,
|
|
|
33 |
threshold: 0.6,
|
|
|
34 |
location: 0,
|
|
|
35 |
distance: 100,
|
|
|
36 |
maxPatternLength: 32,
|
|
|
37 |
minMatchCharLength: 1,
|
|
|
38 |
keys: [
|
|
|
39 |
"fqsen",
|
|
|
40 |
"name",
|
|
|
41 |
"summary",
|
|
|
42 |
"url"
|
|
|
43 |
]
|
|
|
44 |
};
|
|
|
45 |
|
|
|
46 |
// Credit David Walsh (https://davidwalsh.name/javascript-debounce-function)
|
|
|
47 |
// Returns a function, that, as long as it continues to be invoked, will not
|
|
|
48 |
// be triggered. The function will be called after it stops being called for
|
|
|
49 |
// N milliseconds. If `immediate` is passed, trigger the function on the
|
|
|
50 |
// leading edge, instead of the trailing.
|
|
|
51 |
function debounce(func, wait, immediate) {
|
|
|
52 |
var timeout;
|
|
|
53 |
|
|
|
54 |
return function executedFunction() {
|
|
|
55 |
var context = this;
|
|
|
56 |
var args = arguments;
|
|
|
57 |
|
|
|
58 |
var later = function () {
|
|
|
59 |
timeout = null;
|
|
|
60 |
if (!immediate) func.apply(context, args);
|
|
|
61 |
};
|
|
|
62 |
|
|
|
63 |
var callNow = immediate && !timeout;
|
|
|
64 |
clearTimeout(timeout);
|
|
|
65 |
timeout = setTimeout(later, wait);
|
|
|
66 |
if (callNow) func.apply(context, args);
|
|
|
67 |
};
|
|
|
68 |
}
|
|
|
69 |
|
|
|
70 |
function close() {
|
|
|
71 |
// Start scroll prevention: https://css-tricks.com/prevent-page-scrolling-when-a-modal-is-open/
|
|
|
72 |
const scrollY = document.body.style.top;
|
|
|
73 |
document.body.style.position = '';
|
|
|
74 |
document.body.style.top = '';
|
|
|
75 |
window.scrollTo(0, parseInt(scrollY || '0') * -1);
|
|
|
76 |
// End scroll prevention
|
|
|
77 |
|
|
|
78 |
var form = document.querySelector('[data-search-form]');
|
|
|
79 |
var searchResults = document.querySelector('[data-search-results]');
|
|
|
80 |
|
|
|
81 |
form.classList.toggle('phpdocumentor-search--has-results', false);
|
|
|
82 |
searchResults.classList.add('phpdocumentor-search-results--hidden');
|
|
|
83 |
var searchField = document.querySelector('[data-search-form] input[type="search"]');
|
|
|
84 |
searchField.blur();
|
|
|
85 |
}
|
|
|
86 |
|
|
|
87 |
function search(event) {
|
|
|
88 |
// Start scroll prevention: https://css-tricks.com/prevent-page-scrolling-when-a-modal-is-open/
|
|
|
89 |
document.body.style.position = 'fixed';
|
|
|
90 |
document.body.style.top = `-${window.scrollY}px`;
|
|
|
91 |
// End scroll prevention
|
|
|
92 |
|
|
|
93 |
// prevent enter's from autosubmitting
|
|
|
94 |
event.stopPropagation();
|
|
|
95 |
|
|
|
96 |
var form = document.querySelector('[data-search-form]');
|
|
|
97 |
var searchResults = document.querySelector('[data-search-results]');
|
|
|
98 |
var searchResultEntries = document.querySelector('[data-search-results] .phpdocumentor-search-results__entries');
|
|
|
99 |
|
|
|
100 |
searchResultEntries.innerHTML = '';
|
|
|
101 |
|
|
|
102 |
if (!event.target.value) {
|
|
|
103 |
close();
|
|
|
104 |
return;
|
|
|
105 |
}
|
|
|
106 |
|
|
|
107 |
form.classList.toggle('phpdocumentor-search--has-results', true);
|
|
|
108 |
searchResults.classList.remove('phpdocumentor-search-results--hidden');
|
|
|
109 |
var results = fuse.search(event.target.value, {limit: 25});
|
|
|
110 |
|
|
|
111 |
results.forEach(function (result) {
|
|
|
112 |
var entry = document.createElement("li");
|
|
|
113 |
entry.classList.add("phpdocumentor-search-results__entry");
|
|
|
114 |
entry.innerHTML += '<h3><a href="' + document.baseURI + result.url + '">' + result.name + "</a></h3>\n";
|
|
|
115 |
entry.innerHTML += '<small>' + result.fqsen + "</small>\n";
|
|
|
116 |
entry.innerHTML += '<div class="phpdocumentor-summary">' + result.summary + '</div>';
|
|
|
117 |
searchResultEntries.appendChild(entry)
|
|
|
118 |
});
|
|
|
119 |
}
|
|
|
120 |
|
|
|
121 |
function appendIndex(added) {
|
|
|
122 |
index = index.concat(added);
|
|
|
123 |
|
|
|
124 |
// re-initialize search engine when appending an index after initialisation
|
|
|
125 |
if (typeof fuse !== 'undefined') {
|
|
|
126 |
fuse = new Fuse(index, options);
|
|
|
127 |
}
|
|
|
128 |
}
|
|
|
129 |
|
|
|
130 |
function init() {
|
|
|
131 |
fuse = new Fuse(index, options);
|
|
|
132 |
|
|
|
133 |
var form = document.querySelector('[data-search-form]');
|
|
|
134 |
var searchField = document.querySelector('[data-search-form] input[type="search"]');
|
|
|
135 |
|
|
|
136 |
var closeButton = document.querySelector('.phpdocumentor-search-results__close');
|
|
|
137 |
closeButton.addEventListener('click', function() { close() }.bind(this));
|
|
|
138 |
|
|
|
139 |
var searchResults = document.querySelector('[data-search-results]');
|
|
|
140 |
searchResults.addEventListener('click', function() { close() }.bind(this));
|
|
|
141 |
|
|
|
142 |
form.classList.add('phpdocumentor-search--active');
|
|
|
143 |
|
|
|
144 |
searchField.setAttribute('placeholder', 'Search (Press "/" to focus)');
|
|
|
145 |
searchField.removeAttribute('disabled');
|
|
|
146 |
searchField.addEventListener('keyup', debounce(search, 300));
|
|
|
147 |
|
|
|
148 |
window.addEventListener('keyup', function (event) {
|
|
|
149 |
if (event.key === '/') {
|
|
|
150 |
searchField.focus();
|
|
|
151 |
}
|
|
|
152 |
if (event.code === 'Escape') {
|
|
|
153 |
close();
|
|
|
154 |
}
|
|
|
155 |
}.bind(this));
|
|
|
156 |
}
|
|
|
157 |
|
|
|
158 |
return {
|
|
|
159 |
appendIndex,
|
|
|
160 |
init
|
|
|
161 |
}
|
|
|
162 |
})();
|
|
|
163 |
|
|
|
164 |
window.addEventListener('DOMContentLoaded', function () {
|
|
|
165 |
var form = document.querySelector('[data-search-form]');
|
|
|
166 |
|
|
|
167 |
// When JS is supported; show search box. Must be before including the search for it to take effect immediately
|
|
|
168 |
form.classList.add('phpdocumentor-search--enabled');
|
|
|
169 |
});
|
|
|
170 |
|
|
|
171 |
window.addEventListener('load', function () {
|
|
|
172 |
Search.init();
|
|
|
173 |
});
|