diff --git a/dbmodels/dbdata.go b/dbmodels/dbdata.go index 65c8539..905135f 100644 --- a/dbmodels/dbdata.go +++ b/dbmodels/dbdata.go @@ -417,6 +417,8 @@ var PUBLIC_VIEW_RULE = types.Pointer("") var PUBLIC_LIST_RULE = types.Pointer("") const ( + FTS_LIMIT = 100000 + PLACES_TABLE = "places" AGENTS_TABLE = "agents" SERIES_TABLE = "series" diff --git a/dbmodels/entry.go b/dbmodels/entry.go index 45e749b..e6782b2 100644 --- a/dbmodels/entry.go +++ b/dbmodels/entry.go @@ -204,6 +204,10 @@ func (e *Entry) MusenalmID() int { return e.GetInt(MUSENALMID_FIELD) } +func (e *Entry) MusenalmIDString() string { + return e.GetString(MUSENALMID_FIELD) +} + func (e *Entry) SetMusenalmID(musenalmID int) { e.Set(MUSENALMID_FIELD, musenalmID) } diff --git a/dbmodels/fts5.go b/dbmodels/fts5.go index 65c6f10..e9bc560 100644 --- a/dbmodels/fts5.go +++ b/dbmodels/fts5.go @@ -2,7 +2,6 @@ package dbmodels import ( "errors" - "fmt" "strconv" "strings" "unicode" @@ -162,7 +161,6 @@ func NormalizeQuery(query string) Query { } for _, r := range query { - fmt.Printf("Rune: %v\n", r) if r == '"' { if isInQuotes { addToken() @@ -185,12 +183,6 @@ func NormalizeQuery(query string) Query { at() } - fmt.Printf("Query: %v\n", query) - fmt.Printf("Include: %v\n", include) - fmt.Printf("Exclude: %v\n", exclude) - fmt.Printf("UnsafeI: %v\n", unsafeI) - fmt.Printf("UnsafeE: %v\n", unsafeE) - return Query{ Include: include, Exclude: exclude, diff --git a/dbmodels/fts5query.go b/dbmodels/fts5query.go index 3fa768e..03655cc 100644 --- a/dbmodels/fts5query.go +++ b/dbmodels/fts5query.go @@ -1,6 +1,7 @@ package dbmodels import ( + "strconv" "strings" "github.com/Theodor-Springmann-Stiftung/musenalm/helpers/datatypes" @@ -146,7 +147,7 @@ func (q *FTS5Query) Query() string { query += " { " + strings.Join(m.Fields, " ") + " } : \"" + q.Escape(m.Value) + "\"" } - query += "'" + query += "' LIMIT " + strconv.Itoa(FTS_LIMIT) return query } diff --git a/dbmodels/queries.go b/dbmodels/queries.go index aa7a48f..f0cb961 100644 --- a/dbmodels/queries.go +++ b/dbmodels/queries.go @@ -14,7 +14,7 @@ import ( // For scanning, with an Iter_ prefix, yields single row results func REntriesAgents_Agent(app core.App, id string) ([]*REntriesAgents, error) { - return TableByFields[[]*REntriesAgents]( + return TableByFields[*REntriesAgents]( app, RelationTableName(ENTRIES_TABLE, AGENTS_TABLE), AGENTS_TABLE, @@ -23,7 +23,7 @@ func REntriesAgents_Agent(app core.App, id string) ([]*REntriesAgents, error) { } func REntriesAgents_Entry(app core.App, id string) ([]*REntriesAgents, error) { - return TableByFields[[]*REntriesAgents]( + return TableByFields[*REntriesAgents]( app, RelationTableName(ENTRIES_TABLE, AGENTS_TABLE), ENTRIES_TABLE, @@ -32,7 +32,7 @@ func REntriesAgents_Entry(app core.App, id string) ([]*REntriesAgents, error) { } func REntriesAgents_Entries(app core.App, ids []any) ([]*REntriesAgents, error) { - return TableByFields[[]*REntriesAgents]( + return TableByFields[*REntriesAgents]( app, RelationTableName(ENTRIES_TABLE, AGENTS_TABLE), ENTRIES_TABLE, @@ -41,7 +41,7 @@ func REntriesAgents_Entries(app core.App, ids []any) ([]*REntriesAgents, error) } func RContentsAgents_Agent(app core.App, id string) ([]*RContentsAgents, error) { - return TableByFields[[]*RContentsAgents]( + return TableByFields[*RContentsAgents]( app, RelationTableName(CONTENTS_TABLE, AGENTS_TABLE), AGENTS_TABLE, @@ -50,7 +50,7 @@ func RContentsAgents_Agent(app core.App, id string) ([]*RContentsAgents, error) } func RContentsAgents_Contents(app core.App, id []any) ([]*RContentsAgents, error) { - return TableByFields[[]*RContentsAgents]( + return TableByFields[*RContentsAgents]( app, RelationTableName(CONTENTS_TABLE, AGENTS_TABLE), CONTENTS_TABLE, @@ -59,7 +59,7 @@ func RContentsAgents_Contents(app core.App, id []any) ([]*RContentsAgents, error } func RContentsAgents_Content(app core.App, id string) ([]*RContentsAgents, error) { - return TableByFields[[]*RContentsAgents]( + return TableByFields[*RContentsAgents]( app, RelationTableName(CONTENTS_TABLE, AGENTS_TABLE), CONTENTS_TABLE, @@ -68,7 +68,7 @@ func RContentsAgents_Content(app core.App, id string) ([]*RContentsAgents, error } func REntriesSeries_Entries(app core.App, ids []any) ([]*REntriesSeries, error) { - return TableByFields[[]*REntriesSeries]( + return TableByFields[*REntriesSeries]( app, RelationTableName(ENTRIES_TABLE, SERIES_TABLE), ENTRIES_TABLE, @@ -77,7 +77,7 @@ func REntriesSeries_Entries(app core.App, ids []any) ([]*REntriesSeries, error) } func REntriesSeries_Entry(app core.App, id string) ([]*REntriesSeries, error) { - return TableByFields[[]*REntriesSeries]( + return TableByFields[*REntriesSeries]( app, RelationTableName(ENTRIES_TABLE, SERIES_TABLE), ENTRIES_TABLE, @@ -86,7 +86,7 @@ func REntriesSeries_Entry(app core.App, id string) ([]*REntriesSeries, error) { } func REntriesSeries_Seriess(app core.App, ids []any) ([]*REntriesSeries, error) { - return TableByFields[[]*REntriesSeries]( + return TableByFields[*REntriesSeries]( app, RelationTableName(ENTRIES_TABLE, SERIES_TABLE), SERIES_TABLE, @@ -100,7 +100,7 @@ func Agents_ID(app core.App, id string) (*Agent, error) { } func Agents_IDs(app core.App, ids []any) ([]*Agent, error) { - return TableByIDs[[]*Agent](app, AGENTS_TABLE, ids) + return TableByIDs[*Agent](app, AGENTS_TABLE, ids) } func Entries_ID(app core.App, id string) (*Entry, error) { @@ -114,11 +114,11 @@ func Entries_MusenalmID(app core.App, id string) (*Entry, error) { } func Entries_IDs(app core.App, ids []any) ([]*Entry, error) { - return TableByIDs[[]*Entry](app, ENTRIES_TABLE, ids) + return TableByIDs[*Entry](app, ENTRIES_TABLE, ids) } func Series_IDs(app core.App, ids []any) ([]*Series, error) { - return TableByIDs[[]*Series](app, SERIES_TABLE, ids) + return TableByIDs[*Series](app, SERIES_TABLE, ids) } func Series_MusenalmID(app core.App, id string) (*Series, error) { @@ -132,15 +132,15 @@ func Series_ID(app core.App, id string) (*Series, error) { } func Places_IDs(app core.App, ids []any) ([]*Place, error) { - return TableByIDs[[]*Place](app, PLACES_TABLE, ids) + return TableByIDs[*Place](app, PLACES_TABLE, ids) } func Contents_IDs(app core.App, ids []any) ([]*Content, error) { - return TableByIDs[[]*Content](app, CONTENTS_TABLE, ids) + return TableByIDs[*Content](app, CONTENTS_TABLE, ids) } func Contents_Entry(app core.App, id string) ([]*Content, error) { - return TableByFields[[]*Content]( + return TableByFields[*Content]( app, CONTENTS_TABLE, ENTRIES_TABLE, diff --git a/dbmodels/queryhelpers.go b/dbmodels/queryhelpers.go index 485ae6f..b0bb5a6 100644 --- a/dbmodels/queryhelpers.go +++ b/dbmodels/queryhelpers.go @@ -2,6 +2,7 @@ package dbmodels import ( "iter" + "reflect" "github.com/pocketbase/dbx" "github.com/pocketbase/pocketbase/core" @@ -61,6 +62,11 @@ import ( // github.com/pocketbase/pocketbase/apis.NewRouter.BodyLimit.func7(0xc0002da000) // // /home + +const ( + QUERY_PARTITION_SIZE = 1200 +) + func Iter_TableByField[T interface{}](app core.App, table, field string, value interface{}) (iter.Seq2[*T, error], error) { rows, err := app.RecordQuery(table). Where(dbx.HashExp{field: value}). @@ -111,13 +117,42 @@ func TableByField[T interface{}](app core.App, table, field string, value string return ret, nil } -func TableByFields[T interface{}](app core.App, table, field string, values any) (T, error) { - var ret T - err := app.RecordQuery(table). - Where(dbx.HashExp{field: values}). - All(&ret) - if err != nil { - return ret, err +func TableByFields[T interface{}](app core.App, table, field string, values any) ([]T, error) { + var ret []T + if reflect.TypeOf(values).Kind() == reflect.Slice { + v := values.([]any) + if len(v) > QUERY_PARTITION_SIZE { + for i := 0; i < len(v); i += QUERY_PARTITION_SIZE { + part := v[i:] + if len(part) > QUERY_PARTITION_SIZE { + part = part[:QUERY_PARTITION_SIZE] + } + + var partret []T + err := app.RecordQuery(table). + Where(dbx.HashExp{field: part}). + All(&partret) + if err != nil { + return ret, err + } + + ret = append(ret, partret...) + } + } else { + err := app.RecordQuery(table). + Where(dbx.HashExp{field: values}). + All(&ret) + if err != nil { + return ret, err + } + } + } else { + err := app.RecordQuery(table). + Where(dbx.HashExp{field: values}). + All(&ret) + if err != nil { + return ret, err + } } return ret, nil @@ -135,13 +170,32 @@ func TableByID[T interface{}](app core.App, table, id string) (T, error) { return ret, nil } -func TableByIDs[T interface{}](app core.App, table string, ids []any) (T, error) { - var ret T - err := app.RecordQuery(table). - Where(dbx.HashExp{ID_FIELD: ids}). - All(&ret) - if err != nil { - return ret, err +func TableByIDs[T interface{}](app core.App, table string, ids []any) ([]T, error) { + var ret []T + if len(ids) > QUERY_PARTITION_SIZE { + for i := 0; i < len(ids); i += QUERY_PARTITION_SIZE { + part := ids[i:] + if len(part) > QUERY_PARTITION_SIZE { + part = part[:QUERY_PARTITION_SIZE] + } + + var partret []T + err := app.RecordQuery(table). + Where(dbx.HashExp{ID_FIELD: part}). + All(&partret) + if err != nil { + return ret, err + } + + ret = append(ret, partret...) + } + } else { + err := app.RecordQuery(table). + Where(dbx.HashExp{ID_FIELD: ids}). + All(&ret) + if err != nil { + return ret, err + } } return ret, nil diff --git a/helpers/functions/string.go b/helpers/functions/string.go index 045101f..fb3a2b5 100644 --- a/helpers/functions/string.go +++ b/helpers/functions/string.go @@ -44,11 +44,11 @@ func First(s string) string { return string(r[0]) } -func LinksAnnotation(s string) string { +func LinksAnnotation(s, link string) string { annotation := linksexp.ReplaceAllStringFunc(s, func(match string) string { submatches := linksexp.FindStringSubmatch(match) if len(submatches) > 1 { - return fmt.Sprintf(`%s`, submatches[1], match) + return fmt.Sprintf(`%s`, submatches[1], match) } return match }) diff --git a/pages/suche.go b/pages/suche.go index 2f22543..4c28387 100644 --- a/pages/suche.go +++ b/pages/suche.go @@ -1,11 +1,9 @@ package pages import ( - "database/sql" "fmt" "net/http" "slices" - "strings" "github.com/Theodor-Springmann-Stiftung/musenalm/app" "github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels" @@ -23,9 +21,9 @@ const ( URL_SUCHE = "/suche/{type}" URL_SUCHE_ALT = "/suche/{$}" DEFAULT_SUCHE = "/suche/baende" - PARAM_QUERY = "q" PARAM_EXTENDED = "extended" TEMPLATE_SUCHE = "/suche/" + PARAM_QUERY = "q" ) var availableTypes = []string{"baende", "beitraege"} @@ -60,6 +58,10 @@ func (p *SuchePage) Setup(router *router.Router[*core.RequestEvent], app core.Ap return p.SearchBaendeRequest(app, engine, e, *allparas) } + if allparas.IsBeitraegeSearch() { + return p.SearchBeitraegeRequest(app, engine, e, *allparas) + } + data := make(map[string]interface{}) data["parameters"] = allparas return engine.Response200(e, p.Template+paras.Collection+"/", data, p.Layout) @@ -72,10 +74,10 @@ func (p *SuchePage) SimpleSearchReihenRequest(app core.App, engine *templating.E return engine.Response404(e, nil, nil) } -func (p *SuchePage) SearchBaendeRequest(app core.App, engine *templating.Engine, e *core.RequestEvent, params SearchParameters) error { +func (p *SuchePage) SearchBeitraegeRequest(app core.App, engine *templating.Engine, e *core.RequestEvent, params SearchParameters) error { data := make(map[string]interface{}) - result, err := SimpleSearchBaende(app, params) + result, err := NewSearchBeitraege(app, params) if err != nil { return engine.Response404(e, err, nil) } @@ -85,38 +87,20 @@ func (p *SuchePage) SearchBaendeRequest(app core.App, engine *templating.Engine, return engine.Response200(e, p.Template+params.Collection+"/", data, p.Layout) } -const ( - BEITRAEGE_PARAM_ALM_NR = "nr" - BEITRAEGE_PARAM_TITLE = "title" - BEITRAEGE_PARAM_INCIPT = "incipit" - BEITRAEGE_PARAM_PERSONS = "persons" - BEITRAEGE_PARAM_ANNOTATIONS = "annotations" - // INFO: this is expanded search only: - BEITRAEGE_PARAM_PSEUDONYMS = "pseudonyms" - // INFO: these are filter types & expanded search: - BEITRAEGE_PARAM_TYPE = "type" - BEITRAEGE_PARAM_SCANS = "scans" +func (p *SuchePage) SearchBaendeRequest(app core.App, engine *templating.Engine, e *core.RequestEvent, params SearchParameters) error { + data := make(map[string]interface{}) - REIHEN_PARAM_TITLE = "title" - REIHEN_PARAM_ANNOTATIONS = "annotations" - REIHEN_PARAM_REFERENCES = "references" + result, err := NewSearchBaende(app, params) + if err != nil { + return engine.Response404(e, err, nil) + } - BAENDE_PARAM_ALM_NR = "alm" - BAENDE_PARAM_TITLE = "title" - BAENDE_PARAM_SERIES = "series" - BAENDE_PARAM_PERSONS = "persons" - BAENDE_PARAM_PLACES = "places" - BAENDE_PARAM_REFS = "references" - BAENDE_PARAM_ANNOTATIONS = "annotations" - BAENDE_PARAM_YEAR = "year" - // INFO: this is expanded search only: - BAENDE_PARAM_PSEUDONYMS = "pseudonyms" - // INFO: this is a filter type & expanded search: - BAENDE_PARAM_STATE = "state" // STATE: "full" "partial" "none" -) + data["parameters"] = params + data["result"] = result + return engine.Response200(e, p.Template+params.Collection+"/", data, p.Layout) +} var ErrInvalidCollectionType = fmt.Errorf("Invalid collection type") -var ErrNoQuery = fmt.Errorf("No query") type Parameters struct { Extended bool @@ -145,437 +129,3 @@ func NewParameters(e *core.RequestEvent) (*Parameters, error) { func (p *Parameters) NormalizeQuery() dbmodels.Query { return dbmodels.NormalizeQuery(p.Query) } - -type SearchParameters struct { - Parameters - Sort string - - Annotations bool - Persons bool - Title bool - Series bool - Places bool - Refs bool - Year bool - - AnnotationsString string - PersonsString string - TitleString string - AlmString string - SeriesString string - PlacesString string - RefsString string - YearString string - - TypeFilter string -} - -func NewSearchParameters(e *core.RequestEvent, p Parameters) (*SearchParameters, error) { - title := e.Request.URL.Query().Get(BAENDE_PARAM_TITLE) == "on" - series := e.Request.URL.Query().Get(BAENDE_PARAM_SERIES) == "on" - persons := e.Request.URL.Query().Get(BAENDE_PARAM_PERSONS) == "on" - places := e.Request.URL.Query().Get(BAENDE_PARAM_PLACES) == "on" - refs := e.Request.URL.Query().Get(BAENDE_PARAM_REFS) == "on" - annotations := e.Request.URL.Query().Get(BAENDE_PARAM_ANNOTATIONS) == "on" - year := e.Request.URL.Query().Get(BAENDE_PARAM_YEAR) == "on" - - almstring := e.Request.URL.Query().Get(BAENDE_PARAM_ALM_NR + "string") - - titlestring := e.Request.URL.Query().Get(BAENDE_PARAM_TITLE + "string") - seriesstring := e.Request.URL.Query().Get(BAENDE_PARAM_SERIES + "string") - personsstring := e.Request.URL.Query().Get(BAENDE_PARAM_PERSONS + "string") - placesstring := e.Request.URL.Query().Get(BAENDE_PARAM_PLACES + "string") - annotationsstring := e.Request.URL.Query().Get(BAENDE_PARAM_ANNOTATIONS + "string") - yearstring := e.Request.URL.Query().Get(BAENDE_PARAM_YEAR + "string") - - refss := e.Request.URL.Query().Get(BAENDE_PARAM_REFS + "string") - if refss != "" { - refss = "\"" + refss + "\"" - } - - sort := e.Request.URL.Query().Get("sort") - if sort == "" { - sort = "year" - } - - return &SearchParameters{ - Parameters: p, - Sort: sort, - // INFO: Common parameters - Title: title, - Persons: persons, - Annotations: annotations, - - // INFO: Baende parameters - Places: places, - Refs: refs, - Year: year, - Series: series, - - // INFO: Expanded search - AlmString: almstring, - TitleString: titlestring, - SeriesString: seriesstring, - PersonsString: personsstring, - PlacesString: placesstring, - RefsString: refss, - AnnotationsString: annotationsstring, - YearString: yearstring, - }, nil -} - -func (p SearchParameters) AllSearchTerms() string { - res := []string{} - res = append(res, p.includedParams(p.Query)...) - res = append(res, p.includedParams(p.AnnotationsString)...) - res = append(res, p.includedParams(p.PersonsString)...) - res = append(res, p.includedParams(p.TitleString)...) - res = append(res, p.includedParams(p.SeriesString)...) - res = append(res, p.includedParams(p.PlacesString)...) - res = append(res, p.includedParams(p.RefsString)...) - res = append(res, p.includedParams(p.YearString)...) - res = append(res, p.AlmString) - return strings.Join(res, " ") -} - -func (p SearchParameters) includedParams(q string) []string { - res := []string{} - que := dbmodels.NormalizeQuery(q) - for _, qq := range que.Include { - res = append(res, qq) - } - for _, qq := range que.UnsafeI { - res = append(res, qq) - } - return res -} - -func (p SearchParameters) ToQueryParams() string { - q := "?" - - // TODO: use variables, url escape - if p.Extended { - q += "extended=true" - } - - if p.Query != "" { - q += fmt.Sprintf("q=%s", p.Query) - - if p.Title { - q += "&title=on" - } - if p.Persons { - q += "&persons=on" - } - if p.Annotations { - q += "&annotations=on" - } - if p.Series { - q += "&series=on" - } - if p.Places { - q += "&places=on" - } - if p.Refs { - q += "&references=on" - } - if p.Year { - q += "&year=on" - } - } - - if p.YearString != "" { - q += fmt.Sprintf("&yearstring=%s", p.YearString) - } - if p.AnnotationsString != "" { - q += fmt.Sprintf("&annotationsstring=%s", p.AnnotationsString) - } - if p.PersonsString != "" { - q += fmt.Sprintf("&personsstring=%s", p.PersonsString) - } - if p.TitleString != "" { - q += fmt.Sprintf("&titlestring=%s", p.TitleString) - } - if p.SeriesString != "" { - q += fmt.Sprintf("&seriesstring=%s", p.SeriesString) - } - if p.PlacesString != "" { - q += fmt.Sprintf("&placesstring=%s", p.PlacesString) - } - if p.RefsString != "" { - q += fmt.Sprintf("&refsstring=%s", p.RefsString) - } - - return q -} - -func (p SearchParameters) IsBaendeSearch() bool { - return p.Collection == "baende" && (p.Query != "" || p.AlmString != "" || p.AnnotationsString != "" || p.PersonsString != "" || p.TitleString != "" || p.SeriesString != "" || p.PlacesString != "" || p.RefsString != "" || p.YearString != "") -} - -func (p SearchParameters) FieldSetBaende() []dbmodels.FTS5QueryRequest { - ret := []dbmodels.FTS5QueryRequest{} - if p.Query != "" { - fields := []string{dbmodels.ID_FIELD} - if p.Title { - // INFO: Preferred Title is not here to avoid hitting the Reihentitel - fields = append(fields, - dbmodels.TITLE_STMT_FIELD, - dbmodels.SUBTITLE_STMT_FIELD, - dbmodels.INCIPIT_STMT_FIELD, - dbmodels.VARIANT_TITLE_FIELD, - dbmodels.PARALLEL_TITLE_FIELD, - ) - } - if p.Series { - fields = append(fields, dbmodels.SERIES_TABLE) - } - if p.Persons { - fields = append(fields, dbmodels.RESPONSIBILITY_STMT_FIELD, dbmodels.AGENTS_TABLE) - } - if p.Places { - fields = append(fields, dbmodels.PLACES_TABLE, dbmodels.PLACE_STMT_FIELD) - } - if p.Refs { - fields = append(fields, dbmodels.REFERENCES_FIELD) - } - if p.Annotations { - fields = append(fields, dbmodels.ANNOTATION_FIELD) - } - if p.Year { - fields = append(fields, dbmodels.YEAR_FIELD) - } - - que := p.NormalizeQuery() - req := dbmodels.IntoQueryRequests(fields, que) - ret = append(ret, req...) - } - - if p.AnnotationsString != "" { - que := dbmodels.NormalizeQuery(p.AnnotationsString) - req := dbmodels.IntoQueryRequests([]string{dbmodels.ANNOTATION_FIELD}, que) - ret = append(ret, req...) - } - - if p.PersonsString != "" { - que := dbmodels.NormalizeQuery(p.PersonsString) - req := dbmodels.IntoQueryRequests([]string{dbmodels.AGENTS_TABLE, dbmodels.RESPONSIBILITY_STMT_FIELD}, que) - ret = append(ret, req...) - } - - if p.TitleString != "" { - que := dbmodels.NormalizeQuery(p.TitleString) - req := dbmodels.IntoQueryRequests([]string{dbmodels.TITLE_STMT_FIELD, dbmodels.SUBTITLE_STMT_FIELD, dbmodels.INCIPIT_STMT_FIELD, dbmodels.VARIANT_TITLE_FIELD, dbmodels.PARALLEL_TITLE_FIELD}, que) - ret = append(ret, req...) - } - - if p.SeriesString != "" { - que := dbmodels.NormalizeQuery(p.SeriesString) - req := dbmodels.IntoQueryRequests([]string{dbmodels.SERIES_TABLE}, que) - ret = append(ret, req...) - } - - if p.PlacesString != "" { - que := dbmodels.NormalizeQuery(p.PlacesString) - req := dbmodels.IntoQueryRequests([]string{dbmodels.PLACES_TABLE, dbmodels.PLACE_STMT_FIELD}, que) - ret = append(ret, req...) - } - - if p.RefsString != "" { - que := dbmodels.NormalizeQuery(p.RefsString) - req := dbmodels.IntoQueryRequests([]string{dbmodels.REFERENCES_FIELD}, que) - ret = append(ret, req...) - } - - if p.YearString != "" { - que := dbmodels.NormalizeQuery(p.YearString) - req := dbmodels.IntoQueryRequests([]string{dbmodels.YEAR_FIELD}, que) - ret = append(ret, req...) - } - - return ret -} - -func (p SearchParameters) IsExtendedSearch() bool { - return p.AnnotationsString != "" || p.PersonsString != "" || p.TitleString != "" || p.AlmString != "" || p.SeriesString != "" || p.PlacesString != "" || p.RefsString != "" || p.YearString != "" -} - -func (p SearchParameters) NormalizeQuery() dbmodels.Query { - return dbmodels.NormalizeQuery(p.Query) -} - -type SearchResultBaende struct { - Queries []dbmodels.FTS5QueryRequest - - // these are the sorted IDs for hits - Hits []string - Series map[string]*dbmodels.Series // <- Key: Series ID - Entries map[string]*dbmodels.Entry // <- Key: Entry ID - Places map[string]*dbmodels.Place // <- All places, Key: Place IDs - Agents map[string]*dbmodels.Agent // <- Key: Agent IDs - - // INFO: this is as they say doppelt gemoppelt bc of a logic error i made while tired - EntriesSeries map[string][]*dbmodels.REntriesSeries // <- Key: Entry ID - SeriesEntries map[string][]*dbmodels.REntriesSeries // <- Key: Series ID - EntriesAgents map[string][]*dbmodels.REntriesAgents // <- Key: Entry ID -} - -func EmptyResultBaende() *SearchResultBaende { - return &SearchResultBaende{ - Hits: []string{}, - Series: make(map[string]*dbmodels.Series), - Entries: make(map[string]*dbmodels.Entry), - Places: make(map[string]*dbmodels.Place), - Agents: make(map[string]*dbmodels.Agent), - EntriesSeries: make(map[string][]*dbmodels.REntriesSeries), - SeriesEntries: make(map[string][]*dbmodels.REntriesSeries), - EntriesAgents: make(map[string][]*dbmodels.REntriesAgents), - } -} - -func SimpleSearchBaende(app core.App, params SearchParameters) (*SearchResultBaende, error) { - entries := []*dbmodels.Entry{} - queries := params.FieldSetBaende() - - if params.AlmString != "" { - e, err := dbmodels.Entries_MusenalmID(app, params.AlmString) - if err != nil && err == sql.ErrNoRows { - return EmptyResultBaende(), nil - } else if err != nil { - return nil, err - } - - entries = append(entries, e) - } else { - if len(queries) == 0 { - return nil, ErrNoQuery - } - - ids, err := dbmodels.FTS5Search(app, dbmodels.ENTRIES_TABLE, queries...) - if err != nil { - return nil, err - } - - resultids := []any{} - for _, id := range ids { - resultids = append(resultids, id.ID) - } - - e, err := dbmodels.Entries_IDs(app, resultids) - if err != nil { - return nil, err - } - entries = e - } - - resultids := []any{} - for _, entry := range entries { - resultids = append(resultids, entry.Id) - } - - entriesmap := make(map[string]*dbmodels.Entry) - for _, entry := range entries { - entriesmap[entry.Id] = entry - } - - series, relations, err := Series_Entries(app, entries) - if err != nil { - return nil, err - } - - seriesmap := make(map[string]*dbmodels.Series) - for _, s := range series { - seriesmap[s.Id] = s - } - - relationsmap := make(map[string][]*dbmodels.REntriesSeries) - invrelationsmap := make(map[string][]*dbmodels.REntriesSeries) - for _, r := range relations { - invrelationsmap[r.Series()] = append(invrelationsmap[r.Series()], r) - relationsmap[r.Entry()] = append(relationsmap[r.Entry()], r) - } - - agents, arelations, err := Agents_Entries_IDs(app, resultids) - if err != nil { - return nil, err - } - - agentsmap := make(map[string]*dbmodels.Agent) - for _, a := range agents { - agentsmap[a.Id] = a - } - - relationsagentsmap := make(map[string][]*dbmodels.REntriesAgents) - for _, r := range arelations { - relationsagentsmap[r.Entry()] = append(relationsagentsmap[r.Entry()], r) - } - - placesids := []any{} - for _, entry := range entries { - for _, place := range entry.Places() { - placesids = append(placesids, place) - } - } - - places, err := dbmodels.Places_IDs(app, placesids) - if err != nil { - return nil, err - } - - placesmap := make(map[string]*dbmodels.Place) - for _, place := range places { - placesmap[place.Id] = place - } - - hits := []string{} - if params.Sort == "series" { - dbmodels.Sort_Series_Title(series) - for _, s := range series { - hits = append(hits, s.Id) - } - } else { - dbmodels.Sort_Entries_Year_Title(entries) - for _, e := range entries { - hits = append(hits, e.Id) - } - } - - return &SearchResultBaende{ - Hits: hits, - Series: seriesmap, - Entries: entriesmap, - Places: placesmap, - Agents: agentsmap, - EntriesSeries: relationsmap, - SeriesEntries: invrelationsmap, - EntriesAgents: relationsagentsmap, - }, nil - -} - -func (r SearchResultBaende) Count() int { - return len(r.Entries) -} - -func (r SearchResultBaende) SeriesCount() int { - return len(r.Series) -} - -func Agents_Entries_IDs(app core.App, ids []any) ([]*dbmodels.Agent, []*dbmodels.REntriesAgents, error) { - relations, err := dbmodels.REntriesAgents_Entries(app, ids) - if err != nil { - return nil, nil, err - } - - agentids := []any{} - for _, r := range relations { - agentids = append(agentids, r.Agent()) - } - - agents, err := dbmodels.Agents_IDs(app, agentids) - if err != nil { - return nil, nil, err - } - - return agents, relations, nil -} diff --git a/pages/suche_baende.go b/pages/suche_baende.go new file mode 100644 index 0000000..abd300a --- /dev/null +++ b/pages/suche_baende.go @@ -0,0 +1,186 @@ +package pages + +import ( + "database/sql" + + "github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels" + "github.com/pocketbase/pocketbase/core" +) + +type SearchResultBaende struct { + Queries []dbmodels.FTS5QueryRequest + + // these are the sorted IDs for hits + Hits []string + Series map[string]*dbmodels.Series // <- Key: Series ID + Entries map[string]*dbmodels.Entry // <- Key: Entry ID + Places map[string]*dbmodels.Place // <- All places, Key: Place IDs + Agents map[string]*dbmodels.Agent // <- Key: Agent IDs + + // INFO: this is as they say doppelt gemoppelt bc of a logic error i made while tired + EntriesSeries map[string][]*dbmodels.REntriesSeries // <- Key: Entry ID + SeriesEntries map[string][]*dbmodels.REntriesSeries // <- Key: Series ID + EntriesAgents map[string][]*dbmodels.REntriesAgents // <- Key: Entry ID +} + +func EmptyResultBaende() *SearchResultBaende { + return &SearchResultBaende{ + Hits: []string{}, + Series: make(map[string]*dbmodels.Series), + Entries: make(map[string]*dbmodels.Entry), + Places: make(map[string]*dbmodels.Place), + Agents: make(map[string]*dbmodels.Agent), + EntriesSeries: make(map[string][]*dbmodels.REntriesSeries), + SeriesEntries: make(map[string][]*dbmodels.REntriesSeries), + EntriesAgents: make(map[string][]*dbmodels.REntriesAgents), + } +} + +func NewSearchBaende(app core.App, params SearchParameters) (*SearchResultBaende, error) { + entries := []*dbmodels.Entry{} + queries := params.FieldSetBaende() + + if params.AlmString != "" { + e, err := dbmodels.Entries_MusenalmID(app, params.AlmString) + if err != nil && err == sql.ErrNoRows { + return EmptyResultBaende(), nil + } else if err != nil { + return nil, err + } + + entries = append(entries, e) + } else { + if len(queries) == 0 { + return nil, ErrNoQuery + } + + ids, err := dbmodels.FTS5Search(app, dbmodels.ENTRIES_TABLE, queries...) + if err != nil { + return nil, err + } else if len(ids) == 0 { + return EmptyResultBaende(), nil + } + + resultids := []any{} + for _, id := range ids { + resultids = append(resultids, id.ID) + } + + e, err := dbmodels.Entries_IDs(app, resultids) + if err != nil { + return nil, err + } + entries = e + } + + resultids := []any{} + for _, entry := range entries { + resultids = append(resultids, entry.Id) + } + + entriesmap := make(map[string]*dbmodels.Entry) + for _, entry := range entries { + entriesmap[entry.Id] = entry + } + + series, relations, err := Series_Entries(app, entries) + if err != nil { + return nil, err + } + + seriesmap := make(map[string]*dbmodels.Series) + for _, s := range series { + seriesmap[s.Id] = s + } + + relationsmap := make(map[string][]*dbmodels.REntriesSeries) + invrelationsmap := make(map[string][]*dbmodels.REntriesSeries) + for _, r := range relations { + invrelationsmap[r.Series()] = append(invrelationsmap[r.Series()], r) + relationsmap[r.Entry()] = append(relationsmap[r.Entry()], r) + } + + agents, arelations, err := Agents_Entries_IDs(app, resultids) + if err != nil { + return nil, err + } + + agentsmap := make(map[string]*dbmodels.Agent) + for _, a := range agents { + agentsmap[a.Id] = a + } + + relationsagentsmap := make(map[string][]*dbmodels.REntriesAgents) + for _, r := range arelations { + relationsagentsmap[r.Entry()] = append(relationsagentsmap[r.Entry()], r) + } + + placesids := []any{} + for _, entry := range entries { + for _, place := range entry.Places() { + placesids = append(placesids, place) + } + } + + places, err := dbmodels.Places_IDs(app, placesids) + if err != nil { + return nil, err + } + + placesmap := make(map[string]*dbmodels.Place) + for _, place := range places { + placesmap[place.Id] = place + } + + hits := []string{} + if params.Sort == "series" { + dbmodels.Sort_Series_Title(series) + for _, s := range series { + hits = append(hits, s.Id) + } + } else { + dbmodels.Sort_Entries_Year_Title(entries) + for _, e := range entries { + hits = append(hits, e.Id) + } + } + + return &SearchResultBaende{ + Hits: hits, + Series: seriesmap, + Entries: entriesmap, + Places: placesmap, + Agents: agentsmap, + EntriesSeries: relationsmap, + SeriesEntries: invrelationsmap, + EntriesAgents: relationsagentsmap, + }, nil + +} + +func (r SearchResultBaende) Count() int { + return len(r.Entries) +} + +func (r SearchResultBaende) SeriesCount() int { + return len(r.Series) +} + +func Agents_Entries_IDs(app core.App, ids []any) ([]*dbmodels.Agent, []*dbmodels.REntriesAgents, error) { + relations, err := dbmodels.REntriesAgents_Entries(app, ids) + if err != nil { + return nil, nil, err + } + + agentids := []any{} + for _, r := range relations { + agentids = append(agentids, r.Agent()) + } + + agents, err := dbmodels.Agents_IDs(app, agentids) + if err != nil { + return nil, nil, err + } + + return agents, relations, nil +} diff --git a/pages/suche_beitraege.go b/pages/suche_beitraege.go new file mode 100644 index 0000000..0538c84 --- /dev/null +++ b/pages/suche_beitraege.go @@ -0,0 +1,188 @@ +package pages + +import ( + "database/sql" + + "github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels" + "github.com/Theodor-Springmann-Stiftung/musenalm/helpers/datatypes" + "github.com/pocketbase/pocketbase/core" +) + +const ( + DEFAULT_PAGESIZE = 80 +) + +type SearchResultBeitraege struct { + Queries []dbmodels.FTS5QueryRequest + + // these are the sorted IDs for hits + Hits []string + Entries map[string]*dbmodels.Entry // <- Key: Entry ID + Agents map[string]*dbmodels.Agent // <- Key: Agent IDs + Contents map[string][]*dbmodels.Content // <- Key: Entry ID, or year + + ContentsAgents map[string][]*dbmodels.RContentsAgents // <- Key: Content ID + Pages []int +} + +func EmptyResultBeitraege() *SearchResultBeitraege { + return &SearchResultBeitraege{ + Hits: []string{}, + Entries: make(map[string]*dbmodels.Entry), + Agents: make(map[string]*dbmodels.Agent), + Contents: make(map[string][]*dbmodels.Content), + ContentsAgents: make(map[string][]*dbmodels.RContentsAgents), + } +} + +func NewSearchBeitraege(app core.App, params SearchParameters) (*SearchResultBeitraege, error) { + contents := []*dbmodels.Content{} + queries := params.FieldSetBeitraege() + + if params.AlmString != "" { + e, err := dbmodels.Contents_MusenalmID(app, params.AlmString) + if err != nil && err == sql.ErrNoRows { + return EmptyResultBeitraege(), nil + } else if err != nil { + return nil, err + } + + contents = append(contents, e) + } else { + if len(queries) == 0 { + return nil, ErrNoQuery + } + + hits, err := dbmodels.FTS5Search(app, dbmodels.CONTENTS_TABLE, queries...) + if err != nil { + return nil, err + } else if len(hits) == 0 { + return EmptyResultBeitraege(), nil + } + + ids := []any{} + for _, hit := range hits { + ids = append(ids, hit.ID) + } + + cs, err := dbmodels.Contents_IDs(app, ids) + if err != nil { + return nil, err + } + + contents = append(contents, cs...) + } + + resultids := []any{} + resultentryids := []string{} + for _, content := range contents { + resultids = append(resultids, content.Id) + resultentryids = append(resultentryids, content.Entry()) + } + + entries, err := dbmodels.Entries_IDs(app, datatypes.ToAny(resultentryids)) + if err != nil { + return nil, err + } + + if params.Sort == "year" { + dbmodels.Sort_Entries_Year_Title(entries) + } else { + dbmodels.Sort_Entries_Title_Year(entries) + } + + arels, err := dbmodels.RContentsAgents_Contents(app, resultids) + if err != nil { + return nil, err + } + + aids := []any{} + for _, a := range arels { + aids = append(aids, a.Agent()) + } + + agents, err := dbmodels.Agents_IDs(app, aids) + if err != nil { + return nil, err + } + + contentsmap := make(map[string][]*dbmodels.Content) + for _, c := range contents { + contentsmap[c.Entry()] = append(contentsmap[c.Entry()], c) + } + + contentsagents := make(map[string][]*dbmodels.RContentsAgents) + for _, a := range arels { + contentsagents[a.Content()] = append(contentsagents[a.Content()], a) + } + + agentsmap := make(map[string]*dbmodels.Agent) + for _, a := range agents { + agentsmap[a.Id] = a + } + + entriesmap := make(map[string]*dbmodels.Entry) + for _, e := range entries { + entriesmap[e.Id] = e + } + + hits := []string{} + for _, e := range entries { + hits = append(hits, e.Id) + } + + pages := PagesEntries(hits, contentsmap, DEFAULT_PAGESIZE) + if params.Page < 1 || params.Page > len(pages) { + params.Page = 1 + } + + if params.Page == len(pages) { + hits = hits[pages[params.Page-1]:] + } else { + hits = hits[pages[params.Page-1]:pages[params.Page]] + } + + return &SearchResultBeitraege{ + Queries: queries, + Hits: hits, + Entries: entriesmap, + Agents: agentsmap, + Contents: contentsmap, + ContentsAgents: contentsagents, + Pages: pages, + }, nil +} + +func (p *SearchResultBeitraege) CountEntries() int { + return len(p.Entries) +} + +func (p *SearchResultBeitraege) Count() int { + cnt := 0 + for _, c := range p.Contents { + cnt += len(c) + } + return cnt +} + +func (p *SearchResultBeitraege) PagesCount() int { + return len(p.Pages) - 1 +} + +func PagesEntries[T any](hits []string, hitmap map[string][]*T, pagesize int) []int { + ret := []int{0} + m := 0 + for i, hit := range hits { + m += len(hitmap[hit]) + if m >= pagesize { + ret = append(ret, i) + m = 0 + } + } + + if m > 0 { + ret = append(ret, len(hits)) + } + + return ret +} diff --git a/pages/suche_parameters.go b/pages/suche_parameters.go new file mode 100644 index 0000000..8e99ee8 --- /dev/null +++ b/pages/suche_parameters.go @@ -0,0 +1,465 @@ +package pages + +import ( + "fmt" + "strconv" + "strings" + + "github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels" + "github.com/pocketbase/pocketbase/core" +) + +var ErrNoQuery = fmt.Errorf("No query") + +const ( + SEARCH_PARAM_ALM_NR = "alm" + SEARCH_PARAM_TITLE = "title" + SEARCH_PARAM_PERSONS = "persons" + SEARCH_PARAM_ANNOTATIONS = "annotations" + SEARCH_PARAM_YEAR = "year" + BAENDE_PARAM_PLACES = "places" + BAENDE_PARAM_SERIES = "series" + BAENDE_PARAM_REFS = "references" + BEITRAEGE_PARAM_ENTRY = "entry" + BEITRAEGE_PARAM_INCIPT = "incipit" +) + +type SearchParameters struct { + Parameters + Sort string // year, entry, + + Annotations bool + Persons bool + Title bool + Series bool + Places bool + Refs bool + Year bool + Entry bool + Incipit bool + + AnnotationsString string + PersonsString string + TitleString string + AlmString string + SeriesString string + PlacesString string + RefsString string + YearString string + EntryString string + IncipitString string + + Page int +} + +func NewSearchParameters(e *core.RequestEvent, p Parameters) (*SearchParameters, error) { + almstring := e.Request.URL.Query().Get(SEARCH_PARAM_ALM_NR + "string") + title := e.Request.URL.Query().Get(SEARCH_PARAM_TITLE) == "on" + titlestring := e.Request.URL.Query().Get(SEARCH_PARAM_TITLE + "string") + persons := e.Request.URL.Query().Get(SEARCH_PARAM_PERSONS) == "on" + personsstring := e.Request.URL.Query().Get(SEARCH_PARAM_PERSONS + "string") + annotations := e.Request.URL.Query().Get(SEARCH_PARAM_ANNOTATIONS) == "on" + annotationsstring := e.Request.URL.Query().Get(SEARCH_PARAM_ANNOTATIONS + "string") + year := e.Request.URL.Query().Get(SEARCH_PARAM_YEAR) == "on" + yearstring := e.Request.URL.Query().Get(SEARCH_PARAM_YEAR + "string") + + series := e.Request.URL.Query().Get(BAENDE_PARAM_SERIES) == "on" + seriesstring := e.Request.URL.Query().Get(BAENDE_PARAM_SERIES + "string") + places := e.Request.URL.Query().Get(BAENDE_PARAM_PLACES) == "on" + placesstring := e.Request.URL.Query().Get(BAENDE_PARAM_PLACES + "string") + refs := e.Request.URL.Query().Get(BAENDE_PARAM_REFS) == "on" + + refss := e.Request.URL.Query().Get(BAENDE_PARAM_REFS + "string") + if refss != "" { + refss = "\"" + refss + "\"" + } + + incipit := e.Request.URL.Query().Get(BEITRAEGE_PARAM_INCIPT) == "on" + incipitstring := e.Request.URL.Query().Get(BEITRAEGE_PARAM_INCIPT + "string") + entry := e.Request.URL.Query().Get(BEITRAEGE_PARAM_ENTRY) == "on" + entrystring := e.Request.URL.Query().Get(BEITRAEGE_PARAM_ENTRY + "string") + + sort := e.Request.URL.Query().Get("sort") + if sort == "" { + sort = "year" + } + + page := e.Request.URL.Query().Get("page") + if page == "" { + page = "1" + } + + pageint, err := strconv.Atoi(page) + if err != nil { + return nil, err + } + + return &SearchParameters{ + Parameters: p, + Sort: sort, + Page: pageint, + + // INFO: Common parameters + Title: title, + Persons: persons, + Annotations: annotations, + Year: year, + + // INFO: Baende parameters + Places: places, + Refs: refs, + Series: series, + + // INFO: Beitraege parameters + Entry: entry, + Incipit: incipit, + + // INFO: Expanded search + AlmString: almstring, + TitleString: titlestring, + SeriesString: seriesstring, + PersonsString: personsstring, + PlacesString: placesstring, + RefsString: refss, + AnnotationsString: annotationsstring, + YearString: yearstring, + EntryString: entrystring, + IncipitString: incipitstring, + }, nil +} + +func (p SearchParameters) AllSearchTermsBaende() string { + res := []string{} + res = append(res, p.includedParams(p.Query)...) + res = append(res, p.includedParams(p.AnnotationsString)...) + res = append(res, p.includedParams(p.PersonsString)...) + res = append(res, p.includedParams(p.TitleString)...) + res = append(res, p.includedParams(p.SeriesString)...) + res = append(res, p.includedParams(p.PlacesString)...) + res = append(res, p.includedParams(p.RefsString)...) + res = append(res, p.includedParams(p.YearString)...) + res = append(res, p.AlmString) + return strings.Join(res, " ") +} + +func (p SearchParameters) AllSearchTermsBeitraege() string { + res := []string{} + res = append(res, p.includedParams(p.Query)...) + res = append(res, p.includedParams(p.AnnotationsString)...) + res = append(res, p.includedParams(p.PersonsString)...) + res = append(res, p.includedParams(p.TitleString)...) + res = append(res, p.includedParams(p.YearString)...) + res = append(res, p.includedParams(p.EntryString)...) + res = append(res, p.includedParams(p.IncipitString)...) + return strings.Join(res, " ") +} + +func (p SearchParameters) includedParams(q string) []string { + res := []string{} + que := dbmodels.NormalizeQuery(q) + for _, qq := range que.Include { + res = append(res, qq) + } + for _, qq := range que.UnsafeI { + res = append(res, qq) + } + return res +} + +func (p SearchParameters) ToQueryParamsBeitraege() string { + q := "?" + + if p.Extended { + q += "extended=true" + } + + if p.Query != "" { + q += fmt.Sprintf("q=%s", p.Query) + + if p.Title { + q += "&title=on" + } + + if p.Persons { + q += "&persons=on" + } + + if p.Annotations { + q += "&annotations=on" + } + + if p.Year { + q += "&year=on" + } + + if p.Entry { + q += "&entry=on" + } + + if p.Incipit { + q += "&incipit=on" + } + } + + if p.YearString != "" { + q += fmt.Sprintf("&yearstring=%s", p.YearString) + } + + if p.AnnotationsString != "" { + q += fmt.Sprintf("&annotationsstring=%s", p.AnnotationsString) + } + + if p.PersonsString != "" { + q += fmt.Sprintf("&personsstring=%s", p.PersonsString) + } + + if p.TitleString != "" { + q += fmt.Sprintf("&titlestring=%s", p.TitleString) + } + + if p.EntryString != "" { + q += fmt.Sprintf("&entrystring=%s", p.EntryString) + } + + if p.IncipitString != "" { + q += fmt.Sprintf("&incipitstring=%s", p.IncipitString) + } + + return q +} + +func (p SearchParameters) ToQueryParamsBaende() string { + q := "?" + + // TODO: use variables, url escape + if p.Extended { + q += "extended=true" + } + + if p.Query != "" { + q += fmt.Sprintf("q=%s", p.Query) + + if p.Title { + q += "&title=on" + } + if p.Persons { + q += "&persons=on" + } + if p.Annotations { + q += "&annotations=on" + } + if p.Series { + q += "&series=on" + } + if p.Places { + q += "&places=on" + } + if p.Refs { + q += "&references=on" + } + if p.Year { + q += "&year=on" + } + } + + if p.YearString != "" { + q += fmt.Sprintf("&yearstring=%s", p.YearString) + } + if p.AnnotationsString != "" { + q += fmt.Sprintf("&annotationsstring=%s", p.AnnotationsString) + } + if p.PersonsString != "" { + q += fmt.Sprintf("&personsstring=%s", p.PersonsString) + } + if p.TitleString != "" { + q += fmt.Sprintf("&titlestring=%s", p.TitleString) + } + if p.SeriesString != "" { + q += fmt.Sprintf("&seriesstring=%s", p.SeriesString) + } + if p.PlacesString != "" { + q += fmt.Sprintf("&placesstring=%s", p.PlacesString) + } + if p.RefsString != "" { + q += fmt.Sprintf("&refsstring=%s", p.RefsString) + } + + return q +} + +func (p SearchParameters) IsBeitraegeSearch() bool { + return p.Collection == "beitraege" && (p.Query != "" || p.AlmString != "" || p.AnnotationsString != "" || p.PersonsString != "" || p.TitleString != "" || p.YearString != "" || p.EntryString != "" || p.IncipitString != "") +} + +func (p SearchParameters) IsBaendeSearch() bool { + return p.Collection == "baende" && (p.Query != "" || p.AlmString != "" || p.AnnotationsString != "" || p.PersonsString != "" || p.TitleString != "" || p.SeriesString != "" || p.PlacesString != "" || p.RefsString != "" || p.YearString != "") +} + +func (p SearchParameters) FieldSetBeitraege() []dbmodels.FTS5QueryRequest { + ret := []dbmodels.FTS5QueryRequest{} + if p.Query != "" { + fields := []string{dbmodels.ID_FIELD} + if p.Title { + fields = append(fields, dbmodels.TITLE_STMT_FIELD, dbmodels.SUBTITLE_STMT_FIELD, dbmodels.VARIANT_TITLE_FIELD, dbmodels.PARALLEL_TITLE_FIELD) + } + if p.Persons { + fields = append(fields, dbmodels.RESPONSIBILITY_STMT_FIELD, dbmodels.AGENTS_TABLE) + } + if p.Annotations { + fields = append(fields, dbmodels.ANNOTATION_FIELD) + } + if p.Year { + fields = append(fields, dbmodels.YEAR_FIELD) + } + if p.Entry { + fields = append(fields, dbmodels.ENTRIES_TABLE) + } + if p.Incipit { + fields = append(fields, dbmodels.INCIPIT_STMT_FIELD) + } + + que := p.NormalizeQuery() + req := dbmodels.IntoQueryRequests(fields, que) + ret = append(ret, req...) + } + + if p.AnnotationsString != "" { + que := dbmodels.NormalizeQuery(p.AnnotationsString) + req := dbmodels.IntoQueryRequests([]string{dbmodels.ANNOTATION_FIELD}, que) + ret = append(ret, req...) + } + + if p.PersonsString != "" { + que := dbmodels.NormalizeQuery(p.PersonsString) + req := dbmodels.IntoQueryRequests([]string{dbmodels.AGENTS_TABLE, dbmodels.RESPONSIBILITY_STMT_FIELD}, que) + ret = append(ret, req...) + } + + if p.TitleString != "" { + que := dbmodels.NormalizeQuery(p.TitleString) + req := dbmodels.IntoQueryRequests([]string{dbmodels.TITLE_STMT_FIELD, dbmodels.SUBTITLE_STMT_FIELD, dbmodels.VARIANT_TITLE_FIELD, dbmodels.PARALLEL_TITLE_FIELD}, que) + ret = append(ret, req...) + } + + if p.YearString != "" { + que := dbmodels.NormalizeQuery(p.YearString) + req := dbmodels.IntoQueryRequests([]string{dbmodels.YEAR_FIELD}, que) + ret = append(ret, req...) + } + + if p.EntryString != "" { + que := dbmodels.NormalizeQuery(p.EntryString) + req := dbmodels.IntoQueryRequests([]string{dbmodels.ENTRIES_TABLE}, que) + ret = append(ret, req...) + } + + if p.IncipitString != "" { + que := dbmodels.NormalizeQuery(p.IncipitString) + req := dbmodels.IntoQueryRequests([]string{dbmodels.INCIPIT_STMT_FIELD}, que) + ret = append(ret, req...) + } + + return ret +} + +func (p SearchParameters) FieldSetBaende() []dbmodels.FTS5QueryRequest { + ret := []dbmodels.FTS5QueryRequest{} + if p.Query != "" { + fields := []string{dbmodels.ID_FIELD} + if p.Title { + // INFO: Preferred Title is not here to avoid hitting the Reihentitel + fields = append(fields, + dbmodels.TITLE_STMT_FIELD, + dbmodels.SUBTITLE_STMT_FIELD, + dbmodels.INCIPIT_STMT_FIELD, + dbmodels.VARIANT_TITLE_FIELD, + dbmodels.PARALLEL_TITLE_FIELD, + ) + } + if p.Series { + fields = append(fields, dbmodels.SERIES_TABLE) + } + if p.Persons { + fields = append(fields, dbmodels.RESPONSIBILITY_STMT_FIELD, dbmodels.AGENTS_TABLE) + } + if p.Places { + fields = append(fields, dbmodels.PLACES_TABLE, dbmodels.PLACE_STMT_FIELD) + } + if p.Refs { + fields = append(fields, dbmodels.REFERENCES_FIELD) + } + if p.Annotations { + fields = append(fields, dbmodels.ANNOTATION_FIELD) + } + if p.Year { + fields = append(fields, dbmodels.YEAR_FIELD) + } + + que := p.NormalizeQuery() + req := dbmodels.IntoQueryRequests(fields, que) + ret = append(ret, req...) + } + + if p.AnnotationsString != "" { + que := dbmodels.NormalizeQuery(p.AnnotationsString) + req := dbmodels.IntoQueryRequests([]string{dbmodels.ANNOTATION_FIELD}, que) + ret = append(ret, req...) + } + + if p.PersonsString != "" { + que := dbmodels.NormalizeQuery(p.PersonsString) + req := dbmodels.IntoQueryRequests([]string{dbmodels.AGENTS_TABLE, dbmodels.RESPONSIBILITY_STMT_FIELD}, que) + ret = append(ret, req...) + } + + if p.TitleString != "" { + que := dbmodels.NormalizeQuery(p.TitleString) + req := dbmodels.IntoQueryRequests([]string{dbmodels.TITLE_STMT_FIELD, dbmodels.SUBTITLE_STMT_FIELD, dbmodels.INCIPIT_STMT_FIELD, dbmodels.VARIANT_TITLE_FIELD, dbmodels.PARALLEL_TITLE_FIELD}, que) + ret = append(ret, req...) + } + + if p.SeriesString != "" { + que := dbmodels.NormalizeQuery(p.SeriesString) + req := dbmodels.IntoQueryRequests([]string{dbmodels.SERIES_TABLE}, que) + ret = append(ret, req...) + } + + if p.PlacesString != "" { + que := dbmodels.NormalizeQuery(p.PlacesString) + req := dbmodels.IntoQueryRequests([]string{dbmodels.PLACES_TABLE, dbmodels.PLACE_STMT_FIELD}, que) + ret = append(ret, req...) + } + + if p.RefsString != "" { + que := dbmodels.NormalizeQuery(p.RefsString) + req := dbmodels.IntoQueryRequests([]string{dbmodels.REFERENCES_FIELD}, que) + ret = append(ret, req...) + } + + if p.YearString != "" { + que := dbmodels.NormalizeQuery(p.YearString) + req := dbmodels.IntoQueryRequests([]string{dbmodels.YEAR_FIELD}, que) + ret = append(ret, req...) + } + + return ret +} + +func (p SearchParameters) IsExtendedSearch() bool { + return p.AnnotationsString != "" || p.PersonsString != "" || p.TitleString != "" || p.AlmString != "" || p.SeriesString != "" || p.PlacesString != "" || p.RefsString != "" || p.YearString != "" || p.EntryString != "" || p.IncipitString != "" +} + +func (p SearchParameters) NormalizeQuery() dbmodels.Query { + return dbmodels.NormalizeQuery(p.Query) +} + +func (p SearchParameters) Prev() int { + if p.Page > 1 { + return p.Page - 1 + } + return 1 +} + +func (p SearchParameters) Next() int { + return p.Page + 1 +} diff --git a/scratchpad.md b/scratchpad.md index 2b3caa7..d340084 100644 --- a/scratchpad.md +++ b/scratchpad.md @@ -64,6 +64,8 @@ TODO danach: - HTMX + Smooth scroll - Personen: related - Inhaltsliste: Personen sehen komisch aus +- V\Boosted links gen by webcomponents +- Sammlungen u. Querverweise müssen die URL als Parameter bekommen - Sammlungen neuer versuch - Inhaltsliste Personen - Sortierung nach Band A-Z? diff --git a/views/layouts/default/root.gohtml b/views/layouts/default/root.gohtml index 9cf251e..3463505 100644 --- a/views/layouts/default/root.gohtml +++ b/views/layouts/default/root.gohtml @@ -2,6 +2,9 @@ + {{ block "head" . }} diff --git a/views/routes/components/_content.gohtml b/views/routes/components/_content.gohtml index 2a64749..c5b71f0 100644 --- a/views/routes/components/_content.gohtml +++ b/views/routes/components/_content.gohtml @@ -4,6 +4,32 @@ .3 - []*RContentsAgents .4 - map[string]*Agent .5 bool SingleView + .6 .parameters: + type SearchParameters struct { + Parameters + Sort string + + Annotations bool + Persons bool + Title bool + Series bool + Places bool + Refs bool + Year bool + Entry bool + Incipit bool + + AnnotationsString string + PersonsString string + TitleString string + AlmString string + SeriesString string + PlacesString string + RefsString string + YearString string + EntryString string + IncipitString string + } */}} {{- $content := index . 0 -}} @@ -21,6 +47,27 @@ {{- $entrySubView = index . 5 -}} {{- end -}} +{{ $isAlm := false }} +{{ $isTitle := false }} +{{ $isYear := false }} +{{ $isPerson := false }} +{{ $isAnnotation := false }} +{{ $isIncipit := false }} +{{ $isEntry := false }} + +{{- $searchparameters := false -}} +{{- if gt (len .) 6 -}} + {{- $searchparameters = index . 6 -}} + + {{- $isAlm = $searchparameters.AlmString -}} + {{- $isTitle = or $searchparameters.Title $searchparameters.TitleString -}} + {{- $isYear = or $searchparameters.Year $searchparameters.YearString -}} + {{- $isPerson = or $searchparameters.Persons $searchparameters.PersonsString -}} + {{- $isAnnotation = or $searchparameters.Annotations $searchparameters.AnnotationsString -}} + {{- $isIncipit = or $searchparameters.Incipit $searchparameters.IncipitString -}} + {{- $isEntry = or $searchparameters.Entry $searchparameters.EntryString -}} +{{- end -}} +
{{- if or $singleView $entrySubView -}}
Almanach
-
- +
+ {{- $entry.PreferredTitle -}} {{- if $content.Extent -}} @@ -62,15 +109,21 @@ {{- end -}} {{- if $content.TitleStmt -}}
Titel
-
{{- $content.TitleStmt -}}
+
+ {{- $content.TitleStmt -}} +
{{- end -}} {{- if $content.IncipitStmt -}}
Incipit
-
{{ $content.IncipitStmt }}…
+
+ {{ $content.IncipitStmt }}… +
{{- end -}} {{- if $content.ResponsibilityStmt -}}
Autorangabe
-
{{- $content.ResponsibilityStmt -}}
+
+ {{- $content.ResponsibilityStmt -}} +
{{- end -}} {{- if $rcas -}}
Personen
@@ -79,7 +132,11 @@ {{- range $_, $rca := $rcas -}} {{- $agent := index $agents $rca.Agent -}}
- {{- $agent.Name -}} + + {{- $agent.Name -}} + ({{ $agent.BiographicalData -}})
{{- end -}} @@ -87,10 +144,11 @@
{{- end -}} {{- if $content.Annotation -}} + {{- $link := printf "%s%s" "/almanach/" $entry.MusenalmIDString -}}
Anmerkung
-
+
{{- Safe (LinksAnnotation (ReplaceSlashParen - $content.Annotation)) + $content.Annotation) $link) -}}
{{- end -}} @@ -131,7 +189,9 @@
NR - {{ $content.MusenalmID -}} + + {{- $content.MusenalmID -}} +
{{- if $entrySubView -}} {{- if $content.MusenalmType -}} diff --git a/views/routes/edition/einfuehrung/body.gohtml b/views/routes/edition/einfuehrung/body.gohtml index c099551..9281ca0 100644 --- a/views/routes/edition/einfuehrung/body.gohtml +++ b/views/routes/edition/einfuehrung/body.gohtml @@ -1,5 +1,5 @@
-
+
{{ Safe .record.Text }}
diff --git a/views/routes/suche/baende/body.gohtml b/views/routes/suche/baende/body.gohtml index abd237d..ebe82e2 100644 --- a/views/routes/suche/baende/body.gohtml +++ b/views/routes/suche/baende/body.gohtml @@ -68,25 +68,7 @@
{{- template "_heading" $model.parameters -}}
-
- - - -
+ {{- template "_musenalmidbox" Arr $model.parameters.AlmString "baende" -}}
+ {{ if or $isBase $isPersons -}}checked{{- end -}} />
@@ -266,7 +244,7 @@ class="h-min pb-1 border-b-4 border-zinc-300 px-1.5" name="sort" id="sort" - hx-get="{{- $model.parameters.ToQueryParams -}}" + hx-get="{{- $model.parameters.ToQueryParamsBaende -}}" trigger="change" hx-push-url="true" hx-select="main" @@ -315,7 +293,7 @@ let mark_instance = new Mark(elements); // INFO: we wait a little bit before marking, to settle everything setTimeout(() => { - mark_instance.mark('{{ $model.parameters.AllSearchTerms }}', { + mark_instance.mark('{{ $model.parameters.AllSearchTermsBaende }}', { "seperateWordSearch": true, }); }, 200); @@ -326,40 +304,3 @@
Keine Bände gefunden.
{{- end -}} {{- end -}} - - - diff --git a/views/routes/suche/beitraege/body.gohtml b/views/routes/suche/beitraege/body.gohtml index fae86d8..12104fb 100644 --- a/views/routes/suche/beitraege/body.gohtml +++ b/views/routes/suche/beitraege/body.gohtml @@ -1,11 +1,69 @@ {{ $model := . }} +{{/* .result: + type SearchResultBeitraege struct { + Queries []dbmodels.FTS5QueryRequest + + // these are the sorted IDs for hits + Hits []string + Entries map[string]*dbmodels.Entry // <- Key: Entry ID + Agents map[string]*dbmodels.Agent // <- Key: Agent IDs + Contents map[string][]*dbmodels.Content // <- Key: Entry ID, or year + + ContentsAgents map[string][]*dbmodels.RContentsAgents // <- Key: Content ID + } + .parameters: + type SearchParameters struct { + Parameters + Sort string + + Annotations bool + Persons bool + Title bool + Series bool + Places bool + Refs bool + Year bool + Entry bool + Incipit bool + + AnnotationsString string + PersonsString string + TitleString string + AlmString string + SeriesString string + PlacesString string + RefsString string + YearString string + EntryString string + IncipitString string + } + */}} + +{{ $isAlm := false }} +{{ $isTitle := false }} +{{ $isYear := false }} +{{ $isPerson := false }} +{{ $isAnnotation := false }} +{{ $isIncipit := false }} +{{ $isEntry := false }} + +{{- $isAlm = $model.parameters.AlmString -}} +{{- $isTitle = or $model.parameters.Title $model.parameters.TitleString -}} +{{- $isYear = or $model.parameters.Year $model.parameters.YearString -}} +{{- $isPerson = or $model.parameters.Persons $model.parameters.PersonsString -}} +{{- $isAnnotation = or $model.parameters.Annotations $model.parameters.AnnotationsString -}} +{{- $isIncipit = or $model.parameters.Incipit $model.parameters.IncipitString -}} +{{- $isEntry = or $model.parameters.Entry $model.parameters.EntryString -}} + +{{- $isBase := not (or $isTitle $isYear $isPerson $isAnnotation $isIncipit $isEntry) -}}
{{- template "_heading" $model.parameters -}} -
+
+ {{- template "_musenalmidbox" Arr $model.parameters.AlmString "beitraege" -}}
- - + +
- - + +
- - -
-
- +
- - + +
- +
+
+ + +
{{ template "_infotextsimple" true }}
@@ -49,3 +113,127 @@
{{- template "_fieldscript" -}} + +{{- if $model.parameters.IsBeitraegeSearch -}} +
+
+
+ {{ if $model.parameters.Query -}} + Suche nach »{{ $model.parameters.Query }}« · + {{- end -}} + {{- if $isAlm -}} + Inhaltsnummer »{{ $model.parameters.AlmString }}« · + {{- end -}} + + {{ if eq $model.result.Count 1 -}} + Ein Beitrag + {{ else -}} + {{ $model.result.Count }} Beiträge + {{- end }} + in + {{ if eq ($model.result.Entries | len) 1 -}} + einem Band + {{ else -}} + {{ $model.result.Entries | len }} Bänden + {{- end -}} +
+ {{- if gt (len $model.result.Pages) 1 }} +
+ {{ if gt $model.parameters.Page 1 -}} + + + + {{- end -}} + S. {{ $model.parameters.Page }} / + {{ $model.result.PagesCount }} + {{ if lt $model.parameters.Page ($model.result.PagesCount) -}} + + + + {{- end -}} +
+ {{- end -}} + + {{- if not $isAlm -}} +
+ + + {{/* INFO: We always redrect to letter = A bc some letters dont exist for other professions */}} + +
+ {{- end -}} +
+ + {{- if $model.result.Hits -}} + +
+ {{- range $_, $hit := $model.result.Hits -}} + {{- $e := index $model.result.Entries $hit -}} + {{- $contents := index $model.result.Contents $e.Id -}} +
+
+ {{ $e.PreferredTitle }} +
+
+ {{- len $contents -}} +
+
+
+ {{- range $i, $c := $contents -}} + {{- $rels := index $model.result.ContentsAgents $c.Id -}} + {{- template "_content" Arr $c $e $rels $model.result.Agents false true + $model.parameters + -}} + {{- end -}} +
+ {{- end -}} +
+ {{- end -}} +
+ + + +
+ +{{- end -}} diff --git a/views/routes/suche/components/_fieldscript.gohtml b/views/routes/suche/components/_fieldscript.gohtml index fba9ed1..3b51ef6 100644 --- a/views/routes/suche/components/_fieldscript.gohtml +++ b/views/routes/suche/components/_fieldscript.gohtml @@ -14,4 +14,38 @@ } } }); + + const form = document.getElementById("simplesearchform"); + let submitBtn = null; + if (form) { + submitBtn = form.querySelector("#submitbutton"); + } + + function checkValidity(f, btn) { + if (f.checkValidity()) { + btn.disabled = false; + } else { + btn.disabled = true; + } + } + + if (form && submitBtn) { + checkValidity(form, submitBtn); + form.addEventListener("input", (event) => { + checkValidity(form, submitBtn); + }); + } + + const lookupform = document.getElementById("lookupform"); + let lookupsubmitBtn = null; + if (lookupform) { + lookupsubmitBtn = lookupform.querySelector("#submitbutton"); + } + + if (lookupform && lookupsubmitBtn) { + checkValidity(lookupform, lookupsubmitBtn); + lookupform.addEventListener("input", (event) => { + checkValidity(lookupform, lookupsubmitBtn); + }); + } diff --git a/views/routes/suche/components/_musenalmidbox.gohtml b/views/routes/suche/components/_musenalmidbox.gohtml new file mode 100644 index 0000000..370e33a --- /dev/null +++ b/views/routes/suche/components/_musenalmidbox.gohtml @@ -0,0 +1,21 @@ +{{ $p := index . 0 }} +{{ $t := index . 1 }} + + + + + diff --git a/views/transform/site.css b/views/transform/site.css index 5f5f027..016a6d5 100644 --- a/views/transform/site.css +++ b/views/transform/site.css @@ -46,6 +46,7 @@ @layer components { html { font-size: 16px; + scroll-behavior: auto !important; } @media (max-width: 1280px) { html { @@ -458,4 +459,8 @@ @apply bg-stone-100 pr-6 py-4; /*direction: rtl;*/ } + + .tab-list-head[aria-pressed="true"] { + @apply !text-slate-800 bg-stone-50; + } }