diff --git a/examples/gno.land/p/demo/blog/blog.gno b/examples/gno.land/p/demo/blog/blog.gno index 62103b52885..1cf37b7ad3a 100644 --- a/examples/gno.land/p/demo/blog/blog.gno +++ b/examples/gno.land/p/demo/blog/blog.gno @@ -13,13 +13,28 @@ import ( ) type Blog struct { - Title string - Prefix string // i.e. r/gnoland/blog: - Posts avl.Tree // slug -> Post + Title string + Prefix string // i.e. r/gnoland/blog: + Posts avl.Tree // slug -> Post + NoBreadcrumb bool +} + +func (b Blog) RenderLastPostsWidget(limit int) string { + output := "" + i := 0 + b.Posts.Iterate("", "", func(key string, value interface{}) bool { + p := value.(*Post) + output += ufmt.Sprintf("- [%s](%s)\n", p.Title, p.URL()) + i++ + return i >= limit + }) + return output } func (b Blog) RenderHome(res *mux.ResponseWriter, req *mux.Request) { - res.Write(breadcrumb([]string{b.Title})) + if !b.NoBreadcrumb { + res.Write(breadcrumb([]string{b.Title})) + } if b.Posts.Size() == 0 { res.Write("No posts.") @@ -47,12 +62,14 @@ func (b Blog) RenderPost(res *mux.ResponseWriter, req *mux.Request) { } p := post.(*Post) - breadStr := breadcrumb([]string{ - ufmt.Sprintf("[%s](%s)", b.Title, b.Prefix), - "p", - p.Title, - }) - res.Write(breadStr) + if !b.NoBreadcrumb { + breadStr := breadcrumb([]string{ + ufmt.Sprintf("[%s](%s)", b.Title, b.Prefix), + "p", + p.Title, + }) + res.Write(breadStr) + } // output += ufmt.Sprintf("## [%s](%s)\n", p.Title, p.URL()) res.Write(p.Body + "\n\n") @@ -75,12 +92,14 @@ func (b Blog) RenderTag(res *mux.ResponseWriter, req *mux.Request) { return } - breadStr := breadcrumb([]string{ - ufmt.Sprintf("[%s](%s)", b.Title, b.Prefix), - "t", - slug, - }) - res.Write(breadStr) + if !b.NoBreadcrumb { + breadStr := breadcrumb([]string{ + ufmt.Sprintf("[%s](%s)", b.Title, b.Prefix), + "t", + slug, + }) + res.Write(breadStr) + } nb := 0 b.Posts.Iterate("", "", func(key string, value interface{}) bool { diff --git a/examples/gno.land/p/demo/ui/ui.gno b/examples/gno.land/p/demo/ui/ui.gno index efa185914a0..dd21d0510eb 100644 --- a/examples/gno.land/p/demo/ui/ui.gno +++ b/examples/gno.land/p/demo/ui/ui.gno @@ -1,6 +1,9 @@ package ui -import "strings" +import ( + "strconv" + "strings" +) type DOM struct { // metadata @@ -56,6 +59,17 @@ func (dom DOM) String() string { return output } +type Jumbotron []DomStringer + +func (j Jumbotron) String(dom DOM) string { + output := `
` + "\n\n" + for _, elem := range j { + output += elem.String(dom) + "\n" + } + output += `
` + "\n" + return output +} + // XXX: rename Element to Div? type Element []DomStringer @@ -88,6 +102,26 @@ func (b Breadcrumb) String(dom DOM) string { return output } +type Columns struct { + MaxWidth int + Columns []Element +} + +func (c *Columns) Append(elems ...Element) { + c.Columns = append(c.Columns, elems...) +} + +func (c Columns) String(dom DOM) string { + output := `
` + "\n" + for _, entry := range c.Columns { + output += `
` + "\n\n" + output += entry.String(dom) + output += "
\n" + } + output += "
\n" + return output +} + type Link struct { Text string Path string @@ -104,8 +138,14 @@ func (l Link) String(dom DOM) string { case l.Path != "" && l.URL != "": panic("a link should have a path or a URL, not both.") case l.Path != "": + if l.Text == "" { + l.Text = l.Path + } url = dom.Prefix + l.Path case l.URL != "": + if l.Text == "" { + l.Text = l.URL + } url = l.URL } @@ -151,6 +191,7 @@ type ( Italic string Code string Paragraph string + Quote string HR struct{} ) @@ -160,6 +201,7 @@ func (text H3) String(_ DOM) string { return "### " + string(text) + "\n" func (text H4) String(_ DOM) string { return "#### " + string(text) + "\n" } func (text H5) String(_ DOM) string { return "##### " + string(text) + "\n" } func (text H6) String(_ DOM) string { return "###### " + string(text) + "\n" } +func (text Quote) String(_ DOM) string { return "> " + string(text) + "\n" } func (text Bold) String(_ DOM) string { return "**" + string(text) + "**" } func (text Italic) String(_ DOM) string { return "_" + string(text) + "_" } func (text Paragraph) String(_ DOM) string { return "\n" + string(text) + "\n" } diff --git a/examples/gno.land/r/gnoland/blog/gnoblog.gno b/examples/gno.land/r/gnoland/blog/gnoblog.gno index 2982ea88489..cad84507614 100644 --- a/examples/gno.land/r/gnoland/blog/gnoblog.gno +++ b/examples/gno.land/r/gnoland/blog/gnoblog.gno @@ -23,3 +23,7 @@ func AddComment(postSlug, comment string) { func Render(path string) string { return b.Render(path) } + +func RenderLastPostsWidget(limit int) string { + return b.RenderLastPostsWidget(limit) +} diff --git a/examples/gno.land/r/gnoland/home/gno.mod b/examples/gno.land/r/gnoland/home/gno.mod new file mode 100644 index 00000000000..9192b4364d0 --- /dev/null +++ b/examples/gno.land/r/gnoland/home/gno.mod @@ -0,0 +1,8 @@ +module gno.land/r/gnoland/home + +require ( + "gno.land/r/gnoland/blog" v0.0.0-latest + "gno.land/p/demo/ufmt" v0.0.0-latest + "gno.land/p/demo/avl" v0.0.0-latest + "gno.land/p/demo/ui" v0.0.0-latest +) diff --git a/examples/gno.land/r/gnoland/home/home.gno b/examples/gno.land/r/gnoland/home/home.gno new file mode 100644 index 00000000000..5f2a5b9c4b5 --- /dev/null +++ b/examples/gno.land/r/gnoland/home/home.gno @@ -0,0 +1,257 @@ +package home + +import ( + "std" + + "gno.land/p/demo/ufmt" + "gno.land/p/demo/ui" + blog "gno.land/r/gnoland/blog" +) + +// XXX: p/demo/ui API is crappy, we need to make it more idiomatic +// XXX: use an updatable block system to update content from a DAO +// XXX: var blocks avl.Tree + +func Render(_ string) string { + dom := ui.DOM{Prefix: "r/gnoland/home:"} + dom.Title = "Welcome to Gno.land" + + // body + dom.Body.Append(introSection()...) + dom.Body.Append(ui.Jumbotron(worxDAO())) + dom.Body.Append(packageStaffPicks()...) + dom.Body.Append(ui.HR{}) + dom.Body.Append( + ui.Columns{3, []ui.Element{ + lastBlogposts(4), + upcomingEvents(4), + lastContributions(4), + }}, + ) + dom.Body.Append(ui.Jumbotron(discoverLinks())) + + // footer + dom.Footer.Append( + ui.Columns{2, []ui.Element{ + socialLinks(), + quoteOfTheBlock(), + }}, + ) + + // Testnet disclaimer + dom.Footer.Append( + ui.HR{}, + ui.Bold("This is a testnet."), + ui.Text("Package names are not guaranteed to be available for production."), + ) + + return dom.String() +} + +func lastBlogposts(limit int) ui.Element { + posts := blog.RenderLastPostsWidget(limit) + return ui.Element{ + ui.H3("Last Blogposts"), + ui.Text(posts), + } +} + +func lastContributions(limit int) ui.Element { + return ui.Element{ + ui.H3("Last Contributions"), + ui.Text("TODO: import r/gh"), + ui.Link{Text: "#1134", URL: "https://github.com/gnolang/gno/pull/1134"}, + } +} + +func upcomingEvents(limit int) ui.Element { + return ui.Element{ + ui.H3("Upcoming Events"), + ui.Text("TODO: import r/gnoland/events"), + } +} + +func introSection() ui.Element { + return ui.Element{ + ui.H3("An interpretation of the Golang (Go) programming language for advanced developers and intrepid pioneers to build succinct, composable smart contracts for social coordination."), + ui.Paragraph("If you’re concerned about information censorship and want to contribute to the #GnoWorldOrder, follow our socials to find out how."), + ui.Paragraph("Gno.land is in building mode. If you want to help lay the foundations of a fairer and freer world through innovative ideas and exceptional code, join us today."), + } +} + +func worxDAO() ui.Element { + // WorxDAO + // XXX(manfred): please, let me finish a v0, then we can iterate + // highest level == highest responsibility + // teams are responsible for components they don't owne + // flag : realm maintainers VS facilitators + // teams + // committee of trustees to create the directory + // each directory is a name, has a parent and have groups + // homepage team - blocks aggregating events + // XXX: TODO + /*` + # Directory + + * gno.land (owned by group) + * + * gnovm + * gnolang (language) + * gnovm + - current challenges / concerns / issues + * tm2 + * amino + * + + ## Contributors + ``*/ + return ui.Element{ + ui.H3("WorxDAO (WIP)"), + ui.Text(`- A + - A1 + - A1A + - A1B + - A2 + - A3 + - A3A + - A3A1 +- B +- C`), + } +} + +func quoteOfTheBlock() ui.Element { + quotes := []string{ + "Gno is for Truth.", + "Gno is for Social Coordination.", + "Gno is _not only_ for DeFi.", + "Now, you Gno.", + "Come for the Go, Stay for the Gno.", + } + height := std.GetHeight() + idx := int(height) % len(quotes) + qotb := quotes[idx] + + return ui.Element{ + ui.H3(ufmt.Sprintf("Quote of the ~Day~Block#%d", height)), + ui.Quote(qotb), + } +} + +func socialLinks() ui.Element { + return ui.Element{ + ui.H3("Socials"), + ui.BulletList{ + // XXX: improve UI to support a nice GO api for such links + ui.Text("Check out our [community projects](https://github.com/gnolang/awesome-gno)"), + ui.Text("![Discord](static/img/ico-discord.svg) [Discord](https://discord.gg/S8nKUqwkPn)"), + ui.Text("![Twitter](static/img/ico-twitter.svg) [Twitter](https://twitter.com/_gnoland)"), + ui.Text("![Youtube](static/img/ico-youtube.svg) [Youtube](https://www.youtube.com/@_gnoland)"), + ui.Text("![Telegram](static/img/ico-telegram.svg) [Telegram](https://t.me/gnoland)"), + }, + } +} + +func packageStaffPicks() ui.Element { + // XXX: make it modifiable from a DAO + return ui.Element{ + ui.H3("Explore New Packages and Realms"), + ui.Columns{ + 3, + []ui.Element{ + { + ui.H4("r/gnoland"), + ui.BulletList{ + ui.Link{URL: "r/gnoland/blog"}, + ui.Link{URL: "r/gnoland/dao"}, + ui.Link{URL: "r/gnoland/faucet"}, + ui.Link{URL: "r/gnoland/home"}, + ui.Link{URL: "r/gnoland/pages"}, + }, + ui.H4("r/system"), + ui.BulletList{ + ui.Link{URL: "r/system/names"}, + ui.Link{URL: "r/system/rewards"}, + ui.Link{URL: "r/system/validators"}, + }, + }, { + ui.H4("r/demo"), + ui.BulletList{ + ui.Link{URL: "r/demo/boards"}, + ui.Link{URL: "r/demo/users"}, + ui.Link{URL: "r/demo/banktest"}, + ui.Link{URL: "r/demo/foo20"}, + ui.Link{URL: "r/demo/foo721"}, + ui.Link{URL: "r/demo/microblog"}, + ui.Link{URL: "r/demo/nft"}, + ui.Link{URL: "r/demo/types"}, + ui.Link{URL: "r/demo/art"}, + ui.Link{URL: "r/demo/groups"}, + ui.Text("..."), + }, + }, { + ui.H4("p/demo"), + ui.BulletList{ + ui.Link{URL: "p/demo/avl"}, + ui.Link{URL: "p/demo/blog"}, + ui.Link{URL: "p/demo/ui"}, + ui.Link{URL: "p/demo/ufmt"}, + ui.Link{URL: "p/demo/merkle"}, + ui.Link{URL: "p/demo/bf"}, + ui.Link{URL: "p/demo/flow"}, + ui.Link{URL: "p/demo/gnode"}, + ui.Link{URL: "p/demo/grc/grc20"}, + ui.Link{URL: "p/demo/grc/grc721"}, + ui.Text("..."), + }, + }, + }, + }, + } +} + +func discoverLinks() ui.Element { + return ui.Element{ + ui.Text(`
+
+ +### Learn about Gno.land + +- [About](/about) +- [GitHub](https://github.com/gnolang) +- [Subscribe](#subscribe) +- [Tokenomics (soon)](#) +- [Blog](/blog) +- [Events](/events) +- [Partners, Fund, Grants](/partners) + +
+ +
+ +### Build with Gnolang + +- [Gno dev with CLI (soon)](#) +- [Explore the Universe](/ecosystem) +- [Test in the browser (soon)](#) +- [About the Gnolang Language](/gnolang) +- [Docs/ Tutorials](https://github.com/gnolang) +- [Gno by example](https://gno-by-example.com/) +- [Getting started video (soon)](#) + +
+
+ +### Explore the universe + +- [Discover demo packages](https://github.com/gnolang/gno/tree/master/examples) +- [Install Gno Key instructions](/r/demo/boards:testboard/5) +- [Testnets 3](https://test3.gno.land/) +- [Testnets 2](https://test2.gno.land/) +- [Explorer links(soon)](#) +- [Testnet Tokens (faucet)](https://test3.gno.land/faucet) + +
+
`), + } +} diff --git a/examples/gno.land/r/gnoland/home/home_filetest.gno b/examples/gno.land/r/gnoland/home/home_filetest.gno new file mode 100644 index 00000000000..1fffc11792f --- /dev/null +++ b/examples/gno.land/r/gnoland/home/home_filetest.gno @@ -0,0 +1,188 @@ +package main + +import "gno.land/r/gnoland/home" + +func main() { + println(home.Render("")) +} + +// Output: +// # Welcome to Gno.land +// +// ### An interpretation of the Golang (Go) programming language for advanced developers and intrepid pioneers to build succinct, composable smart contracts for social coordination. +// +// +// If you’re concerned about information censorship and want to contribute to the #GnoWorldOrder, follow our socials to find out how. +// +// +// Gno.land is in building mode. If you want to help lay the foundations of a fairer and freer world through innovative ideas and exceptional code, join us today. +// +//
+// +// ### WorxDAO (WIP) +// +// - A +// - A1 +// - A1A +// - A1B +// - A2 +// - A3 +// - A3A +// - A3A1 +// - B +// - C +//
+// +// ### Explore New Packages and Realms +// +//
+//
+// +// #### r/gnoland +// +// - [r/gnoland/blog](r/gnoland/blog) +// - [r/gnoland/dao](r/gnoland/dao) +// - [r/gnoland/faucet](r/gnoland/faucet) +// - [r/gnoland/home](r/gnoland/home) +// - [r/gnoland/pages](r/gnoland/pages) +// +// #### r/system +// +// - [r/system/names](r/system/names) +// - [r/system/rewards](r/system/rewards) +// - [r/system/validators](r/system/validators) +// +//
+//
+// +// #### r/demo +// +// - [r/demo/boards](r/demo/boards) +// - [r/demo/users](r/demo/users) +// - [r/demo/banktest](r/demo/banktest) +// - [r/demo/foo20](r/demo/foo20) +// - [r/demo/foo721](r/demo/foo721) +// - [r/demo/microblog](r/demo/microblog) +// - [r/demo/nft](r/demo/nft) +// - [r/demo/types](r/demo/types) +// - [r/demo/art](r/demo/art) +// - [r/demo/groups](r/demo/groups) +// - ... +// +//
+//
+// +// #### p/demo +// +// - [p/demo/avl](p/demo/avl) +// - [p/demo/blog](p/demo/blog) +// - [p/demo/ui](p/demo/ui) +// - [p/demo/ufmt](p/demo/ufmt) +// - [p/demo/merkle](p/demo/merkle) +// - [p/demo/bf](p/demo/bf) +// - [p/demo/flow](p/demo/flow) +// - [p/demo/gnode](p/demo/gnode) +// - [p/demo/grc/grc20](p/demo/grc/grc20) +// - [p/demo/grc/grc721](p/demo/grc/grc721) +// - ... +// +//
+//
+// +// +// --- +// +//
+//
+// +// ### Last Blogposts +// +// +//
+//
+// +// ### Upcoming Events +// +// TODO: import r/gnoland/events +//
+//
+// +// ### Last Contributions +// +// TODO: import r/gh +// [#1134](https://github.com/gnolang/gno/pull/1134) +//
+//
+// +//
+// +//
+//
+// +// ### Learn about Gno.land +// +// - [About](/about) +// - [GitHub](https://github.com/gnolang) +// - [Subscribe](#subscribe) +// - [Tokenomics (soon)](#) +// - [Blog](/blog) +// - [Events](/events) +// - [Partners, Fund, Grants](/partners) +// +//
+// +//
+// +// ### Build with Gnolang +// +// - [Gno dev with CLI (soon)](#) +// - [Explore the Universe](/ecosystem) +// - [Test in the browser (soon)](#) +// - [About the Gnolang Language](/gnolang) +// - [Docs/ Tutorials](https://github.com/gnolang) +// - [Gno by example](https://gno-by-example.com/) +// - [Getting started video (soon)](#) +// +//
+//
+// +// ### Explore the universe +// +// - [Discover demo packages](https://github.com/gnolang/gno/tree/master/examples) +// - [Install Gno Key instructions](/r/demo/boards:testboard/5) +// - [Testnets 3](https://test3.gno.land/) +// - [Testnets 2](https://test2.gno.land/) +// - [Explorer links(soon)](#) +// - [Testnet Tokens (faucet)](https://test3.gno.land/faucet) +// +//
+//
+//
+// +// +//
+//
+// +// ### Socials +// +// - Check out our [community projects](https://github.com/gnolang/awesome-gno) +// - ![Discord](static/img/ico-discord.svg) [Discord](https://discord.gg/S8nKUqwkPn) +// - ![Twitter](static/img/ico-twitter.svg) [Twitter](https://twitter.com/_gnoland) +// - ![Youtube](static/img/ico-youtube.svg) [Youtube](https://www.youtube.com/@_gnoland) +// - ![Telegram](static/img/ico-telegram.svg) [Telegram](https://t.me/gnoland) +// +//
+//
+// +// ### Quote of the ~Day~Block#123 +// +// > Now, you Gno. +// +//
+//
+// +// +// --- +// +// **This is a testnet.** +// Package names are not guaranteed to be available for production. diff --git a/gno.land/cmd/gnoweb/pages/ABOUT.md b/examples/gno.land/r/gnoland/pages/page_about.gno similarity index 73% rename from gno.land/cmd/gnoweb/pages/ABOUT.md rename to examples/gno.land/r/gnoland/pages/page_about.gno index a5678a7349a..9aba4e39f76 100644 --- a/gno.land/cmd/gnoweb/pages/ABOUT.md +++ b/examples/gno.land/r/gnoland/pages/page_about.gno @@ -1,4 +1,10 @@ -# About Gno.land +package gnopages + +func init() { + path := "about" + title := "Gno.land Is A Platform To Write Smart Contracts In Gnolang (Gno)" + // XXX: description := "On Gno.land, developers write smart contracts and other blockchain apps using Gnolang (Gno) without learning a language that’s exclusive to a single ecosystem." + body := `# About Gno.land Gno.land is a platform to write smart contracts in Gnolang (Gno). Using an interpreted version of the general-purpose programming language Golang (Go), developers can write smart contracts and other blockchain apps without having to learn a language that’s exclusive to a single ecosystem. @@ -9,4 +15,6 @@ Proof of Contribution rewards contributors from technical and non-technical back This consensus mechanism also achieves higher security with fewer validators, optimizing resources for a greener, more sustainable, and enduring blockchain ecosystem. Any blockchain using Gnolang achieves succinctness, composability, expressivity, and completeness not found in any other smart contract platform. -By observing a minimal structure, the design can endure over time and challenge the regime of information censorship we’re living in today. +By observing a minimal structure, the design can endure over time and challenge the regime of information censorship we’re living in today.` + _ = b.NewPost("", path, title, body, nil) +} diff --git a/examples/gno.land/r/gnoland/pages/page_ecosystem.gno b/examples/gno.land/r/gnoland/pages/page_ecosystem.gno new file mode 100644 index 00000000000..68969c44529 --- /dev/null +++ b/examples/gno.land/r/gnoland/pages/page_ecosystem.gno @@ -0,0 +1,35 @@ +package gnopages + +func init() { + var ( + path = "ecosystem" + title = "Discover Gno.land Ecosystem Projects & Initiatives" + // XXX: description = "Dive further into the Gno.land ecosystem and discover the core infrastructure, projects, smart contracts, and tooling we’re building." + body = `# Gno Ecosystem + +## Gno.land Space + +For the best onboarding experience, head over to [Gno.land Space](https://www.gnoland.space/) open ecosystem. Here you can set up your Gno wallet, explore existing community-written Gno smart contracts (realms), and become part of our vibrant community by joining [Gno.land Discord](https://discord.com/invite/x76qK4ttHC). + +## Gno Studio (IDE) + +Gno IDE is a web-based application helping builders quickly spin up Gno realms and packages right on their browsers. Offering a smooth and intuitive UX for building on Gno, you’ll find multiple modes for customizability with all the features you’d expect from an IDE, such as auto compilation in the editor, debugging, and extensive testing capability. + +## Gnoscan + +Developed by the Onbloc team, Gnoscan is Gno.land’s blockchain explorer. Anyone can use Gnoscan to easily find information that resides on the Gno.land blockchain, such as wallet addresses, TX hashes, blocks, and contracts. Gnoscan makes our on-chain data easy to read and intuitive to discover. [Go to Gnoscan.](https://gnoscan.io/) + +## Adena + +Adena is a user-friendly non-custodial wallet for Gno.land. Open-source and developed by Onbloc, Adena currently powers all transactions on Gno.land, allowing gnomes to interact easily with the chain. With an emphasis on UX, Adena is built to handle millions of realms and tokens with a high-quality interface, support for NFTs and custom tokens, and seamless integration. [Get started here.](https://adena.app/) + +## Gnoswap + +Gnoswap is currently under development and led by the Onbloc team. Gnoswap will be the first DEX on Gno.land and is an automated market maker (AMM) protocol written in Gnolang that allows for permissionless token exchanges on the platform. + +## Gno.land Developer Portal + +Through the Gno.land Developer Portal, new developers can explore the exciting world of Gnolang (Gno), a novel programming language that powers the Gno.land blockchain. If you want to interact with Gno.land, start writing a realm, build a dApp, or even port a Solidity contract to a Gnolang realm, you’ll find the resources to [get started here](https://docs.onbloc.xyz/).` + ) + _ = b.NewPost("", path, title, body, nil) +} diff --git a/examples/gno.land/r/gnoland/pages/page_events.gno b/examples/gno.land/r/gnoland/pages/page_events.gno new file mode 100644 index 00000000000..18e7faeb3d3 --- /dev/null +++ b/examples/gno.land/r/gnoland/pages/page_events.gno @@ -0,0 +1,151 @@ +package gnopages + +func init() { + var ( + path = "events" + title = "Gno.land Core Team Attends Industry Events & Meetups" + // XXX: description = "If you’re interested in learning more about Gno.land, you can join us at major blockchain industry events throughout the year either in person or virtually." + body = `# Events + +If you’re interested in building web3 with us, catch up with Gno.land in person at one of our industry events. We’re looking to connect with developers and like-minded thinkers who can contribute to the growth of our platform. + +--- + +## Upcoming Events + +
+
+ +### EthCC + +- **Come Meet Us at our Booth** +- Paris, July 17 - 20, 2023 +- Manfred Touron + +[Learn more](https://www.ethcc.io/) + +
+
+ +### Nebular Summit Gno.land for Developers + +- Paris, July 24 - 25, 2023 +- Manfred Touron + +[Learn more](https://www.nebular.builders/) + +
+
+ +### GopherCon EU + +- **Come Meet Us at our Booth** +- Berlin, July 26 - 29, 2023 + +[Learn more](https://gophercon.eu/) + +
+ +
+ +### GopherCon US + +- **Come Meet Us at our Booth** +- San Diego, September 26 - 29, 2023 + +[Learn more](https://www.gophercon.com/) + +
+
+ +--- + +## Past Events + +
+ +
+ +### Eth Seoul + +- **The Evolution of Smart Contracts: A Journey into Gno.land** +- Seoul, June 3, 2023 +- Manfred Touron + +[Learn more](https://2023.ethseoul.org/) + +
+
+ +### BUIDL Asia + +- **Proof of Contribution in Gno.land** +- Seoul, June 6, 2023 +- Manfred Touron + +[Learn more](https://www.buidl.asia/) + +
+
+ +### Game Developer Conference + +- **Side Event: Web3 Gaming Apps Powered by Gno** +- San Francisco, Mach 23, 2023 +- Jae Kwon + +[Watch the talk](https://www.youtube.com/watch?v=IJ0xel8lr4c) + +
+
+ +### EthDenver + +- **Side Event: Discover Gno.land** +- Denver, Feb 24 - Mar 5, 2023 +- Jae Kwon + +[Watch the talk](https://www.youtube.com/watch?v=IJ0xel8lr4c) + +
+
+ +### Istanbul Blockchain Week + +- Istanbul, Nov 14 - 17, 2022 +- Manfred Touron + +[Watch the talk](https://www.youtube.com/watch?v=JX0gdWT0Cg4) + +
+
+ +### Web Summit Buckle Up and Build with Cosmos + +- Lisbon, Nov 1 - 4, 2022 +- Manfred Touron + +
+
+ +### Cosmoverse + +- Medallin, Sept 26 - 28, 2022 +- Manfred Touron + +[Watch the talk](https://www.youtube.com/watch?v=6s1zG7hgxMk) + +
+
+ +### Berlin Blockchain Week Buckle Up and Build with Cosmos + +- Berlin, Sept 11 - 18, 2022 + +[Watch the talk](https://www.youtube.com/watch?v=hCLErPgnavI) + +
+
` + ) + _ = b.NewPost("", path, title, body, nil) +} diff --git a/examples/gno.land/r/gnoland/pages/page_gnolang.gno b/examples/gno.land/r/gnoland/pages/page_gnolang.gno new file mode 100644 index 00000000000..f0c2bfe276d --- /dev/null +++ b/examples/gno.land/r/gnoland/pages/page_gnolang.gno @@ -0,0 +1,43 @@ +package gnopages + +func init() { + var ( + path = "gnolang" + title = "Gnolang (Gno) Is a Complete Language for Blockchain" + // XXX: description = "Gnolang (Gno) is an interpretation of the popular Golang (Go) language for blockchain created by Tendermint and Cosmos founder Jae Kwon." + body = `# About the Gnolang, the Gno Language + +[Gnolang](https://github.com/gnolang/gno/blob/master/LICENSE.md) (Gno) is an interpretation of the widely-used Golang (Go) programming language for blockchain created by Cosmos co-founder Jae Kwon in 2022 to mark a new era in smart contracting. Gno is ~99% identical to Go, so Go programmers can start coding in Gno right away, with a minimal learning curve. For example, Gno comes with blockchain-specific standard libraries, but any code that doesn’t use blockchain-specific logic can run in Go with minimal processing. Libraries that don’t make sense in the blockchain context, such as network or operating-system access, are not available in Gno. Otherwise, Gno loads and uses many standard libraries that power Go, so most of the parsing of the source code is the same. + +Under the hood, the Gno code is parsed into an abstract syntax tree (AST) and the AST itself is used in the interpreter, rather than bytecode as in many virtual machines such as Java, Python, or Wasm. This makes even the GnoVM accessible to any Go programmer. The novel design of the intuitive GnoVM interpreter allows Gno to freeze and resume the program by persisting and loading the entire memory state. Gno is deterministic, auto-persisted, and auto-Merkle-ized, allowing (smart contract) programs to be succinct, as the programmer doesn’t have to serialize and deserialize objects to persist them into a database (unlike programming applications with the Cosmos SDK). + +## How Gno Differs from Go + +![Gno and Go differences](static/img/gno-language/go-and-gno.jpg) + +The composable nature of Go/Gno allows for type-checked interactions between contracts, making Gno.land safer and more powerful, as well as operationally cheaper and faster. Smart contracts on Gno.land are light, simple, more focused, and easily interoperable—a network of interconnected contracts rather than siloed monoliths that limit interactions with other contracts. + +![Example of Gno code](static/img/gno-language/code-example.jpg) + +## Gno Inherits Go’s Built-in Security Features + +Go supports secure programming through exported/non-exported fields, enabling a “least-authority” design. It is easy to create objects and APIs that expose only what should be accessible to callers while hiding what should not be simply by the capitalization of letters, thus allowing a succinct representation of secure logic that can be called by multiple users. + +Another major advantage of Go is that the language comes with an ecosystem of great tooling, like the compiler and third-party tools that statically analyze code. Gno inherits these advantages from Go directly to create a smart contract programming language that provides embedding, composability, type-check safety, and garbage collection, helping developers to write secure code relying on the compiler, parser, and interpreter to give warning alerts for common mistakes. + +## Gno vs Solidity + +The most widely-adopted smart contract language today is Ethereum’s EVM-compatible Solidity. With bytecode built from the ground up and Turing complete, Solidity opened up a world of possibilities for decentralized applications (dApps) and there are currently more than 10 million contracts deployed on Ethereum. However, Solidity provides limited tooling and its EVM has a stack limit and computational inefficiencies. + +Solidity is designed for one purpose only (writing smart contracts) and is bound by the limitations of the EVM. In addition, developers have to learn several languages if they want to understand the whole stack or work across different ecosystems. Gno aspires to exceed Solidity on multiple fronts (and other smart contract languages like CosmWasm or Substrate) as every part of the stack is written in Gno. It’s easy for developers to understand the entire system just by studying a relatively small code base. + +## Gno Is Essential for the Wider Adoption of Web3 + +Gno makes imports as easy as they are in web2 with runtime-based imports for seamless dependency flow comprehension, and support for complex structs, beyond primitive types. Gno is ultimately cost-effective as dependencies are loaded once, enabling remote function calls as local, and providing automatic and independent per-realm state persistence. + +Using Gno, developers can rapidly accelerate application development and adopt a modular structure by reusing and reassembling existing modules without building from scratch. They can embed one structure inside another in an intuitive way while preserving localism, and the language specification is simple, successfully balancing practicality and minimalism. + +The Go language is so well designed that the Gno smart contract system will become the new gold standard for smart contract development and other blockchain applications. As a programming language that is universally adopted, secure, composable, and complete, Gno is essential for the broader adoption of web3 and its sustainable growth.` + ) + _ = b.NewPost("", path, title, body, nil) +} diff --git a/examples/gno.land/r/gnoland/pages/page_gor.gno b/examples/gno.land/r/gnoland/pages/page_gor.gno new file mode 100644 index 00000000000..3a6bb022e09 --- /dev/null +++ b/examples/gno.land/r/gnoland/pages/page_gor.gno @@ -0,0 +1,221 @@ +package gnopages + +func init() { + path := "gor" + title := "Game of Realms Content For The Best Contributors" + // XXX: description := "Game of Realms is the first high-stakes competition held in two phases to find the best contributors to the Gno.land platform with a 133,700 ATOM prize pool." + body := `# Game of Realms + +
+ +### Game of Realms + +The first high-stakes contest will see participants compete for tiered membership to co-own the Gno.land blockchain. A series of complex technical and non-technical tasks will challenge contributors to create innovative patterns that push the chain to new limits. Start building the foundation for tomorrow through key smart contracts and other contributions that change our understanding of the world. + +
+ +The competition is currently in phase one – for advanced developers only. + +Once the necessary tools to start phase two are ready, we’ll open up the competition to newer devs and non-technical contributors. + +If you want to stack ATOM rewards and play a key role in the success of Gno.land and web3, read more about Game of Realms or open a [PR](https://github.com/gnolang/gno/) today. + +
+ +
+
+
+ +## Phase I. (ongoing) + +- + +- + +- + +
+
+ +## Phase II. (Locked) + +
+
+
+ +
+ +
+ +## Evaluation DAO + +This complex challenge seeks your skills in DAO development and implementation and is one of the most important challenges of phase one. The Evaluation DAO will ensure that contributions in Game of Realms and the Gno.land platform are fairly rewarded. + +
+ + + + + + + +
+ +Game of Realms participants and core contributors are still in discussions, proposing additional ideas, and seeing how the proposal for the Evaluation DAO evolves over time. + +
+ + + +
+ +See [GitHub issue 519](https://github.com/gnolang/gno/issues/519) for the most up-to-date discussion so far on how voting should work for the DAO, what the responsibilities are, how to join, etc. + +
+ + + + + + + + + + + + + + + + + +
+
+ +
+ +## Tutorials + +To progress to phase two of the competition, we need high-quality tutorials, guides, and documentation from phase one participants. Help to create materials that will onboard more contributors to Gno.land. + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +## Governance Module + +Can you define and implement a governance contract suite that rivals existing ones, such as the Cosmos Hub? Show us how! We’re looking for the fairest and most efficient governance solution possible. + +
+ + + + + + + +
+ +Game of Realms participants and core contributors have made significant progress teaming up to complete this challenge but discussions and additional ideas are still ongoing. + +
+ + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +## Register Now + + +
+
+ +
+
+ + +
+ +
+ + +
+ + +
+
+
+ +` + _ = b.NewPost("", path, title, body, nil) +} diff --git a/examples/gno.land/r/gnoland/pages/page_partners.gno b/examples/gno.land/r/gnoland/pages/page_partners.gno new file mode 100644 index 00000000000..440302437fa --- /dev/null +++ b/examples/gno.land/r/gnoland/pages/page_partners.gno @@ -0,0 +1,21 @@ +package gnopages + +func init() { + path := "partners" + title := "Partners" + // XXX: description := """ + body := `## Partnerships + +### Fund and Grants Program + +Are you a builder, tinkerer, or researcher? If you’re looking to create awesome dApps, tooling, infrastructure, or smart contract libraries on Gno.land, you can apply for a grant. The Gno.land Ecosystem Fund and Grants program provides financial contributions for individuals and teams to innovate on the platform. + +
+ +[More information here](https://github.com/gnolang/ecosystem-fund-grants) + +
+` + + _ = b.NewPost("", path, title, body, nil) +} diff --git a/examples/gno.land/r/gnoland/pages/page_start.gno b/examples/gno.land/r/gnoland/pages/page_start.gno new file mode 100644 index 00000000000..a36ec6e52b1 --- /dev/null +++ b/examples/gno.land/r/gnoland/pages/page_start.gno @@ -0,0 +1,21 @@ +package gnopages + +func init() { + path := "start" + title := "Getting Started with Gno" + // XXX: description := "" + + // TODO: codegen to use README files here + + /* TODO: port previous message: This is a demo of Gno smart contract programming. This document was + constructed by Gno onto a smart contract hosted on the data Realm + name ["gno.land/r/demo/boards"](https://gno.land/r/demo/boards/) + ([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)). + */ + body := `## Getting Started with Gno + +- [Install Gno Key](/r/demo/boards:testboard/5) +- TODO: add more links +` + _ = b.NewPost("", path, title, body, nil) +} diff --git a/examples/gno.land/r/gnoland/pages/page_testnets.gno b/examples/gno.land/r/gnoland/pages/page_testnets.gno new file mode 100644 index 00000000000..b6c09ab71ee --- /dev/null +++ b/examples/gno.land/r/gnoland/pages/page_testnets.gno @@ -0,0 +1,19 @@ +package gnopages + +func init() { + path := "testnets" + title := "Gno.land Testnets" + // XXX: description := """ + body := `## Other testnets + +- **[staging.gno.land](https://staging.gno.land) (wiped every commit to master)** +- _[test3.gno.land](https://test3.gno.land) (latest)_ +- _[test2.gno.land](https://test2.gno.land) (archive)_ +- _[test1.gno.land](https://test1.gno.land) (archive)_ + +## Local devnet + +See CONTRIBUTING.md on GitHub. +` + _ = b.NewPost("", path, title, body, nil) +} diff --git a/examples/gno.land/r/gnoland/pages/page_tokenomics.gno b/examples/gno.land/r/gnoland/pages/page_tokenomics.gno new file mode 100644 index 00000000000..de899ae0a70 --- /dev/null +++ b/examples/gno.land/r/gnoland/pages/page_tokenomics.gno @@ -0,0 +1,11 @@ +package gnopages + +func init() { + var ( + path = "tokenomics" + title = "Gno.land Tokenomics" + // XXX: description = """ + body = `Lorem Ipsum` + ) + _ = b.NewPost("", path, title, body, nil) +} diff --git a/examples/gno.land/r/gnoland/pages/pages.gno b/examples/gno.land/r/gnoland/pages/pages.gno index dbc3d855880..6e1f117d1d5 100644 --- a/examples/gno.land/r/gnoland/pages/pages.gno +++ b/examples/gno.land/r/gnoland/pages/pages.gno @@ -4,16 +4,12 @@ import ( "gno.land/p/demo/blog" ) -var b = &blog.Blog{ - Title: "Gnoland's Pages", - Prefix: "/r/gnoland/pages:", -} +// TODO: switch from p/blog to p/pages -func init() { - _ = b.NewPost("", "gor", "Game of Realms", "Lorem Ipsum", nil) - _ = b.NewPost("", "events", "Events", "Lorem Ipsum", nil) - _ = b.NewPost("", "tokenomics", "Tokenomics", "Lorem Ipsum", nil) - _ = b.NewPost("", "start", "Getting Started", "Lorem Ipsum", nil) +var b = &blog.Blog{ + Title: "Gnoland's Pages", + Prefix: "/r/gnoland/pages:", + NoBreadcrumb: true, } func Render(path string) string { diff --git a/examples/gno.land/r/gnoland/pages/pages_test.gno b/examples/gno.land/r/gnoland/pages/pages_test.gno index 1a43153e2c8..5a6fe84ad38 100644 --- a/examples/gno.land/r/gnoland/pages/pages_test.gno +++ b/examples/gno.land/r/gnoland/pages/pages_test.gno @@ -6,48 +6,42 @@ import ( "testing" ) -func TestPackage(t *testing.T) { - std.TestSetOrigCaller(std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq")) - - author := std.GetOrigCaller() - - // by default, lorem ipsum posts - { - got := Render("") - expected := ` -# Gnoland's Pages - -
- -## [Events](/r/gnoland/pages:p/events) -**[Learn More](/r/gnoland/pages:p/events)** - -
- -## [Game of Realms](/r/gnoland/pages:p/gor) -**[Learn More](/r/gnoland/pages:p/gor)** - -
- -## [Getting Started](/r/gnoland/pages:p/start) -**[Learn More](/r/gnoland/pages:p/start)** - -
- -## [Tokenomics](/r/gnoland/pages:p/tokenomics) -**[Learn More](/r/gnoland/pages:p/tokenomics)** - -
-` - assertMDEquals(t, got, expected) +func TestHome(t *testing.T) { + printedOnce := false + got := Render("") + expectedSubtrings := []string{ + "/r/gnoland/pages:p/events", + "/r/gnoland/pages:p/tokenomics", + "/r/gnoland/pages:p/start", + "/r/gnoland/pages:p/gor", + "/r/gnoland/pages:p/about", + "/r/gnoland/pages:p/gnolang", + } + for _, substring := range expectedSubtrings { + if !strings.Contains(got, substring) { + if !printedOnce { + println(got) + printedOnce = true + } + t.Errorf("expected %q, but not found.", substring) + } } } -func assertMDEquals(t *testing.T, got, expected string) { - t.Helper() - expected = strings.TrimSpace(expected) - got = strings.TrimSpace(got) - if expected != got { - t.Errorf("invalid render output.\nexpected %q.\ngot %q.", expected, got) +func TestAbout(t *testing.T) { + printedOnce := false + got := Render("p/about") + expectedSubtrings := []string{ + "# About Gno.land", + "Gno.land is a platform to write smart contracts in Gnolang (Gno).", + } + for _, substring := range expectedSubtrings { + if !strings.Contains(got, substring) { + if !printedOnce { + println(got) + printedOnce = true + } + t.Errorf("expected %q, but not found.", substring) + } } } diff --git a/examples/gno.land/r/manfred/present/admin.gno b/examples/gno.land/r/manfred/present/admin.gno new file mode 100644 index 00000000000..ff0cb075656 --- /dev/null +++ b/examples/gno.land/r/manfred/present/admin.gno @@ -0,0 +1,92 @@ +package present + +import ( + "std" + "strings" + + "gno.land/p/demo/avl" +) + +var ( + adminAddr std.Address + moderatorList avl.Tree + inPause bool +) + +func init() { + // adminAddr = std.GetOrigCaller() // FIXME: find a way to use this from the main's genesis. + adminAddr = "g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq" +} + +func AdminSetAdminAddr(addr std.Address) { + assertIsAdmin() + adminAddr = addr +} + +func AdminSetInPause(state bool) { + assertIsAdmin() + inPause = state +} + +func AdminAddModerator(addr std.Address) { + assertIsAdmin() + moderatorList.Set(addr.String(), true) +} + +func AdminRemoveModerator(addr std.Address) { + assertIsAdmin() + moderatorList.Set(addr.String(), false) // XXX: delete instead? +} + +func ModAddPost(slug, title, body, tags string) { + assertIsModerator() + + caller := std.GetOrigCaller() + tagList := strings.Split(tags, ",") + err := b.NewPost(caller, slug, title, body, tagList) + checkErr(err) +} + +func ModEditPost(slug, title, body, tags string) { + assertIsModerator() + + tagList := strings.Split(tags, ",") + err := b.GetPost(slug).Update(title, body, tagList) + checkErr(err) +} + +func isAdmin(addr std.Address) bool { + return addr == adminAddr +} + +func isModerator(addr std.Address) bool { + _, found := moderatorList.Get(addr.String()) + return found +} + +func assertIsAdmin() { + caller := std.GetOrigCaller() + if !isAdmin(caller) { + panic("access restricted.") + } +} + +func assertIsModerator() { + caller := std.GetOrigCaller() + if isAdmin(caller) || isModerator(caller) { + return + } + panic("access restricted") +} + +func assertNotInPause() { + if inPause { + panic("access restricted (pause)") + } +} + +func checkErr(err error) { + if err != nil { + panic(err) + } +} diff --git a/examples/gno.land/r/manfred/present/gno.mod b/examples/gno.land/r/manfred/present/gno.mod new file mode 100644 index 00000000000..9d1ab5b0e56 --- /dev/null +++ b/examples/gno.land/r/manfred/present/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/manfred/present + +require ( + "gno.land/p/demo/avl" v0.0.0-latest + "gno.land/p/demo/blog" v0.0.0-latest +) diff --git a/examples/gno.land/r/manfred/present/present_miami23.gno b/examples/gno.land/r/manfred/present/present_miami23.gno new file mode 100644 index 00000000000..36b1980bb0b --- /dev/null +++ b/examples/gno.land/r/manfred/present/present_miami23.gno @@ -0,0 +1,44 @@ +package present + +func init() { + path := "miami23" + title := "Portal Loop Demo (Miami 2023)" + body := ` +# Portal Loop Demo (Miami 2023) + +Rendered by Gno. + +[Source (WIP)](https://github.com/gnolang/gno/pull/1176) + +## Portal Loop + +- DONE: Dynamic homepage, key pages, aliases, and redirects. +- TODO: Deploy with history, complete worxdao v0. +- Will replace the static gno.land site. +- Enhances local development. + +[GitHub Issue](https://github.com/gnolang/gno/issues/1108) + +## Roadmap + +- Crafting the roadmap this week, open to collaboration. +- Combining onchain (portal loop) and offchain (GitHub). +- Next week: Unveiling the official v0 roadmap. + +## Teams, DAOs, Projects + +- Developing worxDAO contracts for directories of projects and teams. +- GitHub teams and projects align with this structure. +- CODEOWNER file updates coming. +- Initial teams announced next week. + +## Tech Team Retreat Plan + +- Continue Portal Loop. +- Consider dApp development. +- Explore new topics [here](https://github.com/orgs/gnolang/projects/15/). +- Engage in workshops. +- Connect and have fun with colleagues. +` + _ = b.NewPost(adminAddr, path, title, body, []string{"demo", "portal-loop", "miami"}) +} diff --git a/examples/gno.land/r/manfred/present/present_miami23_filetest.gno b/examples/gno.land/r/manfred/present/present_miami23_filetest.gno new file mode 100644 index 00000000000..05c41905060 --- /dev/null +++ b/examples/gno.land/r/manfred/present/present_miami23_filetest.gno @@ -0,0 +1,57 @@ +package main + +import "gno.land/r/manfred/present" + +func main() { + println(present.Render("")) + println("------------------------------------") + println(present.Render("p/miami23")) +} + +// Output: +//
+// +// ## [Portal Loop Demo (Miami 2023)](/r/manfred/present:p/miami23) +// **[Learn More](/r/manfred/present:p/miami23)** +// +//
+// ------------------------------------ +// # Portal Loop Demo (Miami 2023) +// +// Rendered by Gno. +// +// [Source (WIP)](https://github.com/gnolang/gno/pull/1176) +// +// ## Portal Loop +// +// - DONE: Dynamic homepage, key pages, aliases, and redirects. +// - TODO: Deploy with history, complete worxdao v0. +// - Will replace the static gno.land site. +// - Enhances local development. +// +// [GitHub Issue](https://github.com/gnolang/gno/issues/1108) +// +// ## Roadmap +// +// - Crafting the roadmap this week, open to collaboration. +// - Combining onchain (portal loop) and offchain (GitHub). +// - Next week: Unveiling the official v0 roadmap. +// +// ## Teams, DAOs, Projects +// +// - Developing worxDAO contracts for directories of projects and teams. +// - GitHub teams and projects align with this structure. +// - CODEOWNER file updates coming. +// - Initial teams announced next week. +// +// ## Tech Team Retreat Plan +// +// - Continue Portal Loop. +// - Consider dApp development. +// - Explore new topics [here](https://github.com/orgs/gnolang/projects/15/). +// - Engage in workshops. +// - Connect and have fun with colleagues. +// +// [#demo](/r/manfred/present:t/demo) [#portal-loop](/r/manfred/present:t/portal-loop) [#miami](/r/manfred/present:t/miami) +// +// by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 1970-01-01 12:00am UTC diff --git a/examples/gno.land/r/manfred/present/presentations.gno b/examples/gno.land/r/manfred/present/presentations.gno new file mode 100644 index 00000000000..8a99f502e86 --- /dev/null +++ b/examples/gno.land/r/manfred/present/presentations.gno @@ -0,0 +1,17 @@ +package present + +import ( + "gno.land/p/demo/blog" +) + +// TODO: switch from p/blog to p/present + +var b = &blog.Blog{ + Title: "Manfred's Presentations", + Prefix: "/r/manfred/present:", + NoBreadcrumb: true, +} + +func Render(path string) string { + return b.Render(path) +} diff --git a/gno.land/Makefile b/gno.land/Makefile index 1fd1aaa1f78..be1db280c40 100644 --- a/gno.land/Makefile +++ b/gno.land/Makefile @@ -5,6 +5,9 @@ help: rundep=go run -modfile ../misc/devdeps/go.mod +.PHONY: gnoland.start +gnoland.start:; go run ./cmd/gnoland start + .PHONY: build build: build.gnoland build.gnokey build.gnoweb build.gnofaucet build.gnotxsync diff --git a/gno.land/cmd/gnoweb/main.go b/gno.land/cmd/gnoweb/main.go index e8a2feac0d7..0d9398cb8e2 100644 --- a/gno.land/cmd/gnoweb/main.go +++ b/gno.land/cmd/gnoweb/main.go @@ -18,7 +18,6 @@ import ( "github.com/gnolang/gno/tm2/pkg/amino" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" - osm "github.com/gnolang/gno/tm2/pkg/os" "github.com/gnolang/gno/tm2/pkg/std" "github.com/gorilla/mux" "github.com/gotuna/gotuna" @@ -32,59 +31,93 @@ const ( qFileStr = "vm/qfile" ) +var startedAt time.Time + var flags struct { - bindAddr string - remoteAddr string - captchaSite string - faucetURL string - viewsDir string - pagesDir string - helpChainID string - helpRemote string + BindAddr string + RemoteAddr string + CaptchaSite string + FaucetURL string + ViewsDir string + HelpChainID string + HelpRemote string + WithAnalytics bool } -var startedAt time.Time - func init() { - flag.StringVar(&flags.remoteAddr, "remote", "127.0.0.1:26657", "remote gnoland node address") - flag.StringVar(&flags.bindAddr, "bind", "127.0.0.1:8888", "server listening address") - flag.StringVar(&flags.captchaSite, "captcha-site", "", "recaptcha site key (if empty, captcha are disabled)") - flag.StringVar(&flags.faucetURL, "faucet-url", "http://localhost:5050", "faucet server URL") - flag.StringVar(&flags.viewsDir, "views-dir", "./cmd/gnoweb/views", "views directory location") - flag.StringVar(&flags.pagesDir, "pages-dir", "./cmd/gnoweb/pages", "pages directory location") - flag.StringVar(&flags.helpChainID, "help-chainid", "dev", "help page's chainid") - flag.StringVar(&flags.helpRemote, "help-remote", "127.0.0.1:26657", "help page's remote addr") + flag.StringVar(&flags.RemoteAddr, "remote", "127.0.0.1:26657", "remote gnoland node address") + flag.StringVar(&flags.BindAddr, "bind", "127.0.0.1:8888", "server listening address") + flag.StringVar(&flags.CaptchaSite, "captcha-site", "", "recaptcha site key (if empty, captcha are disabled)") + flag.StringVar(&flags.FaucetURL, "faucet-url", "http://localhost:5050", "faucet server URL") + flag.StringVar(&flags.ViewsDir, "views-dir", "./cmd/gnoweb/views", "views directory location") // XXX: replace with goembed + flag.StringVar(&flags.HelpChainID, "help-chainid", "dev", "help page's chainid") + flag.StringVar(&flags.HelpRemote, "help-remote", "127.0.0.1:26657", "help page's remote addr") + flag.BoolVar(&flags.WithAnalytics, "with-analytics", false, "enable privacy-first analytics") startedAt = time.Now() } func makeApp() gotuna.App { app := gotuna.App{ - ViewFiles: os.DirFS(flags.viewsDir), + ViewFiles: os.DirFS(flags.ViewsDir), Router: gotuna.NewMuxRouter(), Static: static.EmbeddedStatic, // StaticPrefix: "static/", } - app.Router.Handle("/", handlerHome(app)) - app.Router.Handle("/about", handlerAbout(app)) - app.Router.Handle("/game-of-realms", handlerGor(app)) - app.Router.Handle("/faucet", handlerFaucet(app)) - app.Router.Handle("/r/demo/boards:gnolang/6", handlerRedirect(app)) + + // realm aliases + aliases := map[string]string{ + "/": "/r/gnoland/home", + "/about": "/r/gnoland/pages:p/about", + "/gnolang": "/r/gnoland/pages:p/gnolang", + "/ecosystem": "/r/gnoland/pages:p/ecosystem", + "/partners": "/r/gnoland/pages:p/partners", + "/testnets": "/r/gnoland/pages:p/testnets", + "/start": "/r/gnoland/pages:p/start", + "/game-of-realms": "/r/gnoland/pages:p/gor", // XXX: replace with gor realm + "/events": "/r/gnoland/pages:p/events", // XXX: replace with events realm + } + for from, to := range aliases { + app.Router.Handle(from, handlerRealmAlias(app, to)) + } + // http redirects + redirects := map[string]string{ + "/r/demo/boards:gnolang/6": "/r/demo/boards:gnolang/3", // XXX: temporary + "/blog": "/r/gnoland/blog", + "/gor": "/game-of-realms", + "/grants": "/partners", + "/language": "/gnolang", + "/getting-started": "/start", + } + for from, to := range redirects { + app.Router.Handle(from, handlerRedirect(app, to)) + } + // realm routes // NOTE: see rePathPart. app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}/{filename:(?:.*\\.(?:gno|md|txt)$)?}", handlerRealmFile(app)) app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}", handlerRealmMain(app)) app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}:{querystr:.*}", handlerRealmRender(app)) app.Router.Handle("/p/{filepath:.*}", handlerPackageFile(app)) + + // other + app.Router.Handle("/faucet", handlerFaucet(app)) app.Router.Handle("/static/{path:.+}", handlerStaticFile(app)) app.Router.Handle("/favicon.ico", handlerFavicon(app)) + + // api app.Router.Handle("/status.json", handlerStatusJSON(app)) + + app.Router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + path := r.RequestURI + handleNotFound(app, path, w, r) + }) return app } func main() { flag.Parse() - fmt.Printf("Running on http://%s\n", flags.bindAddr) + fmt.Printf("Running on http://%s\n", flags.BindAddr) server := &http.Server{ - Addr: flags.bindAddr, + Addr: flags.BindAddr, ReadHeaderTimeout: 60 * time.Second, Handler: makeApp().Router, } @@ -94,50 +127,59 @@ func main() { } } -func handlerHome(app gotuna.App) http.Handler { - md := filepath.Join(flags.pagesDir, "HOME.md") - homeContent := osm.MustReadFile(md) - - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - app.NewTemplatingEngine(). - Set("Title", "Gno.land Smart Contract Platform Using Gnolang (Gno)"). - Set("Description", "Gno.land is the only smart contract platform using the Gnolang (Gno) programming language, an interpretation of the widely-used Golang (Go)."). - Set("HomeContent", string(homeContent)). - Render(w, r, "home.html", "funcs.html") - }) -} - -func handlerAbout(app gotuna.App) http.Handler { - md := filepath.Join(flags.pagesDir, "ABOUT.md") - mainContent := osm.MustReadFile(md) - +// handlerRealmAlias is used to render official pages from realms. +// url is intended to be shorter. +// UX is intended to be more minimalistic. +// A link to the realm realm is added. +func handlerRealmAlias(app gotuna.App, rlmpath string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - app.NewTemplatingEngine(). - Set("Title", "Gno.land Is A Platform To Write Smart Contracts In Gnolang (Gno)"). - Set("Description", "On Gno.land, developers write smart contracts and other blockchain apps using Gnolang (Gno) without learning a language that’s exclusive to a single ecosystem."). - Set("MainContent", string(mainContent)). - Render(w, r, "generic.html", "funcs.html") - }) -} + rlmfullpath := "gno.land" + rlmpath + querystr := "" // XXX: "?gnoweb-alias=1" + parts := strings.Split(rlmpath, ":") + switch len(parts) { + case 1: // continue + case 2: // r/realm:querystr + rlmfullpath = "gno.land" + parts[0] + querystr = parts[1] + querystr + default: + panic("should not happen") + } + rlmname := strings.TrimPrefix(rlmfullpath, "gno.land/r/") + qpath := "vm/qrender" + data := []byte(fmt.Sprintf("%s\n%s", rlmfullpath, querystr)) + res, err := makeRequest(qpath, data) + if err != nil { + writeError(w, fmt.Errorf("gnoweb failed to query gnoland: %w", err)) + return + } -func handlerGor(app gotuna.App) http.Handler { - md := filepath.Join(flags.pagesDir, "GOR.md") - mainContent := osm.MustReadFile(md) + queryParts := strings.Split(querystr, "/") + pathLinks := []pathLink{} + for i, part := range queryParts { + pathLinks = append(pathLinks, pathLink{ + URL: "/r/" + rlmname + ":" + strings.Join(queryParts[:i+1], "/"), + Text: part, + }) + } - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - app.NewTemplatingEngine(). - Set("MainContent", string(mainContent)). - Set("Title", "Game of Realms Content For The Best Contributors "). - Set("Description", "Game of Realms is the first high-stakes competition held in two phases to find the best contributors to the Gno.land platform with a 133,700 ATOM prize pool."). - Render(w, r, "generic.html", "funcs.html") + tmpl := app.NewTemplatingEngine() + // XXX: extract title from realm's output + // XXX: extract description from realm's output + tmpl.Set("RealmName", rlmname) + tmpl.Set("RealmPath", rlmpath) + tmpl.Set("Query", querystr) + tmpl.Set("PathLinks", pathLinks) + tmpl.Set("Contents", string(res.Data)) + tmpl.Set("Flags", flags) + tmpl.Set("IsAlias", true) + tmpl.Render(w, r, "realm_render.html", "funcs.html") }) } func handlerFaucet(app gotuna.App) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { app.NewTemplatingEngine(). - Set("captchaSite", flags.captchaSite). - Set("faucetURL", flags.faucetURL). + Set("Flags", flags). Render(w, r, "faucet.html", "funcs.html") }) } @@ -192,12 +234,13 @@ func handlerStatusJSON(app gotuna.App) http.Handler { }) } -// XXX temporary. -func handlerRedirect(app gotuna.App) http.Handler { +func handlerRedirect(app gotuna.App, to string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, "/r/boards:gnolang/3", http.StatusFound) - app.NewTemplatingEngine(). - Render(w, r, "home.html", "funcs.html") + http.Redirect(w, r, to, http.StatusFound) + tmpl := app.NewTemplatingEngine() + tmpl.Set("To", to) + tmpl.Set("Flags", flags) + tmpl.Render(w, r, "redirect.html", "funcs.html") }) } @@ -232,10 +275,9 @@ func handlerRealmMain(app gotuna.App) http.Handler { tmpl := app.NewTemplatingEngine() tmpl.Set("FuncName", funcName) tmpl.Set("RealmPath", rlmpath) - tmpl.Set("Remote", flags.helpRemote) - tmpl.Set("ChainID", flags.helpChainID) tmpl.Set("DirPath", pathOf(rlmpath)) tmpl.Set("FunctionSignatures", fsigs) + tmpl.Set("Flags", flags) tmpl.Render(w, r, "realm_help.html", "funcs.html") } else { // Ensure realm exists. TODO optimize. @@ -297,12 +339,14 @@ func handleRealmRender(app gotuna.App, w http.ResponseWriter, r *http.Request) { } // Render template. tmpl := app.NewTemplatingEngine() - + // XXX: extract title from realm's output + // XXX: extract description from realm's output tmpl.Set("RealmName", rlmname) tmpl.Set("RealmPath", rlmpath) tmpl.Set("Query", querystr) tmpl.Set("PathLinks", pathLinks) tmpl.Set("Contents", string(res.Data)) + tmpl.Set("Flags", flags) tmpl.Render(w, r, "realm_render.html", "funcs.html") } @@ -345,6 +389,7 @@ func renderPackageFile(app gotuna.App, w http.ResponseWriter, r *http.Request, d tmpl.Set("DirURI", diruri) tmpl.Set("DirPath", pathOf(diruri)) tmpl.Set("Files", files) + tmpl.Set("Flags", flags) tmpl.Render(w, r, "package_dir.html", "funcs.html") } else { // Request is for a file. @@ -362,6 +407,7 @@ func renderPackageFile(app gotuna.App, w http.ResponseWriter, r *http.Request, d tmpl.Set("DirPath", pathOf(diruri)) tmpl.Set("FileName", filename) tmpl.Set("FileContents", string(res.Data)) + tmpl.Set("Flags", flags) tmpl.Render(w, r, "package_file.html", "funcs.html") } } @@ -371,7 +417,7 @@ func makeRequest(qpath string, data []byte) (res *abci.ResponseQuery, err error) // Height: height, XXX // Prove: false, XXX } - remote := flags.remoteAddr + remote := flags.RemoteAddr cli := client.NewHTTP(remote, "/websocket") qres, err := cli.ABCIQueryWithOptions( qpath, data, opts2) @@ -432,11 +478,20 @@ func handleNotFound(app gotuna.App, path string, w http.ResponseWriter, r *http. app.NewTemplatingEngine(). Set("title", "Not found"). Set("path", path). + Set("Flags", flags). Render(w, r, "404.html", "funcs.html") } func writeError(w http.ResponseWriter, err error) { + // XXX: writeError should return an error page template. w.WriteHeader(500) + + details := errors.Unwrap(err).Error() + main := err.Error() + + fmt.Println("main", main) + fmt.Println("details", details) + w.Write([]byte(err.Error())) } diff --git a/gno.land/cmd/gnoweb/main_test.go b/gno.land/cmd/gnoweb/main_test.go index 579e1bcd06b..974d3f987b7 100644 --- a/gno.land/cmd/gnoweb/main_test.go +++ b/gno.land/cmd/gnoweb/main_test.go @@ -12,7 +12,11 @@ import ( ) func TestRoutes(t *testing.T) { - ok := http.StatusOK + const ( + ok = http.StatusOK + found = http.StatusFound + notFound = http.StatusNotFound + ) routes := []struct { route string status int @@ -32,6 +36,10 @@ func TestRoutes(t *testing.T) { {"/r/demo/deep/very/deep?help", ok, "exposed"}, {"/r/demo/deep/very/deep/", ok, "render.gno"}, {"/r/demo/deep/very/deep/render.gno", ok, "func Render("}, + {"/game-of-realms", ok, "/r/gnoland/pages:p/gor"}, + {"/gor", found, "/game-of-realms"}, + {"/blog", found, "/r/gnoland/blog"}, + {"/404-not-found", notFound, "/404-not-found"}, } if wd, err := os.Getwd(); err == nil { if strings.HasSuffix(wd, "cmd/gnoweb") { @@ -40,6 +48,14 @@ func TestRoutes(t *testing.T) { } else { panic("os.Getwd() -> err: " + err.Error()) } + + // configure default values + flags.RemoteAddr = "127.0.0.1:26657" + flags.HelpRemote = "127.0.0.1:26657" + flags.HelpChainID = "dev" + flags.CaptchaSite = "" + flags.ViewsDir = "./cmd/gnoweb/views" + flags.WithAnalytics = false app := makeApp() for _, r := range routes { @@ -48,8 +64,57 @@ func TestRoutes(t *testing.T) { response := httptest.NewRecorder() app.Router.ServeHTTP(response, request) assert.Equal(t, r.status, response.Code) - assert.Equal(t, strings.Contains(response.Body.String(), r.substring), true) - println(response.Body.String()) + assert.Contains(t, response.Body.String(), r.substring) + // println(response.Body.String()) }) } } + +func TestAnalytics(t *testing.T) { + routes := []string{ + // special realms + "/", // home + "/about", + "/start", + + // redirects + "/game-of-realms", + "/getting-started", + "/blog", + "/boards", + + // realm, source, help page + "/r/gnoland/blog", + "/r/gnoland/blog/admin.gno", + "/r/demo/users:administrator", + "/r/gnoland/blog?help", + + // special pages + "/404-not-found", + } + + t.Run("with", func(t *testing.T) { + for _, route := range routes { + t.Run(route, func(t *testing.T) { + flags.WithAnalytics = true + app := makeApp() + request := httptest.NewRequest(http.MethodGet, route, nil) + response := httptest.NewRecorder() + app.Router.ServeHTTP(response, request) + assert.Contains(t, response.Body.String(), "simpleanalytics") + }) + } + }) + t.Run("without", func(t *testing.T) { + for _, route := range routes { + t.Run(route, func(t *testing.T) { + flags.WithAnalytics = false + app := makeApp() + request := httptest.NewRequest(http.MethodGet, route, nil) + response := httptest.NewRecorder() + app.Router.ServeHTTP(response, request) + assert.Equal(t, strings.Contains(response.Body.String(), "simpleanalytics"), false) + }) + } + }) +} diff --git a/gno.land/cmd/gnoweb/pages/GOR.md b/gno.land/cmd/gnoweb/pages/GOR.md deleted file mode 100644 index 95a155319fb..00000000000 --- a/gno.land/cmd/gnoweb/pages/GOR.md +++ /dev/null @@ -1,30 +0,0 @@ -# Game of Realms - -The first high-stakes contest will see participants compete for the tiered membership to co-own the Gno.land blockchain. -A series of complex technical and non-technical tasks will challenge contributors to create innovative patterns that push the chain to new limits. -Start building the foundation for tomorrow through key smart contracts and other contributions that change our understanding of the world. - -You can start participating in the co-creation of the Game of Realms now by adding your contributions on the dedicated [GitHub page](https://github.com/gnolang/gno/issues/357) to help us formulate the challenges. We want to release the final challenges from your contributions. - -## Register Now - - -
-
- -
-
- - -
- -
- - -
- - -
-
-
- diff --git a/gno.land/cmd/gnoweb/pages/HOME.md b/gno.land/cmd/gnoweb/pages/HOME.md deleted file mode 100644 index 0f3bfaec685..00000000000 --- a/gno.land/cmd/gnoweb/pages/HOME.md +++ /dev/null @@ -1,50 +0,0 @@ -# Welcome to **Gno.land** - -- [About Gno.land](/about) -- [Blogs](/r/gnoland/blog) -- [Install `gnokey`](https://github.com/gnolang/gno/tree/master/gno.land/cmd/gnokey) -- [Acquire testnet tokens](/faucet) -- [Game of Realms](/game-of-realms) - An open worldwide competition for developers to build the best Gnolang smart-contracts. - -# Explore new packages. - -- r/gnoland - - [/r/gnoland/blog](/r/gnoland/blog) - - [/r/gnoland/faucet](/r/gnoland/faucet) -- r/system - - [/r/system/names](/r/system/names) - - [/r/system/rewards](/r/system/rewards) - - [/r/system/validators](/r/system/validators) -- r/demo - - [/r/demo/banktest](/r/demo/banktest) - - [/r/demo/boards](/r/demo/boards) - - [/r/demo/foo20](/r/demo/foo20) - - [/r/demo/nft](/r/demo/nft) - - [/r/demo/types](/r/demo/types) - - [/r/demo/users](/r/demo/users) - - [/r/demo/groups](/r/demo/groups) -- p/demo - - [/p/demo/avl](/p/demo/avl) - - [/p/demo/blog](/p/demo/blog) - - [/p/demo/flow](/p/demo/flow) - - [/p/demo/gnode](/p/demo/gnode) - - [/p/demo/grc/exts](/p/demo/grc/exts) - - [/p/demo/grc/grc20](/p/demo/grc/grc20) - - [/p/demo/grc/grc721](/p/demo/grc/grc721) - -# Other Testnets - -- **[staging.gno.land](https://staging.gno.land) (wiped every commit to master)** -- _[test3.gno.land](https://test3.gno.land) (latest)_ -- _[test2.gno.land](https://test2.gno.land) (archive)_ -- _[test1.gno.land](https://test1.gno.land) (archive)_ - -**This is a testnet.** -Package names are not guaranteed to be available for production. - -# Social - -Check out our [community projects](https://github.com/gnolang/awesome-gno). - -Official channel: [Discord](https://discord.gg/S8nKUqwkPn)
-Other channels: [Telegram](https://t.me/gnoland) [Twitter](https://twitter.com/_gnoland) [Youtube](https://www.youtube.com/@_gnoland) diff --git a/gno.land/cmd/gnoweb/views/404.html b/gno.land/cmd/gnoweb/views/404.html index c51543ca1cd..fee4fff8689 100644 --- a/gno.land/cmd/gnoweb/views/404.html +++ b/gno.land/cmd/gnoweb/views/404.html @@ -2,8 +2,7 @@ - - + {{ template "html_head" . }} 404 - Not Found @@ -13,6 +12,7 @@

{{.Data.path}}

+ {{ template "analytics" .}} {{- end -}} diff --git a/gno.land/cmd/gnoweb/views/faucet.html b/gno.land/cmd/gnoweb/views/faucet.html index 84bcc6f34e5..85d3d6780c5 100644 --- a/gno.land/cmd/gnoweb/views/faucet.html +++ b/gno.land/cmd/gnoweb/views/faucet.html @@ -2,8 +2,8 @@ + {{ template "html_head" . }} Gno.land - {{ template "html_head" }}
diff --git a/gno.land/cmd/gnoweb/views/funcs.html b/gno.land/cmd/gnoweb/views/funcs.html index 8ac2fe6328b..1f8b7a265dd 100644 --- a/gno.land/cmd/gnoweb/views/funcs.html +++ b/gno.land/cmd/gnoweb/views/funcs.html @@ -1,4 +1,4 @@ -{{ define "header_buttons" }} +{{- define "header_buttons" -}}
-{{ end }} {{ define "html_head" }} +{{- end -}} + +{{- define "html_head" -}} + +{{if .Data.Description}}{{end}} -{{ end }} {{ define "header_logo" }} +{{- end -}} + +{{- define "header_logo" -}} -{{ end }} {{ define "footer" }} +{{- end -}} + +{{- define "footer" -}} -{{ end }} {{ define "js" }} +{{- end -}} + +{{- define "js" -}} + -{{ end }} {{ define "subscribe" }} +{{ template "analytics" .}} +{{- end -}} + +{{- define "analytics" -}} +{{- if .Data.Flags.WithAnalytics -}} + + + +{{- end -}} +{{- end -}} + +{{- define "subscribe" -}}
-{{ end }} +{{- end -}} diff --git a/gno.land/cmd/gnoweb/views/generic.html b/gno.land/cmd/gnoweb/views/generic.html index 117ea9d59fe..e671625e26a 100644 --- a/gno.land/cmd/gnoweb/views/generic.html +++ b/gno.land/cmd/gnoweb/views/generic.html @@ -3,8 +3,7 @@ Gno.land - {{ .Data.Title }} - - {{ template "html_head" }} + {{ template "html_head" . }}
@@ -16,7 +15,7 @@
{{ template "footer" }}
- {{ template "js" }} + {{ template "js" .}} {{- end -}} diff --git a/gno.land/cmd/gnoweb/views/home.html b/gno.land/cmd/gnoweb/views/home.html deleted file mode 100644 index a2bf78adb96..00000000000 --- a/gno.land/cmd/gnoweb/views/home.html +++ /dev/null @@ -1,21 +0,0 @@ -{{- define "app" -}} - - - - Gno.land - {{ .Data.Title }} - - {{ template "html_head" }} - - -
- -
-
{{ .Data.HomeContent }}
-
-
{{ template "subscribe" }}
- {{ template "footer" }} -
- {{ template "js" }} - - -{{- end -}} diff --git a/gno.land/cmd/gnoweb/views/package_dir.html b/gno.land/cmd/gnoweb/views/package_dir.html index 6b0bf5cfd48..efaf4d7ad0c 100644 --- a/gno.land/cmd/gnoweb/views/package_dir.html +++ b/gno.land/cmd/gnoweb/views/package_dir.html @@ -2,8 +2,8 @@ - Gno.land - {{ template "html_head" }} + {{ template "html_head" . }} + Gno.land - {{.Data.DirPath}}
@@ -12,13 +12,14 @@ {{ .Data.DirPath }}/*
{{ template "dir_contents" . }}
- {{ template "footer" }} - {{ template "js" }} + {{ template "js" . }} -{{- end -}} {{ define "dir_contents" }} +{{- end -}} + +{{- define "dir_contents" -}}
{{ $dirPath := .Data.DirPath }}
-{{ end }} +{{- end -}} diff --git a/gno.land/cmd/gnoweb/views/package_file.html b/gno.land/cmd/gnoweb/views/package_file.html index 7068854d16c..71aa8b68452 100644 --- a/gno.land/cmd/gnoweb/views/package_file.html +++ b/gno.land/cmd/gnoweb/views/package_file.html @@ -2,8 +2,8 @@ - Gno.land - {{ template "html_head" }} + {{ template "html_head" . }} + Gno.land - {{.Data.DirPath}}/{{.Data.FileName}}
@@ -18,7 +18,7 @@ {{ template "footer" }}
- {{ template "js" }} + {{ template "js" .}} - + {{ template "js" . }} -{{- end -}} +{{ define "func_specs" }} +{{- end -}} + +{{- define "func_specs" -}}
{{ $funcName := .Data.FuncName }} {{ $found := false }} {{ if eq $funcName "" }} {{ range .Data.FunctionSignatures }} {{ template "func_spec" . }} {{ end }} {{ else }} {{ range .Data.FunctionSignatures }} {{ if eq .FuncName $funcName }} {{ $found = true }} {{ template "func_spec" . }} {{ end }} {{ end }} {{ if not $found }} {{ $funcName }} not found. {{ end }} {{ end }}
-{{ end }} {{ define "func_spec" }} +{{- end -}} + +{{- define "func_spec" -}}
@@ -67,7 +69,9 @@
-{{ end }} {{ define "func_param" }} +{{- end -}} + +{{- define "func_param" -}} {{ .Name }} @@ -75,7 +79,9 @@ {{ .Type }} -{{ end }} {{ define "func_result" }} +{{- end -}} + +{{- define "func_result" -}} {{ .Name }} {{ .Type }} diff --git a/gno.land/cmd/gnoweb/views/realm_render.html b/gno.land/cmd/gnoweb/views/realm_render.html index 8a2c35adeca..6337d77aafa 100644 --- a/gno.land/cmd/gnoweb/views/realm_render.html +++ b/gno.land/cmd/gnoweb/views/realm_render.html @@ -2,8 +2,8 @@ - Gno.land - {{ template "html_head" }} + {{ template "html_head" . }} + Gno.land - {{.Data.RealmName}}
@@ -26,10 +26,7 @@
{{ template "footer" }} - {{ template "js" }} - - - + {{ template "js" .}} {{- end -}} diff --git a/gno.land/cmd/gnoweb/views/redirect.html b/gno.land/cmd/gnoweb/views/redirect.html new file mode 100644 index 00000000000..6fe43a7138b --- /dev/null +++ b/gno.land/cmd/gnoweb/views/redirect.html @@ -0,0 +1,16 @@ +{{- define "app" -}} + + + + + + + + Redirecting to {{.Data.To}} + + + {{.Data.To}} + {{ template "analytics" .}} + + +{{- end -}}