working search

This commit is contained in:
Chris Sexton 2019-11-07 23:39:16 -05:00
parent 6ac7ba8fd0
commit 08894f1ef4
7 changed files with 67 additions and 57 deletions

View File

@ -15,6 +15,7 @@ type Entry struct {
db *db.Database db *db.Database
ID int64 ID int64
Slug string Slug string
Title string
Content string Content string
Tags []string Tags []string
Created time.Time Created time.Time
@ -66,6 +67,7 @@ func GetBySlug(db *db.Database, slug string) (Entry, error) {
if err := db.Get(&e, q, slug); err != nil { if err := db.Get(&e, q, slug); err != nil {
return e, err return e, err
} }
e.Title = e.GenerateTitle()
return e, e.populateTags() return e, e.populateTags()
} }
@ -75,14 +77,16 @@ func GetByID(db *db.Database, id int64) (Entry, error) {
if err := db.Get(&e, q, id); err != nil { if err := db.Get(&e, q, id); err != nil {
return e, err return e, err
} }
e.Title = e.GenerateTitle()
return e, e.populateTags() return e, e.populateTags()
} }
func Search(db *db.Database, query string) ([]*Entry, error) { func Search(db *db.Database, query string) ([]*Entry, error) {
entries := []*Entry{} entries := []*Entry{}
log.Debug().Str("query", query).Msg("searching")
if query != "" { if query != "" {
q := `select * from entries where content like '%?%'` q := `select * from entries where content like ?`
err := db.Select(&entries, q, query) err := db.Select(&entries, q, "%"+query+"%")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -95,6 +99,7 @@ func Search(db *db.Database, query string) ([]*Entry, error) {
} }
for _, e := range entries { for _, e := range entries {
e.db = db e.db = db
e.Title = e.GenerateTitle()
e.populateTags() e.populateTags()
} }
return entries, nil return entries, nil
@ -141,18 +146,16 @@ func (e *Entry) removeTag(tag string) error {
return err return err
} }
func (e *Entry) UniqueSlug() string { func (e *Entry) GenerateTitle() string {
candidate := strings.Split(e.Content, "\n")[0] candidate := strings.Split(e.Content, "\n")[0]
candidateNumber := 0 candidateNumber := 0
r := regexp.MustCompile(`[^a-zA-Z0-9 -]`) r := regexp.MustCompile(`[^a-zA-Z0-9 -]`)
candidate = r.ReplaceAllString(candidate, "") candidate = r.ReplaceAllString(candidate, "")
candidate = strings.TrimSpace(candidate) candidate = strings.TrimSpace(candidate)
candidate = strings.ReplaceAll(candidate, " ", "-")
if len(candidate) == 0 { if len(candidate) == 0 {
candidate = "untitled" candidate = "untitled"
} }
candidate = strings.ToLower(candidate)
q := `select slug from entries where slug like ?` q := `select slug from entries where slug like ?`
slugs := []string{} slugs := []string{}
@ -179,6 +182,20 @@ func (e *Entry) UniqueSlug() string {
return tmpCandidate return tmpCandidate
} }
func (e *Entry) UniqueSlug() string {
if e.Title == "" {
e.Title = e.GenerateTitle()
}
candidate := e.Title
r := regexp.MustCompile(`[^a-zA-Z0-9 -]`)
candidate = r.ReplaceAllString(candidate, "")
candidate = strings.TrimSpace(candidate)
candidate = strings.ReplaceAll(candidate, " ", "-")
candidate = strings.ToLower(candidate)
return candidate
}
func (e *Entry) Update() error { func (e *Entry) Update() error {
if e.ID == -1 { if e.ID == -1 {
return e.Create() return e.Create()

View File

@ -56,23 +56,22 @@
this.$store.dispatch('getFile', slug) this.$store.dispatch('getFile', slug)
}, },
tagUpdate: function(newTags) { tagUpdate: function(newTags) {
if (JSON.stringify(newTags) === JSON.stringify(this.file.Tags)) if (JSON.stringify(newTags) === JSON.stringify(this.$store.state.file.Tags))
return return
this.file.Tags = newTags this.$store.commit('setTags', newTags)
this.$emit('markDirty', true) this.$emit('markDirty', true)
this.save() this.save()
}, },
updateContent: function (newContent) { updateContent: function (newContent) {
if (this.$store.state.file.Content === newContent) if (this.$store.state.file.Content === newContent)
return return
this.$store.state.file.Content = newContent this.$store.commit('setContent', newContent)
this.$emit('markDirty', true) this.$emit('markDirty', true)
this.save() this.save()
}, },
save: function () { save: function () {
this.$store.state.file.Content = this.content this.$store.commit('setContent', this.content)
console.log("Saving file: " + this.file.Slug) this.$store.dispatch('saveFile')
this.$store.dispatch('saveFile', this.file)
.then(res => { .then(res => {
this.$emit('markDirty', false) this.$emit('markDirty', false)
this.$store.dispatch('updateSearch') this.$store.dispatch('updateSearch')

View File

@ -7,7 +7,7 @@
<b-col> <b-col>
<b-link <b-link
:to="{ name: target, params: { slug: item.Slug } }" :to="{ name: target, params: { slug: item.Slug } }"
>{{item.Slug}}</b-link> >{{item.Title}}</b-link>
</b-col> </b-col>
</b-row> </b-row>
</b-container> </b-container>
@ -29,6 +29,9 @@
watch: { watch: {
query: function(newValue) { query: function(newValue) {
this.queryText = newValue this.queryText = newValue
},
queryText: function(newValue) {
this.$store.commit('setQuery', newValue)
} }
}, },
computed: { computed: {

View File

@ -40,6 +40,12 @@ export default new Vuex.Store({
}, },
setFile(state, file) { setFile(state, file) {
state.file = file state.file = file
},
setContent(state, content) {
state.file.Content = content
},
setTags(state, tags) {
state.file.Tags = tags
} }
}, },
actions: { actions: {
@ -51,29 +57,23 @@ export default new Vuex.Store({
commit('setFile', res.data) commit('setFile', res.data)
}) })
}, },
getSearchResults: function ({dispatch, commit}, query) { getSearchResults: function ({dispatch}) {
commit('setQuery', query)
dispatch('updateSearch') dispatch('updateSearch')
}, },
updateSearch: function ({commit, state}) { updateSearch: function ({commit, state}) {
let query = state.query let query = state.query || ""
if (query) { console.log("running search for: " + query)
axios.get('/v1/entries?query='+query) axios.get('/v1/entries?query='+query)
.catch(err => state.addError(err))
.then(res =>{
console.log("getSearchResults:"+res.data)
commit('setResults', res.data)
})
}
axios.get('/v1/entries')
.catch(err => state.addError(err)) .catch(err => state.addError(err))
.then(res =>{ .then(res =>{
console.log("getSearchResults:"+res.data) console.log("getSearchResults:"+res.data)
commit('setResults', res.data) commit('setResults', res.data)
}) })
}, },
saveFile: function(state, file) { saveFile: function({state}) {
return axios.put('/v1/entries/'+file.Slug, file) console.log(state.file)
if (state.file)
return axios.put('/v1/entries/'+state.file.Slug, state.file)
} }
}, },
modules: { modules: {

View File

@ -8,7 +8,7 @@
<b-col md="5"> <b-col md="5">
<div> <div>
<b-tabs content-class="mt-3"> <b-tabs content-class="mt-3" v-model="tabIndex">
<b-tab active> <b-tab active>
<template v-slot:title> <template v-slot:title>
Editor <span v-bind:class="{ dirty: isDirty, clean: !isDirty }">&bull;</span> Editor <span v-bind:class="{ dirty: isDirty, clean: !isDirty }">&bull;</span>
@ -25,7 +25,7 @@
</b-col> </b-col>
<b-col md="2"> <b-col md="2">
<h2>Search Results</h2> <h2>Search Results</h2>
<SearchResults target="console-slug" :query="query"/> <SearchResults target="console-slug" />
</b-col> </b-col>
</b-row> </b-row>
</b-container> </b-container>
@ -49,15 +49,11 @@ export default {
data: function() { data: function() {
return { return {
isDirty: false, isDirty: false,
query: '' tabIndex: 0
} }
}, },
provide: {
update: function() {}
},
methods: { methods: {
markDirty: function(dirty) { markDirty: function(dirty) {
console.log('markDirty:'+dirty)
this.isDirty = dirty this.isDirty = dirty
} }
}, },
@ -65,7 +61,6 @@ export default {
// called before the route that renders this component is confirmed. // called before the route that renders this component is confirmed.
// does NOT have access to `this` component instance, // does NOT have access to `this` component instance,
// because it has not been created yet when this guard is called! // because it has not been created yet when this guard is called!
console.log('beforeRouteEnter'+to+from)
next() next()
}, },
beforeRouteUpdate (to, from, next) { beforeRouteUpdate (to, from, next) {
@ -83,13 +78,13 @@ export default {
next(false) next(false)
} }
} }
this.tabIndex = 0
next() next()
}, },
beforeRouteLeave (to, from, next) { beforeRouteLeave (to, from, next) {
// called when the route that renders this component is about to // called when the route that renders this component is about to
// be navigated away from. // be navigated away from.
// has access to `this` component instance. // has access to `this` component instance.
console.log('beforeRouteLeave'+to+from)
next() next()
} }
} }

View File

@ -1,27 +1,20 @@
= Todo = Todo
:icons: font :icons: font
* Operations * Backend
** Authentication
*** [ ] some kind of user auth
** save endpoint
*** [ ] add authentication/authorization
*** [ ] convert document to adoc (give format?)
*** [ ] check for unique tags
** [ ] search endpoint
*** [ ] search for tags
*** [ ] fulltext search
**** with link:https://blevesearch.com/docs/Getting%20Started/[Bleve]
* [ ] CLI Frontend
* [ ] Operations
** [ ] dockerize the build ** [ ] dockerize the build
** [ ] integrate CI/CD ** [ ] integrate CI/CD
** [ ] run on https://cabinet.chrissexton.org[cabinet.chrissexton.org] ** [ ] run on https://cabinet.chrissexton.org[cabinet.chrissexton.org]
** [ ] create redirect or https://cab.chrissexton.org[cab.chrissexton.org] ** [ ] create redirect or https://cab.chrissexton.org[cab.chrissexton.org]
* Vue Frontend
** [ ] spend some time learning about TypeScript/Vue.js style
** Documents
*** [x] adoc editor widget
** Authentication
*** [ ] some kind of user auth
** Views
*** [ ] editor view
*** [ ] public index/search view
* Backend
** [?] save endpoint
*** [ ] need to generate a slug for entries
*** [ ] add authentication/authorization
*** [ ] convert document to adoc (give format?)
*** [x] test in frontend
*** [ ] check for unique tags
*** [ ] set some db fields not null
** [ ] search endpoint
* CLI Frontend

View File

@ -81,8 +81,11 @@ func (web *Web) newEntry(w http.ResponseWriter, r *http.Request) {
} }
func (web *Web) allEntries(w http.ResponseWriter, r *http.Request) { func (web *Web) allEntries(w http.ResponseWriter, r *http.Request) {
r.ParseForm() query := ""
query := r.Form.Get("query") items, ok := r.URL.Query()["query"]
if ok {
query = items[0]
}
entries, err := entry.Search(web.db, query) entries, err := entry.Search(web.db, query)
if err != nil { if err != nil {
w.WriteHeader(500) w.WriteHeader(500)