URI: 
       publisher: Improve class collector for dynamic classes - hugo - [fork] hugo port for 9front
  HTML git clone git@git.drkhsh.at/hugo.git
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
   DIR README
   DIR LICENSE
       ---
   DIR commit d8c94c354afb286b4fba9b883e49c1bd2c326bb3
   DIR parent 6bbec9001445f623cead19de6811ee960cc53a10
  HTML Author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
       Date:   Wed, 19 Jul 2023 17:32:19 +0200
       
       publisher: Improve class collector for dynamic classes
       
       E.g.
       
       * AlpinesJS' :class="isTrue 'class1' : 'class2'"
       * And dynamic classes with colon in them, e.g. `hover:bg-white`
       
       Diffstat:
         M publisher/htmlElementsCollector.go  |      56 +++++++++++++++++++++++++------
         M publisher/htmlElementsCollector_te… |       6 +++++-
       
       2 files changed, 51 insertions(+), 11 deletions(-)
       ---
   DIR diff --git a/publisher/htmlElementsCollector.go b/publisher/htmlElementsCollector.go
       @@ -32,7 +32,7 @@ const eof = -1
        
        var (
                htmlJsonFixer = strings.NewReplacer(", ", "\n")
       -        jsonAttrRe    = regexp.MustCompile(`'?(.*?)'?:.*`)
       +        jsonAttrRe    = regexp.MustCompile(`'?(.*?)'?:\s.*`)
                classAttrRe   = regexp.MustCompile(`(?i)^class$|transition`)
        
                skipInnerElementRe = regexp.MustCompile(`(?i)^(pre|textarea|script|style)`)
       @@ -404,21 +404,31 @@ func (w *htmlElementsCollectorWriter) parseHTMLElement(elStr string) (el htmlEle
                                                if conf.DisableClasses {
                                                        continue
                                                }
       +
                                                if classAttrRe.MatchString(a.Key) {
                                                        el.Classes = append(el.Classes, strings.Fields(a.Val)...)
                                                } else {
                                                        key := strings.ToLower(a.Key)
                                                        val := strings.TrimSpace(a.Val)
       -                                                if strings.Contains(key, "class") && strings.HasPrefix(val, "{") {
       -                                                        // This looks like a Vue or AlpineJS class binding.
       -                                                        val = htmlJsonFixer.Replace(strings.Trim(val, "{}"))
       -                                                        lines := strings.Split(val, "\n")
       -                                                        for i, l := range lines {
       -                                                                lines[i] = strings.TrimSpace(l)
       +
       +                                                if strings.Contains(key, ":class") {
       +                                                        if strings.HasPrefix(val, "{") {
       +                                                                // This looks like a Vue or AlpineJS class binding.
       +                                                                val = htmlJsonFixer.Replace(strings.Trim(val, "{}"))
       +                                                                lines := strings.Split(val, "\n")
       +                                                                for i, l := range lines {
       +                                                                        lines[i] = strings.TrimSpace(l)
       +                                                                }
       +                                                                val = strings.Join(lines, "\n")
       +
       +                                                                val = jsonAttrRe.ReplaceAllString(val, "$1")
       +
       +                                                                el.Classes = append(el.Classes, strings.Fields(val)...)
                                                                }
       -                                                        val = strings.Join(lines, "\n")
       -                                                        val = jsonAttrRe.ReplaceAllString(val, "$1")
       -                                                        el.Classes = append(el.Classes, strings.Fields(val)...)
       +                                                        // Also add single quoted strings.
       +                                                        // This may introduce some false positives, but it covers some missing cases in the above.
       +                                                        // E.g. AlpinesJS' :class="isTrue 'class1' : 'class2'"
       +                                                        el.Classes = append(el.Classes, extractSingleQuotedStrings(val)...)
                                                        }
                                                }
                                        }
       @@ -519,3 +529,29 @@ LOOP:
        func isSpace(b byte) bool {
                return b == ' ' || b == '\t' || b == '\n'
        }
       +
       +func extractSingleQuotedStrings(s string) []string {
       +        var (
       +                inQuote bool
       +                lo      int
       +                hi      int
       +        )
       +
       +        var words []string
       +
       +        for i, r := range s {
       +                switch {
       +                case r == '\'':
       +                        if !inQuote {
       +                                inQuote = true
       +                                lo = i + 1
       +                        } else {
       +                                inQuote = false
       +                                hi = i
       +                                words = append(words, strings.Fields(s[lo:hi])...)
       +                        }
       +                }
       +        }
       +
       +        return words
       +}
   DIR diff --git a/publisher/htmlElementsCollector_test.go b/publisher/htmlElementsCollector_test.go
       @@ -99,6 +99,8 @@ func TestClassCollector(t *testing.T) {
                         pl-2: b == 3,
                        'text-gray-600': (a > 1)
                        }" class="block w-36 cursor-pointer pr-3 no-underline capitalize"></a>`, f("a", "block capitalize cursor-pointer no-underline pl-2 pl-3 pr-3 text-a text-b text-gray-600 w-36", "")},
       +                {"AlpineJS bind 6", `<button :class="isActive(32) ? 'border-gray-500 bg-white pt border-t-2' : 'border-transparent hover:bg-gray-100'"></button>`, f("button", "bg-white border-gray-500 border-t-2 border-transparent hover:bg-gray-100 pt", "")},
       +                {"AlpineJS bind 7", `<button :class="{ 'border-gray-500 bg-white pt border-t-2': isActive(32), 'border-transparent hover:bg-gray-100': !isActive(32) }"></button>`, f("button", "bg-white border-gray-500 border-t-2 border-transparent hover:bg-gray-100 pt", "")},
                        {"AlpineJS transition 1", `<div x-transition:enter-start="opacity-0 transform mobile:-translate-x-8 sm:-translate-y-8">`, f("div", "mobile:-translate-x-8 opacity-0 sm:-translate-y-8 transform", "")},
                        {"Vue bind", `<div v-bind:class="{ active: isActive }"></div>`, f("div", "active", "")},
                        // Issue #7746
       @@ -136,7 +138,9 @@ func TestClassCollector(t *testing.T) {
                                {minify: true},
                        } {
        
       -                        c.Run(fmt.Sprintf("%s--minify-%t", test.name, variant.minify), func(c *qt.C) {
       +                        name := fmt.Sprintf("%s--minify-%t", test.name, variant.minify)
       +
       +                        c.Run(name, func(c *qt.C) {
                                        w := newHTMLElementsCollectorWriter(newHTMLElementsCollector(
                                                config.BuildStats{Enable: true},
                                        ))