diff options
Diffstat (limited to 'internal/web/search.go')
| -rw-r--r-- | internal/web/search.go | 64 |
1 files changed, 64 insertions, 0 deletions
diff --git a/internal/web/search.go b/internal/web/search.go new file mode 100644 index 0000000..ef554ae --- /dev/null +++ b/internal/web/search.go @@ -0,0 +1,64 @@ +package web + +import ( + "html/template" + "regexp" + "strings" + "unicode/utf8" + + "github.com/gramanas/blogspace/internal/store" +) + +const ( + searchPerPage = 20 + maxSearchRunes = 100 // longer queries are cut; nobody types more on purpose + snippetContext = 80 // runes kept on each side of the match +) + +// searchPattern turns what the reader typed into the regular expression both +// Postgres (~*) and Go run: each word literal, the spaces between them "anything +// in between", so "go tem" finds "Go templates". Blank → "". +func searchPattern(q string) string { + words := strings.Fields(q) + for i, w := range words { + words[i] = regexp.QuoteMeta(w) + } + return strings.Join(words, ".*") +} + +// searchHit is one result: the post plus the excerpt the list shows. +type searchHit struct { + store.Post + Snippet template.HTML +} + +// searchSnippet is a short piece of the Markdown body around the first match, +// with the match marked; when only the title matched it is the body's start. +func searchSnippet(body string, re *regexp.Regexp) template.HTML { + text := strings.Join(strings.Fields(body), " ") + loc := re.FindStringIndex(text) + if loc == nil { + loc = []int{0, 0} + } + before, match, after := text[:loc[0]], text[loc[0]:loc[1]], text[loc[1]:] + for n := utf8.RuneCountInString(before); n > snippetContext; n-- { // rune by rune: never cut inside one + _, size := utf8.DecodeRuneInString(before) + before = before[size:] + } + if len(before) < loc[0] { + before = "…" + before + } + if utf8.RuneCountInString(after) > snippetContext { + i := 0 + for n := 0; n < snippetContext; n++ { + _, size := utf8.DecodeRuneInString(after[i:]) + i += size + } + after = after[:i] + "…" + } + out := template.HTMLEscapeString(before) + if match != "" { + out += "<mark>" + template.HTMLEscapeString(match) + "</mark>" + } + return template.HTML(out + template.HTMLEscapeString(after)) +} |
