├── .gitignore ├── actions.go ├── README.md ├── index.html ├── LICENSE ├── main.go ├── index.jsgo.html ├── view-page.go ├── view-editor.go ├── app.go └── store-editor.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .DS_Store 3 | .idea/ 4 | coverage.out 5 | .coverage/ 6 | -------------------------------------------------------------------------------- /actions.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type UserChangedTextAction struct { 4 | Text string 5 | } 6 | 7 | type ChangeTextAction struct { 8 | Text string 9 | } 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # html2vecty 2 | 3 | Convert HTML to Vecty syntax instantly: https://jsgo.io/dave/html2vecty 4 | 5 | This is a [GopherJS](https://github.com/gopherjs/gopherjs) / [Vecty](https://github.com/gopherjs/vecty) app using 6 | my [flux router](https://github.com/dave/flux) and [Jennifer](https://github.com/dave/jennifer) to build the 7 | Go source output. 8 | 9 | It is compiled and deployed using my [jsgo](https://github.com/dave/jsgo) compiler. You can [compile it 10 | here](https://compile.jsgo.io/dave/html2vecty). 11 | 12 | #### Command line 13 | 14 | If you'd like to use this as a command line app, check out [github.com/AnikHasibul/html2vecty](https://github.com/AnikHasibul/html2vecty). 15 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 David Brophy 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gopherjs/vecty" 5 | "github.com/vincent-petithory/dataurl" 6 | ) 7 | 8 | func main() { 9 | vecty.AddStylesheet(dataurl.New([]byte(styles), "text/css").String()) 10 | app := &App{} 11 | app.Init() 12 | p := NewPage(app) 13 | vecty.RenderBody(p) 14 | } 15 | 16 | var styles = ` 17 | html, body { 18 | height: 100%; 19 | } 20 | .editor { 21 | height: 100%; 22 | width: 100%; 23 | } 24 | .split { 25 | height: 100%; 26 | width: 100%; 27 | } 28 | .gutter { 29 | height: 100%; 30 | background-color: #eee; 31 | background-repeat: no-repeat; 32 | background-position: 50%; 33 | } 34 | .gutter.gutter-horizontal { 35 | cursor: col-resize; 36 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==') 37 | } 38 | .split { 39 | -webkit-box-sizing: border-box; 40 | -moz-box-sizing: border-box; 41 | box-sizing: border-box; 42 | } 43 | .split, .gutter.gutter-horizontal { 44 | float: left; 45 | } 46 | ` 47 | -------------------------------------------------------------------------------- /index.jsgo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |
14 |
15 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /view-page.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/dave/splitter" 5 | "github.com/gopherjs/gopherjs/js" 6 | "github.com/gopherjs/vecty" 7 | "github.com/gopherjs/vecty/elem" 8 | "github.com/gopherjs/vecty/prop" 9 | ) 10 | 11 | type Page struct { 12 | vecty.Core 13 | app *App 14 | 15 | split *splitter.Split 16 | } 17 | 18 | func NewPage(app *App) *Page { 19 | v := &Page{ 20 | app: app, 21 | } 22 | return v 23 | } 24 | 25 | func (v *Page) Mount() { 26 | v.app.Watch(v, func(done chan struct{}) { 27 | defer close(done) 28 | // Only top-level page should fire vecty.Rerender 29 | vecty.Rerender(v) 30 | }) 31 | 32 | v.split = splitter.New("split") 33 | v.split.Init( 34 | js.S{"#left", "#right"}, 35 | js.M{"sizes": []float64{50, 50}}, 36 | ) 37 | } 38 | 39 | func (v *Page) Unmount() { 40 | v.app.Delete(v) 41 | } 42 | 43 | func (v *Page) Render() vecty.ComponentOrHTML { 44 | return elem.Body( 45 | elem.Div( 46 | vecty.Markup( 47 | vecty.Class("container-fluid", "p-0", "split", "split-horizontal"), 48 | ), 49 | v.renderLeft(), 50 | v.renderRight(), 51 | ), 52 | ) 53 | } 54 | 55 | func (v *Page) renderLeft() *vecty.HTML { 56 | return elem.Div( 57 | vecty.Markup( 58 | prop.ID("left"), 59 | vecty.Class("split"), 60 | ), 61 | NewEditor(v.app, "html-editor", "html", v.app.Editor.Html(), true, func(value string) { 62 | v.app.Dispatch(&UserChangedTextAction{ 63 | Text: value, 64 | }) 65 | }), 66 | ) 67 | } 68 | 69 | func (v *Page) renderRight() *vecty.HTML { 70 | return elem.Div( 71 | vecty.Markup( 72 | prop.ID("right"), 73 | vecty.Class("split"), 74 | ), 75 | NewEditor(v.app, "code-editor", "golang", v.app.Editor.Code(), false, nil), 76 | ) 77 | } 78 | -------------------------------------------------------------------------------- /view-editor.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gopherjs/gopherjs/js" 7 | "github.com/gopherjs/vecty" 8 | "github.com/gopherjs/vecty/elem" 9 | "github.com/gopherjs/vecty/prop" 10 | "github.com/tulir/gopher-ace" 11 | ) 12 | 13 | type Editor struct { 14 | vecty.Core 15 | app *App 16 | 17 | Text string `vecty:"prop"` 18 | 19 | editor ace.Editor 20 | id, lang string 21 | change func(string) 22 | readonly bool 23 | } 24 | 25 | func NewEditor(app *App, id, lang, text string, readonly bool, change func(string)) *Editor { 26 | v := &Editor{ 27 | app: app, 28 | lang: lang, 29 | id: id, 30 | change: change, 31 | Text: text, 32 | readonly: readonly, 33 | } 34 | return v 35 | } 36 | 37 | func (v *Editor) Mount() { 38 | v.editor = ace.Edit(v.id) 39 | v.editor.SetOptions(map[string]interface{}{ 40 | "mode": "ace/mode/" + v.lang, 41 | }) 42 | if v.Text != "" { 43 | v.editor.SetValue(v.Text) 44 | v.editor.ClearSelection() 45 | v.editor.MoveCursorTo(0, 0) 46 | } 47 | if v.change != nil { 48 | var changes int 49 | v.editor.OnChange(func(ev *js.Object) { 50 | changes++ 51 | before := changes 52 | go func() { 53 | <-time.After(time.Millisecond * 250) 54 | if before == changes { 55 | v.change(v.editor.GetValue()) 56 | } 57 | }() 58 | }) 59 | } 60 | } 61 | 62 | func (v *Editor) Render() vecty.ComponentOrHTML { 63 | if !v.readonly && v.editor.Object != nil && v.Text != v.editor.GetValue() { 64 | // only update the editor if the text is changed 65 | v.editor.SetValue(v.Text) 66 | v.editor.ClearSelection() 67 | v.editor.MoveCursorTo(0, 0) 68 | } 69 | 70 | return elem.Div( 71 | vecty.Markup( 72 | prop.ID(v.id), 73 | vecty.Class("editor"), 74 | ), 75 | vecty.Text(v.Text), 76 | ) 77 | } 78 | -------------------------------------------------------------------------------- /app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/dave/flux" 7 | "github.com/gopherjs/gopherjs/js" 8 | "honnef.co/go/js/dom" 9 | ) 10 | 11 | type App struct { 12 | Dispatcher flux.DispatcherInterface 13 | Watcher flux.WatcherInterface 14 | Notifier flux.NotifierInterface 15 | 16 | Editor *EditorStore 17 | } 18 | 19 | func (a *App) Init() { 20 | 21 | n := flux.NewNotifier() 22 | a.Notifier = n 23 | a.Watcher = n 24 | 25 | a.Editor = NewEditorStore(a) 26 | 27 | a.Dispatcher = flux.NewDispatcher( 28 | // Notifier: 29 | a.Notifier, 30 | // Stores: 31 | a.Editor, 32 | ) 33 | } 34 | 35 | func (a *App) Dispatch(action flux.ActionInterface) chan struct{} { 36 | return a.Dispatcher.Dispatch(action) 37 | } 38 | 39 | func (a *App) Watch(key interface{}, f func(done chan struct{})) { 40 | a.Watcher.Watch(key, f) 41 | } 42 | 43 | func (a *App) Delete(key interface{}) { 44 | a.Watcher.Delete(key) 45 | } 46 | 47 | func (a *App) Fail(err error) { 48 | // TODO: improve this 49 | js.Global.Call("alert", err.Error()) 50 | } 51 | 52 | func (a *App) Debug(message ...interface{}) { 53 | js.Global.Get("console").Call("log", message...) 54 | } 55 | 56 | func (a *App) Log(message ...interface{}) { 57 | m := dom.GetWindow().Document().GetElementByID("message") 58 | if len(message) == 0 { 59 | m.SetInnerHTML("") 60 | return 61 | } 62 | s := fmt.Sprint(message[0]) 63 | if m.InnerHTML() != s { 64 | requestAnimationFrame() 65 | m.SetInnerHTML(s) 66 | requestAnimationFrame() 67 | } 68 | js.Global.Get("console").Call("log", message...) 69 | } 70 | 71 | func (a *App) Logf(format string, args ...interface{}) { 72 | a.Log(fmt.Sprintf(format, args...)) 73 | } 74 | 75 | func requestAnimationFrame() { 76 | c := make(chan struct{}) 77 | js.Global.Call("requestAnimationFrame", func() { close(c) }) 78 | <-c 79 | } 80 | -------------------------------------------------------------------------------- /store-editor.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "bytes" 8 | "io" 9 | 10 | "strings" 11 | 12 | "encoding/xml" 13 | 14 | "github.com/aymerick/douceur/parser" 15 | "github.com/dave/flux" 16 | "github.com/dave/jennifer/jen" 17 | ) 18 | 19 | const defaultText = ` 20 |

html2vecty

21 |

22 | Enter HTML here and the vecty syntax will appear opposite. 23 |

24 | 25 |

Class attributes

26 |

27 | 28 |

Style attributes

29 |

30 | 31 |

Special properties

32 | 33 | Props 34 | 35 |

An example

36 | 55 | ` 56 | 57 | func NewEditorStore(app *App) *EditorStore { 58 | s := &EditorStore{ 59 | app: app, 60 | html: strings.TrimSpace(defaultText), 61 | } 62 | s.transcode() 63 | return s 64 | } 65 | 66 | type EditorStore struct { 67 | app *App 68 | html, code string 69 | } 70 | 71 | func (s *EditorStore) Html() string { 72 | return s.html 73 | } 74 | 75 | func (s *EditorStore) Code() string { 76 | return s.code 77 | } 78 | 79 | func (s *EditorStore) Handle(payload *flux.Payload) bool { 80 | switch action := payload.Action.(type) { 81 | case *UserChangedTextAction: 82 | s.html = action.Text 83 | 84 | if err := s.transcode(); err != nil { 85 | s.app.Fail(err) 86 | return true 87 | } 88 | 89 | payload.Notify() 90 | 91 | default: 92 | fmt.Println(action) 93 | } 94 | return true 95 | } 96 | 97 | func (s *EditorStore) transcode() error { 98 | decoder := xml.NewDecoder(bytes.NewBufferString(s.html)) 99 | 100 | EOT := errors.New("end of tag") 101 | call := jen.Options{ 102 | Close: ")", 103 | Multi: true, 104 | Open: "(", 105 | Separator: ",", 106 | } 107 | /*values := jen.Options{ 108 | Close: "}", 109 | Multi: true, 110 | Open: "{", 111 | Separator: ",", 112 | }*/ 113 | 114 | var transcode func(*xml.Decoder) (jen.Code, error) 115 | transcode = func(decoder *xml.Decoder) (code jen.Code, err error) { 116 | token, err := decoder.Token() 117 | if err != nil { 118 | return nil, err 119 | } 120 | 121 | switch token := token.(type) { 122 | case xml.StartElement: 123 | tag := token.Name.Local 124 | vectyFunction, ok := elemNameMap[tag] 125 | vectyPackage := "github.com/gopherjs/vecty/elem" 126 | vectyParamater := "" 127 | if !ok { 128 | vectyFunction = "Tag" 129 | vectyPackage = "github.com/gopherjs/vecty" 130 | vectyParamater = tag 131 | } 132 | var outer error 133 | q := jen.Qual(vectyPackage, vectyFunction).CustomFunc(call, func(g *jen.Group) { 134 | if vectyParamater != "" { 135 | g.Lit(vectyParamater) 136 | } 137 | if len(token.Attr) > 0 { 138 | g.Qual("github.com/gopherjs/vecty", "Markup").CustomFunc(call, func(g *jen.Group) { 139 | for _, v := range token.Attr { 140 | switch { 141 | case v.Name.Local == "style": 142 | css, err := parser.ParseDeclarations(v.Value) 143 | if err != nil { 144 | outer = err 145 | return 146 | } 147 | for _, dec := range css { 148 | if dec.Important { 149 | dec.Value += "!important" 150 | } 151 | g.Qual("github.com/gopherjs/vecty", "Style").Call( 152 | jen.Lit(dec.Property), 153 | jen.Lit(dec.Value), 154 | ) 155 | } 156 | case v.Name.Local == "class": 157 | g.Qual("github.com/gopherjs/vecty", "Class").CallFunc(func(g *jen.Group) { 158 | classes := strings.Split(v.Value, " ") 159 | for _, class := range classes { 160 | g.Lit(class) 161 | } 162 | }) 163 | case strings.HasPrefix(v.Name.Local, "data-"): 164 | attribute := strings.TrimPrefix(v.Name.Local, "data-") 165 | g.Qual("github.com/gopherjs/vecty", "Data").Call( 166 | jen.Lit(attribute), 167 | jen.Lit(v.Value), 168 | ) 169 | case boolProps[v.Name.Local] != "": 170 | value := v.Value == "true" 171 | g.Qual("github.com/gopherjs/vecty/prop", boolProps[v.Name.Local]).Call( 172 | jen.Lit(value), 173 | ) 174 | case stringProps[v.Name.Local] != "": 175 | g.Qual("github.com/gopherjs/vecty/prop", stringProps[v.Name.Local]).Call( 176 | jen.Lit(v.Value), 177 | ) 178 | case v.Name.Local == "xmlns": 179 | g.Qual("github.com/gopherjs/vecty", "Namespace").Call( 180 | jen.Lit(v.Value), 181 | ) 182 | case v.Name.Local == "type" && typeProps[v.Value] != "": 183 | g.Qual("github.com/gopherjs/vecty/prop", "Type").Call( 184 | jen.Qual("github.com/gopherjs/vecty/prop", typeProps[v.Value]), 185 | ) 186 | default: 187 | g.Qual("github.com/gopherjs/vecty", "Attribute").Call( 188 | jen.Lit(v.Name.Local), 189 | jen.Lit(v.Value), 190 | ) 191 | } 192 | } 193 | }) 194 | } 195 | for { 196 | c, err := transcode(decoder) 197 | if err != nil { 198 | if err == EOT { 199 | break 200 | } 201 | outer = err 202 | return 203 | } 204 | if c != nil { 205 | g.Add(c) 206 | } 207 | } 208 | }) 209 | if outer != nil { 210 | return nil, outer 211 | } 212 | return q, nil 213 | case xml.CharData: 214 | s := strings.TrimSpace(string(token)) 215 | if s == "" { 216 | return nil, nil 217 | } 218 | return jen.Qual("github.com/gopherjs/vecty", "Text").Call(jen.Lit(s)), nil 219 | case xml.EndElement: 220 | return nil, EOT 221 | default: 222 | fmt.Printf("%T %#v\n", token, token) 223 | } 224 | return nil, nil 225 | } 226 | 227 | file := jen.NewFile("main") 228 | file.PackageComment("This file was created with https://jsgo.io/dave/html2vecty") 229 | file.ImportNames(map[string]string{ 230 | "github.com/gopherjs/vecty": "vecty", 231 | "github.com/gopherjs/vecty/elem": "elem", 232 | "github.com/gopherjs/vecty/prop": "prop", 233 | "github.com/gopherjs/vecty/event": "event", 234 | "github.com/gopherjs/vecty/style": "style", 235 | }) 236 | var elements []jen.Code 237 | for { 238 | c, err := transcode(decoder) 239 | if err != nil { 240 | if err == io.EOF || err == EOT { 241 | break 242 | } 243 | s.code = fmt.Sprintf("%s", err) 244 | return nil 245 | } 246 | if c != nil { 247 | elements = append(elements, c) 248 | } 249 | } 250 | /* 251 | func main() { 252 | vecty.RenderBody(&Page{}) 253 | } 254 | 255 | type Page struct { 256 | vecty.Core 257 | } 258 | 259 | func (*Page) Render() vecty.ComponentOrHTML { 260 | return elem.Body(...) 261 | } 262 | */ 263 | file.Func().Id("main").Params().Block( 264 | jen.Qual("github.com/gopherjs/vecty", "RenderBody").Call( 265 | jen.Op("&").Id("Page").Values(), 266 | ), 267 | ) 268 | file.Type().Id("Page").Struct( 269 | jen.Qual("github.com/gopherjs/vecty", "Core"), 270 | ) 271 | file.Func().Params(jen.Op("*").Id("Page")).Id("Render").Params().Qual("github.com/gopherjs/vecty", "ComponentOrHTML").Block( 272 | jen.Return( 273 | jen.Qual("github.com/gopherjs/vecty/elem", "Body").Custom(call, elements...), 274 | ), 275 | ) 276 | /*if len(elements) == 1 { 277 | file.Var().Id("Element").Op("=").Add(elements[0]) 278 | } else if len(elements) > 1 { 279 | file.Var().Id("Elements").Op("=").Index().Op("*").Qual("github.com/gopherjs/vecty", "HTML").Custom(values, elements...) 280 | }*/ 281 | 282 | buf := &bytes.Buffer{} 283 | if err := file.Render(buf); err != nil { 284 | s.code = fmt.Sprintf("%s", err) 285 | return nil 286 | } 287 | 288 | s.code = buf.String() 289 | return nil 290 | } 291 | 292 | var elemNameMap = map[string]string{ 293 | "a": "Anchor", 294 | "abbr": "Abbreviation", 295 | "address": "Address", 296 | "area": "Area", 297 | "article": "Article", 298 | "aside": "Aside", 299 | "audio": "Audio", 300 | "b": "Bold", 301 | "base": "Base", 302 | "bdi": "BidirectionalIsolation", 303 | "bdo": "BidirectionalOverride", 304 | "blockquote": "BlockQuote", 305 | "body": "Body", 306 | "br": "Break", 307 | "button": "Button", 308 | "canvas": "Canvas", 309 | "caption": "Caption", 310 | "cite": "Citation", 311 | "code": "Code", 312 | "col": "Column", 313 | "colgroup": "ColumnGroup", 314 | "data": "Data", 315 | "datalist": "DataList", 316 | "dd": "Description", 317 | "del": "DeletedText", 318 | "details": "Details", 319 | "dfn": "Definition", 320 | "dialog": "Dialog", 321 | "div": "Div", 322 | "dl": "DescriptionList", 323 | "dt": "DefinitionTerm", 324 | "em": "Emphasis", 325 | "embed": "Embed", 326 | "fieldset": "FieldSet", 327 | "figcaption": "FigureCaption", 328 | "figure": "Figure", 329 | "footer": "Footer", 330 | "form": "Form", 331 | "h1": "Heading1", 332 | "h2": "Heading2", 333 | "h3": "Heading3", 334 | "h4": "Heading4", 335 | "h5": "Heading5", 336 | "h6": "Heading6", 337 | "header": "Header", 338 | "hgroup": "HeadingsGroup", 339 | "hr": "HorizontalRule", 340 | "i": "Italic", 341 | "iframe": "InlineFrame", 342 | "img": "Image", 343 | "input": "Input", 344 | "ins": "InsertedText", 345 | "kbd": "KeyboardInput", 346 | "label": "Label", 347 | "legend": "Legend", 348 | "li": "ListItem", 349 | "link": "Link", 350 | "main": "Main", 351 | "map": "Map", 352 | "mark": "Mark", 353 | "meta": "Meta", 354 | "meter": "Meter", 355 | "nav": "Navigation", 356 | "noscript": "NoScript", 357 | "object": "Object", 358 | "ol": "OrderedList", 359 | "optgroup": "OptionsGroup", 360 | "option": "Option", 361 | "output": "Output", 362 | "p": "Paragraph", 363 | "param": "Parameter", 364 | "picture": "Picture", 365 | "pre": "Preformatted", 366 | "progress": "Progress", 367 | "q": "Quote", 368 | "rp": "RubyParenthesis", 369 | "rt": "RubyText", 370 | "rtc": "RubyTextContainer", 371 | "ruby": "Ruby", 372 | "s": "Strikethrough", 373 | "samp": "Sample", 374 | "script": "Script", 375 | "section": "Section", 376 | "select": "Select", 377 | "slot": "Slot", 378 | "small": "Small", 379 | "source": "Source", 380 | "span": "Span", 381 | "strong": "Strong", 382 | "style": "Style", 383 | "sub": "Subscript", 384 | "summary": "Summary", 385 | "sup": "Superscript", 386 | "table": "Table", 387 | "tbody": "TableBody", 388 | "td": "TableData", 389 | "template": "Template", 390 | "textarea": "TextArea", 391 | "tfoot": "TableFoot", 392 | "th": "TableHeader", 393 | "thead": "TableHead", 394 | "time": "Time", 395 | "title": "Title", 396 | "tr": "TableRow", 397 | "track": "Track", 398 | "u": "Underline", 399 | "ul": "UnorderedList", 400 | "var": "Variable", 401 | "video": "Video", 402 | "wbr": "WordBreakOpportunity", 403 | } 404 | 405 | var typeProps = map[string]string{ 406 | "button": "TypeButton", 407 | "checkbox": "TypeCheckbox", 408 | "color": "TypeColor", 409 | "date": "TypeDate", 410 | "datetime": "TypeDatetime", 411 | "datetime-local": "TypeDatetimeLocal", 412 | "email": "TypeEmail", 413 | "file": "TypeFile", 414 | "hidden": "TypeHidden", 415 | "image": "TypeImage", 416 | "month": "TypeMonth", 417 | "number": "TypeNumber", 418 | "password": "TypePassword", 419 | "radio": "TypeRadio", 420 | "range": "TypeRange", 421 | "min": "TypeMin", 422 | "max": "TypeMax", 423 | "value": "TypeValue", 424 | "step": "TypeStep", 425 | "reset": "TypeReset", 426 | "search": "TypeSearch", 427 | "submit": "TypeSubmit", 428 | "tel": "TypeTel", 429 | "text": "TypeText", 430 | "time": "TypeTime", 431 | "url": "TypeUrl", 432 | "week": "TypeWeek", 433 | } 434 | 435 | var boolProps = map[string]string{ 436 | "autofocus": "Autofocus", 437 | "checked": "Checked", 438 | } 439 | 440 | var stringProps = map[string]string{ 441 | "for": "For", 442 | "href": "Href", 443 | "id": "ID", 444 | "placeholder": "Placeholder", 445 | "src": "Src", 446 | "value": "Value", 447 | } 448 | --------------------------------------------------------------------------------