From 896d844cac976afd0ee8aa73dd2fb28e15e7ac79 Mon Sep 17 00:00:00 2001
From: Alan Pearce
Date: Tue, 18 Mar 2025 22:40:46 +0100
Subject: feat: Convert templ components to gomponents
---
internal/components/combined.go | 56 ++++++++++++
internal/components/combined.templ | 47 ----------
internal/components/data.go | 10 +++
internal/components/detail.go | 22 +++++
internal/components/detail.templ | 20 -----
internal/components/dev.go | 23 +++++
internal/components/dev.templ | 18 ----
internal/components/error.go | 17 ++++
internal/components/error.templ | 18 ----
internal/components/markdown.go | 15 ++++
internal/components/markdown.templ | 33 -------
internal/components/optionDetail.go | 71 +++++++++++++++
internal/components/optionDetail.templ | 58 ------------
internal/components/options.go | 50 +++++++++++
internal/components/options.templ | 45 ----------
internal/components/packageDetail.go | 120 +++++++++++++++++++++++++
internal/components/packageDetail.templ | 109 -----------------------
internal/components/packages.go | 53 +++++++++++
internal/components/packages.templ | 48 ----------
internal/components/page.go | 152 ++++++++++++++++++++++++++++++++
internal/components/page.templ | 123 --------------------------
internal/components/results.go | 65 ++++++++++++++
internal/components/results.templ | 64 --------------
internal/components/search.go | 63 +++++++++++++
internal/components/search.templ | 44 ---------
internal/nix/option.go | 17 ++++
internal/server/error.go | 4 +-
internal/server/mux.go | 12 ++-
28 files changed, 741 insertions(+), 636 deletions(-)
create mode 100644 internal/components/combined.go
delete mode 100644 internal/components/combined.templ
create mode 100644 internal/components/detail.go
delete mode 100644 internal/components/detail.templ
create mode 100644 internal/components/dev.go
delete mode 100644 internal/components/dev.templ
create mode 100644 internal/components/error.go
delete mode 100644 internal/components/error.templ
create mode 100644 internal/components/markdown.go
delete mode 100644 internal/components/markdown.templ
create mode 100644 internal/components/optionDetail.go
delete mode 100644 internal/components/optionDetail.templ
create mode 100644 internal/components/options.go
delete mode 100644 internal/components/options.templ
create mode 100644 internal/components/packageDetail.go
delete mode 100644 internal/components/packageDetail.templ
create mode 100644 internal/components/packages.go
delete mode 100644 internal/components/packages.templ
create mode 100644 internal/components/page.go
delete mode 100644 internal/components/page.templ
create mode 100644 internal/components/results.go
delete mode 100644 internal/components/results.templ
create mode 100644 internal/components/search.go
delete mode 100644 internal/components/search.templ
(limited to 'internal')
diff --git a/internal/components/combined.go b/internal/components/combined.go
new file mode 100644
index 0000000..d8c6fea
--- /dev/null
+++ b/internal/components/combined.go
@@ -0,0 +1,56 @@
+package components
+
+import (
+ "go.alanpearce.eu/searchix/internal/config"
+ "go.alanpearce.eu/searchix/internal/index"
+ "go.alanpearce.eu/searchix/internal/nix"
+
+ g "go.alanpearce.eu/gomponents"
+ . "go.alanpearce.eu/gomponents/html"
+)
+
+func CombinedData(data nix.Importable) g.Node {
+ switch data.(type) {
+ case nix.Option:
+ if o := convertMatch[nix.Option](data); o != nil {
+ return firstSentence(o.Description)
+ }
+ case nix.Package:
+ if p := convertMatch[nix.Package](data); p != nil {
+ return g.Text(firstSentence(p.Description))
+ }
+ }
+
+ return g.Text("")
+}
+
+func Combined(result *index.Result) g.Node {
+ return Table(
+ THead(
+ Tr(
+ Th(Scope("col"), g.Text("Attribute")),
+ Th(Scope("col"), g.Text("Description")),
+ g.If(config.DevMode,
+ Th(Scope("col"), g.Text("Score")),
+ ),
+ ),
+ ),
+ TBody(
+ g.Map(result.Hits, func(hit index.DocumentMatch) g.Node {
+ return Tr(
+ Td(
+ openCombinedDialogLink(nix.GetKey(hit.Data)),
+ ),
+ Td(
+ CombinedData(hit.Data),
+ ),
+ g.If(config.DevMode,
+ Td(
+ Score(hit),
+ ),
+ ),
+ )
+ }),
+ ),
+ )
+}
diff --git a/internal/components/combined.templ b/internal/components/combined.templ
deleted file mode 100644
index 3beddcd..0000000
--- a/internal/components/combined.templ
+++ /dev/null
@@ -1,47 +0,0 @@
-package components
-
-import (
- "go.alanpearce.eu/searchix/internal/config"
- "go.alanpearce.eu/searchix/internal/index"
- "go.alanpearce.eu/searchix/internal/nix"
-)
-
-templ Combined(result *index.Result) {
-
-
-
- Attribute |
- Description |
- if config.DevMode {
- Score |
- }
-
-
-
- for _, hit := range result.Hits {
-
-
- @openCombinedDialogLink(nix.GetKey(hit.Data))
- |
-
- switch hit.Data.(type) {
- case nix.Option:
- if o := convertMatch[nix.Option](hit.Data); o != nil {
- @markdown(firstSentence(o.Description))
- }
- case nix.Package:
- if o := convertMatch[nix.Package](hit.Data); o != nil {
- { firstSentence(o.Description) }
- }
- }
- |
- if config.DevMode {
-
- @score(hit)
- |
- }
-
- }
-
-
-}
diff --git a/internal/components/data.go b/internal/components/data.go
index c34dfb8..977b90e 100644
--- a/internal/components/data.go
+++ b/internal/components/data.go
@@ -4,6 +4,7 @@ import (
"go.alanpearce.eu/searchix/frontend"
"go.alanpearce.eu/searchix/internal/config"
search "go.alanpearce.eu/searchix/internal/index"
+ "go.alanpearce.eu/searchix/internal/nix"
)
type TemplateData struct {
@@ -24,3 +25,12 @@ type ResultData struct {
Next string
All string
}
+
+func convertMatch[I nix.Importable](m nix.Importable) *I {
+ i, ok := m.(I)
+ if !ok {
+ return nil
+ }
+
+ return &i
+}
diff --git a/internal/components/detail.go b/internal/components/detail.go
new file mode 100644
index 0000000..e6164fa
--- /dev/null
+++ b/internal/components/detail.go
@@ -0,0 +1,22 @@
+package components
+
+import (
+ "go.alanpearce.eu/searchix/internal/nix"
+
+ g "go.alanpearce.eu/gomponents"
+)
+
+func Detail(thing nix.Importable) g.Node {
+ switch t := thing.(type) {
+ case nix.Option:
+ return OptionDetail(t)
+ case nix.Package:
+ return PackageDetail(t)
+ default:
+ return nil
+ }
+}
+
+func DetailPage(tdata TemplateData, thing nix.Importable) g.Node {
+ return Page(tdata, Detail(thing))
+}
diff --git a/internal/components/detail.templ b/internal/components/detail.templ
deleted file mode 100644
index fa7206c..0000000
--- a/internal/components/detail.templ
+++ /dev/null
@@ -1,20 +0,0 @@
-package components
-
-import (
- "go.alanpearce.eu/searchix/internal/nix"
-)
-
-templ Detail(thing nix.Importable) {
- switch thing.(type) {
- case nix.Option:
- @OptionDetail(thing.(nix.Option))
- case nix.Package:
- @PackageDetail(thing.(nix.Package))
- }
-}
-
-templ DetailPage(tdata TemplateData, thing nix.Importable) {
- @Page(tdata) {
- @Detail(thing)
- }
-}
diff --git a/internal/components/dev.go b/internal/components/dev.go
new file mode 100644
index 0000000..3d77094
--- /dev/null
+++ b/internal/components/dev.go
@@ -0,0 +1,23 @@
+package components
+
+import (
+ "strconv"
+
+ "go.alanpearce.eu/searchix/internal/index"
+
+ g "go.alanpearce.eu/gomponents"
+ . "go.alanpearce.eu/gomponents/html"
+)
+
+func Score(h index.DocumentMatch) g.Node {
+ return g.Group([]g.Node{
+ A(
+ Class("open-sibling-dialog"),
+ g.Text(strconv.FormatFloat(h.Score, 'f', 2, 64)),
+ ),
+ Dialog(
+ Button(AutoFocus(), g.Text("Close")),
+ Pre(g.Text(h.Expl.String())),
+ ),
+ })
+}
diff --git a/internal/components/dev.templ b/internal/components/dev.templ
deleted file mode 100644
index a03eb4b..0000000
--- a/internal/components/dev.templ
+++ /dev/null
@@ -1,18 +0,0 @@
-package components
-
-import (
- "go.alanpearce.eu/searchix/internal/index"
- "strconv"
-)
-
-templ score(h index.DocumentMatch) {
-
- { strconv.FormatFloat(h.Score, 'f', 2, 64) }
-
-
-}
diff --git a/internal/components/error.go b/internal/components/error.go
new file mode 100644
index 0000000..fe34919
--- /dev/null
+++ b/internal/components/error.go
@@ -0,0 +1,17 @@
+package components
+
+import (
+ g "go.alanpearce.eu/gomponents"
+ . "go.alanpearce.eu/gomponents/html"
+)
+
+func Error(tdata TemplateData) g.Node {
+ return P(
+ Class("notice error"),
+ g.Textf("%d %s", tdata.Code, tdata.Message),
+ )
+}
+
+func ErrorPage(tdata TemplateData) g.Node {
+ return Page(tdata, Error(tdata))
+}
diff --git a/internal/components/error.templ b/internal/components/error.templ
deleted file mode 100644
index 8e45095..0000000
--- a/internal/components/error.templ
+++ /dev/null
@@ -1,18 +0,0 @@
-package components
-
-import (
- "strconv"
-)
-
-templ Error(tdata TemplateData) {
-
- { strconv.Itoa(tdata.Code) }
- { tdata.Message }
-
-}
-
-templ ErrorPage(tdata TemplateData) {
- @Page(tdata) {
- @Error(tdata)
- }
-}
diff --git a/internal/components/markdown.go b/internal/components/markdown.go
new file mode 100644
index 0000000..405ab52
--- /dev/null
+++ b/internal/components/markdown.go
@@ -0,0 +1,15 @@
+package components
+
+import (
+ "regexp"
+)
+
+var firstSentenceRegexp = regexp.MustCompile(`^.*?\.[[:space:]]`)
+
+func firstSentence[T ~string](text T) T {
+ if fs := firstSentenceRegexp.FindString(string(text)); fs != "" {
+ return T(fs)
+ }
+
+ return text
+}
diff --git a/internal/components/markdown.templ b/internal/components/markdown.templ
deleted file mode 100644
index 21b0aa0..0000000
--- a/internal/components/markdown.templ
+++ /dev/null
@@ -1,33 +0,0 @@
-package components
-
-import (
- "context"
- "io"
- "regexp"
-
- "github.com/yuin/goldmark"
- "github.com/yuin/goldmark/extension"
-
- "go.alanpearce.eu/searchix/internal/nix"
-)
-
-var (
- md = goldmark.New(
- goldmark.WithExtensions(extension.NewLinkify()),
- )
- firstSentenceRegexp = regexp.MustCompile(`^.*?\.[[:space:]]`)
-)
-
-func firstSentence[T ~string](text T) T {
- if fs := firstSentenceRegexp.FindString(string(text)); fs != "" {
- return T(fs)
- }
-
- return text
-}
-
-func markdown(text nix.Markdown) templ.Component {
- return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
- return md.Convert([]byte(text), w)
- })
-}
diff --git a/internal/components/optionDetail.go b/internal/components/optionDetail.go
new file mode 100644
index 0000000..d5f0c24
--- /dev/null
+++ b/internal/components/optionDetail.go
@@ -0,0 +1,71 @@
+package components
+
+import (
+ "go.alanpearce.eu/searchix/internal/nix"
+
+ g "go.alanpearce.eu/gomponents"
+ . "go.alanpearce.eu/gomponents/html"
+)
+
+func OptionDetail(option nix.Option) g.Node {
+ return g.Group([]g.Node{
+ H2(g.Text(option.Name)),
+ option.Description,
+ Dl(
+ g.If(option.Type != "",
+ g.Group([]g.Node{
+ Dt(g.Text("Type")),
+ Dd(Code(g.Text(option.Type))),
+ }),
+ ),
+ g.Iff(option.Default != nil,
+ func() g.Node {
+ return g.Group([]g.Node{
+ Dt(g.Text("Default")),
+ Dd(
+ g.If(option.Default.Markdown != "",
+ option.Default.Markdown,
+ Pre(Code(g.Text(option.Default.Text))),
+ ),
+ ),
+ })
+ },
+ ),
+ g.Iff(option.Example != nil,
+ func() g.Node {
+ return g.Group([]g.Node{
+ Dt(g.Text("Example")),
+ Dd(
+ g.If(option.Example.Markdown != "",
+ option.Example.Markdown,
+ Pre(Code(g.Text(option.Example.Text))),
+ ),
+ ),
+ })
+ },
+ ),
+ g.If(option.RelatedPackages != "",
+ g.Group([]g.Node{
+ Dt(g.Text("Related Packages")),
+ Dd(
+ option.RelatedPackages,
+ ),
+ }),
+ ),
+ g.If(len(option.Declarations) > 0,
+ g.Group([]g.Node{
+ Dt(g.Text("Declared")),
+ g.Map(option.Declarations, func(d nix.Link) g.Node {
+ return Dd(
+ A(Href(d.URL), g.Text(d.Name)),
+ )
+ }),
+ }),
+ ),
+ ),
+ })
+}
+
+func OptionDetailPage(tdata TemplateData, option nix.Option) g.Node {
+ return Page(tdata, OptionDetail(option))
+}
diff --git a/internal/components/optionDetail.templ b/internal/components/optionDetail.templ
deleted file mode 100644
index 6eaafb4..0000000
--- a/internal/components/optionDetail.templ
+++ /dev/null
@@ -1,58 +0,0 @@
-package components
-
-import "go.alanpearce.eu/searchix/internal/nix"
-
-templ OptionDetail(option nix.Option) {
- { option.Name }
- @markdown(option.Description)
-
- if option.Type != "" {
- - Type
- { option.Type }
- }
- if option.Default != nil {
- if option.Default.Text != "" || option.Default.Markdown != "" {
- - Default
- -
- if option.Default.Markdown != "" {
- @markdown(option.Default.Markdown)
- } else {
-
{ option.Default.Text }
- }
-
- }
- }
- if option.Example != nil {
- if option.Example.Text != "" || option.Example.Markdown != "" {
- - Example
- -
- if option.Example.Markdown != "" {
- @markdown(option.Example.Markdown)
- } else {
-
{ option.Example.Text }
- }
-
- }
- }
- if option.RelatedPackages != "" {
- - Related Packages
- -
- @markdown(option.RelatedPackages)
-
- }
- if len(option.Declarations) > 0 {
- - Declared
- for _, d := range option.Declarations {
- -
- { d.Name }
-
- }
- }
-
-}
-
-templ OptionDetailPage(tdata TemplateData, option nix.Option) {
- @Page(tdata) {
- @OptionDetail(option)
- }
-}
diff --git a/internal/components/options.go b/internal/components/options.go
new file mode 100644
index 0000000..af6c73f
--- /dev/null
+++ b/internal/components/options.go
@@ -0,0 +1,50 @@
+package components
+
+import (
+ "go.alanpearce.eu/searchix/internal/config"
+ "go.alanpearce.eu/searchix/internal/index"
+ "go.alanpearce.eu/searchix/internal/nix"
+
+ g "go.alanpearce.eu/gomponents"
+ . "go.alanpearce.eu/gomponents/html"
+)
+
+func Options(result *index.Result) g.Node {
+ return Table(
+ THead(
+ Tr(
+ Th(Scope("col"), g.Text("Title")),
+ Th(Scope("col"), g.Text("Description")),
+ g.If(config.DevMode,
+ Th(Scope("col"), g.Text("Score")),
+ ),
+ ),
+ ),
+ TBody(
+ g.Map(result.Hits, func(hit index.DocumentMatch) g.Node {
+ if m := convertMatch[nix.Option](hit.Data); m != nil {
+ return optionRow(hit, *m)
+ }
+
+ return nil
+ }),
+ ),
+ )
+}
+
+func optionRow(hit index.DocumentMatch, o nix.Option) g.Node {
+ return Tr(
+ Td(
+ openDialogLink(o.Name),
+ ),
+ Td(
+ firstSentence(o.Description),
+ Dialog(ID(o.Name)),
+ ),
+ g.If(config.DevMode,
+ Td(
+ Score(hit),
+ ),
+ ),
+ )
+}
diff --git a/internal/components/options.templ b/internal/components/options.templ
deleted file mode 100644
index 097f66f..0000000
--- a/internal/components/options.templ
+++ /dev/null
@@ -1,45 +0,0 @@
-package components
-
-import (
- "go.alanpearce.eu/searchix/internal/config"
- "go.alanpearce.eu/searchix/internal/index"
- "go.alanpearce.eu/searchix/internal/nix"
-)
-
-templ Options(result *index.Result) {
-
-
-
- Title |
- Description |
- if config.DevMode {
- Score |
- }
-
-
-
- for _, hit := range result.Hits {
- if m := convertMatch[nix.Option](hit.Data); m != nil {
- @optionRow(hit, *m)
- }
- }
-
-
-}
-
-templ optionRow(hit index.DocumentMatch, o nix.Option) {
-
-
- @openDialogLink(o.Name)
- |
-
- @markdown(firstSentence(o.Description))
-
- |
- if config.DevMode {
-
- @score(hit)
- |
- }
-
-}
diff --git a/internal/components/packageDetail.go b/internal/components/packageDetail.go
new file mode 100644
index 0000000..01b1f4d
--- /dev/null
+++ b/internal/components/packageDetail.go
@@ -0,0 +1,120 @@
+package components
+
+import (
+ "go.alanpearce.eu/searchix/internal/nix"
+
+ g "go.alanpearce.eu/gomponents"
+ . "go.alanpearce.eu/gomponents/html"
+)
+
+func licenseName(l nix.License) string {
+ if l.FullName != "" {
+ return l.FullName
+ }
+
+ return l.Name
+}
+
+func PackageDetail(pkg nix.Package) g.Node {
+ return g.Group([]g.Node{
+ H2(
+ g.If(pkg.Broken,
+ Del(g.Text(pkg.Attribute)),
+ g.Text(pkg.Attribute),
+ ),
+ ),
+ g.If(pkg.LongDescription != "",
+ pkg.LongDescription,
+ P(g.Text(pkg.Description)),
+ ),
+ Dl(
+ g.If(pkg.MainProgram != "",
+ g.Group([]g.Node{
+ Dt(g.Text("Main Program")),
+ Dd(Code(g.Text(pkg.MainProgram))),
+ }),
+ ),
+ g.If(len(pkg.Programs) > 0,
+ g.Group([]g.Node{
+ Dt(g.Text("Programs")),
+ Dd(
+ Ul(
+ g.Map(pkg.Programs, func(p string) g.Node {
+ return Li(Code(g.Text(p)))
+ }),
+ ),
+ ),
+ }),
+ ),
+ g.If(len(pkg.Homepages) > 0,
+ g.Group([]g.Node{
+ Dt(g.Text("Homepage")),
+ Dd(
+ Ul(
+ g.Map(pkg.Homepages, func(u string) g.Node {
+ return Li(A(Href(u), g.Text(u)))
+ }),
+ ),
+ ),
+ }),
+ ),
+ g.If(pkg.Version != "",
+ g.Group([]g.Node{
+ Dt(g.Text("Version")),
+ Dd(g.Text(pkg.Version)),
+ }),
+ ),
+ g.If(len(pkg.Licenses) > 0,
+ g.Group([]g.Node{
+ Dt(g.Text("License")),
+ Dd(
+ Ul(
+ g.Map(pkg.Licenses, func(l nix.License) g.Node {
+ return Li(
+ g.If(l.URL != "",
+ A(Href(l.URL), g.Text(licenseName(l))),
+ g.Text(licenseName(l)),
+ ),
+ g.If(l.AppendixURL != "",
+ A(Href(l.AppendixURL), g.Text("Appendix")),
+ ),
+ )
+ }),
+ ),
+ ),
+ }),
+ ),
+ g.If(len(pkg.Maintainers) > 0,
+ g.Group([]g.Node{
+ Dt(g.Text("Maintainers")),
+ Dd(
+ Ul(
+ g.Map(pkg.Maintainers, func(m nix.Maintainer) g.Node {
+ return Li(
+ g.If(
+ m.Github != "",
+ A(
+ Href(joinPath("https://github.com", m.Github)),
+ g.Text(m.Name),
+ ),
+ g.Text(m.Name),
+ ),
+ )
+ }),
+ ),
+ ),
+ }),
+ ),
+ g.If(pkg.Definition != "",
+ g.Group([]g.Node{
+ Dt(g.Text("Defined")),
+ Dd(A(Href(pkg.Definition), g.Text("Source"))),
+ }),
+ ),
+ ),
+ })
+}
+
+func PackageDetailPage(tdata TemplateData, pkg nix.Package) g.Node {
+ return Page(tdata, PackageDetail(pkg))
+}
diff --git a/internal/components/packageDetail.templ b/internal/components/packageDetail.templ
deleted file mode 100644
index 84d2bdf..0000000
--- a/internal/components/packageDetail.templ
+++ /dev/null
@@ -1,109 +0,0 @@
-package components
-
-import "go.alanpearce.eu/searchix/internal/nix"
-
-func licenseName(l nix.License) string {
- if l.FullName != "" {
- return l.FullName
- } else {
- return l.Name
- }
-}
-
-templ PackageDetail(pkg nix.Package) {
-
- if pkg.Broken {
- { pkg.Attribute }
- } else {
- { pkg.Attribute }
- }
-
- if pkg.LongDescription != "" {
- @markdown(pkg.LongDescription)
- } else {
- { pkg.Description }
- }
-
- if pkg.MainProgram != "" {
- - Main Program
- -
-
{ pkg.MainProgram }
-
- }
- if len(pkg.Programs) > 0 {
- - Programs
- -
-
- for _, p := range pkg.Programs {
- -
-
{ p }
-
- }
-
-
- }
- if len(pkg.Homepages) > 0 {
- - Homepage
- -
-
- for _, u := range pkg.Homepages {
- -
- { u }
-
- }
-
-
- }
- if pkg.Version != "" {
- - Version
- - { pkg.Version }
- }
- if len(pkg.Licenses) > 0 {
- - License
- -
-
- for _, l := range pkg.Licenses {
- -
- if l.URL != "" {
- { licenseName(l) }
- } else {
- { licenseName(l) }
- }
- if l.AppendixURL != "" {
- Appendix
- }
-
- }
-
-
- }
- if len(pkg.Maintainers) > 0 {
- - Maintainers
- -
-
- for _, m := range pkg.Maintainers {
- -
- if m.Github != "" {
- { m.Name }
- } else {
- { m.Name }
- }
-
- }
-
-
- }
- if pkg.Definition != "" {
- - Defined
- -
- Source
-
- }
-
-}
-
-templ PackageDetailPage(tdata TemplateData, pkg nix.Package) {
- @Page(tdata) {
- @PackageDetail(pkg)
- }
-}
diff --git a/internal/components/packages.go b/internal/components/packages.go
new file mode 100644
index 0000000..9bc3f99
--- /dev/null
+++ b/internal/components/packages.go
@@ -0,0 +1,53 @@
+package components
+
+import (
+ "go.alanpearce.eu/searchix/internal/config"
+ "go.alanpearce.eu/searchix/internal/index"
+ "go.alanpearce.eu/searchix/internal/nix"
+
+ g "go.alanpearce.eu/gomponents"
+ . "go.alanpearce.eu/gomponents/html"
+)
+
+func Packages(result *index.Result) g.Node {
+ return Table(
+ THead(
+ Tr(
+ Th(Scope("col"), g.Text("Attribute")),
+ Th(Scope("col"), g.Text("Name")),
+ Th(Scope("col"), g.Text("Description")),
+ g.If(config.DevMode,
+ Th(Scope("col"), g.Text("Score")),
+ ),
+ ),
+ ),
+ TBody(
+ g.Map(result.Hits, func(hit index.DocumentMatch) g.Node {
+ if m := convertMatch[nix.Package](hit.Data); m != nil {
+ return packageRow(hit, *m)
+ }
+
+ return nil
+ }),
+ ),
+ )
+}
+
+func packageRow(hit index.DocumentMatch, p nix.Package) g.Node {
+ return Tr(
+ Td(
+ openDialogLink(p.Attribute),
+ ),
+ Td(
+ g.Text(p.Name),
+ ),
+ Td(
+ g.Text(p.Description),
+ ),
+ g.If(config.DevMode,
+ Td(
+ Score(hit),
+ ),
+ ),
+ )
+}
diff --git a/internal/components/packages.templ b/internal/components/packages.templ
deleted file mode 100644
index 6e14026..0000000
--- a/internal/components/packages.templ
+++ /dev/null
@@ -1,48 +0,0 @@
-package components
-
-import (
- "go.alanpearce.eu/searchix/internal/config"
- "go.alanpearce.eu/searchix/internal/index"
- "go.alanpearce.eu/searchix/internal/nix"
-)
-
-templ Packages(result *index.Result) {
-
-
-
- Attribute |
- Name |
- Description |
- if config.DevMode {
- Score |
- }
-
-
-
- for _, hit := range result.Hits {
- if m := convertMatch[nix.Package](hit.Data); m != nil {
- @packageRow(hit, *m)
- }
- }
-
-
-}
-
-templ packageRow(hit index.DocumentMatch, p nix.Package) {
-
-
- @openDialogLink(p.Attribute)
- |
-
- { p.Name }
- |
-
- { p.Description }
- |
- if config.DevMode {
-
- @score(hit)
- |
- }
-
-}
diff --git a/internal/components/page.go b/internal/components/page.go
new file mode 100644
index 0000000..5cfa4ff
--- /dev/null
+++ b/internal/components/page.go
@@ -0,0 +1,152 @@
+package components
+
+import (
+ "net/url"
+
+ "go.alanpearce.eu/searchix/frontend"
+ "go.alanpearce.eu/searchix/internal/config"
+
+ g "go.alanpearce.eu/gomponents"
+ c "go.alanpearce.eu/gomponents/components"
+ . "go.alanpearce.eu/gomponents/html"
+)
+
+func Page(tdata TemplateData, children ...g.Node) g.Node {
+ return Doctype(
+ HTML(
+ Lang("en-GB"),
+ Head(
+ Meta(Charset("utf-8")),
+ Meta(Name("viewport"), Content("width=device-width, initial-scale=1")),
+ TitleEl(g.Text("Searchix"), g.If(config.DevMode, g.Text(" (Dev)"))),
+ g.Map(tdata.Assets.Stylesheets, css),
+ g.Raw(tdata.ExtraHeadHTML),
+ Link(
+ Rel("search"),
+ Type("application/opensearchdescription+xml"),
+ TitleAttr("Searchix "+sourceNameAndType(nil)),
+ Href(joinPath("opensearch.xml")),
+ ),
+ g.Map(tdata.Sources, func(source *config.Source) g.Node {
+ return Link(
+ Rel("search"),
+ Type("application/opensearchdescription+xml"),
+ TitleAttr("Searchix "+sourceNameAndType(source)),
+ Href(joinPath("/", source.Importer.String(), source.Key, "opensearch.xml")),
+ )
+ }),
+ ),
+ Body(
+ Header(
+ Nav(
+ H1(A(Href("/"), g.Text("Searchix"))),
+ A(
+ c.Classes{
+ "current": tdata.Source == nil,
+ },
+ g.If(
+ tdata.Source == nil,
+ Href("/"),
+ Href(joinPathQuery("/", tdata.Query)),
+ ),
+ g.Text("All"),
+ ),
+ g.Map(tdata.Sources, func(source *config.Source) g.Node {
+ if tdata.Source != nil && tdata.Source.Name == source.Name {
+ return A(
+ Class("current"),
+ Href(
+ joinPath(
+ "/",
+ source.Importer.String(),
+ source.Key,
+ "search",
+ ),
+ ),
+ g.Text(source.Name),
+ )
+ }
+
+ return A(
+ Href(
+ joinPathQuery(
+ joinPath(
+ "/",
+ source.Importer.String(),
+ source.Key,
+ "search",
+ ),
+ tdata.Query,
+ ),
+ ),
+ g.Text(source.Name),
+ )
+ }),
+ ),
+ ),
+ Main(children...),
+ Footer(
+ g.If(config.Version != "",
+ g.Group([]g.Node{
+ g.Text("Searchix "),
+ A(
+ Href("https://git.sr.ht/~alanpearce/searchix/refs/"+config.Version),
+ g.Text(config.Version),
+ ),
+ }),
+ ),
+ g.Text("Made by "),
+ A(Href("https://alanpearce.eu"), g.Text("Alan Pearce")),
+ g.Text(". "),
+ A(Href("https://git.sr.ht/~alanpearce/searchix"), g.Text("Source code")),
+ A(Href("https://todo.sr.ht/~alanpearce/searchix"), g.Text("Report issues")),
+ ),
+ ),
+ ),
+ )
+}
+
+func css(css *frontend.Asset) g.Node {
+ return Link(Href(css.URL), Rel("stylesheet"),
+ Integrity("sha256-"+css.Base64SHA256))
+}
+
+func script(s *frontend.Asset) g.Node {
+ return Script(
+ Src(s.URL),
+ Defer(),
+ g.Attr("integrity", "sha256-"+s.Base64SHA256),
+ )
+}
+
+func sourceNameAndType(source *config.Source) string {
+ if source == nil {
+ return "Combined"
+ }
+
+ switch source.Importer {
+ case config.Options:
+ return source.Name + " " + source.Importer.String()
+ case config.Packages:
+ return source.Name
+ }
+
+ return ""
+}
+
+func joinPath(base string, parts ...string) string {
+ u, err := url.JoinPath(base, parts...)
+ if err != nil {
+ panic(err)
+ }
+
+ return u
+}
+
+func joinPathQuery(path string, query string) string {
+ if query == "" {
+ return path
+ }
+
+ return path + "?query=" + url.QueryEscape(query)
+}
diff --git a/internal/components/page.templ b/internal/components/page.templ
deleted file mode 100644
index 62b2937..0000000
--- a/internal/components/page.templ
+++ /dev/null
@@ -1,123 +0,0 @@
-package components
-
-import (
- "context"
- "io"
- "net/url"
-
- "go.alanpearce.eu/searchix/frontend"
- "go.alanpearce.eu/searchix/internal/config"
-)
-
-templ Page(tdata TemplateData) {
-
-
-
-
-
-
- Searchix
- if config.DevMode {
- (Dev)
- }
-
- for _, sheet := range tdata.Assets.Stylesheets {
-
- }
- @Unsafe(tdata.ExtraHeadHTML)
-
- for _, source := range tdata.Sources {
-
- }
-
-
-
-
- { children... }
-
-
-
-
-}
-
-templ script(s *frontend.Asset) {
-
-}
-
-func Unsafe(html string) templ.Component {
- return templ.ComponentFunc(func(_ context.Context, w io.Writer) (err error) {
- _, err = io.WriteString(w, html)
- return
- })
-}
-
-func sourceNameAndType(source *config.Source) string {
- if source == nil {
- return "Combined"
- }
-
- switch source.Importer {
- case config.Options:
- return source.Name + " " + source.Importer.String()
- case config.Packages:
- return source.Name
- }
- return ""
-}
-
-func joinPath(base string, parts ...string) templ.SafeURL {
- u, err := url.JoinPath(base, parts...)
- if err != nil {
- panic(err)
- }
- return templ.SafeURL(u)
-}
-
-func joinPathQuery[T ~string](path T, query string) templ.SafeURL {
- if query == "" {
- return templ.SafeURL(path)
- }
- return templ.SafeURL(string(path) + "?query=" + url.QueryEscape(query))
-}
diff --git a/internal/components/results.go b/internal/components/results.go
new file mode 100644
index 0000000..4f07a78
--- /dev/null
+++ b/internal/components/results.go
@@ -0,0 +1,65 @@
+package components
+
+import (
+ "go.alanpearce.eu/searchix/internal/config"
+
+ g "go.alanpearce.eu/gomponents"
+ . "go.alanpearce.eu/gomponents/html"
+)
+
+func Results(r ResultData) g.Node {
+ if r.Query == "" {
+ return Br()
+ }
+
+ if r.Results == nil || r.Results.Total == 0 {
+ return Span(Role("status"), g.Text("Nothing found"))
+ }
+
+ var content g.Node
+ if r.Source != nil {
+ switch r.Source.Importer {
+ case config.Options:
+ content = Options(r.Results)
+ case config.Packages:
+ content = Packages(r.Results)
+ }
+ } else {
+ content = Combined(r.Results)
+ }
+
+ return g.Group([]g.Node{
+ content,
+ Footer(
+ g.Attr("aria-label", "pagination"),
+ Nav(
+ ID("pagination"),
+ g.If(r.Prev != "",
+ A(Class("button"), Href(r.Prev), Rel("prev"), g.Text("Prev")),
+ ),
+ g.If(r.Next != "",
+ A(Class("button"), Href(r.Next), Rel("next"), g.Text("Next")),
+ ),
+ ),
+ Span(
+ Role("status"),
+ g.Textf("%d results", r.Results.Total),
+ ),
+ g.If(r.Next != r.Prev && r.Results.Total < config.MaxResultsShowAll,
+ A(Href(r.All), g.Text("Show All")),
+ ),
+ ),
+ })
+}
+
+func ResultsPage(r ResultData) g.Node {
+ return SearchPage(r.TemplateData, r, Results(r))
+}
+
+func openDialogLink(attr string) g.Node {
+ return A(Class("open-dialog"), Href(attr), g.Text(attr))
+}
+
+func openCombinedDialogLink(attr string) g.Node {
+ return A(Class("open-dialog"), Href("/"+attr), g.Text(attr))
+}
diff --git a/internal/components/results.templ b/internal/components/results.templ
deleted file mode 100644
index fee211c..0000000
--- a/internal/components/results.templ
+++ /dev/null
@@ -1,64 +0,0 @@
-package components
-
-import (
- "go.alanpearce.eu/searchix/internal/config"
- "go.alanpearce.eu/searchix/internal/nix"
- "strconv"
-)
-
-func convertMatch[I nix.Importable](m nix.Importable) *I {
- i, ok := m.(I)
- if !ok {
- return nil
- }
- return &i
-}
-
-templ Results(r ResultData) {
- if r.Query != "" {
- if r.Results != nil && r.Results.Total > 0 {
- if r.Source != nil {
- switch r.Source.Importer {
- case config.Options:
- @Options(r.Results)
- case config.Packages:
- @Packages(r.Results)
- }
- } else {
- @Combined(r.Results)
- }
-
- } else {
- Nothing found
- }
- } else {
-
- }
-}
-
-templ ResultsPage(r ResultData) {
- @SearchPage(r.TemplateData, r) {
- @Results(r)
- }
-}
-
-templ openDialogLink(attr string) {
- { attr }
-}
-
-templ openCombinedDialogLink(attr string) {
- { attr }
-}
diff --git a/internal/components/search.go b/internal/components/search.go
new file mode 100644
index 0000000..0a7c991
--- /dev/null
+++ b/internal/components/search.go
@@ -0,0 +1,63 @@
+package components
+
+import (
+ g "go.alanpearce.eu/gomponents"
+ . "go.alanpearce.eu/gomponents/html"
+)
+
+func Search(tdata TemplateData, r ResultData) g.Node {
+ return Form(
+ ID("search"),
+ Role("search"),
+ FieldSet(
+ Legend(
+ ID("legend"),
+ H2(g.Textf("%s search", sourceNameAndType(tdata.Source))),
+ ),
+ Input(
+ ID("query"),
+ Aria("labelledby", "legend"),
+ Name("query"),
+ Type("search"),
+ Value(r.Query),
+ AutoFocus(),
+ g.Attr("spellcheck", "false"),
+ g.Attr("autocapitalize", "none"),
+ ),
+ Button(g.Text("Search")),
+ ),
+ )
+}
+
+func SearchPage(tdata TemplateData, r ResultData, children ...g.Node) g.Node {
+ return Page(
+ tdata,
+ P(
+ g.Text("Search Nix packages and options from "),
+ A(Href("https://nixos.org"), g.Text("NixOS")),
+ g.Text(", "),
+ A(Href("https://github.com/LnL7/nix-darwin"), g.Text("nix-darwin")),
+ g.Text(" and "),
+ A(Href("https://github.com/nix-community/home-manager"), g.Text("home-manager")),
+ ),
+ script(tdata.Assets.ByPath["/static/search.js"]),
+ Search(tdata, r),
+ Section(
+ ID("results"),
+ Role("list"),
+ Aria("label", "search results"),
+ g.Group(children),
+ ),
+ Dialog(
+ ID("dialog"),
+ Button(AutoFocus(), g.Text("Close")),
+ ),
+ NoScript(
+ P(
+ Class("notice"),
+ g.Text("Everything should work fine without JavaScript. If that is not the case, "),
+ A(Href("https://todo.sr.ht/~alanpearce/searchix"), g.Text("report an issue")),
+ ),
+ ),
+ )
+}
diff --git a/internal/components/search.templ b/internal/components/search.templ
deleted file mode 100644
index 7076772..0000000
--- a/internal/components/search.templ
+++ /dev/null
@@ -1,44 +0,0 @@
-package components
-
-templ Search(tdata TemplateData, r ResultData) {
-
-}
-
-templ SearchPage(tdata TemplateData, r ResultData) {
- @Page(tdata) {
-
- Search Nix packages and options from NixOS, nix-darwin
- and home-manager
-
- @script(tdata.Assets.ByPath["/static/search.js"])
- @Search(tdata, r)
-
-
-
- }
-}
diff --git a/internal/nix/option.go b/internal/nix/option.go
index c1cc4c3..96c4546 100644
--- a/internal/nix/option.go
+++ b/internal/nix/option.go
@@ -1,5 +1,13 @@
package nix
+import (
+ "io"
+
+ "github.com/yuin/goldmark"
+ "github.com/yuin/goldmark/extension"
+ "gitlab.com/tozd/go/errors"
+)
+
type Markdown string
type Value struct {
@@ -35,3 +43,12 @@ func (p Option) GetName() string {
func (p Option) GetSource() string {
return p.Source
}
+
+var md = goldmark.New(
+ goldmark.WithExtensions(extension.NewLinkify()),
+)
+
+// implements gomponent.Node
+func (text Markdown) Render(w io.Writer) error {
+ return errors.WithStack(md.Convert([]byte(text), w))
+}
diff --git a/internal/server/error.go b/internal/server/error.go
index c2acf48..b51cfa9 100644
--- a/internal/server/error.go
+++ b/internal/server/error.go
@@ -29,9 +29,9 @@ func createErrorHandler(
w.Header().Del("Vary")
w.WriteHeader(code)
if r.Header.Get("Fetch") == "true" {
- err = components.Error(indexData).Render(r.Context(), w)
+ err = components.Error(indexData).Render(w)
} else {
- err = components.ErrorPage(indexData).Render(r.Context(), w)
+ err = components.ErrorPage(indexData).Render(w)
}
if err != nil {
log.Error(
diff --git a/internal/server/mux.go b/internal/server/mux.go
index 1507860..2bbff8e 100644
--- a/internal/server/mux.go
+++ b/internal/server/mux.go
@@ -44,8 +44,6 @@ func applyDevModeOverrides(cfg *config.Config) {
}
cfg.Web.ContentSecurityPolicy.ScriptSrc = append(
cfg.Web.ContentSecurityPolicy.ScriptSrc,
- cfg.Web.BaseURL.JoinPath("_templ/reload/script.js").String(),
- "http://localhost:7331",
"'unsafe-inline'",
)
}
@@ -174,9 +172,9 @@ func NewMux(
var baseErr error
if r.Header.Get("Fetch") == "true" {
w.Header().Add("Content-Type", "text/html; charset=utf-8")
- baseErr = components.Results(tdata).Render(r.Context(), w)
+ baseErr = components.Results(tdata).Render(w)
} else {
- baseErr = components.ResultsPage(tdata).Render(r.Context(), w)
+ baseErr = components.ResultsPage(tdata).Render(w)
}
if baseErr != nil {
log.Error("template error", "template", importerType, "error", baseErr)
@@ -192,7 +190,7 @@ func NewMux(
Assets: frontend.Assets,
},
components.ResultData{},
- ).Render(r.Context(), w)
+ ).Render(w)
if err != nil {
errorHandler(w, r, err.Error(), http.StatusInternalServerError)
@@ -248,9 +246,9 @@ func NewMux(
var baseErr error
if r.Header.Get("Fetch") == "true" {
w.Header().Add("Content-Type", "text/html; charset=utf-8")
- baseErr = components.Detail(*doc).Render(r.Context(), w)
+ baseErr = components.Detail(*doc).Render(w)
} else {
- baseErr = components.DetailPage(tdata, *doc).Render(r.Context(), w)
+ baseErr = components.DetailPage(tdata, *doc).Render(w)
}
if baseErr != nil {
log.Error("template error", "template", importerSingular, "error", baseErr)
--
cgit 1.4.1