@@ -104,7 +104,7 @@ func stripHTML(text string) string {
104104 // Remove HTML tags
105105 re := regexp .MustCompile (`<[^>]*>` )
106106 text = re .ReplaceAllString (text , "" )
107-
107+
108108 // Replace HTML entities
109109 replacements := map [string ]string {
110110 " " : " " ,
@@ -114,27 +114,27 @@ func stripHTML(text string) string {
114114 """ : "\" " ,
115115 "'" : "'" ,
116116 }
117-
117+
118118 for entity , replacement := range replacements {
119119 text = strings .ReplaceAll (text , entity , replacement )
120120 }
121-
121+
122122 // Remove markdown links [text](url)
123123 re = regexp .MustCompile (`\[([^\]]+)\]\([^)]+\)` )
124124 text = re .ReplaceAllString (text , "$1" )
125-
125+
126126 // Remove inline code `code`
127127 re = regexp .MustCompile ("`([^`]+)`" )
128128 text = re .ReplaceAllString (text , "$1" )
129-
129+
130130 // Remove bold/italic *text* and **text**
131131 re = regexp .MustCompile (`\*{1,2}([^*]+)\*{1,2}` )
132132 text = re .ReplaceAllString (text , "$1" )
133-
133+
134134 // Normalize whitespace
135135 re = regexp .MustCompile (`\s+` )
136136 text = re .ReplaceAllString (text , " " )
137-
137+
138138 return strings .TrimSpace (text )
139139}
140140
@@ -143,7 +143,7 @@ func extractSnippet(content, query string, maxLength int) string {
143143 cleanContent := stripHTML (content )
144144 queryLower := strings .ToLower (query )
145145 contentLower := strings .ToLower (cleanContent )
146-
146+
147147 queryIndex := strings .Index (contentLower , queryLower )
148148 if queryIndex == - 1 {
149149 // No match, return beginning
@@ -152,27 +152,27 @@ func extractSnippet(content, query string, maxLength int) string {
152152 }
153153 return cleanContent
154154 }
155-
155+
156156 // Extract context around the match
157157 start := queryIndex - 100
158158 if start < 0 {
159159 start = 0
160160 }
161-
161+
162162 end := queryIndex + len (query ) + 150
163163 if end > len (cleanContent ) {
164164 end = len (cleanContent )
165165 }
166-
166+
167167 snippet := cleanContent [start :end ]
168-
168+
169169 if start > 0 {
170170 snippet = "..." + snippet
171171 }
172172 if end < len (cleanContent ) {
173173 snippet = snippet + "..."
174174 }
175-
175+
176176 return strings .TrimSpace (snippet )
177177}
178178
@@ -181,27 +181,27 @@ func calculateRelevance(content, title, query string) int {
181181 queryLower := strings .ToLower (query )
182182 titleLower := strings .ToLower (title )
183183 contentLower := strings .ToLower (content )
184-
184+
185185 score := 0
186-
186+
187187 // Title matches are highly relevant
188188 if strings .Contains (titleLower , queryLower ) {
189189 score += 100
190190 if titleLower == queryLower {
191191 score += 50 // Exact title match
192192 }
193193 }
194-
194+
195195 // Count occurrences in content
196196 matches := strings .Count (contentLower , queryLower )
197197 score += matches * 10
198-
198+
199199 // Boost for early occurrence
200200 firstIndex := strings .Index (contentLower , queryLower )
201201 if firstIndex != - 1 && firstIndex < 500 {
202202 score += 20
203203 }
204-
204+
205205 return score
206206}
207207
@@ -211,17 +211,17 @@ func filePathToURL(filePath, contentDir string) string {
211211 if err != nil {
212212 return "/"
213213 }
214-
214+
215215 // Remove .md extension
216216 relPath = strings .TrimSuffix (relPath , ".md" )
217-
217+
218218 // Handle index files
219219 if strings .HasSuffix (relPath , "/index" ) {
220220 relPath = strings .TrimSuffix (relPath , "/index" )
221221 } else if relPath == "index" {
222222 return "/"
223223 }
224-
224+
225225 // Convert to URL path
226226 urlPath := "/" + strings .ReplaceAll (relPath , "\\ " , "/" )
227227 return urlPath
@@ -233,7 +233,7 @@ func determineType(filePath string) string {
233233 return "reference"
234234 }
235235 if strings .Contains (filePath , "/changelog/" ) {
236- return "changelog"
236+ return "changelog"
237237 }
238238 if strings .Contains (filePath , "/tools/" ) {
239239 return "tools"
@@ -249,9 +249,9 @@ func matchesFilter(filePath, filter string) bool {
249249 if filter == "" || filter == "all" {
250250 return true
251251 }
252-
252+
253253 contentType := determineType (filePath )
254-
254+
255255 switch filter {
256256 case "reference" :
257257 return contentType == "reference"
@@ -271,19 +271,19 @@ func matchesFilter(filePath, filter string) bool {
271271// findMarkdownFiles recursively finds all .md files in a directory
272272func findMarkdownFiles (dir string ) ([]string , error ) {
273273 var files []string
274-
274+
275275 err := filepath .WalkDir (dir , func (path string , d fs.DirEntry , err error ) error {
276276 if err != nil {
277277 return nil // Skip files we can't access
278278 }
279-
279+
280280 if ! d .IsDir () && strings .HasSuffix (d .Name (), ".md" ) {
281281 files = append (files , path )
282282 }
283-
283+
284284 return nil
285285 })
286-
286+
287287 return files , err
288288}
289289
@@ -292,7 +292,7 @@ func SearchDocs(query, filter string, limit int, contentDir string) (*SearchResp
292292 if contentDir == "" {
293293 return nil , fmt .Errorf ("content directory not specified" )
294294 }
295-
295+
296296 // Check if content directory exists
297297 if _ , err := os .Stat (contentDir ); os .IsNotExist (err ) {
298298 return & SearchResponse {
@@ -303,44 +303,44 @@ func SearchDocs(query, filter string, limit int, contentDir string) (*SearchResp
303303 Showing : 0 ,
304304 }, fmt .Errorf ("content directory not found: %s" , contentDir )
305305 }
306-
306+
307307 // Find all markdown files
308308 markdownFiles , err := findMarkdownFiles (contentDir )
309309 if err != nil {
310310 return nil , fmt .Errorf ("failed to find markdown files: %w" , err )
311311 }
312-
312+
313313 var results []SearchResult
314314 queryLower := strings .ToLower (query )
315-
315+
316316 // Search through files
317317 for _ , filePath := range markdownFiles {
318318 // Apply filter
319319 if ! matchesFilter (filePath , filter ) {
320320 continue
321321 }
322-
322+
323323 // Read file
324324 content , err := os .ReadFile (filePath )
325325 if err != nil {
326326 continue // Skip files we can't read
327327 }
328-
328+
329329 contentStr := string (content )
330-
330+
331331 // Parse frontmatter
332332 frontmatter , bodyContent := parseFrontMatter (contentStr )
333-
333+
334334 // Skip unlisted pages
335335 if frontmatter .Unlisted {
336336 continue
337337 }
338-
338+
339339 // Check if query matches (case insensitive)
340340 if ! strings .Contains (strings .ToLower (contentStr ), queryLower ) {
341341 continue
342342 }
343-
343+
344344 // Extract metadata
345345 title := frontmatter .Title
346346 if title == "" {
@@ -349,68 +349,68 @@ func SearchDocs(query, filter string, limit int, contentDir string) (*SearchResp
349349 if title == "" {
350350 title = "Untitled"
351351 }
352-
352+
353353 url := SiteURL + filePathToURL (filePath , contentDir )
354354 contentType := determineType (filePath )
355355 snippet := extractSnippet (bodyContent , query , 250 )
356356 score := calculateRelevance (contentStr , title , query )
357-
357+
358358 result := SearchResult {
359359 Title : title ,
360360 URL : url ,
361361 Snippet : snippet ,
362362 Type : contentType ,
363363 Score : score ,
364364 }
365-
365+
366366 results = append (results , result )
367367 }
368-
368+
369369 // Sort by relevance score (highest first)
370370 sort .Slice (results , func (i , j int ) bool {
371371 return results [i ].Score > results [j ].Score
372372 })
373-
373+
374374 // Limit results
375375 total := len (results )
376376 if limit > 0 && limit < len (results ) {
377377 results = results [:limit ]
378378 }
379-
379+
380380 if filter == "" {
381381 filter = "all"
382382 }
383-
383+
384384 response := & SearchResponse {
385385 Query : query ,
386386 Filter : filter ,
387387 Results : results ,
388388 Total : total ,
389389 Showing : len (results ),
390390 }
391-
391+
392392 return response , nil
393393}
394394
395395// FindDocsRepo attempts to locate the docs repository
396396func FindDocsRepo () string {
397397 candidates := []string {
398398 "../docs" ,
399- "../../docs" ,
399+ "../../docs" ,
400400 "./docs" ,
401401 }
402-
402+
403403 for _ , candidate := range candidates {
404404 absPath , err := filepath .Abs (candidate )
405405 if err != nil {
406406 continue
407407 }
408-
408+
409409 contentDir := filepath .Join (absPath , "content" )
410410 if _ , err := os .Stat (contentDir ); err == nil {
411411 return absPath
412412 }
413413 }
414-
414+
415415 return ""
416- }
416+ }
0 commit comments