HTMX + Deps Update
This commit is contained in:
parent
38dc30fb0f
commit
44c84ced2e
17 changed files with 470 additions and 193 deletions
18
go.mod
18
go.mod
|
@ -3,7 +3,7 @@ module goth.stack
|
||||||
go 1.23
|
go 1.23
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alecthomas/chroma/v2 v2.14.0
|
github.com/alecthomas/chroma/v2 v2.15.0
|
||||||
github.com/swaggo/echo-swagger v1.4.1
|
github.com/swaggo/echo-swagger v1.4.1
|
||||||
github.com/swaggo/swag v1.16.4
|
github.com/swaggo/swag v1.16.4
|
||||||
golang.org/x/image v0.23.0
|
golang.org/x/image v0.23.0
|
||||||
|
@ -21,14 +21,14 @@ require (
|
||||||
github.com/labstack/gommon v0.4.2 // indirect
|
github.com/labstack/gommon v0.4.2 // indirect
|
||||||
github.com/mailru/easyjson v0.9.0 // indirect
|
github.com/mailru/easyjson v0.9.0 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||||
github.com/swaggo/files/v2 v2.0.1 // indirect
|
github.com/swaggo/files/v2 v2.0.2 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
golang.org/x/net v0.33.0 // indirect
|
golang.org/x/net v0.34.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.21.0 // indirect
|
||||||
golang.org/x/time v0.8.0 // indirect
|
golang.org/x/time v0.9.0 // indirect
|
||||||
golang.org/x/tools v0.28.0 // indirect
|
golang.org/x/tools v0.29.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -36,12 +36,12 @@ require (
|
||||||
github.com/gookit/color v1.5.4
|
github.com/gookit/color v1.5.4
|
||||||
github.com/gorilla/feeds v1.2.0
|
github.com/gorilla/feeds v1.2.0
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/labstack/echo/v4 v4.13.2
|
github.com/labstack/echo/v4 v4.13.3
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/yuin/goldmark v1.7.8
|
github.com/yuin/goldmark v1.7.8
|
||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||||
golang.org/x/crypto v0.31.0 // indirect
|
golang.org/x/crypto v0.32.0 // indirect
|
||||||
golang.org/x/sys v0.28.0 // indirect
|
golang.org/x/sys v0.29.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
)
|
)
|
||||||
|
|
42
go.sum
42
go.sum
|
@ -1,10 +1,10 @@
|
||||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||||
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
|
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||||
github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||||
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
||||||
github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
|
github.com/alecthomas/chroma/v2 v2.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc=
|
||||||
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
|
github.com/alecthomas/chroma/v2 v2.15.0/go.mod h1:gUhVLrPDXPtp/f+L1jo9xepo9gL4eLwRuGAunSZMkio=
|
||||||
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||||
|
@ -39,15 +39,14 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/labstack/echo/v4 v4.13.2 h1:9aAt4hstpH54qIcqkuUXRLTf+v7yOTfMPWzDtuqLmtA=
|
github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
|
||||||
github.com/labstack/echo/v4 v4.13.2/go.mod h1:uc9gDtHB8UWt3FfbYx0HyxcCuvR4YuPYOxF/1QjoV/c=
|
github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
|
||||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
@ -60,8 +59,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/swaggo/echo-swagger v1.4.1 h1:Yf0uPaJWp1uRtDloZALyLnvdBeoEL5Kc7DtnjzO/TUk=
|
github.com/swaggo/echo-swagger v1.4.1 h1:Yf0uPaJWp1uRtDloZALyLnvdBeoEL5Kc7DtnjzO/TUk=
|
||||||
github.com/swaggo/echo-swagger v1.4.1/go.mod h1:C8bSi+9yH2FLZsnhqMZLIZddpUxZdBYuNHbtaS1Hljc=
|
github.com/swaggo/echo-swagger v1.4.1/go.mod h1:C8bSi+9yH2FLZsnhqMZLIZddpUxZdBYuNHbtaS1Hljc=
|
||||||
github.com/swaggo/files/v2 v2.0.1 h1:XCVJO/i/VosCDsJu1YLpdejGsGnBE9deRMpjN4pJLHk=
|
github.com/swaggo/files/v2 v2.0.2 h1:Bq4tgS/yxLB/3nwOMcul5oLEUKa877Ykgz3CJMVbQKU=
|
||||||
github.com/swaggo/files/v2 v2.0.1/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM=
|
github.com/swaggo/files/v2 v2.0.2/go.mod h1:TVqetIzZsO9OhHX1Am9sRf9LdrFZqoK49N37KON/jr0=
|
||||||
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
|
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
|
||||||
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
|
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
@ -75,28 +74,27 @@ github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
||||||
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
|
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
|
||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
|
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
|
||||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||||
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
|
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
|
||||||
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||||
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
||||||
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
||||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||||
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
|
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
|
||||||
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
|
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
|
|
@ -13,9 +13,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type FrontMatter struct {
|
type FrontMatter struct {
|
||||||
Name string
|
Name string
|
||||||
Date string
|
Description string
|
||||||
Tags []string
|
Date string
|
||||||
|
Tags []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExtractFrontMatter(file fs.DirEntry, contentFS fs.FS) (CardLink, error) {
|
func ExtractFrontMatter(file fs.DirEntry, contentFS fs.FS) (CardLink, error) {
|
||||||
|
|
|
@ -6,20 +6,21 @@ import (
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
contentfs "goth.stack/content"
|
|
||||||
"goth.stack/lib"
|
|
||||||
chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
|
chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/yuin/goldmark"
|
"github.com/yuin/goldmark"
|
||||||
highlighting "github.com/yuin/goldmark-highlighting/v2"
|
highlighting "github.com/yuin/goldmark-highlighting/v2"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
contentfs "goth.stack/content"
|
||||||
|
"goth.stack/lib"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PostProps struct {
|
type PostProps struct {
|
||||||
Content template.HTML
|
Content template.HTML
|
||||||
Name string
|
Name string
|
||||||
Date string
|
Description string
|
||||||
Tags []string
|
Date string
|
||||||
|
Tags []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func Post(c echo.Context) error {
|
func Post(c echo.Context) error {
|
||||||
|
@ -64,10 +65,11 @@ func Post(c echo.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
props := PostProps{
|
props := PostProps{
|
||||||
Content: template.HTML(buf.String()),
|
Content: template.HTML(buf.String()),
|
||||||
Name: frontmatter.Name,
|
Name: frontmatter.Name,
|
||||||
Date: frontmatter.Date,
|
Description: frontmatter.Description,
|
||||||
Tags: frontmatter.Tags,
|
Date: frontmatter.Date,
|
||||||
|
Tags: frontmatter.Tags,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specify the partials used by this page
|
// Specify the partials used by this page
|
||||||
|
|
|
@ -2,8 +2,12 @@
|
||||||
GOTH Stack // Root
|
GOTH Stack // Root
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{define "headercontent"}}
|
{{define "description"}}
|
||||||
{{template "title" .}}
|
HTMX + Golang + Go Template Starter
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "navcontent"}}
|
||||||
|
GOTH Stack // Root
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{define "head"}}
|
{{define "head"}}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="icon" href="/public/favicon.ico" />
|
<link rel="icon" href="/public/favicon.ico" />
|
||||||
<title>{{template "title" .}}</title>
|
<title>{{template "title" .}}</title>
|
||||||
<meta name="description" content="Just here for the vibes...">
|
<meta name="description" content="{{template "description" .}}">
|
||||||
{{template "head" .}}
|
{{template "head" .}}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="icon" href="/public/favicon.ico" />
|
<link rel="icon" href="/public/favicon.ico" />
|
||||||
<title>{{.Name}}</title>
|
<title>{{template "title" .}}</title>
|
||||||
<meta name="description" content="Just here for the vibes...">
|
<meta name="description" content="{{template "description" .}}">
|
||||||
{{template "head" .}}
|
{{template "head" .}}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -52,8 +52,8 @@
|
||||||
|
|
||||||
</article>
|
</article>
|
||||||
</main>
|
</main>
|
||||||
<script src="/public/js/htmx.preload.js"></script>
|
|
||||||
<script src="/public/js/htmx.base.js"></script>
|
<script src="/public/js/htmx.base.js"></script>
|
||||||
|
<script src="/public/js/htmx.preload.js"></script>
|
||||||
{{template "foot" .}}
|
{{template "foot" .}}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{{define "header"}}
|
{{define "header"}}
|
||||||
<header class="navbar bg-base-100">
|
<header class="navbar bg-base-100">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="btn btn-ghost normal-case text-lg sm:text-xl text-white" href="/">{{template "headercontent".}}</a>
|
<a class="btn btn-ghost normal-case text-lg sm:text-xl text-white" href="/">{{template "navcontent".}}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-end z-50">
|
<div class="navbar-end z-50">
|
||||||
<div class="dropdown dropdown-end">
|
<div class="dropdown dropdown-end">
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
{{define "title"}}
|
{{define "title"}}
|
||||||
GOTH Stack // Post
|
GOTH Stack // Post // {{.Name}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{define "headercontent"}}
|
{{define "description"}}
|
||||||
{{template "title" .}}
|
{{.Description}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "navcontent"}}
|
||||||
|
GOTH Stack // Post // {{.Name}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{define "head"}}
|
{{define "head"}}
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
{{define "title"}}
|
{{define "title"}}
|
||||||
GOTH Stack // Blog
|
GOTH Stack // Posts
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{define "headercontent"}}
|
{{define "description"}}
|
||||||
{{template "title" .}}
|
Blog Posts
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "navcontent"}}
|
||||||
|
GOTH Stack // Posts
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{define "head"}}
|
{{define "head"}}
|
||||||
|
|
|
@ -2,8 +2,12 @@
|
||||||
GOTH Stack // Tools
|
GOTH Stack // Tools
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{define "headercontent"}}
|
{{define "description"}}
|
||||||
{{template "title" .}}
|
Misc. Tools for Demo Purposes
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "navcontent"}}
|
||||||
|
GOTH Stack // Tools
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{define "head"}}
|
{{define "head"}}
|
||||||
|
|
|
@ -2,8 +2,12 @@
|
||||||
GOTH Stack // Tools // Resizer
|
GOTH Stack // Tools // Resizer
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{define "headercontent"}}
|
{{define "description"}}
|
||||||
{{template "title" .}}
|
A tool to re-size images.
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "navcontent"}}
|
||||||
|
GOTH Stack // Tools // Resizer
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{define "head"}}
|
{{define "head"}}
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
{{define "title"}}GOTH Stack // Tools // SSE Demo{{end}}
|
{{define "title"}}
|
||||||
|
GOTH Stack // Tools // SSE Demo
|
||||||
|
{{end}}
|
||||||
|
|
||||||
{{define "headercontent"}}
|
{{define "navcontent"}}
|
||||||
{{template "title" .}}
|
GOTH Stack // Tools // SSE Demo
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "description"}}
|
||||||
|
A demo of an SSE implimentation.
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{define "head"}}
|
{{define "head"}}
|
||||||
|
|
1
public/css/styles.css
vendored
1
public/css/styles.css
vendored
File diff suppressed because one or more lines are too long
2
public/js/htmx.base.js
vendored
2
public/js/htmx.base.js
vendored
File diff suppressed because one or more lines are too long
491
public/js/htmx.preload.js
vendored
491
public/js/htmx.preload.js
vendored
|
@ -1,141 +1,388 @@
|
||||||
// This adds the "preload" extension to htmx. By default, this will
|
(function() {
|
||||||
// preload the targets of any tags with `href` or `hx-get` attributes
|
/**
|
||||||
// if they also have a `preload` attribute as well. See documentation
|
* This adds the "preload" extension to htmx. The extension will
|
||||||
// for more details
|
* preload the targets of elements with "preload" attribute if:
|
||||||
htmx.defineExtension('preload', {
|
* - they also have `href`, `hx-get` or `data-hx-get` attributes
|
||||||
|
* - they are radio buttons, checkboxes, select elements and submit
|
||||||
|
* buttons of forms with `method="get"` or `hx-get` attributes
|
||||||
|
* The extension relies on browser cache and for it to work
|
||||||
|
* server response must include `Cache-Control` header
|
||||||
|
* e.g. `Cache-Control: private, max-age=60`.
|
||||||
|
* For more details @see https://htmx.org/extensions/preload/
|
||||||
|
*/
|
||||||
|
|
||||||
onEvent: function(name, event) {
|
htmx.defineExtension('preload', {
|
||||||
// Only take actions on "htmx:afterProcessNode"
|
onEvent: function(name, event) {
|
||||||
if (name !== 'htmx:afterProcessNode') {
|
// Process preload attributes on `htmx:afterProcessNode`
|
||||||
|
if (name === 'htmx:afterProcessNode') {
|
||||||
|
// Initialize all nodes with `preload` attribute
|
||||||
|
const parent = event.target || event.detail.elt;
|
||||||
|
const preloadNodes = [
|
||||||
|
...parent.hasAttribute("preload") ? [parent] : [],
|
||||||
|
...parent.querySelectorAll("[preload]")]
|
||||||
|
preloadNodes.forEach(function(node) {
|
||||||
|
// Initialize the node with the `preload` attribute
|
||||||
|
init(node)
|
||||||
|
|
||||||
|
// Initialize all child elements which has
|
||||||
|
// `href`, `hx-get` or `data-hx-get` attributes
|
||||||
|
node.querySelectorAll('[href],[hx-get],[data-hx-get]').forEach(init)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intercept HTMX preload requests on `htmx:beforeRequest` and
|
||||||
|
// send them as XHR requests instead to avoid side-effects,
|
||||||
|
// such as showing loading indicators while preloading data.
|
||||||
|
if (name === 'htmx:beforeRequest') {
|
||||||
|
const requestHeaders = event.detail.requestConfig.headers
|
||||||
|
if (!("HX-Preloaded" in requestHeaders
|
||||||
|
&& requestHeaders["HX-Preloaded"] === "true")) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault()
|
||||||
|
// Reuse XHR created by HTMX with replaced callbacks
|
||||||
|
const xhr = event.detail.xhr
|
||||||
|
xhr.onload = function() {
|
||||||
|
processResponse(event.detail.elt, xhr.responseText)
|
||||||
|
}
|
||||||
|
xhr.onerror = null
|
||||||
|
xhr.onabort = null
|
||||||
|
xhr.ontimeout = null
|
||||||
|
xhr.send()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize `node`, set up event handlers based on own or inherited
|
||||||
|
* `preload` attributes and set `node.preloadState` to `READY`.
|
||||||
|
*
|
||||||
|
* `node.preloadState` can have these values:
|
||||||
|
* - `READY` - event handlers have been set up and node is ready to preload
|
||||||
|
* - `TIMEOUT` - a triggering event has been fired, but `node` is not
|
||||||
|
* yet being loaded because some time need to pass first e.g. user
|
||||||
|
* has to keep hovering over an element for 100ms for preload to start
|
||||||
|
* - `LOADING` means that `node` is in the process of being preloaded
|
||||||
|
* - `DONE` means that the preloading process is complete and `node`
|
||||||
|
* doesn't need a repeated preload (indicated by preload="always")
|
||||||
|
* @param {Node} node
|
||||||
|
*/
|
||||||
|
function init(node) {
|
||||||
|
// Guarantee that each node is initialized only once
|
||||||
|
if (node.preloadState !== undefined) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// SOME HELPER FUNCTIONS WE'LL NEED ALONG THE WAY
|
if (!isValidNodeForPreloading(node)) {
|
||||||
|
return
|
||||||
// attr gets the closest non-empty value from the attribute.
|
|
||||||
var attr = function(node, property) {
|
|
||||||
if (node == undefined) { return undefined }
|
|
||||||
return node.getAttribute(property) || node.getAttribute('data-' + property) || attr(node.parentElement, property)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// load handles the actual HTTP fetch, and uses htmx.ajax in cases where we're
|
// Initialize form element preloading
|
||||||
// preloading an htmx resource (this sends the same HTTP headers as a regular htmx request)
|
if (node instanceof HTMLFormElement) {
|
||||||
var load = function(node) {
|
const form = node
|
||||||
// Called after a successful AJAX request, to mark the
|
// Only initialize forms with `method="get"` or `hx-get` attributes
|
||||||
// content as loaded (and prevent additional AJAX calls.)
|
if (!((form.hasAttribute('method') && form.method === 'get')
|
||||||
var done = function(html) {
|
|| form.hasAttribute('hx-get') || form.hasAttribute('hx-data-get'))) {
|
||||||
if (!node.preloadAlways) {
|
return
|
||||||
node.preloadState = 'DONE'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attr(node, 'preload-images') == 'true') {
|
|
||||||
document.createElement('div').innerHTML = html // create and populate a node to load linked resources, too.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
for (let i = 0; i < form.elements.length; i++) {
|
||||||
return function() {
|
const element = form.elements.item(i);
|
||||||
// If this value has already been loaded, then do not try again.
|
init(element);
|
||||||
if (node.preloadState !== 'READY') {
|
element.labels.forEach(init);
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special handling for HX-GET - use built-in htmx.ajax function
|
|
||||||
// so that headers match other htmx requests, then set
|
|
||||||
// node.preloadState = TRUE so that requests are not duplicated
|
|
||||||
// in the future
|
|
||||||
var hxGet = node.getAttribute('hx-get') || node.getAttribute('data-hx-get')
|
|
||||||
if (hxGet) {
|
|
||||||
htmx.ajax('GET', hxGet, {
|
|
||||||
source: node,
|
|
||||||
handler: function(elt, info) {
|
|
||||||
done(info.xhr.responseText)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, perform a standard xhr request, then set
|
|
||||||
// node.preloadState = TRUE so that requests are not duplicated
|
|
||||||
// in the future.
|
|
||||||
if (node.getAttribute('href')) {
|
|
||||||
var r = new XMLHttpRequest()
|
|
||||||
r.open('GET', node.getAttribute('href'))
|
|
||||||
r.onload = function() { done(r.responseText) }
|
|
||||||
r.send()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function processes a specific node and sets up event handlers.
|
// Process node configuration from preload attribute
|
||||||
// We'll search for nodes and use it below.
|
let preloadAttr = getClosestAttribute(node, 'preload');
|
||||||
var init = function(node) {
|
node.preloadAlways = preloadAttr && preloadAttr.includes('always');
|
||||||
// If this node DOES NOT include a "GET" transaction, then there's nothing to do here.
|
if (node.preloadAlways) {
|
||||||
if (node.getAttribute('href') + node.getAttribute('hx-get') + node.getAttribute('data-hx-get') == '') {
|
preloadAttr = preloadAttr.replace('always', '').trim();
|
||||||
return
|
}
|
||||||
}
|
let triggerEventName = preloadAttr || 'mousedown';
|
||||||
|
|
||||||
// Guarantee that we only initialize each node once.
|
// Set up event handlers listening for triggering events
|
||||||
if (node.preloadState !== undefined) {
|
const needsTimeout = triggerEventName === 'mouseover'
|
||||||
return
|
node.addEventListener(triggerEventName, getEventHandler(node, needsTimeout))
|
||||||
}
|
|
||||||
|
|
||||||
// Get event name from config.
|
// Add `touchstart` listener for touchscreen support
|
||||||
var on = attr(node, 'preload') || 'mousedown'
|
// if `mousedown` or `mouseover` is used
|
||||||
const always = on.indexOf('always') !== -1
|
if (triggerEventName === 'mousedown' || triggerEventName === 'mouseover') {
|
||||||
if (always) {
|
node.addEventListener('touchstart', getEventHandler(node))
|
||||||
on = on.replace('always', '').trim()
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// FALL THROUGH to here means we need to add an EventListener
|
// If `mouseover` is used, set up `mouseout` listener,
|
||||||
|
// which will abort preloading if user moves mouse outside
|
||||||
// Apply the listener to the node
|
// the element in less than 100ms after hovering over it
|
||||||
node.addEventListener(on, function(evt) {
|
if (triggerEventName === 'mouseover') {
|
||||||
if (node.preloadState === 'PAUSE') { // Only add one event listener
|
node.addEventListener('mouseout', function(evt) {
|
||||||
node.preloadState = 'READY' // Required for the `load` function to trigger
|
if ((evt.target === node) && (node.preloadState === 'TIMEOUT')) {
|
||||||
|
node.preloadState = 'READY'
|
||||||
// Special handling for "mouseover" events. Wait 100ms before triggering load.
|
|
||||||
if (on === 'mouseover') {
|
|
||||||
window.setTimeout(load(node), 100)
|
|
||||||
} else {
|
|
||||||
load(node)() // all other events trigger immediately.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Special handling for certain built-in event handlers
|
|
||||||
switch (on) {
|
|
||||||
case 'mouseover':
|
|
||||||
// Mirror `touchstart` events (fires immediately)
|
|
||||||
node.addEventListener('touchstart', load(node))
|
|
||||||
|
|
||||||
// WHhen the mouse leaves, immediately disable the preload
|
|
||||||
node.addEventListener('mouseout', function(evt) {
|
|
||||||
if ((evt.target === node) && (node.preloadState === 'READY')) {
|
|
||||||
node.preloadState = 'PAUSE'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'mousedown':
|
|
||||||
// Mirror `touchstart` events (fires immediately)
|
|
||||||
node.addEventListener('touchstart', load(node))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark the node as ready to run.
|
|
||||||
node.preloadState = 'PAUSE'
|
|
||||||
node.preloadAlways = always
|
|
||||||
htmx.trigger(node, 'preload:init') // This event can be used to load content immediately.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search for all child nodes that have a "preload" attribute
|
// Mark the node as ready to be preloaded
|
||||||
const parent = event.target || event.detail.elt;
|
node.preloadState = 'READY'
|
||||||
parent.querySelectorAll("[preload]").forEach(function(node) {
|
|
||||||
// Initialize the node with the "preload" attribute
|
|
||||||
init(node)
|
|
||||||
|
|
||||||
// Initialize all child elements that are anchors or have `hx-get` (use with care)
|
// This event can be used to load content immediately
|
||||||
node.querySelectorAll('a,[hx-get],[data-hx-get]').forEach(init)
|
htmx.trigger(node, 'preload:init')
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
/**
|
||||||
|
* Return event handler which can be called by event listener to start
|
||||||
|
* the preloading process of `node` with or without a timeout
|
||||||
|
* @param {Node} node
|
||||||
|
* @param {boolean=} needsTimeout
|
||||||
|
* @returns {function(): void}
|
||||||
|
*/
|
||||||
|
function getEventHandler(node, needsTimeout = false) {
|
||||||
|
return function() {
|
||||||
|
// Do not preload uninitialized nodes, nodes which are in process
|
||||||
|
// of being preloaded or have been preloaded and don't need repeat
|
||||||
|
if (node.preloadState !== 'READY') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsTimeout) {
|
||||||
|
node.preloadState = 'TIMEOUT'
|
||||||
|
const timeoutMs = 100
|
||||||
|
window.setTimeout(function() {
|
||||||
|
if (node.preloadState === 'TIMEOUT') {
|
||||||
|
node.preloadState = 'READY'
|
||||||
|
load(node)
|
||||||
|
}
|
||||||
|
}, timeoutMs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
load(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preload the target of node, which can be:
|
||||||
|
* - hx-get or data-hx-get attribute
|
||||||
|
* - href or form action attribute
|
||||||
|
* @param {Node} node
|
||||||
|
*/
|
||||||
|
function load(node) {
|
||||||
|
// Do not preload uninitialized nodes, nodes which are in process
|
||||||
|
// of being preloaded or have been preloaded and don't need repeat
|
||||||
|
if (node.preloadState !== 'READY') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
node.preloadState = 'LOADING'
|
||||||
|
|
||||||
|
// Load nodes with `hx-get` or `data-hx-get` attribute
|
||||||
|
// Forms don't reach this because only their elements are initialized
|
||||||
|
const hxGet = node.getAttribute('hx-get') || node.getAttribute('data-hx-get')
|
||||||
|
if (hxGet) {
|
||||||
|
sendHxGetRequest(hxGet, node);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load nodes with `href` attribute
|
||||||
|
const hxBoost = getClosestAttribute(node, "hx-boost") === "true"
|
||||||
|
if (node.hasAttribute('href')) {
|
||||||
|
const url = node.getAttribute('href');
|
||||||
|
if (hxBoost) {
|
||||||
|
sendHxGetRequest(url, node);
|
||||||
|
} else {
|
||||||
|
sendXmlGetRequest(url, node);
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load form elements
|
||||||
|
if (isPreloadableFormElement(node)) {
|
||||||
|
const url = node.form.getAttribute('action')
|
||||||
|
|| node.form.getAttribute('hx-get')
|
||||||
|
|| node.form.getAttribute('data-hx-get');
|
||||||
|
const formData = htmx.values(node.form);
|
||||||
|
const isStandardForm = !(node.form.getAttribute('hx-get')
|
||||||
|
|| node.form.getAttribute('data-hx-get')
|
||||||
|
|| hxBoost);
|
||||||
|
const sendGetRequest = isStandardForm ? sendXmlGetRequest : sendHxGetRequest
|
||||||
|
|
||||||
|
// submit button
|
||||||
|
if (node.type === 'submit') {
|
||||||
|
sendGetRequest(url, node.form, formData)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// select
|
||||||
|
const inputName = node.name || node.control.name;
|
||||||
|
if (node.tagName === 'SELECT') {
|
||||||
|
Array.from(node.options).forEach(option => {
|
||||||
|
if (option.selected) return;
|
||||||
|
formData.set(inputName, option.value);
|
||||||
|
const formDataOrdered = forceFormDataInOrder(node.form, formData);
|
||||||
|
sendGetRequest(url, node.form, formDataOrdered)
|
||||||
|
});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// radio and checkbox
|
||||||
|
const inputType = node.getAttribute("type") || node.control.getAttribute("type");
|
||||||
|
const nodeValue = node.value || node.control?.value;
|
||||||
|
if (inputType === 'radio') {
|
||||||
|
formData.set(inputName, nodeValue);
|
||||||
|
} else if (inputType === 'checkbox'){
|
||||||
|
const inputValues = formData.getAll(inputName);
|
||||||
|
if (inputValues.includes(nodeValue)) {
|
||||||
|
formData[inputName] = inputValues.filter(value => value !== nodeValue);
|
||||||
|
} else {
|
||||||
|
formData.append(inputName, nodeValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const formDataOrdered = forceFormDataInOrder(node.form, formData);
|
||||||
|
sendGetRequest(url, node.form, formDataOrdered)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force formData values to be in the order of form elements.
|
||||||
|
* This is useful to apply after alternating formData values
|
||||||
|
* and before passing them to a HTTP request because cache is
|
||||||
|
* sensitive to GET parameter order e.g., cached `/link?a=1&b=2`
|
||||||
|
* will not be used for `/link?b=2&a=1`.
|
||||||
|
* @param {HTMLFormElement} form
|
||||||
|
* @param {FormData} formData
|
||||||
|
* @returns {FormData}
|
||||||
|
*/
|
||||||
|
function forceFormDataInOrder(form, formData) {
|
||||||
|
const formElements = form.elements;
|
||||||
|
const orderedFormData = new FormData();
|
||||||
|
for(let i = 0; i < formElements.length; i++) {
|
||||||
|
const element = formElements.item(i);
|
||||||
|
if (formData.has(element.name) && element.tagName === 'SELECT') {
|
||||||
|
orderedFormData.append(
|
||||||
|
element.name, formData.get(element.name));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (formData.has(element.name) && formData.getAll(element.name)
|
||||||
|
.includes(element.value)) {
|
||||||
|
orderedFormData.append(element.name, element.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return orderedFormData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send GET request with `hx-request` headers as if `sourceNode`
|
||||||
|
* target was loaded. Send alternated values if `formData` is set.
|
||||||
|
*
|
||||||
|
* Note that this request is intercepted and sent as XMLHttpRequest.
|
||||||
|
* It is necessary to use `htmx.ajax` to acquire correct headers which
|
||||||
|
* HTMX and extensions add based on `sourceNode`. But it cannot be used
|
||||||
|
* to perform the request due to side-effects e.g. loading indicators.
|
||||||
|
* @param {string} url
|
||||||
|
* @param {Node} sourceNode
|
||||||
|
* @param {FormData=} formData
|
||||||
|
*/
|
||||||
|
function sendHxGetRequest(url, sourceNode, formData = undefined) {
|
||||||
|
htmx.ajax('GET', url, {
|
||||||
|
source: sourceNode,
|
||||||
|
values: formData,
|
||||||
|
headers: {"HX-Preloaded": "true"}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send XML GET request to `url`. Send `formData` as URL params if set.
|
||||||
|
* @param {string} url
|
||||||
|
* @param {Node} sourceNode
|
||||||
|
* @param {FormData=} formData
|
||||||
|
*/
|
||||||
|
function sendXmlGetRequest(url, sourceNode, formData = undefined) {
|
||||||
|
const xhr = new XMLHttpRequest()
|
||||||
|
if (formData) {
|
||||||
|
url += '?' + new URLSearchParams(formData.entries()).toString()
|
||||||
|
}
|
||||||
|
xhr.open('GET', url);
|
||||||
|
xhr.setRequestHeader("HX-Preloaded", "true")
|
||||||
|
xhr.onload = function() { processResponse(sourceNode, xhr.responseText) }
|
||||||
|
xhr.send()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process request response by marking node `DONE` to prevent repeated
|
||||||
|
* requests, except if preload attribute contains `always`,
|
||||||
|
* and load linked resources (e.g. images) returned in the response
|
||||||
|
* if `preload-images` attribute is `true`
|
||||||
|
* @param {Node} node
|
||||||
|
* @param {string} responseText
|
||||||
|
*/
|
||||||
|
function processResponse(node, responseText) {
|
||||||
|
node.preloadState = node.preloadAlways ? 'READY' : 'DONE'
|
||||||
|
|
||||||
|
if (getClosestAttribute(node, 'preload-images') === 'true') {
|
||||||
|
// Load linked resources
|
||||||
|
document.createElement('div').innerHTML = responseText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets attribute value from node or one of its parents
|
||||||
|
* @param {Node} node
|
||||||
|
* @param {string} attribute
|
||||||
|
* @returns { string | undefined }
|
||||||
|
*/
|
||||||
|
function getClosestAttribute(node, attribute) {
|
||||||
|
if (node == undefined) { return undefined }
|
||||||
|
return node.getAttribute(attribute)
|
||||||
|
|| node.getAttribute('data-' + attribute)
|
||||||
|
|| getClosestAttribute(node.parentElement, attribute)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if node is valid for preloading and should be
|
||||||
|
* initialized by setting up event listeners and handlers
|
||||||
|
* @param {Node} node
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function isValidNodeForPreloading(node) {
|
||||||
|
// Add listeners only to nodes which include "GET" transactions
|
||||||
|
// or preloadable "GET" form elements
|
||||||
|
const getReqAttrs = ['href', 'hx-get', 'data-hx-get'];
|
||||||
|
const includesGetRequest = node => getReqAttrs.some(a => node.hasAttribute(a))
|
||||||
|
|| node.method === 'get';
|
||||||
|
const isPreloadableGetFormElement = node.form instanceof HTMLFormElement
|
||||||
|
&& includesGetRequest(node.form)
|
||||||
|
&& isPreloadableFormElement(node)
|
||||||
|
if (!includesGetRequest(node) && !isPreloadableGetFormElement) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't preload <input> elements contained in <label>
|
||||||
|
// to prevent sending two requests. Interaction on <input> in a
|
||||||
|
// <label><input></input></label> situation activates <label> too.
|
||||||
|
if (node instanceof HTMLInputElement && node.closest('label')) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if node is a form element which can be preloaded,
|
||||||
|
* i.e., `radio`, `checkbox`, `select` or `submit` button
|
||||||
|
* or a `label` of a form element which can be preloaded.
|
||||||
|
* @param {Node} node
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function isPreloadableFormElement(node) {
|
||||||
|
if (node instanceof HTMLInputElement || node instanceof HTMLButtonElement) {
|
||||||
|
const type = node.getAttribute('type');
|
||||||
|
return ['checkbox', 'radio', 'submit'].includes(type);
|
||||||
|
}
|
||||||
|
if (node instanceof HTMLLabelElement) {
|
||||||
|
return node.control && isPreloadableFormElement(node.control);
|
||||||
|
}
|
||||||
|
return node instanceof HTMLSelectElement;
|
||||||
|
}
|
||||||
|
})()
|
8
public/js/htmx.ws.js
vendored
8
public/js/htmx.ws.js
vendored
|
@ -411,8 +411,12 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
|
||||||
*/
|
*/
|
||||||
function maybeCloseWebSocketSource(elt) {
|
function maybeCloseWebSocketSource(elt) {
|
||||||
if (!api.bodyContains(elt)) {
|
if (!api.bodyContains(elt)) {
|
||||||
api.getInternalData(elt).webSocket.close()
|
var internalData = api.getInternalData(elt)
|
||||||
return true
|
if (internalData.webSocket) {
|
||||||
|
internalData.webSocket.close()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue