~repos /gromer

#golang#htmx#ssr

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.


85ed74bf Peter John

tag: v1.0.0

v1.0.0

3 years ago
improve stuff
_example/components/Checkbox.go DELETED
@@ -1,13 +0,0 @@
1
- package components
2
-
3
- import (
4
- . "github.com/pyros2097/gromer/gsx"
5
- )
6
-
7
- // var CheckboxStyles = M{}
8
-
9
- func Checkbox(c *Context, value bool) []*Tag {
10
- return c.Render(`
11
- <input class="checkbox" type="checkbox" checked="{value}" />
12
- `)
13
- }
_example/components/Todo.go CHANGED
@@ -21,6 +21,7 @@ func Todo(c *Context, todo *todos.Todo) []*Tag {
21
21
  checked = "/icons/checked.svg?fill=green-500"
22
22
  }
23
23
  c.Set("checked", checked)
24
+ c.Styles(TodoStyles)
24
25
  return c.Render(`
25
26
  <div id="todo-{todo.ID}" class="Todo">
26
27
  <div class="row">
_example/containers/TodoList.go CHANGED
@@ -6,11 +6,10 @@ import (
6
6
  . "github.com/pyros2097/gromer/gsx"
7
7
  )
8
8
 
9
- var TodoListStyles = M{
10
- "container": "list-none",
11
- }
12
-
13
9
  func TodoList(c *Context, page int, filter string) []*Tag {
10
+ // c.Styles(M{
11
+ // "container": "list-none",
12
+ // })
14
13
  index := Default(page, 1)
15
14
  todos, err := todos.GetAllTodo(c, todos.GetAllTodoParams{
16
15
  Filter: filter,
_example/main.go CHANGED
@@ -1,10 +1,9 @@
1
- // Code generated by gromer. DO NOT EDIT.
2
1
  package main
3
2
 
4
3
  import (
5
4
  "github.com/gorilla/mux"
6
5
  "github.com/pyros2097/gromer"
7
- "github.com/pyros2097/gromer/assets"
6
+ gromer_assets "github.com/pyros2097/gromer/assets"
8
7
  "github.com/pyros2097/gromer/gsx"
9
8
  "github.com/rs/zerolog/log"
10
9
  "gocloud.dev/server"
@@ -13,37 +12,32 @@ import (
13
12
  "github.com/pyros2097/gromer/_example/components"
14
13
  "github.com/pyros2097/gromer/_example/containers"
15
14
  "github.com/pyros2097/gromer/_example/routes"
16
- "github.com/pyros2097/gromer/_example/routes/about"
17
-
18
15
  )
19
16
 
20
17
  func init() {
21
18
  gsx.RegisterComponent(components.Todo, components.TodoStyles, "todo")
22
- gsx.RegisterComponent(components.Checkbox, nil, "value")
23
19
  gsx.RegisterComponent(components.Status, components.StatusStyles, "status", "error")
24
20
  gsx.RegisterComponent(containers.TodoCount, nil, "filter")
25
- gsx.RegisterComponent(containers.TodoList, containers.TodoListStyles, "page", "filter")
21
+ gsx.RegisterComponent(containers.TodoList, nil, "page", "filter")
26
22
  }
27
23
 
28
24
  func main() {
29
25
  baseRouter := mux.NewRouter()
30
26
  baseRouter.Use(gromer.LogMiddleware)
31
- gromer.RegisterStatusHandler(baseRouter, components.Status, components.StatusStyles)
27
+ gromer.RegisterStatusHandler(baseRouter, components.Status)
32
-
28
+
33
29
  staticRouter := baseRouter.NewRoute().Subrouter()
34
30
  staticRouter.Use(gromer.CacheMiddleware)
35
31
  staticRouter.Use(gromer.CompressMiddleware)
36
32
  gromer.StaticRoute(staticRouter, "/gromer/", gromer_assets.FS)
37
33
  gromer.StaticRoute(staticRouter, "/assets/", assets.FS)
38
34
  gromer.IconsRoute(staticRouter, "/icons/", assets.FS)
39
- gromer.PageStylesRoute(staticRouter, "/styles.css")
40
35
  gromer.ComponentStylesRoute(staticRouter, "/components.css")
41
36
 
42
37
  pageRouter := baseRouter.NewRoute().Subrouter()
43
- gromer.Handle(pageRouter, "GET", "/", routes.GET, routes.Meta, routes.Styles)
38
+ gromer.PageRoute(pageRouter, "/", routes.TodosPage, routes.TodosAction)
44
- gromer.Handle(pageRouter, "POST", "/", routes.POST, routes.Meta, routes.Styles)
45
- gromer.Handle(pageRouter, "GET", "/about", about.GET, about.Meta, about.Styles)
39
+ gromer.PageRoute(pageRouter, "/about", routes.AboutPage, nil)
46
-
40
+
47
41
  log.Info().Msg("http server listening on http://localhost:3000")
48
42
  srv := server.New(baseRouter, nil)
49
43
  if err := srv.ListenAndServe(":3000"); err != nil {
_example/routes/about.go ADDED
@@ -0,0 +1,21 @@
1
+ package routes
2
+
3
+ import (
4
+ . "github.com/pyros2097/gromer/gsx"
5
+ )
6
+
7
+ func AboutPage(c *Context) ([]*Tag, int, error) {
8
+ c.Meta(M{
9
+ "title": "About Gromer",
10
+ "description": "About Gromer",
11
+ })
12
+ c.Styles(M{
13
+ "container": "flex flex-col justify-center items-center",
14
+ })
15
+ return c.Render(`
16
+ <div class="About">
17
+ A new link is here
18
+ <h1>About Me</h1>
19
+ </div>
20
+ `), 200, nil
21
+ }
_example/routes/about/get.go DELETED
@@ -1,22 +0,0 @@
1
- package about
2
-
3
- import (
4
- . "github.com/pyros2097/gromer/gsx"
5
- )
6
-
7
- var (
8
- Meta = M{
9
- "title": "About Gromer",
10
- "description": "About Gromer",
11
- }
12
- Styles = M{}
13
- )
14
-
15
- func GET(c *Context) ([]*Tag, int, error) {
16
- return c.Render(`
17
- <div class="flex flex-col justify-center items-center">
18
- A new link is here
19
- P<h1>About Me</h1>
20
- </div>
21
- `), 200, nil
22
- }
_example/routes/get.go DELETED
@@ -1,127 +0,0 @@
1
- package routes
2
-
3
- import (
4
- _ "github.com/pyros2097/gromer/_example/components"
5
- . "github.com/pyros2097/gromer/gsx"
6
- )
7
-
8
- var (
9
- Meta = M{
10
- "title": "Gromer Todos",
11
- "description": "Gromer Todos",
12
- "author": "gromer",
13
- "keywords": "gromer",
14
- }
15
-
16
- Styles = M{
17
- "bg": "bg-gray-50 min-h-screen font-sans",
18
- "container": "container mx-auto flex flex-col items-center",
19
- "title": "text-opacity-20 text-red-900 text-8xl text-center",
20
- "main": M{
21
- "container": "mt-8 shadow-xl w-full max-w-prose bg-white",
22
- "input-box": "flex flex-row text-2xl h-16",
23
- "button": "ml-4 w-8 disabled",
24
- "input-form": "flex flex-1",
25
- "input": "flex-1 min-w-0 p-2 placeholder:text-gray-300",
26
- },
27
- "bottom": M{
28
- "container": "flex flex-row items-center flex-wrap sm:flex-nowrap p-2 font-light border-t-2 border-gray-100",
29
- "row": "flex-1 flex flex-row",
30
- "section-1": "flex-1 flex flex-row order-1 justify-start",
31
- "section-2": "flex-1 flex flex-row order-2 sm:order-3 justify-end",
32
- "section-3": "flex-1 flex flex-row order-3 sm:order-2 min-w-full sm:min-w-min justify-center",
33
- "link": "rounded border px-1 mx-2 hover:border-red-100",
34
- "active": "border-red-900",
35
- "clear": "font-light hover:underline",
36
- "disabled": "invisible disabled",
37
- },
38
- "footer": M{
39
- "container": "mt-16 p-4 flex flex-col",
40
- "link": "hover:underline",
41
- "subtitle": "m-0.5 text-xs text-center text-gray-500",
42
- },
43
- }
44
- )
45
-
46
- type GetParams struct {
47
- Page int `json:"page"`
48
- Filter string `json:"filter"`
49
- }
50
-
51
- func getActive(v bool) string {
52
- if v {
53
- return "active"
54
- }
55
- return ""
56
- }
57
-
58
- func GET(c *Context, params GetParams) ([]*Tag, int, error) {
59
- allClass := getActive(params.Filter == "all")
60
- activeClass := getActive(params.Filter == "active")
61
- completedClass := getActive(params.Filter == "completed")
62
- c.Set("allClass", allClass)
63
- c.Set("activeClass", activeClass)
64
- c.Set("completedClass", completedClass)
65
- return c.Render(`
66
- <div id="bg" class="bg">
67
- <div class="container">
68
- <header>
69
- <h1 class="title">"todos"</h1>
70
- </header>
71
- <main class="main">
72
- <div class="input-box">
73
- <form hx-target="#todo-list" hx-post="/">
74
- <input type="hidden" name="intent" value="select_all" />
75
- <button id="check-all" class="button" hx-swap-oob="true">
76
- <img src="/icons/check-all.svg?fill=gray-400" />
77
- </button>
78
- </form>
79
- <form class="input-form" hx-post="/" hx-target="#todo-list" hx-swap="afterbegin" _="on htmx:afterOnLoad set #text.value to ''">
80
- <input type="hidden" name="intent" value="create" />
81
- <input id="text" name="text" class="input" placeholder="What needs to be done?" autocomplete="off" />
82
- </form>
83
- </div>
84
- <TodoList id="todo-list" page={params.Page} filter={params.Filter} />
85
- <div class="bottom">
86
- <div class="section-1">
87
- <TodoCount filter={params.Filter} />
88
- </div>
89
- <ul class="section-2" hx-boost="true">
90
- <li>
91
- <a href="?filter=all" class="link {allClass}">"All"</a>
92
- </li>
93
- <li>
94
- <a href="?filter=active" class="link {activeClass}">"Active"</a>
95
- </li>
96
- <li>
97
- <a href="?filter=completed" class="link {completedClass}">"Completed"</a>
98
- </li>
99
- </ul>
100
- <div class="section-3">
101
- <form hx-target="#todo-list" hx-post="/">
102
- <input type="hidden" name="intent" value="clear_completed" />
103
- <button type="submit" class="bottom-clear">"Clear completed"</button>
104
- </form>
105
- </div>
106
- </div>
107
- </main>
108
- <div id="error">
109
- </div>
110
- <footer class="footer">
111
- <span class="subtitle">"Written by "
112
- <a class="link" href="https://github.com/pyrossh/">"pyrossh"</a>
113
- </span>
114
- <span class="subtitle">"using "
115
- <a class="link" href="https://github.com/pyrossh/gromer">"Gromer"</a>
116
- </span>
117
- <span class="subtitle">"thanks to"
118
- <a class="link" href="https://github.com/wishawa/">"Wisha Wa"</a>
119
- </span>
120
- <span class="subtitle">"according to the spec "
121
- <a class="link" href="https://todomvc.com/">"TodoMVC"</a>
122
- </span>
123
- </footer>
124
- </div>
125
- </div>
126
- `), 200, nil
127
- }
_example/routes/post.go DELETED
@@ -1,96 +0,0 @@
1
- package routes
2
-
3
- import (
4
- _ "github.com/pyros2097/gromer/_example/components"
5
- "github.com/pyros2097/gromer/_example/services/todos"
6
- . "github.com/pyros2097/gromer/gsx"
7
- "github.com/rotisserie/eris"
8
- )
9
-
10
- type PostParams struct {
11
- Intent string `json:"intent"`
12
- ID string `json:"id"`
13
- Text string `json:"text"`
14
- }
15
-
16
- func POST(c *Context, params PostParams) ([]*Tag, int, error) {
17
- if params.Intent == "select_all" {
18
- allTodos, err := todos.GetAllTodo(c, todos.GetAllTodoParams{
19
- Filter: "all",
20
- Limit: 1000,
21
- })
22
- if err != nil {
23
- return nil, 500, err
24
- }
25
- for _, t := range allTodos {
26
- _, err := todos.UpdateTodo(c, t.ID, todos.UpdateTodoParams{
27
- Text: t.Text,
28
- Completed: true,
29
- })
30
- if err != nil {
31
- return nil, 500, err
32
- }
33
- }
34
- return c.Render(`
35
- <TodoCount filter="all" page="1" />
36
- <button id="check-all" class="button" hx-swap-oob="true">
37
- <img src="/icons/check-all.svg?fill=green-500" />
38
- </button>
39
- <TodoList id="todo-list" filter="all" page="1" />
40
- `), 200, nil
41
- } else if params.Intent == "clear_completed" {
42
- allTodos, err := todos.GetAllTodo(c, todos.GetAllTodoParams{
43
- Filter: "all",
44
- Limit: 1000,
45
- })
46
- if err != nil {
47
- return nil, 500, err
48
- }
49
- for _, t := range allTodos {
50
- if t.Completed {
51
- _, err := todos.DeleteTodo(c, t.ID)
52
- if err != nil {
53
- return nil, 500, err
54
- }
55
- }
56
- }
57
- return c.Render(`
58
- <TodoCount filter="all" page="1" />
59
- <TodoList id="todo-list" filter="all" page="1" />
60
- `), 200, nil
61
- } else if params.Intent == "create" {
62
- todo, err := todos.CreateTodo(c, params.Text)
63
- if err != nil {
64
- return nil, 500, err
65
- }
66
- c.Set("todo", todo)
67
- return c.Render(`
68
- <TodoCount filter="all" page="1" />
69
- <Todo />
70
- `), 200, nil
71
- } else if params.Intent == "delete" {
72
- _, err := todos.DeleteTodo(c, params.ID)
73
- if err != nil {
74
- return nil, 500, err
75
- }
76
- return nil, 200, nil
77
- } else if params.Intent == "complete" {
78
- todo, err := todos.GetTodo(c, params.ID)
79
- if err != nil {
80
- return nil, 500, err
81
- }
82
- _, err = todos.UpdateTodo(c, params.ID, todos.UpdateTodoParams{
83
- Text: todo.Text,
84
- Completed: !todo.Completed,
85
- })
86
- if err != nil {
87
- return nil, 500, err
88
- }
89
- c.Set("todo", todo)
90
- return c.Render(`
91
- <TodoCount filter="all" page="1" />
92
- <Todo />
93
- `), 200, nil
94
- }
95
- return nil, 404, eris.Errorf("Intent not specified: %s", params.Intent)
96
- }
_example/routes/todos.go ADDED
@@ -0,0 +1,215 @@
1
+ package routes
2
+
3
+ import (
4
+ _ "github.com/pyros2097/gromer/_example/components"
5
+ "github.com/pyros2097/gromer/_example/services/todos"
6
+ . "github.com/pyros2097/gromer/gsx"
7
+ "github.com/rotisserie/eris"
8
+ )
9
+
10
+ var TodoStyles = M{
11
+ "bg": "bg-gray-50 min-h-screen font-sans",
12
+ "container": "container mx-auto flex flex-col items-center",
13
+ "title": "text-opacity-20 text-red-900 text-8xl text-center",
14
+ "main": M{
15
+ "container": "mt-8 shadow-xl w-full max-w-prose bg-white",
16
+ "input-box": "flex flex-row text-2xl h-16",
17
+ "button": "ml-4 w-8 disabled",
18
+ "input-form": "flex flex-1",
19
+ "input": "flex-1 min-w-0 p-2 placeholder:text-gray-300",
20
+ },
21
+ "bottom": M{
22
+ "container": "flex flex-row items-center flex-wrap sm:flex-nowrap p-2 font-light border-t-2 border-gray-100",
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",
28
+ "active": "border-red-900",
29
+ "clear": "font-light hover:underline",
30
+ "disabled": "invisible disabled",
31
+ },
32
+ "footer": M{
33
+ "container": "mt-16 p-4 flex flex-col",
34
+ "link": "hover:underline",
35
+ "subtitle": "m-0.5 text-xs text-center text-gray-500",
36
+ },
37
+ }
38
+
39
+ type TodosPageParams struct {
40
+ Page int `json:"page"`
41
+ Filter string `json:"filter"`
42
+ }
43
+
44
+ func getActive(v bool) string {
45
+ if v {
46
+ return "active"
47
+ }
48
+ return ""
49
+ }
50
+
51
+ func TodosPage(c *Context, params TodosPageParams) ([]*Tag, int, error) {
52
+ allClass := getActive(params.Filter == "all")
53
+ activeClass := getActive(params.Filter == "active")
54
+ completedClass := getActive(params.Filter == "completed")
55
+ c.Set("allClass", allClass)
56
+ c.Set("activeClass", activeClass)
57
+ c.Set("completedClass", completedClass)
58
+ c.Meta(M{
59
+ "title": "Gromer Todos",
60
+ "description": "Gromer Todos",
61
+ "author": "gromer",
62
+ "keywords": "gromer",
63
+ })
64
+ c.Styles(TodoStyles)
65
+ return c.Render(`
66
+ <div id="bg" class="bg">
67
+ <div class="container">
68
+ <header>
69
+ <h1 class="title">"todos"</h1>
70
+ </header>
71
+ <main class="main">
72
+ <div class="input-box">
73
+ <form hx-target="#todo-list" hx-post="/">
74
+ <input type="hidden" name="intent" value="select_all" />
75
+ <button id="check-all" class="button" hx-swap-oob="true">
76
+ <img src="/icons/check-all.svg?fill=gray-400" />
77
+ </button>
78
+ </form>
79
+ <form class="input-form" hx-post="/" hx-target="#todo-list" hx-swap="afterbegin" _="on htmx:afterOnLoad set #text.value to ''">
80
+ <input type="hidden" name="intent" value="create" />
81
+ <input id="text" name="text" class="input" placeholder="What needs to be done?" autocomplete="off" />
82
+ </form>
83
+ </div>
84
+ <TodoList id="todo-list" page={params.Page} filter={params.Filter} />
85
+ <div class="bottom">
86
+ <div class="section-1">
87
+ <TodoCount filter={params.Filter} />
88
+ </div>
89
+ <ul class="section-2" hx-boost="true">
90
+ <li>
91
+ <a href="?filter=all" class="link {allClass}">"All"</a>
92
+ </li>
93
+ <li>
94
+ <a href="?filter=active" class="link {activeClass}">"Active"</a>
95
+ </li>
96
+ <li>
97
+ <a href="?filter=completed" class="link {completedClass}">"Completed"</a>
98
+ </li>
99
+ </ul>
100
+ <div class="section-3">
101
+ <form hx-target="#todo-list" hx-post="/">
102
+ <input type="hidden" name="intent" value="clear_completed" />
103
+ <button type="submit" class="bottom-clear">"Clear completed"</button>
104
+ </form>
105
+ </div>
106
+ </div>
107
+ </main>
108
+ <div id="error">
109
+ </div>
110
+ <footer class="footer">
111
+ <span class="subtitle">"Written by "
112
+ <a class="link" href="https://github.com/pyrossh/">"pyrossh"</a>
113
+ </span>
114
+ <span class="subtitle">"using "
115
+ <a class="link" href="https://github.com/pyrossh/gromer">"Gromer"</a>
116
+ </span>
117
+ <span class="subtitle">"thanks to"
118
+ <a class="link" href="https://github.com/wishawa/">"Wisha Wa"</a>
119
+ </span>
120
+ <span class="subtitle">"according to the spec "
121
+ <a class="link" href="https://todomvc.com/">"TodoMVC"</a>
122
+ </span>
123
+ </footer>
124
+ </div>
125
+ </div>
126
+ `), 200, nil
127
+ }
128
+
129
+ type TodosActionParams struct {
130
+ Intent string `json:"intent"`
131
+ ID string `json:"id"`
132
+ Text string `json:"text"`
133
+ }
134
+
135
+ func TodosAction(c *Context, params TodosActionParams) ([]*Tag, int, error) {
136
+ if params.Intent == "select_all" {
137
+ allTodos, err := todos.GetAllTodo(c, todos.GetAllTodoParams{
138
+ Filter: "all",
139
+ Limit: 1000,
140
+ })
141
+ if err != nil {
142
+ return nil, 500, err
143
+ }
144
+ for _, t := range allTodos {
145
+ _, err := todos.UpdateTodo(c, t.ID, todos.UpdateTodoParams{
146
+ Text: t.Text,
147
+ Completed: true,
148
+ })
149
+ if err != nil {
150
+ return nil, 500, err
151
+ }
152
+ }
153
+ return c.Render(`
154
+ <TodoCount filter="all" page="1" />
155
+ <button id="check-all" class="button" hx-swap-oob="true">
156
+ <img src="/icons/check-all.svg?fill=green-500" />
157
+ </button>
158
+ <TodoList id="todo-list" filter="all" page="1" />
159
+ `), 200, nil
160
+ } else if params.Intent == "clear_completed" {
161
+ allTodos, err := todos.GetAllTodo(c, todos.GetAllTodoParams{
162
+ Filter: "all",
163
+ Limit: 1000,
164
+ })
165
+ if err != nil {
166
+ return nil, 500, err
167
+ }
168
+ for _, t := range allTodos {
169
+ if t.Completed {
170
+ _, err := todos.DeleteTodo(c, t.ID)
171
+ if err != nil {
172
+ return nil, 500, err
173
+ }
174
+ }
175
+ }
176
+ return c.Render(`
177
+ <TodoCount filter="all" page="1" />
178
+ <TodoList id="todo-list" filter="all" page="1" />
179
+ `), 200, nil
180
+ } else if params.Intent == "create" {
181
+ todo, err := todos.CreateTodo(c, params.Text)
182
+ if err != nil {
183
+ return nil, 500, err
184
+ }
185
+ c.Set("todo", todo)
186
+ return c.Render(`
187
+ <TodoCount filter="all" page="1" />
188
+ <Todo />
189
+ `), 200, nil
190
+ } else if params.Intent == "delete" {
191
+ _, err := todos.DeleteTodo(c, params.ID)
192
+ if err != nil {
193
+ return nil, 500, err
194
+ }
195
+ return nil, 200, nil
196
+ } else if params.Intent == "complete" {
197
+ todo, err := todos.GetTodo(c, params.ID)
198
+ if err != nil {
199
+ return nil, 500, err
200
+ }
201
+ _, err = todos.UpdateTodo(c, params.ID, todos.UpdateTodoParams{
202
+ Text: todo.Text,
203
+ Completed: !todo.Completed,
204
+ })
205
+ if err != nil {
206
+ return nil, 500, err
207
+ }
208
+ c.Set("todo", todo)
209
+ return c.Render(`
210
+ <TodoCount filter="all" page="1" />
211
+ <Todo />
212
+ `), 200, nil
213
+ }
214
+ return nil, 404, eris.Errorf("Intent not specified: %s", params.Intent)
215
+ }
assets/css/codemirror@5.63.1.css DELETED
@@ -1,349 +0,0 @@
1
- /* BASICS */
2
-
3
- .CodeMirror {
4
- /* Set height, width, borders, and global font properties here */
5
- font-family: monospace;
6
- height: 300px;
7
- color: black;
8
- direction: ltr;
9
- }
10
-
11
- /* PADDING */
12
-
13
- .CodeMirror-lines {
14
- padding: 4px 0; /* Vertical padding around content */
15
- }
16
- .CodeMirror pre.CodeMirror-line,
17
- .CodeMirror pre.CodeMirror-line-like {
18
- padding: 0 4px; /* Horizontal padding of content */
19
- }
20
-
21
- .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
22
- background-color: white; /* The little square between H and V scrollbars */
23
- }
24
-
25
- /* GUTTER */
26
-
27
- .CodeMirror-gutters {
28
- border-right: 1px solid #ddd;
29
- background-color: #f7f7f7;
30
- white-space: nowrap;
31
- }
32
- .CodeMirror-linenumbers {}
33
- .CodeMirror-linenumber {
34
- padding: 0 3px 0 5px;
35
- min-width: 20px;
36
- text-align: right;
37
- color: #999;
38
- white-space: nowrap;
39
- }
40
-
41
- .CodeMirror-guttermarker { color: black; }
42
- .CodeMirror-guttermarker-subtle { color: #999; }
43
-
44
- /* CURSOR */
45
-
46
- .CodeMirror-cursor {
47
- border-left: 1px solid black;
48
- border-right: none;
49
- width: 0;
50
- }
51
- /* Shown when moving in bi-directional text */
52
- .CodeMirror div.CodeMirror-secondarycursor {
53
- border-left: 1px solid silver;
54
- }
55
- .cm-fat-cursor .CodeMirror-cursor {
56
- width: auto;
57
- border: 0 !important;
58
- background: #7e7;
59
- }
60
- .cm-fat-cursor div.CodeMirror-cursors {
61
- z-index: 1;
62
- }
63
- .cm-fat-cursor-mark {
64
- background-color: rgba(20, 255, 20, 0.5);
65
- -webkit-animation: blink 1.06s steps(1) infinite;
66
- -moz-animation: blink 1.06s steps(1) infinite;
67
- animation: blink 1.06s steps(1) infinite;
68
- }
69
- .cm-animate-fat-cursor {
70
- width: auto;
71
- -webkit-animation: blink 1.06s steps(1) infinite;
72
- -moz-animation: blink 1.06s steps(1) infinite;
73
- animation: blink 1.06s steps(1) infinite;
74
- background-color: #7e7;
75
- }
76
- @-moz-keyframes blink {
77
- 0% {}
78
- 50% { background-color: transparent; }
79
- 100% {}
80
- }
81
- @-webkit-keyframes blink {
82
- 0% {}
83
- 50% { background-color: transparent; }
84
- 100% {}
85
- }
86
- @keyframes blink {
87
- 0% {}
88
- 50% { background-color: transparent; }
89
- 100% {}
90
- }
91
-
92
- /* Can style cursor different in overwrite (non-insert) mode */
93
- .CodeMirror-overwrite .CodeMirror-cursor {}
94
-
95
- .cm-tab { display: inline-block; text-decoration: inherit; }
96
-
97
- .CodeMirror-rulers {
98
- position: absolute;
99
- left: 0; right: 0; top: -50px; bottom: 0;
100
- overflow: hidden;
101
- }
102
- .CodeMirror-ruler {
103
- border-left: 1px solid #ccc;
104
- top: 0; bottom: 0;
105
- position: absolute;
106
- }
107
-
108
- /* DEFAULT THEME */
109
-
110
- .cm-s-default .cm-header {color: blue;}
111
- .cm-s-default .cm-quote {color: #090;}
112
- .cm-negative {color: #d44;}
113
- .cm-positive {color: #292;}
114
- .cm-header, .cm-strong {font-weight: bold;}
115
- .cm-em {font-style: italic;}
116
- .cm-link {text-decoration: underline;}
117
- .cm-strikethrough {text-decoration: line-through;}
118
-
119
- .cm-s-default .cm-keyword {color: #708;}
120
- .cm-s-default .cm-atom {color: #219;}
121
- .cm-s-default .cm-number {color: #164;}
122
- .cm-s-default .cm-def {color: #00f;}
123
- .cm-s-default .cm-variable,
124
- .cm-s-default .cm-punctuation,
125
- .cm-s-default .cm-property,
126
- .cm-s-default .cm-operator {}
127
- .cm-s-default .cm-variable-2 {color: #05a;}
128
- .cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
129
- .cm-s-default .cm-comment {color: #a50;}
130
- .cm-s-default .cm-string {color: #a11;}
131
- .cm-s-default .cm-string-2 {color: #f50;}
132
- .cm-s-default .cm-meta {color: #555;}
133
- .cm-s-default .cm-qualifier {color: #555;}
134
- .cm-s-default .cm-builtin {color: #30a;}
135
- .cm-s-default .cm-bracket {color: #997;}
136
- .cm-s-default .cm-tag {color: #170;}
137
- .cm-s-default .cm-attribute {color: #00c;}
138
- .cm-s-default .cm-hr {color: #999;}
139
- .cm-s-default .cm-link {color: #00c;}
140
-
141
- .cm-s-default .cm-error {color: #f00;}
142
- .cm-invalidchar {color: #f00;}
143
-
144
- .CodeMirror-composing { border-bottom: 2px solid; }
145
-
146
- /* Default styles for common addons */
147
-
148
- div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
149
- div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
150
- .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
151
- .CodeMirror-activeline-background {background: #e8f2ff;}
152
-
153
- /* STOP */
154
-
155
- /* The rest of this file contains styles related to the mechanics of
156
- the editor. You probably shouldn't touch them. */
157
-
158
- .CodeMirror {
159
- position: relative;
160
- overflow: hidden;
161
- background: white;
162
- }
163
-
164
- .CodeMirror-scroll {
165
- overflow: scroll !important; /* Things will break if this is overridden */
166
- /* 50px is the magic margin used to hide the element's real scrollbars */
167
- /* See overflow: hidden in .CodeMirror */
168
- margin-bottom: -50px; margin-right: -50px;
169
- padding-bottom: 50px;
170
- height: 100%;
171
- outline: none; /* Prevent dragging from highlighting the element */
172
- position: relative;
173
- }
174
- .CodeMirror-sizer {
175
- position: relative;
176
- border-right: 50px solid transparent;
177
- }
178
-
179
- /* The fake, visible scrollbars. Used to force redraw during scrolling
180
- before actual scrolling happens, thus preventing shaking and
181
- flickering artifacts. */
182
- .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
183
- position: absolute;
184
- z-index: 6;
185
- display: none;
186
- outline: none;
187
- }
188
- .CodeMirror-vscrollbar {
189
- right: 0; top: 0;
190
- overflow-x: hidden;
191
- overflow-y: scroll;
192
- }
193
- .CodeMirror-hscrollbar {
194
- bottom: 0; left: 0;
195
- overflow-y: hidden;
196
- overflow-x: scroll;
197
- }
198
- .CodeMirror-scrollbar-filler {
199
- right: 0; bottom: 0;
200
- }
201
- .CodeMirror-gutter-filler {
202
- left: 0; bottom: 0;
203
- }
204
-
205
- .CodeMirror-gutters {
206
- position: absolute; left: 0; top: 0;
207
- min-height: 100%;
208
- z-index: 3;
209
- }
210
- .CodeMirror-gutter {
211
- white-space: normal;
212
- height: 100%;
213
- display: inline-block;
214
- vertical-align: top;
215
- margin-bottom: -50px;
216
- }
217
- .CodeMirror-gutter-wrapper {
218
- position: absolute;
219
- z-index: 4;
220
- background: none !important;
221
- border: none !important;
222
- }
223
- .CodeMirror-gutter-background {
224
- position: absolute;
225
- top: 0; bottom: 0;
226
- z-index: 4;
227
- }
228
- .CodeMirror-gutter-elt {
229
- position: absolute;
230
- cursor: default;
231
- z-index: 4;
232
- }
233
- .CodeMirror-gutter-wrapper ::selection { background-color: transparent }
234
- .CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
235
-
236
- .CodeMirror-lines {
237
- cursor: text;
238
- min-height: 1px; /* prevents collapsing before first draw */
239
- }
240
- .CodeMirror pre.CodeMirror-line,
241
- .CodeMirror pre.CodeMirror-line-like {
242
- /* Reset some styles that the rest of the page might have set */
243
- -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
244
- border-width: 0;
245
- background: transparent;
246
- font-family: inherit;
247
- font-size: inherit;
248
- margin: 0;
249
- white-space: pre;
250
- word-wrap: normal;
251
- line-height: inherit;
252
- color: inherit;
253
- z-index: 2;
254
- position: relative;
255
- overflow: visible;
256
- -webkit-tap-highlight-color: transparent;
257
- -webkit-font-variant-ligatures: contextual;
258
- font-variant-ligatures: contextual;
259
- }
260
- .CodeMirror-wrap pre.CodeMirror-line,
261
- .CodeMirror-wrap pre.CodeMirror-line-like {
262
- word-wrap: break-word;
263
- white-space: pre-wrap;
264
- word-break: normal;
265
- }
266
-
267
- .CodeMirror-linebackground {
268
- position: absolute;
269
- left: 0; right: 0; top: 0; bottom: 0;
270
- z-index: 0;
271
- }
272
-
273
- .CodeMirror-linewidget {
274
- position: relative;
275
- z-index: 2;
276
- padding: 0.1px; /* Force widget margins to stay inside of the container */
277
- }
278
-
279
- .CodeMirror-widget {}
280
-
281
- .CodeMirror-rtl pre { direction: rtl; }
282
-
283
- .CodeMirror-code {
284
- outline: none;
285
- }
286
-
287
- /* Force content-box sizing for the elements where we expect it */
288
- .CodeMirror-scroll,
289
- .CodeMirror-sizer,
290
- .CodeMirror-gutter,
291
- .CodeMirror-gutters,
292
- .CodeMirror-linenumber {
293
- -moz-box-sizing: content-box;
294
- box-sizing: content-box;
295
- }
296
-
297
- .CodeMirror-measure {
298
- position: absolute;
299
- width: 100%;
300
- height: 0;
301
- overflow: hidden;
302
- visibility: hidden;
303
- }
304
-
305
- .CodeMirror-cursor {
306
- position: absolute;
307
- pointer-events: none;
308
- }
309
- .CodeMirror-measure pre { position: static; }
310
-
311
- div.CodeMirror-cursors {
312
- visibility: hidden;
313
- position: relative;
314
- z-index: 3;
315
- }
316
- div.CodeMirror-dragcursors {
317
- visibility: visible;
318
- }
319
-
320
- .CodeMirror-focused div.CodeMirror-cursors {
321
- visibility: visible;
322
- }
323
-
324
- .CodeMirror-selected { background: #d9d9d9; }
325
- .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
326
- .CodeMirror-crosshair { cursor: crosshair; }
327
- .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
328
- .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
329
-
330
- .cm-searching {
331
- background-color: #ffa;
332
- background-color: rgba(255, 255, 0, .4);
333
- }
334
-
335
- /* Used to force a border model for a node */
336
- .cm-force-border { padding-right: .1px; }
337
-
338
- @media print {
339
- /* Hide the cursor when printing */
340
- .CodeMirror div.CodeMirror-cursors {
341
- visibility: hidden;
342
- }
343
- }
344
-
345
- /* See issue #2901 */
346
- .cm-tab-wrap-hack:after { content: ''; }
347
-
348
- /* Help users use markselection to safely style text background */
349
- span.CodeMirror-selectedtext { background: none; }
assets/css/normalize@3.0.0.css ADDED
@@ -0,0 +1,49 @@
1
+ *, ::before, ::after { box-sizing: border-box; }
2
+ html { -moz-tab-size: 4; -o-tab-size: 4; tab-size: 4; line-height: 1.15; -webkit-text-size-adjust: 100%; }
3
+ body { margin: 0; font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'; }
4
+ hr { height: 0; color: inherit; }
5
+ abbr[title] { -webkit-text-decoration: underline dotted; text-decoration: underline dotted; }
6
+ b, strong { font-weight: bolder; }
7
+ code, kbd, samp, pre { font-family: ui-monospace, SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 1em; }
8
+ small { font-size: 80%; }
9
+ sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; }
10
+ sub { bottom: -0.25em; }
11
+ sup { top: -0.5em; }
12
+ table { text-indent: 0; border-color: inherit; }
13
+ button, input, optgroup, select, textarea { font-family: inherit; font-size: 100%; line-height: 1.15; margin: 0; }
14
+ button, select { text-transform: none; }
15
+ button, [type='button'], [type='reset'], [type='submit'] { -webkit-appearance: button; }
16
+ ::-moz-focus-inner { border-style: none; padding: 0; }
17
+ :-moz-focusring { outline: 1px dotted ButtonText; outline: auto; }
18
+ :-moz-ui-invalid { box-shadow: none; }
19
+ legend { padding: 0; }
20
+ progress { vertical-align: baseline; }
21
+ ::-webkit-inner-spin-button, ::-webkit-outer-spin-button { height: auto; }
22
+ [type='search'] { -webkit-appearance: textfield; outline-offset: -2px; }
23
+ ::-webkit-search-decoration { -webkit-appearance: none; }
24
+ ::-webkit-file-upload-button { -webkit-appearance: button; font: inherit; }
25
+ summary { display: list-item; }
26
+ blockquote, dl, dd, h1, h2, h3, h4, h5, h6, hr, figure, p, pre { margin: 0; }
27
+ button { background-color: transparent; background-image: none; }
28
+ fieldset { margin: 0; padding: 0; }
29
+ ol, ul { list-style: none; margin: 0; padding: 0; }
30
+ 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; }
31
+ body { font-family: inherit; line-height: inherit; }
32
+ *, ::before, ::after { box-sizing: border-box; border-width: 0; border-style: solid; border-color: currentColor; }
33
+ hr { border-top-width: 1px; }
34
+ img { border-style: solid; }
35
+ textarea { resize: vertical; }
36
+ input::-moz-placeholder, textarea::-moz-placeholder { opacity: 1; color: #9ca3af; }
37
+ input:-ms-input-placeholder, textarea:-ms-input-placeholder { opacity: 1; color: #9ca3af; }
38
+ input::placeholder, textarea::placeholder { opacity: 1; color: #9ca3af; }
39
+ button, [role="button"] { cursor: pointer; }
40
+ table { border-collapse: collapse; }
41
+ h1, h2, h3, h4, h5, h6 { font-size: inherit; font-weight: inherit; }
42
+ a { color: inherit; text-decoration: inherit; }
43
+ button, input, optgroup, select, textarea { padding: 0; line-height: inherit; color: inherit; }
44
+ pre, code, kbd, samp { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
45
+ img, svg, video, canvas, audio, iframe, embed, object { display: block; vertical-align: middle; }
46
+ img, video { max-width: 100%; height: auto; }
47
+ [hidden] { display: none; }
48
+ *, ::before, ::after { --tw-border-opacity: 1; border-color: rgba(229, 231, 235, var(--tw-border-opacity)); }
49
+ form { display: flex; }
assets/css/styles.css DELETED
@@ -1,540 +0,0 @@
1
- html,
2
- body {
3
- height: 100vh;
4
- }
5
-
6
- #left .CodeMirror {
7
- height: 400px;
8
- }
9
-
10
- #right .CodeMirror {
11
- height: calc(100vh - 60px);
12
- }
13
-
14
- .form-select {
15
- -webkit-appearance: none;
16
- -moz-appearance: none;
17
- appearance: none;
18
- -webkit-print-color-adjust: exact;
19
- color-adjust: exact;
20
- background-repeat: no-repeat;
21
- background-color: #fff;
22
- border-color: #e2e8f0;
23
- border-width: 1px;
24
- border-radius: 0.25rem;
25
- padding-top: 0.5rem;
26
- padding-right: 2.5rem;
27
- padding-bottom: 0.5rem;
28
- padding-left: 0.75rem;
29
- font-size: 1rem;
30
- line-height: 1.5;
31
- background-position: right 0.5rem center;
32
- background-size: 1.5em 1.5em;
33
- }
34
-
35
- .form-select:focus {
36
- outline: none;
37
- box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5);
38
- border-color: #63b3ed;
39
- }
40
-
41
- table {
42
- width: 100%;
43
- }
44
-
45
- tr {
46
- width: 100%;
47
- }
48
-
49
- td,
50
- th {
51
- border-bottom: 1px solid rgb(204, 204, 204);
52
- border-left: 1px solid rgb(204, 204, 204);
53
- text-align: left;
54
- }
55
-
56
- textarea:focus,
57
- input:focus {
58
- outline: none;
59
- box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5);
60
- border-color: #63b3ed;
61
- }
62
-
63
- *:focus {
64
- outline: none;
65
- box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5);
66
- border-color: #63b3ed;
67
- }
68
-
69
- .spinner {
70
- animation: rotate 2s linear infinite;
71
- width: 24px;
72
- height: 24px;
73
- }
74
-
75
- .spinner .path {
76
- stroke: rgba(249, 250, 251, 1);
77
- stroke-linecap: round;
78
- animation: dash 1.5s ease-in-out infinite;
79
- }
80
-
81
- @keyframes rotate {
82
- 100% {
83
- transform: rotate(360deg);
84
- }
85
- }
86
-
87
- @keyframes dash {
88
- 0% {
89
- stroke-dasharray: 1, 150;
90
- stroke-dashoffset: 0;
91
- }
92
-
93
- 50% {
94
- stroke-dasharray: 90, 150;
95
- stroke-dashoffset: -35;
96
- }
97
-
98
- 100% {
99
- stroke-dasharray: 90, 150;
100
- stroke-dashoffset: -124;
101
- }
102
- }
103
- *,
104
- ::before,
105
- ::after {
106
- box-sizing: border-box;
107
- }
108
-
109
- html {
110
- -moz-tab-size: 4;
111
- -o-tab-size: 4;
112
- tab-size: 4;
113
- line-height: 1.15;
114
- -webkit-text-size-adjust: 100%;
115
- }
116
-
117
- body {
118
- margin: 0;
119
- font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';
120
- }
121
-
122
- hr {
123
- height: 0;
124
- color: inherit;
125
- }
126
-
127
- abbr[title] {
128
- -webkit-text-decoration: underline dotted;
129
- text-decoration: underline dotted;
130
- }
131
-
132
- b,
133
- strong {
134
- font-weight: bolder;
135
- }
136
-
137
- code,
138
- kbd,
139
- samp,
140
- pre {
141
- font-family: ui-monospace, SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
142
- font-size: 1em;
143
- }
144
-
145
- small {
146
- font-size: 80%;
147
- }
148
-
149
- sub,
150
- sup {
151
- font-size: 75%;
152
- line-height: 0;
153
- position: relative;
154
- vertical-align: baseline;
155
- }
156
-
157
- sub {
158
- bottom: -0.25em;
159
- }
160
-
161
- sup {
162
- top: -0.5em;
163
- }
164
-
165
- table {
166
- text-indent: 0;
167
- border-color: inherit;
168
- }
169
-
170
- button,
171
- input,
172
- optgroup,
173
- select,
174
- textarea {
175
- font-family: inherit;
176
- font-size: 100%;
177
- line-height: 1.15;
178
- margin: 0;
179
- }
180
-
181
- button,
182
- select {
183
- text-transform: none;
184
- }
185
-
186
- button,
187
- [type='button'],
188
- [type='reset'],
189
- [type='submit'] {
190
- -webkit-appearance: button;
191
- }
192
-
193
- ::-moz-focus-inner {
194
- border-style: none;
195
- padding: 0;
196
- }
197
-
198
- :-moz-focusring {
199
- outline: 1px dotted ButtonText;
200
- outline: auto;
201
- }
202
-
203
- :-moz-ui-invalid {
204
- box-shadow: none;
205
- }
206
-
207
- legend {
208
- padding: 0;
209
- }
210
-
211
- progress {
212
- vertical-align: baseline;
213
- }
214
-
215
- ::-webkit-inner-spin-button,
216
- ::-webkit-outer-spin-button {
217
- height: auto;
218
- }
219
-
220
- [type='search'] {
221
- -webkit-appearance: textfield;
222
- outline-offset: -2px;
223
- }
224
-
225
- ::-webkit-search-decoration {
226
- -webkit-appearance: none;
227
- }
228
-
229
- ::-webkit-file-upload-button {
230
- -webkit-appearance: button;
231
- font: inherit;
232
- }
233
-
234
- summary {
235
- display: list-item;
236
- }
237
-
238
- blockquote,
239
- dl,
240
- dd,
241
- h1,
242
- h2,
243
- h3,
244
- h4,
245
- h5,
246
- h6,
247
- hr,
248
- figure,
249
- p,
250
- pre {
251
- margin: 0;
252
- }
253
-
254
- button {
255
- background-color: transparent;
256
- background-image: none;
257
- }
258
-
259
- fieldset {
260
- margin: 0;
261
- padding: 0;
262
- }
263
-
264
- ol,
265
- ul {
266
- list-style: none;
267
- margin: 0;
268
- padding: 0;
269
- }
270
-
271
- html {
272
- font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif,
273
- 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
274
- line-height: 1.5;
275
- }
276
-
277
- body {
278
- font-family: inherit;
279
- line-height: inherit;
280
- }
281
-
282
- *,
283
- ::before,
284
- ::after {
285
- box-sizing: border-box;
286
- border-width: 0;
287
- border-style: solid;
288
- border-color: currentColor;
289
- }
290
-
291
- hr {
292
- border-top-width: 1px;
293
- }
294
-
295
- img {
296
- border-style: solid;
297
- }
298
-
299
- textarea {
300
- resize: vertical;
301
- }
302
-
303
- input::-moz-placeholder,
304
- textarea::-moz-placeholder {
305
- opacity: 1;
306
- color: #9ca3af;
307
- }
308
-
309
- input:-ms-input-placeholder,
310
- textarea:-ms-input-placeholder {
311
- opacity: 1;
312
- color: #9ca3af;
313
- }
314
-
315
- input::placeholder,
316
- textarea::placeholder {
317
- opacity: 1;
318
- color: #9ca3af;
319
- }
320
-
321
- button,
322
- [role='button'] {
323
- cursor: pointer;
324
- }
325
-
326
- table {
327
- border-collapse: collapse;
328
- }
329
-
330
- h1,
331
- h2,
332
- h3,
333
- h4,
334
- h5,
335
- h6 {
336
- font-size: inherit;
337
- font-weight: inherit;
338
- }
339
-
340
- a {
341
- color: inherit;
342
- text-decoration: inherit;
343
- }
344
-
345
- button,
346
- input,
347
- optgroup,
348
- select,
349
- textarea {
350
- padding: 0;
351
- line-height: inherit;
352
- color: inherit;
353
- }
354
-
355
- pre,
356
- code,
357
- kbd,
358
- samp {
359
- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
360
- }
361
-
362
- img,
363
- svg,
364
- video,
365
- canvas,
366
- audio,
367
- iframe,
368
- embed,
369
- object {
370
- display: block;
371
- vertical-align: middle;
372
- }
373
-
374
- img,
375
- video {
376
- max-width: 100%;
377
- height: auto;
378
- }
379
-
380
- [hidden] {
381
- display: none;
382
- }
383
-
384
- *,
385
- ::before,
386
- ::after {
387
- --tw-border-opacity: 1;
388
- border-color: rgba(229, 231, 235, var(--tw-border-opacity));
389
- }
390
-
391
- .flex {
392
- display: flex;
393
- }
394
-
395
- .flex-col {
396
- flex-direction: column;
397
- }
398
-
399
- .w-full {
400
- width: 100%;
401
- }
402
-
403
- .p-2 {
404
- padding: 0.5rem;
405
- }
406
-
407
- .bg-gray-50 {
408
- background-color: rgba(249, 250, 251, 1);
409
- }
410
-
411
- .border-b {
412
- border-bottom-width: 1px;
413
- }
414
-
415
- .border-gray-200 {
416
- border-color: rgba(229, 231, 235, 1);
417
- }
418
-
419
- .items-center {
420
- align-items: center;
421
- }
422
-
423
- .justify-start {
424
- justify-content: flex-start;
425
- }
426
-
427
- .flex-1 {
428
- flex: 1;
429
- }
430
-
431
- .justify-end {
432
- justify-content: flex-end;
433
- }
434
-
435
- .mr-4 {
436
- margin-right: 1rem;
437
- }
438
-
439
- .text-gray-700 {
440
- color: rgba(55, 65, 81, 1);
441
- }
442
-
443
- .text-2xl {
444
- font-size: 1.5rem;
445
- line-height: 2rem;
446
- }
447
-
448
- .font-bold {
449
- font-weight: 700;
450
- }
451
-
452
- .text-xl {
453
- font-size: 1.25rem;
454
- line-height: 1.75rem;
455
- }
456
-
457
- .block {
458
- display: block;
459
- }
460
-
461
- .ml-3 {
462
- margin-left: 0.75rem;
463
- }
464
-
465
- .mr-3 {
466
- margin-right: 0.75rem;
467
- }
468
-
469
- .bg-gray-200 {
470
- background-color: rgba(229, 231, 235, 1);
471
- }
472
-
473
- .border {
474
- border-width: 1px;
475
- }
476
-
477
- .border-gray-400 {
478
- border-color: rgba(156, 163, 175, 1);
479
- }
480
-
481
- .rounded-md {
482
- border-radius: 0.375rem;
483
- }
484
-
485
- .pt-2 {
486
- padding-top: 0.5rem;
487
- }
488
-
489
- .pb-2 {
490
- padding-bottom: 0.5rem;
491
- }
492
-
493
- .pl-6 {
494
- padding-left: 1.5rem;
495
- }
496
-
497
- .pr-6 {
498
- padding-right: 1.5rem;
499
- }
500
-
501
- .flex-row {
502
- flex-direction: row;
503
- }
504
-
505
- .pr-8 {
506
- padding-right: 2rem;
507
- }
508
-
509
- .border-r {
510
- border-right-width: 1px;
511
- }
512
-
513
- .border-gray-300 {
514
- border-color: rgba(209, 213, 219, 1);
515
- }
516
-
517
- .text-sm {
518
- font-size: 0.875rem;
519
- line-height: 1.25rem;
520
- }
521
-
522
- .uppercase {
523
- text-transform: uppercase;
524
- }
525
-
526
- .pl-2 {
527
- padding-left: 0.5rem;
528
- }
529
-
530
- .p-1 {
531
- padding: 0.25rem;
532
- }
533
-
534
- .border-l {
535
- border-left-width: 1px;
536
- }
537
-
538
- .border-l-gray-200 {
539
- border-left-color: rgba(229, 231, 235, 1);
540
- }
cmd/gromer/main.go DELETED
@@ -1,233 +0,0 @@
1
- package main
2
-
3
- import (
4
- "bytes"
5
- "flag"
6
- "fmt"
7
- "io/ioutil"
8
- "log"
9
- "os"
10
- "path/filepath"
11
- "sort"
12
- "strings"
13
- "unicode"
14
-
15
- "github.com/gobuffalo/velvet"
16
- "github.com/pyros2097/gromer"
17
- "golang.org/x/mod/modfile"
18
- )
19
-
20
- type Route struct {
21
- Method string
22
- Path string
23
- Pkg string
24
- }
25
-
26
- func getMethod(src string) string {
27
- if strings.HasSuffix(src, "get.go") {
28
- return "GET"
29
- } else if strings.HasSuffix(src, "post.go") {
30
- return "POST"
31
- } else if strings.HasSuffix(src, "put.go") {
32
- return "PUT"
33
- } else if strings.HasSuffix(src, "patch.go") {
34
- return "PATCH"
35
- } else if strings.HasSuffix(src, "delete.go") {
36
- return "DELETE"
37
- } else if strings.HasSuffix(src, "head.go") {
38
- return "HEAD"
39
- } else if strings.HasSuffix(src, "options.go") {
40
- return "OPTIONS"
41
- } else if strings.HasSuffix(src, "connect.go") {
42
- return "CONNECT"
43
- } else if strings.HasSuffix(src, "trace.go") {
44
- return "TRACE"
45
- } else {
46
- return ""
47
- }
48
- }
49
-
50
- func getRoute(method, src string) string {
51
- return strings.ReplaceAll(src, "/"+strings.ToLower(method)+".go", "")
52
- }
53
-
54
- func rewritePath(route string) string {
55
- muxRoute := bytes.NewBuffer(nil)
56
- foundStart := false
57
- for _, v := range route {
58
- if string(v) == "_" && !foundStart {
59
- foundStart = true
60
- muxRoute.WriteString("{")
61
- } else if string(v) == "_" && foundStart {
62
- foundStart = false
63
- muxRoute.WriteString("}")
64
- } else {
65
- muxRoute.WriteString(string(v))
66
- }
67
- }
68
- return muxRoute.String()
69
- }
70
-
71
- func lowerFirst(s string) string {
72
- for i, v := range s {
73
- return string(unicode.ToLower(v)) + s[i+1:]
74
- }
75
- return ""
76
- }
77
-
78
- func main() {
79
- moduleName := ""
80
- notFoundPkg := ""
81
- pkgFlag := flag.String("pkg", "", "specify a package name")
82
- flag.Parse()
83
- if pkgFlag == nil || *pkgFlag == "" {
84
- data, err := ioutil.ReadFile("go.mod")
85
- if err != nil {
86
- log.Fatalf("go.mod file not found %s", err.Error())
87
- }
88
- modTree, err := modfile.Parse("go.mod", data, nil)
89
- if err != nil {
90
- log.Fatalf("could not parse go.mod %s", err.Error())
91
- }
92
- moduleName = modTree.Module.Mod.Path
93
- } else {
94
- moduleName = *pkgFlag
95
- }
96
- err := filepath.Walk("routes",
97
- func(filesrc string, info os.FileInfo, err error) error {
98
- if err != nil {
99
- return err
100
- }
101
- if !info.IsDir() {
102
- route := strings.ReplaceAll(filesrc, "routes", "")
103
- method := getMethod(route)
104
- if method == "" {
105
- return nil
106
- }
107
- path := getRoute(method, route)
108
- if path == "" { // for index page
109
- path = "/"
110
- }
111
- data, err := ioutil.ReadFile(filesrc)
112
- if err != nil {
113
- return err
114
- }
115
- lines := strings.Split(string(data), "\n")
116
- pkg := strings.Replace(""+lines[0], "package ", "", 1)
117
- if strings.Contains(filesrc, "/404/") {
118
- notFoundPkg = pkg
119
- return nil
120
- }
121
- gromer.RouteDefs = append(gromer.RouteDefs, gromer.RouteDefinition{
122
- Pkg: pkg,
123
- PkgPath: getRoute(method, route),
124
- Method: method,
125
- Path: rewritePath(path),
126
- })
127
- }
128
- return nil
129
- })
130
- if err != nil {
131
- log.Fatal(err)
132
- }
133
- sort.Slice(gromer.RouteDefs, func(i, j int) bool {
134
- return gromer.RouteDefs[i].Path < gromer.RouteDefs[j].Path
135
- })
136
- pageRoutes := []gromer.RouteDefinition{}
137
- for _, r := range gromer.RouteDefs {
138
- fmt.Printf("%-6s %s %-6s\n", r.Method, r.Path, r.PkgPath)
139
- pageRoutes = append(pageRoutes, r)
140
- }
141
- hasRouteMap := map[string]bool{}
142
- routeImports := []gromer.RouteDefinition{}
143
- for _, v := range gromer.RouteDefs {
144
- if _, ok := hasRouteMap[v.PkgPath]; !ok {
145
- routeImports = append(routeImports, v)
146
- hasRouteMap[v.PkgPath] = true
147
- }
148
- }
149
- componentNames := []string{}
150
- containerNames := []string{}
151
- for _, p := range []string{"components", "containers"} {
152
- err = filepath.Walk(p,
153
- func(filesrc string, info os.FileInfo, err error) error {
154
- if err != nil {
155
- return err
156
- }
157
- if !info.IsDir() {
158
- filename := strings.ReplaceAll(filepath.Base(filesrc), ".go", "")
159
- if p == "containers" {
160
- containerNames = append(containerNames, strings.Title((filename)))
161
- } else {
162
- componentNames = append(componentNames, strings.Title((filename)))
163
- }
164
- }
165
- return nil
166
- })
167
- if err != nil {
168
- log.Fatal(err)
169
- }
170
- }
171
- ctx := velvet.NewContext()
172
- ctx.Set("moduleName", moduleName)
173
- ctx.Set("pageRoutes", pageRoutes)
174
- ctx.Set("routeImports", routeImports)
175
- ctx.Set("componentNames", componentNames)
176
- ctx.Set("containerNames", containerNames)
177
- ctx.Set("notFoundPkg", notFoundPkg)
178
- ctx.Set("tick", "`")
179
- s, err := velvet.Render(`// Code generated by gromer. DO NOT EDIT.
180
- package main
181
-
182
- import (
183
- "github.com/gorilla/mux"
184
- "github.com/pyros2097/gromer"
185
- "github.com/pyros2097/gromer/assets"
186
- "github.com/pyros2097/gromer/gsx"
187
- "github.com/rs/zerolog/log"
188
- "gocloud.dev/server"
189
-
190
- "{{ moduleName }}/assets"
191
- "{{ moduleName }}/components"
192
- "{{ moduleName }}/containers"
193
- {{#if notFoundPkg}}"{{ moduleName }}/routes/404"{{/if}}
194
- {{#each routeImports as |route| }}"{{ moduleName }}/routes{{ route.PkgPath }}"
195
- {{/each}}
196
- )
197
-
198
- func init() {
199
- {{#each componentNames as |name| }}gsx.RegisterComponent(components.{{ name }})
200
- {{/each}}{{#each containerNames as |name| }}gsx.RegisterComponent(containers.{{ name }})
201
- {{/each}}
202
- }
203
-
204
- func main() {
205
- baseRouter := mux.NewRouter()
206
- baseRouter.Use(gromer.LogMiddleware)
207
- {{#if notFoundPkg}}baseRouter.NotFoundHandler = gromer.StatusHandler({{ notFoundPkg }}.GET)
208
- {{/if}}
209
- staticRouter := baseRouter.NewRoute().Subrouter()
210
- staticRouter.Use(gromer.CacheMiddleware)
211
- gromer.StaticRoute(staticRouter, "/gromer/", gromer_assets.FS)
212
- gromer.StaticRoute(staticRouter, "/assets/", assets.FS)
213
- gromer.StylesRoute(staticRouter, "/styles.css")
214
-
215
- pageRouter := baseRouter.NewRoute().Subrouter()
216
- {{#each pageRoutes as |route| }}gromer.Handle(pageRouter, "{{ route.Method }}", "{{ route.Path }}", {{ route.Pkg }}.{{ route.Method }})
217
- {{/each}}
218
-
219
- log.Info().Msg("http server listening on http://localhost:3000")
220
- srv := server.New(baseRouter, nil)
221
- if err := srv.ListenAndServe(":3000"); err != nil {
222
- log.Fatal().Stack().Err(err).Msg("failed to listen")
223
- }
224
- }
225
- `, ctx)
226
- if err != nil {
227
- panic(err)
228
- }
229
- err = ioutil.WriteFile("main.go", []byte(s), 0644)
230
- if err != nil {
231
- panic(err)
232
- }
233
- }
go.mod CHANGED
@@ -4,6 +4,7 @@ go 1.18
4
4
 
5
5
  require (
6
6
  github.com/alecthomas/participle/v2 v2.0.0-beta.3
7
+ github.com/alecthomas/repr v0.1.0
7
8
  github.com/felixge/httpsnoop v1.0.1
8
9
  github.com/go-playground/validator/v10 v10.9.0
9
10
  github.com/gobuffalo/velvet v0.0.0-20170320144106-d97471bf5d8f
go.sum CHANGED
@@ -103,6 +103,7 @@ github.com/alecthomas/assert/v2 v2.0.3 h1:WKqJODfOiQG0nEJKFKzDIG3E29CN2/4zR9XGJz
103
103
  github.com/alecthomas/participle/v2 v2.0.0-beta.3 h1:9HnyNuDsqOG8sl63Dz+KubqHhU8aWqsrjKdecim8GW0=
104
104
  github.com/alecthomas/participle/v2 v2.0.0-beta.3/go.mod h1:RC764t6n4L8D8ITAJv0qdokritYSNR3wV5cVwmIEaMM=
105
105
  github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
106
+ github.com/alecthomas/repr v0.1.0/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
106
107
  github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
107
108
  github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
108
109
  github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
gsx/context.go CHANGED
@@ -20,6 +20,7 @@ type Context struct {
20
20
  meta M
21
21
  links map[string]link
22
22
  scripts map[string]bool
23
+ styles M
23
24
  }
24
25
 
25
26
  func NewContext(c context.Context, hx *HX) *Context {
@@ -30,6 +31,7 @@ func NewContext(c context.Context, hx *HX) *Context {
30
31
  meta: M{},
31
32
  links: map[string]link{},
32
33
  scripts: map[string]bool{},
34
+ styles: M{},
33
35
  }
34
36
  }
35
37
 
@@ -65,6 +67,10 @@ func (c *Context) Data(data M) {
65
67
  c.data = data
66
68
  }
67
69
 
70
+ func (c *Context) Styles(s M) {
71
+ c.styles = s
72
+ }
73
+
68
74
  func (c *Context) Render(tpl string) []*Tag {
69
75
  name, ok := c.Get("funcName").(string)
70
76
  if !ok {
gsx/gsx.go CHANGED
@@ -18,7 +18,6 @@ var (
18
18
  voidElements = []string{"area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"}
19
19
  compMap = map[string]ComponentFunc{}
20
20
  funcMap = map[string]interface{}{}
21
- classesMap = map[string]M{}
22
21
  refRegex = regexp.MustCompile(`{(.*?)}`)
23
22
  )
24
23
 
@@ -27,10 +26,10 @@ type (
27
26
  MS map[string]string
28
27
  Arr []interface{}
29
28
  ComponentFunc struct {
30
- Name string
29
+ Name string
31
- Func interface{}
30
+ Func interface{}
32
- Args []string
31
+ Args []string
33
- Classes M
32
+ Styles M
34
33
  }
35
34
  link struct {
36
35
  Rel string
@@ -40,13 +39,13 @@ type (
40
39
  }
41
40
  )
42
41
 
43
- func RegisterComponent(f interface{}, classes M, args ...string) {
42
+ func RegisterComponent(f interface{}, styles M, args ...string) {
44
43
  name := getFunctionName(f)
45
44
  compMap[name] = ComponentFunc{
46
- Name: name,
45
+ Name: name,
47
- Func: f,
46
+ Func: f,
48
- Args: args,
47
+ Args: args,
49
- Classes: classes,
48
+ Styles: styles,
50
49
  }
51
50
  }
52
51
 
@@ -117,53 +116,50 @@ func (comp ComponentFunc) Render(c *Context, tag *Tag) []*Tag {
117
116
 
118
117
  func Write(c *Context, w io.Writer, tags []*Tag) {
119
118
  if c.hx == nil {
120
- w.Write([]byte(`<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8">`))
119
+ w.Write([]byte("<!DOCTYPE html>\n<html lang='en'>\n<head>\n<meta charset='UTF-8'>\n"))
121
- w.Write([]byte(`<meta http-equiv="Content-Type" content="text/html;charset=utf-8"><meta content="utf-8" http-equiv="encoding">`))
120
+ w.Write([]byte(" <meta http-equiv='Content-Type' content='text/html;charset=utf-8'><meta content='utf-8' http-equiv='encoding'>\n"))
122
- w.Write([]byte(`<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, viewport-fit=cover">`))
121
+ w.Write([]byte(" <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, viewport-fit=cover'>\n"))
123
122
  for k, v := range c.meta {
124
- w.Write([]byte(fmt.Sprintf(`<meta name="%s" content="%s">`, k, v)))
123
+ w.Write([]byte(fmt.Sprintf(" <meta name='%s' content='%s'>\n", k, v)))
125
124
  }
126
125
  for k, v := range c.meta {
127
126
  if k == "title" {
128
- w.Write([]byte(fmt.Sprintf(`<title>%s</title>`, v)))
127
+ w.Write([]byte(fmt.Sprintf(" <title>%s</title>\n", v)))
129
128
  }
130
129
  }
130
+
131
131
  for _, v := range c.links {
132
132
  if v.Type != "" || v.As != "" {
133
- w.Write([]byte(fmt.Sprintf(`<link rel="%s" href="%s" type="%s" as="%s">`, v.Rel, v.Href, v.Type, v.As)))
133
+ w.Write([]byte(fmt.Sprintf(" <link rel='%s' href='%s' type='%s' as='%s'>\n", v.Rel, v.Href, v.Type, v.As)))
134
134
  } else {
135
- w.Write([]byte(fmt.Sprintf(`<link rel="%s" href="%s">`, v.Rel, v.Href)))
135
+ w.Write([]byte(fmt.Sprintf(" <link rel='%s' href='%s'>\n", v.Rel, v.Href)))
136
136
  }
137
137
  }
138
+ funcName := c.Get("funcName").(string)
139
+ styles := computeCss(c.styles, funcName)
140
+ w.Write([]byte(fmt.Sprintf(" <style>%s</style>\n", styles)))
141
+
138
142
  for src, sdefer := range c.scripts {
139
143
  if sdefer {
140
- w.Write([]byte(fmt.Sprintf(`<script src="%s" defer="true"></script>`, src)))
144
+ w.Write([]byte(fmt.Sprintf(" <script src='%s' defer='true'></script>\n", src)))
141
145
  } else {
142
- w.Write([]byte(fmt.Sprintf(`<script src="%s"></script>`, src)))
146
+ w.Write([]byte(fmt.Sprintf(" <script src='%s'></script>\n", src)))
143
147
  }
144
148
  }
145
- w.Write([]byte(`</head><body _="on htmx:error(errorInfo) put errorInfo.xhr.response into #error">`))
149
+ w.Write([]byte("</head>\n <body _='on htmx:error(errorInfo) put errorInfo.xhr.response into #error'>\n"))
146
150
  }
147
151
  out := RenderString(tags)
148
152
  w.Write([]byte(out))
149
153
  if c.hx == nil {
150
- w.Write([]byte(`</body></html>`))
154
+ w.Write([]byte(" </body>\n</html>"))
151
155
  }
152
156
  }
153
157
 
154
- func SetClasses(k string, m M) {
155
- classesMap[k] = m
156
- }
157
-
158
- func GetPageStyles(k string) string {
159
- return normalizeCss + "\n" + computeCss(classesMap[k], k)
160
- }
161
-
162
158
  func GetComponentStyles() string {
163
159
  css := ""
164
160
  for k, v := range compMap {
165
- if v.Classes != nil {
161
+ if v.Styles != nil {
166
- css += computeCss(v.Classes, k)
162
+ css += computeCss(v.Styles, k)
167
163
  }
168
164
  }
169
165
  return css
gsx/twx.go CHANGED
@@ -569,58 +569,3 @@ func computeCss(classMap M, parent string) string {
569
569
  }
570
570
  return p
571
571
  }
572
-
573
- var normalizeCss = `*, ::before, ::after { box-sizing: border-box; }
574
- html { -moz-tab-size: 4; -o-tab-size: 4; tab-size: 4; line-height: 1.15; -webkit-text-size-adjust: 100%; }
575
- body { margin: 0; font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'; }
576
- hr { height: 0; color: inherit; }
577
- abbr[title] { -webkit-text-decoration: underline dotted; text-decoration: underline dotted; }
578
- b, strong { font-weight: bolder; }
579
- code, kbd, samp, pre { font-family: ui-monospace, SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 1em; }
580
- small { font-size: 80%; }
581
- sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; }
582
- sub { bottom: -0.25em; }
583
- sup { top: -0.5em; }
584
- table { text-indent: 0; border-color: inherit; }
585
- button, input, optgroup, select, textarea { font-family: inherit; font-size: 100%; line-height: 1.15; margin: 0; }
586
- button, select { text-transform: none; }
587
- button, [type='button'], [type='reset'], [type='submit'] { -webkit-appearance: button; }
588
- ::-moz-focus-inner { border-style: none; padding: 0; }
589
- :-moz-focusring { outline: 1px dotted ButtonText; outline: auto; }
590
- :-moz-ui-invalid { box-shadow: none; }
591
- legend { padding: 0; }
592
- progress { vertical-align: baseline; }
593
- ::-webkit-inner-spin-button, ::-webkit-outer-spin-button { height: auto; }
594
- [type='search'] { -webkit-appearance: textfield; outline-offset: -2px; }
595
- ::-webkit-search-decoration { -webkit-appearance: none; }
596
- ::-webkit-file-upload-button { -webkit-appearance: button; font: inherit; }
597
- summary { display: list-item; }
598
- blockquote, dl, dd, h1, h2, h3, h4, h5, h6, hr, figure, p, pre { margin: 0; }
599
- button { background-color: transparent; background-image: none; }
600
- fieldset { margin: 0; padding: 0; }
601
- ol, ul { list-style: none; margin: 0; padding: 0; }
602
- 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; }
603
- body { font-family: inherit; line-height: inherit; }
604
- *, ::before, ::after { box-sizing: border-box; border-width: 0; border-style: solid; border-color: currentColor; }
605
- hr { border-top-width: 1px; }
606
- img { border-style: solid; }
607
- textarea { resize: vertical; }
608
- input::-moz-placeholder, textarea::-moz-placeholder { opacity: 1; color: #9ca3af; }
609
- input:-ms-input-placeholder, textarea:-ms-input-placeholder { opacity: 1; color: #9ca3af; }
610
- input::placeholder, textarea::placeholder { opacity: 1; color: #9ca3af; }
611
- button, [role="button"] { cursor: pointer; }
612
- table { border-collapse: collapse; }
613
- h1, h2, h3, h4, h5, h6 { font-size: inherit; font-weight: inherit; }
614
- a { color: inherit; text-decoration: inherit; }
615
- button, input, optgroup, select, textarea { padding: 0; line-height: inherit; color: inherit; }
616
- pre, code, kbd, samp { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
617
- img, svg, video, canvas, audio, iframe, embed, object { display: block; vertical-align: middle; }
618
- img, video { max-width: 100%; height: auto; }
619
- [hidden] { display: none; }
620
- *, ::before, ::after { --tw-border-opacity: 1; border-color: rgba(229, 231, 235, var(--tw-border-opacity)); }
621
- form { display: flex; }
622
-
623
- .group:hover .group-hover-opacity-100 {
624
- opacity: 1;
625
- }
626
- `
http.go CHANGED
@@ -99,7 +99,7 @@ func RespondError(w http.ResponseWriter, r *http.Request, status int, err error)
99
99
  })
100
100
  log.Error().Msg(err.Error() + "\n" + formattedStr)
101
101
  }
102
- c := createCtx(r, "Status", "Status", gsx.M{}, gsx.M{})
102
+ c := createCtx(r, "Status")
103
103
  c.Set("funcName", "error")
104
104
  c.Set("error", err.Error())
105
105
  if r.Header.Get("HX-Request") == "true" || globalStatusComponent == nil {
@@ -124,7 +124,7 @@ func GetRouteParams(route string) []string {
124
124
  return params
125
125
  }
126
126
 
127
- func PerformRequest(route string, h interface{}, c *gsx.Context, w http.ResponseWriter, r *http.Request) {
127
+ func PerformRequest(route string, h interface{}, c *gsx.Context, w http.ResponseWriter, r *http.Request, isJson bool) {
128
128
  params := GetRouteParams(route)
129
129
  args := []reflect.Value{reflect.ValueOf(c)}
130
130
  funcType := reflect.TypeOf(h)
@@ -211,6 +211,17 @@ func PerformRequest(route string, h interface{}, c *gsx.Context, w http.Response
211
211
  RespondError(w, r, responseStatus, eris.Wrap(responseError.(error), "Render failed"))
212
212
  return
213
213
  }
214
+ if isJson {
215
+ w.Header().Set("Content-Type", "application/json")
216
+ w.WriteHeader(responseStatus)
217
+ data, err := json.Marshal(response)
218
+ if err != nil {
219
+ RespondError(w, r, responseStatus, eris.Wrap(responseError.(error), "Marshals failed"))
220
+ return
221
+ }
222
+ w.Write(data)
223
+ return
224
+ }
214
225
  w.Header().Set("Content-Type", "text/html")
215
226
  // This has to be at end always
216
227
  w.WriteHeader(responseStatus)
@@ -280,20 +291,6 @@ func IconsRoute(router *mux.Router, path string, fs embed.FS) {
280
291
  })
281
292
  }
282
293
 
283
- func PageStylesRoute(router *mux.Router, route string) {
284
- router.Path(route).Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
285
- err := r.ParseForm()
286
- if err != nil {
287
- RespondError(w, r, 400, err)
288
- return
289
- }
290
- key := r.Form.Get("key")
291
- w.Header().Set("Content-Type", "text/css")
292
- w.WriteHeader(200)
293
- w.Write([]byte(gsx.GetPageStyles(key)))
294
- })
295
- }
296
-
297
294
  func ComponentStylesRoute(router *mux.Router, route string) {
298
295
  router.Path(route).Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
299
296
  w.Header().Set("Content-Type", "text/css")
@@ -302,7 +299,7 @@ func ComponentStylesRoute(router *mux.Router, route string) {
302
299
  })
303
300
  }
304
301
 
305
- func createCtx(r *http.Request, route, key string, meta, styles gsx.M) *gsx.Context {
302
+ func createCtx(r *http.Request, route string) *gsx.Context {
306
303
  newCtx := context.WithValue(context.WithValue(r.Context(), "url", r.URL), "header", r.Header)
307
304
  var hx *gsx.HX
308
305
  if r.Header.Get("HX-Request") == "true" {
@@ -316,24 +313,21 @@ func createCtx(r *http.Request, route, key string, meta, styles gsx.M) *gsx.Cont
316
313
  }
317
314
  }
318
315
  c := gsx.NewContext(newCtx, hx)
319
- c.Set("funcName", route)
316
+ c.Set("funcName", camelcase.Camelcase(route))
320
317
  c.Set("requestId", uuid.NewString())
321
- c.Link("stylesheet", GetPageStylesUrl(key), "", "")
318
+ c.Link("stylesheet", "/gromer/css/normalize@3.0.0.css", "", "")
322
319
  c.Link("stylesheet", GetComponentsStylesUrl(), "", "")
323
320
  c.Link("icon", "/assets/favicon.ico", "image/x-icon", "image")
324
321
  c.Script("/gromer/js/htmx@1.7.0.js", false)
325
322
  c.Script("/gromer/js/hyperscript@0.9.6.js", false)
326
323
  // c.Script("/gromer/js/alpinejs@3.9.6.js", true)
327
- c.Meta(meta)
328
324
  return c
329
325
  }
330
326
 
331
- func RegisterStatusHandler(router *mux.Router, comp StatusComponent, styles gsx.M) {
327
+ func RegisterStatusHandler(router *mux.Router, comp StatusComponent) {
332
- key := "Status"
333
- gsx.SetClasses(key, styles)
334
328
  globalStatusComponent = comp
335
329
  router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
336
- c := createCtx(r, key, key, gsx.M{}, styles)
330
+ c := createCtx(r, "Status")
337
331
  tags := comp(c, 404, nil)
338
332
  w.Header().Set("Content-Type", "text/html")
339
333
  w.WriteHeader(404)
@@ -341,12 +335,21 @@ func RegisterStatusHandler(router *mux.Router, comp StatusComponent, styles gsx.
341
335
  })
342
336
  }
343
337
 
338
+ func PageRoute(router *mux.Router, route string, page, action interface{}) {
339
+ router.HandleFunc(route, func(w http.ResponseWriter, r *http.Request) {
340
+ c := createCtx(r, route)
341
+ if r.Method == "GET" {
342
+ PerformRequest(route, page, c, w, r, false)
343
+ } else {
344
+ PerformRequest(route, action, c, w, r, false)
345
+ }
346
+ }).Methods("GET", "POST")
347
+ }
348
+
344
- func Handle(router *mux.Router, method, route string, h interface{}, meta, styles gsx.M) {
349
+ func ApiRoute(router *mux.Router, method, route string, h interface{}) {
345
- key := camelcase.Camelcase(route)
346
- gsx.SetClasses(key, styles)
347
350
  router.HandleFunc(route, func(w http.ResponseWriter, r *http.Request) {
348
- c := createCtx(r, route, key, meta, styles)
351
+ c := createCtx(r, route)
349
- PerformRequest(route, h, c, w, r)
352
+ PerformRequest(route, h, c, w, r, true)
350
353
  }).Methods(method)
351
354
  }
352
355
 
@@ -380,13 +383,6 @@ func GetAssetUrl(fs embed.FS, path string) string {
380
383
  return fmt.Sprintf("/assets/%s?hash=%s", path, sum)
381
384
  }
382
385
 
383
- func GetPageStylesUrl(k string) string {
384
- sum := getSum("styles.css", func() [16]byte {
385
- return md5.Sum([]byte(gsx.GetPageStyles(k)))
386
- })
387
- return fmt.Sprintf("/styles.css?key=%s&hash=%s", k, sum)
388
- }
389
-
390
386
  func GetComponentsStylesUrl() string {
391
387
  sum := getSum("components.css", func() [16]byte {
392
388
  return md5.Sum([]byte(gsx.GetComponentStyles()))