@@ -8,6 +8,7 @@ let render
88?page
99~(package : Package.package)
1010~(documentation_status: Package.documentation_status)
11+ ~(search_index_digest: string option)
1112~left_sidebar_html
1213~right_sidebar_html
1314inner =
@@ -60,13 +61,21 @@ Layout.render
6061 </li>
6162 </ol>
6263
63- <div title="Sorry, in-package search is not yet implemented, but this is where it's going to appear." class="flex w-full items-center">
64- <input disabled type="search" name="q" class="focus:border-gray-800 text-gray-800 bg-gray-300 border-gray-300 h-10 rounded-l-md appearance-none px-4 flex-grow"
65- autocomplete="off"
66- placeholder="Sorry, in-package search is not yet implemented, but this is where it's going to appear :-)">
67- <button disabled aria-label="search" class="h-10 rounded-r-md bg-gray-300 text-gray-800 flex items-center justify-center px-4">
68- <%s! Icons.magnifying_glass "w-6 h-6" %>
69- </button>
64+ <div id="in-package-search" class="relative w-full">
65+ <div class="flex w-full items-center">
66+ <% if Option.is_some search_index_digest then (%>
67+ <input id="in-package-search-input" type="search" name="q" class="focus:border-gray-800 text-gray-800 border-primary-600 h-10 rounded-l-md appearance-none px-4 flex-grow"
68+ autocomplete="off"
69+ placeholder="Search names in this package..."
70+ >
71+ <div aria-hidden="true" class="h-10 rounded-r-md bg-primary-600 text-white flex items-center justify-center px-4">
72+ <%s! Icons.magnifying_glass "w-6 h-6" %>
73+ </div>
74+ <% ); %>
75+ </div>
76+
77+ <div id="in-package-search-results" class="absolute top-12 right-0 left-0 bg-white z-20 w-full max-h-[60vh] overflow-y-auto border rounded-lg shadow-xl">
78+ </div>
7079 </div>
7180 </div>
7281
@@ -78,6 +87,7 @@ Layout.render
7887 x-transition:leave-end="-translate-x-full">
7988 <%s! left_sidebar_html %>
8089 </div>
90+
8191 <div class="flex-1 z-0 z- min-w-0 pt-6 pb-12 md:pb-[70vh]">
8292 <%s! inner %>
8393 </div>
@@ -91,4 +101,158 @@ Layout.render
91101 </div>
92102 </div>
93103</div>
94- <%s! Toc.script %>
104+ <%s! Toc.script %>
105+ <% (match search_index_digest with | Some(digest) -> %>
106+ <script src="<%s Ocamlorg_static.Asset. url "vendors/minisearch.min.js" %>"></script>
107+ <script>
108+ function Fuse() {}
109+ </script>
110+ <script>
111+ {
112+ let miniSearch;
113+ let results = [];
114+
115+ function shortness_factor(r) {
116+ return 1 + 2*Math.log(1 + 1/(r.prefixname.length + r.name.length));
117+ }
118+
119+ function perform_search() {
120+ let q = document.getElementById("in-package-search-input").value;
121+ results = miniSearch.search(q, {
122+ fields: ['name', 'prefixname', 'comment'],
123+ prefix: true,
124+ boost: {
125+ name: 6,
126+ prefixname: 2.5,
127+ comment: 0.8,
128+ },
129+ fuzzy: 0.15,
130+ }).slice(0,50);
131+ results = results.map(r => {return {...r, score: r.score * shortness_factor(r), shortness: shortness_factor(r)}}).sort((r1,r2) => r2.score - r1.score);
132+ let container = document.getElementById("in-package-search-results");
133+ container.innerHTML = "";
134+
135+ let search_results = document.createElement("ol");
136+
137+ results.map((entry) => {
138+ let kind = document.createElement("tt");
139+ kind.innerText = entry.kind
140+ .replace("module type", "mty").replace("root", "mod").replace("module", "mod")
141+ .replace("method", "mtd").replace("class type", "cty").replace("class", "cls")
142+ .replace("core type", "typ").replace("type", "typ")
143+ .replace("exception", "exc").replace("core exception", "exc")
144+ .replace("parameter", "par")
145+ .replace("leaf page", "man").replace("page", "man");
146+ kind.title = entry.kind;
147+ kind.classList.add("entry-kind");
148+
149+ let list_item = document.createElement("li");
150+ let a = document.createElement("a");
151+ a.href = "/" + entry.url;
152+ a.id = "search-result-"+entry.id;
153+ a.classList.add("search-entry", kind.innerText.slice(0,3));
154+ let title = document.createElement("div");
155+ title.classList.add("entry-title");
156+
157+ let prefixname = document.createElement("tt");
158+ prefixname.innerText = entry.prefixname.split(".").reverse().join(".") + (entry.prefixname != "" ? ".": "");
159+ prefixname.classList.add("prefix-name");
160+ let name = document.createElement("tt");
161+ name.classList.add("entry-name");
162+ name.innerText = entry.name;
163+
164+ title.appendChild(kind);
165+ title.appendChild(prefixname);
166+ title.appendChild(name);
167+
168+ let comment = document.createElement("div");
169+ comment.innerText = entry.comment;
170+ comment.classList.add("entry-comment");
171+
172+ a.appendChild(title);
173+ a.appendChild(comment);
174+
175+ list_item.appendChild(a);
176+
177+ search_results.appendChild(list_item);
178+ });
179+ container.appendChild(search_results);
180+
181+ search_results_position = null;
182+ }
183+
184+ function init_search() {
185+ documents = documents.map((d, i) => {return {...d, id: i}});
186+
187+ miniSearch = new MiniSearch({
188+ fields: ['name', 'prefixname', 'comment'],
189+ storeFields: ['name', 'prefixname', 'kind', 'url', 'comment'],
190+ });
191+
192+ miniSearch.addAll(documents);
193+
194+ document.getElementById("in-package-search-input").addEventListener("input", perform_search);
195+ perform_search();
196+ }
197+
198+ function user_interacts() {
199+ let scriptTag = document.createElement("script");
200+ scriptTag.src = "<%s Url.Package.search_index ?version:(Package.url_version package) package.name ~digest %>";
201+ scriptTag.addEventListener("load", init_search);
202+ document.body.appendChild(scriptTag);
203+
204+ document.getElementById("in-package-search-input").removeEventListener("focus", user_interacts);
205+ }
206+
207+ document.getElementById("in-package-search-input").addEventListener("focus", user_interacts);
208+
209+
210+ let search_results_position = null;
211+ function adjust_position(event) {
212+ if (results.length == 0) return;
213+
214+ if (event.key == "ArrowDown") {
215+ if (search_results_position === null) {
216+ search_results_position = 0;
217+ return;
218+ }
219+ if (search_results_position < results.length - 1) {
220+ search_results_position++;
221+ return;
222+ }
223+ } else if (event.key == "ArrowUp") {
224+ if (search_results_position === null) return;
225+
226+ if (search_results_position == 0) {
227+ search_results_position = null;
228+ return;
229+ }
230+
231+ search_results_position--;
232+ return;
233+ }
234+ }
235+
236+ function keydown(event) {
237+ event.stopPropagation();
238+ if (event.key == "Enter") {
239+ if (results.length > 0) {
240+ let url = "/"+results[search_results_position || 0].url;
241+ window.location = url;
242+ }
243+ return false;
244+ }
245+
246+ if (search_results_position !== null) document.getElementById("search-result-"+results[search_results_position].id).classList.remove("active");
247+ adjust_position(event);
248+ if (search_results_position !== null) {
249+ let el = document.getElementById("search-result-"+results[search_results_position].id);
250+ el.classList.add("active");
251+ el.scrollIntoView({block: "end"});
252+ }
253+ }
254+
255+ document.getElementById("in-package-search-input").addEventListener("keydown", keydown);
256+ }
257+ </script>
258+ <% | _ -> () ); %>
0 commit comments