Better search terms include+exclude
This commit is contained in:
parent
ede472a128
commit
ee0e6cf696
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
"git.kirsle.net/apps/gophertype/pkg/models"
|
||||
"git.kirsle.net/apps/gophertype/pkg/responses"
|
||||
"git.kirsle.net/apps/gophertype/pkg/search"
|
||||
"git.kirsle.net/apps/gophertype/pkg/session"
|
||||
)
|
||||
|
||||
|
@ -21,7 +22,10 @@ func BlogSearch(w http.ResponseWriter, r *http.Request) {
|
|||
page = a
|
||||
}
|
||||
|
||||
pp, err := models.Posts.SearchPosts(query, page, 24)
|
||||
// Parse their search string (includes and excludes)
|
||||
search := search.ParseSearchString(query)
|
||||
|
||||
pp, err := models.Posts.SearchPosts(search, page, 24)
|
||||
if err != nil {
|
||||
session.FlashError(w, r, "Error searching posts: %s", err)
|
||||
responses.Redirect(w, r, "/")
|
||||
|
@ -31,6 +35,7 @@ func BlogSearch(w http.ResponseWriter, r *http.Request) {
|
|||
v := responses.NewTemplateVars(w, r)
|
||||
v.V["posts"] = pp
|
||||
v.V["search"] = query
|
||||
v.V["terms"] = search
|
||||
|
||||
responses.RenderTemplate(w, r, "_builtin/blog/search.gohtml", v)
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"git.kirsle.net/apps/gophertype/pkg/markdown"
|
||||
"git.kirsle.net/apps/gophertype/pkg/mogrify"
|
||||
"git.kirsle.net/apps/gophertype/pkg/rng"
|
||||
"git.kirsle.net/apps/gophertype/pkg/search"
|
||||
"github.com/albrow/forms"
|
||||
)
|
||||
|
||||
|
@ -145,7 +146,7 @@ func (m postMan) GetIndexPosts(privacy string, page, perPage int) (PagedPosts, e
|
|||
}
|
||||
|
||||
// SearchPosts does a full text search over posts (public only).
|
||||
func (m postMan) SearchPosts(search string, page, perPage int) (PagedPosts, error) {
|
||||
func (m postMan) SearchPosts(search *search.Search, page, perPage int) (PagedPosts, error) {
|
||||
var pp = PagedPosts{
|
||||
Page: page,
|
||||
PerPage: perPage,
|
||||
|
@ -158,10 +159,29 @@ func (m postMan) SearchPosts(search string, page, perPage int) (PagedPosts, erro
|
|||
pp.PerPage = 20
|
||||
}
|
||||
|
||||
var like = fmt.Sprintf("%%%s%%", search)
|
||||
var (
|
||||
wheres = []string{}
|
||||
placeholders = []interface{}{}
|
||||
)
|
||||
|
||||
// Global filters.
|
||||
wheres = append(wheres, "privacy = ?")
|
||||
placeholders = append(placeholders, Public)
|
||||
|
||||
// Search terms.
|
||||
for _, term := range search.Includes {
|
||||
var ilike = "%" + strings.ToLower(term) + "%"
|
||||
wheres = append(wheres, "(title ILIKE ? OR body ILIKE ?)")
|
||||
placeholders = append(placeholders, ilike, ilike)
|
||||
}
|
||||
for _, term := range search.Excludes {
|
||||
var ilike = "%" + strings.ToLower(term) + "%"
|
||||
wheres = append(wheres, "(title NOT ILIKE ? AND body NOT ILIKE ?)")
|
||||
placeholders = append(placeholders, ilike, ilike)
|
||||
}
|
||||
|
||||
query := DB.Preload("Author").Preload("Tags").
|
||||
Where("privacy = 'public' AND body ILIKE ?", like).
|
||||
Where(strings.Join(wheres, " AND "), placeholders...).
|
||||
Order("sticky desc, created_at desc")
|
||||
|
||||
// Count the total number of rows for paging purposes.
|
||||
|
|
72
pkg/search/search.go
Normal file
72
pkg/search/search.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
package search
|
||||
|
||||
import "strings"
|
||||
|
||||
// Search represents a parsed search query with inclusions and exclusions.
|
||||
type Search struct {
|
||||
Includes []string
|
||||
Excludes []string
|
||||
}
|
||||
|
||||
// ParseSearchString parses a user search query and supports "quoted phrases" and -negations.
|
||||
func ParseSearchString(input string) *Search {
|
||||
var result = new(Search)
|
||||
|
||||
var (
|
||||
negate bool
|
||||
phrase bool
|
||||
buf = []rune{}
|
||||
commit = func() {
|
||||
var text = strings.TrimSpace(string(buf))
|
||||
if len(text) == 0 {
|
||||
return
|
||||
}
|
||||
if negate {
|
||||
result.Excludes = append(result.Excludes, text)
|
||||
negate = false
|
||||
} else {
|
||||
result.Includes = append(result.Includes, text)
|
||||
}
|
||||
buf = []rune{}
|
||||
}
|
||||
)
|
||||
|
||||
for _, char := range input {
|
||||
// Inside a quoted phrase?
|
||||
if phrase {
|
||||
if char == '"' {
|
||||
// End of quoted phrase.
|
||||
commit()
|
||||
phrase = false
|
||||
continue
|
||||
}
|
||||
buf = append(buf, char)
|
||||
continue
|
||||
}
|
||||
|
||||
// Start a quoted phrase?
|
||||
if char == '"' {
|
||||
phrase = true
|
||||
continue
|
||||
}
|
||||
|
||||
// Negation indicator?
|
||||
if len(buf) == 0 && char == '-' {
|
||||
negate = true
|
||||
continue
|
||||
}
|
||||
|
||||
// End of a word?
|
||||
if char == ' ' {
|
||||
commit()
|
||||
continue
|
||||
}
|
||||
|
||||
buf = append(buf, char)
|
||||
}
|
||||
|
||||
// Last word?
|
||||
commit()
|
||||
|
||||
return result
|
||||
}
|
|
@ -8,7 +8,13 @@
|
|||
{{ $Pager := .V.posts }}
|
||||
|
||||
<p>
|
||||
<em>{{ $Pager.Total}} results found for:</em> {{.V.search}}
|
||||
<em>{{ $Pager.Total}} results found for:</em>
|
||||
{{ range .V.terms.Includes }}
|
||||
<span class="tag is-success">{{.}}</span>
|
||||
{{end}}
|
||||
{{ range .V.terms.Excludes }}
|
||||
<span class="tag is-danger">-{{.}}</span>
|
||||
{{end}}
|
||||
</p>
|
||||
|
||||
<div class="row">
|
||||
|
|
Loading…
Reference in New Issue
Block a user