Skip to content

Commit 544f432

Browse files
authored
Adds package autocomplete search to the search dropdown (#939)
* Adds package autocomplete search to the search dropdown * factor highlight_search_terms into a component * add interactive search to packages page
1 parent 2eac706 commit 544f432

File tree

15 files changed

+144
-60
lines changed

15 files changed

+144
-60
lines changed

src/ocamlorg_frontend/components/header.eml

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,22 @@ let render
4444
<li class="relative">
4545
<form action="/packages/search" method="GET">
4646
<div class="header__search relative flex items-center justify-center">
47-
<input type="search" autocomplete="off" name="q" class="header__search__input focus:border-gray-800 text-gray-800 border-primary-600 h-10 rounded-l-md appearance-none px-4 py-1 lg:w-56 xl:w-80" placeholder="Search OCaml Packages">
47+
<input type="search" name="q" class="header__search__input focus:border-gray-800 text-gray-800 border-primary-600 h-10 rounded-l-md appearance-none px-4 py-1 lg:w-56 xl:w-80"
48+
autocomplete="off"
49+
placeholder="Search OCaml Packages"
50+
hx-get="/packages/autocomplete"
51+
hx-params="q"
52+
hx-trigger="keyup changed delay:500ms, search"
53+
hx-target="#search-results"
54+
hx-indicator=".htmx-indicator">
4855
<button aria-label="search" class="h-10 rounded-r-md bg-primary-600 text-white flex items-center justify-center px-4">
4956
<%s! Icons.magnifying_glass "w-6 h-6 text-white" %>
5057
</button>
51-
<div class="header__search__dropdown z-10 absolute rounded-b-md w-full top-0 mt-10 p-2 bg-white font-semibold border border-primary-600">
52-
<span class="pl-2">Or go to:</span>
53-
<a class="flex py-2 px-4 gap-4 hover:bg-primary-100 font-regular hover:font-semibold text-primary-600" href="<%s Url.api %>">
58+
<div class="header__search__dropdown z-10 absolute rounded-b-md w-full top-0 mt-10 p-2 bg-white border border-primary-600">
59+
<span class="htmx-indicator mx-2">Searching...</span>
60+
<div id="search-results"></div>
61+
<span class="pl-2 font-semibold">Or go to:</span>
62+
<a class="flex py-2 px-4 gap-4 hover:bg-primary-100 font-semibold hover:font-semibold text-primary-600" href="<%s Url.api %>">
5463
Standard Library API
5564
<%s! Icons.arrow_top_right_on_square "w-6 h-6" %>
5665
</a>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
let highlight_search_terms
2+
~class_
3+
~search
4+
(text: string)
5+
=
6+
let render_item = function
7+
| Str.Delim s -> {|<span class="|} ^ Dream.html_escape class_ ^ {|">|} ^ Dream.html_escape s ^ {|</span>|}
8+
| Text s -> Dream.html_escape s
9+
in
10+
let r = Str.global_replace (Str.regexp "[ \t]+") "\\|" search in
11+
let split = Str.full_split (Str.regexp_case_fold r) text in
12+
List.fold_left (fun a b -> a ^ render_item b) "" split
Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
/* all search inputs */
22

3-
.header__search__input::placeholder, .package__search::placeholder {
4-
@apply text-gray-500;
5-
}
6-
7-
.header__search__input:focus, .package__search:focus {
3+
.header__search__input:focus,
4+
.packages_search__search__input:focus
5+
.packages__search__input:focus {
86
box-shadow: none;
97
}
108

11-
/* header search dropdown */
9+
/* search dropdown */
1210

1311
.header__search__dropdown{
1412
display:none;
@@ -22,6 +20,6 @@
2220
border-bottom-right-radius: 0;
2321
}
2422

25-
.header__search:focus-within .header__search__dropdown{
23+
.header__search:focus-within .header__search__dropdown {
2624
display:block;
2725
}

src/ocamlorg_frontend/dune

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@
109109
(deps packages.eml)
110110
(action
111111
(run %{bin:dream_eml} %{deps} --workspace %{workspace_root})))
112+
(rule
113+
(targets packages_autocomplete_fragment.ml)
114+
(deps packages_autocomplete_fragment.eml)
115+
(action
116+
(run %{bin:dream_eml} %{deps} --workspace %{workspace_root})))
112117
(rule
113118
(targets packages_search.ml)
114119
(deps packages_search.eml)
@@ -187,6 +192,11 @@
187192
(deps pagination.eml)
188193
(action
189194
(run %{bin:dream_eml} %{deps} --workspace %{workspace_root})))
195+
(rule
196+
(targets search.ml)
197+
(deps search.eml)
198+
(action
199+
(run %{bin:dream_eml} %{deps} --workspace %{workspace_root})))
190200
(rule
191201
(targets toc.ml)
192202
(deps toc.eml)

src/ocamlorg_frontend/ocamlorg_frontend.ml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ let package_documentation_not_found ~is_latest_url ~path ~package =
4242
let packages stats = Packages.render stats
4343
let packages_search ~total packages = Packages_search.render ~total packages
4444

45+
let packages_autocomplete_fragment packages =
46+
Packages_autocomplete_fragment.render packages
47+
4548
let papers ?search ~recommended_papers papers =
4649
Papers.render ?search ~recommended_papers papers
4750

src/ocamlorg_frontend/pages/packages.eml

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,29 @@ Layout.render
1010
~active_top_nav_item:Header.Packages @@
1111
<div class="intro-section-simple">
1212
<div class="container-fluid">
13-
<div class="text-center w-full lg:w-2/3 m-auto">
14-
<h1 class="font-bold mb-6">OCaml Packages</h1>
15-
<p>Explore thousands of open-source OCaml packages with their documentation.</p>
13+
<div class="w-full lg:w-2/3 m-auto">
14+
<h1 class="font-bold text-center mb-6">OCaml Packages</h1>
15+
<p class="text-center">Explore thousands of open-source OCaml packages with their documentation.</p>
1616
<div
1717
class="flex justify-center flex-col lg:flex-row lg:space-x-6 space-y-5 lg:space-y-0 md:space-y-5 w-auto lg:w-auto mt-16">
18-
<form action="/packages/search" method="GET" class="form-input">
19-
<div class="form-input__icon">
20-
<%s! Icons.magnifying_glass "h-5 w-5 text-body-400" %>
21-
</div>
22-
<input class="focus:ring-orange-500" type="search" name="q" placeholder="Search for packages">
23-
<button class="btn btn-lg btn-secondary mt-4 lg:mt-0" type="submit">Search</button>
18+
<form action="/packages/search" method="GET" class="flex flex-col justify-center">
19+
<div class="packages__search relative inline-flex mx-auto items-center justify-center">
20+
<input type="search" name="q" class="packages__search__input focus:border-gray-800 text-gray-800 border-primary-600 h-10 rounded-l-md appearance-none px-4 py-1 md:w-96 lg:w-[32rem] xl:w-[32rem]"
21+
autocomplete="off"
22+
placeholder="Search OCaml Packages"
23+
hx-get="/packages/autocomplete"
24+
hx-params="q"
25+
hx-trigger="keyup changed delay:500ms, search"
26+
hx-target="#packages-search-results"
27+
hx-indicator=".htmx-indicator">
28+
<button aria-label="search" class="h-10 rounded-r-md bg-primary-600 text-white flex items-center justify-center px-4">
29+
<%s! Icons.magnifying_glass "w-6 h-6 text-white" %>
30+
</button>
31+
</div>
32+
<div>
33+
<span class="htmx-indicator">Searching...</span>
34+
<div id="packages-search-results"></div>
35+
</div>
2436
</form>
2537
</div>
2638
</div>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
let render
2+
~search
3+
(packages : Package_intf.package list)
4+
=
5+
<div class="mx-2 mb-2">
6+
<span class="font-semibold">Packages:</span>
7+
<% if List.length packages = 0 then ( %>
8+
<p class="break-words">
9+
We didn't find a match for "<%s search %>".
10+
</p>
11+
<% ); %>
12+
13+
<ul class="flex flex-col">
14+
<% packages |> List.iter (fun (package : Package_intf.package) -> %>
15+
<li class="flex flex-row">
16+
<a
17+
href="<%s Url.package_with_version package.name %>"
18+
class="flex-grow px-2 py-2 leading-6 font-semibold hover:bg-primary-100 text-primary-600 inline-block"
19+
>
20+
<%s! Search.highlight_search_terms ~class_:"bg-background-light-blue text-gray-800 font-normal" ~search package.name %>
21+
</a>
22+
<a class="justify-self-end px-2 py-2 leading-6 font-semibold hover:bg-primary-100" href="<%s Url.package_doc package.name %>">
23+
docs
24+
</a>
25+
</li>
26+
<% ); %>
27+
</ul>
28+
<% if List.length packages > 0 then ( %>
29+
<a class="font-semibold hover:bg-primary-100 px-2 py-2 flex text-right" href="<%s Url.packages_search %>?q=<%s Dream.to_percent_encoded search %>">see more...</a>
30+
<% ); %>
31+
</div>

src/ocamlorg_frontend/pages/packages_search.eml

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,3 @@
1-
let highlight_search_terms
2-
~search
3-
(text: string)
4-
=
5-
let render_item = function
6-
| Str.Delim s -> {|<span class="bg-background-light-blue">|} ^ Dream.html_escape s ^ {|</span>|}
7-
| Text s -> Dream.html_escape s
8-
in
9-
let r = Str.global_replace (Str.regexp "[ \t]+") "\\|" search in
10-
let split = Str.full_split (Str.regexp_case_fold r) text in
11-
List.fold_left (fun a b -> a ^ render_item b) "" split
12-
131
let render ~total ~search (packages : Package_intf.package list) = Layout.render ~title:"OCaml Packages · Search Result"
142
~description:"Find the package you need to build your application in the thousands of available OCaml packages."
153
~canonical:(Url.packages_search ^ "?q=" ^ search)
@@ -24,7 +12,7 @@ let render ~total ~search (packages : Package_intf.package list) = Layout.render
2412
<input
2513
type="search"
2614
name="q" id="q2"
27-
class="package__search w-full focus:border-gray-800 text-gray-800 border-primary-600 h-10 rounded-l-md appearance-none px-4 py-1"
15+
class="packages_search__search__input w-full focus:border-gray-800 text-gray-800 border-primary-600 h-10 rounded-l-md appearance-none px-4 py-1"
2816
value="<%s search %>"
2917
placeholder="Search OCaml Packages">
3018
<button aria-label="search" class="h-10 rounded-r-md bg-primary-600 text-white flex items-center justify-center px-4">
@@ -67,14 +55,14 @@ let render ~total ~search (packages : Package_intf.package list) = Layout.render
6755
>
6856
<%s package.name %>
6957
</a>
70-
<div><%s! highlight_search_terms ~search package.description %></div>
58+
<div><%s! Search.highlight_search_terms ~class_:"bg-background-light-blue" ~search package.description %></div>
7159
<div class="flex flex-wrap">
7260
<% package.tags |> List.iter (fun (tag : string) -> %>
7361
<a
7462
href="<%s Url.packages_search %>?q=tag%3A%22<%s Dream.to_percent_encoded tag %>%22"
7563
class="px-2 py-1 text-body-400 font-medium bg-gray-100 rounded hover:underline mr-3 mb-2 mt-1 text-sm"
7664
>
77-
<%s! highlight_search_terms ~search tag %>
65+
<%s! Search.highlight_search_terms ~class_:"bg-background-light-blue" ~search tag %>
7866
</a>
7967
<% ); %>
8068
</div>
@@ -85,7 +73,7 @@ let render ~total ~search (packages : Package_intf.package list) = Layout.render
8573
href="<%s Url.packages_search %>?q=author%3A%22<%s Dream.to_percent_encoded author.name %>%22"
8674
class="text-sm hover:underline mr-3"
8775
>
88-
<%s! highlight_search_terms ~search author.name %>
76+
<%s! Search.highlight_search_terms ~class_:"bg-background-light-blue" ~search author.name %>
8977
</a>
9078
<% ); %>
9179
</div>

src/ocamlorg_frontend/url.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
let index = "/"
22
let packages = "/packages"
33
let packages_search = "/packages/search"
4+
let packages_autocomplete_fragment = "/packages/autocomplete"
45
let with_hash = Option.fold ~none:"/p" ~some:(( ^ ) "/u/")
56
let package ?hash v = with_hash hash ^ "/" ^ v
67
let package_docs v = "/p/" ^ v ^ "/doc"

src/ocamlorg_package/lib/ocamlorg_package.ml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -687,11 +687,11 @@ end = struct
687687
Float.compare s2 s1
688688
end
689689

690-
let search_package ?(by_popularity = false) t pattern =
690+
let search_package ?(sort_by_popularity = false) t query =
691691
let compare =
692-
Search.(if by_popularity then compare_by_popularity else compare)
692+
Search.(if sort_by_popularity then compare_by_popularity else compare)
693693
in
694-
let request = Search.to_request pattern in
694+
let request = Search.to_request query in
695695
all_packages_latest t
696696
|> List.filter (Search.match_request request)
697697
|> List.sort (compare request)

0 commit comments

Comments
 (0)