diff --git a/dict/main.go b/dict/main.go
index 8b4328b..5234d26 100644
--- a/dict/main.go
+++ b/dict/main.go
@@ -69,10 +69,11 @@ func ParseEntry(entry jmdict.JmdictEntry) Entry {
}
}
-func Search(query string) (exactResults []Entry, otherResults []Entry, truncated bool) {
+func Search(query string) queryResult {
query = strings.TrimSpace(query)
- exactResults = make([]Entry, 0)
- otherResults = make([]Entry, 0)
+ exactResults := make([]Entry, 0)
+ otherResults := make([]Entry, 0)
+ truncated := false
count := 0
for _, jmdictEntry := range dict.Entries {
exactMatch := false
@@ -105,18 +106,8 @@ func Search(query string) (exactResults []Entry, otherResults []Entry, truncated
break
}
}
- return
-}
-
-type searchTemplateData struct {
- ExactResults []Entry
- OtherResults []Entry
- Truncated bool
- Count int
-}
-
-func initSearchTemplateData(exactResults []Entry, otherResults []Entry, truncated bool) searchTemplateData {
- return searchTemplateData{
+ return queryResult{
+ Query: query,
ExactResults: exactResults,
OtherResults: otherResults,
Truncated: truncated,
@@ -124,6 +115,26 @@ func initSearchTemplateData(exactResults []Entry, otherResults []Entry, truncate
}
}
+func Lookup(word string) *Entry {
+ for _, jmdictEntry := range dict.Entries {
+ entry := ParseEntry(jmdictEntry)
+ if entry.Kanji == word {
+ return &entry
+ }
+ }
+ return nil
+}
+
+type queryResult struct {
+ // Fields must be capitalized
+ // to be accessible in templates
+ Query string
+ ExactResults []Entry
+ OtherResults []Entry
+ Truncated bool
+ Count int
+}
+
func main() {
err := LoadDict()
if err != nil {
@@ -133,51 +144,80 @@ func main() {
fmt.Println("JMdict loaded!")
r := mux.NewRouter()
r.HandleFunc("/", httputils.GenerateHandler(
- "index.html",
func(w http.ResponseWriter, r *http.Request) bool { return true },
- func(w http.ResponseWriter, r *http.Request) any { return nil },
+ httputils.NewTemplateSet("index.html"),
+ func(w http.ResponseWriter, r *http.Request) (string, any) { return "index.html", nil },
[]string{http.MethodGet},
))
- redirectToHome := func(w http.ResponseWriter, r *http.Request) {
- http.Redirect(w, r, "/", http.StatusPermanentRedirect)
+ rawSearchHandler := func(w http.ResponseWriter, r *http.Request) {
+ r.ParseMultipartForm(0)
+ q := r.FormValue("q")
+ var redirect string
+ if q == "" {
+ redirect = "/"
+ } else {
+ redirect = "/search/" + q
+ }
+ http.Redirect(w, r, redirect, http.StatusMovedPermanently)
}
- r.HandleFunc("/search", redirectToHome)
- r.HandleFunc("/search/", redirectToHome)
+ r.HandleFunc("/search", rawSearchHandler)
+ r.HandleFunc("/search/", rawSearchHandler)
r.HandleFunc("/search/{query}", httputils.GenerateHandler(
- "index.html",
- func(w http.ResponseWriter, r *http.Request) bool {
- return true
- },
- func(w http.ResponseWriter, r *http.Request) any {
- query := mux.Vars(r)["query"]
- return struct {
- Query string
- Results searchTemplateData
- }{
- Query: query,
- Results: initSearchTemplateData(Search(query)),
- }
- },
- []string{http.MethodGet},
- ))
- r.HandleFunc("/api/search", httputils.GenerateHandler(
- "search.html",
+ // handler whether or not to use template
func(w http.ResponseWriter, r *http.Request) bool {
+ // If Accept: applicaiton/json we'll use the template
if r.Header.Get("Accept") != "application/json" {
return true
}
+
+ // Otherwise, let's send JSON
+ query := mux.Vars(r)["query"]
+ result := Search(query)
+ jsonBytes, _ := json.Marshal(append(result.ExactResults, result.OtherResults...))
+
w.Header().Set("Content-Type", "application/json; charset=utf-8")
- r.ParseMultipartForm(0)
- query := r.FormValue("q")
- exactResults, otherResults, _ := Search(query)
- jsonBytes, _ := json.Marshal(append(exactResults, otherResults...))
fmt.Fprint(w, string(jsonBytes))
+
return false
},
- func(w http.ResponseWriter, r *http.Request) any {
- r.ParseMultipartForm(0)
- query := r.FormValue("q")
- return initSearchTemplateData(Search(query))
+ httputils.NewTemplateSet("index.html", "search.html"),
+ // template data
+ func(w http.ResponseWriter, r *http.Request) (template string, data any) {
+ if r.Header.Get("HX-Request") == "" {
+ template = "search.html"
+ } else {
+ template = "search"
+ }
+ // Only runs if handler returns true
+ query := mux.Vars(r)["query"]
+ data = Search(query)
+ return
+ },
+ []string{http.MethodGet},
+ ))
+ rawWordHandler := func(w http.ResponseWriter, r *http.Request) {
+ fmt.Println("Redirecting raw word handler")
+ http.Redirect(w, r, "/", http.StatusMovedPermanently)
+ }
+ r.HandleFunc("/word", rawWordHandler)
+ r.HandleFunc("/word/", rawWordHandler)
+ r.HandleFunc("/word/{word}", httputils.GenerateHandler(
+ func(w http.ResponseWriter, r *http.Request) bool { return true },
+ // Order matters
+ // word.html overrided the results block in index.html
+ // so should be loaded second
+ httputils.NewTemplateSet("index.html", "word.html"),
+ func(w http.ResponseWriter, r *http.Request) (template string, data any) {
+ template = "word.html"
+ query := mux.Vars(r)["word"]
+ data = struct {
+ Query any
+ Entry *Entry
+ }{
+ Query: nil,
+ Entry: Lookup(query),
+ }
+ return
},
[]string{http.MethodGet},
))
diff --git a/dict/templates/index.html b/dict/templates/index.html
index ca01362..ea33ea8 100644
--- a/dict/templates/index.html
+++ b/dict/templates/index.html
@@ -1,33 +1,42 @@
+{{- define "index" -}}
- jidict
+ {{ block "title" . }}{{ template "sitetitle" . }}{{ end }}
-
+
- {{ with .Results }}{{ template "search" . }}{{ end }}
+ {{ block "results" . }}{{ if .Query }}{{ template "search" . }}{{ end }}{{ end }}
-
\ No newline at end of file
+
+{{- end -}}
+{{- template "index" . -}}
\ No newline at end of file
diff --git a/dict/templates/definition.html b/dict/templates/partials/definition.html
similarity index 100%
rename from dict/templates/definition.html
rename to dict/templates/partials/definition.html
diff --git a/dict/templates/partials/entry.html b/dict/templates/partials/entry.html
new file mode 100644
index 0000000..9d74569
--- /dev/null
+++ b/dict/templates/partials/entry.html
@@ -0,0 +1,23 @@
+{{ define "entry" }}
+
+
+ {{- if .Kanji -}}
+ {{- .Kanji -}}( {{- .Reading -}} )
+ {{- else -}}
+ {{- .Reading -}}
+ {{- end -}}
+
+ {{- $count := len .Definitions -}}
+ {{ if eq $count 1 -}}
+
{{- template "definition" (index .Definitions 0) -}}
+ {{- else if ne $count 0 -}}
+
+ {{- range .Definitions }}
+
+ {{ template "definition" . }}
+
+ {{- end }}
+
+ {{- end }}
+
+{{ end }}
\ No newline at end of file
diff --git a/dict/templates/partials/entryfull.html b/dict/templates/partials/entryfull.html
new file mode 100644
index 0000000..8488cbc
--- /dev/null
+++ b/dict/templates/partials/entryfull.html
@@ -0,0 +1,23 @@
+{{ define "entryfull" }}
+
+
+ {{- if .Kanji -}}
+ {{- .Kanji -}}( {{- .Reading -}} )
+ {{- else -}}
+ {{- .Reading -}}
+ {{- end -}}
+
+ {{- $count := len .Definitions -}}
+ {{ if eq $count 1 -}}
+
{{- template "definition" (index .Definitions 0) -}}
+ {{- else if ne $count 0 -}}
+
+ {{- range .Definitions }}
+
+ {{ template "definition" . }}
+
+ {{- end }}
+
+ {{- end }}
+
+{{ end }}
\ No newline at end of file
diff --git a/dict/templates/partials/search.html b/dict/templates/partials/search.html
new file mode 100644
index 0000000..2c6b809
--- /dev/null
+++ b/dict/templates/partials/search.html
@@ -0,0 +1,11 @@
+{{- define "search" -}}
+{{ if .Truncated }}Truncated results, showing first {{ .Count }}{{ else }}{{ if eq .Count 0 }}No results{{ else }}{{ .Count }} result{{ if ne .Count 1}}s{{ end }}{{ end }}{{ end }}.
+{{ range .ExactResults -}}
+{{- template "entry" . -}}
+{{- end }}
+{{ if and (ne (len .ExactResults) 0) (ne (len .OtherResults) 0) }} {{ end }}
+{{ range .OtherResults -}}
+{{ template "entry" . }}
+{{- end -}}
+{{- end -}}
+{{- template "search" . -}}
\ No newline at end of file
diff --git a/dict/templates/partials/sitetitle.html b/dict/templates/partials/sitetitle.html
new file mode 100644
index 0000000..67e6c0a
--- /dev/null
+++ b/dict/templates/partials/sitetitle.html
@@ -0,0 +1 @@
+{{ define "sitetitle" }}jidict{{ end }}
\ No newline at end of file
diff --git a/dict/templates/search.html b/dict/templates/search.html
index 90053b1..ffa8680 100644
--- a/dict/templates/search.html
+++ b/dict/templates/search.html
@@ -1,11 +1,9 @@
-{{- define "search" -}}
-{{ if .Truncated }}Truncated results, showing first {{ .Count }}{{ else }}{{ if eq .Count 0 }}No results{{ else }}{{ .Count }} result{{ if ne .Count 1}}s{{ end }}{{ end }}{{ end }}.
-{{- range .ExactResults -}}
-{{- template "word" . -}}
-{{- end }}
-
-{{ range .OtherResults -}}
-{{ template "word" . }}
+{{- define "title" }}{{ .Query }} search - {{ template "sitetitle" . }}{{- end -}}
+
+{{- define "value" }}{{ .Query }}{{- end -}}
+
+{{- define "results" -}}
+{{- template "entryfull" .Entry -}}
{{- end -}}
-{{- end -}}
-{{- template "search" . -}}
\ No newline at end of file
+
+{{- template "index" . -}}
\ No newline at end of file
diff --git a/dict/templates/word.html b/dict/templates/word.html
index 40d56f8..245c10b 100644
--- a/dict/templates/word.html
+++ b/dict/templates/word.html
@@ -1,22 +1,5 @@
-{{ define "word" }}
-
-
- {{- if .Kanji -}}
- {{- .Kanji -}}( {{- .Reading -}} )
- {{- else -}}
- {{- .Reading -}}
- {{- end -}}
-
- {{ if le (len .Definitions) 2 -}}
-
{{- template "definition" (index .Definitions 0) -}}
- {{- else -}}
-
- {{- range .Definitions }}
-
- {{ template "definition" . }}
-
- {{- end }}
-
- {{- end }}
-
-{{ end }}
\ No newline at end of file
+{{- define "title" }}{{ .Entry.Kanji }} - {{ template "sitetitle" . }}{{- end -}}
+{{- define "results" -}}
+{{- template "entryfull" .Entry -}}
+{{- end -}}
+{{- template "index" . -}}
\ No newline at end of file
diff --git a/httputils/handler.go b/httputils/handler.go
index a98c763..d1f152b 100644
--- a/httputils/handler.go
+++ b/httputils/handler.go
@@ -1,25 +1,21 @@
package httputils
import (
+ "bytes"
"fmt"
"net/http"
"os"
"path/filepath"
+ "reflect"
"strings"
- "text/template"
"time"
)
type Handler = func(http.ResponseWriter, *http.Request)
-const templateFolder = "templates"
-
-var templatePaths, templateModTimes, _ = getTemplates()
-var templates *template.Template = template.Must(template.ParseFiles(templatePaths...))
-
-func getTemplates() ([]string, map[string]time.Time, error) {
+func getPartials() ([]string, map[string]time.Time, error) {
var modTimes map[string]time.Time = make(map[string]time.Time)
- err := filepath.Walk(templateFolder, func(path string, info os.FileInfo, err error) error {
+ err := filepath.Walk(partialsFolder, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
@@ -37,35 +33,15 @@ func getTemplates() ([]string, map[string]time.Time, error) {
return paths, modTimes, err
}
-func reloadTemplateIfModified(path string) {
- fileInfo, _ := os.Stat(path)
- modTime := fileInfo.ModTime()
- if modTime.After(templateModTimes[path]) {
- fmt.Printf("Reloading template %s...\n", path)
- templates.ParseFiles(path)
- templateModTimes[path] = modTime
- }
-}
-
-func reloadTemplatesIfModified() {
- for _, path := range templatePaths {
- reloadTemplateIfModified(path)
- }
-}
-
const reloadTemplates = true
func GenerateHandler(
- file string,
handler func(http.ResponseWriter, *http.Request) bool,
- data func(http.ResponseWriter, *http.Request) any,
+ templateSet TemplateSet,
+ template func(http.ResponseWriter, *http.Request) (template string, data any),
methods []string,
) Handler {
return func(w http.ResponseWriter, r *http.Request) {
- // All templates must be reloaded in case of dependencies
- if reloadTemplates {
- reloadTemplatesIfModified()
- }
for _, method := range methods {
if method == r.Method {
goto ok
@@ -75,9 +51,18 @@ func GenerateHandler(
return
ok:
renderTemplate := handler(w, r)
- if renderTemplate && file != "" {
+ if renderTemplate {
+ file_path, data := template(w, r)
+ buf := &bytes.Buffer{}
+ err := templateSet.ExecuteTemplate(buf, file_path, reflect.ValueOf(data))
+ if err != nil {
+ fmt.Println(err)
+ w.WriteHeader(http.StatusInternalServerError)
+ fmt.Fprint(w, "500 Internal Server Error")
+ return
+ }
w.Header().Set("Content-Type", "text/html; charset=utf-8")
- templates.ExecuteTemplate(w, file, data(w, r))
+ fmt.Fprint(w, buf.String())
}
}
}
diff --git a/httputils/templates.go b/httputils/templates.go
new file mode 100644
index 0000000..c4e8eb2
--- /dev/null
+++ b/httputils/templates.go
@@ -0,0 +1,86 @@
+package httputils
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "strings"
+ "text/template"
+ "time"
+)
+
+type TemplateSet struct {
+ templates *template.Template
+ paths []string
+ modTimes map[string]time.Time
+}
+
+func newTemplateSet(partials *TemplateSet, paths ...string) TemplateSet {
+ var partialPaths []string
+ if partials == nil {
+ partialPaths = make([]string, 0)
+ } else {
+ partialPaths = partials.paths
+ }
+ allPaths := append(partialPaths, paths...)
+ modTimes := make(map[string]time.Time)
+ for _, path := range allPaths {
+ fileInfo, _ := os.Stat(path)
+ modTimes[path] = fileInfo.ModTime()
+ }
+ templates := template.Must(template.ParseFiles(allPaths...))
+ return TemplateSet{
+ templates: templates,
+ paths: allPaths,
+ modTimes: modTimes,
+ }
+}
+
+func NewTemplateSet(paths ...string) TemplateSet {
+ for i, path := range paths {
+ paths[i] = fmt.Sprintf("%s/%s", templateFolder, path)
+ }
+ return newTemplateSet(&partials, paths...)
+}
+
+func (templateSet *TemplateSet) ExecuteTemplate(wr io.Writer, name string, data any) error {
+ templateSet.reloadTemplatesIfModified()
+ return templateSet.templates.ExecuteTemplate(wr, name, data)
+}
+
+func (templateSet *TemplateSet) reloadTemplateIfModified(path string) {
+ fileInfo, _ := os.Stat(path)
+ modTime := fileInfo.ModTime()
+ if modTime.After(templateSet.modTimes[path]) {
+ fmt.Printf("Reloading template %s...\n", path)
+ templateSet.templates.ParseFiles(path)
+ templateSet.modTimes[path] = modTime
+ }
+}
+
+func (templateSet *TemplateSet) reloadTemplatesIfModified() {
+ for _, path := range templateSet.paths {
+ templateSet.reloadTemplateIfModified(path)
+ }
+}
+
+func getTemplatePathsInDirectory(directory string) (paths []string, err error) {
+ paths = make([]string, 0)
+ err = filepath.Walk(directory, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ if !info.IsDir() && strings.HasSuffix(path, ".html") {
+ paths = append(paths, path)
+ }
+ return nil
+ })
+ return
+}
+
+const templateFolder = "templates"
+const partialsFolder = templateFolder + "/partials"
+
+var paths, _ = getTemplatePathsInDirectory(partialsFolder)
+var partials = newTemplateSet(nil, paths...)