~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.
c9c1d70a
—
Peter John 3 years ago
improve styling
- _example/assets/favicon.ico +0 -0
- _example/assets/images/icon.png +0 -0
- _example/components/Checkbox.go +2 -30
- _example/components/todo.go +1 -1
- _example/containers/Error.go +1 -11
- _example/containers/TodoCount.go +1 -12
- _example/containers/TodoList.go +1 -93
- _example/main.go +3 -3
- _example/routes/404/get.go +8 -2
- _example/routes/about/get.go +9 -3
- _example/routes/get.go +79 -113
- _example/routes/post.go +1 -1
- _example/routes/styles.go +0 -153
- go.mod +3 -0
- go.sum +6 -0
- gsx/gsx.go +75 -58
- gsx/twx.go +552 -0
- http.go +35 -35
- utils.go +0 -4
_example/assets/favicon.ico
CHANGED
|
Binary file
|
_example/assets/images/icon.png
DELETED
|
Binary file
|
_example/components/Checkbox.go
CHANGED
|
@@ -4,37 +4,9 @@ import (
|
|
|
4
4
|
. "github.com/pyros2097/gromer/gsx"
|
|
5
5
|
)
|
|
6
6
|
|
|
7
|
-
var _ = Css(`
|
|
8
|
-
.checkbox {
|
|
9
|
-
text-align: center;
|
|
10
|
-
width: 40px;
|
|
11
|
-
/* auto, since non-WebKit browsers doesn't support input styling */
|
|
12
|
-
height: auto;
|
|
13
|
-
position: absolute;
|
|
14
|
-
top: 0;
|
|
15
|
-
bottom: 0;
|
|
16
|
-
margin: auto 0;
|
|
17
|
-
|
|
7
|
+
// var CheckboxStyles = M{}
|
|
18
|
-
-webkit-appearance: none;
|
|
19
|
-
appearance: none;
|
|
20
|
-
}
|
|
21
8
|
|
|
22
|
-
|
|
23
|
-
opacity: 0;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
.checkbox + label {
|
|
27
|
-
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');
|
|
28
|
-
background-repeat: no-repeat;
|
|
29
|
-
background-position: center left;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
.checkbox:checked + label {
|
|
33
|
-
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');
|
|
34
|
-
}
|
|
35
|
-
`)
|
|
36
|
-
|
|
37
|
-
func Checkbox(c Context, value bool) *Node {
|
|
9
|
+
func Checkbox(c *Context, value bool) *Node {
|
|
38
10
|
return c.Render(`
|
|
39
11
|
<input class="checkbox" type="checkbox" checked="{value}" />
|
|
40
12
|
`)
|
_example/components/todo.go
CHANGED
|
@@ -5,7 +5,7 @@ import (
|
|
|
5
5
|
. "github.com/pyros2097/gromer/gsx"
|
|
6
6
|
)
|
|
7
7
|
|
|
8
|
-
func Todo(c Context, todo *todos.Todo) *Node {
|
|
8
|
+
func Todo(c *Context, todo *todos.Todo) *Node {
|
|
9
9
|
return c.Render(`
|
|
10
10
|
<li id="todo-{todo.ID}" class="{ completed: todo.Completed }">
|
|
11
11
|
<div class="view">
|
_example/containers/Error.go
CHANGED
|
@@ -4,7 +4,7 @@ import (
|
|
|
4
4
|
. "github.com/pyros2097/gromer/gsx"
|
|
5
5
|
)
|
|
6
6
|
|
|
7
|
-
func Error(c Context, err error) *Node {
|
|
7
|
+
func Error(c *Context, err error) *Node {
|
|
8
8
|
c.Set("err", err.Error())
|
|
9
9
|
return c.Render(`
|
|
10
10
|
<span class="error">
|
|
@@ -12,13 +12,3 @@ func Error(c Context, err error) *Node {
|
|
|
12
12
|
</span>
|
|
13
13
|
`)
|
|
14
14
|
}
|
|
15
|
-
|
|
16
|
-
var _ = Css(`
|
|
17
|
-
.error {
|
|
18
|
-
color: red;
|
|
19
|
-
font-size: 18px;
|
|
20
|
-
font-weight: 500;
|
|
21
|
-
padding: 24px;
|
|
22
|
-
border: red 1px solid;
|
|
23
|
-
}
|
|
24
|
-
`)
|
_example/containers/TodoCount.go
CHANGED
|
@@ -5,7 +5,7 @@ import (
|
|
|
5
5
|
. "github.com/pyros2097/gromer/gsx"
|
|
6
6
|
)
|
|
7
7
|
|
|
8
|
-
func TodoCount(c Context, filter string) *Node {
|
|
8
|
+
func TodoCount(c *Context, filter string) *Node {
|
|
9
9
|
todos, err := todos.GetAllTodo(c, todos.GetAllTodoParams{
|
|
10
10
|
Filter: filter,
|
|
11
11
|
Limit: 1000,
|
|
@@ -20,14 +20,3 @@ func TodoCount(c Context, filter string) *Node {
|
|
|
20
20
|
</span>
|
|
21
21
|
`)
|
|
22
22
|
}
|
|
23
|
-
|
|
24
|
-
var _ = Css(`
|
|
25
|
-
.todo-count {
|
|
26
|
-
float: left;
|
|
27
|
-
text-align: left;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
.todo-count strong {
|
|
31
|
-
font-weight: 300;
|
|
32
|
-
}
|
|
33
|
-
`)
|
_example/containers/TodoList.go
CHANGED
|
@@ -6,7 +6,7 @@ import (
|
|
|
6
6
|
. "github.com/pyros2097/gromer/gsx"
|
|
7
7
|
)
|
|
8
8
|
|
|
9
|
-
func TodoList(c Context, page int, filter string) *Node {
|
|
9
|
+
func TodoList(c *Context, page int, filter string) *Node {
|
|
10
10
|
index := Default(page, 1)
|
|
11
11
|
todos, err := todos.GetAllTodo(c, todos.GetAllTodoParams{
|
|
12
12
|
Filter: filter,
|
|
@@ -22,95 +22,3 @@ func TodoList(c Context, page int, filter string) *Node {
|
|
|
22
22
|
</ul>
|
|
23
23
|
`)
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
var _ = Css(`
|
|
27
|
-
.todo-list {
|
|
28
|
-
margin: 0;
|
|
29
|
-
padding: 0;
|
|
30
|
-
list-style: none;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.todo-list li {
|
|
34
|
-
position: relative;
|
|
35
|
-
font-size: 24px;
|
|
36
|
-
border-bottom: 1px solid #ededed;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
.todo-list li:last-child {
|
|
40
|
-
border-bottom: none;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.todo-list li.editing {
|
|
44
|
-
border-bottom: none;
|
|
45
|
-
padding: 0;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
.todo-list li.editing .edit {
|
|
49
|
-
display: block;
|
|
50
|
-
width: 506px;
|
|
51
|
-
padding: 12px 16px;
|
|
52
|
-
margin: 0 0 0 43px;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.todo-list li.editing .view {
|
|
56
|
-
display: none;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
.todo-list li label {
|
|
60
|
-
word-break: break-all;
|
|
61
|
-
padding: 15px 15px 15px 60px;
|
|
62
|
-
display: block;
|
|
63
|
-
line-height: 1.2;
|
|
64
|
-
transition: color 0.4s;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
.todo-list li.completed label {
|
|
68
|
-
color: #d9d9d9;
|
|
69
|
-
text-decoration: line-through;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
.todo-list li .destroy {
|
|
73
|
-
display: none;
|
|
74
|
-
position: absolute;
|
|
75
|
-
top: 0;
|
|
76
|
-
right: 10px;
|
|
77
|
-
bottom: 0;
|
|
78
|
-
width: 40px;
|
|
79
|
-
height: 40px;
|
|
80
|
-
margin: auto 0;
|
|
81
|
-
font-size: 30px;
|
|
82
|
-
color: #cc9a9a;
|
|
83
|
-
margin-bottom: 11px;
|
|
84
|
-
transition: color 0.2s ease-out;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
.todo-list li .destroy:hover {
|
|
88
|
-
color: #af5b5e;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
.todo-list li .destroy:after {
|
|
92
|
-
content: '×';
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
.todo-list li:hover .destroy {
|
|
96
|
-
display: block;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.todo-list li .edit {
|
|
100
|
-
display: none;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
.todo-list li.editing:last-child {
|
|
104
|
-
margin-bottom: -1px;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
@media screen and (-webkit-min-device-pixel-ratio: 0) {
|
|
108
|
-
.todo-list li .toggle {
|
|
109
|
-
background: none;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
.todo-list li .toggle {
|
|
113
|
-
height: 40px;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
`)
|
_example/main.go
CHANGED
|
@@ -38,9 +38,9 @@ func main() {
|
|
|
38
38
|
gromer.StylesRoute(staticRouter, "/styles.css")
|
|
39
39
|
|
|
40
40
|
pageRouter := baseRouter.NewRoute().Subrouter()
|
|
41
|
-
gromer.Handle(pageRouter, "GET", "/", routes.GET)
|
|
41
|
+
gromer.Handle(pageRouter, "GET", "/", routes.GET, routes.Meta, routes.Styles)
|
|
42
|
-
gromer.Handle(pageRouter, "POST", "/", routes.POST)
|
|
42
|
+
gromer.Handle(pageRouter, "POST", "/", routes.POST, routes.Meta, routes.Styles)
|
|
43
|
-
gromer.Handle(pageRouter, "GET", "/about", about.GET)
|
|
43
|
+
gromer.Handle(pageRouter, "GET", "/about", about.GET, about.Meta, about.Styles)
|
|
44
44
|
|
|
45
45
|
log.Info().Msg("http server listening on http://localhost:3000")
|
|
46
46
|
srv := server.New(baseRouter, nil)
|
_example/routes/404/get.go
CHANGED
|
@@ -4,8 +4,14 @@ import (
|
|
|
4
4
|
. "github.com/pyros2097/gromer/gsx"
|
|
5
5
|
)
|
|
6
6
|
|
|
7
|
+
var (
|
|
8
|
+
Meta = M{
|
|
9
|
+
"title": "Page Not Found",
|
|
10
|
+
}
|
|
11
|
+
Styles = M{}
|
|
12
|
+
)
|
|
13
|
+
|
|
7
|
-
func GET(c Context) (*Node, int, error) {
|
|
14
|
+
func GET(c *Context) (*Node, int, error) {
|
|
8
|
-
c.Meta("title", "Page Not Found")
|
|
9
15
|
return c.Render(`
|
|
10
16
|
<main class="box center">
|
|
11
17
|
<h1>Page Not Found</h1>
|
_example/routes/about/get.go
CHANGED
|
@@ -4,9 +4,15 @@ import (
|
|
|
4
4
|
. "github.com/pyros2097/gromer/gsx"
|
|
5
5
|
)
|
|
6
6
|
|
|
7
|
+
var (
|
|
8
|
+
Meta = M{
|
|
9
|
+
"title": "About Gromer",
|
|
10
|
+
"description": "About Gromer",
|
|
11
|
+
}
|
|
12
|
+
Styles = M{}
|
|
13
|
+
)
|
|
14
|
+
|
|
7
|
-
func GET(c Context) (*Node, int, error) {
|
|
15
|
+
func GET(c *Context) (*Node, int, error) {
|
|
8
|
-
c.Meta("title", "About Gromer")
|
|
9
|
-
c.Meta("description", "About Gromer")
|
|
10
16
|
return c.Render(`
|
|
11
17
|
<div class="flex flex-col justify-center items-center">
|
|
12
18
|
A new link is here
|
_example/routes/get.go
CHANGED
|
@@ -5,130 +5,96 @@ import (
|
|
|
5
5
|
. "github.com/pyros2097/gromer/gsx"
|
|
6
6
|
)
|
|
7
7
|
|
|
8
|
-
var
|
|
8
|
+
var (
|
|
9
|
-
|
|
9
|
+
Meta = M{
|
|
10
|
-
background: #fff;
|
|
11
|
-
margin: 130px 0 40px 0;
|
|
12
|
-
position: relative;
|
|
13
|
-
|
|
10
|
+
"title": "Gromer Todos",
|
|
11
|
+
"description": "Gromer Todos",
|
|
12
|
+
"author": "gromer",
|
|
13
|
+
"keywords": "gromer",
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Styles = M{
|
|
17
|
+
"container": "bg-gray-50 min-h-screen font-sans",
|
|
18
|
+
"todos-container": "container mx-auto flex flex-col items-center",
|
|
19
|
+
"title": "text-opacity-20 text-red-900 text-8xl text-center",
|
|
20
|
+
"main": "mt-8 shadow-xl w-full max-w-prose bg-white",
|
|
17
|
-
|
|
21
|
+
"bottom": M{
|
|
18
|
-
top: -155px;
|
|
19
|
-
width: 100%;
|
|
20
|
-
font-size: 100px;
|
|
21
|
-
font-weight: 100;
|
|
22
|
-
|
|
22
|
+
"container": "flex flex-row items-center flex-wrap sm:flex-nowrap p-2 font-light border-t-2 border-gray-100",
|
|
23
|
-
|
|
23
|
+
"row": "flex-1 flex flex-row",
|
|
24
|
+
"section-1": "flex-1 flex flex-row order-1 justify-start",
|
|
25
|
+
"section-2": "flex-1 flex flex-row order-2 sm:order-3 justify-end",
|
|
26
|
+
"section-3": "flex-1 flex flex-row order-3 sm:order-2 min-w-full sm:min-w-min justify-center",
|
|
27
|
+
"link": "rounded border px-1 mx-2 hover:border-red-100",
|
|
24
|
-
|
|
28
|
+
"active": "border-red-900",
|
|
29
|
+
"clear": "font-light hover:underline",
|
|
25
|
-
|
|
30
|
+
"disabled": "invisible disabled",
|
|
31
|
+
},
|
|
32
|
+
"footer": M{
|
|
33
|
+
"container": "mt-16 p-4 flex flex-col",
|
|
26
|
-
|
|
34
|
+
"link": "hover:underline",
|
|
35
|
+
"subtitle": "m-0.5 text-xs text-center text-gray-500",
|
|
36
|
+
},
|
|
27
37
|
}
|
|
28
|
-
|
|
29
|
-
input::-webkit-input-placeholder {
|
|
30
|
-
font-style: italic;
|
|
31
|
-
font-weight: 300;
|
|
32
|
-
color: #e6e6e6;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
input::-moz-placeholder {
|
|
36
|
-
font-style: italic;
|
|
37
|
-
font-weight: 300;
|
|
38
|
-
color: #e6e6e6;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
input::input-placeholder {
|
|
42
|
-
font-style: italic;
|
|
43
|
-
font-weight: 300;
|
|
44
|
-
color: #e6e6e6;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
.clear-completed, .clear-completed:active {
|
|
48
|
-
float: right;
|
|
49
|
-
position: relative;
|
|
50
|
-
line-height: 20px;
|
|
51
|
-
text-decoration: none;
|
|
52
|
-
cursor: pointer;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.clear-completed:hover {
|
|
56
|
-
text-decoration: underline;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
.filters {
|
|
60
|
-
margin: 0;
|
|
61
|
-
padding: 0;
|
|
62
|
-
list-style: none;
|
|
63
|
-
position: absolute;
|
|
64
|
-
right: 0;
|
|
65
|
-
left: 0;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.filters li {
|
|
69
|
-
display: inline;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
.filters li a {
|
|
73
|
-
color: inherit;
|
|
74
|
-
margin: 3px;
|
|
75
|
-
padding: 3px 7px;
|
|
76
|
-
text-decoration: none;
|
|
77
|
-
border: 1px solid transparent;
|
|
78
|
-
border-radius: 3px;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
.filters li a:hover {
|
|
82
|
-
border-color: rgba(175, 47, 47, 0.1);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
.filters li a.selected {
|
|
86
|
-
border-color: rgba(175, 47, 47, 0.2);
|
|
87
|
-
}
|
|
88
|
-
|
|
38
|
+
)
|
|
89
39
|
|
|
90
40
|
type GetParams struct {
|
|
91
41
|
Page int `json:"page"`
|
|
92
42
|
Filter string `json:"filter"`
|
|
93
43
|
}
|
|
94
44
|
|
|
95
|
-
func GET(c Context, params GetParams) (*Node, int, error) {
|
|
45
|
+
func GET(c *Context, params GetParams) (*Node, int, error) {
|
|
96
|
-
c.Meta("title", "Gromer Todos")
|
|
97
|
-
c.Meta("description", "Gromer Todos")
|
|
98
|
-
c.Meta("author", "gromer")
|
|
99
|
-
c.Meta("keywords", "gromer")
|
|
100
46
|
return c.Render(`
|
|
101
47
|
<div class="container">
|
|
102
|
-
<
|
|
48
|
+
<div class="todos-container">
|
|
49
|
+
<header>
|
|
103
|
-
|
|
50
|
+
<h1 class="title">todos</h1>
|
|
104
|
-
|
|
51
|
+
<form hx-post="/" hx-target="#todo-list" hx-swap="afterbegin" _="on htmx:afterOnLoad set #text.value to ''">
|
|
105
|
-
|
|
52
|
+
<input type="hidden" name="intent" value="create" />
|
|
106
|
-
|
|
53
|
+
<input class="new-todo" id="text" name="text" placeholder="What needs to be done?" autofocus="false" autocomplete="off" />
|
|
107
|
-
|
|
54
|
+
</form>
|
|
108
|
-
|
|
55
|
+
</header>
|
|
109
|
-
|
|
56
|
+
<main class="main">
|
|
110
|
-
|
|
57
|
+
<input class="toggle-all" id="toggle-all" type="checkbox" />
|
|
111
|
-
|
|
58
|
+
<label for="toggle-all">Mark all as complete</label>
|
|
112
|
-
|
|
59
|
+
<TodoList id="todo-list" page="{params.Page}" filter="{params.Filter}"></TodoList>
|
|
60
|
+
<div class="bottom">
|
|
61
|
+
<div class="section-1">
|
|
62
|
+
<TodoCount filter="{params.Filter}"></TodoCount>
|
|
63
|
+
</div>
|
|
64
|
+
<ul class="section-2">
|
|
65
|
+
<li>
|
|
66
|
+
<a href="?filter=all" class="link active">All</a>
|
|
67
|
+
</li>
|
|
68
|
+
<li>
|
|
69
|
+
<a href="?filter=active" class="link">Active</a>
|
|
70
|
+
</li>
|
|
71
|
+
<li>
|
|
72
|
+
<a href="?filter=completed" class="link">Completed</a>
|
|
73
|
+
</li>
|
|
74
|
+
</ul>
|
|
75
|
+
<div class="section-3">
|
|
76
|
+
<form hx-target="#todo-list" hx-post="/">
|
|
77
|
+
<input type="hidden" name="intent" value="clear_completed" />
|
|
78
|
+
<button type="submit" class="bottom-clear">Clear completed</button>
|
|
79
|
+
</form>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
113
|
-
|
|
82
|
+
</main>
|
|
114
|
-
|
|
83
|
+
<footer class="footer">
|
|
115
|
-
<TodoCount filter="{params.Filter}" />
|
|
116
|
-
|
|
84
|
+
<span class="subtitle">Written by
|
|
117
|
-
<li>
|
|
118
|
-
<a href="?filter=all">All</a>
|
|
119
|
-
</li>
|
|
120
|
-
<li>
|
|
121
|
-
<a href="?filter=active">Active</a>
|
|
122
|
-
</li>
|
|
123
|
-
<li>
|
|
124
|
-
<a href="?filter=completed">Completed</a>
|
|
125
|
-
</li>
|
|
126
|
-
</ul>
|
|
127
|
-
<form hx-target="#todo-list" hx-post="/">
|
|
128
|
-
|
|
85
|
+
<a class="link" href="https://github.com/pyrossh/">pyrossh</a>
|
|
129
|
-
<button type="submit" class="clear-completed" >Clear completed</button>
|
|
130
|
-
|
|
86
|
+
</span>
|
|
87
|
+
<span class="subtitle">using
|
|
88
|
+
<a class="link" href="https://github.com/pyrossh/gromer">Gromer</a>
|
|
89
|
+
</span>
|
|
90
|
+
<span class="subtitle">thanks to
|
|
91
|
+
<a class="link" href="https://github.com/wishawa/">Wisha Wa</a>
|
|
92
|
+
</span>
|
|
93
|
+
<span class="subtitle">according to the spec
|
|
94
|
+
<a class="link" href="https://todomvc.com/">TodoMVC</a>
|
|
95
|
+
</span>
|
|
131
|
-
|
|
96
|
+
</footer>
|
|
97
|
+
</div>
|
|
132
98
|
</div>
|
|
133
99
|
`), 200, nil
|
|
134
100
|
}
|
_example/routes/post.go
CHANGED
|
@@ -14,7 +14,7 @@ type PostParams struct {
|
|
|
14
14
|
Text string `json:"text"`
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
func POST(c Context, params PostParams) (*Node, int, error) {
|
|
17
|
+
func POST(c *Context, params PostParams) (*Node, int, error) {
|
|
18
18
|
if params.Intent == "clear_completed" {
|
|
19
19
|
allTodos, err := todos.GetAllTodo(c, todos.GetAllTodoParams{
|
|
20
20
|
Filter: "all",
|
_example/routes/styles.go
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
package routes
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
. "github.com/pyros2097/gromer/gsx"
|
|
5
|
-
)
|
|
6
|
-
|
|
7
|
-
var _ = Css(`
|
|
8
|
-
hr {
|
|
9
|
-
margin: 20px 0;
|
|
10
|
-
border: 0;
|
|
11
|
-
border-top: 1px dashed #c5c5c5;
|
|
12
|
-
border-bottom: 1px dashed #f7f7f7;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
html,
|
|
16
|
-
body {
|
|
17
|
-
margin: 0;
|
|
18
|
-
padding: 0;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
button {
|
|
22
|
-
margin: 0;
|
|
23
|
-
padding: 0;
|
|
24
|
-
border: 0;
|
|
25
|
-
background: none;
|
|
26
|
-
font-size: 100%;
|
|
27
|
-
vertical-align: baseline;
|
|
28
|
-
font-family: inherit;
|
|
29
|
-
font-weight: inherit;
|
|
30
|
-
color: inherit;
|
|
31
|
-
-webkit-appearance: none;
|
|
32
|
-
appearance: none;
|
|
33
|
-
-webkit-font-smoothing: antialiased;
|
|
34
|
-
-moz-osx-font-smoothing: grayscale;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
body {
|
|
38
|
-
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
39
|
-
line-height: 1.4em;
|
|
40
|
-
background: #f5f5f5;
|
|
41
|
-
color: #4d4d4d;
|
|
42
|
-
min-width: 230px;
|
|
43
|
-
max-width: 550px;
|
|
44
|
-
margin: 0 auto;
|
|
45
|
-
-webkit-font-smoothing: antialiased;
|
|
46
|
-
-moz-osx-font-smoothing: grayscale;
|
|
47
|
-
font-weight: 300;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
:focus {
|
|
51
|
-
outline: 0;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
.hidden {
|
|
55
|
-
display: none;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
.new-todo,
|
|
59
|
-
.edit {
|
|
60
|
-
position: relative;
|
|
61
|
-
margin: 0;
|
|
62
|
-
width: 100%;
|
|
63
|
-
font-size: 24px;
|
|
64
|
-
font-family: inherit;
|
|
65
|
-
font-weight: inherit;
|
|
66
|
-
line-height: 1.4em;
|
|
67
|
-
border: 0;
|
|
68
|
-
color: inherit;
|
|
69
|
-
padding: 6px;
|
|
70
|
-
border: 1px solid #999;
|
|
71
|
-
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
|
|
72
|
-
box-sizing: border-box;
|
|
73
|
-
-webkit-font-smoothing: antialiased;
|
|
74
|
-
-moz-osx-font-smoothing: grayscale;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
.new-todo {
|
|
78
|
-
padding: 16px 16px 16px 60px;
|
|
79
|
-
border: none;
|
|
80
|
-
background: rgba(0, 0, 0, 0.003);
|
|
81
|
-
box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.main {
|
|
85
|
-
position: relative;
|
|
86
|
-
z-index: 2;
|
|
87
|
-
border-top: 1px solid #e6e6e6;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
.toggle-all {
|
|
91
|
-
text-align: center;
|
|
92
|
-
border: none; /* Mobile Safari */
|
|
93
|
-
opacity: 0;
|
|
94
|
-
position: absolute;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
.toggle-all + label {
|
|
98
|
-
width: 60px;
|
|
99
|
-
height: 34px;
|
|
100
|
-
font-size: 0;
|
|
101
|
-
position: absolute;
|
|
102
|
-
top: -52px;
|
|
103
|
-
left: -13px;
|
|
104
|
-
-webkit-transform: rotate(90deg);
|
|
105
|
-
transform: rotate(90deg);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
.toggle-all + label:before {
|
|
109
|
-
content: '❯';
|
|
110
|
-
font-size: 22px;
|
|
111
|
-
color: #e6e6e6;
|
|
112
|
-
padding: 10px 27px 10px 27px;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
.toggle-all:checked + label:before {
|
|
116
|
-
color: #737373;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.footer {
|
|
120
|
-
color: #777;
|
|
121
|
-
padding: 10px 15px;
|
|
122
|
-
height: 20px;
|
|
123
|
-
text-align: center;
|
|
124
|
-
border-top: 1px solid #e6e6e6;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
.footer:before {
|
|
128
|
-
content: '';
|
|
129
|
-
position: absolute;
|
|
130
|
-
right: 0;
|
|
131
|
-
bottom: 0;
|
|
132
|
-
left: 0;
|
|
133
|
-
height: 50px;
|
|
134
|
-
overflow: hidden;
|
|
135
|
-
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6, 0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6, 0 17px 2px -6px rgba(0, 0, 0, 0.2);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
@media screen and (-webkit-min-device-pixel-ratio: 0) {
|
|
139
|
-
.toggle-all {
|
|
140
|
-
background: none;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
@media (max-width: 430px) {
|
|
145
|
-
.footer {
|
|
146
|
-
height: 50px;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
.filters {
|
|
150
|
-
bottom: 10px;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
`)
|
go.mod
CHANGED
|
@@ -20,13 +20,16 @@ require (
|
|
|
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/repr v0.1.0 // indirect
|
|
23
24
|
github.com/aymerick/douceur v0.2.0 // indirect
|
|
24
25
|
github.com/aymerick/raymond v2.0.2+incompatible // indirect
|
|
25
26
|
github.com/blang/semver v3.5.1+incompatible // indirect
|
|
26
27
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
27
28
|
github.com/felixge/httpsnoop v1.0.1 // indirect
|
|
29
|
+
github.com/go-errors/errors v1.4.2 // indirect
|
|
28
30
|
github.com/go-playground/locales v0.14.0 // indirect
|
|
29
31
|
github.com/go-playground/universal-translator v0.18.0 // indirect
|
|
32
|
+
github.com/go-stack/stack v1.8.1 // indirect
|
|
30
33
|
github.com/gobuffalo/envy v1.6.5 // indirect
|
|
31
34
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
|
32
35
|
github.com/golang/protobuf v1.5.2 // indirect
|
go.sum
CHANGED
|
@@ -99,6 +99,8 @@ 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/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
|
|
103
|
+
github.com/alecthomas/repr v0.1.0/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
|
102
104
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
|
103
105
|
github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
|
|
104
106
|
github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
|
@@ -169,6 +171,8 @@ github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5
|
|
|
169
171
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
|
170
172
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
|
171
173
|
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
|
174
|
+
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
|
175
|
+
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
|
172
176
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
|
173
177
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
|
174
178
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
|
@@ -185,6 +189,8 @@ github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GO
|
|
|
185
189
|
github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A=
|
|
186
190
|
github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
|
187
191
|
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
|
192
|
+
github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
|
|
193
|
+
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
|
|
188
194
|
github.com/gobuffalo/envy v1.6.5 h1:X3is06x7v0nW2xiy2yFbbIjwHz57CD6z6MkvqULTCm8=
|
|
189
195
|
github.com/gobuffalo/envy v1.6.5/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ=
|
|
190
196
|
github.com/gobuffalo/velvet v0.0.0-20170320144106-d97471bf5d8f h1:ddIdPdlkAgKMB0mbkft2LT3oxN1h3MN1fopCFrOgkhY=
|
gsx/gsx.go
CHANGED
|
@@ -21,14 +21,17 @@ var (
|
|
|
21
21
|
Data: "div",
|
|
22
22
|
DataAtom: atom.Lookup([]byte("div")),
|
|
23
23
|
}
|
|
24
|
-
htmlTags
|
|
25
|
-
compMap
|
|
26
|
-
funcMap
|
|
27
|
-
|
|
28
|
-
refRegex
|
|
24
|
+
htmlTags = []string{"a", "abbr", "acronym", "address", "applet", "area", "article", "aside", "audio", "b", "base", "basefont", "bb", "bdo", "big", "blockquote", "body", "br /", "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "command", "datagrid", "datalist", "dd", "del", "details", "dfn", "dialog", "dir", "div", "dl", "dt", "em", "embed", "eventsource", "fieldset", "figcaption", "figure", "font", "footer", "form", "frame", "frameset", "h1 to <h6>", "head", "header", "hgroup", "hr /", "html", "i", "iframe", "img", "input", "ins", "isindex", "kbd", "keygen", "label", "legend", "li", "link", "map", "mark", "menu", "meta", "meter", "nav", "noframes", "noscript", "object", "ol", "optgroup", "option", "output", "p", "param", "pre", "progress", "q", "rp", "rt", "ruby", "s", "samp", "script", "section", "select", "small", "source", "span", "strike", "strong", "style", "sub", "sup", "table", "tbody", "td", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "track", "tt", "u", "ul", "var", "video", "wbr"}
|
|
25
|
+
compMap = map[string]ComponentFunc{}
|
|
26
|
+
funcMap = map[string]interface{}{}
|
|
27
|
+
classesMap = map[string]M{}
|
|
28
|
+
refRegex = regexp.MustCompile(`{(.*?)}`)
|
|
29
29
|
)
|
|
30
30
|
|
|
31
31
|
type (
|
|
32
|
+
M map[string]interface{}
|
|
33
|
+
MS map[string]string
|
|
34
|
+
Arr []interface{}
|
|
32
35
|
ComponentFunc struct {
|
|
33
36
|
Func interface{}
|
|
34
37
|
Args []string
|
|
@@ -42,8 +45,8 @@ type (
|
|
|
42
45
|
Context struct {
|
|
43
46
|
context.Context
|
|
44
47
|
hxRequest bool
|
|
45
|
-
data
|
|
48
|
+
data M
|
|
46
|
-
|
|
49
|
+
meta M
|
|
47
50
|
links map[string]link
|
|
48
51
|
scripts map[string]bool
|
|
49
52
|
}
|
|
@@ -52,61 +55,76 @@ type (
|
|
|
52
55
|
}
|
|
53
56
|
)
|
|
54
57
|
|
|
55
|
-
func NewContext(c context.Context, hxRequest bool) Context {
|
|
58
|
+
func NewContext(c context.Context, hxRequest bool) *Context {
|
|
59
|
+
return &Context{
|
|
60
|
+
Context: c,
|
|
61
|
+
hxRequest: hxRequest,
|
|
62
|
+
data: M{},
|
|
63
|
+
meta: M{},
|
|
64
|
+
links: map[string]link{},
|
|
65
|
+
scripts: map[string]bool{},
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
56
|
-
|
|
69
|
+
func (c *Context) Get(k string) interface{} {
|
|
70
|
+
return c.data[k]
|
|
57
71
|
}
|
|
58
72
|
|
|
59
|
-
func (
|
|
73
|
+
func (c *Context) Set(k string, v interface{}) {
|
|
60
|
-
|
|
74
|
+
c.data[k] = v
|
|
61
75
|
}
|
|
62
76
|
|
|
63
|
-
func (
|
|
77
|
+
func (c *Context) Meta(meta M) {
|
|
64
|
-
|
|
78
|
+
c.meta = meta
|
|
65
79
|
}
|
|
66
80
|
|
|
67
|
-
func (
|
|
81
|
+
func (c *Context) AddMeta(k, v string) {
|
|
68
|
-
|
|
82
|
+
c.meta[k] = v
|
|
69
83
|
}
|
|
70
84
|
|
|
71
|
-
func (
|
|
85
|
+
func (c *Context) Link(rel, href, t, as string) {
|
|
72
|
-
|
|
86
|
+
c.links[href] = link{rel, href, t, as}
|
|
73
87
|
}
|
|
74
88
|
|
|
75
|
-
func (
|
|
89
|
+
func (c *Context) Script(src string, sdefer bool) {
|
|
76
|
-
|
|
90
|
+
c.scripts[src] = sdefer
|
|
77
91
|
}
|
|
78
92
|
|
|
93
|
+
func (c *Context) Data(data M) {
|
|
94
|
+
c.data = data
|
|
95
|
+
}
|
|
96
|
+
|
|
79
|
-
func (
|
|
97
|
+
func (c *Context) Render(tpl string) *Node {
|
|
80
98
|
newTpl := stripWhitespace(tpl)
|
|
81
99
|
doc, err := html.ParseFragment(bytes.NewBuffer([]byte(newTpl)), contextNode)
|
|
82
100
|
if err != nil {
|
|
83
101
|
panic(err)
|
|
84
102
|
}
|
|
85
|
-
populate(
|
|
103
|
+
populate(c, doc[0])
|
|
86
104
|
return &Node{*doc[0]}
|
|
87
105
|
}
|
|
88
106
|
|
|
89
|
-
func (n *Node) Write(
|
|
107
|
+
func (n *Node) Write(c *Context, w io.Writer) {
|
|
90
|
-
if !
|
|
108
|
+
if !c.hxRequest {
|
|
91
109
|
w.Write([]byte(`<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8">`))
|
|
92
110
|
w.Write([]byte(`<meta http-equiv="Content-Type" content="text/html;charset=utf-8"><meta content="utf-8" http-equiv="encoding">`))
|
|
93
111
|
w.Write([]byte(`<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, viewport-fit=cover">`))
|
|
94
|
-
for k, v := range
|
|
112
|
+
for k, v := range c.meta {
|
|
95
113
|
w.Write([]byte(fmt.Sprintf(`<meta name="%s" content="%s">`, k, v)))
|
|
96
114
|
}
|
|
97
|
-
for k, v := range
|
|
115
|
+
for k, v := range c.meta {
|
|
98
116
|
if k == "title" {
|
|
99
117
|
w.Write([]byte(fmt.Sprintf(`<title>%s</title>`, v)))
|
|
100
118
|
}
|
|
101
119
|
}
|
|
102
|
-
for _, v := range
|
|
120
|
+
for _, v := range c.links {
|
|
103
121
|
if v.Type != "" || v.As != "" {
|
|
104
122
|
w.Write([]byte(fmt.Sprintf(`<link rel="%s" href="%s" type="%s" as="%s">`, v.Rel, v.Href, v.Type, v.As)))
|
|
105
123
|
} else {
|
|
106
124
|
w.Write([]byte(fmt.Sprintf(`<link rel="%s" href="%s">`, v.Rel, v.Href)))
|
|
107
125
|
}
|
|
108
126
|
}
|
|
109
|
-
for src, sdefer := range
|
|
127
|
+
for src, sdefer := range c.scripts {
|
|
110
128
|
if sdefer {
|
|
111
129
|
w.Write([]byte(fmt.Sprintf(`<script src="%s" defer="true"></script>`, src)))
|
|
112
130
|
} else {
|
|
@@ -116,7 +134,7 @@ func (n *Node) Write(ctx Context, w io.Writer) {
|
|
|
116
134
|
w.Write([]byte(`</head><body>`))
|
|
117
135
|
}
|
|
118
136
|
html.Render(w, &n.Node)
|
|
119
|
-
if !
|
|
137
|
+
if !c.hxRequest {
|
|
120
138
|
w.Write([]byte(`</body></html>`))
|
|
121
139
|
}
|
|
122
140
|
}
|
|
@@ -139,6 +157,14 @@ func assertName(t, name string) {
|
|
|
139
157
|
}
|
|
140
158
|
}
|
|
141
159
|
|
|
160
|
+
func SetClasses(k string, m M) {
|
|
161
|
+
classesMap[k] = m
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
func GetStyles(k string) string {
|
|
165
|
+
return normalizeCss + "\n" + computeCss(classesMap[k], k)
|
|
166
|
+
}
|
|
167
|
+
|
|
142
168
|
func RegisterComponent(f interface{}, args ...string) {
|
|
143
169
|
name := strings.ToLower(getFunctionName(f))
|
|
144
170
|
assertName("component", name)
|
|
@@ -154,15 +180,6 @@ func RegisterFunc(f interface{}) {
|
|
|
154
180
|
funcMap[name] = f
|
|
155
181
|
}
|
|
156
182
|
|
|
157
|
-
func Css(v string) string {
|
|
158
|
-
styles += v
|
|
159
|
-
return v
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
func GetStyles() string {
|
|
163
|
-
return styles
|
|
164
|
-
}
|
|
165
|
-
|
|
166
183
|
func getFunctionName(temp interface{}) string {
|
|
167
184
|
strs := strings.Split((runtime.FuncForPC(reflect.ValueOf(temp).Pointer()).Name()), ".")
|
|
168
185
|
return strs[len(strs)-1]
|
|
@@ -194,13 +211,13 @@ func convert(ref string, i interface{}) interface{} {
|
|
|
194
211
|
}
|
|
195
212
|
}
|
|
196
213
|
|
|
197
|
-
func getRefValue(
|
|
214
|
+
func getRefValue(c *Context, ref string) interface{} {
|
|
198
215
|
if f, ok := funcMap[ref]; ok {
|
|
199
216
|
return f.(func() string)()
|
|
200
217
|
} else {
|
|
201
218
|
parts := strings.Split(strings.ReplaceAll(ref, "!", ""), ".")
|
|
202
219
|
if len(parts) == 2 {
|
|
203
|
-
if v, ok :=
|
|
220
|
+
if v, ok := c.data[parts[0]]; ok {
|
|
204
221
|
a := reflect.ValueOf(v)
|
|
205
222
|
if a.Kind() == reflect.Ptr {
|
|
206
223
|
i := a.Elem().FieldByName(parts[1]).Interface()
|
|
@@ -211,7 +228,7 @@ func getRefValue(ctx Context, ref string) interface{} {
|
|
|
211
228
|
}
|
|
212
229
|
}
|
|
213
230
|
}
|
|
214
|
-
return convert(ref,
|
|
231
|
+
return convert(ref, c.data[ref])
|
|
215
232
|
}
|
|
216
233
|
}
|
|
217
234
|
|
|
@@ -219,11 +236,11 @@ func removeBrackets(v string) string {
|
|
|
219
236
|
return strings.ReplaceAll(strings.ReplaceAll(v, "{", ""), "}", "")
|
|
220
237
|
}
|
|
221
238
|
|
|
222
|
-
func substituteString(
|
|
239
|
+
func substituteString(c *Context, v string) string {
|
|
223
240
|
found := refRegex.FindString(v)
|
|
224
241
|
if found != "" {
|
|
225
242
|
varName := removeBrackets(found)
|
|
226
|
-
varValue := fmt.Sprintf("%v", getRefValue(
|
|
243
|
+
varValue := fmt.Sprintf("%v", getRefValue(c, varName))
|
|
227
244
|
return strings.ReplaceAll(v, found, varValue)
|
|
228
245
|
}
|
|
229
246
|
return v
|
|
@@ -280,10 +297,10 @@ func cloneNode(n *html.Node) *html.Node {
|
|
|
280
297
|
return newNode
|
|
281
298
|
}
|
|
282
299
|
|
|
283
|
-
func populate(
|
|
300
|
+
func populate(c *Context, n *html.Node) {
|
|
284
301
|
if n.Type == html.TextNode {
|
|
285
302
|
if n.Data != "" && strings.Contains(n.Data, "{") && n.Data != "{children}" {
|
|
286
|
-
n.Data = substituteString(
|
|
303
|
+
n.Data = substituteString(c, n.Data)
|
|
287
304
|
}
|
|
288
305
|
} else if n.Type == html.ElementNode {
|
|
289
306
|
for i, at := range n.Attr {
|
|
@@ -292,7 +309,7 @@ func populate(ctx Context, n *html.Node) {
|
|
|
292
309
|
arr := strings.Split(xfor, " in ")
|
|
293
310
|
ctxItemKey := arr[0]
|
|
294
311
|
ctxKey := arr[1]
|
|
295
|
-
data :=
|
|
312
|
+
data := c.data[ctxKey]
|
|
296
313
|
switch reflect.TypeOf(data).Kind() {
|
|
297
314
|
case reflect.Slice:
|
|
298
315
|
v := reflect.ValueOf(data)
|
|
@@ -308,8 +325,8 @@ func populate(ctx Context, n *html.Node) {
|
|
|
308
325
|
firstChild := cloneNode(n.FirstChild)
|
|
309
326
|
n.RemoveChild(n.FirstChild)
|
|
310
327
|
for i := 0; i < v.Len(); i++ {
|
|
311
|
-
compCtx := Context{
|
|
328
|
+
compCtx := &Context{
|
|
312
|
-
Context:
|
|
329
|
+
Context: c.Context,
|
|
313
330
|
data: map[string]interface{}{
|
|
314
331
|
ctxItemKey: v.Index(i).Interface(),
|
|
315
332
|
},
|
|
@@ -333,7 +350,7 @@ func populate(ctx Context, n *html.Node) {
|
|
|
333
350
|
kvarray := strings.Split(kv, ":")
|
|
334
351
|
k := strings.TrimSpace(kvarray[0])
|
|
335
352
|
v := strings.TrimSpace(kvarray[1])
|
|
336
|
-
varValue := getRefValue(
|
|
353
|
+
varValue := getRefValue(c, v)
|
|
337
354
|
if varValue.(bool) {
|
|
338
355
|
classes += k
|
|
339
356
|
}
|
|
@@ -347,26 +364,26 @@ func populate(ctx Context, n *html.Node) {
|
|
|
347
364
|
n.Attr[i] = html.Attribute{
|
|
348
365
|
Namespace: at.Namespace,
|
|
349
366
|
Key: at.Key,
|
|
350
|
-
Val: substituteString(
|
|
367
|
+
Val: substituteString(c, at.Val),
|
|
351
368
|
}
|
|
352
369
|
}
|
|
353
370
|
}
|
|
354
371
|
}
|
|
355
|
-
for
|
|
372
|
+
for child := n.FirstChild; child != nil; child = child.NextSibling {
|
|
356
|
-
populate(
|
|
373
|
+
populate(c, child)
|
|
357
374
|
}
|
|
358
375
|
if comp, ok := compMap[n.Data]; ok {
|
|
359
|
-
newNode := populateComponent(
|
|
376
|
+
newNode := populateComponent(c, comp, n, true)
|
|
360
377
|
n.AppendChild(newNode)
|
|
361
378
|
}
|
|
362
379
|
}
|
|
363
380
|
}
|
|
364
381
|
|
|
365
|
-
func renderComponent(
|
|
382
|
+
func renderComponent(c *Context, comp ComponentFunc, n *html.Node) *Node {
|
|
366
|
-
args := []reflect.Value{reflect.ValueOf(
|
|
383
|
+
args := []reflect.Value{reflect.ValueOf(c)}
|
|
367
384
|
funcType := reflect.TypeOf(comp.Func)
|
|
368
385
|
for i, arg := range comp.Args {
|
|
369
|
-
if v, ok :=
|
|
386
|
+
if v, ok := c.data[arg]; ok {
|
|
370
387
|
args = append(args, reflect.ValueOf(v))
|
|
371
388
|
} else {
|
|
372
389
|
v := getAttribute(arg, n.Attr)
|
|
@@ -388,8 +405,8 @@ func renderComponent(ctx Context, comp ComponentFunc, n *html.Node) *Node {
|
|
|
388
405
|
return compNode
|
|
389
406
|
}
|
|
390
407
|
|
|
391
|
-
func populateComponent(
|
|
408
|
+
func populateComponent(c *Context, comp ComponentFunc, n *html.Node, remove bool) *html.Node {
|
|
392
|
-
compNode := renderComponent(
|
|
409
|
+
compNode := renderComponent(c, comp, n)
|
|
393
410
|
if n.FirstChild != nil {
|
|
394
411
|
newChild := cloneNode(n.FirstChild)
|
|
395
412
|
newChild.Parent = nil
|
|
@@ -397,7 +414,7 @@ func populateComponent(ctx Context, comp ComponentFunc, n *html.Node, remove boo
|
|
|
397
414
|
n.RemoveChild(n.FirstChild)
|
|
398
415
|
}
|
|
399
416
|
if !remove {
|
|
400
|
-
populate(
|
|
417
|
+
populate(c, newChild)
|
|
401
418
|
}
|
|
402
419
|
if compNode.FirstChild != nil {
|
|
403
420
|
populateChildren(compNode.FirstChild, newChild)
|
gsx/twx.go
ADDED
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
package gsx
|
|
2
|
+
|
|
3
|
+
import "strings"
|
|
4
|
+
|
|
5
|
+
type KeyValues struct {
|
|
6
|
+
Keys M
|
|
7
|
+
Values MS
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
var colors = KeyValues{
|
|
11
|
+
Keys: M{
|
|
12
|
+
"bg": "background-color",
|
|
13
|
+
"text": "color",
|
|
14
|
+
"divide": "border-color",
|
|
15
|
+
"border": "border-color",
|
|
16
|
+
"ring": "--tw-ring-color",
|
|
17
|
+
"border-l": "border-left-color",
|
|
18
|
+
"border-r": "border-right-color",
|
|
19
|
+
"border-t": "border-top-color",
|
|
20
|
+
"border-b": "border-bottom-color",
|
|
21
|
+
},
|
|
22
|
+
Values: MS{
|
|
23
|
+
"transparent": "transparent",
|
|
24
|
+
"current": "currentColor",
|
|
25
|
+
"black": "rgba(0, 0, 0, 1)",
|
|
26
|
+
"white": "rgba(255, 255, 255, 1)",
|
|
27
|
+
"gray-50": "rgba(249, 250, 251, 1)",
|
|
28
|
+
"gray-100": "rgba(243, 244, 246, 1)",
|
|
29
|
+
"gray-200": "rgba(229, 231, 235, 1)",
|
|
30
|
+
"gray-300": "rgba(209, 213, 219, 1)",
|
|
31
|
+
"gray-400": "rgba(156, 163, 175, 1)",
|
|
32
|
+
"gray-500": "rgba(107, 114, 128, 1)",
|
|
33
|
+
"gray-600": "rgba(75, 85, 99, 1)",
|
|
34
|
+
"gray-700": "rgba(55, 65, 81, 1)",
|
|
35
|
+
"gray-800": "rgba(31, 41, 55, 1)",
|
|
36
|
+
"gray-900": "rgba(17, 24, 39, 1)",
|
|
37
|
+
"red-50": "rgba(254, 242, 242, 1)",
|
|
38
|
+
"red-100": "rgba(254, 226, 226, 1)",
|
|
39
|
+
"red-200": "rgba(254, 202, 202, 1)",
|
|
40
|
+
"red-300": "rgba(252, 165, 165, 1)",
|
|
41
|
+
"red-400": "rgba(248, 113, 113, 1)",
|
|
42
|
+
"red-500": "rgba(239, 68, 68, 1)",
|
|
43
|
+
"red-600": "rgba(220, 38, 38, 1)",
|
|
44
|
+
"red-700": "rgba(185, 28, 28, 1)",
|
|
45
|
+
"red-800": "rgba(153, 27, 27, 1)",
|
|
46
|
+
"red-900": "rgba(127, 29, 29, 1)",
|
|
47
|
+
"yellow-50": "rgba(255, 251, 235, 1)",
|
|
48
|
+
"yellow-100": "rgba(254, 243, 199, 1)",
|
|
49
|
+
"yellow-200": "rgba(253, 230, 138, 1)",
|
|
50
|
+
"yellow-300": "rgba(252, 211, 77, 1)",
|
|
51
|
+
"yellow-400": "rgba(251, 191, 36, 1)",
|
|
52
|
+
"yellow-500": "rgba(245, 158, 11, 1)",
|
|
53
|
+
"yellow-600": "rgba(217, 119, 6, 1)",
|
|
54
|
+
"yellow-700": "rgba(180, 83, 9, 1)",
|
|
55
|
+
"yellow-800": "rgba(146, 64, 14, 1)",
|
|
56
|
+
"yellow-900": "rgba(120, 53, 15, 1)",
|
|
57
|
+
"green-50": "rgba(236, 253, 245, 1)",
|
|
58
|
+
"green-100": "rgba(209, 250, 229, 1)",
|
|
59
|
+
"green-200": "rgba(167, 243, 208, 1)",
|
|
60
|
+
"green-300": "rgba(110, 231, 183, 1)",
|
|
61
|
+
"green-400": "rgba(52, 211, 153, 1)",
|
|
62
|
+
"green-500": "rgba(16, 185, 129, 1)",
|
|
63
|
+
"green-600": "rgba(5, 150, 105, 1)",
|
|
64
|
+
"green-700": "rgba(4, 120, 87, 1)",
|
|
65
|
+
"green-800": "rgba(6, 95, 70, 1)",
|
|
66
|
+
"green-900": "rgba(6, 78, 59, 1)",
|
|
67
|
+
"blue-50": "rgba(239, 246, 255, 1)",
|
|
68
|
+
"blue-100": "rgba(219, 234, 254, 1)",
|
|
69
|
+
"blue-200": "rgba(191, 219, 254, 1)",
|
|
70
|
+
"blue-300": "rgba(147, 197, 253, 1)",
|
|
71
|
+
"blue-400": "rgba(96, 165, 250, 1)",
|
|
72
|
+
"blue-500": "rgba(59, 130, 246, 1)",
|
|
73
|
+
"blue-600": "rgba(37, 99, 235, 1)",
|
|
74
|
+
"blue-700": "rgba(29, 78, 216, 1)",
|
|
75
|
+
"blue-800": "rgba(30, 64, 175, 1)",
|
|
76
|
+
"blue-900": "rgba(30, 58, 138, 1)",
|
|
77
|
+
"indigo-50": "rgba(238, 242, 255, 1)",
|
|
78
|
+
"indigo-100": "rgba(224, 231, 255, 1)",
|
|
79
|
+
"indigo-200": "rgba(199, 210, 254, 1)",
|
|
80
|
+
"indigo-300": "rgba(165, 180, 252, 1)",
|
|
81
|
+
"indigo-400": "rgba(129, 140, 248, 1)",
|
|
82
|
+
"indigo-500": "rgba(99, 102, 241, 1)",
|
|
83
|
+
"indigo-600": "rgba(79, 70, 229, 1)",
|
|
84
|
+
"indigo-700": "rgba(67, 56, 202, 1)",
|
|
85
|
+
"indigo-800": "rgba(55, 48, 163, 1)",
|
|
86
|
+
"indigo-900": "rgba(49, 46, 129, 1)",
|
|
87
|
+
"purple-50": "rgba(245, 243, 255, 1)",
|
|
88
|
+
"purple-100": "rgba(237, 233, 254, 1)",
|
|
89
|
+
"purple-200": "rgba(221, 214, 254, 1)",
|
|
90
|
+
"purple-300": "rgba(196, 181, 253, 1)",
|
|
91
|
+
"purple-400": "rgba(167, 139, 250, 1)",
|
|
92
|
+
"purple-500": "rgba(139, 92, 246, 1)",
|
|
93
|
+
"purple-600": "rgba(124, 58, 237, 1)",
|
|
94
|
+
"purple-700": "rgba(109, 40, 217, 1)",
|
|
95
|
+
"purple-800": "rgba(91, 33, 182, 1)",
|
|
96
|
+
"purple-900": "rgba(76, 29, 149, 1)",
|
|
97
|
+
"pink-50": "rgba(253, 242, 248, 1)",
|
|
98
|
+
"pink-100": "rgba(252, 231, 243, 1)",
|
|
99
|
+
"pink-200": "rgba(251, 207, 232, 1)",
|
|
100
|
+
"pink-300": "rgba(249, 168, 212, 1)",
|
|
101
|
+
"pink-400": "rgba(244, 114, 182, 1)",
|
|
102
|
+
"pink-500": "rgba(236, 72, 153, 1)",
|
|
103
|
+
"pink-600": "rgba(219, 39, 119, 1)",
|
|
104
|
+
"pink-700": "rgba(190, 24, 93, 1)",
|
|
105
|
+
"pink-800": "rgba(157, 23, 77, 1)",
|
|
106
|
+
"pink-900": "rgba(131, 24, 67, 1)",
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
var spacing = KeyValues{
|
|
111
|
+
Keys: M{
|
|
112
|
+
"mr": "margin-right",
|
|
113
|
+
"ml": "margin-left",
|
|
114
|
+
"mt": "margin-top",
|
|
115
|
+
"mb": "margin-bottom",
|
|
116
|
+
"mx": Arr{
|
|
117
|
+
"margin-left",
|
|
118
|
+
"margin-right",
|
|
119
|
+
},
|
|
120
|
+
"my": Arr{
|
|
121
|
+
"margin-top",
|
|
122
|
+
"margin-bottom",
|
|
123
|
+
},
|
|
124
|
+
"m": "margin",
|
|
125
|
+
"pr": "padding-right",
|
|
126
|
+
"pl": "padding-left",
|
|
127
|
+
"pt": "padding-top",
|
|
128
|
+
"pb": "padding-bottom",
|
|
129
|
+
"px": Arr{
|
|
130
|
+
"padding-left",
|
|
131
|
+
"padding-right",
|
|
132
|
+
},
|
|
133
|
+
"py": Arr{
|
|
134
|
+
"padding-top",
|
|
135
|
+
"padding-bottom",
|
|
136
|
+
},
|
|
137
|
+
"p": "padding",
|
|
138
|
+
},
|
|
139
|
+
Values: MS{
|
|
140
|
+
"0": "0px",
|
|
141
|
+
"1": "0.25rem",
|
|
142
|
+
"2": "0.5rem",
|
|
143
|
+
"3": "0.75rem",
|
|
144
|
+
"4": "1rem",
|
|
145
|
+
"5": "1.25rem",
|
|
146
|
+
"6": "1.5rem",
|
|
147
|
+
"7": "1.75rem",
|
|
148
|
+
"8": "2rem",
|
|
149
|
+
"9": "2.25rem",
|
|
150
|
+
"10": "2.5rem",
|
|
151
|
+
"11": "2.75rem",
|
|
152
|
+
"12": "3rem",
|
|
153
|
+
"14": "3.5rem",
|
|
154
|
+
"16": "4rem",
|
|
155
|
+
"20": "5rem",
|
|
156
|
+
"24": "6rem",
|
|
157
|
+
"28": "7rem",
|
|
158
|
+
"32": "8rem",
|
|
159
|
+
"36": "9rem",
|
|
160
|
+
"40": "10rem",
|
|
161
|
+
"44": "11rem",
|
|
162
|
+
"48": "12rem",
|
|
163
|
+
"52": "13rem",
|
|
164
|
+
"56": "14rem",
|
|
165
|
+
"60": "15rem",
|
|
166
|
+
"64": "16rem",
|
|
167
|
+
"72": "18rem",
|
|
168
|
+
"80": "20rem",
|
|
169
|
+
"96": "24rem",
|
|
170
|
+
"auto": "auto",
|
|
171
|
+
"px": "1px",
|
|
172
|
+
"0.5": "0.125rem",
|
|
173
|
+
"1.5": "0.375rem",
|
|
174
|
+
"2.5": "0.625rem",
|
|
175
|
+
"3.5": "0.875rem",
|
|
176
|
+
},
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
var radius = KeyValues{
|
|
180
|
+
Keys: M{
|
|
181
|
+
"rounded": "border-radius",
|
|
182
|
+
"rounded-t": "border-top-radius",
|
|
183
|
+
"rounded-r": "border-right-radius",
|
|
184
|
+
"rounded-l": "border-left-radius",
|
|
185
|
+
"rounded-b": "border-bottom-radius",
|
|
186
|
+
"rounded-tl": Arr{
|
|
187
|
+
"border-top-radius",
|
|
188
|
+
"border-left-radius",
|
|
189
|
+
},
|
|
190
|
+
"rounded-tr": Arr{
|
|
191
|
+
"border-top-radius",
|
|
192
|
+
"border-right-radius",
|
|
193
|
+
},
|
|
194
|
+
"rounded-bl": Arr{
|
|
195
|
+
"border-bottom-radius",
|
|
196
|
+
"border-left-radius",
|
|
197
|
+
},
|
|
198
|
+
"rounded-br": Arr{
|
|
199
|
+
"border-bottom-radius",
|
|
200
|
+
"border-right-radius",
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
Values: MS{
|
|
204
|
+
"none": "0px",
|
|
205
|
+
"sm": "0.125rem",
|
|
206
|
+
"": "0.25rem",
|
|
207
|
+
"md": "0.375rem",
|
|
208
|
+
"lg": "0.5rem",
|
|
209
|
+
"xl": "0.75rem",
|
|
210
|
+
"2xl": "1rem",
|
|
211
|
+
"3xl": "1.5rem",
|
|
212
|
+
"full": "9999px",
|
|
213
|
+
},
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
var borders = KeyValues{
|
|
217
|
+
Keys: M{
|
|
218
|
+
"border": "border-width",
|
|
219
|
+
"border-l": "border-left-width",
|
|
220
|
+
"border-r": "border-right-width",
|
|
221
|
+
"border-t": "border-top-width",
|
|
222
|
+
"border-b": "border-bottom-width",
|
|
223
|
+
},
|
|
224
|
+
Values: MS{
|
|
225
|
+
"0": "0px",
|
|
226
|
+
"2": "2px",
|
|
227
|
+
"4": "4px",
|
|
228
|
+
"8": "8px",
|
|
229
|
+
"": "1px",
|
|
230
|
+
},
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
var sizes = KeyValues{
|
|
234
|
+
Keys: M{
|
|
235
|
+
"h": "height",
|
|
236
|
+
"w": "width",
|
|
237
|
+
"top": "top",
|
|
238
|
+
"left": "left",
|
|
239
|
+
"bottom": "bottom",
|
|
240
|
+
"right": "right",
|
|
241
|
+
"minh": "min-height",
|
|
242
|
+
"minw": "min-width",
|
|
243
|
+
"maxh": "max-height",
|
|
244
|
+
"maxw": "max-width",
|
|
245
|
+
},
|
|
246
|
+
Values: MS{
|
|
247
|
+
"auto": "auto",
|
|
248
|
+
"min": "min-content",
|
|
249
|
+
"max": "max-content",
|
|
250
|
+
"0": "0px",
|
|
251
|
+
"1": "0.25rem",
|
|
252
|
+
"2": "0.5rem",
|
|
253
|
+
"3": "0.75rem",
|
|
254
|
+
"4": "1rem",
|
|
255
|
+
"5": "1.25rem",
|
|
256
|
+
"6": "1.5rem",
|
|
257
|
+
"7": "1.75rem",
|
|
258
|
+
"8": "2rem",
|
|
259
|
+
"9": "2.25rem",
|
|
260
|
+
"10": "2.5rem",
|
|
261
|
+
"11": "2.75rem",
|
|
262
|
+
"12": "3rem",
|
|
263
|
+
"14": "3.5rem",
|
|
264
|
+
"16": "4rem",
|
|
265
|
+
"20": "5rem",
|
|
266
|
+
"24": "6rem",
|
|
267
|
+
"28": "7rem",
|
|
268
|
+
"32": "8rem",
|
|
269
|
+
"36": "9rem",
|
|
270
|
+
"40": "10rem",
|
|
271
|
+
"44": "11rem",
|
|
272
|
+
"48": "12rem",
|
|
273
|
+
"52": "13rem",
|
|
274
|
+
"56": "14rem",
|
|
275
|
+
"60": "15rem",
|
|
276
|
+
"64": "16rem",
|
|
277
|
+
"72": "18rem",
|
|
278
|
+
"80": "20rem",
|
|
279
|
+
"96": "24rem",
|
|
280
|
+
"px": "1px",
|
|
281
|
+
"0.5": "0.125rem",
|
|
282
|
+
"1.5": "0.375rem",
|
|
283
|
+
"2.5": "0.625rem",
|
|
284
|
+
"3.5": "0.875rem",
|
|
285
|
+
"1/2": "50%",
|
|
286
|
+
"1/3": "33.33%",
|
|
287
|
+
"2/3": "66.66%",
|
|
288
|
+
"1/4": "25%",
|
|
289
|
+
"2/4": "50%",
|
|
290
|
+
"3/4": "75%",
|
|
291
|
+
"1/5": "20%",
|
|
292
|
+
"2/5": "40%",
|
|
293
|
+
"3/5": "60%",
|
|
294
|
+
"4/5": "80%",
|
|
295
|
+
"1/6": "16.66%",
|
|
296
|
+
"2/6": "33.33%",
|
|
297
|
+
"3/6": "50%",
|
|
298
|
+
"4/6": "66.66%",
|
|
299
|
+
"5/6": "83.33%",
|
|
300
|
+
"1/12": "8.33%",
|
|
301
|
+
"2/12": "16.66%",
|
|
302
|
+
"3/12": "25%",
|
|
303
|
+
"4/12": "33.33%",
|
|
304
|
+
"5/12": "41.66%",
|
|
305
|
+
"6/12": "50%",
|
|
306
|
+
"7/12": "58.33%",
|
|
307
|
+
"8/12": "66.66%",
|
|
308
|
+
"9/12": "75%",
|
|
309
|
+
"10/12": "83.33%",
|
|
310
|
+
"11/12": "91.66%",
|
|
311
|
+
"full": "100%",
|
|
312
|
+
},
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
var twClassLookup = MS{
|
|
316
|
+
"flex": "display: flex;",
|
|
317
|
+
"inline-flex": "display: inline-flex;",
|
|
318
|
+
"block": "display: block;",
|
|
319
|
+
"inline-block": "display: inline-block;",
|
|
320
|
+
"inline": "display: inline;",
|
|
321
|
+
"table": "display: table;",
|
|
322
|
+
"inline-table": "display: inline-table;",
|
|
323
|
+
"grid": "display: grid;",
|
|
324
|
+
"inline-grid": "display: inline-grid;",
|
|
325
|
+
"contents": "display: contents;",
|
|
326
|
+
"list-item": "display: list-item;",
|
|
327
|
+
"hidden": "display: none;",
|
|
328
|
+
"flex-1": "flex: 1;",
|
|
329
|
+
"flex-row": "flex-direction: row;",
|
|
330
|
+
"flex-col": "flex-direction: column;",
|
|
331
|
+
"flex-wrap": "flex-wrap: wrap;",
|
|
332
|
+
"flex-nowrap": "flex-wrap: nowrap;",
|
|
333
|
+
"flex-wrap-reverse": "flex-wrap: wrap-reverse;",
|
|
334
|
+
"items-baseline": "align-items: baseline;",
|
|
335
|
+
"items-start": "align-items: flex-start;",
|
|
336
|
+
"items-center": "align-items: center;",
|
|
337
|
+
"items-end": "align-items: flex-end;",
|
|
338
|
+
"items-stretch": "align-items: stretch;",
|
|
339
|
+
"justify-start": "justify-content: flex-start;",
|
|
340
|
+
"justify-end": "justify-content: flex-end;",
|
|
341
|
+
"justify-center": "justify-content: center;",
|
|
342
|
+
"justify-between": "justify-content: space-between;",
|
|
343
|
+
"justify-around": "justify-content: space-around;",
|
|
344
|
+
"justify-evenly": "justify-content: space-evenly;",
|
|
345
|
+
"uppercase": "text-transform: uppercase",
|
|
346
|
+
"lowercase": "text-transform: lowercase",
|
|
347
|
+
"capitalize": "text-transform: capitalize",
|
|
348
|
+
"normal-case": "text-transform: normal-case",
|
|
349
|
+
"text-left": "text-align: left;",
|
|
350
|
+
"text-center": "text-align: center;",
|
|
351
|
+
"text-right": "text-align: right;",
|
|
352
|
+
"text-justify": "text-align: justify;",
|
|
353
|
+
"underline": "text-decoration: underline;",
|
|
354
|
+
"line-through": "text-decoration: line-through;",
|
|
355
|
+
"no-underline": "text-decoration: none;",
|
|
356
|
+
"whitespace-normal": "white-space: normal;",
|
|
357
|
+
"whitespace-nowrap": "white-space: nowrap;",
|
|
358
|
+
"whitespace-pre": "white-space: pre;",
|
|
359
|
+
"whitespace-pre-line": "white-space: pre-line;",
|
|
360
|
+
"whitespace-pre-wrap": "white-space: pre-wrap;",
|
|
361
|
+
"break-normal": "word-break: normal; overflow-wrap: normal;",
|
|
362
|
+
"break-words": "word-break: break-word;",
|
|
363
|
+
"break-all": "word-break: break-all;",
|
|
364
|
+
"font-sans": "font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";",
|
|
365
|
+
"font-serif": "font-family: ui-serif, Georgia, Cambria, \"Times New Roman\", Times, serif;",
|
|
366
|
+
"font-mono": "font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;",
|
|
367
|
+
"font-thin": "font-weight: 100;",
|
|
368
|
+
"font-extralight": "font-weight: 200;",
|
|
369
|
+
"font-light": "font-weight: 300;",
|
|
370
|
+
"font-normal": "font-weight: 400;",
|
|
371
|
+
"font-medium": "font-weight: 500;",
|
|
372
|
+
"font-semibold": "font-weight: 600;",
|
|
373
|
+
"font-bold": "font-weight: 700;",
|
|
374
|
+
"font-extrabold": "font-weight: 800;",
|
|
375
|
+
"font-black": "font-weight: 900;",
|
|
376
|
+
"text-xs": "font-size: 0.75rem; line-height: 1rem;",
|
|
377
|
+
"text-sm": "font-size: 0.875rem; line-height: 1.25rem;",
|
|
378
|
+
"text-base": "font-size: 1rem; line-height: 1.5rem;",
|
|
379
|
+
"text-lg": "font-size: 1.125rem; line-height: 1.75rem;",
|
|
380
|
+
"text-xl": "font-size: 1.25rem; line-height: 1.75rem;",
|
|
381
|
+
"text-2xl": "font-size: 1.5rem; line-height: 2rem;",
|
|
382
|
+
"text-3xl": "font-size: 1.875rem; line-height: 2.25rem;",
|
|
383
|
+
"text-4xl": "font-size: 2.25rem; line-height: 2.5rem;",
|
|
384
|
+
"text-5xl": "font-size: 3rem; line-height: 1;",
|
|
385
|
+
"text-6xl": "font-size: 3.75rem;; line-height: 1;",
|
|
386
|
+
"text-7xl": "font-size: 4.5rem; line-height: 1;",
|
|
387
|
+
"text-8xl": "font-size: 6rem; line-height: 1;",
|
|
388
|
+
"text-9xl": "font-size: 8rem; line-height: 1;",
|
|
389
|
+
"cursor-auto": "cursor: auto;",
|
|
390
|
+
"cursor-default": "cursor: default;",
|
|
391
|
+
"cursor-pointer": "cursor: pointer;",
|
|
392
|
+
"cursor-wait": "cursor: wait;",
|
|
393
|
+
"cursor-text": "cursor: text;",
|
|
394
|
+
"cursor-move": "cursor: move;",
|
|
395
|
+
"cursor-help": "cursor: help;",
|
|
396
|
+
"cursor-not-allowed": "cursor: not-allowed;",
|
|
397
|
+
"pointer-events-none": "pointer-events: none;",
|
|
398
|
+
"pointer-events-auto": "pointer-events: auto;",
|
|
399
|
+
"select-none": "user-select: none;",
|
|
400
|
+
"select-text": "user-select: text;",
|
|
401
|
+
"select-all": "user-select: all;",
|
|
402
|
+
"select-auto": "user-select: auto;",
|
|
403
|
+
"w-screen": "100vw",
|
|
404
|
+
"h-screen": "100vh",
|
|
405
|
+
"static": "position: static;",
|
|
406
|
+
"fixed": "position: fixed;",
|
|
407
|
+
"absolute": "position: absolute;",
|
|
408
|
+
"relative": "position: relative;",
|
|
409
|
+
"sticky": "position: sticky;",
|
|
410
|
+
"overflow-auto": "overflow: auto;",
|
|
411
|
+
"overflow-hidden": "overflow: hidden;",
|
|
412
|
+
"overflow-visible": "overflow: visible;",
|
|
413
|
+
"overflow-scroll": "overflow: scroll;",
|
|
414
|
+
"overflow-x-auto": "overflow-x: auto;",
|
|
415
|
+
"overflow-y-auto": "overflow-y: auto;",
|
|
416
|
+
"overflow-x-hidden": "overflow-x: hidden;",
|
|
417
|
+
"overflow-y-hidden": "overflow-y: hidden;",
|
|
418
|
+
"overflow-x-visible": "overflow-x: visible;",
|
|
419
|
+
"overflow-y-visible": "overflow-y: visible;",
|
|
420
|
+
"overflow-x-scroll": "overflow-x: scroll;",
|
|
421
|
+
"overflow-y-scroll": "overflow-y: scroll;",
|
|
422
|
+
"origin-center": "transform-origin: center;",
|
|
423
|
+
"origin-top": "transform-origin: top;",
|
|
424
|
+
"origin-top-right": "transform-origin: top right;",
|
|
425
|
+
"origin-right": "transform-origin: right;",
|
|
426
|
+
"origin-bottom-right": "transform-origin: bottom right;",
|
|
427
|
+
"origin-bottom": "transform-origin: bottom;",
|
|
428
|
+
"origin-bottom-left": "transform-origin: bottom left;",
|
|
429
|
+
"origin-left": "transform-origin: left;",
|
|
430
|
+
"origin-top-left": "transform-origin: top left;",
|
|
431
|
+
"shadow-sm": "box-shadow: 0 0 #0000, 0 0 #0000, 0 1px 2px 0 rgba(0, 0, 0, 0.05);",
|
|
432
|
+
"shadow": "box-shadow: 0 0 #0000, 0 0 #0000, 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);",
|
|
433
|
+
"shadow-md": "box-shadow: 0 0 #0000, 0 0 #0000, 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);",
|
|
434
|
+
"shadow-lg": "box-shadow: 0 0 #0000, 0 0 #0000, 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);",
|
|
435
|
+
"shadow-xl": "box-shadow: 0 0 #0000, 0 0 #0000, 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);",
|
|
436
|
+
"shadow-2xl": "box-shadow: 0 0 #0000, 0 0 #0000, 0 25px 50px -12px rgba(0, 0, 0, 0.25);",
|
|
437
|
+
"shadow-inner": "box-shadow: 0 0 #0000, 0 0 #0000, inset 0 2px 4px 0 rgba(0, 0, 0, 0.06);",
|
|
438
|
+
"shadow-none": "box-shadow: 0 0 #0000, 0 0 #0000, 0 0 #0000;",
|
|
439
|
+
"ring-inset": "--tw-ring-inset: insest;",
|
|
440
|
+
"ring-0": "box-shadow: 0 0 0 calc(0px + 0px) rgba(59, 130, 246, 0.5);",
|
|
441
|
+
"ring-1": "box-shadow: 0 0 0 calc(1px + 0px) rgba(59, 130, 246, 0.5);",
|
|
442
|
+
"ring-2": "box-shadow: 0 0 0 calc(2px + 0px) rgba(59, 130, 246, 0.5);",
|
|
443
|
+
"ring-4": "box-shadow: 0 0 0 calc(4px + 0px) rgba(59, 130, 246, 0.5);",
|
|
444
|
+
"ring-8": "box-shadow: 0 0 0 calc(8px + 0px) rgba(59, 130, 246, 0.5);",
|
|
445
|
+
"ring": "box-shadow: 0 0 0 calc(3px + 0px) rgba(59, 130, 246, 0.5);",
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
func init() {
|
|
449
|
+
mapApply(sizes)
|
|
450
|
+
mapApply(spacing)
|
|
451
|
+
mapApply(colors)
|
|
452
|
+
mapApply(borders)
|
|
453
|
+
mapApply(radius)
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
func mapApply(obj KeyValues) {
|
|
457
|
+
for key, v := range obj.Keys {
|
|
458
|
+
for vkey, vv := range obj.Values {
|
|
459
|
+
suffix := ""
|
|
460
|
+
if vkey != "" {
|
|
461
|
+
suffix = "-" + vkey
|
|
462
|
+
}
|
|
463
|
+
className := key + suffix
|
|
464
|
+
if vstring, ok := v.(string); ok {
|
|
465
|
+
twClassLookup[className] = vstring + ": " + vv + ";"
|
|
466
|
+
}
|
|
467
|
+
if varr, ok := v.(Arr); ok {
|
|
468
|
+
twClassLookup[className] = ""
|
|
469
|
+
for _, kk := range varr {
|
|
470
|
+
twClassLookup[className] += kk.(string) + ": " + vv + ";"
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
var normalizeCss = `*, ::before, ::after { box-sizing: border-box; }
|
|
478
|
+
html { -moz-tab-size: 4; -o-tab-size: 4; tab-size: 4; line-height: 1.15; -webkit-text-size-adjust: 100%; }
|
|
479
|
+
body { margin: 0; font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'; }
|
|
480
|
+
hr { height: 0; color: inherit; }
|
|
481
|
+
abbr[title] { -webkit-text-decoration: underline dotted; text-decoration: underline dotted; }
|
|
482
|
+
b, strong { font-weight: bolder; }
|
|
483
|
+
code, kbd, samp, pre { font-family: ui-monospace, SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 1em; }
|
|
484
|
+
small { font-size: 80%; }
|
|
485
|
+
sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; }
|
|
486
|
+
sub { bottom: -0.25em; }
|
|
487
|
+
sup { top: -0.5em; }
|
|
488
|
+
table { text-indent: 0; border-color: inherit; }
|
|
489
|
+
button, input, optgroup, select, textarea { font-family: inherit; font-size: 100%; line-height: 1.15; margin: 0; }
|
|
490
|
+
button, select { text-transform: none; }
|
|
491
|
+
button, [type='button'], [type='reset'], [type='submit'] { -webkit-appearance: button; }
|
|
492
|
+
::-moz-focus-inner { border-style: none; padding: 0; }
|
|
493
|
+
:-moz-focusring { outline: 1px dotted ButtonText; outline: auto; }
|
|
494
|
+
:-moz-ui-invalid { box-shadow: none; }
|
|
495
|
+
legend { padding: 0; }
|
|
496
|
+
progress { vertical-align: baseline; }
|
|
497
|
+
::-webkit-inner-spin-button, ::-webkit-outer-spin-button { height: auto; }
|
|
498
|
+
[type='search'] { -webkit-appearance: textfield; outline-offset: -2px; }
|
|
499
|
+
::-webkit-search-decoration { -webkit-appearance: none; }
|
|
500
|
+
::-webkit-file-upload-button { -webkit-appearance: button; font: inherit; }
|
|
501
|
+
summary { display: list-item; }
|
|
502
|
+
blockquote, dl, dd, h1, h2, h3, h4, h5, h6, hr, figure, p, pre { margin: 0; }
|
|
503
|
+
button { background-color: transparent; background-image: none; }
|
|
504
|
+
fieldset { margin: 0; padding: 0; }
|
|
505
|
+
ol, ul { list-style: none; margin: 0; padding: 0; }
|
|
506
|
+
html { font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; line-height: 1.5; }
|
|
507
|
+
body { font-family: inherit; line-height: inherit; }
|
|
508
|
+
*, ::before, ::after { box-sizing: border-box; border-width: 0; border-style: solid; border-color: currentColor; }
|
|
509
|
+
hr { border-top-width: 1px; }
|
|
510
|
+
img { border-style: solid; }
|
|
511
|
+
textarea { resize: vertical; }
|
|
512
|
+
input::-moz-placeholder, textarea::-moz-placeholder { opacity: 1; color: #9ca3af; }
|
|
513
|
+
input:-ms-input-placeholder, textarea:-ms-input-placeholder { opacity: 1; color: #9ca3af; }
|
|
514
|
+
input::placeholder, textarea::placeholder { opacity: 1; color: #9ca3af; }
|
|
515
|
+
button, [role="button"] { cursor: pointer; }
|
|
516
|
+
table { border-collapse: collapse; }
|
|
517
|
+
h1, h2, h3, h4, h5, h6 { font-size: inherit; font-weight: inherit; }
|
|
518
|
+
a { color: inherit; text-decoration: inherit; }
|
|
519
|
+
button, input, optgroup, select, textarea { padding: 0; line-height: inherit; color: inherit; }
|
|
520
|
+
pre, code, kbd, samp { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
|
|
521
|
+
img, svg, video, canvas, audio, iframe, embed, object { display: block; vertical-align: middle; }
|
|
522
|
+
img, video { max-width: 100%; height: auto; }
|
|
523
|
+
[hidden] { display: none; }
|
|
524
|
+
*, ::before, ::after { --tw-border-opacity: 1; border-color: rgba(229, 231, 235, var(--tw-border-opacity)); }`
|
|
525
|
+
|
|
526
|
+
func computeCss(classMap M, parent string) string {
|
|
527
|
+
p := "\n"
|
|
528
|
+
for k, v := range classMap {
|
|
529
|
+
switch it := v.(type) {
|
|
530
|
+
case string:
|
|
531
|
+
if parent != "" {
|
|
532
|
+
if k == "container" {
|
|
533
|
+
p += "." + parent
|
|
534
|
+
} else {
|
|
535
|
+
p += "." + parent + " ." + k
|
|
536
|
+
}
|
|
537
|
+
} else {
|
|
538
|
+
p += "." + k
|
|
539
|
+
}
|
|
540
|
+
p += " {\n"
|
|
541
|
+
for _, c := range strings.Split(it, " ") {
|
|
542
|
+
if s, ok := twClassLookup[c]; ok {
|
|
543
|
+
p += " " + s + "\n"
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
p += "}\n"
|
|
547
|
+
case M:
|
|
548
|
+
p += computeCss(it, k)
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return p
|
|
552
|
+
}
|
http.go
CHANGED
|
@@ -20,6 +20,7 @@ import (
|
|
|
20
20
|
"time"
|
|
21
21
|
|
|
22
22
|
"github.com/felixge/httpsnoop"
|
|
23
|
+
"github.com/go-errors/errors"
|
|
23
24
|
"github.com/go-playground/validator/v10"
|
|
24
25
|
"github.com/google/uuid"
|
|
25
26
|
"github.com/gorilla/handlers"
|
|
@@ -69,7 +70,6 @@ func init() {
|
|
|
69
70
|
PartsExclude: []string{zerolog.TimestampFieldName},
|
|
70
71
|
})
|
|
71
72
|
}
|
|
72
|
-
gsx.RegisterFunc(GetStylesUrl)
|
|
73
73
|
gsx.RegisterFunc(GetAssetUrl)
|
|
74
74
|
}
|
|
75
75
|
|
|
@@ -86,9 +86,8 @@ func RespondError(w http.ResponseWriter, status int, err error) {
|
|
|
86
86
|
}
|
|
87
87
|
if status >= 500 {
|
|
88
88
|
merror["error"] = "Internal Server Error"
|
|
89
|
-
|
|
89
|
+
sterr, _ := err.(*errors.Error)
|
|
90
|
-
println(stack)
|
|
91
|
-
log.
|
|
90
|
+
log.Error().Msg(err.Error() + "\n" + sterr.ErrorStack())
|
|
92
91
|
}
|
|
93
92
|
validationErrors, ok := err.(validator.ValidationErrors)
|
|
94
93
|
if ok {
|
|
@@ -107,15 +106,9 @@ func GetRouteParams(route string) []string {
|
|
|
107
106
|
return params
|
|
108
107
|
}
|
|
109
108
|
|
|
110
|
-
func PerformRequest(route string, h interface{},
|
|
109
|
+
func PerformRequest(route string, h interface{}, c *gsx.Context, w http.ResponseWriter, r *http.Request) {
|
|
111
110
|
params := GetRouteParams(route)
|
|
112
|
-
renderContext := gsx.NewContext(ctx, r.Header.Get("HX-Request") == "true")
|
|
113
|
-
renderContext.Set("requestId", uuid.NewString())
|
|
114
|
-
renderContext.Link("stylesheet", GetStylesUrl(), "", "")
|
|
115
|
-
renderContext.Link("icon", "/assets/favicon.ico", "image/x-icon", "image")
|
|
116
|
-
renderContext.Script("/gromer/js/htmx@1.7.0.js", false)
|
|
117
|
-
renderContext.Script("/gromer/js/alpinejs@3.9.6.js", true)
|
|
118
|
-
args := []reflect.Value{reflect.ValueOf(
|
|
111
|
+
args := []reflect.Value{reflect.ValueOf(c)}
|
|
119
112
|
funcType := reflect.TypeOf(h)
|
|
120
113
|
icount := funcType.NumIn()
|
|
121
114
|
vars := mux.Vars(r)
|
|
@@ -189,7 +182,7 @@ func PerformRequest(route string, h interface{}, ctx context.Context, w http.Res
|
|
|
189
182
|
RespondError(w, 400, fmt.Errorf("Illegal Content-Type tag found %s", contentType))
|
|
190
183
|
return
|
|
191
184
|
}
|
|
192
|
-
|
|
185
|
+
c.Set("params", instance.Elem().Interface())
|
|
193
186
|
args = append(args, instance.Elem())
|
|
194
187
|
}
|
|
195
188
|
values := reflect.ValueOf(h).Call(args)
|
|
@@ -200,25 +193,18 @@ func PerformRequest(route string, h interface{}, ctx context.Context, w http.Res
|
|
|
200
193
|
RespondError(w, responseStatus, responseError.(error))
|
|
201
194
|
return
|
|
202
195
|
}
|
|
203
|
-
if v, ok := response.(*gsx.Node); ok {
|
|
204
|
-
|
|
196
|
+
w.Header().Set("Content-Type", "text/html")
|
|
205
|
-
// This has to be at end always
|
|
206
|
-
w.WriteHeader(responseStatus)
|
|
207
|
-
v.Write(renderContext, w)
|
|
208
|
-
return
|
|
209
|
-
}
|
|
210
|
-
w.Header().Set("Content-Type", "application/json")
|
|
211
197
|
// This has to be at end always
|
|
212
198
|
w.WriteHeader(responseStatus)
|
|
213
|
-
|
|
199
|
+
response.(*gsx.Node).Write(c, w)
|
|
214
|
-
w.Write(data)
|
|
215
200
|
}
|
|
216
201
|
|
|
217
202
|
func LogMiddleware(next http.Handler) http.Handler {
|
|
218
203
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
219
204
|
defer func() {
|
|
220
205
|
if err := recover(); err != nil {
|
|
206
|
+
log.Error().Msgf("%s %d %s %s", r.Method, 599, useragent.Parse(r.UserAgent()).Name, r.URL.Path)
|
|
221
|
-
RespondError(w, 599,
|
|
207
|
+
RespondError(w, 599, errors.Errorf(fmt.Sprintf("%+v", err)))
|
|
222
208
|
}
|
|
223
209
|
}()
|
|
224
210
|
m := httpsnoop.CaptureMetrics(next, w, r)
|
|
@@ -226,10 +212,7 @@ func LogMiddleware(next http.Handler) http.Handler {
|
|
|
226
212
|
if len(ip) > 0 && ip[0] == '[' {
|
|
227
213
|
ip = ip[1 : len(ip)-1]
|
|
228
214
|
}
|
|
229
|
-
log.
|
|
215
|
+
log.Info().Msgf("%s %d %.2fkb %s %s %s", r.Method, m.Code, float64(m.Written)/1024.0, m.Duration.Round(time.Millisecond).String(),
|
|
230
|
-
m.Code,
|
|
231
|
-
float64(m.Written)/1024.0,
|
|
232
|
-
m.Duration.Round(time.Millisecond).String(),
|
|
233
216
|
useragent.Parse(r.UserAgent()).Name,
|
|
234
217
|
r.URL.Path,
|
|
235
218
|
)
|
|
@@ -273,16 +256,33 @@ func StaticRoute(router *mux.Router, path string, fs embed.FS) {
|
|
|
273
256
|
|
|
274
257
|
func StylesRoute(router *mux.Router, path string) {
|
|
275
258
|
router.Path(path).Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
259
|
+
err := r.ParseForm()
|
|
260
|
+
if err != nil {
|
|
261
|
+
RespondError(w, 400, err)
|
|
262
|
+
return
|
|
263
|
+
}
|
|
264
|
+
key := r.Form.Get("key")
|
|
276
265
|
w.Header().Set("Content-Type", "text/css")
|
|
277
266
|
w.WriteHeader(200)
|
|
278
|
-
w.Write([]byte(gsx.GetStyles()))
|
|
267
|
+
w.Write([]byte(gsx.GetStyles(key)))
|
|
279
268
|
})
|
|
280
269
|
}
|
|
281
270
|
|
|
282
|
-
func Handle(router *mux.Router, method, route string, h interface{}) {
|
|
271
|
+
func Handle(router *mux.Router, method, route string, h interface{}, meta, styles gsx.M) {
|
|
272
|
+
key := getSum(route, func() [16]byte {
|
|
273
|
+
return md5.Sum([]byte(route))
|
|
274
|
+
})
|
|
275
|
+
gsx.SetClasses(key, styles)
|
|
283
276
|
router.HandleFunc(route, func(w http.ResponseWriter, r *http.Request) {
|
|
284
|
-
|
|
277
|
+
newCtx := context.WithValue(context.WithValue(r.Context(), "url", r.URL), "header", r.Header)
|
|
278
|
+
c := gsx.NewContext(newCtx, r.Header.Get("HX-Request") == "true")
|
|
279
|
+
c.Set("requestId", uuid.NewString())
|
|
280
|
+
c.Link("stylesheet", GetStylesUrl(key), "", "")
|
|
281
|
+
c.Link("icon", "/assets/favicon.ico", "image/x-icon", "image")
|
|
282
|
+
c.Script("/gromer/js/htmx@1.7.0.js", false)
|
|
283
|
+
c.Script("/gromer/js/alpinejs@3.9.6.js", true)
|
|
284
|
+
c.Meta(meta)
|
|
285
|
-
PerformRequest(route, h,
|
|
285
|
+
PerformRequest(route, h, c, w, r)
|
|
286
286
|
}).Methods(method, "OPTIONS")
|
|
287
287
|
}
|
|
288
288
|
|
|
@@ -316,9 +316,9 @@ func GetAssetUrl(fs embed.FS, path string) string {
|
|
|
316
316
|
return fmt.Sprintf("/assets/%s?hash=%s", path, sum)
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
-
func GetStylesUrl() string {
|
|
319
|
+
func GetStylesUrl(k string) string {
|
|
320
320
|
sum := getSum("styles.css", func() [16]byte {
|
|
321
|
-
return md5.Sum([]byte(gsx.GetStyles()))
|
|
321
|
+
return md5.Sum([]byte(gsx.GetStyles(k)))
|
|
322
322
|
})
|
|
323
|
-
return fmt.Sprintf("/styles.css?hash=%s", sum)
|
|
323
|
+
return fmt.Sprintf("/styles.css?key=%s&hash=%s", k, sum)
|
|
324
324
|
}
|
utils.go
CHANGED
|
@@ -11,10 +11,6 @@ import (
|
|
|
11
11
|
"github.com/segmentio/go-camelcase"
|
|
12
12
|
)
|
|
13
13
|
|
|
14
|
-
type M map[string]interface{}
|
|
15
|
-
type MS map[string]string
|
|
16
|
-
type Arr []interface{}
|
|
17
|
-
|
|
18
14
|
var Validator = validator.New()
|
|
19
15
|
var ValidatorErrorMap = map[string]string{
|
|
20
16
|
"required": "is required",
|