refactor(html): share deterministic attribute rendering
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
2e8886bbd7
commit
cc75f3b533
3 changed files with 29 additions and 40 deletions
18
layout.go
18
layout.go
|
|
@ -3,7 +3,6 @@ package html
|
|||
import (
|
||||
"errors"
|
||||
"maps"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
|
@ -224,20 +223,9 @@ func (l *Layout) Render(ctx *Context) string {
|
|||
|
||||
b.WriteByte('<')
|
||||
b.WriteString(escapeHTML(meta.tag))
|
||||
if len(l.attrs) > 0 {
|
||||
keys := slices.Collect(maps.Keys(l.attrs))
|
||||
slices.Sort(keys)
|
||||
for _, key := range keys {
|
||||
if key == "role" || key == "data-block" {
|
||||
continue
|
||||
}
|
||||
b.WriteByte(' ')
|
||||
b.WriteString(escapeHTML(key))
|
||||
b.WriteString(`="`)
|
||||
b.WriteString(escapeAttr(l.attrs[key]))
|
||||
b.WriteByte('"')
|
||||
}
|
||||
}
|
||||
writeSortedAttrs(&b, l.attrs, func(key string) bool {
|
||||
return key == "role" || key == "data-block"
|
||||
})
|
||||
b.WriteString(` role="`)
|
||||
b.WriteString(escapeAttr(meta.role))
|
||||
b.WriteString(`" data-block="`)
|
||||
|
|
|
|||
33
node.go
33
node.go
|
|
@ -117,6 +117,28 @@ func escapeAttr(s string) string {
|
|||
return html.EscapeString(s)
|
||||
}
|
||||
|
||||
// writeSortedAttrs renders a deterministic attribute list to the builder.
|
||||
// An optional skip callback can omit reserved keys while preserving ordering
|
||||
// for the remaining attributes.
|
||||
func writeSortedAttrs(b *strings.Builder, attrs map[string]string, skip func(string) bool) {
|
||||
if len(attrs) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
keys := slices.Collect(maps.Keys(attrs))
|
||||
slices.Sort(keys)
|
||||
for _, key := range keys {
|
||||
if skip != nil && skip(key) {
|
||||
continue
|
||||
}
|
||||
b.WriteByte(' ')
|
||||
b.WriteString(escapeHTML(key))
|
||||
b.WriteString(`="`)
|
||||
b.WriteString(escapeAttr(attrs[key]))
|
||||
b.WriteByte('"')
|
||||
}
|
||||
}
|
||||
|
||||
// --- rawNode ---
|
||||
|
||||
type rawNode struct {
|
||||
|
|
@ -622,16 +644,7 @@ func (n *elNode) renderWithPath(ctx *Context, path string) string {
|
|||
b.WriteByte('<')
|
||||
b.WriteString(escapeHTML(n.tag))
|
||||
|
||||
// Sort attribute keys for deterministic output.
|
||||
keys := slices.Collect(maps.Keys(n.attrs))
|
||||
slices.Sort(keys)
|
||||
for _, key := range keys {
|
||||
b.WriteByte(' ')
|
||||
b.WriteString(escapeHTML(key))
|
||||
b.WriteString(`="`)
|
||||
b.WriteString(escapeAttr(n.attrs[key]))
|
||||
b.WriteByte('"')
|
||||
}
|
||||
writeSortedAttrs(&b, n.attrs, nil)
|
||||
|
||||
b.WriteByte('>')
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package html
|
|||
|
||||
import (
|
||||
"maps"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
|
@ -232,20 +231,9 @@ func (r *Responsive) renderWithPath(ctx *Context, path string) string {
|
|||
var b strings.Builder
|
||||
for _, v := range r.variants {
|
||||
b.WriteString(`<div`)
|
||||
if len(r.attrs) > 0 {
|
||||
keys := slices.Collect(maps.Keys(r.attrs))
|
||||
slices.Sort(keys)
|
||||
for _, key := range keys {
|
||||
if key == "data-variant" {
|
||||
continue
|
||||
}
|
||||
b.WriteByte(' ')
|
||||
b.WriteString(escapeHTML(key))
|
||||
b.WriteString(`="`)
|
||||
b.WriteString(escapeAttr(r.attrs[key]))
|
||||
b.WriteByte('"')
|
||||
}
|
||||
}
|
||||
writeSortedAttrs(&b, r.attrs, func(key string) bool {
|
||||
return key == "data-variant"
|
||||
})
|
||||
b.WriteString(` data-variant="`)
|
||||
b.WriteString(escapeAttr(v.name))
|
||||
b.WriteString(`">`)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue