Skip to content

feat(gnoweb): form - input md extension #4061

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 62 commits into from
Jun 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
d643b58
feat: form adn input extension
alexiscolin Apr 5, 2025
2720ddf
feat: args order
alexiscolin Apr 5, 2025
24366a7
chore: update oderer
alexiscolin Apr 5, 2025
0a79e1f
feat: flexible queries
alexiscolin Apr 5, 2025
4abe486
feat: remove input index
alexiscolin Apr 5, 2025
8dbae09
fix: erase get data on form repost
alexiscolin Apr 5, 2025
4a8dc11
feat: add tests
alexiscolin Apr 7, 2025
b61e947
Merge branch 'master' into feat/gno-input-form
alexiscolin Apr 7, 2025
8898579
fix: remove useless submit
alexiscolin Apr 7, 2025
9e09984
style: update forms
alexiscolin Apr 7, 2025
f228d60
feat: improve security UX
alexiscolin Apr 7, 2025
e5ad925
Merge branch 'master' into feat/gno-input-form
alexiscolin Apr 7, 2025
4703d13
fix: tests
alexiscolin Apr 7, 2025
5443842
fix: linter
alexiscolin Apr 7, 2025
b0ad610
Merge branch 'master' into feat/gno-input-form
alexiscolin Apr 8, 2025
6381f0d
Merge branch 'master' into feat/gno-input-form
alexiscolin Apr 9, 2025
eac576f
chore: add info
Apr 11, 2025
f85a262
style: tooltip width
alexiscolin Apr 11, 2025
97a4e1a
Merge branch 'master' into feat/gno-input-form
alexiscolin Apr 11, 2025
6ec43f5
refactor: remove context approach
alexiscolin Apr 15, 2025
e39a707
Merge branch 'master' into feat/gno-input-form
alexiscolin Apr 15, 2025
e2051be
chore: generate
alexiscolin Apr 15, 2025
35aa217
fix: docs
alexiscolin Apr 15, 2025
34b59f1
Merge branch 'master' into feat/gno-input-form
alexiscolin Apr 16, 2025
58d57a5
Merge branch 'master' into feat/gno-input-form
alexiscolin May 22, 2025
d5ce51e
fix: style
alexiscolin May 22, 2025
5f33f9f
fix: linter
alexiscolin May 22, 2025
916ce94
fix: tests
alexiscolin May 22, 2025
2f035fb
Merge branch 'master' into feat/gno-input-form
alexiscolin May 23, 2025
09dab36
feat: input type
alexiscolin May 23, 2025
eef3e06
feat: add path and tests
alexiscolin May 23, 2025
4901dd0
fix: linter
alexiscolin May 23, 2025
479bd6e
Merge branch 'master' into feat/gno-input-form
alexiscolin May 27, 2025
62a02c9
Update gno.land/pkg/gnoweb/markdown/ext_forms.go
alexiscolin May 27, 2025
666a733
Update gno.land/pkg/gnoweb/markdown/ext_forms.go
alexiscolin May 27, 2025
0bee90b
Update gno.land/pkg/gnoweb/markdown/ext_forms.go
alexiscolin May 27, 2025
b36a51e
Update gno.land/pkg/gnoweb/markdown/ext_forms.go
alexiscolin May 27, 2025
a4b8ffd
Update gno.land/pkg/gnoweb/markdown/ext_forms.go
alexiscolin May 27, 2025
ff5358a
Update gno.land/pkg/gnoweb/markdown/ext_forms.go
alexiscolin May 27, 2025
4335090
Update gno.land/pkg/gnoweb/markdown/ext_forms.go
alexiscolin May 27, 2025
7fdfea8
fix: comments and review
alexiscolin May 27, 2025
6a4df27
Merge branch 'master' into feat/gno-input-form
alexiscolin May 27, 2025
842a592
Merge branch 'master' into feat/gno-input-form
alexiscolin May 28, 2025
5c986c9
Merge branch 'master' into feat/gno-input-form
alexiscolin May 28, 2025
7893ada
Merge branch 'master' into feat/gno-input-form
alexiscolin Jun 17, 2025
194c62e
Merge branch 'master' into feat/gno-input-form
alexiscolin Jun 17, 2025
5ef59cd
Merge branch 'master' into feat/gno-input-form
alexiscolin Jun 19, 2025
3299ff8
fix: fix forms using a single node
gfanton Jun 19, 2025
0798559
feat(md): add md txtar test
gfanton Jun 19, 2025
428ab30
fix: update txtar
gfanton Jun 19, 2025
a2e1833
Merge branch 'master' into feat/gno-input-form
alexiscolin Jun 20, 2025
b0cc900
chore: generate
alexiscolin Jun 20, 2025
7a3c19d
chore: cleanup unused stuff
gfanton Jun 20, 2025
94e8868
fix: docs
alexiscolin Jun 20, 2025
6d22a8f
fix: docs
alexiscolin Jun 20, 2025
9d2dd6b
fix: avoid uplidate name
gfanton Jun 20, 2025
c1bb767
fix: TestServeHTTPMethodNotAllowed
alexiscolin Jun 20, 2025
6f72a57
fix: html space
alexiscolin Jun 20, 2025
26ce2b1
Merge branch 'master' into feat/gno-input-form
alexiscolin Jun 22, 2025
792fdb8
fix: header colors
alexiscolin Jun 22, 2025
a76edc5
Merge branch 'master' into feat/gno-input-form
alexiscolin Jun 23, 2025
ed94f73
fix: nits
alexiscolin Jun 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 97 additions & 2 deletions examples/gno.land/r/docs/markdown/markdown.gno
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,103 @@ My Image ![Alt](/public/imgs/gnoland.svg)

---

### Forms

Gno-Flavored Markdown introduces a secure form system that integrates directly with realms. The system uses custom HTML-like tags that are rendered with proper styling and validation.

#### Basic Usage

Create a form using the ±<gno-form>± tag and add inputs with ±<gno-input>±:

±±±markdown
<gno-form>
<gno-input name="name" placeholder="Enter your name" />
<gno-input name="email" placeholder="Enter your email" />
</gno-form>
±±±

<gno-form>
<gno-input name="name" placeholder="Enter your name" />
<gno-input name="email" placeholder="Enter your email" />
</gno-form>

#### Form Structure

1. **Form Container**
- Must start with ±<gno-form>± and end with ±</gno-form>±
- Optional attributes:
- ±path±: Render path after form submission (e.g. ±path="user"± will redirect to ±:user?[params]±)
- Form data is always sent as query parameters
- Cannot be nested (forms inside forms are not allowed)
- Automatically includes a header showing the realm name

2. **Input Fields**
- Created with ±<gno-input>± tag
- Required attributes:
- ±name±: Unique identifier for the input (required)
- ±type±: Type of input (optional, defaults to "text")
- ±placeholder±: Hint text shown in the input (optional, defaults to "Enter value")
- Supported input types:
- ±type="text"± (default): For text input
- ±type="number"±: For numeric values only (with browser validation)
- ±type="email"±: For email addresses (with basic browser validation)
- ±type="tel"±: For phone numbers (no specific validation)
- ±type="password"±: For password input (masked characters)
- Note: Input validation is handled by the browser's HTML5 validation
- Any other type will default to "text"
- Each input must have a unique name
- Inputs are rendered with labels and proper styling

#### Example Use Cases

1. Form with Path and Query Parameters
±±±markdown
<gno-form path="user">
<gno-input name="username" type="text" placeholder="Enter username" />
<gno-input name="age" type="number" placeholder="Your age" />
</gno-form>
±±±

<gno-form path="user">
<gno-input name="username" type="text" placeholder="Enter username" />
<gno-input name="age" type="number" placeholder="Your age" />
</gno-form>

This form will submit to ±:user?username=value&age=value± on the same realm.

2. Form with Query Parameters Only
±±±markdown
<gno-form>
<gno-input name="recipient" type="text" placeholder="Enter recipient address" />
<gno-input name="email" type="email" placeholder="Your email" />
<gno-input name="pswd" type="password" placeholder="Your password" />
</gno-form>
±±±

<gno-form>
<gno-input name="recipient" type="text" placeholder="Enter recipient address" />
<gno-input name="email" type="email" placeholder="Your email" />
<gno-input name="pswd" type="password" placeholder="Your password" />
</gno-form>

This form will submit to ±?recipient=value&email=value&pswd=value± on the same realm.

#### Important Rules

1. **Validation Rules**:
- Every ±<gno-input>± must have a ±name± attribute
- Duplicate attribute names are not allowed
- Forms must be properly closed
- Nested forms will not render

2. **Security Features**:
- Forms are processed on the realm where they are defined
- Each form submission is associated with its realm
- The realm name is displayed in the form header
- Input validation is handled by the realm's smart contract

---

### Alerts

Alerts are a way to highlight important information in your markdown.
Expand Down Expand Up @@ -458,8 +555,6 @@ By default, the alert boxes are opened. The title is optional and if not provide
> > [!NOTE]
> > This is a scoped note

---

### Usernames

XXX: TODO (add again this feature that was removed)
Expand Down
23 changes: 23 additions & 0 deletions gno.land/pkg/gnoweb/frontend/css/input.css
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,29 @@
@apply grow shrink basis-52 lg:basis-44;
}

.realm-view .gno-form {
@apply block border rounded pt-2 pb-4 px-4 my-12;
}
.realm-view .gno-form_header {
@apply flex justify-between text-gray-300 text-50 mb-6;
}
.realm-view .gno-form label {
@apply absolute top-0 left-2 -translate-y-1/2 text-gray-300 text-50 bg-light px-1 hidden;
}
.realm-view .gno-form_input {
@apply relative;
}
.realm-view .gno-form_input:has(input:focus) label,
.realm-view .gno-form_input:has(input:not(:placeholder-shown)) label {
@apply block;
}
.realm-view .gno-form input {
@apply block p-2 rounded-sm border-gray-200 focus:border-gray-300 hover:border-gray-300 text-gray-600 outline-none font-mono mb-4 w-full;
}
.realm-view .gno-form input[type="submit"] {
@apply bg-green-600 text-light border-green-600 cursor-pointer font-interVar mb-2 hover:opacity-90;
}

/* Alert */
.realm-view .gno-alert {
@apply border-l-4 border-l-gray-300 px-4 py-3 text-gray-600 rounded my-10;
Expand Down
43 changes: 38 additions & 5 deletions gno.land/pkg/gnoweb/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,15 @@
func (h *WebHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.Logger.Debug("receiving request", "method", r.Method, "path", r.URL.Path)

if r.Method != http.MethodGet {
switch r.Method {
case http.MethodGet:
w.Header().Add("Content-Type", "text/html; charset=utf-8")
h.Get(w, r)
case http.MethodPost:
h.Post(w, r)

Check warning on line 104 in gno.land/pkg/gnoweb/handler.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/gnoweb/handler.go#L103-L104

Added lines #L103 - L104 were not covered by tests
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}

w.Header().Add("Content-Type", "text/html; charset=utf-8")
h.Get(w, r)
}

// Get processes a GET HTTP request.
Expand Down Expand Up @@ -169,6 +171,37 @@
}
}

// Post processes a POST HTTP request.
func (h *WebHandler) Post(w http.ResponseWriter, r *http.Request) {
start := time.Now()
defer func() {
h.Logger.Debug("request completed",
"url", r.URL.String(),
"elapsed", time.Since(start).String())
}()

Check warning on line 181 in gno.land/pkg/gnoweb/handler.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/gnoweb/handler.go#L175-L181

Added lines #L175 - L181 were not covered by tests

// Parse the form data
if err := r.ParseForm(); err != nil {
h.Logger.Error("failed to parse form", "error", err)
http.Error(w, "bad request", http.StatusBadRequest)
return
}

Check warning on line 188 in gno.land/pkg/gnoweb/handler.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/gnoweb/handler.go#L184-L188

Added lines #L184 - L188 were not covered by tests

// Parse the URL
gnourl, err := weburl.ParseFromURL(r.URL)
if err != nil {
h.Logger.Warn("unable to parse url path", "path", r.URL.Path, "error", err)
http.Error(w, "invalid path", http.StatusNotFound)
return
}

Check warning on line 196 in gno.land/pkg/gnoweb/handler.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/gnoweb/handler.go#L191-L196

Added lines #L191 - L196 were not covered by tests

// Use form data as query
gnourl.Query = r.PostForm

// Redirect to the new URL
http.Redirect(w, r, gnourl.EncodeWebURL(), http.StatusSeeOther)

Check warning on line 202 in gno.land/pkg/gnoweb/handler.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/gnoweb/handler.go#L199-L202

Added lines #L199 - L202 were not covered by tests
}

// prepareIndexBodyView prepares the data and main view for the index.
func (h *WebHandler) prepareIndexBodyView(r *http.Request, indexData *components.IndexData) (int, *components.View) {
aliasTarget, aliasExists := h.Aliases[r.URL.Path]
Expand Down
4 changes: 2 additions & 2 deletions gno.land/pkg/gnoweb/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ func TestIsHomePath(t *testing.T) {
assert.False(t, gnoweb.IsHomePath("/foo"))
}

// TestServeHTTPMethodNotAllowed verifies 405 for POST/PUT/etc methods.
// TestServeHTTPMethodNotAllowed verifies 405 for HTTP methods.
func TestServeHTTPMethodNotAllowed(t *testing.T) {
t.Parallel()

Expand All @@ -434,7 +434,7 @@ func TestServeHTTPMethodNotAllowed(t *testing.T) {
handler, err := gnoweb.NewWebHandler(logger, cfg)
require.NoError(t, err)

req := httptest.NewRequest(http.MethodPost, "/r/ex", nil)
req := httptest.NewRequest(http.MethodDelete, "/r/ex", nil)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)

Expand Down
3 changes: 3 additions & 0 deletions gno.land/pkg/gnoweb/markdown/ext.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ func (e *GnoExtension) Extend(m goldmark.Markdown) {
// Add link extension
ExtLinks.Extend(m)

// Add form / inputs extension
ExtForms.Extend(m)

// If set, setup images filter
if e.cfg.imgValidatorFunc != nil {
ExtImageValidator.Extend(m, e.cfg.imgValidatorFunc)
Expand Down
Loading
Loading