mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2025-10-29 09:15:33 +00:00
FTS5-Suche
This commit is contained in:
@@ -8,6 +8,8 @@ import (
|
|||||||
"github.com/Theodor-Springmann-Stiftung/musenalm/helpers/datatypes"
|
"github.com/Theodor-Springmann-Stiftung/musenalm/helpers/datatypes"
|
||||||
"github.com/pocketbase/dbx"
|
"github.com/pocketbase/dbx"
|
||||||
"github.com/pocketbase/pocketbase/core"
|
"github.com/pocketbase/pocketbase/core"
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/language"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -94,6 +96,52 @@ var CONTENTS_FTS5_FIELDS = []string{
|
|||||||
COMMENT_FIELD,
|
COMMENT_FIELD,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ErrInvalidQuery = errors.New("invalid input into the search function")
|
||||||
|
|
||||||
|
func NormalizeQuery(query string) []string {
|
||||||
|
query = datatypes.NormalizeString(query)
|
||||||
|
query = datatypes.DeleteTags(query)
|
||||||
|
query = datatypes.RemovePunctuation(query)
|
||||||
|
query = cases.Lower(language.German).String(query)
|
||||||
|
// TODO: how to normalize, which unicode normalization to use?
|
||||||
|
|
||||||
|
split := strings.Split(query, " ")
|
||||||
|
res := []string{}
|
||||||
|
for _, s := range split {
|
||||||
|
if len(s) > 2 {
|
||||||
|
res = append(res, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func FTS5Search(app core.App, table string, mapfq ...FTS5QueryRequest) ([]*FTS5IDQueryResult, error) {
|
||||||
|
if mapfq == nil || len(mapfq) == 0 || table == "" {
|
||||||
|
return nil, ErrInvalidQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
q := NewFTS5Query().From(table).SelectID()
|
||||||
|
for _, v := range mapfq {
|
||||||
|
for _, que := range v.Query {
|
||||||
|
q.AndMatch(v.Fields, que)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
querystring := q.Query()
|
||||||
|
if querystring == "" {
|
||||||
|
return nil, ErrInvalidQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
res := []*FTS5IDQueryResult{}
|
||||||
|
err := app.DB().NewQuery(querystring).All(&res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
func CreateFTS5TableQuery(tablename string, fields ...string) string {
|
func CreateFTS5TableQuery(tablename string, fields ...string) string {
|
||||||
if len(fields) == 0 {
|
if len(fields) == 0 {
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ import (
|
|||||||
"github.com/Theodor-Springmann-Stiftung/musenalm/helpers/datatypes"
|
"github.com/Theodor-Springmann-Stiftung/musenalm/helpers/datatypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type FTS5QueryRequest struct {
|
||||||
|
Fields []string
|
||||||
|
Query []string
|
||||||
|
}
|
||||||
|
|
||||||
type FTS5IDQueryResult struct {
|
type FTS5IDQueryResult struct {
|
||||||
ID string `db:"id"`
|
ID string `db:"id"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,15 @@ func BasicSearchSeries(app core.App, query string) ([]*Series, []*Series, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// INFO: Needing to differentiate matches
|
// INFO: Needing to differentiate matches
|
||||||
altids, err := FTS5SearchSeries(app, query)
|
querysplit := NormalizeQuery(query)
|
||||||
|
if len(querysplit) == 0 {
|
||||||
|
return series, []*Series{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
altids, err := FTS5Search(app, SERIES_TABLE, FTS5QueryRequest{
|
||||||
|
Fields: []string{SERIES_TITLE_FIELD, ANNOTATION_FIELD, REFERENCES_FIELD},
|
||||||
|
Query: querysplit,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
5
helpers/functions/slices.go
Normal file
5
helpers/functions/slices.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package functions
|
||||||
|
|
||||||
|
func Length(arr []any) int {
|
||||||
|
return len(arr)
|
||||||
|
}
|
||||||
@@ -1,58 +1,43 @@
|
|||||||
package pagemodels
|
package pagemodels
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
"github.com/Theodor-Springmann-Stiftung/musenalm/templating"
|
"github.com/Theodor-Springmann-Stiftung/musenalm/templating"
|
||||||
"github.com/pocketbase/pocketbase/core"
|
"github.com/pocketbase/pocketbase/core"
|
||||||
"github.com/pocketbase/pocketbase/tools/router"
|
"github.com/pocketbase/pocketbase/tools/router"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DefaultPage struct {
|
type DefaultPage[T IPageCollection] struct {
|
||||||
core.BaseRecordProxy
|
Record T
|
||||||
Name string
|
Name string
|
||||||
Template string
|
Template string
|
||||||
Layout string
|
Layout string
|
||||||
URL string
|
URL string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefaultPage(record *core.Record) *DefaultPage {
|
func (r *DefaultPage[T]) Up(app core.App, engine *templating.Engine) error {
|
||||||
i := &DefaultPage{}
|
_, err := app.FindCollectionByNameOrId(GeneratePageTableName(r.Name))
|
||||||
i.SetProxyRecord(record)
|
if err == sql.ErrNoRows {
|
||||||
return i
|
collection := r.Record.Collection(r.Name)
|
||||||
}
|
err = app.Save(collection)
|
||||||
|
if err != nil {
|
||||||
|
app.Logger().Error("Error saving collection", "Name", GeneratePageTableName(r.Name), "Error", err, "Collection", collection)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
app.Logger().Error("Error finding collection %s: %s", GeneratePageTableName(r.Name), err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (r *DefaultPage) Title() string {
|
|
||||||
return r.GetString(F_TITLE)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultPage) SetTitle(titel string) {
|
|
||||||
r.Set(F_TITLE, titel)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultPage) Description() string {
|
|
||||||
return r.GetString(F_DESCRIPTION)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultPage) SetDescription(beschreibung string) {
|
|
||||||
r.Set(F_DESCRIPTION, beschreibung)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultPage) Keywords() string {
|
|
||||||
return r.GetString(F_TAGS)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultPage) SetKeywords(keywords string) {
|
|
||||||
r.Set(F_TAGS, keywords)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultPage) Up(app core.App, engine *templating.Engine) error {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DefaultPage) Down(app core.App, engine *templating.Engine) error {
|
func (r *DefaultPage[T]) Down(app core.App, engine *templating.Engine) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *DefaultPage) Setup(router *router.Router[*core.RequestEvent], app core.App, engine *templating.Engine) error {
|
func (p *DefaultPage[T]) Setup(router *router.Router[*core.RequestEvent], app core.App, engine *templating.Engine) error {
|
||||||
router.GET(p.URL, func(e *core.RequestEvent) error {
|
router.GET(p.URL, func(e *core.RequestEvent) error {
|
||||||
data := make(map[string]interface{})
|
data := make(map[string]interface{})
|
||||||
|
|
||||||
@@ -64,14 +49,14 @@ func (p *DefaultPage) Setup(router *router.Router[*core.RequestEvent], app core.
|
|||||||
return engine.Response404(e, err, data)
|
return engine.Response404(e, err, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
p.SetProxyRecord(record)
|
p.Record.SetProxyRecord(record)
|
||||||
data["record"] = p
|
data["record"] = p
|
||||||
return engine.Response200(e, p.Template, data, p.Layout)
|
return engine.Response200(e, p.Template, data, p.Layout)
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *DefaultPage) Get(e *core.RequestEvent, engine *templating.Engine, data map[string]interface{}) error {
|
func (p *DefaultPage[T]) Get(e *core.RequestEvent, engine *templating.Engine, data map[string]interface{}) error {
|
||||||
err := p.SetCommonData(e.App, data)
|
err := p.SetCommonData(e.App, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return engine.Response404(e, err, data)
|
return engine.Response404(e, err, data)
|
||||||
@@ -80,17 +65,17 @@ func (p *DefaultPage) Get(e *core.RequestEvent, engine *templating.Engine, data
|
|||||||
return engine.Response200(e, p.Template, data, p.Layout)
|
return engine.Response200(e, p.Template, data, p.Layout)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *DefaultPage) SetCommonData(app core.App, data map[string]interface{}) error {
|
func (p *DefaultPage[T]) SetCommonData(app core.App, data map[string]interface{}) error {
|
||||||
record, err := p.GetLatestData(app)
|
record, err := p.GetLatestData(app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
p.SetProxyRecord(record)
|
p.Record.SetProxyRecord(record)
|
||||||
data["page"] = p
|
data["page"] = p.Record
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *DefaultPage) GetLatestData(app core.App) (*core.Record, error) {
|
func (p *DefaultPage[T]) GetLatestData(app core.App) (*core.Record, error) {
|
||||||
record := &core.Record{}
|
record := &core.Record{}
|
||||||
tn := GeneratePageTableName(p.Name)
|
tn := GeneratePageTableName(p.Name)
|
||||||
err := app.RecordQuery(tn).OrderBy("created").Limit(1).One(record)
|
err := app.RecordQuery(tn).OrderBy("created").Limit(1).One(record)
|
||||||
|
|||||||
44
pagemodels/defaultrecord.go
Normal file
44
pagemodels/defaultrecord.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package pagemodels
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pocketbase/pocketbase/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DefaultPageRecord struct {
|
||||||
|
core.BaseRecordProxy
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultPageRecord(record *core.Record) *DefaultPageRecord {
|
||||||
|
i := &DefaultPageRecord{}
|
||||||
|
i.SetProxyRecord(record)
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DefaultPageRecord) Title() string {
|
||||||
|
return r.GetString(F_TITLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DefaultPageRecord) SetTitle(titel string) {
|
||||||
|
r.Set(F_TITLE, titel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DefaultPageRecord) Description() string {
|
||||||
|
return r.GetString(F_DESCRIPTION)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DefaultPageRecord) SetDescription(beschreibung string) {
|
||||||
|
r.Set(F_DESCRIPTION, beschreibung)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DefaultPageRecord) Keywords() string {
|
||||||
|
return r.GetString(F_TAGS)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DefaultPageRecord) SetKeywords(keywords string) {
|
||||||
|
r.Set(F_TAGS, keywords)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DefaultPageRecord) Collection(pagename string) *core.Collection {
|
||||||
|
coll := BasePageCollection(pagename)
|
||||||
|
return coll
|
||||||
|
}
|
||||||
@@ -88,3 +88,13 @@ func (t *IndexTexte) Abs2() string {
|
|||||||
func (t *IndexTexte) SetAbs2(abs2 string) {
|
func (t *IndexTexte) SetAbs2(abs2 string) {
|
||||||
t.Set(F_INDEX_TEXTE_ABS2, abs2)
|
t.Set(F_INDEX_TEXTE_ABS2, abs2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *IndexTexte) Collection(pagename string) *core.Collection {
|
||||||
|
coll := BasePageCollection(pagename)
|
||||||
|
coll.Fields = append(coll.Fields, StandardPageFields()...)
|
||||||
|
coll.Fields = append(coll.Fields, core.NewFieldsList(
|
||||||
|
EditorField(F_INDEX_TEXTE_ABS1),
|
||||||
|
EditorField(F_INDEX_TEXTE_ABS2),
|
||||||
|
)...)
|
||||||
|
return coll
|
||||||
|
}
|
||||||
|
|||||||
8
pagemodels/pagecollection.go
Normal file
8
pagemodels/pagecollection.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package pagemodels
|
||||||
|
|
||||||
|
import "github.com/pocketbase/pocketbase/core"
|
||||||
|
|
||||||
|
type IPageCollection interface {
|
||||||
|
core.RecordProxy
|
||||||
|
Collection(pagename string) *core.Collection
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ package pagemodels
|
|||||||
const (
|
const (
|
||||||
P_DATENSCHUTZ_NAME = "datenschutz"
|
P_DATENSCHUTZ_NAME = "datenschutz"
|
||||||
|
|
||||||
|
P_SUCHE_NAME = "suche"
|
||||||
|
|
||||||
P_INDEX_NAME = "index"
|
P_INDEX_NAME = "index"
|
||||||
T_INDEX_BILDER = "bilder"
|
T_INDEX_BILDER = "bilder"
|
||||||
T_INDEX_TEXTE = "texte"
|
T_INDEX_TEXTE = "texte"
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
ip := &IndexPage{
|
ip := &IndexPage{
|
||||||
DefaultPage: pagemodels.DefaultPage{
|
DefaultPage: pagemodels.DefaultPage[*pagemodels.IndexTexte]{
|
||||||
Name: pagemodels.P_INDEX_NAME,
|
Name: pagemodels.P_INDEX_NAME,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type IndexPage struct {
|
type IndexPage struct {
|
||||||
pagemodels.DefaultPage
|
pagemodels.DefaultPage[*pagemodels.IndexTexte]
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
|
|||||||
@@ -22,15 +22,19 @@ const (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rp := &ReihenPage{
|
rp := &ReihenPage{
|
||||||
DefaultPage: pagemodels.DefaultPage{
|
DefaultPage: pagemodels.DefaultPage[*pagemodels.DefaultPageRecord]{
|
||||||
Name: pagemodels.P_REIHEN_NAME,
|
Name: pagemodels.P_REIHEN_NAME,
|
||||||
|
URL: URL_REIHEN,
|
||||||
|
Template: URL_REIHEN,
|
||||||
|
Layout: templating.DEFAULT_LAYOUT_NAME,
|
||||||
|
Record: &pagemodels.DefaultPageRecord{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
app.Register(rp)
|
app.Register(rp)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReihenPage struct {
|
type ReihenPage struct {
|
||||||
pagemodels.DefaultPage
|
pagemodels.DefaultPage[*pagemodels.DefaultPageRecord]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReihenPage) Setup(router *router.Router[*core.RequestEvent], app core.App, engine *templating.Engine) error {
|
func (p *ReihenPage) Setup(router *router.Router[*core.RequestEvent], app core.App, engine *templating.Engine) error {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ func RegisterStaticPage(url, name string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: mocve textpage to defaultpage with T = TextPageRecord
|
||||||
func RegisterTextPage(url, name string) {
|
func RegisterTextPage(url, name string) {
|
||||||
app.Register(&pagemodels.TextPage{
|
app.Register(&pagemodels.TextPage{
|
||||||
Name: name,
|
Name: name,
|
||||||
@@ -33,10 +34,11 @@ func RegisterTextPage(url, name string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RegisterDefaultPage(url string, name string) {
|
func RegisterDefaultPage(url string, name string) {
|
||||||
app.Register(&pagemodels.DefaultPage{
|
app.Register(&pagemodels.DefaultPage[*pagemodels.DefaultPageRecord]{
|
||||||
Name: name,
|
Name: name,
|
||||||
Layout: templating.DEFAULT_LAYOUT_NAME,
|
Layout: templating.DEFAULT_LAYOUT_NAME,
|
||||||
Template: url,
|
Template: url,
|
||||||
URL: url,
|
URL: url,
|
||||||
|
Record: &pagemodels.DefaultPageRecord{},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
162
pages/suche.go
Normal file
162
pages/suche.go
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
package pages
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Theodor-Springmann-Stiftung/musenalm/app"
|
||||||
|
"github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels"
|
||||||
|
"github.com/Theodor-Springmann-Stiftung/musenalm/pagemodels"
|
||||||
|
"github.com/Theodor-Springmann-Stiftung/musenalm/templating"
|
||||||
|
"github.com/pocketbase/pocketbase/core"
|
||||||
|
"github.com/pocketbase/pocketbase/tools/router"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SuchePage struct {
|
||||||
|
pagemodels.DefaultPage[*pagemodels.DefaultPageRecord]
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
URL_SUCHE = "/suche/{type}"
|
||||||
|
URL_SUCHE_ALT = "/suche/{$}"
|
||||||
|
DEFAULT_SUCHE = "/suche/reihen"
|
||||||
|
PARAM_QUERY = "q"
|
||||||
|
PARAM_EXTENDED = "extended"
|
||||||
|
TEMPLATE_SUCHE = "/suche/"
|
||||||
|
)
|
||||||
|
|
||||||
|
var availableTypes = []string{"reihen", "baende", "beitraege", "personen"}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rp := &SuchePage{
|
||||||
|
DefaultPage: pagemodels.DefaultPage[*pagemodels.DefaultPageRecord]{
|
||||||
|
Record: &pagemodels.DefaultPageRecord{},
|
||||||
|
Name: pagemodels.P_SUCHE_NAME,
|
||||||
|
Template: TEMPLATE_SUCHE,
|
||||||
|
Layout: templating.DEFAULT_LAYOUT_NAME,
|
||||||
|
URL: URL_SUCHE,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app.Register(rp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *SuchePage) Setup(router *router.Router[*core.RequestEvent], app core.App, engine *templating.Engine) error {
|
||||||
|
router.GET(URL_SUCHE_ALT, func(e *core.RequestEvent) error {
|
||||||
|
return e.Redirect(http.StatusPermanentRedirect, DEFAULT_SUCHE)
|
||||||
|
})
|
||||||
|
|
||||||
|
router.GET(URL_SUCHE, func(e *core.RequestEvent) error {
|
||||||
|
if !slices.Contains(availableTypes, e.Request.PathValue("type")) {
|
||||||
|
return engine.Response404(e, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
q := e.Request.URL.Query().Get(PARAM_QUERY)
|
||||||
|
q = strings.TrimSpace(q)
|
||||||
|
if q != "" {
|
||||||
|
return p.SimpleSearchRequest(app, engine, e)
|
||||||
|
}
|
||||||
|
// if e.Request.URL.Query().Get(PARAM_QUERY) != "" {
|
||||||
|
// return p.SearchRequest(app, engine, e)
|
||||||
|
// }
|
||||||
|
return p.DefaultRequest(app, engine, e)
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *SuchePage) SimpleSearchRequest(app core.App, engine *templating.Engine, e *core.RequestEvent) error {
|
||||||
|
t := e.Request.PathValue("type")
|
||||||
|
if t == "reihen" {
|
||||||
|
return p.SimpleSearchReihenRequest(app, engine, e)
|
||||||
|
}
|
||||||
|
// if t == "baende" {
|
||||||
|
// return p.SimpleSearchBaendeRequest(app, engine, e)
|
||||||
|
// }
|
||||||
|
// if t == "beitraege" {
|
||||||
|
// return p.SimpleSearchBeitraegeRequest(app, engine, e)
|
||||||
|
// }
|
||||||
|
// if t == "personen" {
|
||||||
|
// return p.SimpleSearchPersonenRequest(app, engine, e)
|
||||||
|
// }
|
||||||
|
return engine.Response404(e, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *SuchePage) SimpleSearchReihenRequest(app core.App, engine *templating.Engine, e *core.RequestEvent) error {
|
||||||
|
q := e.Request.URL.Query().Get(PARAM_QUERY)
|
||||||
|
data := p.CommonData(app, engine, e)
|
||||||
|
data["q"] = q
|
||||||
|
|
||||||
|
hasTitle := e.Request.URL.Query().Get("title") == "on"
|
||||||
|
hasAnnotations := e.Request.URL.Query().Get("annotations") == "on"
|
||||||
|
hasReferences := e.Request.URL.Query().Get("references") == "on"
|
||||||
|
|
||||||
|
if !hasTitle && !hasAnnotations && !hasReferences {
|
||||||
|
engine.Response404(e, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := []string{}
|
||||||
|
options := map[string]bool{}
|
||||||
|
if hasTitle {
|
||||||
|
fields = append(fields, dbmodels.SERIES_TITLE_FIELD)
|
||||||
|
options["title"] = true
|
||||||
|
}
|
||||||
|
if hasAnnotations {
|
||||||
|
fields = append(fields, dbmodels.ANNOTATION_FIELD)
|
||||||
|
options["annotations"] = true
|
||||||
|
}
|
||||||
|
if hasReferences {
|
||||||
|
fields = append(fields, dbmodels.REFERENCES_FIELD)
|
||||||
|
options["references"] = true
|
||||||
|
}
|
||||||
|
data["options"] = options
|
||||||
|
|
||||||
|
query := dbmodels.NormalizeQuery(q)
|
||||||
|
if len(q) == 0 {
|
||||||
|
return engine.Response404(e, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
ids, err := dbmodels.FTS5Search(app, dbmodels.SERIES_TABLE, dbmodels.FTS5QueryRequest{
|
||||||
|
Fields: fields,
|
||||||
|
Query: query,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return engine.Response500(e, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
idsany := []any{}
|
||||||
|
for _, id := range ids {
|
||||||
|
idsany = append(idsany, id.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
series, err := dbmodels.SeriessesForIds(app, idsany)
|
||||||
|
rmap, bmap, err := dbmodels.EntriesForSeriesses(app, series)
|
||||||
|
if err != nil {
|
||||||
|
return engine.Response500(e, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
dbmodels.SortSeriessesByTitle(series)
|
||||||
|
data["series"] = series
|
||||||
|
data["relations"] = rmap
|
||||||
|
data["entries"] = bmap
|
||||||
|
|
||||||
|
data["count"] = len(series)
|
||||||
|
// TODO: get relavant agents, years and places
|
||||||
|
|
||||||
|
return engine.Response200(e, p.Template, data, p.Layout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *SuchePage) DefaultRequest(app core.App, engine *templating.Engine, e *core.RequestEvent) error {
|
||||||
|
data := p.CommonData(app, engine, e)
|
||||||
|
return engine.Response200(e, p.Template, data, p.Layout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *SuchePage) CommonData(app core.App, engine *templating.Engine, e *core.RequestEvent) map[string]interface{} {
|
||||||
|
data := map[string]interface{}{}
|
||||||
|
data["type"] = e.Request.PathValue("type")
|
||||||
|
data[PARAM_EXTENDED] = false
|
||||||
|
if e.Request.URL.Query().Get(PARAM_EXTENDED) == "true" {
|
||||||
|
data[PARAM_EXTENDED] = true
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
@@ -28,19 +28,36 @@ Modelle umwandeln (zzt RecordProxy)
|
|||||||
- Ersellen & Abfragen FTS5-Tabellen
|
- Ersellen & Abfragen FTS5-Tabellen
|
||||||
- Erstellen Textseiten
|
- Erstellen Textseiten
|
||||||
|
|
||||||
|
- Technologie-Stack auf Server-Rendering / Go Templates umgestellt
|
||||||
|
- Die Seiten sollten jetzt insgesamt schneller laden
|
||||||
|
|
||||||
- Man kann auf der Startseit nach Almanach-Nummern suchen
|
- Man kann auf der Startseite nach Almanach-Nummern suchen
|
||||||
- Überall werden die Almanachnummer und Inhaltsnummer angezeigt
|
- Überall werden die Almanachnummer und Inhaltsnummer angezeigt
|
||||||
- Die URL referenziert die Almanachnummern, nicht mher die DB-IDs
|
- Die URL referenziert die Almanachnummern, nicht mher die DB-IDs
|
||||||
|
|
||||||
- In der Almanach-Ansicht werden die Abkürzungen erklärt
|
- In der Almanach-Ansicht werden die Abkürzungen erklärt
|
||||||
- In der Almanach-Ansicht können die Beiträge nach Sammlungen gruppiert werden
|
- In der Almanach- und Suchansicht werden Sammlungen abgehoben
|
||||||
- In der Almanach-Ansicht kann nach Inhalten frei gefiltert werden, oder nach Typ
|
- In der Almanach- und Suchansicht werden auch mehrere Bilder zu einem Eintrag angezeigt
|
||||||
|
- In der Almanach- und Suchansicht kann nach Inhalten frei gefiltert werden, oder nach Typ
|
||||||
|
|
||||||
- Es gibt neue URLs sowohl für die einzelne Reihe, als auch für den einzelnen Beitrag
|
- Es gibt neue URLs, die fest verlinkt werden können für einzelne:
|
||||||
|
- Personen
|
||||||
|
- Reihen
|
||||||
|
- Bände
|
||||||
|
- Beiträge
|
||||||
|
|
||||||
- Die Suche ist klar nach Typ unterteilt
|
- Die Suche ist klar nach Typ unterteilt und insgesamt zuverlässiger
|
||||||
- Zusätzlich zur jetzigen Suchfunktion gibt es für jeden Typ noch eine Detailsuche
|
- Zusätzlich zur jetzigen Suchfunktion gibt es für jeden Typ noch eine Detailsuche
|
||||||
- Suchergebnisse können nach Typ, Person, Jahr gefiltert werden
|
- Suchergebnisse können nach Typ, Person, Jahr gefiltert werden
|
||||||
- Suchergebnisse könnnen nach Jahr und Band, nach Band und Jahr (nach Personen) sortiert werden
|
- Suchergebnisse könnnen nach Jahr und Band, nach Band und Jahr (nach Personen) sortiert werden
|
||||||
- Jede Suche hat eine eindeutige URL
|
- Jede Suche hat eine eindeutige URL
|
||||||
|
|
||||||
|
|
||||||
|
TODO danach:
|
||||||
|
- Google-Suchoptimierung
|
||||||
|
- Error Pages prüfen & error-Verhalten von HTMX
|
||||||
|
- Stimmigere Page-Abstraktion
|
||||||
|
- Weißraum in den Antworten
|
||||||
|
- Antworten komprimieren
|
||||||
|
- Cache?
|
||||||
|
- Footer
|
||||||
|
|||||||
@@ -234,7 +234,14 @@ class C extends HTMLElement {
|
|||||||
super(), this._tooltipBox = null;
|
super(), this._tooltipBox = null;
|
||||||
}
|
}
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
this.style.position = "relative";
|
this.classList.add(
|
||||||
|
"w-full",
|
||||||
|
"h-full",
|
||||||
|
"relative",
|
||||||
|
"block",
|
||||||
|
"leading-none",
|
||||||
|
"[&>*]:leading-normal"
|
||||||
|
);
|
||||||
const i = this.querySelector(".data-tip"), t = i ? i.innerHTML : "Tooltip";
|
const i = this.querySelector(".data-tip"), t = i ? i.innerHTML : "Tooltip";
|
||||||
i && i.remove(), this._tooltipBox = document.createElement("div"), this._tooltipBox.innerHTML = t, this._tooltipBox.className = [
|
i && i.remove(), this._tooltipBox = document.createElement("div"), this._tooltipBox.innerHTML = t, this._tooltipBox.className = [
|
||||||
"opacity-0",
|
"opacity-0",
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,6 @@
|
|||||||
{{ $model := . }}
|
{{ $model := . }}
|
||||||
<div
|
<div
|
||||||
|
id="mainmenu"
|
||||||
class="pb-1.5 border-b border-zinc-300"
|
class="pb-1.5 border-b border-zinc-300"
|
||||||
x-data="{ openeditionmenu: window.location.pathname.startsWith('/edition/')}">
|
x-data="{ openeditionmenu: window.location.pathname.startsWith('/edition/')}">
|
||||||
<div class="flex flex-row justify-between">
|
<div class="flex flex-row justify-between">
|
||||||
@@ -13,7 +14,7 @@
|
|||||||
|
|
||||||
<nav
|
<nav
|
||||||
class="self-end font-serif font-bold flex flex-row gap-x-4 [&>a]:no-underline
|
class="self-end font-serif font-bold flex flex-row gap-x-4 [&>a]:no-underline
|
||||||
[&>*]:px-1.5 [&>*]:pt-1 [&>*]:-mb-1.5">
|
[&>*]:px-2 [&>*]:pt-1 [&>*]:-mb-1.5">
|
||||||
<a
|
<a
|
||||||
href="/reihen/"
|
href="/reihen/"
|
||||||
{{ if and $model.page (HasPrefix $model.page.Path "/reihe") -}}
|
{{ if and $model.page (HasPrefix $model.page.Path "/reihe") -}}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
hx-boost="false">
|
hx-boost="false">
|
||||||
<i class="ri-links-line"></i
|
<i class="ri-links-line"></i
|
||||||
></a>
|
></a>
|
||||||
<div class="data-tip">Permalink zu dieser Suchanfrage</div>
|
<div class="data-tip">Link zu dieser Suchanfrage</div>
|
||||||
</tool-tip>
|
</tool-tip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
{{ $entries := index . 1 }}
|
{{ $entries := index . 1 }}
|
||||||
{{ $relations := index . 2 }}
|
{{ $relations := index . 2 }}
|
||||||
{{ $showidseries := index . 3 }}
|
{{ $showidseries := index . 3 }}
|
||||||
{{ $markar := index . 4 }}
|
{{ $marka := index . 4 }}
|
||||||
|
{{ $markr := index . 5 }}
|
||||||
|
|
||||||
{{ $bds := index $relations $r.Id }}
|
{{ $bds := index $relations $r.Id }}
|
||||||
|
|
||||||
@@ -10,7 +11,7 @@
|
|||||||
<div class="flex flex-col lg:flex-row mb-1.5">
|
<div class="flex flex-col lg:flex-row mb-1.5">
|
||||||
<div class="grow-0 shrink-0 w-[12rem] flex flex-col">
|
<div class="grow-0 shrink-0 w-[12rem] flex flex-col">
|
||||||
{{ if $r.References }}
|
{{ if $r.References }}
|
||||||
<div class="text-sm font-sans px-2 py-1 bg-stone-100 {{ if $markar }}reihen-text{{ end }}">
|
<div class="text-sm font-sans px-2 py-1 bg-stone-100 {{ if $markr }}reihen-text{{ end }}">
|
||||||
{{ $r.References }}
|
{{ $r.References }}
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
@@ -37,7 +38,7 @@
|
|||||||
<span class="font-bold reihen-text">{{ $r.Title }}</span>
|
<span class="font-bold reihen-text">{{ $r.Title }}</span>
|
||||||
{{ if $r.Annotation }}
|
{{ if $r.Annotation }}
|
||||||
<span> · </span>
|
<span> · </span>
|
||||||
<span class="{{ if $markar }}reihen-text{{ end }}">{{ Safe $r.Annotation }}</span>
|
<span class="{{ if $marka }}reihen-text{{ end }}">{{ Safe $r.Annotation }}</span>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
<div></div>
|
<div></div>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<h1>Die Seite konnte nicht gefunden werden!</h1>
|
<div class="container-normal">
|
||||||
{{ if .Error }}
|
<h1>Die Seite konnte nicht gefunden werden!</h1>
|
||||||
<p>{{ .Error }}</p>
|
{{ if .Error }}
|
||||||
{{ end }}
|
<p>{{ .Error }}</p>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -13,16 +13,16 @@
|
|||||||
<img src="{{ .record.ImagePath }}" />
|
<img src="{{ .record.ImagePath }}" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute top-0 right-0 p-4">
|
<div class="absolute top-0 right-0 m-4 mr-8">
|
||||||
<tool-tip position="left">
|
<tool-tip position="left">
|
||||||
|
<div class="data-tip">Hinweis schließen</div>
|
||||||
<button
|
<button
|
||||||
@click="open = false"
|
@click="open = false"
|
||||||
class="text-xl p-2 text-stone-500 opacity-85 hover:opacity-100 transition-opacity
|
class="text-3xl text-stone-500 opacity-85 hover:opacity-100 transition-opacity
|
||||||
duration-200 hover:text-stone-900
|
duration-200 hover:text-stone-900 leading-none
|
||||||
hover:cursor-pointer">
|
hover:cursor-pointer">
|
||||||
<i class="ri-close-circle-fill"></i>
|
<i class="ri-close-circle-fill"></i>
|
||||||
</button>
|
</button>
|
||||||
<div class="data-tip">Hinweis schließen</div>
|
|
||||||
</tool-tip>
|
</tool-tip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -138,7 +138,7 @@
|
|||||||
{{ if and .search .idseries }}
|
{{ if and .search .idseries }}
|
||||||
<div class="mb-1 max-w-[60rem] hyphens-auto">
|
<div class="mb-1 max-w-[60rem] hyphens-auto">
|
||||||
{{ range $id, $r := .idseries }}
|
{{ range $id, $r := .idseries }}
|
||||||
{{ template "_reihe" (Arr $r $model.entries $model.relations true false) }}
|
{{ template "_reihe" (Arr $r $model.entries $model.relations true false false) }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
@@ -146,7 +146,9 @@
|
|||||||
{{ if .series }}
|
{{ if .series }}
|
||||||
<div class="mb-1 max-w-[60rem] hyphens-auto">
|
<div class="mb-1 max-w-[60rem] hyphens-auto">
|
||||||
{{ range $id, $r := .series }}
|
{{ range $id, $r := .series }}
|
||||||
{{ template "_reihe" (Arr $r $model.entries $model.relations false false) }}
|
{{ template "_reihe" (Arr $r $model.entries $model.relations false false
|
||||||
|
false)
|
||||||
|
}}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
@@ -171,11 +173,11 @@
|
|||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<div class="border-t mb-1.5 text-sm font-sans text-right pt-0.5">
|
<div class="border-t mb-1.5 text-sm font-sans text-right pt-0.5">
|
||||||
Treffer in allen Feldern (Anmerkungen, Verweisen etc.) ↓
|
Treffer in allen Feldern (inkl. Anmerkungen & Verweise) ↓
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-1 max-w-[60rem] hyphens-auto">
|
<div class="mb-1 max-w-[60rem] hyphens-auto">
|
||||||
{{ range $id, $r := .altseries }}
|
{{ range $id, $r := .altseries }}
|
||||||
{{ template "_reihe" (Arr $r $model.entries $model.relations false true) }}
|
{{ template "_reihe" (Arr $r $model.entries $model.relations false true true) }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|||||||
88
views/routes/suche/body.gohtml
Normal file
88
views/routes/suche/body.gohtml
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
{{ $model := . }}
|
||||||
|
|
||||||
|
|
||||||
|
<div id="searchcontrol" class="container-normal">
|
||||||
|
<div id="searchheading" class="flex flex-row justify-between min-h-14 items-end relative">
|
||||||
|
<nav id="searchnav" class="flex flex-row items-end">
|
||||||
|
<div class="align-bottom h-min self-end pb-0.5 hidden">Durchsuchen:</div>
|
||||||
|
<a
|
||||||
|
href="/suche/reihen"
|
||||||
|
class="block no-underline"
|
||||||
|
{{ if eq $model.type "reihen" }}aria-current="page"{{- end -}}
|
||||||
|
>Reihen</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="/suche/baende"
|
||||||
|
class="block no-underline"
|
||||||
|
{{ if eq $model.type "baende" }}aria-current="page"{{- end -}}
|
||||||
|
>Bände</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="/suche/beitraege"
|
||||||
|
class="block no-underline"
|
||||||
|
{{ if eq $model.type "beitraege" }}aria-current="page"{{- end -}}
|
||||||
|
>Beiträge</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="/suche/personen"
|
||||||
|
class="block no-underline"
|
||||||
|
{{ if eq $model.type "personen" }}aria-current="page"{{- end -}}
|
||||||
|
>Personen</a
|
||||||
|
>
|
||||||
|
</nav>
|
||||||
|
<h1
|
||||||
|
class="text-3xl font-bold px-3 relative translate-y-[45%] w-min whitespace-nowrap
|
||||||
|
bg-stone-50 mr-24 z-20">
|
||||||
|
Suche · <span class="">
|
||||||
|
{{- if eq $model.type "reihen" -}}
|
||||||
|
Reihen
|
||||||
|
{{- else if eq $model.type "personen" -}}
|
||||||
|
Personen & Körperschaften
|
||||||
|
{{- else if eq $model.type "baende" -}}
|
||||||
|
Bände
|
||||||
|
{{- else if eq $model.type "beitraege" -}}
|
||||||
|
Beiträge
|
||||||
|
{{- end -}}
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div id="" class="border-l border-zinc-300 px-8 py-10 relative">
|
||||||
|
{{ template "searchform" $model }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{- if $model.q -}}
|
||||||
|
<div id="searchresults">
|
||||||
|
{{- if eq $model.type "reihen" -}}
|
||||||
|
<!-- INFO: Resultate Reihen -->
|
||||||
|
<div id="" class="container-normal mt-4">
|
||||||
|
{{- if $model.series -}}
|
||||||
|
{{- $includeReferences := index $model.options "references" -}}
|
||||||
|
{{- $includeAnnotations := index $model.options "annotations" -}}
|
||||||
|
<div class="mb-1 max-w-[60rem] hyphens-auto">
|
||||||
|
{{- range $id, $r := $model.series -}}
|
||||||
|
{{- template "_reihe" (Arr $r $model.entries $model.relations false
|
||||||
|
$includeAnnotations $includeReferences)
|
||||||
|
-}}
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
{{ else }}
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
<script type="module">
|
||||||
|
let elements = document.querySelectorAll('.reihen-text');
|
||||||
|
let mark_instance = new Mark(elements);
|
||||||
|
// INFO: we wait a little bit before marking, to settle everything
|
||||||
|
setTimeout(() => {
|
||||||
|
let word = '{{ $model.q }}';
|
||||||
|
word = word.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"");
|
||||||
|
mark_instance.mark(word, {
|
||||||
|
"seperateWordSearch": true,
|
||||||
|
"ignorePunctuation": [""],
|
||||||
|
});
|
||||||
|
}, 200);
|
||||||
|
</script>
|
||||||
|
<!-- INFO: Resultate Reihen Ende -->
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
17
views/routes/suche/components/infotextsimple.gohtml
Normal file
17
views/routes/suche/components/infotextsimple.gohtml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{{ $extendable := . }}
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col-span-6 flex flex-row text-stone-700 gap-x-2">
|
||||||
|
<div>
|
||||||
|
<i class="ri-information-2-fill"></i>
|
||||||
|
</div>
|
||||||
|
<div class="font-sans hyphens-auto text-sm pt-1">
|
||||||
|
Die Suche durchsucht ganze Datensätze nach dem Vorkommen aller eingegebenen Suchbegriffe. Felder
|
||||||
|
können oben einzeln aus der Suche ausgeschlossen werden. Auch partielle Treffer in Worten werden
|
||||||
|
angezeigt. Wörter mit weniger als drei Zeichen, Sonderzeichen – auch Satzzeichen –
|
||||||
|
sowie die Groß- und Kleinschreibung werden dabei ignoriert
|
||||||
|
{{- if $extendable }}
|
||||||
|
(für mehr Optionen s. die → <a href="?extended=true">erweiterte Suche</a>)
|
||||||
|
{{- end -}}.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
45
views/routes/suche/components/searchboxsimple.gohtml
Normal file
45
views/routes/suche/components/searchboxsimple.gohtml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{{ $model := index . 0 }}
|
||||||
|
{{ $extendable := index . 1 }}
|
||||||
|
|
||||||
|
|
||||||
|
<label for="q" class="hidden">Suchbegriffe</label>
|
||||||
|
<input
|
||||||
|
{{ if $model.q }}value="{{ $model.q }}"{{- end -}}
|
||||||
|
type="search"
|
||||||
|
name="q"
|
||||||
|
autofocus="true"
|
||||||
|
minlength="3"
|
||||||
|
required
|
||||||
|
placeholder="Suchbegriff (min. 3 Zeichen)"
|
||||||
|
class="w-full col-span-8
|
||||||
|
placeholder:italic" />
|
||||||
|
<button id="submitbutton" type="submit" class="col-span-2">Suchen</button>
|
||||||
|
|
||||||
|
{{ if $extendable }}
|
||||||
|
<a
|
||||||
|
href="/suche/{{ $model.type }}?extended=true"
|
||||||
|
class="whitespace-nowrap self-end block col-span-2">
|
||||||
|
<i class="ri-arrow-right-long-line"></i> Erweiterte Suche
|
||||||
|
</a>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
const form = document.getElementById("searchform");
|
||||||
|
const submitBtn = document.getElementById("submitbutton");
|
||||||
|
|
||||||
|
function checkValidity() {
|
||||||
|
if (form.checkValidity()) {
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
} else {
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkValidity();
|
||||||
|
if (form && submitBtn) {
|
||||||
|
form.addEventListener("input", (event) => {
|
||||||
|
checkValidity();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
193
views/routes/suche/components/searchform.gohtml
Normal file
193
views/routes/suche/components/searchform.gohtml
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
{{ $model := . }}
|
||||||
|
{{- $includeReferences := true -}}
|
||||||
|
{{- $includeAnnotations := true -}}
|
||||||
|
{{- $includeTitle := true -}}
|
||||||
|
|
||||||
|
{{- if eq $model.type "reihen" -}}
|
||||||
|
{{- if $model.options -}}
|
||||||
|
{{- $includeReferences = not (and $model.q (not (index $model.options "references"))) -}}
|
||||||
|
{{- $includeAnnotations = not (and $model.q (not (index $model.options "annotations"))) -}}
|
||||||
|
{{- $includeTitle = not (and $model.q (not (index $model.options "title"))) -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
|
||||||
|
<form
|
||||||
|
id="searchform"
|
||||||
|
class="w-full font-serif"
|
||||||
|
method="get"
|
||||||
|
action="/suche/{{- $model.type -}}"
|
||||||
|
autocomplete="off">
|
||||||
|
{{- if eq $model.type "reihen" -}}
|
||||||
|
<!-- INFO: Reihen -->
|
||||||
|
{{- if not $model.extended -}}
|
||||||
|
<div class="grid grid-cols-12 gap-y-3 w-full gap-x-4">
|
||||||
|
{{ template "searchboxsimple" Arr . false }}
|
||||||
|
<fieldset class="selectgroup">
|
||||||
|
<div class="selectgroup-option">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
name="title"
|
||||||
|
id="title"
|
||||||
|
{{ if $includeTitle -}}
|
||||||
|
checked
|
||||||
|
{{- end -}} />
|
||||||
|
<label for="title">Titel</label>
|
||||||
|
</div>
|
||||||
|
<div class="selectgroup-option">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
name="annotations"
|
||||||
|
id="annotations"
|
||||||
|
{{ if $includeAnnotations -}}
|
||||||
|
checked
|
||||||
|
{{- end -}} />
|
||||||
|
<label for="annotations">Anmerkungen</label>
|
||||||
|
</div>
|
||||||
|
<div class="selectgroup-option">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
name="references"
|
||||||
|
id="references"
|
||||||
|
{{ if $includeReferences -}}
|
||||||
|
checked
|
||||||
|
{{- end -}} />
|
||||||
|
<label for="references">Nachweise</label>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
{{ template "infotextsimple" false }}
|
||||||
|
</div>
|
||||||
|
{{- else -}}
|
||||||
|
Extended search Reihen
|
||||||
|
{{- end -}}
|
||||||
|
<!-- INFO: Ende Reihen -->
|
||||||
|
{{- else if eq $model.type "personen" -}}
|
||||||
|
<!-- INFO: Personen -->
|
||||||
|
{{- if not $model.extended -}}
|
||||||
|
<div class="grid grid-cols-12 gap-y-3 w-full gap-x-4">
|
||||||
|
{{ template "searchboxsimple" Arr . false }}
|
||||||
|
<fieldset class="selectgroup">
|
||||||
|
<div class="selectgroup-option">
|
||||||
|
<input type="checkbox" name="names" id="names" checked />
|
||||||
|
<label for="names">Namen & Pseudonyme</label>
|
||||||
|
</div>
|
||||||
|
<div class="selectgroup-option">
|
||||||
|
<input type="checkbox" name="biographical" id="biographical" checked />
|
||||||
|
<label for="biographical">Lebensdaten</label>
|
||||||
|
</div>
|
||||||
|
<div class="selectgroup-option">
|
||||||
|
<input type="checkbox" name="profession" id="profession" checked />
|
||||||
|
<label for="profession">Beruf(e)</label>
|
||||||
|
</div>
|
||||||
|
<div class="selectgroup-option">
|
||||||
|
<input type="checkbox" name="annotations" id="annotations" checked />
|
||||||
|
<label for="annotations">Anmerkungen</label>
|
||||||
|
</div>
|
||||||
|
<div class="selectgroup-option">
|
||||||
|
<input type="checkbox" name="references" id="references" checked />
|
||||||
|
<label for="references">Nachweise</label>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
{{ template "infotextsimple" false }}
|
||||||
|
</div>
|
||||||
|
{{- else -}}
|
||||||
|
Extended search Personen
|
||||||
|
{{- end -}}
|
||||||
|
<!-- INFO: Ende Personen -->
|
||||||
|
{{- else if eq $model.type "baende" -}}
|
||||||
|
<!-- INFO: Bände -->
|
||||||
|
{{- if not $model.extended -}}
|
||||||
|
<div class="grid grid-cols-12 gap-y-3 w-full gap-x-4">
|
||||||
|
{{ template "searchboxsimple" Arr . true }}
|
||||||
|
<fieldset class="selectgroup">
|
||||||
|
<div class="selectgroup-option">
|
||||||
|
<input type="checkbox" name="number" id="number" checked />
|
||||||
|
<label for="number">Almanach-Nr.</label>
|
||||||
|
</div>
|
||||||
|
<div class="selectgroup-option">
|
||||||
|
<input type="checkbox" name="title" id="title" checked />
|
||||||
|
<label for="title">Titel</label>
|
||||||
|
</div>
|
||||||
|
<div class="selectgroup-option">
|
||||||
|
<input type="checkbox" name="series" id="series" checked />
|
||||||
|
<label for="series">Reihentitel</label>
|
||||||
|
</div>
|
||||||
|
<div class="selectgroup-option">
|
||||||
|
<input type="checkbox" name="person" id="person" checked />
|
||||||
|
<label for="person">Personen & Verlage</label>
|
||||||
|
</div>
|
||||||
|
<div class="selectgroup-option">
|
||||||
|
<input type="checkbox" name="pubdata" id="pubdata" checked />
|
||||||
|
<label for="pubdata">Orte</label>
|
||||||
|
</div>
|
||||||
|
<div class="selectgroup-option">
|
||||||
|
<input type="checkbox" name="year" id="year" checked />
|
||||||
|
<label for="year">Jahr</label>
|
||||||
|
</div>
|
||||||
|
<div class="selectgroup-option">
|
||||||
|
<input type="checkbox" name="references" id="references" checked />
|
||||||
|
<label for="references">Nachweise</label>
|
||||||
|
</div>
|
||||||
|
<div class="selectgroup-option">
|
||||||
|
<input type="checkbox" name="annotations" id="annotations" checked />
|
||||||
|
<label for="annotations">Anmerkungen</label>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
{{ template "infotextsimple" true }}
|
||||||
|
</div>
|
||||||
|
{{- else -}}
|
||||||
|
Extended search Bände
|
||||||
|
{{- end -}}
|
||||||
|
<!-- INFO: Ende Bände -->
|
||||||
|
{{- else if eq $model.type "beitraege" -}}
|
||||||
|
<!-- INFO: Beiträge -->
|
||||||
|
{{- if not $model.extended -}}
|
||||||
|
<div class="grid grid-cols-12 gap-y-3 w-full gap-x-4">
|
||||||
|
{{ template "searchboxsimple" Arr . true }}
|
||||||
|
<fieldset class="selectgroup">
|
||||||
|
<div class="selectgroup-option">
|
||||||
|
<input type="checkbox" name="number" id="number" checked />
|
||||||
|
<label for="number">Almanach-Nr.</label>
|
||||||
|
</div>
|
||||||
|
<div class="selectgroup-option">
|
||||||
|
<input type="checkbox" name="title" id="title" checked />
|
||||||
|
<label for="title">Titelinformationen</label>
|
||||||
|
</div>
|
||||||
|
<div class="selectgroup-option">
|
||||||
|
<input type="checkbox" name="entry" id="entry" checked />
|
||||||
|
<label for="entry">Bandtitel</label>
|
||||||
|
</div>
|
||||||
|
<div class="selectgroup-option">
|
||||||
|
<input type="checkbox" name="person" id="person" checked />
|
||||||
|
<label for="person">Personen & Pseudonyme</label>
|
||||||
|
</div>
|
||||||
|
<div class="selectgroup-option">
|
||||||
|
<input type="checkbox" name="annotations" id="annotations" checked />
|
||||||
|
<label for="annotations">Anmerkungen</label>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
{{ template "infotextsimple" true }}
|
||||||
|
</div>
|
||||||
|
{{- else -}}
|
||||||
|
Extended search Beiträge
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
let fieldset = document.querySelector("fieldset.selectgroup");
|
||||||
|
let checkboxes = Array.from(fieldset.querySelectorAll('input[type="checkbox"]'));
|
||||||
|
fieldset.addEventListener("change", (event) => {
|
||||||
|
let target = event.target;
|
||||||
|
if (target.type === "checkbox") {
|
||||||
|
let name = target.name;
|
||||||
|
let checked = target.checked;
|
||||||
|
if (!checked) {
|
||||||
|
let allchecked = checkboxes.filter((checkbox) => checkbox.checked);
|
||||||
|
if (allchecked.length === 0) {
|
||||||
|
target.checked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
1
views/routes/suche/head.gohtml
Normal file
1
views/routes/suche/head.gohtml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<title>{{ .site.title }}: Suche</title>
|
||||||
@@ -430,7 +430,14 @@ class ToolTip extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
this.style.position = "relative";
|
this.classList.add(
|
||||||
|
"w-full",
|
||||||
|
"h-full",
|
||||||
|
"relative",
|
||||||
|
"block",
|
||||||
|
"leading-none",
|
||||||
|
"[&>*]:leading-normal",
|
||||||
|
);
|
||||||
const dataTipElem = this.querySelector(".data-tip");
|
const dataTipElem = this.querySelector(".data-tip");
|
||||||
const tipContent = dataTipElem ? dataTipElem.innerHTML : "Tooltip";
|
const tipContent = dataTipElem ? dataTipElem.innerHTML : "Tooltip";
|
||||||
|
|
||||||
|
|||||||
@@ -73,23 +73,23 @@
|
|||||||
@apply ml-14 list-disc;
|
@apply ml-14 list-disc;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav > a {
|
#mainmenu nav > a {
|
||||||
@apply hover:!border-zinc-200;
|
@apply hover:!border-zinc-200;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav > * {
|
#mainmenu nav > * {
|
||||||
@apply border-b-4 border-transparent;
|
@apply border-b-4 border-transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav > button[aria-current="true"] {
|
#mainmenu nav > button[aria-current="true"] {
|
||||||
@apply !bg-slate-200;
|
@apply !bg-slate-200;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav a[aria-current="page"] {
|
#mainmenu nav a[aria-current="page"] {
|
||||||
@apply text-slate-800;
|
@apply text-slate-800;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav a[aria-current="page"] {
|
#mainmenu nav a[aria-current="page"] {
|
||||||
@apply !border-zinc-300;
|
@apply !border-zinc-300;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,4 +197,58 @@
|
|||||||
.indented p {
|
.indented p {
|
||||||
@apply -indent-3.5 ml-3.5;
|
@apply -indent-3.5 ml-3.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#searchnav > a:nth-of-type(1) {
|
||||||
|
@apply ml-12;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchnav > a {
|
||||||
|
@apply odd:bg-stone-100 even:bg-zinc-100 mx-1.5 border-zinc-300 border-x border-t px-2.5 no-underline transition-all duration-75 py-0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchnav > a[aria-current="page"]:not(.inactive) {
|
||||||
|
@apply font-bold italic !bg-stone-50 relative -bottom-3 border-b z-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchnav > a:hover:not([aria-current="page"]:not(.inactive)) {
|
||||||
|
@apply pb-2 !bg-stone-50 relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchheading:before {
|
||||||
|
content: "";
|
||||||
|
@apply bg-zinc-300 w-[80%] absolute bottom-0 right-[20%] h-[1px] z-10;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchform:before {
|
||||||
|
content: "";
|
||||||
|
@apply bg-zinc-300 w-[30%] absolute bottom-0 right-[70%] h-[1px] z-10;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchform input {
|
||||||
|
@apply w-full mx-auto px-2 py-1 border-zinc-600 border;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchform button {
|
||||||
|
@apply bg-stone-100 text-base px-2.5 py-1 rounded font-sans transition-all duration-75;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchform button:hover:not(:disabled) {
|
||||||
|
@apply cursor-pointer bg-stone-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchform button:disabled {
|
||||||
|
@apply bg-stone-400 cursor-not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchform .selectgroup {
|
||||||
|
@apply col-span-12 w-full flex flex-row gap-x-6;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchform .selectgroup .selectgroup-option {
|
||||||
|
@apply flex flex-row select-none gap-x-1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchform .selectgroup .selectgroup-option label {
|
||||||
|
@apply whitespace-nowrap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user