working search
This commit is contained in:
parent
6ac7ba8fd0
commit
08894f1ef4
|
@ -15,6 +15,7 @@ type Entry struct {
|
|||
db *db.Database
|
||||
ID int64
|
||||
Slug string
|
||||
Title string
|
||||
Content string
|
||||
Tags []string
|
||||
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 {
|
||||
return e, err
|
||||
}
|
||||
e.Title = e.GenerateTitle()
|
||||
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 {
|
||||
return e, err
|
||||
}
|
||||
e.Title = e.GenerateTitle()
|
||||
return e, e.populateTags()
|
||||
}
|
||||
|
||||
func Search(db *db.Database, query string) ([]*Entry, error) {
|
||||
entries := []*Entry{}
|
||||
log.Debug().Str("query", query).Msg("searching")
|
||||
if query != "" {
|
||||
q := `select * from entries where content like '%?%'`
|
||||
err := db.Select(&entries, q, query)
|
||||
q := `select * from entries where content like ?`
|
||||
err := db.Select(&entries, q, "%"+query+"%")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -95,6 +99,7 @@ func Search(db *db.Database, query string) ([]*Entry, error) {
|
|||
}
|
||||
for _, e := range entries {
|
||||
e.db = db
|
||||
e.Title = e.GenerateTitle()
|
||||
e.populateTags()
|
||||
}
|
||||
return entries, nil
|
||||
|
@ -141,18 +146,16 @@ func (e *Entry) removeTag(tag string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func (e *Entry) UniqueSlug() string {
|
||||
func (e *Entry) GenerateTitle() string {
|
||||
candidate := strings.Split(e.Content, "\n")[0]
|
||||
candidateNumber := 0
|
||||
|
||||
r := regexp.MustCompile(`[^a-zA-Z0-9 -]`)
|
||||
candidate = r.ReplaceAllString(candidate, "")
|
||||
candidate = strings.TrimSpace(candidate)
|
||||
candidate = strings.ReplaceAll(candidate, " ", "-")
|
||||
if len(candidate) == 0 {
|
||||
candidate = "untitled"
|
||||
}
|
||||
candidate = strings.ToLower(candidate)
|
||||
|
||||
q := `select slug from entries where slug like ?`
|
||||
slugs := []string{}
|
||||
|
@ -179,6 +182,20 @@ func (e *Entry) UniqueSlug() string {
|
|||
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 {
|
||||
if e.ID == -1 {
|
||||
return e.Create()
|
||||
|
|
|
@ -56,23 +56,22 @@
|
|||
this.$store.dispatch('getFile', slug)
|
||||
},
|
||||
tagUpdate: function(newTags) {
|
||||
if (JSON.stringify(newTags) === JSON.stringify(this.file.Tags))
|
||||
if (JSON.stringify(newTags) === JSON.stringify(this.$store.state.file.Tags))
|
||||
return
|
||||
this.file.Tags = newTags
|
||||
this.$store.commit('setTags', newTags)
|
||||
this.$emit('markDirty', true)
|
||||
this.save()
|
||||
},
|
||||
updateContent: function (newContent) {
|
||||
if (this.$store.state.file.Content === newContent)
|
||||
return
|
||||
this.$store.state.file.Content = newContent
|
||||
this.$store.commit('setContent', newContent)
|
||||
this.$emit('markDirty', true)
|
||||
this.save()
|
||||
},
|
||||
save: function () {
|
||||
this.$store.state.file.Content = this.content
|
||||
console.log("Saving file: " + this.file.Slug)
|
||||
this.$store.dispatch('saveFile', this.file)
|
||||
this.$store.commit('setContent', this.content)
|
||||
this.$store.dispatch('saveFile')
|
||||
.then(res => {
|
||||
this.$emit('markDirty', false)
|
||||
this.$store.dispatch('updateSearch')
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<b-col>
|
||||
<b-link
|
||||
:to="{ name: target, params: { slug: item.Slug } }"
|
||||
>{{item.Slug}}</b-link>
|
||||
>{{item.Title}}</b-link>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-container>
|
||||
|
@ -29,6 +29,9 @@
|
|||
watch: {
|
||||
query: function(newValue) {
|
||||
this.queryText = newValue
|
||||
},
|
||||
queryText: function(newValue) {
|
||||
this.$store.commit('setQuery', newValue)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -40,6 +40,12 @@ export default new Vuex.Store({
|
|||
},
|
||||
setFile(state, file) {
|
||||
state.file = file
|
||||
},
|
||||
setContent(state, content) {
|
||||
state.file.Content = content
|
||||
},
|
||||
setTags(state, tags) {
|
||||
state.file.Tags = tags
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
|
@ -51,29 +57,23 @@ export default new Vuex.Store({
|
|||
commit('setFile', res.data)
|
||||
})
|
||||
},
|
||||
getSearchResults: function ({dispatch, commit}, query) {
|
||||
commit('setQuery', query)
|
||||
getSearchResults: function ({dispatch}) {
|
||||
dispatch('updateSearch')
|
||||
},
|
||||
updateSearch: function ({commit, state}) {
|
||||
let query = state.query
|
||||
if (query) {
|
||||
let query = state.query || ""
|
||||
console.log("running search for: " + 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))
|
||||
.then(res =>{
|
||||
console.log("getSearchResults:"+res.data)
|
||||
commit('setResults', res.data)
|
||||
})
|
||||
},
|
||||
saveFile: function(state, file) {
|
||||
return axios.put('/v1/entries/'+file.Slug, file)
|
||||
saveFile: function({state}) {
|
||||
console.log(state.file)
|
||||
if (state.file)
|
||||
return axios.put('/v1/entries/'+state.file.Slug, state.file)
|
||||
}
|
||||
},
|
||||
modules: {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<b-col md="5">
|
||||
|
||||
<div>
|
||||
<b-tabs content-class="mt-3">
|
||||
<b-tabs content-class="mt-3" v-model="tabIndex">
|
||||
<b-tab active>
|
||||
<template v-slot:title>
|
||||
Editor <span v-bind:class="{ dirty: isDirty, clean: !isDirty }">•</span>
|
||||
|
@ -25,7 +25,7 @@
|
|||
</b-col>
|
||||
<b-col md="2">
|
||||
<h2>Search Results</h2>
|
||||
<SearchResults target="console-slug" :query="query"/>
|
||||
<SearchResults target="console-slug" />
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-container>
|
||||
|
@ -49,15 +49,11 @@ export default {
|
|||
data: function() {
|
||||
return {
|
||||
isDirty: false,
|
||||
query: ''
|
||||
tabIndex: 0
|
||||
}
|
||||
},
|
||||
provide: {
|
||||
update: function() {}
|
||||
},
|
||||
methods: {
|
||||
markDirty: function(dirty) {
|
||||
console.log('markDirty:'+dirty)
|
||||
this.isDirty = dirty
|
||||
}
|
||||
},
|
||||
|
@ -65,7 +61,6 @@ export default {
|
|||
// called before the route that renders this component is confirmed.
|
||||
// does NOT have access to `this` component instance,
|
||||
// because it has not been created yet when this guard is called!
|
||||
console.log('beforeRouteEnter'+to+from)
|
||||
next()
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
|
@ -83,13 +78,13 @@ export default {
|
|||
next(false)
|
||||
}
|
||||
}
|
||||
this.tabIndex = 0
|
||||
next()
|
||||
},
|
||||
beforeRouteLeave (to, from, next) {
|
||||
// called when the route that renders this component is about to
|
||||
// be navigated away from.
|
||||
// has access to `this` component instance.
|
||||
console.log('beforeRouteLeave'+to+from)
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
|
33
todo.adoc
33
todo.adoc
|
@ -1,27 +1,20 @@
|
|||
= Todo
|
||||
: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
|
||||
** [ ] integrate CI/CD
|
||||
** [ ] run on https://cabinet.chrissexton.org[cabinet.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
|
||||
|
|
|
@ -81,8 +81,11 @@ func (web *Web) newEntry(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func (web *Web) allEntries(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
query := r.Form.Get("query")
|
||||
query := ""
|
||||
items, ok := r.URL.Query()["query"]
|
||||
if ok {
|
||||
query = items[0]
|
||||
}
|
||||
entries, err := entry.Search(web.db, query)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
|
|
Loading…
Reference in New Issue