From af2112bdde3baac49fc11a9853587f9033debe95 Mon Sep 17 00:00:00 2001
From: gfanton <8671905+gfanton@users.noreply.github.com>
Date: Mon, 25 Nov 2024 18:27:12 +0900
Subject: [PATCH 001/118] wip: rework gnoweb
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
---
contribs/gnodev/cmd/gnodev/setup_web.go | 85 +++-
contribs/gnodev/go.mod | 6 +-
contribs/gnodev/go.sum | 20 +-
gno.land/Makefile | 8 +-
gno.land/cmd/gnoweb2/main.go | 165 ++++++++
gno.land/pkg/gnoweb2/.gitignore | 2 +
gno.land/pkg/gnoweb2/Makefile | 113 ++++++
gno.land/pkg/gnoweb2/alias.go | 47 +++
gno.land/pkg/gnoweb2/app.go | 88 +++++
gno.land/pkg/gnoweb2/components/breadcrumb.go | 18 +
.../pkg/gnoweb2/components/breadcrumb.gohtml | 12 +
gno.land/pkg/gnoweb2/components/help.go | 40 ++
gno.land/pkg/gnoweb2/components/help.gohtml | 113 ++++++
gno.land/pkg/gnoweb2/components/index.go | 42 ++
gno.land/pkg/gnoweb2/components/index.gohtml | 148 +++++++
.../pkg/gnoweb2/components/logosvg.gohtml | 21 +
gno.land/pkg/gnoweb2/components/realm.go | 32 ++
gno.land/pkg/gnoweb2/components/realm.gohtml | 47 +++
gno.land/pkg/gnoweb2/components/source.go | 19 +
gno.land/pkg/gnoweb2/components/source.gohtml | 68 ++++
.../pkg/gnoweb2/components/spritesvg.gohtml | 131 ++++++
gno.land/pkg/gnoweb2/components/status.gohtml | 5 +
gno.land/pkg/gnoweb2/components/template.go | 129 ++++++
gno.land/pkg/gnoweb2/components/util.gohtml | 9 +
gno.land/pkg/gnoweb2/frontend/css/input.css | 335 ++++++++++++++++
.../pkg/gnoweb2/frontend/css/tx.config.js | 78 ++++
gno.land/pkg/gnoweb2/frontend/js/realmhelp.ts | 100 +++++
gno.land/pkg/gnoweb2/frontend/js/searchbar.ts | 62 +++
.../static/fonts/intervar/Inter.var.woff2 | Bin 0 -> 324864 bytes
.../fonts/roboto/roboto-mono-normal.woff | Bin 0 -> 15832 bytes
.../fonts/roboto/roboto-mono-normal.woff2 | Bin 0 -> 12764 bytes
.../gnoweb2/frontend/static/imgs/gnoland.svg | 4 +
.../gnoweb2/frontend/static/js/realmhelp.js | 71 ++++
gno.land/pkg/gnoweb2/handler.go | 373 ++++++++++++++++++
.../public/fonts/intervar/Inter.var.woff2 | Bin 0 -> 324864 bytes
.../fonts/roboto/roboto-mono-normal.woff | Bin 0 -> 15832 bytes
.../fonts/roboto/roboto-mono-normal.woff2 | Bin 0 -> 12764 bytes
gno.land/pkg/gnoweb2/public/imgs/gnoland.svg | 4 +
gno.land/pkg/gnoweb2/public/js/realmhelp.js | 1 +
gno.land/pkg/gnoweb2/public/js/searchbar.js | 1 +
gno.land/pkg/gnoweb2/public/styles.css | 3 +
gno.land/pkg/gnoweb2/static.go | 25 ++
.../pkg/gnoweb2/tools/cmd/logname/colors.go | 60 +++
.../pkg/gnoweb2/tools/cmd/logname/main.go | 41 ++
gno.land/pkg/gnoweb2/tools/go.mod | 60 +++
gno.land/pkg/gnoweb2/tools/go.sum | 336 ++++++++++++++++
gno.land/pkg/gnoweb2/tools/tools.go | 7 +
gno.land/pkg/gnoweb2/url.go | 139 +++++++
gno.land/pkg/gnoweb2/url_test.go | 129 ++++++
gno.land/pkg/markdown/ext_column.go | 287 ++++++++++++++
gno.land/pkg/markdown/ext_column_test.go | 158 ++++++++
gno.land/pkg/markdown/ext_render.go | 23 ++
gno.land/pkg/markdown/flags_test.go | 5 +
gno.land/pkg/markdown/nohtml.go | 195 +++++++++
.../testdata/column_simple_basic.golden | 8 +
.../testdata/column_simple_left.golden | 8 +
.../testdata/column_simple_middle.golden | 8 +
.../testdata/column_simple_right.golden | 8 +
gno.land/pkg/markdown/toc.go | 137 +++++++
gno.land/pkg/service/render.go | 141 +++++++
go.mod | 4 +
go.sum | 15 +
62 files changed, 4166 insertions(+), 28 deletions(-)
create mode 100644 gno.land/cmd/gnoweb2/main.go
create mode 100644 gno.land/pkg/gnoweb2/.gitignore
create mode 100644 gno.land/pkg/gnoweb2/Makefile
create mode 100644 gno.land/pkg/gnoweb2/alias.go
create mode 100644 gno.land/pkg/gnoweb2/app.go
create mode 100644 gno.land/pkg/gnoweb2/components/breadcrumb.go
create mode 100644 gno.land/pkg/gnoweb2/components/breadcrumb.gohtml
create mode 100644 gno.land/pkg/gnoweb2/components/help.go
create mode 100644 gno.land/pkg/gnoweb2/components/help.gohtml
create mode 100644 gno.land/pkg/gnoweb2/components/index.go
create mode 100644 gno.land/pkg/gnoweb2/components/index.gohtml
create mode 100644 gno.land/pkg/gnoweb2/components/logosvg.gohtml
create mode 100644 gno.land/pkg/gnoweb2/components/realm.go
create mode 100644 gno.land/pkg/gnoweb2/components/realm.gohtml
create mode 100644 gno.land/pkg/gnoweb2/components/source.go
create mode 100644 gno.land/pkg/gnoweb2/components/source.gohtml
create mode 100644 gno.land/pkg/gnoweb2/components/spritesvg.gohtml
create mode 100644 gno.land/pkg/gnoweb2/components/status.gohtml
create mode 100644 gno.land/pkg/gnoweb2/components/template.go
create mode 100644 gno.land/pkg/gnoweb2/components/util.gohtml
create mode 100644 gno.land/pkg/gnoweb2/frontend/css/input.css
create mode 100644 gno.land/pkg/gnoweb2/frontend/css/tx.config.js
create mode 100644 gno.land/pkg/gnoweb2/frontend/js/realmhelp.ts
create mode 100644 gno.land/pkg/gnoweb2/frontend/js/searchbar.ts
create mode 100644 gno.land/pkg/gnoweb2/frontend/static/fonts/intervar/Inter.var.woff2
create mode 100644 gno.land/pkg/gnoweb2/frontend/static/fonts/roboto/roboto-mono-normal.woff
create mode 100644 gno.land/pkg/gnoweb2/frontend/static/fonts/roboto/roboto-mono-normal.woff2
create mode 100644 gno.land/pkg/gnoweb2/frontend/static/imgs/gnoland.svg
create mode 100644 gno.land/pkg/gnoweb2/frontend/static/js/realmhelp.js
create mode 100644 gno.land/pkg/gnoweb2/handler.go
create mode 100644 gno.land/pkg/gnoweb2/public/fonts/intervar/Inter.var.woff2
create mode 100644 gno.land/pkg/gnoweb2/public/fonts/roboto/roboto-mono-normal.woff
create mode 100644 gno.land/pkg/gnoweb2/public/fonts/roboto/roboto-mono-normal.woff2
create mode 100644 gno.land/pkg/gnoweb2/public/imgs/gnoland.svg
create mode 100644 gno.land/pkg/gnoweb2/public/js/realmhelp.js
create mode 100644 gno.land/pkg/gnoweb2/public/js/searchbar.js
create mode 100644 gno.land/pkg/gnoweb2/public/styles.css
create mode 100644 gno.land/pkg/gnoweb2/static.go
create mode 100644 gno.land/pkg/gnoweb2/tools/cmd/logname/colors.go
create mode 100644 gno.land/pkg/gnoweb2/tools/cmd/logname/main.go
create mode 100644 gno.land/pkg/gnoweb2/tools/go.mod
create mode 100644 gno.land/pkg/gnoweb2/tools/go.sum
create mode 100644 gno.land/pkg/gnoweb2/tools/tools.go
create mode 100644 gno.land/pkg/gnoweb2/url.go
create mode 100644 gno.land/pkg/gnoweb2/url_test.go
create mode 100644 gno.land/pkg/markdown/ext_column.go
create mode 100644 gno.land/pkg/markdown/ext_column_test.go
create mode 100644 gno.land/pkg/markdown/ext_render.go
create mode 100644 gno.land/pkg/markdown/flags_test.go
create mode 100644 gno.land/pkg/markdown/nohtml.go
create mode 100644 gno.land/pkg/markdown/testdata/column_simple_basic.golden
create mode 100644 gno.land/pkg/markdown/testdata/column_simple_left.golden
create mode 100644 gno.land/pkg/markdown/testdata/column_simple_middle.golden
create mode 100644 gno.land/pkg/markdown/testdata/column_simple_right.golden
create mode 100644 gno.land/pkg/markdown/toc.go
create mode 100644 gno.land/pkg/service/render.go
diff --git a/contribs/gnodev/cmd/gnodev/setup_web.go b/contribs/gnodev/cmd/gnodev/setup_web.go
index 635c27af19d..765314ca245 100644
--- a/contribs/gnodev/cmd/gnodev/setup_web.go
+++ b/contribs/gnodev/cmd/gnodev/setup_web.go
@@ -1,26 +1,89 @@
package main
import (
+ "context"
+ "fmt"
"log/slog"
"net/http"
gnodev "github.com/gnolang/gno/contribs/gnodev/pkg/dev"
- "github.com/gnolang/gno/gno.land/pkg/gnoweb"
+ "github.com/gnolang/gno/gno.land/pkg/gnoclient"
+ gnoweb "github.com/gnolang/gno/gno.land/pkg/gnoweb2"
+ "github.com/gnolang/gno/gno.land/pkg/service"
+ "github.com/gnolang/gno/tm2/pkg/bft/rpc/client"
+ "github.com/yuin/goldmark"
)
+func makeWebApp(logger *slog.Logger, cfg *devCfg, dnode *gnodev.Node) error {
+
+ // bindaddr, err := net.ResolveTCPAddr("tcp", cfg.bind)
+ // if err != nil {
+ // return fmt.Errorf("unable to resolve listener: %q", cfg.bind)
+ // }
+
+ // logger.Info("Running", "listener", bindaddr.String())
+
+ // server := &http.Server{
+ // Handler: mux,
+ // Addr: bindaddr.String(),
+ // ReadHeaderTimeout: 60 * time.Second,
+ // }
+
+ // if err := server.ListenAndServe(); err != nil {
+ // logger.Error("HTTP server stopped", " error:", err)
+ // os.Exit(1)
+ // }
+
+}
+
// setupGnowebServer initializes and starts the Gnoweb server.
-func setupGnoWebServer(logger *slog.Logger, cfg *devCfg, dnode *gnodev.Node) http.Handler {
- webConfig := gnoweb.NewDefaultConfig()
+func setupGnoWebServer(logger *slog.Logger, cfg *devCfg, dnode *gnodev.Node) (http.Handler, error) {
+ remote := dnode.GetRemoteAddress()
- webConfig.HelpChainID = cfg.chainId
- webConfig.RemoteAddr = dnode.GetRemoteAddress()
- webConfig.HelpRemote = cfg.webRemoteHelperAddr
+ md := goldmark.New()
+
+ client, err := client.NewHTTPClient(remote)
+ if err != nil {
+ return nil, fmt.Errorf("unable to create http client: %W", err)
+ }
- // If `HelpRemote` is empty default it to `RemoteAddr`
- if webConfig.HelpRemote == "" {
- webConfig.HelpRemote = webConfig.RemoteAddr
+ mnemo := "index brass unknown lecture autumn provide royal shrimp elegant wink now zebra discover swarm act ill you bullet entire outdoor tilt usage gap multiply"
+ bip39Passphrase := ""
+ account, index := uint32(0), uint32(0)
+ chainID := cfg.chainId
+ signer, err := gnoclient.SignerFromBip39(mnemo, chainID, bip39Passphrase, account, index)
+ if err != nil {
+ return nil, fmt.Errorf("unable to create signer: %w", err)
}
- app := gnoweb.MakeApp(logger, webConfig)
- return app.Router
+ // Setup webservice
+ cl := gnoclient.Client{
+ Signer: signer,
+ RPCClient: client,
+ }
+ webcli := service.NewWebRender(logger, &cl, md)
+
+ var webConfig gnoweb.WebHandlerConfig
+
+ webConfig.RenderClient = webcli
+
+ // static meta
+ // webConfig.Meta.AssetsPath = // XXX
+ webConfig.Meta.RemoteHelp = cfg.webRemoteHelperAddr
+ webConfig.Meta.ChaindID = cfg.chainId
+
+ // Setup main handler
+ webhandler := gnoweb.NewWebHandler(
+ context.TODO(), // XXX
+ logger,
+ webConfig,
+ )
+
+ mux := http.NewServeMux()
+ mux.Handle(webConfig.Meta.AssetsPath, gnoweb.AssetHandler())
+
+ // Setup Alias Middleware
+ mux.Handle("/", gnoweb.AliasAndRedirectMiddleware(webhandler))
+
+ return mux, nil
}
diff --git a/contribs/gnodev/go.mod b/contribs/gnodev/go.mod
index a315d88591c..216f610af33 100644
--- a/contribs/gnodev/go.mod
+++ b/contribs/gnodev/go.mod
@@ -27,7 +27,7 @@ require (
require (
dario.cat/mergo v1.0.1 // indirect
- github.com/alecthomas/chroma/v2 v2.8.0 // indirect
+ github.com/alecthomas/chroma/v2 v2.14.0 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
@@ -48,7 +48,7 @@ require (
github.com/creack/pty v1.1.21 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
- github.com/dlclark/regexp2 v1.4.0 // indirect
+ github.com/dlclark/regexp2 v1.11.0 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
@@ -82,7 +82,7 @@ require (
github.com/rs/xid v1.6.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
- github.com/yuin/goldmark v1.5.4 // indirect
+ github.com/yuin/goldmark v1.7.2 // indirect
github.com/yuin/goldmark-emoji v1.0.2 // indirect
github.com/zondax/hid v0.9.2 // indirect
github.com/zondax/ledger-go v0.14.3 // indirect
diff --git a/contribs/gnodev/go.sum b/contribs/gnodev/go.sum
index e38c3621483..98838234a4f 100644
--- a/contribs/gnodev/go.sum
+++ b/contribs/gnodev/go.sum
@@ -1,12 +1,12 @@
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
-github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=
-github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
-github.com/alecthomas/chroma/v2 v2.8.0 h1:w9WJUjFFmHHB2e8mRpL9jjy3alYDlU0QLDezj1xE264=
-github.com/alecthomas/chroma/v2 v2.8.0/go.mod h1:yrkMI9807G1ROx13fhe1v6PN2DDeaR73L3d+1nmYQtw=
-github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
-github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
+github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
+github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
+github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
+github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
+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/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
@@ -91,8 +91,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeC
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
-github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
-github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
+github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
+github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
@@ -235,8 +235,8 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU=
-github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/yuin/goldmark v1.7.2 h1:NjGd7lO7zrUn/A7eKwn5PEOt4ONYGqpxSEeZuduvgxc=
+github.com/yuin/goldmark v1.7.2/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark-emoji v1.0.2 h1:c/RgTShNgHTtc6xdz2KKI74jJr6rWi7FPgnP9GAsO5s=
github.com/yuin/goldmark-emoji v1.0.2/go.mod h1:RhP/RWpexdp+KHs7ghKnifRoIs/Bq4nDS7tRbCkOwKY=
github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U=
diff --git a/gno.land/Makefile b/gno.land/Makefile
index 7b2afd5779f..263e9c0b9de 100644
--- a/gno.land/Makefile
+++ b/gno.land/Makefile
@@ -28,23 +28,23 @@ GOTEST_FLAGS ?= -v -p 1 -timeout=30m
start.gnoland:; go run ./cmd/gnoland start -lazy
.PHONY: start.gnoweb
-start.gnoweb:; go run ./cmd/gnoweb
+start.gnoweb:; go run ./cmd/gnoweb2
.PHONY: build
build: build.gnoland build.gnokey build.gnoweb
build.gnoland:; go build -o build/gnoland ./cmd/gnoland
-build.gnoweb:; go build -o build/gnoweb ./cmd/gnoweb
+build.gnoweb:; go build -o build/gnoweb2 ./cmd/gnoweb
build.gnokey:; go build -o build/gnokey ./cmd/gnokey
run.gnoland:; go run ./cmd/gnoland start
-run.gnoweb:; go run ./cmd/gnoweb
+run.gnoweb:; go run ./cmd/gnoweb2
.PHONY: install
install: install.gnoland install.gnoweb install.gnokey
install.gnoland:; go install ./cmd/gnoland
-install.gnoweb:; go install ./cmd/gnoweb
+install.gnoweb:; go install ./cmd/gnoweb2
install.gnokey:; go install ./cmd/gnokey
.PHONY: fclean
diff --git a/gno.land/cmd/gnoweb2/main.go b/gno.land/cmd/gnoweb2/main.go
new file mode 100644
index 00000000000..5396475ce6e
--- /dev/null
+++ b/gno.land/cmd/gnoweb2/main.go
@@ -0,0 +1,165 @@
+package main
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "net"
+ "net/http"
+ "os"
+ "strings"
+ "time"
+
+ "github.com/gnolang/gno/gno.land/pkg/gnoclient"
+ gnoweb "github.com/gnolang/gno/gno.land/pkg/gnoweb2"
+ "github.com/gnolang/gno/gno.land/pkg/log"
+ "github.com/gnolang/gno/gno.land/pkg/service"
+ "github.com/gnolang/gno/tm2/pkg/bft/rpc/client"
+ "github.com/gnolang/gno/tm2/pkg/commands"
+ "github.com/yuin/goldmark"
+ "go.uber.org/zap/zapcore"
+)
+
+type webCfg struct {
+ chainid string
+ remote string
+ bind string
+}
+
+var defaultWebOptions = &webCfg{
+ chainid: "dev",
+ remote: "127.0.0.1:26657",
+ bind: ":8888",
+}
+
+func main() {
+ cfg := &webCfg{}
+
+ stdio := commands.NewDefaultIO()
+ cmd := commands.NewCommand(
+ commands.Metadata{
+ Name: "gnoweb",
+ ShortUsage: "gnoweb [flags] [path ...]",
+ ShortHelp: "runs gno.land web interface",
+ LongHelp: `gnoweb web interface`,
+ },
+ cfg,
+ func(_ context.Context, args []string) error {
+ return execWeb(cfg, args, stdio)
+ })
+
+ cmd.Execute(context.Background(), os.Args[1:])
+}
+
+func (c *webCfg) RegisterFlags(fs *flag.FlagSet) {
+ fs.StringVar(
+ &c.remote,
+ "remote",
+ defaultWebOptions.remote,
+ "user's local directory for keys",
+ )
+
+ fs.StringVar(
+ &c.chainid,
+ "chainid",
+ defaultWebOptions.chainid,
+ "user's local directory for keys",
+ )
+
+ fs.StringVar(
+ &c.bind,
+ "bind",
+ defaultWebOptions.bind,
+ "user's local directory for keys",
+ )
+
+}
+
+func execWeb(cfg *webCfg, args []string, io commands.IO) (err error) {
+ zapLogger := log.NewZapConsoleLogger(os.Stdout, zapcore.DebugLevel)
+ defer zapLogger.Sync()
+
+ // Setup logger
+ logger := log.ZapLoggerToSlog(zapLogger)
+
+ md := goldmark.New()
+
+ staticMeta := gnoweb.StaticMetadata{
+ AssetsPath: "/public/",
+ RemoteHelp: cfg.remote,
+ }
+
+ mux := http.NewServeMux()
+
+ // Setup asset handler
+ // if cfg.dev {
+ // mux.Handle(staticMeta.AssetsPath, AssetDevHandler())
+ // } else {
+ mux.Handle(staticMeta.AssetsPath, gnoweb.AssetHandler())
+ // }
+
+ client, err := client.NewHTTPClient(cfg.remote)
+ if err != nil {
+ return fmt.Errorf("unable to create http client: %W", err)
+ }
+
+ mnemo := "index brass unknown lecture autumn provide royal shrimp elegant wink now zebra discover swarm act ill you bullet entire outdoor tilt usage gap multiply"
+ bip39Passphrase := ""
+ account, index := uint32(0), uint32(0)
+ chainID := cfg.chainid
+ signer, err := gnoclient.SignerFromBip39(mnemo, chainID, bip39Passphrase, account, index)
+ if err != nil {
+ return fmt.Errorf("unable to create signer: %w", err)
+ }
+
+ // Setup webservice
+ cl := gnoclient.Client{
+ Signer: signer,
+ RPCClient: client,
+ }
+ webcli := service.NewWebRender(logger, &cl, md)
+
+ if len(args) > 0 {
+ var qargs string
+ if len(args) > 1 {
+ qargs = strings.Join(args[1:], ",")
+ }
+
+ _, err = webcli.Render(io.Out(), args[0], qargs)
+ return
+ }
+
+ webcfg := gnoweb.WebHandlerConfig{
+ RenderClient: webcli,
+ Meta: staticMeta,
+ }
+
+ // Setup main handler
+ webhandler := gnoweb.NewWebHandler(
+ logger,
+ webcfg,
+ )
+
+ // Setup Alias Middleware
+ mux.Handle("/", gnoweb.AliasAndRedirectMiddleware(webhandler))
+
+ bindaddr, err := net.ResolveTCPAddr("tcp", cfg.bind)
+ if err != nil {
+ return fmt.Errorf("unable to resolve listener: %q", cfg.bind)
+ }
+
+ logger.Info("Running", "listener", bindaddr.String())
+
+ server := &http.Server{
+ Handler: mux,
+ Addr: bindaddr.String(),
+ ReadHeaderTimeout: 60 * time.Second,
+ }
+
+ if err := server.ListenAndServe(); err != nil {
+ logger.Error("HTTP server stopped", " error:", err)
+ os.Exit(1)
+ }
+
+ return nil
+}
diff --git a/gno.land/pkg/gnoweb2/.gitignore b/gno.land/pkg/gnoweb2/.gitignore
new file mode 100644
index 00000000000..92b2c45a1b2
--- /dev/null
+++ b/gno.land/pkg/gnoweb2/.gitignore
@@ -0,0 +1,2 @@
+node_modules/
+tmp/
diff --git a/gno.land/pkg/gnoweb2/Makefile b/gno.land/pkg/gnoweb2/Makefile
new file mode 100644
index 00000000000..129b54a4e54
--- /dev/null
+++ b/gno.land/pkg/gnoweb2/Makefile
@@ -0,0 +1,113 @@
+# configurable arguments
+DEV_REMOTE ?= 127.0.0.1:26657
+CHAINID ?= test3
+
+# Variable Declarations
+COMPONENTS_FOLDER := ./components
+
+tools_run := go run -modfile ./tools/go.mod
+tools_install := go install -modfile ./tools/go.mod
+
+run_air := $(tools_run) github.com/air-verse/air
+
+run_logname := go -C ./tools run ./cmd/logname
+
+# css config
+input_css := ./frontend/css/input.css
+output_css := ./public/styles.css
+tw_version := 3.4.14
+tw_config_path := ./frontend/css/tx.config.js
+
+# static config
+static_folder := ./frontend/static
+output_statics := ./public
+
+# Esbuild config
+input_js := ./frontend/js/**/*.ts
+output_js := ./public/js
+
+#############
+# Targets
+#############
+.PHONY: all generate fmt css ts templ
+
+# Install dependencies
+all: generate
+
+install: generate
+ go install -v .
+
+# Generate process
+generate: clean css ts fonts
+css:
+ npx tailwindcss@$(tw_version) -c $(tw_config_path) -i $(input_css) -o $(output_css) --minify # tailwind
+ts:
+ npx -y esbuild $(input_js) --bundle --outdir=$(output_js) --format=esm --minify
+fonts:
+ mkdir -p $(output_statics)
+ cp -r $(static_folder)/* $(output_statics)
+
+# Format process
+fmt:
+ go fmt ./...
+ $(run_templ) fmt $(COMPONENTS_FOLDER)
+
+ ###############################
+ # Developments
+ ###############################
+.PHONY: dev dev.templ dev.server dev.css dev.ts deps
+
+# Run the development dependencies in parallel
+dev:
+ @echo "-- starting development tools"
+ @$(MAKE) -j 4 \
+ dev.gnoweb \
+ dev.fonts \
+ dev.ts \
+ dev.css
+
+# Go server in development mode
+dev.gnoweb: | .cache
+ @killall gnowebserve || true # make sure that previous instances as been kill
+ $(run_air) \
+ --build.cmd "go build -o .cache/gnowebserve ." \
+ --build.bin ".cache/gnowebserve -dev -chain-id=${CHAIN_ID} -remote=${DEV_REMOTE}" \
+ --build.exclude_unchanged "true" \
+ --build.exclude_dir "gotools,node_modules" \
+ --build.exclude_regex '^\.#' \
+ --build.include_ext "go,gohtml" \
+ --build.stop_on_error "false" \
+ --build.rerun_delay "1000" \
+ 2>&1 | $(run_logname) gnoweb
+
+# Tailwind CSS in development mode
+dev.css: | public
+ npx tailwindcss@$(tw_version) -c $(tw_config_path) --verbose -i $(input_css) -o $(output_css) --watch \
+ 2>&1 | $(run_logname) tailwind
+
+# TS in development mode
+dev.ts: | public
+ npx -y esbuild $(input_js) \
+ --bundle \
+ --outdir=$(output_js) \
+ --format=esm \
+ --watch \
+ --minify
+
+dev.fonts:
+ mkdir -p $(output_statics)
+ cp -r $(static_folder)/* $(output_statics)
+
+# Install deps for development purpose
+deps:
+ $(tools_install) github.com/a-h/templ/cmd/templ
+
+# Cleanup
+clean:
+ rm -rf public tmp
+fclean: clean
+ rm -rf node_modules .cache
+
+# Dirs
+.cache:; mkdir -p $@
+public:; mkdir -p $@
diff --git a/gno.land/pkg/gnoweb2/alias.go b/gno.land/pkg/gnoweb2/alias.go
new file mode 100644
index 00000000000..5054b639d4a
--- /dev/null
+++ b/gno.land/pkg/gnoweb2/alias.go
@@ -0,0 +1,47 @@
+package gnoweb
+
+import "net/http"
+
+// realm aliases
+var Aliases = map[string]string{
+ "/": "/r/gnoland/home",
+ "/about": "/r/gnoland/pages:p/about",
+ "/gnolang": "/r/gnoland/pages:p/gnolang",
+ "/ecosystem": "/r/gnoland/pages:p/ecosystem",
+ "/partners": "/r/gnoland/pages:p/partners",
+ "/testnets": "/r/gnoland/pages:p/testnets",
+ "/start": "/r/gnoland/pages:p/start",
+ "/license": "/r/gnoland/pages:p/license",
+ "/contribute": "/r/gnoland/pages:p/contribute",
+ "/events": "/r/gnoland/events",
+}
+
+// http redirects
+var Redirects = map[string]string{
+ "/r/demo/boards:gnolang/6": "/r/demo/boards:gnolang/3", // XXX: temporary
+ "/blog": "/r/gnoland/blog",
+ "/gor": "/contribute",
+ "/game-of-realms": "/contribute",
+ "/grants": "/partners",
+ "/language": "/gnolang",
+ "/getting-started": "/start",
+ "/gophercon24": "https://docs.gno.land",
+}
+
+func AliasAndRedirectMiddleware(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // Check if the request path matches an alias
+ if newPath, ok := Aliases[r.URL.Path]; ok {
+ r.URL.Path = newPath
+ }
+
+ // Check if the request path matches a redirect
+ if newPath, ok := Redirects[r.URL.Path]; ok {
+ http.Redirect(w, r, newPath, http.StatusFound)
+ return
+ }
+
+ // Call the next handler
+ next.ServeHTTP(w, r)
+ })
+}
diff --git a/gno.land/pkg/gnoweb2/app.go b/gno.land/pkg/gnoweb2/app.go
new file mode 100644
index 00000000000..6dcbd7b952e
--- /dev/null
+++ b/gno.land/pkg/gnoweb2/app.go
@@ -0,0 +1,88 @@
+package gnoweb
+
+import (
+ "fmt"
+ "log/slog"
+ "net/http"
+
+ "github.com/gnolang/gno/gno.land/pkg/gnoclient"
+ "github.com/gnolang/gno/gno.land/pkg/service"
+ "github.com/gnolang/gno/tm2/pkg/bft/rpc/client"
+ "github.com/yuin/goldmark"
+)
+
+type AppConfig struct {
+ Remote string
+ RemoteHelp string
+ ChainID string
+ AssetsPath string
+}
+
+func NewDefaultAppConfig() *AppConfig {
+ const defaultRemote = "127.0.0.1:26657"
+
+ return &AppConfig{
+ Remote: defaultRemote, RemoteHelp: defaultRemote, // same as `Remote` by default
+ ChainID: "dev",
+ AssetsPath: "public",
+ }
+}
+
+type App struct {
+ mux *http.ServeMux
+ handler WebHandler
+}
+
+func (a *App) Router(w http.ResponseWriter, r *http.Request) {
+ a.mux.ServeHTTP(w, r)
+}
+
+func MakeApp(logger *slog.Logger, cfg *AppConfig) (http.Handler, error) {
+ md := goldmark.New()
+
+ client, err := client.NewHTTPClient(cfg.Remote)
+ if err != nil {
+ return nil, fmt.Errorf("unable to create http client: %W", err)
+ }
+
+ signer, err := generateWebSigner(cfg.ChainID)
+ if err != nil {
+ return nil, fmt.Errorf("unable to generate web signer: %w", err)
+ }
+
+ // Setup webservice
+ cl := gnoclient.Client{
+ Signer: signer,
+ RPCClient: client,
+ }
+ webcli := service.NewWebRender(logger, &cl, md)
+
+ var webConfig WebHandlerConfig
+
+ webConfig.RenderClient = webcli
+
+ // static meta
+ webConfig.Meta.AssetsPath = cfg.AssetsPath
+ webConfig.Meta.RemoteHelp = cfg.RemoteHelp
+ webConfig.Meta.ChaindID = cfg.ChainID
+
+ // Setup main handler
+ webhandler := NewWebHandler(logger, webConfig)
+
+ mux := http.NewServeMux()
+
+ // Setup Alias Middleware
+ mux.Handle("/", AliasAndRedirectMiddleware(webhandler))
+
+ // Setup asset path
+ mux.Handle(cfg.AssetsPath, AssetHandler())
+
+ return mux, nil
+}
+
+func generateWebSigner(chainid string) (gnoclient.Signer, error) {
+ mnemo := "index brass unknown lecture autumn provide royal shrimp elegant wink now zebra discover swarm act ill you bullet entire outdoor tilt usage gap multiply"
+ bip39Passphrase := ""
+ account, index := uint32(0), uint32(0)
+ return gnoclient.SignerFromBip39(mnemo, chainid, bip39Passphrase, account, index)
+}
diff --git a/gno.land/pkg/gnoweb2/components/breadcrumb.go b/gno.land/pkg/gnoweb2/components/breadcrumb.go
new file mode 100644
index 00000000000..9e7a97b2fae
--- /dev/null
+++ b/gno.land/pkg/gnoweb2/components/breadcrumb.go
@@ -0,0 +1,18 @@
+package components
+
+import (
+ "io"
+)
+
+type BreadcrumbPart struct {
+ Name string
+ Path string
+}
+
+type BreadcrumbData struct {
+ Parts []BreadcrumbPart
+}
+
+func RenderBreadcrumpComponent(w io.Writer, data BreadcrumbData) error {
+ return tmpl.ExecuteTemplate(w, "Breadcrumb", data)
+}
diff --git a/gno.land/pkg/gnoweb2/components/breadcrumb.gohtml b/gno.land/pkg/gnoweb2/components/breadcrumb.gohtml
new file mode 100644
index 00000000000..39b81f339c0
--- /dev/null
+++ b/gno.land/pkg/gnoweb2/components/breadcrumb.gohtml
@@ -0,0 +1,12 @@
+{{ define "breadcrumb" }}
+
+ {{- range $index, $part := .Parts }}
+ {{- if $index }}
+ -
+ {{- else }}
+
-
+ {{- end }}
+ {{ $part.Name }}
+ {{- end }}
+
+{{ end }}
\ No newline at end of file
diff --git a/gno.land/pkg/gnoweb2/components/help.go b/gno.land/pkg/gnoweb2/components/help.go
new file mode 100644
index 00000000000..7f6fa248a73
--- /dev/null
+++ b/gno.land/pkg/gnoweb2/components/help.go
@@ -0,0 +1,40 @@
+package components
+
+import (
+ "html/template"
+ "io"
+ "strings"
+
+ "github.com/gnolang/gno/gno.land/pkg/sdk/vm" // for error types
+)
+
+type HelpData struct {
+ RealmName string
+ Functions []vm.FunctionSignature
+ ChainId string
+ Remote string
+ PkgPath string
+}
+
+func registerHelpFuncs(funcs template.FuncMap) {
+ funcs["helpFuncSignature"] = func(fsig vm.FunctionSignature) (string, error) {
+ var fsigStr strings.Builder
+
+ fsigStr.WriteString(fsig.FuncName)
+ fsigStr.WriteRune('(')
+ for i, param := range fsig.Params {
+ if i > 0 {
+ fsigStr.WriteString(", ")
+ }
+ fsigStr.WriteString(param.Name)
+ }
+ fsigStr.WriteRune(')')
+
+ return fsigStr.String(), nil
+ }
+}
+
+
+func RenderHelpComponent(w io.Writer, data HelpData) error {
+ return tmpl.ExecuteTemplate(w, "renderHelp", data)
+}
diff --git a/gno.land/pkg/gnoweb2/components/help.gohtml b/gno.land/pkg/gnoweb2/components/help.gohtml
new file mode 100644
index 00000000000..49726eb2505
--- /dev/null
+++ b/gno.land/pkg/gnoweb2/components/help.gohtml
@@ -0,0 +1,113 @@
+{{ define "renderHelp" }}
+
+
+
+
+
+
+
+
{{ .RealmName }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ range .Functions }}
+
+
+ {{ .FuncName }}
+
+
+
+
Command
+
+
+
gnokey maketx call -pkgpath "{{ $.PkgPath }}" -func "{{ .FuncName }}" -gas-fee 1000000ugnot -gas-wanted 2000000 -send "" -broadcast -chainid "{{ $.ChainId }}"{{ range .Params }} -args ""{{ end }} -remote "{{ $.Remote }}" ADDRESSgnokey query -remote "{{ $.Remote }}" auth/accounts/ADDRESS
+gnokey maketx call -pkgpath "{{ $.PkgPath }}" -func "{{ .FuncName }}" -gas-fee 1000000ugnot -gas-wanted 2000000 -send "" {{ range .Params }} -args ""{{ end }} ADDRESS > call.tx
+gnokey sign -tx-path call.tx -chainid "{{ $.ChainId }}" -account-number ACCOUNTNUMBER -account-sequence SEQUENCENUMBER ADDRESS
+gnokey broadcast -remote "{{ $.Remote }}" call.tx
+
+
+
+ {{ end }}
+
+
+
+
+
+
+{{ end }}
diff --git a/gno.land/pkg/gnoweb2/components/index.go b/gno.land/pkg/gnoweb2/components/index.go
new file mode 100644
index 00000000000..782f99e9160
--- /dev/null
+++ b/gno.land/pkg/gnoweb2/components/index.go
@@ -0,0 +1,42 @@
+package components
+
+import (
+ "context"
+ "html/template"
+ "io"
+ "net/url"
+)
+
+type HeadData struct {
+ Title string
+ Description string
+ Canonical string
+ Image string
+ URL string
+ AdditionalLinks []struct {
+ Rel string
+ Href string
+ }
+}
+
+type HeaderData struct {
+ RealmPath string
+ Breadcrumb BreadcrumbData
+ WebQuery url.Values
+}
+
+type IndexData struct {
+ HeadData
+ HeaderData
+ Body template.HTML
+}
+
+func IndexComponent(data IndexData) Component {
+ return func(ctx context.Context, tmpl *template.Template, w io.Writer) error {
+ return tmpl.ExecuteTemplate(w, "index", data)
+ }
+}
+
+func RenderIndexComponent(w io.Writer, data IndexData) error {
+ return tmpl.ExecuteTemplate(w, "index", data)
+}
diff --git a/gno.land/pkg/gnoweb2/components/index.gohtml b/gno.land/pkg/gnoweb2/components/index.gohtml
new file mode 100644
index 00000000000..c54e6f4d7e3
--- /dev/null
+++ b/gno.land/pkg/gnoweb2/components/index.gohtml
@@ -0,0 +1,148 @@
+{{ define "index" }}
+
+ {{ template "head" .HeadData }}
+
+ {{ template "spritesvg" }}
+
+
+ {{ template "header" .HeaderData }}
+
+
+ {{ template "main" .Body }}
+
+
+ {{ template "footer" }}
+
+
+{{ end }}
+
+{{ define "head" }}
+
+
+
+ {{ .Title }}
+
+
+
+
+
+ {{ if .Canonical }}
+
+ {{ end }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{{ end }}
+
+{{ define "header" }}
+
+
+
+{{ end }}
+
+{{ define "main" }}
+ {{ . }}
+{{ end }}
+
+{{ define "footer" }}
+
+
+{{ end }}
diff --git a/gno.land/pkg/gnoweb2/components/logosvg.gohtml b/gno.land/pkg/gnoweb2/components/logosvg.gohtml
new file mode 100644
index 00000000000..5ebe6460ee3
--- /dev/null
+++ b/gno.land/pkg/gnoweb2/components/logosvg.gohtml
@@ -0,0 +1,21 @@
+{{ define "logosvg" }}
+
+{{ end }}
diff --git a/gno.land/pkg/gnoweb2/components/realm.go b/gno.land/pkg/gnoweb2/components/realm.go
new file mode 100644
index 00000000000..6b40cab07fd
--- /dev/null
+++ b/gno.land/pkg/gnoweb2/components/realm.go
@@ -0,0 +1,32 @@
+package components
+
+import (
+ "context"
+ "html/template"
+ "io"
+
+ "github.com/gnolang/gno/gno.land/pkg/markdown"
+)
+
+type RealmTOCData struct {
+ Items []*markdown.TocItem
+}
+
+func RealmTOCComponent(data *RealmTOCData) Component {
+ return func(ctx context.Context, tmpl *template.Template, w io.Writer) error {
+ return tmpl.ExecuteTemplate(w, "renderRealmToc", data)
+ }
+}
+
+func RenderRealmTOCComponent(w io.Writer, data *RealmTOCData) error {
+ return tmpl.ExecuteTemplate(w, "renderRealmToc", data)
+}
+
+type RealmData struct {
+ Content template.HTML
+ TocItems *RealmTOCData
+}
+
+func RenderRealmComponent(w io.Writer, data RealmData) error {
+ return tmpl.ExecuteTemplate(w, "renderRealm", data)
+}
diff --git a/gno.land/pkg/gnoweb2/components/realm.gohtml b/gno.land/pkg/gnoweb2/components/realm.gohtml
new file mode 100644
index 00000000000..c05edb48944
--- /dev/null
+++ b/gno.land/pkg/gnoweb2/components/realm.gohtml
@@ -0,0 +1,47 @@
+{{ define "renderRealmToc" }}
+
+{{ end }}
+
+{{ define "renderRealm" }}
+
+
+
+
+
+ {{ .Content }}
+
+
+
+{{ end }}
diff --git a/gno.land/pkg/gnoweb2/components/source.go b/gno.land/pkg/gnoweb2/components/source.go
new file mode 100644
index 00000000000..8478d4cb8d1
--- /dev/null
+++ b/gno.land/pkg/gnoweb2/components/source.go
@@ -0,0 +1,19 @@
+package components
+
+import (
+ "html/template"
+ "io"
+)
+
+type SourceData struct {
+ PkgPath string
+ Files []string
+ FileName string
+ FileSize float32
+ FileLines int
+ FileSource template.HTML
+}
+
+func RenderSourceComponent(w io.Writer, data SourceData) error {
+ return tmpl.ExecuteTemplate(w, "renderSource", data)
+}
diff --git a/gno.land/pkg/gnoweb2/components/source.gohtml b/gno.land/pkg/gnoweb2/components/source.gohtml
new file mode 100644
index 00000000000..cdfa8af09d4
--- /dev/null
+++ b/gno.land/pkg/gnoweb2/components/source.gohtml
@@ -0,0 +1,68 @@
+{{ define "renderSource" }}
+
+
+
+
+
+
+
+
{{ .FileName }}
+
+
+
+
+
+
+
+ {{ .FileSource }}
+
+
+
+
+{{ end }}
diff --git a/gno.land/pkg/gnoweb2/components/spritesvg.gohtml b/gno.land/pkg/gnoweb2/components/spritesvg.gohtml
new file mode 100644
index 00000000000..5c1b77fb1a7
--- /dev/null
+++ b/gno.land/pkg/gnoweb2/components/spritesvg.gohtml
@@ -0,0 +1,131 @@
+{{ define "spritesvg" }}
+
+{{ end }}
diff --git a/gno.land/pkg/gnoweb2/components/status.gohtml b/gno.land/pkg/gnoweb2/components/status.gohtml
new file mode 100644
index 00000000000..3995287f7f7
--- /dev/null
+++ b/gno.land/pkg/gnoweb2/components/status.gohtml
@@ -0,0 +1,5 @@
+{{ define "status" }}
+
+ {{ .Message }}
+
+{{ end }}
diff --git a/gno.land/pkg/gnoweb2/components/template.go b/gno.land/pkg/gnoweb2/components/template.go
new file mode 100644
index 00000000000..05abf559460
--- /dev/null
+++ b/gno.land/pkg/gnoweb2/components/template.go
@@ -0,0 +1,129 @@
+package components
+
+import (
+ "bytes"
+ "context"
+ "embed"
+ "html/template"
+ "io"
+ "net/url"
+)
+
+//go:embed *.gohtml
+var gohtml embed.FS
+
+var funcMap = template.FuncMap{
+ "noescape_string": func(in string) template.HTML {
+ return template.HTML(in)
+ },
+ "noescape_bytes": func(in []byte) template.HTML {
+ return template.HTML(in)
+ },
+ "queryHas": func(vals url.Values, key string) bool {
+ if vals == nil {
+ return false
+ }
+
+ return vals.Has(key)
+ },
+}
+
+var tmpl = template.New("web").Funcs(funcMap)
+
+func init() {
+ registerHelpFuncs(funcMap)
+ tmpl.Funcs(funcMap)
+
+ var err error
+ tmpl, err = tmpl.ParseFS(gohtml, "*.gohtml")
+ if err != nil {
+ panic("unable to parse embed tempalates: " + err.Error())
+ }
+}
+
+type Component func(ctx context.Context, tmpl *template.Template, w io.Writer) error
+
+func (c Component) Render(ctx context.Context, w io.Writer) error {
+ return RenderComponent(ctx, w, c)
+}
+
+func RenderComponent(ctx context.Context, w io.Writer, c Component) error {
+ var render *template.Template
+ funcmap := template.FuncMap{
+ "render": func(cf Component) (string, error) {
+ var buf bytes.Buffer
+ if err := cf(ctx, render, &buf); err != nil {
+ return "", err
+ }
+
+ return buf.String(), nil
+ },
+ }
+
+ render = tmpl.Funcs(funcmap)
+ return c(ctx, render, w)
+}
+
+type StatusData struct {
+ Message string
+}
+
+func RenderStatusComponent(w io.Writer, message string) error {
+ return tmpl.ExecuteTemplate(w, "status", StatusData{
+ Message: message,
+ })
+}
+
+
+
+// func (c *ComponentsTemplate) ExecuteTemplate(ctx context.Context, wr io.Writer, name string, data any) {
+// c.Template.Funcs(RenderFunc(ctx)).ExecuteTemplate(wr, name, data)
+// }
+
+// func Templates() (tmpl *template.Template) {
+// return tmpl
+// }
+
+// type Slot struct {
+// ID string
+// Html string
+// }
+
+// func StreamShadowRoot(w http.ResponseWriter, ss <-chan Slot) error {
+// w.(http.Flusher).Flush()
+// for slot := range ss {
+// if err := tmpl.ExecuteTemplate(w, "slot", nil); err != nil {
+// return fmt.Errorf("unable to execute slot template: %w", err)
+// }
+// }
+// }
+
+// func templateMain() {
+// tmpl, err := template.ParseFS(gohtml, "*.gohtml")
+// if err != nil {
+// panic(err)
+// }
+
+// http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+// data := struct {
+// Meta PageMetadata
+// Body template.HTML
+// }{
+// Meta: PageMetadata{
+// Title: "My Page Title",
+// Description: "A short description of my page.",
+// Canonical: "http://example.com",
+// Image: "http://example.com/image.jpg",
+// URL: "http://example.com",
+// },
+// Body: template.HTML("This is the main content of the page.
"),
+// }
+
+// err := tmpl.ExecuteTemplate(w, "index", data)
+// if err != nil {
+// http.Error(w, err.Error(), http.StatusInternalServerError)
+// }
+// })
+
+// http.ListenAndServe(":8080", nil)
+// }
diff --git a/gno.land/pkg/gnoweb2/components/util.gohtml b/gno.land/pkg/gnoweb2/components/util.gohtml
new file mode 100644
index 00000000000..269791c29a4
--- /dev/null
+++ b/gno.land/pkg/gnoweb2/components/util.gohtml
@@ -0,0 +1,9 @@
+{{ define "slot" }}
+{{.Html}}
+{{end}}
+
+{{define "tail"}}
+
+
+