diff --git a/gno.land/pkg/gnoweb/Makefile b/gno.land/pkg/gnoweb/Makefile index 8e8b6bf1a2ce..c8d662ec3b5a 100644 --- a/gno.land/pkg/gnoweb/Makefile +++ b/gno.land/pkg/gnoweb/Makefile @@ -13,7 +13,7 @@ input_css := frontend/css/input.css output_css := $(PUBLIC_DIR)/styles.css tw_version := 3.4.14 tw_config_path := frontend/css/tx.config.js -templates_files := $(shell find . -iname '*.gohtml') +templates_files := $(shell find . -iname '*.html') # static config src_dir_static := frontend/static @@ -79,7 +79,7 @@ dev: # Go server in development mode dev.gnoweb: generate - $(run_reflex) -s -r '.*\.go(html)?' -- \ + $(run_reflex) -s -r '.*\.(go|html)' -- \ go run ../../cmd/gnoweb -assets-dir=${PUBLIC_DIR} -chainid=${CHAIN_ID} -remote=${DEV_REMOTE} \ 2>&1 | $(run_logname) gnoweb diff --git a/gno.land/pkg/gnoweb/alias.go b/gno.land/pkg/gnoweb/alias.go index 06bb3941e41a..a837a2dcb49a 100644 --- a/gno.land/pkg/gnoweb/alias.go +++ b/gno.land/pkg/gnoweb/alias.go @@ -39,10 +39,10 @@ func AliasAndRedirectMiddleware(next http.Handler, analytics bool) http.Handler // Check if the request path matches a redirect if newPath, ok := Redirects[r.URL.Path]; ok { http.Redirect(w, r, newPath, http.StatusFound) - components.RenderRedirectComponent(w, components.RedirectData{ + components.RedirectView(components.RedirectData{ To: newPath, WithAnalytics: analytics, - }) + }).Render(w) return } diff --git a/gno.land/pkg/gnoweb/app.go b/gno.land/pkg/gnoweb/app.go index 455a9aafaf12..61773ef39af2 100644 --- a/gno.land/pkg/gnoweb/app.go +++ b/gno.land/pkg/gnoweb/app.go @@ -95,10 +95,10 @@ func NewRouter(logger *slog.Logger, cfg *AppConfig) (http.Handler, error) { if cfg.FaucetURL != "" { mux.Handle("/faucet", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, cfg.FaucetURL, http.StatusFound) - components.RenderRedirectComponent(w, components.RedirectData{ + components.RedirectView(components.RedirectData{ To: cfg.FaucetURL, WithAnalytics: cfg.Analytics, - }) + }).Render(w) })) } diff --git a/gno.land/pkg/gnoweb/components/breadcrumb.gohtml b/gno.land/pkg/gnoweb/components/breadcrumb.gohtml deleted file mode 100644 index 3824eb5894fe..000000000000 --- a/gno.land/pkg/gnoweb/components/breadcrumb.gohtml +++ /dev/null @@ -1,18 +0,0 @@ -{{ define "breadcrumb" }} -
    - {{- range $index, $part := .Parts }} - {{- if $index }} -
  1. - {{- else }} -
  2. - {{- end }} - {{ $part.Name }} -
  3. - {{- end }} - {{- if .Args }} -
  4. - {{ .Args }} -
  5. - {{- end }} -
-{{ end }} diff --git a/gno.land/pkg/gnoweb/components/component.go b/gno.land/pkg/gnoweb/components/component.go new file mode 100644 index 000000000000..7a7c8a3d160e --- /dev/null +++ b/gno.land/pkg/gnoweb/components/component.go @@ -0,0 +1,35 @@ +package components + +import ( + "io" +) + +type Component interface { + Render(w io.Writer) error +} + +type TemplateComponent struct { + name string + data any +} + +func (c *TemplateComponent) Render(w io.Writer) error { + return tmpl.ExecuteTemplate(w, c.name, c.data) +} + +func NewTemplateComponent(name string, data any) Component { + return &TemplateComponent{name: name, data: data} +} + +type readerComponent struct { + io.Reader +} + +func NewReaderComponent(reader io.Reader) Component { + return &readerComponent{reader} +} + +func (c *readerComponent) Render(w io.Writer) (err error) { + _, err = io.Copy(w, c) + return err +} diff --git a/gno.land/pkg/gnoweb/components/directory.go b/gno.land/pkg/gnoweb/components/directory.go deleted file mode 100644 index 6e47db3b2c40..000000000000 --- a/gno.land/pkg/gnoweb/components/directory.go +++ /dev/null @@ -1,15 +0,0 @@ -package components - -import ( - "io" -) - -type DirData struct { - PkgPath string - Files []string - FileCounter int -} - -func RenderDirectoryComponent(w io.Writer, data DirData) error { - return tmpl.ExecuteTemplate(w, "renderDir", data) -} diff --git a/gno.land/pkg/gnoweb/components/help.go b/gno.land/pkg/gnoweb/components/help.go deleted file mode 100644 index e819705006be..000000000000 --- a/gno.land/pkg/gnoweb/components/help.go +++ /dev/null @@ -1,51 +0,0 @@ -package components - -import ( - "html/template" - "io" - "strings" - - "github.com/gnolang/gno/gno.land/pkg/sdk/vm" // for error types -) - -type HelpData struct { - // Selected function - SelectedFunc string - SelectedArgs map[string]string - - 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 - } - - funcs["getSelectedArgValue"] = func(data HelpData, param vm.NamedType) (string, error) { - if data.SelectedArgs == nil { - return "", nil - } - - return data.SelectedArgs[param.Name], nil - } -} - -func RenderHelpComponent(w io.Writer, data HelpData) error { - return tmpl.ExecuteTemplate(w, "renderHelp", data) -} diff --git a/gno.land/pkg/gnoweb/components/help.gohtml b/gno.land/pkg/gnoweb/components/help.gohtml deleted file mode 100644 index 535cb56e9d61..000000000000 --- a/gno.land/pkg/gnoweb/components/help.gohtml +++ /dev/null @@ -1,110 +0,0 @@ -{{ define "renderHelp" }} - {{ $data := . }} -
-
-
-
-

{{ .RealmName }}

-
-
-
- - - - -
-
- - -
-
-
- -
- - {{ range .Functions }} -
-

{{ .FuncName }}

-
-
-

Params

-
- {{ $funcName := .FuncName }} - {{ range .Params }} -
-
- - -
-
- {{ end }} -
-
-
-
-

Command

-
- -
gnokey maketx call -pkgpath "{{ $.PkgPath }}" -func "{{ .FuncName }}" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid "{{ $.ChainId }}"{{ range .Params }} -args ""{{ end }} -remote "{{ $.Remote }}" ADDRESS
-
-
-
- {{ end }} - -
-
-
-{{ end }} diff --git a/gno.land/pkg/gnoweb/components/index.go b/gno.land/pkg/gnoweb/components/index.go deleted file mode 100644 index 0cc020ae2619..000000000000 --- a/gno.land/pkg/gnoweb/components/index.go +++ /dev/null @@ -1,47 +0,0 @@ -package components - -import ( - "context" - "html/template" - "io" - "net/url" -) - -type HeadData struct { - Title string - Description string - Canonical string - Image string - URL string - ChromaPath string - AssetsPath string - Analytics bool -} - -type HeaderData struct { - RealmPath string - Breadcrumb BreadcrumbData - WebQuery url.Values -} - -type FooterData struct { - Analytics bool - AssetsPath string -} - -type IndexData struct { - HeadData - HeaderData - FooterData - 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/gnoweb/components/index.gohtml b/gno.land/pkg/gnoweb/components/index.gohtml deleted file mode 100644 index a87decc14bfd..000000000000 --- a/gno.land/pkg/gnoweb/components/index.gohtml +++ /dev/null @@ -1,159 +0,0 @@ -{{ define "index" }} - - - {{ template "head" .HeadData }} - - {{ template "spritesvg" }} - - - {{ template "header" .HeaderData }} - - - {{ template "main" .Body }} - - - {{ template "footer" .FooterData }} - - -{{ end }} - -{{ define "head" }} - - - - {{ .Title }} - - - - - - - - - - - {{ if .Canonical }} - - {{ end }} - - - - - - - - - - - - - - - - - - - - - - - - - - -{{ end }} - -{{ define "header" }} -
- -
-{{ end }} - -{{ define "main" }} - {{ . }} -{{ end }} - -{{ define "footer" }} - - -{{- if .Analytics -}} {{- template "analytics" }} {{- end -}} - -{{- end }} - -{{- define "analytics" -}} - - - -{{- end -}} diff --git a/gno.land/pkg/gnoweb/components/layout_footer.go b/gno.land/pkg/gnoweb/components/layout_footer.go new file mode 100644 index 000000000000..05c83ba130be --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layout_footer.go @@ -0,0 +1,50 @@ +package components + +type FooterData struct { + Analytics bool + AssetsPath string + Sections []FooterSection +} + +type FooterLink struct { + Label string + URL string +} + +type FooterSection struct { + Title string + Links []FooterLink +} + +func EnrichFooterData(data FooterData) FooterData { + data.Sections = []FooterSection{ + { + Title: "Footer navigation", + Links: []FooterLink{ + {Label: "About", URL: "/about"}, + {Label: "Docs", URL: "https://docs.gno.land/"}, + {Label: "Faucet", URL: "https://faucet.gno.land/"}, + {Label: "Blog", URL: "https://gno.land/r/gnoland/blog"}, + {Label: "Status", URL: "https://status.gnoteam.com/"}, + }, + }, + { + Title: "Social media", + Links: []FooterLink{ + {Label: "GitHub", URL: "https://github.com/gnolang/gno"}, + {Label: "X", URL: "https://twitter.com/_gnoland"}, + {Label: "Discord", URL: "https://discord.gg/S8nKUqwkPn"}, + {Label: "YouTube", URL: "https://www.youtube.com/@_gnoland"}, + }, + }, + { + Title: "Legal", + Links: []FooterLink{ + {Label: "Terms", URL: "https://github.com/gnolang/gno/blob/master/LICENSE.md"}, + {Label: "Privacy", URL: "https://github.com/gnolang/gno/blob/master/LICENSE.md"}, + }, + }, + } + + return data +} diff --git a/gno.land/pkg/gnoweb/components/layout_header.go b/gno.land/pkg/gnoweb/components/layout_header.go new file mode 100644 index 000000000000..b85efde5f852 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layout_header.go @@ -0,0 +1,60 @@ +package components + +import ( + "net/url" +) + +type HeaderLink struct { + Label string + URL string + Icon string + IsActive bool +} + +type HeaderData struct { + RealmPath string + Breadcrumb BreadcrumbData + WebQuery url.Values + Links []HeaderLink +} + +func StaticHeaderLinks(realmPath string, webQuery url.Values) []HeaderLink { + return []HeaderLink{ + { + Label: "Content", + URL: realmPath, + Icon: "ico-info", + IsActive: isActive(webQuery, "Content"), + }, + { + Label: "Source", + URL: realmPath + "$source", + Icon: "ico-code", + IsActive: isActive(webQuery, "Source"), + }, + { + Label: "Docs", + URL: realmPath + "$help", + Icon: "ico-docs", + IsActive: isActive(webQuery, "Docs"), + }, + } +} + +func EnrichHeaderData(data HeaderData) HeaderData { + data.Links = StaticHeaderLinks(data.RealmPath, data.WebQuery) + return data +} + +func isActive(webQuery url.Values, label string) bool { + switch label { + case "Content": + return !(webQuery.Has("source") || webQuery.Has("help")) + case "Source": + return webQuery.Has("source") + case "Docs": + return webQuery.Has("help") + default: + return false + } +} diff --git a/gno.land/pkg/gnoweb/components/layout_index.go b/gno.land/pkg/gnoweb/components/layout_index.go new file mode 100644 index 000000000000..8b49e8f8adac --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layout_index.go @@ -0,0 +1,67 @@ +package components + +// Layout +const ( + SidebarLayout = "sidebar" + FullLayout = "full" +) + +type HeadData struct { + Title string + Description string + Canonical string + Image string + URL string + ChromaPath string + AssetsPath string + Analytics bool +} + +type IndexData struct { + HeadData + HeaderData + FooterData + BodyView *View +} + +type indexLayoutParams struct { + IndexData + + // Additional data + IsDevmodView bool + Layout string + ViewType string +} + +func IndexLayout(data IndexData) Component { + data.FooterData = EnrichFooterData(data.FooterData) + data.HeaderData = EnrichHeaderData(data.HeaderData) + + dataLayout := indexLayoutParams{ + IndexData: data, + // Set default value + Layout: FullLayout, + ViewType: data.BodyView.String(), + } + + switch data.BodyView.Type { + case RealmViewType: + dataLayout.Layout = SidebarLayout + + case HelpViewType: + dataLayout.IsDevmodView = true + dataLayout.Layout = SidebarLayout + + case SourceViewType: + dataLayout.IsDevmodView = true + dataLayout.Layout = SidebarLayout + + case DirectoryViewType: + dataLayout.IsDevmodView = true + + case StatusViewType: + dataLayout.IsDevmodView = true + } + + return NewTemplateComponent("index", dataLayout) +} diff --git a/gno.land/pkg/gnoweb/components/layouts/analytics.html b/gno.land/pkg/gnoweb/components/layouts/analytics.html new file mode 100644 index 000000000000..8782c80d7ae2 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layouts/analytics.html @@ -0,0 +1,7 @@ +{{- define "layout/analytics" -}} + + + +{{- end -}} diff --git a/gno.land/pkg/gnoweb/components/layouts/article.html b/gno.land/pkg/gnoweb/components/layouts/article.html new file mode 100644 index 000000000000..63862e9a4dc7 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layouts/article.html @@ -0,0 +1,3 @@ +{{ define "layout/article" }} +
{{ render .ComponentContent }}
+{{ end }} diff --git a/gno.land/pkg/gnoweb/components/layouts/aside.html b/gno.land/pkg/gnoweb/components/layouts/aside.html new file mode 100644 index 000000000000..2b33d7a8e2be --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layouts/aside.html @@ -0,0 +1,10 @@ +{{- define "layout/aside" }} + +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/layouts/footer.html b/gno.land/pkg/gnoweb/components/layouts/footer.html new file mode 100644 index 000000000000..1ae51add9953 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layouts/footer.html @@ -0,0 +1,23 @@ +{{ define "layouts/footer" }} + + + + + +{{- if .Analytics -}} {{- template "layout/analytics" }}{{- end -}} {{ end }} \ No newline at end of file diff --git a/gno.land/pkg/gnoweb/components/layouts/head.html b/gno.land/pkg/gnoweb/components/layouts/head.html new file mode 100644 index 000000000000..1b78eeeb3242 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layouts/head.html @@ -0,0 +1,44 @@ +{{ define "layouts/head" }} + + + + {{ .Title }} + + + + + + + + + + {{ if .Canonical }} + + {{ end }} + + + + + + + + + + + + + + + + + + + + + + + + + + +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/layouts/header.html b/gno.land/pkg/gnoweb/components/layouts/header.html new file mode 100644 index 000000000000..8a1433ccd1c3 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layouts/header.html @@ -0,0 +1,24 @@ +{{ define "layouts/header" }} +
+ +
+{{ end }} diff --git a/gno.land/pkg/gnoweb/components/layouts/index.html b/gno.land/pkg/gnoweb/components/layouts/index.html new file mode 100644 index 000000000000..c42a868e6faf --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layouts/index.html @@ -0,0 +1,29 @@ +{{ define "index" -}} + + + + {{ template "layouts/head" .IndexData.HeadData -}} + + {{ template "ui/icons" -}} + + + {{ template "layouts/header" .IndexData.HeaderData -}} + + +
+
+ {{ render .IndexData.BodyView -}} +
+
+ + + {{ template "layouts/footer" .FooterData -}} + + +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/logosvg.gohtml b/gno.land/pkg/gnoweb/components/logosvg.gohtml deleted file mode 100644 index 5ebe6460ee30..000000000000 --- a/gno.land/pkg/gnoweb/components/logosvg.gohtml +++ /dev/null @@ -1,21 +0,0 @@ -{{ define "logosvg" }} - - - - - - - - - - - - - - - - - - - -{{ end }} diff --git a/gno.land/pkg/gnoweb/components/realm.go b/gno.land/pkg/gnoweb/components/realm.go deleted file mode 100644 index 027760bb3822..000000000000 --- a/gno.land/pkg/gnoweb/components/realm.go +++ /dev/null @@ -1,32 +0,0 @@ -package components - -import ( - "context" - "html/template" - "io" - - "github.com/gnolang/gno/gno.land/pkg/gnoweb/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/gnoweb/components/realm.gohtml b/gno.land/pkg/gnoweb/components/realm.gohtml deleted file mode 100644 index 55f39ef36d77..000000000000 --- a/gno.land/pkg/gnoweb/components/realm.gohtml +++ /dev/null @@ -1,41 +0,0 @@ -{{ define "renderRealmToc" }} - -{{ end }} - -{{ define "renderRealm" }} -
-
- -
- - {{ .Content }} -
-
-
-{{ end }} diff --git a/gno.land/pkg/gnoweb/components/redirect.go b/gno.land/pkg/gnoweb/components/redirect.go deleted file mode 100644 index 873ddf56ff5a..000000000000 --- a/gno.land/pkg/gnoweb/components/redirect.go +++ /dev/null @@ -1,12 +0,0 @@ -package components - -import "io" - -type RedirectData struct { - To string - WithAnalytics bool -} - -func RenderRedirectComponent(w io.Writer, data RedirectData) error { - return tmpl.ExecuteTemplate(w, "renderRedirect", data) -} diff --git a/gno.land/pkg/gnoweb/components/source.go b/gno.land/pkg/gnoweb/components/source.go deleted file mode 100644 index 231707766576..000000000000 --- a/gno.land/pkg/gnoweb/components/source.go +++ /dev/null @@ -1,20 +0,0 @@ -package components - -import ( - "html/template" - "io" -) - -type SourceData struct { - PkgPath string - Files []string - FileName string - FileSize string - FileLines int - FileCounter int - FileSource template.HTML -} - -func RenderSourceComponent(w io.Writer, data SourceData) error { - return tmpl.ExecuteTemplate(w, "renderSource", data) -} diff --git a/gno.land/pkg/gnoweb/components/source.gohtml b/gno.land/pkg/gnoweb/components/source.gohtml deleted file mode 100644 index cb2430b504ad..000000000000 --- a/gno.land/pkg/gnoweb/components/source.gohtml +++ /dev/null @@ -1,57 +0,0 @@ -{{ define "renderSource" }} -
-
-
-
-

{{ .FileName }}

-
-
- {{ .FileSize }} · {{ .FileLines }} lines - -
-
- - -
-
- {{ .FileSource }} -
-
-
-
-{{ end }} diff --git a/gno.land/pkg/gnoweb/components/spritesvg.gohtml b/gno.land/pkg/gnoweb/components/spritesvg.gohtml deleted file mode 100644 index c061e97bf58f..000000000000 --- a/gno.land/pkg/gnoweb/components/spritesvg.gohtml +++ /dev/null @@ -1,125 +0,0 @@ -{{ define "spritesvg" }} - - - Search - - - - - - - Apps - - - - Documentation - - - - Source - - - - Content - - - - File - - - - Folder - - - - - - - - - - - Download - - - - Copy - - - - - - - - - - -{{ end }} diff --git a/gno.land/pkg/gnoweb/components/status.gohtml b/gno.land/pkg/gnoweb/components/status.gohtml deleted file mode 100644 index 2321d1110bd0..000000000000 --- a/gno.land/pkg/gnoweb/components/status.gohtml +++ /dev/null @@ -1,12 +0,0 @@ -{{ define "status" }} -
-
-
- gno land -

Error: {{ .Message }}

-

Something went wrong. Let’s find our way back!

- Go Back Home -
-
-
-{{ end }} diff --git a/gno.land/pkg/gnoweb/components/template.go b/gno.land/pkg/gnoweb/components/template.go index 9c08703f4603..ee2605d14367 100644 --- a/gno.land/pkg/gnoweb/components/template.go +++ b/gno.land/pkg/gnoweb/components/template.go @@ -2,76 +2,57 @@ package components import ( "bytes" - "context" "embed" + "fmt" "html/template" - "io" "net/url" ) -//go:embed *.gohtml -var gohtml embed.FS +//go:embed ui/*.html views/*.html layouts/*.html +var html embed.FS -var funcMap = template.FuncMap{ +var funcMap = template.FuncMap{} + +var tmpl = template.New("web") + +func registerCommonFuncs(funcs template.FuncMap) { // NOTE: this method does NOT escape HTML, use with caution - "noescape_string": func(in string) template.HTML { + funcs["noescape_string"] = func(in string) template.HTML { return template.HTML(in) //nolint:gosec - }, + } // NOTE: this method does NOT escape HTML, use with caution - "noescape_bytes": func(in []byte) template.HTML { + funcs["noescape_bytes"] = func(in []byte) template.HTML { return template.HTML(in) //nolint:gosec - }, - "queryHas": func(vals url.Values, key string) bool { + } + // NOTE: this method does NOT escape HTML, use with caution + // Render Component element into raw html element + funcs["render"] = func(comp Component) (template.HTML, error) { + var buf bytes.Buffer + if err := comp.Render(&buf); err != nil { + return "", fmt.Errorf("unable to render component: %w", err) + } + + return template.HTML(buf.String()), nil //nolint:gosec + } + funcs["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() { + // Register templates functions + registerCommonFuncs(funcMap) registerHelpFuncs(funcMap) tmpl.Funcs(funcMap) + // Parse templates var err error - tmpl, err = tmpl.ParseFS(gohtml, "*.gohtml") + tmpl, err = tmpl.ParseFS(html, "layouts/*.html", "ui/*.html", "views/*.html") 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, - }) -} diff --git a/gno.land/pkg/gnoweb/components/ui/breadcrumb.html b/gno.land/pkg/gnoweb/components/ui/breadcrumb.html new file mode 100644 index 000000000000..42f3186a4760 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/breadcrumb.html @@ -0,0 +1,19 @@ +{{ define "ui/breadcrumb" }} +
    + {{- range $index, $part := .Parts }} {{- if $index }} +
  1. {{- else }}
  2. + +
  3. + {{- end }} + {{ $part.Name }} +
  4. + {{- end }} {{- if .Args }} +
  5. + {{ .Args }} +
  6. + {{- end }} +
+{{ end }} diff --git a/gno.land/pkg/gnoweb/components/ui/btn_copy.html b/gno.land/pkg/gnoweb/components/ui/btn_copy.html new file mode 100644 index 000000000000..5f6e7c6f7a82 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/btn_copy.html @@ -0,0 +1,6 @@ +{{ define "ui/copy" }} + + + + +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/ui/code_wrapper.html b/gno.land/pkg/gnoweb/components/ui/code_wrapper.html new file mode 100644 index 000000000000..b05adb0532de --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/code_wrapper.html @@ -0,0 +1,3 @@ +{{ define "ui/code_wrapper" }} +
{{ render . }}
+{{ end }} \ No newline at end of file diff --git a/gno.land/pkg/gnoweb/components/ui/expend_label.html b/gno.land/pkg/gnoweb/components/ui/expend_label.html new file mode 100644 index 000000000000..53b92721c07f --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/expend_label.html @@ -0,0 +1,14 @@ +{{ define "ui/expend_label" }} + +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/ui/header_link.html b/gno.land/pkg/gnoweb/components/ui/header_link.html new file mode 100644 index 000000000000..e70c6a7f733c --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/header_link.html @@ -0,0 +1,14 @@ + +{{ define "ui/header_link" }} + +
+ + + + + +
+
+{{ end }} \ No newline at end of file diff --git a/gno.land/pkg/gnoweb/components/ui/help_function.html b/gno.land/pkg/gnoweb/components/ui/help_function.html new file mode 100644 index 000000000000..59c5286b0934 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/help_function.html @@ -0,0 +1,43 @@ +{{ define "ui/help_function" }} +{{ $data := . }} +{{ range .Functions }} +
+

{{ .FuncName }}

+
+
+

Params

+
+ {{ $funcName := .FuncName }} {{ range .Params }} +
+
+ + +
+
+ {{ end }} +
+
+
+
+

Command

+
+ +
gnokey maketx call -pkgpath "{{ $.PkgPath }}" -func "{{ .FuncName }}" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid "{{ $.ChainId }}"{{ range .Params }} -args ""{{ end }} -remote "{{ $.Remote }}" ADDRESS
+
+
+
+{{ end }} +{{ end }} \ No newline at end of file diff --git a/gno.land/pkg/gnoweb/components/ui/icons.html b/gno.land/pkg/gnoweb/components/ui/icons.html new file mode 100644 index 000000000000..feef8226be72 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/icons.html @@ -0,0 +1,124 @@ +{{ define "ui/icons" }} + + + Search + + + + + + + Apps + + + + Documentation + + + + Source + + + + Content + + + + File + + + + Folder + + + + + + + + + + + Download + + + + Copy + + + + + + + + + +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/ui/logo.html b/gno.land/pkg/gnoweb/components/ui/logo.html new file mode 100644 index 000000000000..a61beafd3cd6 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/logo.html @@ -0,0 +1,51 @@ +{{ define "ui/logo" }} + + + + + + + + + + + + + + + + + + + +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/ui/toc_generic.html b/gno.land/pkg/gnoweb/components/ui/toc_generic.html new file mode 100644 index 000000000000..3cd027f2a0cb --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/toc_generic.html @@ -0,0 +1,14 @@ +{{- define "ui/toc_generic" }} + +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/ui/toc_realm.html b/gno.land/pkg/gnoweb/components/ui/toc_realm.html new file mode 100644 index 000000000000..dc09db4d0e66 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/toc_realm.html @@ -0,0 +1,10 @@ +{{ define "ui/toc_realm" }} + +{{ end }} \ No newline at end of file diff --git a/gno.land/pkg/gnoweb/components/breadcrumb.go b/gno.land/pkg/gnoweb/components/ui_breadcrumb.go similarity index 100% rename from gno.land/pkg/gnoweb/components/breadcrumb.go rename to gno.land/pkg/gnoweb/components/ui_breadcrumb.go diff --git a/gno.land/pkg/gnoweb/components/view.go b/gno.land/pkg/gnoweb/components/view.go new file mode 100644 index 000000000000..f2da05b4b907 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/view.go @@ -0,0 +1,32 @@ +package components + +import ( + "fmt" + "io" +) + +type ViewType string + +type View struct { + Type ViewType + Component +} + +func (v *View) String() string { + return string(v.Type) +} + +func (v *View) Render(w io.Writer) error { + if err := v.Component.Render(w); err != nil { + return fmt.Errorf("view %q error: %w", string(v.Type), err) + } + + return nil +} + +func NewTemplateView(typ ViewType, name string, data any) *View { + return &View{ + Type: typ, + Component: NewTemplateComponent(name, data), + } +} diff --git a/gno.land/pkg/gnoweb/components/view_directory.go b/gno.land/pkg/gnoweb/components/view_directory.go new file mode 100644 index 000000000000..a105291a4ddc --- /dev/null +++ b/gno.land/pkg/gnoweb/components/view_directory.go @@ -0,0 +1,13 @@ +package components + +const DirectoryViewType ViewType = "dir-view" + +type DirData struct { + PkgPath string + Files []string + FileCounter int +} + +func DirectoryView(data DirData) *View { + return NewTemplateView(DirectoryViewType, "renderDir", data) +} diff --git a/gno.land/pkg/gnoweb/components/view_help.go b/gno.land/pkg/gnoweb/components/view_help.go new file mode 100644 index 000000000000..3473f5a3f420 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/view_help.go @@ -0,0 +1,83 @@ +package components + +import ( + "html/template" + + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" // for error types +) + +const HelpViewType ViewType = "help-view" + +type HelpData struct { + // Selected function + SelectedFunc string + SelectedArgs map[string]string + + RealmName string + Functions []vm.FunctionSignature + ChainId string + Remote string + PkgPath string +} + +type HelpTocData struct { + Icon string + Items []HelpTocItem +} + +type HelpTocItem struct { + Link string + Text string +} + +type helpViewParams struct { + HelpData + Article ArticleData + ComponentTOC Component +} + +func registerHelpFuncs(funcs template.FuncMap) { + funcs["getSelectedArgValue"] = func(data HelpData, param vm.NamedType) (string, error) { + if data.SelectedArgs == nil { + return "", nil + } + + return data.SelectedArgs[param.Name], nil + } +} + +func HelpView(data HelpData) *View { + tocData := HelpTocData{ + Icon: "code", + Items: make([]HelpTocItem, len(data.Functions)), + } + + for i, fn := range data.Functions { + sig := fn.FuncName + "(" + for j, param := range fn.Params { + if j > 0 { + sig += ", " + } + sig += param.Name + } + sig += ")" + + tocData.Items[i] = HelpTocItem{ + Link: "#func-" + fn.FuncName, + Text: sig, + } + } + + toc := NewTemplateComponent("ui/toc_generic", tocData) + content := NewTemplateComponent("ui/help_function", data) + viewData := helpViewParams{ + HelpData: data, + Article: ArticleData{ + ComponentContent: content, + Classes: "", + }, + ComponentTOC: toc, + } + + return NewTemplateView(HelpViewType, "renderHelp", viewData) +} diff --git a/gno.land/pkg/gnoweb/components/view_realm.go b/gno.land/pkg/gnoweb/components/view_realm.go new file mode 100644 index 000000000000..49372244fd42 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/view_realm.go @@ -0,0 +1,38 @@ +package components + +import ( + "github.com/gnolang/gno/gno.land/pkg/gnoweb/markdown" +) + +const RealmViewType ViewType = "realm-view" + +type RealmTOCData struct { + Items []*markdown.TocItem +} + +type RealmData struct { + ComponentContent Component + TocItems *RealmTOCData +} + +type ArticleData struct { + ComponentContent Component + Classes string +} + +type realmViewParams struct { + Article ArticleData + ComponentTOC Component +} + +func RealmView(data RealmData) *View { + viewData := realmViewParams{ + Article: ArticleData{ + ComponentContent: data.ComponentContent, + Classes: "realm-view lg:row-start-1", + }, + ComponentTOC: NewTemplateComponent("ui/toc_realm", data.TocItems), + } + + return NewTemplateView(RealmViewType, "renderRealm", viewData) +} diff --git a/gno.land/pkg/gnoweb/components/view_redirect.go b/gno.land/pkg/gnoweb/components/view_redirect.go new file mode 100644 index 000000000000..57d2f59e20ab --- /dev/null +++ b/gno.land/pkg/gnoweb/components/view_redirect.go @@ -0,0 +1,12 @@ +package components + +const RedirectViewType = "redirect-view" + +type RedirectData struct { + To string + WithAnalytics bool +} + +func RedirectView(data RedirectData) *View { + return NewTemplateView(RedirectViewType, "renderRedirect", data) +} diff --git a/gno.land/pkg/gnoweb/components/view_source.go b/gno.land/pkg/gnoweb/components/view_source.go new file mode 100644 index 000000000000..7eb5227dea19 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/view_source.go @@ -0,0 +1,66 @@ +package components + +const SourceViewType ViewType = "source-view" + +type SourceData struct { + PkgPath string + Files []string + FileName string + FileSize string + FileLines int + FileCounter int + FileSource Component +} + +type SourceTocData struct { + Icon string + Items []SourceTocItem +} + +type SourceTocItem struct { + Link string + Text string +} + +type sourceViewParams struct { + Article ArticleData + Files []string + FileName string + FileSize string + FileLines int + FileCounter int + PkgPath string + ComponentTOC Component +} + +func SourceView(data SourceData) *View { + tocData := SourceTocData{ + Icon: "file", + Items: make([]SourceTocItem, len(data.Files)), + } + + for i, file := range data.Files { + tocData.Items[i] = SourceTocItem{ + Link: data.PkgPath + "$source&file=" + file, + Text: file, + } + } + + toc := NewTemplateComponent("ui/toc_generic", tocData) + content := NewTemplateComponent("ui/code_wrapper", data.FileSource) + viewData := sourceViewParams{ + Article: ArticleData{ + ComponentContent: content, + Classes: "source-view col-span-1 lg:col-span-7 lg:row-start-2 pb-24 text-gray-900", + }, + ComponentTOC: toc, + Files: data.Files, + FileName: data.FileName, + FileSize: data.FileSize, + FileLines: data.FileLines, + FileCounter: data.FileCounter, + PkgPath: data.PkgPath, + } + + return NewTemplateView(SourceViewType, "renderSource", viewData) +} diff --git a/gno.land/pkg/gnoweb/components/view_status.go b/gno.land/pkg/gnoweb/components/view_status.go new file mode 100644 index 000000000000..46f998c45cb8 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/view_status.go @@ -0,0 +1,11 @@ +package components + +const StatusViewType ViewType = "status-view" + +type StatusData struct { + Message string +} + +func StatusComponent(message string) *View { + return NewTemplateView(StatusViewType, "status", StatusData{message}) +} diff --git a/gno.land/pkg/gnoweb/components/directory.gohtml b/gno.land/pkg/gnoweb/components/views/directory.html similarity index 85% rename from gno.land/pkg/gnoweb/components/directory.gohtml rename to gno.land/pkg/gnoweb/components/views/directory.html index 2254886f7af8..9aedd658defa 100644 --- a/gno.land/pkg/gnoweb/components/directory.gohtml +++ b/gno.land/pkg/gnoweb/components/views/directory.html @@ -1,7 +1,4 @@ {{ define "renderDir" }} -
-
- {{ $pkgpath := .PkgPath }}
@@ -31,8 +28,5 @@

{{ $pkgpath }}

-
- -
{{ end }} diff --git a/gno.land/pkg/gnoweb/components/views/help.html b/gno.land/pkg/gnoweb/components/views/help.html new file mode 100644 index 000000000000..b4bd7f92a561 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/views/help.html @@ -0,0 +1,38 @@ +{{ define "renderHelp" }} +{{ $data := . }} + +
+
+

{{ .RealmName }}

+
+
+
+ + + + +
+
+ + +
+
+
+ + +{{ with render .ComponentTOC }} +{{ template "layout/aside" .}} +{{ end }} + + +{{ template "layout/article" .Article}} +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/views/realm.html b/gno.land/pkg/gnoweb/components/views/realm.html new file mode 100644 index 000000000000..6e0a771e701f --- /dev/null +++ b/gno.land/pkg/gnoweb/components/views/realm.html @@ -0,0 +1,8 @@ +{{ define "renderRealm" }} + +{{ with render .ComponentTOC }} +{{ template "layout/aside" .}} +{{ end }} + + +{{ template "layout/article" .Article}} {{ end }} diff --git a/gno.land/pkg/gnoweb/components/redirect.gohtml b/gno.land/pkg/gnoweb/components/views/redirect.html similarity index 79% rename from gno.land/pkg/gnoweb/components/redirect.gohtml rename to gno.land/pkg/gnoweb/components/views/redirect.html index 45dac0981cd6..cd192b850a2f 100644 --- a/gno.land/pkg/gnoweb/components/redirect.gohtml +++ b/gno.land/pkg/gnoweb/components/views/redirect.html @@ -10,7 +10,7 @@ {{.To}} - {{- if .WithAnalytics -}} {{- template "analytics" }} {{- end -}} + {{- if .WithAnalytics -}} {{- template "layout/analytics" }} {{- end -}} -{{- end -}} \ No newline at end of file +{{- end -}} diff --git a/gno.land/pkg/gnoweb/components/views/source.html b/gno.land/pkg/gnoweb/components/views/source.html new file mode 100644 index 000000000000..44d39ca94cae --- /dev/null +++ b/gno.land/pkg/gnoweb/components/views/source.html @@ -0,0 +1,26 @@ +{{ define "renderSource" }} + + +{{ with render .ComponentTOC }} +{{ template "layout/aside" . }} +{{ end }} + + +
+
+

{{ .FileName }}

+
+
+ {{ .FileSize }} · {{ .FileLines }} lines + +
+
+ + +{{ template "layout/article" .Article }} +{{ end }} + + diff --git a/gno.land/pkg/gnoweb/components/views/status.html b/gno.land/pkg/gnoweb/components/views/status.html new file mode 100644 index 000000000000..ab068cbf7e46 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/views/status.html @@ -0,0 +1,8 @@ +{{ define "status" }} +
+ gno land +

Error: {{ .Message }}

+

Something went wrong. Let’s find our way back!

+ Go Back Home +
+{{ end }} diff --git a/gno.land/pkg/gnoweb/frontend/css/input.css b/gno.land/pkg/gnoweb/frontend/css/input.css index fb6a3dcd0999..63d83e0d9f11 100644 --- a/gno.land/pkg/gnoweb/frontend/css/input.css +++ b/gno.land/pkg/gnoweb/frontend/css/input.css @@ -39,199 +39,199 @@ @apply my-0; } - .realm-content { - @apply text-200 break-words pt-10; + .realm-view { + @apply text-200 break-words pt-6 lg:pt-10; } - .realm-content > *:first-child { + .realm-view > *:first-child { @apply !mt-0; } - .realm-content a { + .realm-view a { @apply text-green-600 font-medium hover:underline; } - .realm-content h1, - .realm-content h2, - .realm-content h3, - .realm-content h4 { + .realm-view h1, + .realm-view h2, + .realm-view h3, + .realm-view h4 { @apply text-gray-900 mt-12 leading-tight; } - .realm-content h2, - .realm-content h2 * { + .realm-view h2, + .realm-view h2 * { @apply font-bold; } - .realm-content h3, - .realm-content h3 *, - .realm-content h4, - .realm-content h4 * { + .realm-view h3, + .realm-view h3 *, + .realm-view h4, + .realm-view h4 * { @apply font-semibold; } - .realm-content h1 + h2, - .realm-content h2 + h3, - .realm-content h3 + h4 { + .realm-view h1 + h2, + .realm-view h2 + h3, + .realm-view h3 + h4 { @apply mt-4; } - .realm-content h1 { + .realm-view h1 { @apply text-800 font-bold; } - .realm-content h2 { + .realm-view h2 { @apply text-600; } - .realm-content h3 { + .realm-view h3 { @apply text-400 text-gray-600 mt-10; } - .realm-content h4 { + .realm-view h4 { @apply text-300 text-gray-600 font-medium my-6; } - .realm-content p { + .realm-view p { @apply my-5; } - .realm-content strong { + .realm-view strong { @apply font-bold text-gray-900; } - .realm-content strong * { + .realm-view strong * { @apply font-bold; } - .realm-content em { + .realm-view em { @apply italic-subtle; } - .realm-content blockquote { + .realm-view blockquote { @apply border-l-4 border-gray-300 pl-4 text-gray-600 italic-subtle my-4; } - .realm-content ul, - .realm-content ol { + .realm-view ul, + .realm-view ol { @apply pl-4 my-6; } - .realm-content ul li, - .realm-content ol li { + .realm-view ul li, + .realm-view ol li { @apply mb-2; } - .realm-content img { + .realm-view img { @apply max-w-full my-8; } - .realm-content figure { + .realm-view figure { @apply my-6 text-center; } - .realm-content figcaption { + .realm-view figcaption { @apply text-100 text-gray-600; } - .realm-content :not(pre) > code { + .realm-view :not(pre) > code { @apply bg-gray-100 px-1 py-0.5 rounded-sm text-[.96em] font-mono; } - .realm-content pre { + .realm-view pre { @apply bg-gray-50 p-4 rounded overflow-x-auto font-mono; } - .realm-content hr { + .realm-view hr { @apply border-t border-gray-100 my-10; } - .realm-content table { - @apply border-collapse my-8 block w-full max-w-full overflow-x-auto border-collapse; + .realm-view table { + @apply my-8 block w-full max-w-full overflow-x-auto border-collapse; } - .realm-content th, - .realm-content td { + .realm-view th, + .realm-view td { @apply border px-4 py-2 break-words whitespace-normal; } - .realm-content th { + .realm-view th { @apply bg-gray-100 font-bold; } - .realm-content caption { + .realm-view caption { @apply mt-2 text-100 text-gray-600 text-left; } - .realm-content q { + .realm-view q { @apply quotes; } - .realm-content q::before { + .realm-view q::before { content: open-quote; } - .realm-content q::after { + .realm-view q::after { content: close-quote; } - .realm-content ul ul, - .realm-content ul ol, - .realm-content ol ul, - .realm-content ol ol { + .realm-view ul ul, + .realm-view ul ol, + .realm-view ol ul, + .realm-view ol ol { @apply mt-3 mb-2 pl-4; } - .realm-content ul { + .realm-view ul { @apply list-disc; } - .realm-content ol { + .realm-view ol { @apply list-decimal; } - .realm-content abbr[title] { + .realm-view abbr[title] { @apply border-b border-dotted cursor-help; } - .realm-content details { + .realm-view details { @apply my-5; } - .realm-content summary { + .realm-view summary { @apply font-bold cursor-pointer; } - .realm-content a code { + .realm-view a code { @apply text-inherit; } - .realm-content video { + .realm-view video { @apply max-w-full my-8; } - .realm-content math { + .realm-view math { @apply font-mono; } - .realm-content small { + .realm-view small { @apply text-100; } - .realm-content del { + .realm-view del { @apply line-through; } - .realm-content sub { + .realm-view sub { @apply text-50 align-sub; } - .realm-content sup { + .realm-view sup { @apply text-50 align-super; } - .realm-content input, - .realm-content button { + .realm-view input, + .realm-view button { @apply px-4 py-2 border border-gray-300; } @@ -269,7 +269,7 @@ @apply block; } - :is(.main-header:has(#sidemenu-source:checked), .main-header:has(#sidemenu-docs:checked), .main-header:has(#sidemenu-meta:checked)) + main .realm-content, + :is(.main-header:has(#sidemenu-source:checked), .main-header:has(#sidemenu-docs:checked), .main-header:has(#sidemenu-meta:checked)) + main .realm-view, :is(.main-header:has(#sidemenu-source:checked), .main-header:has(#sidemenu-docs:checked), .main-header:has(#sidemenu-meta:checked)) .main-navigation { @apply md:col-span-6; } @@ -285,22 +285,32 @@ main :is(.source-code) > pre { @apply !bg-light overflow-scroll rounded py-4 md:py-8 px-1 md:px-3 font-mono text-100 md:text-200; } - main .realm-content > pre a { + main .realm-view > pre a { @apply hover:no-underline; } - main :is(.realm-content, .source-code) > pre .chroma-ln:target { + main :is(.realm-view, .source-code) > pre .chroma-ln:target { @apply !bg-transparent; } - main :is(.realm-content, .source-code) > pre .chroma-line:has(.chroma-ln:target), - main :is(.realm-content, .source-code) > pre .chroma-line:has(.chroma-lnlinks:hover), - main :is(.realm-content, .source-code) > pre .chroma-line:has(.chroma-ln:target) .chroma-cl, - main :is(.realm-content, .source-code) > pre .chroma-line:has(.chroma-lnlinks:hover) .chroma-cl { + main :is(.realm-view, .source-code) > pre .chroma-line:has(.chroma-ln:target), + main :is(.realm-view, .source-code) > pre .chroma-line:has(.chroma-lnlinks:hover), + main :is(.realm-view, .source-code) > pre .chroma-line:has(.chroma-ln:target) .chroma-cl, + main :is(.realm-view, .source-code) > pre .chroma-line:has(.chroma-lnlinks:hover) .chroma-cl { @apply !bg-gray-100 rounded; } - main :is(.realm-content, .source-code) > pre .chroma-ln { + main :is(.realm-view, .source-code) > pre .chroma-ln { @apply scroll-mt-24; } + + .dev-mode .toc-expend-btn { + @apply bg-gray-100 hover:bg-gray-50 cursor-pointer border lg:border-none lg:bg-transparent; + } + .dev-mode #sidebar-summary { + @apply bg-light lg:bg-transparent; + } + .dev-mode .toc-nav { + @apply font-mono; + } } @layer utilities { diff --git a/gno.land/pkg/gnoweb/frontend/css/tx.config.js b/gno.land/pkg/gnoweb/frontend/css/tx.config.js index 21b6a101dd6e..451688d7da67 100644 --- a/gno.land/pkg/gnoweb/frontend/css/tx.config.js +++ b/gno.land/pkg/gnoweb/frontend/css/tx.config.js @@ -1,7 +1,7 @@ const pxToRem = (px) => px / 16; export default { - content: ["./components/**/*.{gohtml,ts}"], + content: ["./components/**/*.{html,ts}"], theme: { screens: { xs: `${pxToRem(360)}rem`, @@ -68,5 +68,6 @@ export default { 900: `${pxToRem(42)}rem`, }, }, + safelist: ["realm-view", { pattern: /^realm-view/ }], plugins: [], }; diff --git a/gno.land/pkg/gnoweb/frontend/js/copy.ts b/gno.land/pkg/gnoweb/frontend/js/copy.ts index f3e5c7257831..5f559e7eb469 100644 --- a/gno.land/pkg/gnoweb/frontend/js/copy.ts +++ b/gno.land/pkg/gnoweb/frontend/js/copy.ts @@ -9,7 +9,7 @@ class Copy { private isAnimationRunning: boolean = false; private static SELECTORS = { - button: "[data-copy-btn]", + button: ".js-copy-btn", icon: `[data-copy-icon] > use`, content: (id: string) => `[data-copy-content="${id}"]`, }; diff --git a/gno.land/pkg/gnoweb/frontend/js/index.ts b/gno.land/pkg/gnoweb/frontend/js/index.ts index 3927f794b947..47b60f70360c 100644 --- a/gno.land/pkg/gnoweb/frontend/js/index.ts +++ b/gno.land/pkg/gnoweb/frontend/js/index.ts @@ -6,15 +6,15 @@ const modules: Record = { copy: { - selector: "[data-copy-btn]", + selector: ".js-copy-btn", path: "/public/js/copy.js", }, help: { - selector: "#help", + selector: ".js-help-view", path: "/public/js/realmhelp.js", }, searchBar: { - selector: "#header-searchbar", + selector: ".js-header-searchbar", path: "/public/js/searchbar.js", }, }; diff --git a/gno.land/pkg/gnoweb/frontend/js/realmhelp.ts b/gno.land/pkg/gnoweb/frontend/js/realmhelp.ts index d72102e2a2ea..3177e0342574 100644 --- a/gno.land/pkg/gnoweb/frontend/js/realmhelp.ts +++ b/gno.land/pkg/gnoweb/frontend/js/realmhelp.ts @@ -11,7 +11,7 @@ class Help { private funcList: HelpFunc[]; private static SELECTORS = { - container: "#help", + container: ".js-help-view", func: "[data-func]", addressInput: "[data-role='help-input-addr']", cmdModeSelect: "[data-role='help-select-mode']", diff --git a/gno.land/pkg/gnoweb/frontend/js/searchbar.ts b/gno.land/pkg/gnoweb/frontend/js/searchbar.ts index 6cca444aa0f3..a6859245af6c 100644 --- a/gno.land/pkg/gnoweb/frontend/js/searchbar.ts +++ b/gno.land/pkg/gnoweb/frontend/js/searchbar.ts @@ -8,7 +8,7 @@ class SearchBar { private baseUrl: string; private static SELECTORS = { - container: "#header-searchbar", + container: ".js-header-searchbar", inputSearch: "[data-role='header-input-search']", breadcrumb: "[data-role='header-breadcrumb-search']", }; diff --git a/gno.land/pkg/gnoweb/handler.go b/gno.land/pkg/gnoweb/handler.go index 2dc51d640292..3fdfc33909c7 100644 --- a/gno.land/pkg/gnoweb/handler.go +++ b/gno.land/pkg/gnoweb/handler.go @@ -4,8 +4,6 @@ import ( "bytes" "errors" "fmt" - "html/template" - "io" "log/slog" "net/http" "path/filepath" @@ -47,6 +45,13 @@ type WebHandler struct { Client WebClient } +// PageData groups layout, component, and dev mode information. +type PageData struct { + Layout string + Component string + IsDevmodView bool +} + // NewWebHandler creates a new WebHandler. func NewWebHandler(logger *slog.Logger, cfg WebHandlerConfig) (*WebHandler, error) { if err := cfg.validate(); err != nil { @@ -74,8 +79,6 @@ func (h *WebHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Get processes a GET HTTP request. func (h *WebHandler) Get(w http.ResponseWriter, r *http.Request) { - var body bytes.Buffer - start := time.Now() defer func() { h.Logger.Debug("request completed", @@ -94,29 +97,22 @@ func (h *WebHandler) Get(w http.ResponseWriter, r *http.Request) { }, } - status, err := h.renderPage(&body, r, &indexData) - if err != nil { - http.Error(w, "internal server error", http.StatusInternalServerError) - return - } - - w.WriteHeader(status) - - // NOTE: HTML escaping should have already been done by markdown rendering package - indexData.Body = template.HTML(body.String()) //nolint:gosec + var status int + status, indexData.BodyView = h.prepareIndexBodyView(r, &indexData) // Render the final page with the rendered body - if err = components.RenderIndexComponent(w, indexData); err != nil { - h.Logger.Error("failed to render index component", "err", err) + w.WriteHeader(status) + if err := components.IndexLayout(indexData).Render(w); err != nil { + h.Logger.Error("failed to render index component", "error", err) } } -// renderPage renders the page into the given buffer and prepares the index data. -func (h *WebHandler) renderPage(body *bytes.Buffer, r *http.Request, indexData *components.IndexData) (int, error) { +// prepareIndexBodyView prepares the data and main view for the index. +func (h *WebHandler) prepareIndexBodyView(r *http.Request, indexData *components.IndexData) (int, *components.View) { gnourl, err := ParseGnoURL(r.URL) if err != nil { - h.Logger.Warn("unable to parse url path", "path", r.URL.Path, "err", err) - return http.StatusNotFound, components.RenderStatusComponent(body, "invalid path") + h.Logger.Warn("unable to parse url path", "path", r.URL.Path, "error", err) + return http.StatusNotFound, components.StatusComponent("invalid path") } breadcrumb := generateBreadcrumbPaths(gnourl) @@ -129,69 +125,62 @@ func (h *WebHandler) renderPage(body *bytes.Buffer, r *http.Request, indexData * switch { case gnourl.IsRealm(), gnourl.IsPure(): - return h.GetPackagePage(body, gnourl) + return h.GetPackageView(gnourl) default: h.Logger.Debug("invalid path: path is neither a pure package or a realm") - return http.StatusBadRequest, components.RenderStatusComponent(body, "invalid path") + return http.StatusBadRequest, components.StatusComponent("invalid path") } } -// GetPackagePage handles package pages. -func (h *WebHandler) GetPackagePage(w io.Writer, gnourl *GnoURL) (int, error) { - h.Logger.Info("component render", "path", gnourl.Path, "args", gnourl.Args) - +// GetPackageView handles package pages. +func (h *WebHandler) GetPackageView(gnourl *GnoURL) (int, *components.View) { // Handle Help page if gnourl.WebQuery.Has("help") { - return h.GetHelpPage(w, gnourl) + return h.GetHelpView(gnourl) } // Handle Source page if gnourl.WebQuery.Has("source") || gnourl.IsFile() { - return h.GetSourcePage(w, gnourl) + return h.GetSourceView(gnourl) } // Handle Source page if gnourl.IsDir() || gnourl.IsPure() { - return h.GetDirectoryPage(w, gnourl) + return h.GetDirectoryView(gnourl) } - // Ultimately render realm content - return h.renderRealmContent(w, gnourl) + // Ultimately get realm view + return h.GetRealmView(gnourl) } -// renderRealmContent renders the content of a realm. -func (h *WebHandler) renderRealmContent(w io.Writer, gnourl *GnoURL) (int, error) { +func (h *WebHandler) GetRealmView(gnourl *GnoURL) (int, *components.View) { var content bytes.Buffer + meta, err := h.Client.RenderRealm(&content, gnourl.Path, gnourl.EncodeArgs()) if err != nil { - h.Logger.Error("unable to render realm", "err", err, "path", gnourl.EncodeArgs()) - return renderClientErrorStatusPage(w, gnourl, err) + h.Logger.Error("unable to render realm", "error", err, "path", gnourl.EncodeURL()) + return GetClientErrorStatusPage(gnourl, err) } - err = components.RenderRealmComponent(w, components.RealmData{ + return http.StatusOK, components.RealmView(components.RealmData{ TocItems: &components.RealmTOCData{ Items: meta.Toc.Items, }, + // NOTE: `RenderRealm` should ensure that HTML content is // sanitized before rendering - Content: template.HTML(content.String()), //nolint:gosec + ComponentContent: components.NewReaderComponent(&content), }) - if err != nil { - h.Logger.Error("unable to render template", "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") - } - - return http.StatusOK, nil } -// GetHelpPage renders the help page. -func (h *WebHandler) GetHelpPage(w io.Writer, gnourl *GnoURL) (int, error) { +func (h *WebHandler) GetHelpView(gnourl *GnoURL) (int, *components.View) { fsigs, err := h.Client.Functions(gnourl.Path) if err != nil { - h.Logger.Error("unable to fetch path functions", "err", err) - return renderClientErrorStatusPage(w, gnourl, err) + h.Logger.Error("unable to fetch path functions", "error", err) + return GetClientErrorStatusPage(gnourl, err) } + // Get selected function selArgs := make(map[string]string) selFn := gnourl.WebQuery.Get("func") if selFn != "" { @@ -210,36 +199,29 @@ func (h *WebHandler) GetHelpPage(w io.Writer, gnourl *GnoURL) (int, error) { } realmName := filepath.Base(gnourl.Path) - err = components.RenderHelpComponent(w, components.HelpData{ + return http.StatusOK, components.HelpView(components.HelpData{ SelectedFunc: selFn, SelectedArgs: selArgs, RealmName: realmName, - ChainId: h.Static.ChainId, // TODO: get chain domain and use that. + ChainId: h.Static.ChainId, PkgPath: filepath.Join(h.Static.Domain, gnourl.Path), Remote: h.Static.RemoteHelp, Functions: fsigs, }) - if err != nil { - h.Logger.Error("unable to render helper", "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") - } - - return http.StatusOK, nil } -// GetSource renders the source page. -func (h *WebHandler) GetSourcePage(w io.Writer, gnourl *GnoURL) (int, error) { +func (h *WebHandler) GetSourceView(gnourl *GnoURL) (int, *components.View) { pkgPath := gnourl.Path files, err := h.Client.Sources(pkgPath) if err != nil { - h.Logger.Error("unable to list sources file", "path", gnourl.Path, "err", err) - return renderClientErrorStatusPage(w, gnourl, err) + h.Logger.Error("unable to list sources file", "path", gnourl.Path, "error", err) + return GetClientErrorStatusPage(gnourl, err) } if len(files) == 0 { h.Logger.Debug("no files available", "path", gnourl.Path) - return http.StatusOK, components.RenderStatusComponent(w, "no files available") + return http.StatusOK, components.StatusComponent("no files available") } var fileName string @@ -250,76 +232,62 @@ func (h *WebHandler) GetSourcePage(w io.Writer, gnourl *GnoURL) (int, error) { } if fileName == "" { - fileName = files[0] // fallback on the first file if + fileName = files[0] // fallback on the first file } var source bytes.Buffer meta, err := h.Client.SourceFile(&source, pkgPath, fileName) if err != nil { - h.Logger.Error("unable to get source file", "file", fileName, "err", err) - return renderClientErrorStatusPage(w, gnourl, err) + h.Logger.Error("unable to get source file", "file", fileName, "error", err) + return GetClientErrorStatusPage(gnourl, err) } fileSizeStr := fmt.Sprintf("%.2f Kb", meta.SizeKb) - err = components.RenderSourceComponent(w, components.SourceData{ + return http.StatusOK, components.SourceView(components.SourceData{ PkgPath: gnourl.Path, Files: files, FileName: fileName, FileCounter: len(files), FileLines: meta.Lines, FileSize: fileSizeStr, - FileSource: template.HTML(source.String()), //nolint:gosec + FileSource: components.NewReaderComponent(&source), }) - if err != nil { - h.Logger.Error("unable to render helper", "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") - } - - return http.StatusOK, nil } -// GetDirectoryPage renders the directory page. -func (h *WebHandler) GetDirectoryPage(w io.Writer, gnourl *GnoURL) (int, error) { +func (h *WebHandler) GetDirectoryView(gnourl *GnoURL) (int, *components.View) { pkgPath := strings.TrimSuffix(gnourl.Path, "/") - files, err := h.Client.Sources(pkgPath) if err != nil { - h.Logger.Error("unable to list sources file", "path", gnourl.Path, "err", err) - return renderClientErrorStatusPage(w, gnourl, err) + h.Logger.Error("unable to list sources file", "path", gnourl.Path, "error", err) + return GetClientErrorStatusPage(gnourl, err) } if len(files) == 0 { h.Logger.Debug("no files available", "path", gnourl.Path) - return http.StatusOK, components.RenderStatusComponent(w, "no files available") + return http.StatusOK, components.StatusComponent("no files available") } - err = components.RenderDirectoryComponent(w, components.DirData{ + return http.StatusOK, components.DirectoryView(components.DirData{ PkgPath: gnourl.Path, Files: files, FileCounter: len(files), }) - if err != nil { - h.Logger.Error("unable to render directory", "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "not found") - } - - return http.StatusOK, nil } -func renderClientErrorStatusPage(w io.Writer, _ *GnoURL, err error) (int, error) { +func GetClientErrorStatusPage(_ *GnoURL, err error) (int, *components.View) { if err == nil { return http.StatusOK, nil } switch { case errors.Is(err, ErrClientPathNotFound): - return http.StatusNotFound, components.RenderStatusComponent(w, err.Error()) + return http.StatusNotFound, components.StatusComponent(err.Error()) case errors.Is(err, ErrClientBadRequest): - return http.StatusInternalServerError, components.RenderStatusComponent(w, "bad request") + return http.StatusInternalServerError, components.StatusComponent("bad request") case errors.Is(err, ErrClientResponse): fallthrough // XXX: for now fallback as internal error default: - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") + return http.StatusInternalServerError, components.StatusComponent("internal error") } } diff --git a/gno.land/pkg/gnoweb/public/js/copy.js b/gno.land/pkg/gnoweb/public/js/copy.js index 918a30b1ca3a..2bcfea74f844 100644 --- a/gno.land/pkg/gnoweb/public/js/copy.js +++ b/gno.land/pkg/gnoweb/public/js/copy.js @@ -1 +1 @@ -var s=class o{DOM;static FEEDBACK_DELAY=750;btnClicked=null;btnClickedIcons=[];isAnimationRunning=!1;static SELECTORS={button:"[data-copy-btn]",icon:"[data-copy-icon] > use",content:t=>`[data-copy-content="${t}"]`};constructor(){this.DOM={el:document.querySelector("main")},this.DOM.el?this.init():console.warn("Copy: Main container not found.")}init(){this.bindEvents()}bindEvents(){this.DOM.el?.addEventListener("click",this.handleClick.bind(this))}handleClick(t){let e=t.target.closest(o.SELECTORS.button);if(!e)return;this.btnClicked=e,this.btnClickedIcons=Array.from(e.querySelectorAll(o.SELECTORS.icon));let i=e.getAttribute("data-copy-btn");if(!i){console.warn("Copy: No content ID found on the button.");return}let r=this.DOM.el?.querySelector(o.SELECTORS.content(i));r?this.copyToClipboard(r,this.btnClickedIcons):console.warn(`Copy: No content found for ID "${i}".`)}sanitizeContent(t){let n=t.innerHTML.replace(/]*class="chroma-ln"[^>]*>[\s\S]*?<\/span>/g,""),e=document.createElement("div");return e.innerHTML=n,e.textContent?.trim()||""}toggleIcons(t){t.forEach(n=>{n.classList.toggle("hidden")})}showFeedback(t){!this.btnClicked||this.isAnimationRunning===!0||(this.isAnimationRunning=!0,this.toggleIcons(t),window.setTimeout(()=>{this.toggleIcons(t),this.isAnimationRunning=!1},o.FEEDBACK_DELAY))}async copyToClipboard(t,n){let e=this.sanitizeContent(t);if(!navigator.clipboard){console.error("Copy: Clipboard API is not supported in this browser."),this.showFeedback(n);return}try{await navigator.clipboard.writeText(e),this.showFeedback(n)}catch(i){console.error("Copy: Error while copying text.",i),this.showFeedback(n)}}},a=()=>new s;export{a as default}; +var s=class o{DOM;static FEEDBACK_DELAY=750;btnClicked=null;btnClickedIcons=[];isAnimationRunning=!1;static SELECTORS={button:".js-copy-btn",icon:"[data-copy-icon] > use",content:t=>`[data-copy-content="${t}"]`};constructor(){this.DOM={el:document.querySelector("main")},this.DOM.el?this.init():console.warn("Copy: Main container not found.")}init(){this.bindEvents()}bindEvents(){this.DOM.el?.addEventListener("click",this.handleClick.bind(this))}handleClick(t){let e=t.target.closest(o.SELECTORS.button);if(!e)return;this.btnClicked=e,this.btnClickedIcons=Array.from(e.querySelectorAll(o.SELECTORS.icon));let i=e.getAttribute("data-copy-btn");if(!i){console.warn("Copy: No content ID found on the button.");return}let r=this.DOM.el?.querySelector(o.SELECTORS.content(i));r?this.copyToClipboard(r,this.btnClickedIcons):console.warn(`Copy: No content found for ID "${i}".`)}sanitizeContent(t){let n=t.innerHTML.replace(/]*class="chroma-ln"[^>]*>[\s\S]*?<\/span>/g,""),e=document.createElement("div");return e.innerHTML=n,e.textContent?.trim()||""}toggleIcons(t){t.forEach(n=>{n.classList.toggle("hidden")})}showFeedback(t){!this.btnClicked||this.isAnimationRunning===!0||(this.isAnimationRunning=!0,this.toggleIcons(t),window.setTimeout(()=>{this.toggleIcons(t),this.isAnimationRunning=!1},o.FEEDBACK_DELAY))}async copyToClipboard(t,n){let e=this.sanitizeContent(t);if(!navigator.clipboard){console.error("Copy: Clipboard API is not supported in this browser."),this.showFeedback(n);return}try{await navigator.clipboard.writeText(e),this.showFeedback(n)}catch(i){console.error("Copy: Error while copying text.",i),this.showFeedback(n)}}},a=()=>new s;export{a as default}; diff --git a/gno.land/pkg/gnoweb/public/js/index.js b/gno.land/pkg/gnoweb/public/js/index.js index e990dd91f5f9..0230bbd64bc5 100644 --- a/gno.land/pkg/gnoweb/public/js/index.js +++ b/gno.land/pkg/gnoweb/public/js/index.js @@ -1 +1 @@ -(()=>{let s={copy:{selector:"[data-copy-btn]",path:"/public/js/copy.js"},help:{selector:"#help",path:"/public/js/realmhelp.js"},searchBar:{selector:"#header-searchbar",path:"/public/js/searchbar.js"}},r=async({selector:e,path:o})=>{if(document.querySelector(e))try{(await import(o)).default()}catch(t){console.error(`Error while loading script ${o}:`,t)}else console.warn(`Module not loaded: no element matches selector "${e}"`)},l=async()=>{let e=Object.values(s).map(o=>r(o));await Promise.all(e)};document.addEventListener("DOMContentLoaded",l)})(); +(()=>{let t={copy:{selector:".js-copy-btn",path:"/public/js/copy.js"},help:{selector:".js-help-view",path:"/public/js/realmhelp.js"},searchBar:{selector:".js-header-searchbar",path:"/public/js/searchbar.js"}},r=async({selector:e,path:o})=>{if(document.querySelector(e))try{(await import(o)).default()}catch(s){console.error(`Error while loading script ${o}:`,s)}else console.warn(`Module not loaded: no element matches selector "${e}"`)},l=async()=>{let e=Object.values(t).map(o=>r(o));await Promise.all(e)};document.addEventListener("DOMContentLoaded",l)})(); diff --git a/gno.land/pkg/gnoweb/public/js/realmhelp.js b/gno.land/pkg/gnoweb/public/js/realmhelp.js index 5d4a3feeba6e..68bcafbb75f0 100644 --- a/gno.land/pkg/gnoweb/public/js/realmhelp.js +++ b/gno.land/pkg/gnoweb/public/js/realmhelp.js @@ -1 +1 @@ -function d(s,e=250){let t;return function(...a){t!==void 0&&clearTimeout(t),t=setTimeout(()=>{s.apply(this,a)},e)}}var l=class s{DOM;funcList;static SELECTORS={container:"#help",func:"[data-func]",addressInput:"[data-role='help-input-addr']",cmdModeSelect:"[data-role='help-select-mode']"};constructor(){this.DOM={el:document.querySelector(s.SELECTORS.container),funcs:[],addressInput:null,cmdModeSelect:null},this.funcList=[],this.DOM.el?this.init():console.warn("Help: Main container not found.")}init(){let{el:e}=this.DOM;e&&(this.DOM.funcs=Array.from(e.querySelectorAll(s.SELECTORS.func)),this.DOM.addressInput=e.querySelector(s.SELECTORS.addressInput),this.DOM.cmdModeSelect=e.querySelector(s.SELECTORS.cmdModeSelect),this.funcList=this.DOM.funcs.map(t=>new o(t)),this.restoreAddress(),this.bindEvents())}restoreAddress(){let{addressInput:e}=this.DOM;if(e){let t=localStorage.getItem("helpAddressInput");t&&(e.value=t,this.funcList.forEach(a=>a.updateAddr(t)))}}bindEvents(){let{addressInput:e,cmdModeSelect:t}=this.DOM,a=d(r=>{let n=r.value;localStorage.setItem("helpAddressInput",n),this.funcList.forEach(i=>i.updateAddr(n))});e?.addEventListener("input",()=>a(e)),t?.addEventListener("change",r=>{let n=r.target;this.funcList.forEach(i=>i.updateMode(n.value))})}},o=class s{DOM;funcName;static SELECTORS={address:"[data-role='help-code-address']",args:"[data-role='help-code-args']",mode:"[data-code-mode]",paramInput:"[data-role='help-param-input']"};constructor(e){this.DOM={el:e,addrs:Array.from(e.querySelectorAll(s.SELECTORS.address)),args:Array.from(e.querySelectorAll(s.SELECTORS.args)),modes:Array.from(e.querySelectorAll(s.SELECTORS.mode)),paramInputs:Array.from(e.querySelectorAll(s.SELECTORS.paramInput))},this.funcName=e.dataset.func||null,this.initializeArgs(),this.bindEvents()}static sanitizeArgsInput(e){let t=e.dataset.param||"",a=e.value.trim();return t||console.warn("sanitizeArgsInput: param is missing in arg input dataset."),{paramName:t,paramValue:a}}bindEvents(){let e=d((t,a)=>{t&&this.updateArg(t,a)});this.DOM.el.addEventListener("input",t=>{let a=t.target;if(a.dataset.role==="help-param-input"){let{paramName:r,paramValue:n}=s.sanitizeArgsInput(a);e(r,n)}})}initializeArgs(){this.DOM.paramInputs.forEach(e=>{let{paramName:t,paramValue:a}=s.sanitizeArgsInput(e);t&&this.updateArg(t,a)})}updateArg(e,t){this.DOM.args.filter(a=>a.dataset.arg===e).forEach(a=>{a.textContent=t||""})}updateAddr(e){this.DOM.addrs.forEach(t=>{t.textContent=e.trim()||"ADDRESS"})}updateMode(e){this.DOM.modes.forEach(t=>{let a=t.dataset.codeMode===e;t.classList.toggle("inline",a),t.classList.toggle("hidden",!a),t.dataset.copyContent=a?`help-cmd-${this.funcName}`:""})}},p=()=>new l;export{p as default}; +function d(a,e=250){let t;return function(...s){t!==void 0&&clearTimeout(t),t=setTimeout(()=>{a.apply(this,s)},e)}}var l=class a{DOM;funcList;static SELECTORS={container:".js-help-view",func:"[data-func]",addressInput:"[data-role='help-input-addr']",cmdModeSelect:"[data-role='help-select-mode']"};constructor(){this.DOM={el:document.querySelector(a.SELECTORS.container),funcs:[],addressInput:null,cmdModeSelect:null},this.funcList=[],this.DOM.el?this.init():console.warn("Help: Main container not found.")}init(){let{el:e}=this.DOM;e&&(this.DOM.funcs=Array.from(e.querySelectorAll(a.SELECTORS.func)),this.DOM.addressInput=e.querySelector(a.SELECTORS.addressInput),this.DOM.cmdModeSelect=e.querySelector(a.SELECTORS.cmdModeSelect),this.funcList=this.DOM.funcs.map(t=>new o(t)),this.restoreAddress(),this.bindEvents())}restoreAddress(){let{addressInput:e}=this.DOM;if(e){let t=localStorage.getItem("helpAddressInput");t&&(e.value=t,this.funcList.forEach(s=>s.updateAddr(t)))}}bindEvents(){let{addressInput:e,cmdModeSelect:t}=this.DOM,s=d(r=>{let n=r.value;localStorage.setItem("helpAddressInput",n),this.funcList.forEach(i=>i.updateAddr(n))});e?.addEventListener("input",()=>s(e)),t?.addEventListener("change",r=>{let n=r.target;this.funcList.forEach(i=>i.updateMode(n.value))})}},o=class a{DOM;funcName;static SELECTORS={address:"[data-role='help-code-address']",args:"[data-role='help-code-args']",mode:"[data-code-mode]",paramInput:"[data-role='help-param-input']"};constructor(e){this.DOM={el:e,addrs:Array.from(e.querySelectorAll(a.SELECTORS.address)),args:Array.from(e.querySelectorAll(a.SELECTORS.args)),modes:Array.from(e.querySelectorAll(a.SELECTORS.mode)),paramInputs:Array.from(e.querySelectorAll(a.SELECTORS.paramInput))},this.funcName=e.dataset.func||null,this.initializeArgs(),this.bindEvents()}static sanitizeArgsInput(e){let t=e.dataset.param||"",s=e.value.trim();return t||console.warn("sanitizeArgsInput: param is missing in arg input dataset."),{paramName:t,paramValue:s}}bindEvents(){let e=d((t,s)=>{t&&this.updateArg(t,s)});this.DOM.el.addEventListener("input",t=>{let s=t.target;if(s.dataset.role==="help-param-input"){let{paramName:r,paramValue:n}=a.sanitizeArgsInput(s);e(r,n)}})}initializeArgs(){this.DOM.paramInputs.forEach(e=>{let{paramName:t,paramValue:s}=a.sanitizeArgsInput(e);t&&this.updateArg(t,s)})}updateArg(e,t){this.DOM.args.filter(s=>s.dataset.arg===e).forEach(s=>{s.textContent=t||""})}updateAddr(e){this.DOM.addrs.forEach(t=>{t.textContent=e.trim()||"ADDRESS"})}updateMode(e){this.DOM.modes.forEach(t=>{let s=t.dataset.codeMode===e;t.classList.toggle("inline",s),t.classList.toggle("hidden",!s),t.dataset.copyContent=s?`help-cmd-${this.funcName}`:""})}},p=()=>new l;export{p as default}; diff --git a/gno.land/pkg/gnoweb/public/js/searchbar.js b/gno.land/pkg/gnoweb/public/js/searchbar.js index e8012b9b6d9e..424e8f75db1e 100644 --- a/gno.land/pkg/gnoweb/public/js/searchbar.js +++ b/gno.land/pkg/gnoweb/public/js/searchbar.js @@ -1 +1 @@ -var n=class r{DOM;baseUrl;static SELECTORS={container:"#header-searchbar",inputSearch:"[data-role='header-input-search']",breadcrumb:"[data-role='header-breadcrumb-search']"};constructor(){this.DOM={el:document.querySelector(r.SELECTORS.container),inputSearch:null,breadcrumb:null},this.baseUrl=window.location.origin,this.DOM.el?this.init():console.warn("SearchBar: Main container not found.")}init(){let{el:e}=this.DOM;this.DOM.inputSearch=e?.querySelector(r.SELECTORS.inputSearch)??null,this.DOM.breadcrumb=e?.querySelector(r.SELECTORS.breadcrumb)??null,this.DOM.inputSearch||console.warn("SearchBar: Input element for search not found."),this.bindEvents()}bindEvents(){this.DOM.el?.addEventListener("submit",e=>{e.preventDefault(),this.searchUrl()})}searchUrl(){let e=this.DOM.inputSearch?.value.trim();if(e){let t=e;/^https?:\/\//i.test(t)||(t=`${this.baseUrl}${t.startsWith("/")?"":"/"}${t}`);try{window.location.href=new URL(t).href}catch{console.error("SearchBar: Invalid URL. Please enter a valid URL starting with http:// or https://.")}}else console.error("SearchBar: Please enter a URL to search.")}},i=()=>new n;export{i as default}; +var n=class r{DOM;baseUrl;static SELECTORS={container:".js-header-searchbar",inputSearch:"[data-role='header-input-search']",breadcrumb:"[data-role='header-breadcrumb-search']"};constructor(){this.DOM={el:document.querySelector(r.SELECTORS.container),inputSearch:null,breadcrumb:null},this.baseUrl=window.location.origin,this.DOM.el?this.init():console.warn("SearchBar: Main container not found.")}init(){let{el:e}=this.DOM;this.DOM.inputSearch=e?.querySelector(r.SELECTORS.inputSearch)??null,this.DOM.breadcrumb=e?.querySelector(r.SELECTORS.breadcrumb)??null,this.DOM.inputSearch||console.warn("SearchBar: Input element for search not found."),this.bindEvents()}bindEvents(){this.DOM.el?.addEventListener("submit",e=>{e.preventDefault(),this.searchUrl()})}searchUrl(){let e=this.DOM.inputSearch?.value.trim();if(e){let t=e;/^https?:\/\//i.test(t)||(t=`${this.baseUrl}${t.startsWith("/")?"":"/"}${t}`);try{window.location.href=new URL(t).href}catch{console.error("SearchBar: Invalid URL. Please enter a valid URL starting with http:// or https://.")}}else console.error("SearchBar: Please enter a URL to search.")}},i=()=>new n;export{i as default}; diff --git a/gno.land/pkg/gnoweb/public/styles.css b/gno.land/pkg/gnoweb/public/styles.css index a4c02629111d..ec575bb3735b 100644 --- a/gno.land/pkg/gnoweb/public/styles.css +++ b/gno.land/pkg/gnoweb/public/styles.css @@ -1,3 +1,3 @@ @font-face{font-family:Roboto;font-style:normal;font-weight:900;font-display:swap;src:url(fonts/roboto/roboto-mono-normal.woff2) format("woff2"),url(fonts/roboto/roboto-mono-normal.woff) format("woff")}@font-face{font-family:Inter var;font-weight:100 900;font-display:block;font-style:oblique 0deg 10deg;src:url(fonts/intervar/Intervar.woff2) format("woff2")}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: } -/*! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #bdbdbd}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#7c7c7c}input::placeholder,textarea::placeholder{opacity:1;color:#7c7c7c}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));font-family:Inter var,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji,sans-serif;font-size:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased;font-variant-ligatures:contextual common-ligatures;font-kerning:normal;text-rendering:optimizeLegibility}svg{max-height:100%;max-width:100%}form{margin-top:0;margin-bottom:0}.realm-content{overflow-wrap:break-word;padding-top:2.5rem;font-size:1rem}.realm-content>:first-child{margin-top:0!important}.realm-content a{font-weight:500;--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.realm-content a:hover{text-decoration-line:underline}.realm-content h1,.realm-content h2,.realm-content h3,.realm-content h4{margin-top:3rem;line-height:1.25;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content h2,.realm-content h2 *{font-weight:700}.realm-content h3,.realm-content h3 *,.realm-content h4,.realm-content h4 *{font-weight:600}.realm-content h1+h2,.realm-content h2+h3,.realm-content h3+h4{margin-top:1rem}.realm-content h1{font-size:2.375rem;font-weight:700}.realm-content h2{font-size:1.5rem}.realm-content h3{margin-top:2.5rem;font-size:1.25rem}.realm-content h3,.realm-content h4{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content h4{margin-top:1.5rem;margin-bottom:1.5rem;font-size:1.125rem;font-weight:500}.realm-content p{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content strong{font-weight:700;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content strong *{font-weight:700}.realm-content em{font-style:oblique 14deg}.realm-content blockquote{margin-top:1rem;margin-bottom:1rem;border-left-width:4px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-style:oblique 14deg}.realm-content ol,.realm-content ul{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}.realm-content ol li,.realm-content ul li{margin-bottom:.5rem}.realm-content img{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content figure{margin-top:1.5rem;margin-bottom:1.5rem;text-align:center}.realm-content figcaption{font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content :not(pre)>code{border-radius:.25rem;background-color:rgb(226 226 226/var(--tw-bg-opacity));padding:.125rem .25rem;font-size:.96em}.realm-content :not(pre)>code,.realm-content pre{--tw-bg-opacity:1;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content pre{overflow-x:auto;border-radius:.375rem;background-color:rgb(240 240 240/var(--tw-bg-opacity));padding:1rem}.realm-content hr{margin-top:2.5rem;margin-bottom:2.5rem;border-top-width:1px;--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.realm-content table{margin-top:2rem;margin-bottom:2rem;display:block;width:100%;max-width:100%;border-collapse:collapse;overflow-x:auto}.realm-content td,.realm-content th{white-space:normal;overflow-wrap:break-word;border-width:1px;padding:.5rem 1rem}.realm-content th{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));font-weight:700}.realm-content caption{margin-top:.5rem;text-align:left;font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content q{margin-top:1.5rem;margin-bottom:1.5rem;border-left-width:4px;--tw-border-opacity:1;border-left-color:rgb(204 204 204/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(85 85 85/var(--tw-text-opacity));font-style:oblique 14deg;quotes:"“" "”" "‘" "’"}.realm-content q:after,.realm-content q:before{margin-right:.25rem;font-size:1.5rem;--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity));content:open-quote;vertical-align:-.4rem}.realm-content q:after{content:close-quote}.realm-content q:before{content:open-quote}.realm-content q:after{content:close-quote}.realm-content ol ol,.realm-content ol ul,.realm-content ul ol,.realm-content ul ul{margin-top:.75rem;margin-bottom:.5rem;padding-left:1rem}.realm-content ul{list-style-type:disc}.realm-content ol{list-style-type:decimal}.realm-content abbr[title]{cursor:help;border-bottom-width:1px;border-style:dotted}.realm-content details{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content summary{cursor:pointer;font-weight:700}.realm-content a code{color:inherit}.realm-content video{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content math{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content small{font-size:.875rem}.realm-content del{text-decoration-line:line-through}.realm-content sub{vertical-align:sub;font-size:.75rem}.realm-content sup{vertical-align:super;font-size:.75rem}.realm-content button,.realm-content input{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}main :is(h1,h2,h3,h4){scroll-margin-top:6rem}::-moz-selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}::selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.sidemenu .peer:checked+label>svg{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.toc-expend-btn:has(#toc-expend:checked)+nav{display:block}.toc-expend-btn:has(#toc-expend:checked) .toc-expend-btn_ico{--tw-rotate:180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.main-header:has(#sidemenu-docs:checked)+main #sidebar #sidebar-docs,.main-header:has(#sidemenu-meta:checked)+main #sidebar #sidebar-meta,.main-header:has(#sidemenu-source:checked)+main #sidebar #sidebar-source,.main-header:has(#sidemenu-summary:checked)+main #sidebar #sidebar-summary{display:block}@media (min-width:40rem){:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .main-navigation,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main .realm-content{grid-column:span 6/span 6}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .sidemenu,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar{grid-column:span 4/span 4}}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar:before{position:absolute;top:0;left:-1.75rem;z-index:-1;display:block;height:100%;width:50vw;--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));--tw-content:"";content:var(--tw-content)}main :is(.source-code)>pre{overflow:scroll;border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(255 255 255/var(--tw-bg-opacity))!important;padding:1rem .25rem;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-size:.875rem}@media (min-width:40rem){main :is(.source-code)>pre{padding:2rem .75rem;font-size:1rem}}main .realm-content>pre a:hover{text-decoration-line:none}main :is(.realm-content,.source-code)>pre .chroma-ln:target{background-color:transparent!important}main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target) .chroma-cl,main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover) .chroma-cl{border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(226 226 226/var(--tw-bg-opacity))!important}main :is(.realm-content,.source-code)>pre .chroma-ln{scroll-margin-top:6rem}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.bottom-1{bottom:.25rem}.left-0{left:0}.right-2{right:.5rem}.right-3{right:.75rem}.top-0{top:0}.top-1\/2{top:50%}.top-14{top:3.5rem}.top-2{top:.5rem}.z-1{z-index:1}.z-max{z-index:9999}.col-span-1{grid-column:span 1/span 1}.col-span-10{grid-column:span 10/span 10}.col-span-3{grid-column:span 3/span 3}.col-span-7{grid-column:span 7/span 7}.row-span-1{grid-row:span 1/span 1}.row-start-1{grid-row-start:1}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-8{margin-bottom:2rem}.mr-10{margin-right:2.5rem}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-full{height:100%}.max-h-screen{max-height:100vh}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-full{width:100%}.min-w-2{min-width:.5rem}.min-w-48{min-width:12rem}.max-w-screen-max{max-width:98.75rem}.shrink-0{flex-shrink:0}.grow-\[2\]{flex-grow:2}.-translate-y-1\/2{--tw-translate-y:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-flow-dense{grid-auto-flow:dense}.auto-rows-min{grid-auto-rows:min-content}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-2{row-gap:.5rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.375rem}.rounded-sm{border-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-gray-100{--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgb(153 153 153/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.bg-light{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-px{padding-top:1px;padding-bottom:1px}.pb-24{padding-bottom:6rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pb-8{padding-bottom:2rem}.pl-4{padding-left:1rem}.pr-10{padding-right:2.5rem}.pt-0\.5{padding-top:.125rem}.pt-2{padding-top:.5rem}.font-mono{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.text-100{font-size:.875rem}.text-200{font-size:1rem}.text-50{font-size:.75rem}.text-600{font-size:1.5rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.leading-tight{line-height:1.25}.text-gray-300{--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(124 124 124/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(19 19 19/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.text-light{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.outline-none{outline:2px solid transparent;outline-offset:2px}.text-stroke{-webkit-text-stroke:currentColor;-webkit-text-stroke-width:.6px}.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.\*\:pl-0>*{padding-left:0}.before\:px-\[0\.18rem\]:before{content:var(--tw-content);padding-left:.18rem;padding-right:.18rem}.before\:text-gray-300:before{content:var(--tw-content);--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.before\:content-\[\'\/\'\]:before{--tw-content:"/";content:var(--tw-content)}.before\:content-\[\'\:\'\]:before{--tw-content:":";content:var(--tw-content)}.before\:content-\[\'open\'\]:before{--tw-content:"open";content:var(--tw-content)}.after\:pointer-events-none:after{content:var(--tw-content);pointer-events:none}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:bottom-0:after{content:var(--tw-content);bottom:0}.after\:left-0:after{content:var(--tw-content);left:0}.after\:top-0:after{content:var(--tw-content);top:0}.after\:block:after{content:var(--tw-content);display:block}.after\:h-1:after{content:var(--tw-content);height:.25rem}.after\:h-full:after{content:var(--tw-content);height:100%}.after\:w-full:after{content:var(--tw-content);width:100%}.after\:rounded-t-sm:after{content:var(--tw-content);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.after\:bg-gray-100:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.after\:bg-green-600:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.first\:border-t:first-child{border-top-width:1px}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.hover\:bg-green-600:hover{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.hover\:text-green-600:hover{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.hover\:text-light:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-gray-300:focus{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.focus\:border-l-gray-300:focus{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-gray-300{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-l-gray-300{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group.is-active .group-\[\.is-active\]\:text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.peer:checked~.peer-checked\:before\:content-\[\'close\'\]:before{--tw-content:"close";content:var(--tw-content)}.peer:focus-within~.peer-focus-within\:hidden{display:none}.has-\[ul\:empty\]\:hidden:has(ul:empty){display:none}.has-\[\:focus-within\]\:border-gray-300:has(:focus-within){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.has-\[\:focus\]\:border-gray-300:has(:focus){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}@media (min-width:30rem){.sm\:gap-6{gap:1.5rem}}@media (min-width:40rem){.md\:col-span-3{grid-column:span 3/span 3}.md\:mb-0{margin-bottom:0}.md\:h-4{height:1rem}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:pb-0{padding-bottom:0}}@media (min-width:51.25rem){.lg\:order-2{order:2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:row-span-2{grid-row:span 2/span 2}.lg\:row-start-1{grid-row-start:1}.lg\:row-start-2{grid-row-start:2}.lg\:mb-4{margin-bottom:1rem}.lg\:mt-0{margin-top:0}.lg\:mt-10{margin-top:2.5rem}.lg\:block{display:block}.lg\:hidden{display:none}.lg\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:justify-start{justify-content:flex-start}.lg\:justify-between{justify-content:space-between}.lg\:gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.lg\:border-none{border-style:none}.lg\:bg-transparent{background-color:transparent}.lg\:p-0{padding:0}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-2{padding-left:.5rem;padding-right:.5rem}.lg\:py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.lg\:pb-28{padding-bottom:7rem}.lg\:pt-2{padding-top:.5rem}.lg\:text-200{font-size:1rem}.lg\:font-semibold{font-weight:600}.lg\:hover\:bg-transparent:hover{background-color:transparent}}@media (min-width:63.75rem){.xl\:inline{display:inline}.xl\:hidden{display:none}.xl\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:flex-row{flex-direction:row}.xl\:items-center{align-items:center}.xl\:gap-20{gap:5rem}.xl\:gap-6{gap:1.5rem}.xl\:pt-0{padding-top:0}}@media (min-width:85.375rem){.xxl\:inline-block{display:inline-block}.xxl\:h-4{height:1rem}.xxl\:w-4{width:1rem}.xxl\:gap-20{gap:5rem}.xxl\:gap-x-32{-moz-column-gap:8rem;column-gap:8rem}.xxl\:pr-1{padding-right:.25rem}} \ No newline at end of file +/*! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #bdbdbd}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#7c7c7c}input::placeholder,textarea::placeholder{opacity:1;color:#7c7c7c}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));font-family:Inter var,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji,sans-serif;font-size:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased;font-variant-ligatures:contextual common-ligatures;font-kerning:normal;text-rendering:optimizeLegibility}svg{max-height:100%;max-width:100%}form{margin-top:0;margin-bottom:0}.realm-view{overflow-wrap:break-word;padding-top:1.5rem;font-size:1rem}@media (min-width:51.25rem){.realm-view{padding-top:2.5rem}}.realm-view>:first-child{margin-top:0!important}.realm-view a{font-weight:500;--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.realm-view a:hover{text-decoration-line:underline}.realm-view h1,.realm-view h2,.realm-view h3,.realm-view h4{margin-top:3rem;line-height:1.25;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-view h2,.realm-view h2 *{font-weight:700}.realm-view h3,.realm-view h3 *,.realm-view h4,.realm-view h4 *{font-weight:600}.realm-view h1+h2,.realm-view h2+h3,.realm-view h3+h4{margin-top:1rem}.realm-view h1{font-size:2.375rem;font-weight:700}.realm-view h2{font-size:1.5rem}.realm-view h3{margin-top:2.5rem;font-size:1.25rem}.realm-view h3,.realm-view h4{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-view h4{margin-top:1.5rem;margin-bottom:1.5rem;font-size:1.125rem;font-weight:500}.realm-view p{margin-top:1.25rem;margin-bottom:1.25rem}.realm-view strong{font-weight:700;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-view strong *{font-weight:700}.realm-view em{font-style:oblique 14deg}.realm-view blockquote{margin-top:1rem;margin-bottom:1rem;border-left-width:4px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-style:oblique 14deg}.realm-view ol,.realm-view ul{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}.realm-view ol li,.realm-view ul li{margin-bottom:.5rem}.realm-view img{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-view figure{margin-top:1.5rem;margin-bottom:1.5rem;text-align:center}.realm-view figcaption{font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-view :not(pre)>code{border-radius:.25rem;background-color:rgb(226 226 226/var(--tw-bg-opacity));padding:.125rem .25rem;font-size:.96em}.realm-view :not(pre)>code,.realm-view pre{--tw-bg-opacity:1;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-view pre{overflow-x:auto;border-radius:.375rem;background-color:rgb(240 240 240/var(--tw-bg-opacity));padding:1rem}.realm-view hr{margin-top:2.5rem;margin-bottom:2.5rem;border-top-width:1px;--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.realm-view table{margin-top:2rem;margin-bottom:2rem;display:block;width:100%;max-width:100%;border-collapse:collapse;overflow-x:auto}.realm-view td,.realm-view th{white-space:normal;overflow-wrap:break-word;border-width:1px;padding:.5rem 1rem}.realm-view th{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));font-weight:700}.realm-view caption{margin-top:.5rem;text-align:left;font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-view q{margin-top:1.5rem;margin-bottom:1.5rem;border-left-width:4px;--tw-border-opacity:1;border-left-color:rgb(204 204 204/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(85 85 85/var(--tw-text-opacity));font-style:oblique 14deg;quotes:"“" "”" "‘" "’"}.realm-view q:after,.realm-view q:before{margin-right:.25rem;font-size:1.5rem;--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity));content:open-quote;vertical-align:-.4rem}.realm-view q:after{content:close-quote}.realm-view q:before{content:open-quote}.realm-view q:after{content:close-quote}.realm-view ol ol,.realm-view ol ul,.realm-view ul ol,.realm-view ul ul{margin-top:.75rem;margin-bottom:.5rem;padding-left:1rem}.realm-view ul{list-style-type:disc}.realm-view ol{list-style-type:decimal}.realm-view abbr[title]{cursor:help;border-bottom-width:1px;border-style:dotted}.realm-view details{margin-top:1.25rem;margin-bottom:1.25rem}.realm-view summary{cursor:pointer;font-weight:700}.realm-view a code{color:inherit}.realm-view video{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-view math{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-view small{font-size:.875rem}.realm-view del{text-decoration-line:line-through}.realm-view sub{vertical-align:sub;font-size:.75rem}.realm-view sup{vertical-align:super;font-size:.75rem}.realm-view button,.realm-view input{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}main :is(h1,h2,h3,h4){scroll-margin-top:6rem}::-moz-selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}::selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.sidemenu .peer:checked+label>svg{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.toc-expend-btn:has(#toc-expend:checked)+nav{display:block}.toc-expend-btn:has(#toc-expend:checked) .toc-expend-btn_ico{--tw-rotate:180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.main-header:has(#sidemenu-docs:checked)+main #sidebar #sidebar-docs,.main-header:has(#sidemenu-meta:checked)+main #sidebar #sidebar-meta,.main-header:has(#sidemenu-source:checked)+main #sidebar #sidebar-source,.main-header:has(#sidemenu-summary:checked)+main #sidebar #sidebar-summary{display:block}@media (min-width:40rem){:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .main-navigation,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main .realm-view{grid-column:span 6/span 6}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .sidemenu,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar{grid-column:span 4/span 4}}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar:before{position:absolute;top:0;left:-1.75rem;z-index:-1;display:block;height:100%;width:50vw;--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));--tw-content:"";content:var(--tw-content)}main :is(.source-code)>pre{overflow:scroll;border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(255 255 255/var(--tw-bg-opacity))!important;padding:1rem .25rem;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-size:.875rem}@media (min-width:40rem){main :is(.source-code)>pre{padding:2rem .75rem;font-size:1rem}}main .realm-view>pre a:hover{text-decoration-line:none}main :is(.realm-view,.source-code)>pre .chroma-ln:target{background-color:transparent!important}main :is(.realm-view,.source-code)>pre .chroma-line:has(.chroma-ln:target),main :is(.realm-view,.source-code)>pre .chroma-line:has(.chroma-ln:target) .chroma-cl,main :is(.realm-view,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover),main :is(.realm-view,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover) .chroma-cl{border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(226 226 226/var(--tw-bg-opacity))!important}main :is(.realm-view,.source-code)>pre .chroma-ln{scroll-margin-top:6rem}.dev-mode .toc-expend-btn{cursor:pointer;border-width:1px;--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.dev-mode .toc-expend-btn:hover{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}@media (min-width:51.25rem){.dev-mode .toc-expend-btn{border-style:none;background-color:transparent}}.dev-mode #sidebar-summary{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}@media (min-width:51.25rem){.dev-mode #sidebar-summary{background-color:transparent}}.dev-mode .toc-nav{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.bottom-1{bottom:.25rem}.left-0{left:0}.right-2{right:.5rem}.right-3{right:.75rem}.top-0{top:0}.top-1\/2{top:50%}.top-14{top:3.5rem}.top-2{top:.5rem}.z-1{z-index:1}.z-max{z-index:9999}.col-span-1{grid-column:span 1/span 1}.col-span-10{grid-column:span 10/span 10}.col-span-3{grid-column:span 3/span 3}.col-span-7{grid-column:span 7/span 7}.row-span-1{grid-row:span 1/span 1}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-8{margin-bottom:2rem}.mr-10{margin-right:2.5rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-full{height:100%}.max-h-screen{max-height:100vh}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-full{width:100%}.min-w-2{min-width:.5rem}.min-w-48{min-width:12rem}.max-w-screen-max{max-width:98.75rem}.shrink-0{flex-shrink:0}.grow-\[2\]{flex-grow:2}.-translate-y-1\/2{--tw-translate-y:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-flow-dense{grid-auto-flow:dense}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-2{row-gap:.5rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.375rem}.rounded-sm{border-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-gray-100{--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgb(153 153 153/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.bg-light{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-px{padding-top:1px;padding-bottom:1px}.pb-24{padding-bottom:6rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pb-8{padding-bottom:2rem}.pl-4{padding-left:1rem}.pr-10{padding-right:2.5rem}.pt-0\.5{padding-top:.125rem}.pt-2{padding-top:.5rem}.font-mono{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.text-100{font-size:.875rem}.text-200{font-size:1rem}.text-50{font-size:.75rem}.text-600{font-size:1.5rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.leading-tight{line-height:1.25}.text-gray-300{--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(124 124 124/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(19 19 19/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.text-light{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.outline-none{outline:2px solid transparent;outline-offset:2px}.text-stroke{-webkit-text-stroke:currentColor;-webkit-text-stroke-width:.6px}.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.\*\:pl-0>*{padding-left:0}.before\:px-\[0\.18rem\]:before{content:var(--tw-content);padding-left:.18rem;padding-right:.18rem}.before\:text-gray-300:before{content:var(--tw-content);--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.before\:content-\[\'\/\'\]:before{--tw-content:"/";content:var(--tw-content)}.before\:content-\[\'\:\'\]:before{--tw-content:":";content:var(--tw-content)}.before\:content-\[\'open\'\]:before{--tw-content:"open";content:var(--tw-content)}.after\:pointer-events-none:after{content:var(--tw-content);pointer-events:none}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:bottom-0:after{content:var(--tw-content);bottom:0}.after\:left-0:after{content:var(--tw-content);left:0}.after\:top-0:after{content:var(--tw-content);top:0}.after\:block:after{content:var(--tw-content);display:block}.after\:h-1:after{content:var(--tw-content);height:.25rem}.after\:h-full:after{content:var(--tw-content);height:100%}.after\:w-full:after{content:var(--tw-content);width:100%}.after\:rounded-t-sm:after{content:var(--tw-content);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.after\:bg-gray-100:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.after\:bg-green-600:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.first\:mt-8:first-child{margin-top:2rem}.first\:border-t:first-child{border-top-width:1px}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.hover\:bg-green-600:hover{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.hover\:text-green-600:hover{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.hover\:text-light:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-gray-300:focus{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.focus\:border-l-gray-300:focus{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-gray-300{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-l-gray-300{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group.is-active .group-\[\.is-active\]\:text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.peer:checked~.peer-checked\:before\:content-\[\'close\'\]:before{--tw-content:"close";content:var(--tw-content)}.peer:focus-within~.peer-focus-within\:hidden{display:none}.has-\[ul\:empty\]\:hidden:has(ul:empty){display:none}.has-\[\:focus-within\]\:border-gray-300:has(:focus-within){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.has-\[\:focus\]\:border-gray-300:has(:focus){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}@media (min-width:30rem){.sm\:gap-6{gap:1.5rem}}@media (min-width:40rem){.md\:col-span-3{grid-column:span 3/span 3}.md\:mb-0{margin-bottom:0}.md\:h-4{height:1rem}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:pb-0{padding-bottom:0}}@media (min-width:51.25rem){.lg\:order-2{order:2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:row-span-2{grid-row:span 2/span 2}.lg\:row-start-1{grid-row-start:1}.lg\:mb-4{margin-bottom:1rem}.lg\:mt-0{margin-top:0}.lg\:mt-10{margin-top:2.5rem}.lg\:block{display:block}.lg\:hidden{display:none}.lg\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:justify-start{justify-content:flex-start}.lg\:justify-between{justify-content:space-between}.lg\:gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.lg\:border-none{border-style:none}.lg\:bg-transparent{background-color:transparent}.lg\:p-0{padding:0}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-2{padding-left:.5rem;padding-right:.5rem}.lg\:py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.lg\:pb-28{padding-bottom:7rem}.lg\:pt-2{padding-top:.5rem}.lg\:text-200{font-size:1rem}.lg\:font-semibold{font-weight:600}.lg\:first\:mt-0:first-child{margin-top:0}.lg\:hover\:bg-transparent:hover{background-color:transparent}}@media (min-width:63.75rem){.xl\:inline{display:inline}.xl\:hidden{display:none}.xl\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:flex-row{flex-direction:row}.xl\:items-center{align-items:center}.xl\:gap-20{gap:5rem}.xl\:gap-6{gap:1.5rem}.xl\:pt-0{padding-top:0}}@media (min-width:85.375rem){.xxl\:inline-block{display:inline-block}.xxl\:h-4{height:1rem}.xxl\:w-4{width:1rem}.xxl\:gap-20{gap:5rem}.xxl\:gap-x-32{-moz-column-gap:8rem;column-gap:8rem}.xxl\:pr-1{padding-right:.25rem}} \ No newline at end of file