~repos /gromer
git clone https://pyrossh.dev/repos/gromer.git
gromer is a framework and cli to build multipage web apps in golang using htmx and alpinejs.
13e9ba96
—
Peter John 3 years ago
something working
- _example/components/page.go +4 -8
- _example/components/todo.go +4 -4
- _example/containers/TodoCount.go +4 -6
- _example/containers/TodoList.go +9 -19
- _example/main.go +9 -9
- _example/pages/about/get.go +0 -20
- _example/pages/get.go +0 -103
- _example/{pages → routes}/404/get.go +4 -5
- _example/routes/about/get.go +18 -0
- _example/routes/get.go +119 -0
- _example/{pages → routes}/post.go +16 -17
- cmd/gromer/main.go +13 -14
- go.mod +22 -6
- go.sum +38 -5
- gsx/gsx.go +106 -27
- gsx/gsx_test.go +84 -10
- http.go +13 -7
_example/components/page.go
CHANGED
|
@@ -217,9 +217,9 @@ var _ = Css(`
|
|
|
217
217
|
}
|
|
218
218
|
`)
|
|
219
219
|
|
|
220
|
-
func Page(
|
|
220
|
+
func Page(ctx Context, title string) *Node {
|
|
221
|
+
ctx.Set("title", title)
|
|
221
|
-
return
|
|
222
|
+
return ctx.Render(`
|
|
222
|
-
<!DOCTYPE html>
|
|
223
223
|
<html lang="en">
|
|
224
224
|
<head>
|
|
225
225
|
<meta charset="UTF-8" />
|
|
@@ -228,12 +228,8 @@ func Page(h Html, title string) string {
|
|
|
228
228
|
<title>{title}</title>
|
|
229
229
|
<meta name="description" content="{title}" />
|
|
230
230
|
<meta name="author" content="pyrossh" />
|
|
231
|
-
<meta name="keywords" content="pyros.sh, pyrossh, gromer"
|
|
231
|
+
<meta name="keywords" content="pyros.sh, pyrossh, gromer" />
|
|
232
232
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, viewport-fit=cover" />
|
|
233
|
-
<link rel="icon" href="{GetAssetUrl "images/icon.png"}" />
|
|
234
|
-
<link rel="stylesheet" href="{GetStylesUrl}" />
|
|
235
|
-
<script src="{GetAlpineJsUrl}"></script>
|
|
236
|
-
<script src="{GetHtmxJsUrl}" defer=""></script>
|
|
237
233
|
</head>
|
|
238
234
|
<body>
|
|
239
235
|
{children}
|
_example/components/todo.go
CHANGED
|
@@ -8,14 +8,14 @@ import (
|
|
|
8
8
|
var _ = Css(`
|
|
9
9
|
`)
|
|
10
10
|
|
|
11
|
-
func Todo(
|
|
11
|
+
func Todo(ctx Context, todo *todos.Todo) *Node {
|
|
12
|
-
return
|
|
12
|
+
return ctx.Render(`
|
|
13
|
-
<li id="todo-{todo.ID}" class={
|
|
13
|
+
<li id="todo-{todo.ID}" class="{ completed: todo.Completed }">
|
|
14
14
|
<div class="view">
|
|
15
15
|
<form hx-target="#todo-{todo.ID}" hx-swap="outerHTML">
|
|
16
16
|
<input type="hidden" name="intent" value="complete" />
|
|
17
17
|
<input type="hidden" name="id" value="{todo.ID}" />
|
|
18
|
-
<input hx-post="/" class="checkbox" type="checkbox" checked={
|
|
18
|
+
<input hx-post="/" class="checkbox" type="checkbox" checked="{ completed: todo.Completed }" />
|
|
19
19
|
</form>
|
|
20
20
|
<label>{todo.Text}</label>
|
|
21
21
|
<form hx-post="/" hx-target="#todo-{todo.ID}" hx-swap="delete">
|
_example/containers/TodoCount.go
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
package containers
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
-
"context"
|
|
5
|
-
|
|
6
4
|
"github.com/pyros2097/gromer/_example/services/todos"
|
|
7
5
|
. "github.com/pyros2097/gromer/gsx"
|
|
8
6
|
)
|
|
@@ -18,16 +16,16 @@ var _ = Css(`
|
|
|
18
16
|
}
|
|
19
17
|
`)
|
|
20
18
|
|
|
21
|
-
func TodoCount(
|
|
19
|
+
func TodoCount(ctx Context, filter string) (*Node, error) {
|
|
22
20
|
todos, err := todos.GetAllTodo(ctx, todos.GetAllTodoParams{
|
|
23
21
|
Filter: filter,
|
|
24
22
|
Limit: 1000,
|
|
25
23
|
})
|
|
26
24
|
if err != nil {
|
|
27
|
-
return
|
|
25
|
+
return nil, err
|
|
28
26
|
}
|
|
29
|
-
|
|
27
|
+
ctx.Set("count", len(todos))
|
|
30
|
-
return
|
|
28
|
+
return ctx.Render(`
|
|
31
29
|
<span class="todo-count" id="todo-count" hx-swap-oob="true">
|
|
32
30
|
<strong>{count}</strong> item left
|
|
33
31
|
</span>
|
_example/containers/TodoList.go
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
package containers
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
-
"context"
|
|
5
|
-
|
|
6
4
|
. "github.com/pyros2097/gromer"
|
|
7
5
|
"github.com/pyros2097/gromer/_example/services/todos"
|
|
8
6
|
. "github.com/pyros2097/gromer/gsx"
|
|
@@ -129,27 +127,19 @@ var _ = Css(`
|
|
|
129
127
|
|
|
130
128
|
`)
|
|
131
129
|
|
|
132
|
-
type TodoListProps struct {
|
|
133
|
-
ID string `json:"id"`
|
|
134
|
-
Page int `json:"page"`
|
|
135
|
-
Filter string `json:"filter"`
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
func TodoList(
|
|
130
|
+
func TodoList(ctx Context, page int, filter string) (*Node, error) {
|
|
139
|
-
index := Default(
|
|
131
|
+
index := Default(page, 1)
|
|
140
132
|
todos, err := todos.GetAllTodo(ctx, todos.GetAllTodoParams{
|
|
141
|
-
Filter:
|
|
133
|
+
Filter: filter,
|
|
142
134
|
Limit: index,
|
|
143
135
|
})
|
|
144
136
|
if err != nil {
|
|
145
|
-
return
|
|
137
|
+
return nil, err
|
|
146
|
-
}
|
|
138
|
+
}
|
|
147
|
-
|
|
139
|
+
ctx.Set("todos", todos)
|
|
148
|
-
return
|
|
140
|
+
return ctx.Render(`
|
|
149
|
-
<ul id="todo-list" class="relative">
|
|
141
|
+
<ul id="todo-list" class="relative" x-for="todo in todos">
|
|
150
|
-
<For key="todos" itemKey="todo">
|
|
151
|
-
<Todo key="todo"></Todo>
|
|
152
|
-
</
|
|
142
|
+
<Todo />
|
|
153
143
|
</ul>
|
|
154
144
|
`), nil
|
|
155
145
|
}
|
_example/main.go
CHANGED
|
@@ -10,19 +10,19 @@ import (
|
|
|
10
10
|
"github.com/pyros2097/gromer/_example/assets"
|
|
11
11
|
"github.com/pyros2097/gromer/_example/components"
|
|
12
12
|
"github.com/pyros2097/gromer/_example/containers"
|
|
13
|
-
"github.com/pyros2097/gromer/_example/
|
|
13
|
+
"github.com/pyros2097/gromer/_example/routes/404"
|
|
14
|
-
"github.com/pyros2097/gromer/_example/
|
|
14
|
+
"github.com/pyros2097/gromer/_example/routes"
|
|
15
|
-
"github.com/pyros2097/gromer/_example/
|
|
15
|
+
"github.com/pyros2097/gromer/_example/routes/about"
|
|
16
16
|
"github.com/pyros2097/gromer/gsx"
|
|
17
17
|
|
|
18
18
|
)
|
|
19
19
|
|
|
20
20
|
func init() {
|
|
21
|
-
gsx.RegisterComponent(components.Page)
|
|
21
|
+
gsx.RegisterComponent(components.Page, "title")
|
|
22
|
-
gsx.RegisterComponent(components.Todo)
|
|
22
|
+
gsx.RegisterComponent(components.Todo, "todo")
|
|
23
23
|
|
|
24
|
-
gsx.RegisterComponent(containers.TodoCount)
|
|
24
|
+
gsx.RegisterComponent(containers.TodoCount, "filter")
|
|
25
|
-
gsx.RegisterComponent(containers.TodoList)
|
|
25
|
+
gsx.RegisterComponent(containers.TodoList, "page", "filter")
|
|
26
26
|
gromer.RegisterAssets(assets.FS)
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -40,8 +40,8 @@ func main() {
|
|
|
40
40
|
|
|
41
41
|
pageRouter := baseRouter.NewRoute().Subrouter()
|
|
42
42
|
// gromer.ApiExplorerRoute(pageRouter, "/explorer")
|
|
43
|
-
gromer.Handle(pageRouter, "GET", "/",
|
|
43
|
+
gromer.Handle(pageRouter, "GET", "/", routes.GET)
|
|
44
|
-
gromer.Handle(pageRouter, "POST", "/",
|
|
44
|
+
gromer.Handle(pageRouter, "POST", "/", routes.POST)
|
|
45
45
|
gromer.Handle(pageRouter, "GET", "/about", about.GET)
|
|
46
46
|
|
|
47
47
|
|
_example/pages/about/get.go
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
package about
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"context"
|
|
5
|
-
|
|
6
|
-
. "github.com/pyros2097/gromer/gsx"
|
|
7
|
-
)
|
|
8
|
-
|
|
9
|
-
func GET(h Html, c context.Context) (string, int, error) {
|
|
10
|
-
return h.Render(`
|
|
11
|
-
{{#Page title="About me"}}
|
|
12
|
-
<div class="flex flex-col justify-center items-center">
|
|
13
|
-
{{#Header}}
|
|
14
|
-
A new link is here
|
|
15
|
-
{{/Header}}
|
|
16
|
-
<h1>About Me</h1>
|
|
17
|
-
</div>
|
|
18
|
-
{{/Page}}
|
|
19
|
-
`), 200, nil
|
|
20
|
-
}
|
_example/pages/get.go
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
package pages
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"context"
|
|
5
|
-
|
|
6
|
-
_ "github.com/pyros2097/gromer/_example/components"
|
|
7
|
-
. "github.com/pyros2097/gromer/gsx"
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
var _ = Css(`
|
|
11
|
-
.todoapp {
|
|
12
|
-
background: #fff;
|
|
13
|
-
margin: 130px 0 40px 0;
|
|
14
|
-
position: relative;
|
|
15
|
-
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
.todoapp input::-webkit-input-placeholder {
|
|
19
|
-
font-style: italic;
|
|
20
|
-
font-weight: 300;
|
|
21
|
-
color: #e6e6e6;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
.todoapp input::-moz-placeholder {
|
|
25
|
-
font-style: italic;
|
|
26
|
-
font-weight: 300;
|
|
27
|
-
color: #e6e6e6;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
.todoapp input::input-placeholder {
|
|
31
|
-
font-style: italic;
|
|
32
|
-
font-weight: 300;
|
|
33
|
-
color: #e6e6e6;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
.todoapp h1 {
|
|
37
|
-
position: absolute;
|
|
38
|
-
top: -155px;
|
|
39
|
-
width: 100%;
|
|
40
|
-
font-size: 100px;
|
|
41
|
-
font-weight: 100;
|
|
42
|
-
text-align: center;
|
|
43
|
-
color: rgba(175, 47, 47, 0.15);
|
|
44
|
-
-webkit-text-rendering: optimizeLegibility;
|
|
45
|
-
-moz-text-rendering: optimizeLegibility;
|
|
46
|
-
text-rendering: optimizeLegibility;
|
|
47
|
-
}
|
|
48
|
-
`)
|
|
49
|
-
|
|
50
|
-
// var (
|
|
51
|
-
// Container = Css(`
|
|
52
|
-
// background: #fff;
|
|
53
|
-
// margin: 130px 0 40px 0;
|
|
54
|
-
// position: relative;
|
|
55
|
-
// box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
|
56
|
-
// `)
|
|
57
|
-
// )
|
|
58
|
-
|
|
59
|
-
type GetParams struct {
|
|
60
|
-
Page int `json:"page"`
|
|
61
|
-
Filter string `json:"filter"`
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
func GET(h Html, ctx context.Context, params GetParams) (string, int, error) {
|
|
65
|
-
h["page"] = params.Page
|
|
66
|
-
h["filter"] = params.Filter
|
|
67
|
-
return h.Render(`
|
|
68
|
-
<Page title="gromer example">
|
|
69
|
-
<section class="todoapp">
|
|
70
|
-
<header class="header">
|
|
71
|
-
<h1>todos</h1>
|
|
72
|
-
<form hx-post="/" hx-target="#todo-list" hx-swap="afterbegin" _="on htmx:afterOnLoad set #text.value to ''">
|
|
73
|
-
<input type="hidden" name="intent" value="create"></input>
|
|
74
|
-
<input class="new-todo" id="text" name="text" placeholder="What needs to be done?" autofocus="false" autocomplete="off"></input>
|
|
75
|
-
</form>
|
|
76
|
-
</header>
|
|
77
|
-
<section class="main">
|
|
78
|
-
<input class="toggle-all" id="toggle-all" type="checkbox">
|
|
79
|
-
<label for="toggle-all">Mark all as complete</label>
|
|
80
|
-
<TodoList id="todo-list" page={page} filter={filter}></TodoList>
|
|
81
|
-
</section>
|
|
82
|
-
<footer class="footer">
|
|
83
|
-
<TodoCount filter={filter}></TodoCount>
|
|
84
|
-
<ul class="filters">
|
|
85
|
-
<li>
|
|
86
|
-
<a href="?filter=all">All</a>
|
|
87
|
-
</li>
|
|
88
|
-
<li>
|
|
89
|
-
<a href="?filter=active">Active</a>
|
|
90
|
-
</li>
|
|
91
|
-
<li>
|
|
92
|
-
<a href="?filter=completed">Completed</a>
|
|
93
|
-
</li>
|
|
94
|
-
</ul>
|
|
95
|
-
<form hx-target="#todo-list" hx-post="/">
|
|
96
|
-
<input type="hidden" name="intent" value="clear_completed" />
|
|
97
|
-
<button type="submit" class="clear-completed" >Clear completed</button>
|
|
98
|
-
</form>
|
|
99
|
-
</footer>
|
|
100
|
-
</section>
|
|
101
|
-
</Page>
|
|
102
|
-
`), 200, nil
|
|
103
|
-
}
|
_example/{pages → routes}/404/get.go
RENAMED
|
@@ -6,16 +6,15 @@ import (
|
|
|
6
6
|
. "github.com/pyros2097/gromer/gsx"
|
|
7
7
|
)
|
|
8
8
|
|
|
9
|
-
func GET(h
|
|
9
|
+
func GET(h Context, c context.Context) (*Node, int, error) {
|
|
10
10
|
return h.Render(`
|
|
11
|
-
|
|
11
|
+
<Page title="Page Not Found">
|
|
12
|
-
{{#Header}}{{/Header}}
|
|
13
12
|
<main class="box center">
|
|
14
13
|
<h1>Page Not Found</h1>
|
|
15
14
|
<h2 class="mt-6">
|
|
16
15
|
<a class="is-underlined" href="/">Go Back</a>
|
|
17
|
-
</
|
|
16
|
+
</h2>
|
|
18
17
|
</main>
|
|
19
|
-
|
|
18
|
+
</Page>
|
|
20
19
|
`), 404, nil
|
|
21
20
|
}
|
_example/routes/about/get.go
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
package about
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
|
|
6
|
+
. "github.com/pyros2097/gromer/gsx"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
func GET(h Context, c context.Context) (*Node, int, error) {
|
|
10
|
+
return h.Render(`
|
|
11
|
+
<Page title="About me">
|
|
12
|
+
<div class="flex flex-col justify-center items-center">
|
|
13
|
+
A new link is here
|
|
14
|
+
P<h1>About Me</h1>
|
|
15
|
+
</div>
|
|
16
|
+
</Page>
|
|
17
|
+
`), 200, nil
|
|
18
|
+
}
|
_example/routes/get.go
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
package routes
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
|
|
6
|
+
_ "github.com/pyros2097/gromer/_example/components"
|
|
7
|
+
. "github.com/pyros2097/gromer/gsx"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
var _ = Css(`
|
|
11
|
+
.todoapp {
|
|
12
|
+
background: #fff;
|
|
13
|
+
margin: 130px 0 40px 0;
|
|
14
|
+
position: relative;
|
|
15
|
+
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.todoapp input::-webkit-input-placeholder {
|
|
19
|
+
font-style: italic;
|
|
20
|
+
font-weight: 300;
|
|
21
|
+
color: #e6e6e6;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.todoapp input::-moz-placeholder {
|
|
25
|
+
font-style: italic;
|
|
26
|
+
font-weight: 300;
|
|
27
|
+
color: #e6e6e6;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.todoapp input::input-placeholder {
|
|
31
|
+
font-style: italic;
|
|
32
|
+
font-weight: 300;
|
|
33
|
+
color: #e6e6e6;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.todoapp h1 {
|
|
37
|
+
position: absolute;
|
|
38
|
+
top: -155px;
|
|
39
|
+
width: 100%;
|
|
40
|
+
font-size: 100px;
|
|
41
|
+
font-weight: 100;
|
|
42
|
+
text-align: center;
|
|
43
|
+
color: rgba(175, 47, 47, 0.15);
|
|
44
|
+
-webkit-text-rendering: optimizeLegibility;
|
|
45
|
+
-moz-text-rendering: optimizeLegibility;
|
|
46
|
+
text-rendering: optimizeLegibility;
|
|
47
|
+
}
|
|
48
|
+
`)
|
|
49
|
+
|
|
50
|
+
// var (
|
|
51
|
+
// Container = Css(`
|
|
52
|
+
// background: #fff;
|
|
53
|
+
// margin: 130px 0 40px 0;
|
|
54
|
+
// position: relative;
|
|
55
|
+
// box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
|
56
|
+
// `)
|
|
57
|
+
// )
|
|
58
|
+
|
|
59
|
+
type GetParams struct {
|
|
60
|
+
Page int `json:"page"`
|
|
61
|
+
Filter string `json:"filter"`
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// func IndexLoader() {
|
|
65
|
+
// Data: M{},
|
|
66
|
+
// Meta: []*Meta{},
|
|
67
|
+
// Links: []*Link{},
|
|
68
|
+
// }
|
|
69
|
+
|
|
70
|
+
func IndexAction(h Context, ctx context.Context, params PostParams) {
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func IndexPage() {
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// <meta name="description" content="{title}" />
|
|
77
|
+
// <meta name="author" content="pyrossh" />
|
|
78
|
+
// <meta name="keywords" content="pyros.sh, pyrossh, gromer" />
|
|
79
|
+
// <meta />
|
|
80
|
+
|
|
81
|
+
func GET(h Context, params GetParams) (*Node, int, error) {
|
|
82
|
+
h.Meta("title", "Todos")
|
|
83
|
+
h.Set("page", params.Page)
|
|
84
|
+
h.Set("filter", params.Filter)
|
|
85
|
+
return h.Render(`
|
|
86
|
+
<section class="todoapp">
|
|
87
|
+
<header class="header">
|
|
88
|
+
<h1>todos</h1>
|
|
89
|
+
<form hx-post="/" hx-target="#todo-list" hx-swap="afterbegin" _="on htmx:afterOnLoad set #text.value to ''">
|
|
90
|
+
<input type="hidden" name="intent" value="create" />
|
|
91
|
+
<input class="new-todo" id="text" name="text" placeholder="What needs to be done?" autofocus="false" autocomplete="off" /s>
|
|
92
|
+
</form>
|
|
93
|
+
</header>
|
|
94
|
+
<section class="main">
|
|
95
|
+
<input class="toggle-all" id="toggle-all" type="checkbox" />
|
|
96
|
+
<label for="toggle-all">Mark all as complete</label>
|
|
97
|
+
<TodoList id="todo-list" page="{page}" filter="{filter}" />
|
|
98
|
+
</section>
|
|
99
|
+
<footer class="footer">
|
|
100
|
+
<TodoCount filter="{filter}" />
|
|
101
|
+
<ul class="filters">
|
|
102
|
+
<li>
|
|
103
|
+
<a href="?filter=all">All</a>
|
|
104
|
+
</li>
|
|
105
|
+
<li>
|
|
106
|
+
<a href="?filter=active">Active</a>
|
|
107
|
+
</li>
|
|
108
|
+
<li>
|
|
109
|
+
<a href="?filter=completed">Completed</a>
|
|
110
|
+
</li>
|
|
111
|
+
</ul>
|
|
112
|
+
<form hx-target="#todo-list" hx-post="/">
|
|
113
|
+
<input type="hidden" name="intent" value="clear_completed" />
|
|
114
|
+
<button type="submit" class="clear-completed" >Clear completed</button>
|
|
115
|
+
</form>
|
|
116
|
+
</footer>
|
|
117
|
+
</section>
|
|
118
|
+
`), 200, nil
|
|
119
|
+
}
|
_example/{pages → routes}/post.go
RENAMED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
package
|
|
1
|
+
package routes
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
-
"context"
|
|
5
4
|
"fmt"
|
|
6
5
|
|
|
7
6
|
_ "github.com/pyros2097/gromer/_example/components"
|
|
@@ -15,59 +14,59 @@ type PostParams struct {
|
|
|
15
14
|
Text string `json:"text"`
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
func POST(
|
|
17
|
+
func POST(ctx Context, params PostParams) (*Node, int, error) {
|
|
19
18
|
if params.Intent == "clear_completed" {
|
|
20
19
|
allTodos, err := todos.GetAllTodo(ctx, todos.GetAllTodoParams{
|
|
21
20
|
Filter: "all",
|
|
22
21
|
Limit: 1000,
|
|
23
22
|
})
|
|
24
23
|
if err != nil {
|
|
25
|
-
return
|
|
24
|
+
return nil, 500, err
|
|
26
25
|
}
|
|
27
26
|
for _, t := range allTodos {
|
|
28
27
|
if t.Completed {
|
|
29
28
|
_, err := todos.DeleteTodo(ctx, t.ID)
|
|
30
29
|
if err != nil {
|
|
31
|
-
return
|
|
30
|
+
return nil, 500, err
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
33
|
}
|
|
35
|
-
return
|
|
34
|
+
return ctx.Render(`
|
|
36
35
|
<TodoList id="todo-list" filter="all" page="1"></TodoList>
|
|
37
36
|
<TodoCount filter="all" page="1"></TodoCount>
|
|
38
37
|
`), 200, nil
|
|
39
38
|
} else if params.Intent == "create" {
|
|
40
39
|
todo, err := todos.CreateTodo(ctx, params.Text)
|
|
41
40
|
if err != nil {
|
|
42
|
-
return
|
|
41
|
+
return nil, 500, err
|
|
43
42
|
}
|
|
44
|
-
|
|
43
|
+
ctx.Set("todo", todo)
|
|
45
|
-
return
|
|
44
|
+
return ctx.Render(`
|
|
46
45
|
<Todo todo=todo></Todo>
|
|
47
|
-
<TodoCount filter="all" page=1></TodoCount>
|
|
46
|
+
<TodoCount filter="all" page="1"></TodoCount>
|
|
48
47
|
`), 200, nil
|
|
49
48
|
} else if params.Intent == "delete" {
|
|
50
49
|
_, err := todos.DeleteTodo(ctx, params.ID)
|
|
51
50
|
if err != nil {
|
|
52
|
-
return
|
|
51
|
+
return nil, 500, err
|
|
53
52
|
}
|
|
54
|
-
return
|
|
53
|
+
return nil, 200, nil
|
|
55
54
|
} else if params.Intent == "complete" {
|
|
56
55
|
todo, err := todos.GetTodo(ctx, params.ID)
|
|
57
56
|
if err != nil {
|
|
58
|
-
return
|
|
57
|
+
return nil, 500, err
|
|
59
58
|
}
|
|
60
59
|
_, err = todos.UpdateTodo(ctx, params.ID, todos.UpdateTodoParams{
|
|
61
60
|
Text: todo.Text,
|
|
62
61
|
Completed: !todo.Completed,
|
|
63
62
|
})
|
|
64
63
|
if err != nil {
|
|
65
|
-
return
|
|
64
|
+
return nil, 500, err
|
|
66
65
|
}
|
|
67
|
-
|
|
66
|
+
ctx.Set("todo", todo)
|
|
68
|
-
return
|
|
67
|
+
return ctx.Render(`
|
|
69
68
|
{{#Todo todo=todo}}{{/Todo}}
|
|
70
69
|
`), 200, nil
|
|
71
70
|
}
|
|
72
|
-
return
|
|
71
|
+
return nil, 404, fmt.Errorf("Intent not specified: %s", params.Intent)
|
|
73
72
|
}
|
cmd/gromer/main.go
CHANGED
|
@@ -12,9 +12,8 @@ import (
|
|
|
12
12
|
"strings"
|
|
13
13
|
"unicode"
|
|
14
14
|
|
|
15
|
+
"github.com/gobuffalo/velvet"
|
|
15
16
|
"github.com/pyros2097/gromer"
|
|
16
|
-
"github.com/pyros2097/gromer/handlebars"
|
|
17
|
-
. "github.com/pyros2097/gromer/handlebars"
|
|
18
17
|
"golang.org/x/mod/modfile"
|
|
19
18
|
)
|
|
20
19
|
|
|
@@ -141,7 +140,7 @@ func main() {
|
|
|
141
140
|
pageRoutes = append(pageRoutes, r)
|
|
142
141
|
}
|
|
143
142
|
}
|
|
144
|
-
err =
|
|
143
|
+
err = velvet.Helpers.Add("title", func(v string) string {
|
|
145
144
|
return strings.Title(strings.ToLower(v))
|
|
146
145
|
})
|
|
147
146
|
if err != nil {
|
|
@@ -177,7 +176,16 @@ func main() {
|
|
|
177
176
|
log.Fatal(err)
|
|
178
177
|
}
|
|
179
178
|
}
|
|
179
|
+
ctx := velvet.NewContext()
|
|
180
|
+
ctx.Set("moduleName", moduleName)
|
|
181
|
+
ctx.Set("pageRoutes", pageRoutes)
|
|
182
|
+
ctx.Set("apiRoutes", apiRoutes)
|
|
183
|
+
ctx.Set("routeImports", routeImports)
|
|
184
|
+
ctx.Set("componentNames", componentNames)
|
|
185
|
+
ctx.Set("containerNames", containerNames)
|
|
186
|
+
ctx.Set("notFoundPkg", notFoundPkg)
|
|
187
|
+
ctx.Set("tick", "`")
|
|
180
|
-
s,
|
|
188
|
+
s, err := velvet.Render(`// Code generated by gromer. DO NOT EDIT.
|
|
181
189
|
package main
|
|
182
190
|
|
|
183
191
|
import (
|
|
@@ -231,16 +239,7 @@ func main() {
|
|
|
231
239
|
log.Fatal().Stack().Err(err).Msg("failed to listen")
|
|
232
240
|
}
|
|
233
241
|
}
|
|
234
|
-
`)
|
|
242
|
+
`, ctx)
|
|
235
|
-
"moduleName", moduleName,
|
|
236
|
-
"pageRoutes", pageRoutes,
|
|
237
|
-
"apiRoutes", apiRoutes,
|
|
238
|
-
"routeImports", routeImports,
|
|
239
|
-
"componentNames", componentNames,
|
|
240
|
-
"containerNames", containerNames,
|
|
241
|
-
"notFoundPkg", notFoundPkg,
|
|
242
|
-
"tick", "`",
|
|
243
|
-
).Render()
|
|
244
243
|
if err != nil {
|
|
245
244
|
panic(err)
|
|
246
245
|
}
|
go.mod
CHANGED
|
@@ -3,40 +3,56 @@ module github.com/pyros2097/gromer
|
|
|
3
3
|
go 1.18
|
|
4
4
|
|
|
5
5
|
require (
|
|
6
|
-
github.com/aymerick/raymond v2.0.2+incompatible
|
|
7
|
-
github.com/
|
|
6
|
+
github.com/alecthomas/repr v0.1.0
|
|
8
7
|
github.com/go-playground/validator/v10 v10.9.0
|
|
8
|
+
github.com/gobuffalo/velvet v0.0.0-20170320144106-d97471bf5d8f
|
|
9
9
|
github.com/gorilla/mux v1.8.0
|
|
10
10
|
github.com/imdario/mergo v0.3.12
|
|
11
|
-
github.com/pkg/errors v0.9.1
|
|
12
11
|
github.com/rs/zerolog v1.26.1
|
|
13
12
|
github.com/segmentio/go-camelcase v0.0.0-20160726192923-7085f1e3c734
|
|
14
13
|
github.com/stretchr/testify v1.7.0
|
|
15
14
|
gocloud.dev v0.24.0
|
|
16
15
|
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57
|
|
16
|
+
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f
|
|
17
17
|
xojoc.pw/useragent v0.0.0-20200116211053-1ec61d55e8fe
|
|
18
18
|
)
|
|
19
19
|
|
|
20
20
|
require (
|
|
21
21
|
cloud.google.com/go v0.94.0 // indirect
|
|
22
22
|
cloud.google.com/go/firestore v1.5.0 // indirect
|
|
23
|
-
github.com/alecthomas/participle/v2 v2.0.0-alpha9 // indirect
|
|
24
|
-
github.com/
|
|
23
|
+
github.com/aymerick/douceur v0.2.0 // indirect
|
|
24
|
+
github.com/aymerick/raymond v2.0.2+incompatible // indirect
|
|
25
25
|
github.com/blang/semver v3.5.1+incompatible // indirect
|
|
26
26
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
27
27
|
github.com/go-playground/locales v0.14.0 // indirect
|
|
28
28
|
github.com/go-playground/universal-translator v0.18.0 // indirect
|
|
29
|
+
github.com/gobuffalo/envy v1.6.5 // indirect
|
|
29
30
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
|
30
31
|
github.com/golang/protobuf v1.5.2 // indirect
|
|
31
32
|
github.com/google/go-cmp v0.5.6 // indirect
|
|
32
33
|
github.com/google/uuid v1.3.0 // indirect
|
|
33
34
|
github.com/google/wire v0.5.0 // indirect
|
|
34
35
|
github.com/googleapis/gax-go/v2 v2.1.0 // indirect
|
|
36
|
+
github.com/gorilla/css v1.0.0 // indirect
|
|
37
|
+
github.com/joho/godotenv v1.3.0 // indirect
|
|
35
38
|
github.com/leodido/go-urn v1.2.1 // indirect
|
|
39
|
+
github.com/markbates/inflect v1.0.4 // indirect
|
|
40
|
+
github.com/microcosm-cc/bluemonday v1.0.18 // indirect
|
|
41
|
+
github.com/pkg/errors v0.9.1 // indirect
|
|
36
42
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
43
|
+
github.com/russross/blackfriday v1.5.2 // indirect
|
|
44
|
+
github.com/sergi/go-diff v1.2.0 // indirect
|
|
45
|
+
github.com/shurcooL/github_flavored_markdown v0.0.0-20210228213109-c3a9aa474629 // indirect
|
|
46
|
+
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636 // indirect
|
|
47
|
+
github.com/shurcooL/go-goon v1.0.0 // indirect
|
|
48
|
+
github.com/shurcooL/highlight_diff v0.0.0-20181222201841-111da2e7d480 // indirect
|
|
49
|
+
github.com/shurcooL/highlight_go v0.0.0-20191220051317-782971ddf21b // indirect
|
|
50
|
+
github.com/shurcooL/octicon v0.0.0-20191102190552-cbb32d6a785c // indirect
|
|
51
|
+
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
|
52
|
+
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d // indirect
|
|
53
|
+
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e // indirect
|
|
37
54
|
go.opencensus.io v0.23.0 // indirect
|
|
38
55
|
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e // indirect
|
|
39
|
-
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect
|
|
40
56
|
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
|
|
41
57
|
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect
|
|
42
58
|
golang.org/x/text v0.3.7 // indirect
|
go.sum
CHANGED
|
@@ -99,9 +99,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
|
|
99
99
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
|
100
100
|
github.com/GoogleCloudPlatform/cloudsql-proxy v1.24.0/go.mod h1:3tx938GhY4FC+E1KT/jNjDw7Z5qxAEtIiERJ2sXjnII=
|
|
101
101
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
|
102
|
-
github.com/alecthomas/participle/v2 v2.0.0-alpha9 h1:TnflwDbtf5/aG6JMbmdiA+YB3bLg0sc6yRtmAfedfN4=
|
|
103
|
-
github.com/alecthomas/participle/v2 v2.0.0-alpha9/go.mod h1:NumScqsC42o9x+dGj8/YqsIfhrIQjFEOFovxotbBirA=
|
|
104
|
-
github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
|
105
102
|
github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
|
|
106
103
|
github.com/alecthomas/repr v0.1.0/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
|
107
104
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
|
@@ -130,13 +127,13 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.7.0 h1:1at4e5P+lvHNl2nUktdM2/v+rpICg
|
|
|
130
127
|
github.com/aws/aws-sdk-go-v2/service/sts v1.7.0/go.mod h1:0qcSMCyASQPN2sk/1KQLQ2Fh6yq8wm0HSDAimPhzCoM=
|
|
131
128
|
github.com/aws/smithy-go v1.8.0 h1:AEwwwXQZtUwP5Mz506FeXXrKBe0jA8gVM+1gEcSRooc=
|
|
132
129
|
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
|
|
130
|
+
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
|
131
|
+
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
|
133
132
|
github.com/aymerick/raymond v2.0.2+incompatible h1:VEp3GpgdAnv9B2GFyTvqgcKvY+mfKMjPOA3SbKLtnU0=
|
|
134
133
|
github.com/aymerick/raymond v2.0.2+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
|
|
135
134
|
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
|
136
135
|
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
|
|
137
136
|
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
|
138
|
-
github.com/carlmjohnson/versioninfo v0.22.1 h1:NVwTmCUpSoxBxy8+z10CbyBazeRZ4R2n6QgrNi3Wd6M=
|
|
139
|
-
github.com/carlmjohnson/versioninfo v0.22.1/go.mod h1:QT9mph3wcVfISUKd0i9sZfVrPviHuSF+cUtLjm2WSf8=
|
|
140
137
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
|
141
138
|
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
|
142
139
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
|
@@ -188,6 +185,10 @@ github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GO
|
|
|
188
185
|
github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A=
|
|
189
186
|
github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
|
190
187
|
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
|
188
|
+
github.com/gobuffalo/envy v1.6.5 h1:X3is06x7v0nW2xiy2yFbbIjwHz57CD6z6MkvqULTCm8=
|
|
189
|
+
github.com/gobuffalo/envy v1.6.5/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ=
|
|
190
|
+
github.com/gobuffalo/velvet v0.0.0-20170320144106-d97471bf5d8f h1:ddIdPdlkAgKMB0mbkft2LT3oxN1h3MN1fopCFrOgkhY=
|
|
191
|
+
github.com/gobuffalo/velvet v0.0.0-20170320144106-d97471bf5d8f/go.mod h1:m9x1vDSQYrGiEhEiu0c4XuE0SZzw31Ms8ULjGdhaA54=
|
|
191
192
|
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
|
192
193
|
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
|
193
194
|
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
|
@@ -283,6 +284,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
|
|
|
283
284
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
|
284
285
|
github.com/googleapis/gax-go/v2 v2.1.0 h1:6DWmvNpomjL1+3liNSZbVns3zsYzzCjm6pRBO1tLeso=
|
|
285
286
|
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
|
287
|
+
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
|
288
|
+
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
|
286
289
|
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
|
287
290
|
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
|
288
291
|
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
|
@@ -297,6 +300,7 @@ github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht
|
|
|
297
300
|
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
|
298
301
|
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
|
299
302
|
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
|
303
|
+
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
|
300
304
|
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
|
301
305
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
302
306
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
|
@@ -316,9 +320,13 @@ github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgx
|
|
|
316
320
|
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
|
317
321
|
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
|
318
322
|
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
|
323
|
+
github.com/markbates/inflect v1.0.4 h1:5fh1gzTFhfae06u3hzHYO9xe3l3v3nW5Pwt3naLTP5g=
|
|
324
|
+
github.com/markbates/inflect v1.0.4/go.mod h1:1fR9+pO2KHEO9ZRtto13gDwwZaAKstQzferVeWqbgNs=
|
|
319
325
|
github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI=
|
|
320
326
|
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
|
|
321
327
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
|
328
|
+
github.com/microcosm-cc/bluemonday v1.0.18 h1:6HcxvXDAi3ARt3slx6nTesbvorIc3QeTzBNRvWktHBo=
|
|
329
|
+
github.com/microcosm-cc/bluemonday v1.0.18/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM=
|
|
322
330
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
|
323
331
|
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
|
324
332
|
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
|
@@ -339,11 +347,34 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po
|
|
|
339
347
|
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
|
340
348
|
github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc=
|
|
341
349
|
github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc=
|
|
350
|
+
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
|
351
|
+
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
|
342
352
|
github.com/segmentio/go-camelcase v0.0.0-20160726192923-7085f1e3c734 h1:Cpx2WLIv6fuPvaJAHNhYOgYzk/8RcJXu/8+mOrxf2KM=
|
|
343
353
|
github.com/segmentio/go-camelcase v0.0.0-20160726192923-7085f1e3c734/go.mod h1:hqVOMAwu+ekffC3Tvq5N1ljnXRrFKcaSjbCmQ8JgYaI=
|
|
354
|
+
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
|
355
|
+
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
|
356
|
+
github.com/shurcooL/github_flavored_markdown v0.0.0-20210228213109-c3a9aa474629 h1:86e54L0i3pH3dAIA8OxBbfLrVyhoGpnNk1iJCigAWYs=
|
|
357
|
+
github.com/shurcooL/github_flavored_markdown v0.0.0-20210228213109-c3a9aa474629/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
|
358
|
+
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636 h1:aSISeOcal5irEhJd1M+IrApc0PdcN7e7Aj4yuEnOrfQ=
|
|
359
|
+
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
|
360
|
+
github.com/shurcooL/go-goon v1.0.0 h1:BCQPvxGkHHJ4WpBO4m/9FXbITVIsvAm/T66cCcCGI7E=
|
|
361
|
+
github.com/shurcooL/go-goon v1.0.0/go.mod h1:2wTHMsGo7qnpmqA8ADYZtP4I1DD94JpXGQ3Dxq2YQ5w=
|
|
362
|
+
github.com/shurcooL/highlight_diff v0.0.0-20181222201841-111da2e7d480 h1:KaKXZldeYH73dpQL+Nr38j1r5BgpAYQjYvENOUpIZDQ=
|
|
363
|
+
github.com/shurcooL/highlight_diff v0.0.0-20181222201841-111da2e7d480/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
|
|
364
|
+
github.com/shurcooL/highlight_go v0.0.0-20191220051317-782971ddf21b h1:rBIwpb5ggtqf0uZZY5BPs1sL7njUMM7I8qD2jiou70E=
|
|
365
|
+
github.com/shurcooL/highlight_go v0.0.0-20191220051317-782971ddf21b/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
|
|
366
|
+
github.com/shurcooL/octicon v0.0.0-20191102190552-cbb32d6a785c h1:p3w+lTqXulfa3aDeycxmcLJDNxyUB89gf2/XqqK3eO0=
|
|
367
|
+
github.com/shurcooL/octicon v0.0.0-20191102190552-cbb32d6a785c/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
|
368
|
+
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
|
369
|
+
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
|
370
|
+
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d h1:yKm7XZV6j9Ev6lojP2XaIshpT4ymkqhMeSghO5Ps00E=
|
|
371
|
+
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
|
372
|
+
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e h1:qpG93cPwA5f7s/ZPBJnGOYQNK/vKsaDaseuKT5Asee8=
|
|
373
|
+
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
|
344
374
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
|
345
375
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
346
376
|
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
|
377
|
+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
347
378
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
348
379
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
349
380
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
|
@@ -775,11 +806,13 @@ google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+Rur
|
|
|
775
806
|
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
|
776
807
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
777
808
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
809
|
+
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
778
810
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
|
779
811
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
|
780
812
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
|
781
813
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
782
814
|
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
815
|
+
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
783
816
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
784
817
|
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
|
785
818
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gsx/gsx.go
CHANGED
|
@@ -2,6 +2,7 @@ package gsx
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"bytes"
|
|
5
|
+
"context"
|
|
5
6
|
"fmt"
|
|
6
7
|
"io"
|
|
7
8
|
"reflect"
|
|
@@ -9,6 +10,7 @@ import (
|
|
|
9
10
|
"runtime"
|
|
10
11
|
"strings"
|
|
11
12
|
|
|
13
|
+
"github.com/alecthomas/repr"
|
|
12
14
|
"golang.org/x/net/html"
|
|
13
15
|
"golang.org/x/net/html/atom"
|
|
14
16
|
)
|
|
@@ -31,29 +33,92 @@ type (
|
|
|
31
33
|
Func interface{}
|
|
32
34
|
Args []string
|
|
33
35
|
}
|
|
36
|
+
link struct {
|
|
37
|
+
Rel string
|
|
38
|
+
Href string
|
|
39
|
+
Type string
|
|
40
|
+
As string
|
|
41
|
+
}
|
|
42
|
+
Context struct {
|
|
43
|
+
context.Context
|
|
34
|
-
|
|
44
|
+
data map[string]interface{}
|
|
45
|
+
metas map[string]string
|
|
46
|
+
links map[string]link
|
|
47
|
+
scripts map[string]bool
|
|
48
|
+
}
|
|
35
49
|
Node struct {
|
|
36
|
-
|
|
50
|
+
html.Node
|
|
37
51
|
}
|
|
38
52
|
)
|
|
39
53
|
|
|
54
|
+
func NewContext(c context.Context) Context {
|
|
55
|
+
return Context{Context: c, data: map[string]interface{}{}, metas: map[string]string{}, links: map[string]link{}, scripts: map[string]bool{}}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
func (h Context) Get(k string) interface{} {
|
|
59
|
+
return h.data[k]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
func (h Context) Set(k string, v interface{}) {
|
|
63
|
+
h.data[k] = v
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
func (h Context) Meta(k, v string) {
|
|
67
|
+
h.metas[k] = v
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
func (h Context) Link(rel, href, t, as string) {
|
|
71
|
+
h.links[href] = link{rel, href, t, as}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
func (h Context) Script(src string, sdefer bool) {
|
|
75
|
+
h.scripts[src] = sdefer
|
|
76
|
+
}
|
|
77
|
+
|
|
40
|
-
func (h
|
|
78
|
+
func (h Context) Render(tpl string) *Node {
|
|
41
79
|
newTpl := stripWhitespace(tpl)
|
|
42
80
|
doc, err := html.ParseFragment(bytes.NewBuffer([]byte(newTpl)), contextNode)
|
|
43
81
|
if err != nil {
|
|
44
82
|
panic(err)
|
|
45
83
|
}
|
|
46
84
|
populate(h, doc[0])
|
|
47
|
-
return Node{doc[0]}
|
|
85
|
+
return &Node{*doc[0]}
|
|
48
86
|
}
|
|
49
87
|
|
|
50
|
-
func (n Node) Write(w io.Writer)
|
|
88
|
+
func (n *Node) Write(ctx Context, w io.Writer) {
|
|
89
|
+
w.Write([]byte(`<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8">`))
|
|
90
|
+
w.Write([]byte(`<meta http-equiv="Content-Type" content="text/html;charset=utf-8"><meta content="utf-8" http-equiv="encoding">`))
|
|
91
|
+
w.Write([]byte(`<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, viewport-fit=cover">`))
|
|
92
|
+
for k, v := range ctx.metas {
|
|
93
|
+
w.Write([]byte(fmt.Sprintf(`<meta name="%s" content="%s">`, k, v)))
|
|
94
|
+
}
|
|
95
|
+
for k, v := range ctx.metas {
|
|
96
|
+
if k == "title" {
|
|
97
|
+
w.Write([]byte(fmt.Sprintf(`<title>%s</title>`, v)))
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
for _, v := range ctx.links {
|
|
101
|
+
if v.Type != "" || v.As != "" {
|
|
102
|
+
w.Write([]byte(fmt.Sprintf(`<link rel="%s" href="%s" type="%s" as="%s">`, v.Rel, v.Href, v.Type, v.As)))
|
|
103
|
+
} else {
|
|
104
|
+
w.Write([]byte(fmt.Sprintf(`<link rel="%s" href="%s">`, v.Rel, v.Href)))
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
for src, sdefer := range ctx.scripts {
|
|
108
|
+
if sdefer {
|
|
109
|
+
w.Write([]byte(fmt.Sprintf(`<script src="%s" defer="true"></script>`, src)))
|
|
110
|
+
} else {
|
|
111
|
+
w.Write([]byte(fmt.Sprintf(`<script src="%s"></script>`, src)))
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
w.Write([]byte(`</head><body>`))
|
|
51
|
-
|
|
115
|
+
html.Render(w, &n.Node)
|
|
116
|
+
w.Write([]byte(`</body></html>`))
|
|
52
117
|
}
|
|
53
118
|
|
|
54
|
-
func (n Node) String() string {
|
|
119
|
+
func (n *Node) String() string {
|
|
55
120
|
b := bytes.NewBuffer(nil)
|
|
56
|
-
html.Render(b, n.Node)
|
|
121
|
+
html.Render(b, &n.Node)
|
|
57
122
|
return b.String()
|
|
58
123
|
}
|
|
59
124
|
|
|
@@ -121,18 +186,18 @@ func convert(ref string, i interface{}) interface{} {
|
|
|
121
186
|
return nil
|
|
122
187
|
}
|
|
123
188
|
|
|
124
|
-
func getRefValue(ctx
|
|
189
|
+
func getRefValue(ctx Context, ref string) interface{} {
|
|
125
190
|
if f, ok := funcMap[ref]; ok {
|
|
126
191
|
return f.(func() string)()
|
|
127
192
|
} else {
|
|
128
193
|
parts := strings.Split(strings.ReplaceAll(ref, "!", ""), ".")
|
|
129
194
|
if len(parts) == 2 {
|
|
130
|
-
if v, ok := ctx[parts[0]]; ok {
|
|
195
|
+
if v, ok := ctx.data[parts[0]]; ok {
|
|
131
196
|
i := reflect.ValueOf(v).Elem().FieldByName(parts[1]).Interface()
|
|
132
197
|
return convert(ref, i)
|
|
133
198
|
}
|
|
134
199
|
}
|
|
135
|
-
return convert(ref, ctx[ref])
|
|
200
|
+
return convert(ref, ctx.data[ref])
|
|
136
201
|
}
|
|
137
202
|
}
|
|
138
203
|
|
|
@@ -140,7 +205,7 @@ func removeBrackets(v string) string {
|
|
|
140
205
|
return strings.ReplaceAll(strings.ReplaceAll(v, "{", ""), "}", "")
|
|
141
206
|
}
|
|
142
207
|
|
|
143
|
-
func substituteString(ctx
|
|
208
|
+
func substituteString(ctx Context, v string) string {
|
|
144
209
|
found := refRegex.FindString(v)
|
|
145
210
|
if found != "" {
|
|
146
211
|
varName := removeBrackets(found)
|
|
@@ -201,7 +266,7 @@ func cloneNode(n *html.Node) *html.Node {
|
|
|
201
266
|
return newNode
|
|
202
267
|
}
|
|
203
268
|
|
|
204
|
-
func populate(ctx
|
|
269
|
+
func populate(ctx Context, n *html.Node) {
|
|
205
270
|
if n.Type == html.TextNode {
|
|
206
271
|
if n.Data != "" && strings.Contains(n.Data, "{") && n.Data != "{children}" {
|
|
207
272
|
n.Data = substituteString(ctx, n.Data)
|
|
@@ -213,15 +278,28 @@ func populate(ctx Html, n *html.Node) {
|
|
|
213
278
|
arr := strings.Split(xfor, " in ")
|
|
214
279
|
ctxItemKey := arr[0]
|
|
215
280
|
ctxKey := arr[1]
|
|
216
|
-
data := ctx[ctxKey]
|
|
281
|
+
data := ctx.data[ctxKey]
|
|
217
282
|
switch reflect.TypeOf(data).Kind() {
|
|
218
283
|
case reflect.Slice:
|
|
219
284
|
v := reflect.ValueOf(data)
|
|
285
|
+
if v.Len() == 0 {
|
|
286
|
+
if n.FirstChild != nil {
|
|
287
|
+
n.RemoveChild(n.FirstChild)
|
|
288
|
+
}
|
|
289
|
+
continue
|
|
290
|
+
}
|
|
291
|
+
repr.Println("AAAAAAA", n.Data)
|
|
292
|
+
if n.FirstChild == nil {
|
|
293
|
+
continue
|
|
294
|
+
}
|
|
220
295
|
firstChild := cloneNode(n.FirstChild)
|
|
221
296
|
n.RemoveChild(n.FirstChild)
|
|
222
297
|
for i := 0; i < v.Len(); i++ {
|
|
298
|
+
compCtx := Context{
|
|
299
|
+
Context: ctx.Context,
|
|
223
|
-
|
|
300
|
+
data: map[string]interface{}{
|
|
224
|
-
|
|
301
|
+
ctxItemKey: v.Index(i).Interface(),
|
|
302
|
+
},
|
|
225
303
|
}
|
|
226
304
|
itemChild := cloneNode(firstChild)
|
|
227
305
|
itemChild.Parent = nil
|
|
@@ -261,21 +339,20 @@ func populate(ctx Html, n *html.Node) {
|
|
|
261
339
|
}
|
|
262
340
|
}
|
|
263
341
|
}
|
|
342
|
+
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
|
343
|
+
populate(ctx, c)
|
|
344
|
+
}
|
|
264
345
|
if comp, ok := compMap[n.Data]; ok {
|
|
265
346
|
newNode := populateComponent(ctx, comp, n, true)
|
|
266
347
|
n.AppendChild(newNode)
|
|
267
348
|
}
|
|
268
|
-
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
|
269
|
-
populate(ctx, c)
|
|
270
|
-
}
|
|
271
349
|
}
|
|
272
350
|
}
|
|
273
351
|
|
|
274
|
-
func renderComponent(ctx
|
|
352
|
+
func renderComponent(ctx Context, comp ComponentFunc, n *html.Node) *Node {
|
|
275
|
-
h := Html(ctx)
|
|
276
|
-
args := []reflect.Value{reflect.ValueOf(
|
|
353
|
+
args := []reflect.Value{reflect.ValueOf(ctx)}
|
|
277
354
|
for _, arg := range comp.Args {
|
|
278
|
-
if v, ok := ctx[arg]; ok {
|
|
355
|
+
if v, ok := ctx.data[arg]; ok {
|
|
279
356
|
args = append(args, reflect.ValueOf(v))
|
|
280
357
|
} else {
|
|
281
358
|
v := getAttribute(arg, n.Attr)
|
|
@@ -283,11 +360,11 @@ func renderComponent(ctx Html, comp ComponentFunc, n *html.Node) Node {
|
|
|
283
360
|
}
|
|
284
361
|
}
|
|
285
362
|
result := reflect.ValueOf(comp.Func).Call(args)
|
|
286
|
-
compNode := result[0].Interface().(Node)
|
|
363
|
+
compNode := result[0].Interface().(*Node)
|
|
287
364
|
return compNode
|
|
288
365
|
}
|
|
289
366
|
|
|
290
|
-
func populateComponent(ctx
|
|
367
|
+
func populateComponent(ctx Context, comp ComponentFunc, n *html.Node, remove bool) *html.Node {
|
|
291
368
|
compNode := renderComponent(ctx, comp, n)
|
|
292
369
|
if n.FirstChild != nil {
|
|
293
370
|
newChild := cloneNode(n.FirstChild)
|
|
@@ -298,7 +375,9 @@ func populateComponent(ctx Html, comp ComponentFunc, n *html.Node, remove bool)
|
|
|
298
375
|
if !remove {
|
|
299
376
|
populate(ctx, newChild)
|
|
300
377
|
}
|
|
378
|
+
if compNode.FirstChild != nil {
|
|
301
|
-
|
|
379
|
+
populateChildren(compNode.FirstChild, newChild)
|
|
380
|
+
}
|
|
302
381
|
}
|
|
303
|
-
return compNode.Node
|
|
382
|
+
return &compNode.Node
|
|
304
383
|
}
|
gsx/gsx_test.go
CHANGED
|
@@ -12,7 +12,7 @@ type TodoData struct {
|
|
|
12
12
|
Completed bool
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
func Todo(h
|
|
15
|
+
func Todo(h Context, todo *TodoData) *Node {
|
|
16
16
|
return h.Render(`
|
|
17
17
|
<li id="todo-{todo.ID}" class="{ completed: todo.Completed }">
|
|
18
18
|
<div class="view">
|
|
@@ -28,6 +28,14 @@ func Todo(h Html, todo *TodoData) Node {
|
|
|
28
28
|
`)
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
func TodoList(ctx Context, todos []*TodoData) (*Node, error) {
|
|
32
|
+
return ctx.Render(`
|
|
33
|
+
<ul id="todo-list" class="relative" x-for="todo in todos">
|
|
34
|
+
<Todo />
|
|
35
|
+
</ul>
|
|
36
|
+
`), nil
|
|
37
|
+
}
|
|
38
|
+
|
|
31
39
|
func WebsiteName() string {
|
|
32
40
|
return "My Website"
|
|
33
41
|
}
|
|
@@ -36,9 +44,11 @@ func TestComponent(t *testing.T) {
|
|
|
36
44
|
r := require.New(t)
|
|
37
45
|
RegisterComponent(Todo, "todo")
|
|
38
46
|
RegisterFunc(WebsiteName)
|
|
47
|
+
h := Context{
|
|
39
|
-
|
|
48
|
+
data: map[string]interface{}{
|
|
40
|
-
|
|
49
|
+
"todo": &TodoData{ID: "4", Text: "My fourth todo", Completed: false},
|
|
41
|
-
|
|
50
|
+
},
|
|
51
|
+
}
|
|
42
52
|
actual := h.Render(`
|
|
43
53
|
<div>
|
|
44
54
|
<Todo>
|
|
@@ -67,13 +77,15 @@ func TestFor(t *testing.T) {
|
|
|
67
77
|
r := require.New(t)
|
|
68
78
|
RegisterComponent(Todo, "todo")
|
|
69
79
|
RegisterFunc(WebsiteName)
|
|
80
|
+
h := Context{
|
|
70
|
-
|
|
81
|
+
data: map[string]interface{}{
|
|
71
|
-
|
|
82
|
+
"todos": []*TodoData{
|
|
72
|
-
|
|
83
|
+
{ID: "1", Text: "My first todo", Completed: true},
|
|
73
|
-
|
|
84
|
+
{ID: "2", Text: "My second todo", Completed: false},
|
|
74
|
-
|
|
85
|
+
{ID: "3", Text: "My third todo", Completed: false},
|
|
86
|
+
},
|
|
75
87
|
},
|
|
76
|
-
}
|
|
88
|
+
}
|
|
77
89
|
actual := h.Render(`
|
|
78
90
|
<div>
|
|
79
91
|
<ul x-for="todo in todos" class="relative">
|
|
@@ -121,3 +133,65 @@ func TestFor(t *testing.T) {
|
|
|
121
133
|
`)
|
|
122
134
|
r.Equal(expected, actual)
|
|
123
135
|
}
|
|
136
|
+
|
|
137
|
+
func TestForComponent(t *testing.T) {
|
|
138
|
+
r := require.New(t)
|
|
139
|
+
RegisterComponent(Todo, "todo")
|
|
140
|
+
RegisterComponent(TodoList, "todos")
|
|
141
|
+
RegisterFunc(WebsiteName)
|
|
142
|
+
h := Context{
|
|
143
|
+
data: map[string]interface{}{
|
|
144
|
+
"todos": []*TodoData{
|
|
145
|
+
{ID: "1", Text: "My first todo", Completed: true},
|
|
146
|
+
{ID: "2", Text: "My second todo", Completed: false},
|
|
147
|
+
{ID: "3", Text: "My third todo", Completed: false},
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
actual := h.Render(`
|
|
152
|
+
<div>
|
|
153
|
+
<TodoList />
|
|
154
|
+
</div>
|
|
155
|
+
`).String()
|
|
156
|
+
expected := stripWhitespace(`
|
|
157
|
+
<div>
|
|
158
|
+
<ul id="todo-list" class="relative" x-for="todo in todos">
|
|
159
|
+
<li id="todo-1" class="completed">
|
|
160
|
+
<div class="view"><span>My first todo</span><span>My first todo</span></div>
|
|
161
|
+
<div class="todo-panel"><span>My first todo</span><span>true</span></div>
|
|
162
|
+
<div class="count"><span>true</span><span>true</span></div>
|
|
163
|
+
</li>
|
|
164
|
+
<li id="todo-2" class="">
|
|
165
|
+
<div class="view"><span>My second todo</span><span>My second todo</span></div>
|
|
166
|
+
<div class="todo-panel"><span>My second todo</span><span>false</span></div>
|
|
167
|
+
<div class="count"><span>false</span><span>false</span></div>
|
|
168
|
+
</li>
|
|
169
|
+
<li id="todo-3" class="">
|
|
170
|
+
<div class="view"><span>My third todo</span><span>My third todo</span></div>
|
|
171
|
+
<div class="todo-panel"><span>My third todo</span><span>false</span></div>
|
|
172
|
+
<div class="count"><span>false</span><span>false</span></div>
|
|
173
|
+
</li>
|
|
174
|
+
</ul>
|
|
175
|
+
</div>
|
|
176
|
+
`)
|
|
177
|
+
r.Equal(expected, actual)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// func TestPage(t *testing.T) {
|
|
181
|
+
// r := require.New(t)
|
|
182
|
+
// RegisterFunc(WebsiteName)
|
|
183
|
+
// h := Context{
|
|
184
|
+
// data: map[string]interface{}{},
|
|
185
|
+
// }
|
|
186
|
+
// actual := h.Render(`
|
|
187
|
+
// <Page title="test">
|
|
188
|
+
// <span>Hello</span>
|
|
189
|
+
// </Page}>
|
|
190
|
+
// `).String()
|
|
191
|
+
// expected := stripWhitespace(`
|
|
192
|
+
// <page title="test">
|
|
193
|
+
// <meta charset="UTF-8"/>
|
|
194
|
+
// </page>
|
|
195
|
+
// `)
|
|
196
|
+
// r.Equal(expected, actual)
|
|
197
|
+
// }
|
http.go
CHANGED
|
@@ -21,6 +21,7 @@ import (
|
|
|
21
21
|
|
|
22
22
|
"github.com/alecthomas/repr"
|
|
23
23
|
"github.com/go-playground/validator/v10"
|
|
24
|
+
"github.com/google/uuid"
|
|
24
25
|
"github.com/gorilla/mux"
|
|
25
26
|
"github.com/pyros2097/gromer/assets"
|
|
26
27
|
"github.com/pyros2097/gromer/gsx"
|
|
@@ -224,10 +225,15 @@ func addRouteDef(method, route string, h interface{}) {
|
|
|
224
225
|
})
|
|
225
226
|
}
|
|
226
227
|
|
|
227
|
-
func PerformRequest(route string, h interface{}, ctx
|
|
228
|
+
func PerformRequest(route string, h interface{}, ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
|
228
229
|
params := GetRouteParams(route)
|
|
229
|
-
|
|
230
|
+
renderContext := gsx.NewContext(ctx)
|
|
231
|
+
renderContext.Set("requestId", uuid.NewString())
|
|
232
|
+
renderContext.Link("rel", GetAssetUrl("images/icon.png"), "", "")
|
|
233
|
+
renderContext.Link("stylesheet", GetStylesUrl(), "", "")
|
|
234
|
+
renderContext.Script(GetAlpineJsUrl(), true)
|
|
235
|
+
renderContext.Script(GetHtmxJsUrl(), false)
|
|
230
|
-
args := []reflect.Value{reflect.ValueOf(
|
|
236
|
+
args := []reflect.Value{reflect.ValueOf(renderContext)}
|
|
231
237
|
funcType := reflect.TypeOf(h)
|
|
232
238
|
icount := funcType.NumIn()
|
|
233
239
|
vars := mux.Vars(r)
|
|
@@ -312,11 +318,11 @@ func PerformRequest(route string, h interface{}, ctx interface{}, w http.Respons
|
|
|
312
318
|
RespondError(w, responseStatus, responseError.(error))
|
|
313
319
|
return
|
|
314
320
|
}
|
|
315
|
-
if v, ok := response.(
|
|
321
|
+
if v, ok := response.(*gsx.Node); ok {
|
|
316
322
|
w.Header().Set("Content-Type", "text/html")
|
|
317
323
|
// This has to be at end always
|
|
318
324
|
w.WriteHeader(responseStatus)
|
|
319
|
-
|
|
325
|
+
v.Write(renderContext, w)
|
|
320
326
|
return
|
|
321
327
|
}
|
|
322
328
|
// if v, ok := response.(handlebars.CssContent); ok {
|
|
@@ -445,7 +451,7 @@ func CacheMiddleware(next http.Handler) http.Handler {
|
|
|
445
451
|
func StatusHandler(h interface{}) http.Handler {
|
|
446
452
|
return LogMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
447
453
|
ctx := context.WithValue(context.WithValue(r.Context(), "url", r.URL), "header", r.Header)
|
|
448
|
-
values := reflect.ValueOf(h).Call([]reflect.Value{reflect.ValueOf(ctx)})
|
|
454
|
+
values := reflect.ValueOf(h).Call([]reflect.Value{reflect.ValueOf(map[string]interface{}{}), reflect.ValueOf(ctx)})
|
|
449
455
|
response := values[0].Interface()
|
|
450
456
|
responseStatus := values[1].Interface().(int)
|
|
451
457
|
responseError := values[2].Interface()
|
|
@@ -457,7 +463,7 @@ func StatusHandler(h interface{}) http.Handler {
|
|
|
457
463
|
|
|
458
464
|
// This has to be at end always after headers are set
|
|
459
465
|
w.WriteHeader(responseStatus)
|
|
460
|
-
|
|
466
|
+
response.(*gsx.Node).Write(gsx.NewContext(r.Context()), w)
|
|
461
467
|
})).(http.Handler)
|
|
462
468
|
}
|
|
463
469
|
|