Skip to content

Commit 176d5ae

Browse files
authored
Merge pull request #128 from jasalt/feat-improve-search
feat: index `contents/documentation/` for search along API
2 parents 69afb01 + f1a8821 commit 176d5ae

File tree

4 files changed

+139
-14
lines changed

4 files changed

+139
-14
lines changed

build/src/php/FileGenerator/Application/ApiSearchGenerator.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,19 @@ public function generateSearchIndex(): array
5151
}
5252

5353
$result[] = [
54+
'id' => 'api_' . $fn->fnName(),
5455
'fnName' => $fn->fnName(),
5556
'fnSignature' => $fn->fnSignature(),
5657
'desc' => $this->formatDescription($fn->description()),
5758
'anchor' => $anchor,
59+
'type' => 'api',
5860
];
5961
}
6062

63+
// Add documentation files to search index
64+
$documentationItems = $this->generateDocumentationSearchItems();
65+
$result = array_merge($result, $documentationItems);
66+
6167
return $result;
6268
}
6369

@@ -68,4 +74,61 @@ private function formatDescription(string $desc): string
6874
{
6975
return preg_replace('/\[(.*?)\]\((.*?)\)/', '<i>$1</i>', $desc);
7076
}
77+
78+
/**
79+
* Generate search index items for documentation files
80+
*
81+
* @return array<array{id: string, title: string, content: string, url: string, type: string}>
82+
*/
83+
private function generateDocumentationSearchItems(): array
84+
{
85+
$result = [];
86+
$documentationPath = __DIR__ . '/../../../../../content/documentation';
87+
88+
if (!is_dir($documentationPath)) {
89+
error_log("Documentation path not found: " . $documentationPath);
90+
return [];
91+
}
92+
93+
$files = scandir($documentationPath);
94+
if ($files === false) {
95+
error_log("Could not scan documentation directory: " . $documentationPath);
96+
return [];
97+
}
98+
99+
foreach ($files as $file) {
100+
if (pathinfo($file, PATHINFO_EXTENSION) !== 'md' || $file === '_index.md') {
101+
continue;
102+
}
103+
104+
$filePath = $documentationPath . '/' . $file;
105+
$content = file_get_contents($filePath);
106+
107+
// Extract title from frontmatter
108+
$title = pathinfo($file, PATHINFO_FILENAME);
109+
if (preg_match('/title = "([^"]+)"/', $content, $matches)) {
110+
$title = $matches[1];
111+
}
112+
113+
// Remove frontmatter
114+
$content = preg_replace('/\+\+\+.*?\+\+\+/s', '', $content);
115+
116+
// Remove markdown formatting and clean content
117+
$content = preg_replace('/[#`*\[\]()]/', ' ', $content);
118+
$content = preg_replace('/\s+/', ' ', trim($content));
119+
120+
// Limit content length for search index
121+
$content = substr($content, 0, 500);
122+
123+
$result[] = [
124+
'id' => 'doc_' . pathinfo($file, PATHINFO_FILENAME),
125+
'title' => $title,
126+
'content' => $content,
127+
'url' => '/documentation/' . pathinfo($file, PATHINFO_FILENAME),
128+
'type' => 'documentation',
129+
];
130+
}
131+
132+
return $result;
133+
}
71134
}

build/tests/php/FileGenerator/Domain/ApiSearchGeneratorTest.php

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,20 @@ public function test_generate_search_index_one_item(): void
2929

3030
$expected = [
3131
[
32+
'id' => 'api_table?',
3233
'fnName' => 'table?',
3334
'fnSignature' => '(table? x)',
3435
'desc' => 'doc for table?',
3536
'anchor' => 'table',
37+
'type' => 'api',
3638
],
3739
];
3840

39-
self::assertEquals($expected, $actual);
41+
// Filter out documentation items for this test
42+
$apiItems = array_filter($actual, fn($item) => $item['type'] === 'api');
43+
$apiItems = array_values($apiItems); // Re-index array
44+
45+
self::assertEquals($expected, $apiItems);
4046
}
4147

4248
public function test_multiple_items_in_different_groups(): void
@@ -63,20 +69,28 @@ public function test_multiple_items_in_different_groups(): void
6369

6470
$expected = [
6571
[
72+
'id' => 'api_table',
6673
'fnName' => 'table',
6774
'fnSignature' => '(table & xs)',
6875
'desc' => 'doc for table',
6976
'anchor' => 'table',
77+
'type' => 'api',
7078
],
7179
[
80+
'id' => 'api_not',
7281
'fnName' => 'not',
7382
'fnSignature' => '(not x)',
7483
'desc' => 'doc for not',
7584
'anchor' => 'not',
85+
'type' => 'api',
7686
],
7787
];
7888

79-
self::assertEquals($expected, $actual);
89+
// Filter out documentation items for this test
90+
$apiItems = array_filter($actual, fn($item) => $item['type'] === 'api');
91+
$apiItems = array_values($apiItems); // Re-index array
92+
93+
self::assertEquals($expected, $apiItems);
8094
}
8195

8296
public function test_multiple_items_in_the_same_group(): void
@@ -103,20 +117,28 @@ public function test_multiple_items_in_the_same_group(): void
103117

104118
$expected = [
105119
[
120+
'id' => 'api_table',
106121
'fnName' => 'table',
107122
'fnSignature' => '(table & xs)',
108123
'desc' => 'doc for table',
109124
'anchor' => 'table',
125+
'type' => 'api',
110126
],
111127
[
128+
'id' => 'api_table?',
112129
'fnName' => 'table?',
113130
'fnSignature' => '(table? x)',
114131
'desc' => 'doc for table?',
115132
'anchor' => 'table-1',
133+
'type' => 'api',
116134
],
117135
];
118136

119-
self::assertEquals($expected, $actual);
137+
// Filter out documentation items for this test
138+
$apiItems = array_filter($actual, fn($item) => $item['type'] === 'api');
139+
$apiItems = array_values($apiItems); // Re-index array
140+
141+
self::assertEquals($expected, $apiItems);
120142
}
121143

122144
public function test_fn_name_with_slash_in_the_middle(): void
@@ -143,20 +165,28 @@ public function test_fn_name_with_slash_in_the_middle(): void
143165

144166
$expected = [
145167
[
168+
'id' => 'api_http/response',
146169
'fnName' => 'http/response',
147170
'fnSignature' => '',
148171
'desc' => '',
149172
'anchor' => 'http-response',
173+
'type' => 'api',
150174
],
151175
[
176+
'id' => 'api_http/response?',
152177
'fnName' => 'http/response?',
153178
'fnSignature' => '',
154179
'desc' => '',
155180
'anchor' => 'http-response-1',
181+
'type' => 'api',
156182
],
157183
];
158184

159-
self::assertEquals($expected, $actual);
185+
// Filter out documentation items for this test
186+
$apiItems = array_filter($actual, fn($item) => $item['type'] === 'api');
187+
$apiItems = array_values($apiItems); // Re-index array
188+
189+
self::assertEquals($expected, $apiItems);
160190
}
161191

162192
public function test_fn_name_ending_with_minus(): void
@@ -183,20 +213,28 @@ public function test_fn_name_ending_with_minus(): void
183213

184214
$expected = [
185215
[
216+
'id' => 'api_defn',
186217
'fnName' => 'defn',
187218
'fnSignature' => '',
188219
'desc' => '',
189220
'anchor' => 'defn',
221+
'type' => 'api',
190222
],
191223
[
224+
'id' => 'api_defn-',
192225
'fnName' => 'defn-',
193226
'fnSignature' => '',
194227
'desc' => '',
195228
'anchor' => 'defn-1',
229+
'type' => 'api',
196230
],
197231
];
198232

199-
self::assertEquals($expected, $actual);
233+
// Filter out documentation items for this test
234+
$apiItems = array_filter($actual, fn($item) => $item['type'] === 'api');
235+
$apiItems = array_values($apiItems); // Re-index array
236+
237+
self::assertEquals($expected, $apiItems);
200238
}
201239

202240
public function test_fn_name_with_upper_case(): void
@@ -223,19 +261,27 @@ public function test_fn_name_with_upper_case(): void
223261

224262
$expected = [
225263
[
264+
'id' => 'api_NAN',
226265
'fnName' => 'NAN',
227266
'fnSignature' => '',
228267
'desc' => '',
229268
'anchor' => 'nan',
269+
'type' => 'api',
230270
],
231271
[
272+
'id' => 'api_nan?',
232273
'fnName' => 'nan?',
233274
'fnSignature' => '',
234275
'desc' => '',
235276
'anchor' => 'nan-1',
277+
'type' => 'api',
236278
],
237279
];
238280

239-
self::assertEquals($expected, $actual);
281+
// Filter out documentation items for this test
282+
$apiItems = array_filter($actual, fn($item) => $item['type'] === 'api');
283+
$apiItems = array_values($apiItems); // Re-index array
284+
285+
self::assertEquals($expected, $apiItems);
240286
}
241287
}

static/search.js

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,9 @@ function initSearch() {
109109
const index = elasticlunr(function () {
110110
this.addField("fnName");
111111
this.addField("desc");
112-
this.setRef("anchor");
112+
this.addField("title");
113+
this.addField("content");
114+
this.setRef("id");
113115
elasticlunr.stopWordFilter.stopWords = {};
114116
elasticlunr.Pipeline.registerFunction(elasticlunr.trimmer, "trimmer");
115117
elasticlunr.tokenizer.seperator = /[\s~~]+/;
@@ -174,10 +176,12 @@ function showResults(index) {
174176
}
175177

176178
const options = {
177-
bool: "AND",
179+
bool: "OR",
178180
fields: {
179181
fnName: {boost: 3},
182+
title: {boost: 2},
180183
desc: {boost: 1},
184+
content: {boost: 1}
181185
},
182186
expand: true
183187
};
@@ -188,6 +192,7 @@ function showResults(index) {
188192
fnSignature: "",
189193
desc: "Cannot provide any Phel symbol. Try something else",
190194
anchor: "#",
195+
type: "api"
191196
};
192197

193198
createMenuItem(emptyResult, null);
@@ -215,11 +220,22 @@ function createMenuItem(result, index) {
215220
}
216221

217222
function formatSearchResultItem(item) {
218-
return `<a href="/documentation/api/#${item.anchor}">`
219-
+ `<div class="search-results__item">${item.fnName} `
220-
+ `<small class="fn-signature">${item.fnSignature}</small>`
221-
+ `<span class="desc">${item.desc}</span>`
222-
+ `</div></a>`;
223+
if (item.type === "documentation") {
224+
return `<a href="${item.url}">`
225+
+ `<div class="search-results__item">`
226+
+ `<span class="result-type">Documentation: </span>`
227+
+ `<strong>${item.title}</strong>`
228+
+ `<span class="desc">${item.content}</span>`
229+
+ `</div></a>`;
230+
} else {
231+
return `<a href="/documentation/api/#${item.anchor}">`
232+
+ `<div class="search-results__item">`
233+
+ `<span class="result-type">API: </span>`
234+
+ `${item.fnName} `
235+
+ `<small class="fn-signature">${item.fnSignature}</small>`
236+
+ `<span class="desc">${item.desc}</span>`
237+
+ `</div></a>`;
238+
}
223239
}
224240

225241
function removeSelectedClassFromSearchResult() {

templates/header.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
</div>
2222

2323
<div class="site-header__search">
24-
<input type="search" id="search" autocomplete="off" placeholder="Search in the API...">
24+
<input type="search" id="search" autocomplete="off" placeholder="Search in the docs & API...">
2525

2626
<div id="search-results" class="search-results">
2727
<ul id="search-results__items" class="search-results__items"></ul>

0 commit comments

Comments
 (0)