Subversion Repositories php_users

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
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
});