mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2025-10-28 16:55:32 +00:00
FTS5-Suche
This commit is contained in:
@@ -8,6 +8,8 @@ import (
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/helpers/datatypes"
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -94,6 +96,52 @@ var CONTENTS_FTS5_FIELDS = []string{
|
||||
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 {
|
||||
if len(fields) == 0 {
|
||||
return ""
|
||||
|
||||
@@ -6,6 +6,11 @@ import (
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/helpers/datatypes"
|
||||
)
|
||||
|
||||
type FTS5QueryRequest struct {
|
||||
Fields []string
|
||||
Query []string
|
||||
}
|
||||
|
||||
type FTS5IDQueryResult struct {
|
||||
ID string `db:"id"`
|
||||
}
|
||||
|
||||
@@ -51,7 +51,15 @@ func BasicSearchSeries(app core.App, query string) ([]*Series, []*Series, error)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/templating"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/tools/router"
|
||||
)
|
||||
|
||||
type DefaultPage struct {
|
||||
core.BaseRecordProxy
|
||||
type DefaultPage[T IPageCollection] struct {
|
||||
Record T
|
||||
Name string
|
||||
Template string
|
||||
Layout string
|
||||
URL string
|
||||
}
|
||||
|
||||
func NewDefaultPage(record *core.Record) *DefaultPage {
|
||||
i := &DefaultPage{}
|
||||
i.SetProxyRecord(record)
|
||||
return i
|
||||
}
|
||||
func (r *DefaultPage[T]) Up(app core.App, engine *templating.Engine) error {
|
||||
_, err := app.FindCollectionByNameOrId(GeneratePageTableName(r.Name))
|
||||
if err == sql.ErrNoRows {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
p.SetProxyRecord(record)
|
||||
p.Record.SetProxyRecord(record)
|
||||
data["record"] = p
|
||||
return engine.Response200(e, p.Template, data, p.Layout)
|
||||
})
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.SetProxyRecord(record)
|
||||
data["page"] = p
|
||||
p.Record.SetProxyRecord(record)
|
||||
data["page"] = p.Record
|
||||
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{}
|
||||
tn := GeneratePageTableName(p.Name)
|
||||
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) {
|
||||
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 (
|
||||
P_DATENSCHUTZ_NAME = "datenschutz"
|
||||
|
||||
P_SUCHE_NAME = "suche"
|
||||
|
||||
P_INDEX_NAME = "index"
|
||||
T_INDEX_BILDER = "bilder"
|
||||
T_INDEX_TEXTE = "texte"
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
func init() {
|
||||
ip := &IndexPage{
|
||||
DefaultPage: pagemodels.DefaultPage{
|
||||
DefaultPage: pagemodels.DefaultPage[*pagemodels.IndexTexte]{
|
||||
Name: pagemodels.P_INDEX_NAME,
|
||||
},
|
||||
}
|
||||
@@ -21,7 +21,7 @@ func init() {
|
||||
}
|
||||
|
||||
type IndexPage struct {
|
||||
pagemodels.DefaultPage
|
||||
pagemodels.DefaultPage[*pagemodels.IndexTexte]
|
||||
}
|
||||
|
||||
// TODO:
|
||||
|
||||
@@ -22,15 +22,19 @@ const (
|
||||
|
||||
func init() {
|
||||
rp := &ReihenPage{
|
||||
DefaultPage: pagemodels.DefaultPage{
|
||||
Name: pagemodels.P_REIHEN_NAME,
|
||||
DefaultPage: pagemodels.DefaultPage[*pagemodels.DefaultPageRecord]{
|
||||
Name: pagemodels.P_REIHEN_NAME,
|
||||
URL: URL_REIHEN,
|
||||
Template: URL_REIHEN,
|
||||
Layout: templating.DEFAULT_LAYOUT_NAME,
|
||||
Record: &pagemodels.DefaultPageRecord{},
|
||||
},
|
||||
}
|
||||
app.Register(rp)
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
@@ -23,6 +23,7 @@ func RegisterStaticPage(url, name string) {
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: mocve textpage to defaultpage with T = TextPageRecord
|
||||
func RegisterTextPage(url, name string) {
|
||||
app.Register(&pagemodels.TextPage{
|
||||
Name: name,
|
||||
@@ -33,10 +34,11 @@ func RegisterTextPage(url, name string) {
|
||||
}
|
||||
|
||||
func RegisterDefaultPage(url string, name string) {
|
||||
app.Register(&pagemodels.DefaultPage{
|
||||
app.Register(&pagemodels.DefaultPage[*pagemodels.DefaultPageRecord]{
|
||||
Name: name,
|
||||
Layout: templating.DEFAULT_LAYOUT_NAME,
|
||||
Template: 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
|
||||
- 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
|
||||
- Die URL referenziert die Almanachnummern, nicht mher die DB-IDs
|
||||
|
||||
- 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-Ansicht kann nach Inhalten frei gefiltert werden, oder nach Typ
|
||||
- In der Almanach- und Suchansicht werden Sammlungen abgehoben
|
||||
- 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
|
||||
- Suchergebnisse können nach Typ, Person, Jahr gefiltert werden
|
||||
- Suchergebnisse könnnen nach Jahr und Band, nach Band und Jahr (nach Personen) sortiert werden
|
||||
- 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;
|
||||
}
|
||||
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";
|
||||
i && i.remove(), this._tooltipBox = document.createElement("div"), this._tooltipBox.innerHTML = t, this._tooltipBox.className = [
|
||||
"opacity-0",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,6 @@
|
||||
{{ $model := . }}
|
||||
<div
|
||||
id="mainmenu"
|
||||
class="pb-1.5 border-b border-zinc-300"
|
||||
x-data="{ openeditionmenu: window.location.pathname.startsWith('/edition/')}">
|
||||
<div class="flex flex-row justify-between">
|
||||
@@ -13,7 +14,7 @@
|
||||
|
||||
<nav
|
||||
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
|
||||
href="/reihen/"
|
||||
{{ if and $model.page (HasPrefix $model.page.Path "/reihe") -}}
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
hx-boost="false">
|
||||
<i class="ri-links-line"></i
|
||||
></a>
|
||||
<div class="data-tip">Permalink zu dieser Suchanfrage</div>
|
||||
<div class="data-tip">Link zu dieser Suchanfrage</div>
|
||||
</tool-tip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
{{ $entries := index . 1 }}
|
||||
{{ $relations := index . 2 }}
|
||||
{{ $showidseries := index . 3 }}
|
||||
{{ $markar := index . 4 }}
|
||||
{{ $marka := index . 4 }}
|
||||
{{ $markr := index . 5 }}
|
||||
|
||||
{{ $bds := index $relations $r.Id }}
|
||||
|
||||
@@ -10,7 +11,7 @@
|
||||
<div class="flex flex-col lg:flex-row mb-1.5">
|
||||
<div class="grow-0 shrink-0 w-[12rem] flex flex-col">
|
||||
{{ 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 }}
|
||||
</div>
|
||||
{{ end }}
|
||||
@@ -37,7 +38,7 @@
|
||||
<span class="font-bold reihen-text">{{ $r.Title }}</span>
|
||||
{{ if $r.Annotation }}
|
||||
<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 }}
|
||||
</div>
|
||||
<div></div>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<h1>Die Seite konnte nicht gefunden werden!</h1>
|
||||
{{ if .Error }}
|
||||
<p>{{ .Error }}</p>
|
||||
{{ end }}
|
||||
<div class="container-normal">
|
||||
<h1>Die Seite konnte nicht gefunden werden!</h1>
|
||||
{{ if .Error }}
|
||||
<p>{{ .Error }}</p>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
@@ -13,16 +13,16 @@
|
||||
<img src="{{ .record.ImagePath }}" />
|
||||
</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">
|
||||
<div class="data-tip">Hinweis schließen</div>
|
||||
<button
|
||||
@click="open = false"
|
||||
class="text-xl p-2 text-stone-500 opacity-85 hover:opacity-100 transition-opacity
|
||||
duration-200 hover:text-stone-900
|
||||
class="text-3xl text-stone-500 opacity-85 hover:opacity-100 transition-opacity
|
||||
duration-200 hover:text-stone-900 leading-none
|
||||
hover:cursor-pointer">
|
||||
<i class="ri-close-circle-fill"></i>
|
||||
</button>
|
||||
<div class="data-tip">Hinweis schließen</div>
|
||||
</tool-tip>
|
||||
</div>
|
||||
</div>
|
||||
@@ -138,7 +138,7 @@
|
||||
{{ if and .search .idseries }}
|
||||
<div class="mb-1 max-w-[60rem] hyphens-auto">
|
||||
{{ 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 }}
|
||||
</div>
|
||||
{{ end }}
|
||||
@@ -146,7 +146,9 @@
|
||||
{{ if .series }}
|
||||
<div class="mb-1 max-w-[60rem] hyphens-auto">
|
||||
{{ 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 }}
|
||||
</div>
|
||||
{{ end }}
|
||||
@@ -171,11 +173,11 @@
|
||||
</div>
|
||||
{{ end }}
|
||||
<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 class="mb-1 max-w-[60rem] hyphens-auto">
|
||||
{{ 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 }}
|
||||
</div>
|
||||
{{ 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() {
|
||||
this.style.position = "relative";
|
||||
this.classList.add(
|
||||
"w-full",
|
||||
"h-full",
|
||||
"relative",
|
||||
"block",
|
||||
"leading-none",
|
||||
"[&>*]:leading-normal",
|
||||
);
|
||||
const dataTipElem = this.querySelector(".data-tip");
|
||||
const tipContent = dataTipElem ? dataTipElem.innerHTML : "Tooltip";
|
||||
|
||||
|
||||
@@ -73,23 +73,23 @@
|
||||
@apply ml-14 list-disc;
|
||||
}
|
||||
|
||||
nav > a {
|
||||
#mainmenu nav > a {
|
||||
@apply hover:!border-zinc-200;
|
||||
}
|
||||
|
||||
nav > * {
|
||||
#mainmenu nav > * {
|
||||
@apply border-b-4 border-transparent;
|
||||
}
|
||||
|
||||
nav > button[aria-current="true"] {
|
||||
#mainmenu nav > button[aria-current="true"] {
|
||||
@apply !bg-slate-200;
|
||||
}
|
||||
|
||||
nav a[aria-current="page"] {
|
||||
#mainmenu nav a[aria-current="page"] {
|
||||
@apply text-slate-800;
|
||||
}
|
||||
|
||||
nav a[aria-current="page"] {
|
||||
#mainmenu nav a[aria-current="page"] {
|
||||
@apply !border-zinc-300;
|
||||
}
|
||||
|
||||
@@ -197,4 +197,58 @@
|
||||
.indented p {
|
||||
@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