|
@@ -0,0 +1,361 @@
|
|
|
+package gen
|
|
|
+
|
|
|
+import (
|
|
|
+ "fmt"
|
|
|
+ "gex/pkg/gen/domquery"
|
|
|
+ "gex/pkg/gen/img"
|
|
|
+ "gex/pkg/gen/layout"
|
|
|
+ "gex/pkg/gen/partial"
|
|
|
+ "math/rand"
|
|
|
+ "os"
|
|
|
+ "path/filepath"
|
|
|
+ "strings"
|
|
|
+)
|
|
|
+
|
|
|
+var appRuleList cssRuleList
|
|
|
+
|
|
|
+//var appJsStr string
|
|
|
+
|
|
|
+func GeneratePages(dir string) error {
|
|
|
+ err := processTemplate(dir)
|
|
|
+ if err != nil {
|
|
|
+ fmt.Println(err)
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ // Process Partials
|
|
|
+ err = processPartials(dir)
|
|
|
+ if err != nil {
|
|
|
+ fmt.Println(err)
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ // Build the layout & blocks
|
|
|
+ err = processLayout(dir)
|
|
|
+ if err != nil {
|
|
|
+ fmt.Println(err)
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ // Process IMG on the generated files
|
|
|
+ err = ProcessImg(dir)
|
|
|
+ if err != nil {
|
|
|
+ fmt.Println(err)
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ // Clean block tags
|
|
|
+ err = cleanBlocks(dir)
|
|
|
+ if err != nil {
|
|
|
+ fmt.Println(err)
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ appRuleList = mergeMediaRules(appRuleList)
|
|
|
+ appRuleList = orderRules(appRuleList)
|
|
|
+ cssStr := rulesToCSSstr(appRuleList)
|
|
|
+
|
|
|
+ os.WriteFile("app/pub/css/app.gen.css", []byte(cssStr), 0655)
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func processTemplate(dir string) error {
|
|
|
+ processFile := func(fp string) error {
|
|
|
+ if strings.Contains(fp, ".gen.tpl") {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ if !strings.Contains(fp, ".tpl") {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ // get the html string
|
|
|
+ data, err := os.ReadFile(fp)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ htmlStr := string(data)
|
|
|
+
|
|
|
+ // add the scope class to attributes
|
|
|
+ className := genRandName(8)
|
|
|
+ htmlStr = AddClassAttr(htmlStr, className)
|
|
|
+
|
|
|
+ // get & strip the css rules
|
|
|
+ cssStr := ExtractCSS(&htmlStr)
|
|
|
+
|
|
|
+ ruleList := toRuleList(cssStr)
|
|
|
+
|
|
|
+ // scope the rules with css class
|
|
|
+ ruleList = scopeSelectors(ruleList, className)
|
|
|
+
|
|
|
+ appRuleList = append(appRuleList, ruleList...)
|
|
|
+
|
|
|
+ // Process JS on the generated file
|
|
|
+ htmlStr = ProcessJs(htmlStr, className)
|
|
|
+
|
|
|
+ // write the .gen.html file as
|
|
|
+ newPath := strings.Replace(fp, ".tpl", ".gen.tpl", 1)
|
|
|
+ return os.WriteFile(newPath, []byte(htmlStr), 0655)
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ walk := func(path string, info os.FileInfo, err error) error {
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ if info.IsDir() {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ return processFile(path)
|
|
|
+
|
|
|
+ }
|
|
|
+ return filepath.Walk(dir, walk)
|
|
|
+}
|
|
|
+
|
|
|
+func processPartials(dir string) error {
|
|
|
+ processFile := func(fp string) error {
|
|
|
+ if !strings.Contains(fp, ".gen.tpl") {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ data, err := os.ReadFile(fp)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ htmlStr := string(data)
|
|
|
+
|
|
|
+ htmlStr, err = partial.ProcessHTML(htmlStr)
|
|
|
+ if err != nil {
|
|
|
+ fmt.Println("\n" + fp)
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ return os.WriteFile(fp, []byte(htmlStr), 0655)
|
|
|
+ }
|
|
|
+
|
|
|
+ walk := func(path string, info os.FileInfo, err error) error {
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ if info.IsDir() {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ return processFile(path)
|
|
|
+
|
|
|
+ }
|
|
|
+ err := filepath.Walk(dir, walk)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func processLayout(dir string) error {
|
|
|
+ processFile := func(fp string) error {
|
|
|
+ if !strings.Contains(fp, ".gen.tpl") {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ data, err := os.ReadFile(fp)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ htmlStr := string(data)
|
|
|
+
|
|
|
+ htmlStr, err = layout.ProcessHTML(htmlStr)
|
|
|
+ if err != nil {
|
|
|
+ fmt.Println("\n" + fp)
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ os.WriteFile(fp, []byte(htmlStr), 0655)
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ walk := func(path string, info os.FileInfo, err error) error {
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ if info.IsDir() {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ return processFile(path)
|
|
|
+ }
|
|
|
+
|
|
|
+ filepath.Walk(dir, walk)
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func ProcessJs(htmlStr string, className string) string {
|
|
|
+
|
|
|
+ dom := domquery.LoadHTML(htmlStr)
|
|
|
+
|
|
|
+ scripts := dom.QuerySelectorAll("script")
|
|
|
+
|
|
|
+ i := 0
|
|
|
+ jsTag := ""
|
|
|
+ for _, script := range scripts {
|
|
|
+ scriptName := className + fmt.Sprintf("%d", i)
|
|
|
+
|
|
|
+ if script.GetAttribute("inline") != "" {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ if script.GetAttribute("src") != "" {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ jsStr := script.InnerHTML()
|
|
|
+
|
|
|
+ funcStr := `if (typeof ` + scriptName + ` === 'undefined') {`
|
|
|
+ funcStr += `window.` + scriptName + ` = () => {`
|
|
|
+ funcStr += jsStr
|
|
|
+ funcStr += `
|
|
|
+ if(typeof unload == "undefined")
|
|
|
+ return
|
|
|
+
|
|
|
+ const watchInt = setInterval(() => {
|
|
|
+ let watch = document.querySelector(".` + className + `");
|
|
|
+ if (!watch) {
|
|
|
+ unload();
|
|
|
+ clearInterval(watchInt);
|
|
|
+ }
|
|
|
+ },100);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ` + scriptName + `()
|
|
|
+ `
|
|
|
+
|
|
|
+ srcPath := "app/pub/gen/" + scriptName + ".gen.js"
|
|
|
+ os.WriteFile(srcPath, []byte(funcStr), 0655)
|
|
|
+
|
|
|
+ jsTag += `<script defer src="/` + srcPath + `"></script>`
|
|
|
+
|
|
|
+ if script.GetAttribute("type") == "module" {
|
|
|
+ jsTag = `<script type="module" defer src="/` + srcPath + `"></script>`
|
|
|
+ }
|
|
|
+
|
|
|
+ script.Remove()
|
|
|
+
|
|
|
+ i++
|
|
|
+ }
|
|
|
+ insDiv := dom.QuerySelector("." + className)
|
|
|
+ insDiv.SetInnerHTML(jsTag + insDiv.InnerHTML())
|
|
|
+
|
|
|
+ htmlStr = dom.InnerHTML()
|
|
|
+
|
|
|
+ return htmlStr
|
|
|
+}
|
|
|
+
|
|
|
+func ProcessImg(dir string) error {
|
|
|
+
|
|
|
+ processFile := func(fp string) error {
|
|
|
+ htmlDir := filepath.Dir(fp)
|
|
|
+
|
|
|
+ if !strings.Contains(fp, ".gen.tpl") {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ // get the html string
|
|
|
+ data, err := os.ReadFile(fp)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ htmlStr := string(data)
|
|
|
+
|
|
|
+ document := domquery.LoadHTML(htmlStr)
|
|
|
+
|
|
|
+ imgTags := document.QuerySelectorAll("img")
|
|
|
+
|
|
|
+ for _, tag := range imgTags {
|
|
|
+ if !(tag.HasAttribute("webp") && tag.HasAttribute("src")) {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ img.Process(htmlDir, tag)
|
|
|
+ }
|
|
|
+
|
|
|
+ htmlStr = document.InnerHTML()
|
|
|
+ os.WriteFile(fp, []byte(htmlStr), 0655)
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ walk := func(path string, info os.FileInfo, err error) error {
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ if info.IsDir() {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ return processFile(path)
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ return filepath.Walk(dir, walk)
|
|
|
+}
|
|
|
+
|
|
|
+func cleanBlocks(dir string) error {
|
|
|
+ processFile := func(fp string) error {
|
|
|
+ if !strings.Contains(fp, ".gen.tpl") {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ // get the html string
|
|
|
+ data, err := os.ReadFile(fp)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ htmlStr := string(data)
|
|
|
+ dom := domquery.LoadHTML(htmlStr)
|
|
|
+
|
|
|
+ var clean func()
|
|
|
+ clean = func() {
|
|
|
+ blocks := dom.QuerySelectorAll("block")
|
|
|
+
|
|
|
+ // grab the top block only as blocks is invalid after a SetOuterHTML event
|
|
|
+ for _, block := range blocks {
|
|
|
+ block.SetOuterHTML(block.InnerHTML())
|
|
|
+ clean()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+ clean()
|
|
|
+
|
|
|
+ htmlStr = dom.InnerHTML()
|
|
|
+ os.WriteFile(fp, []byte(htmlStr), 0655)
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ walk := func(dir string, info os.FileInfo, err error) error {
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ if info.IsDir() {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ return processFile(dir)
|
|
|
+ }
|
|
|
+ return filepath.Walk(dir, walk)
|
|
|
+}
|
|
|
+
|
|
|
+// generate a random string to use as a class name
|
|
|
+func genRandName(length int) string {
|
|
|
+ const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
|
+ b := make([]byte, length)
|
|
|
+ for i := range b {
|
|
|
+ b[i] = charset[rand.Intn(len(charset))]
|
|
|
+ }
|
|
|
+ return string(b)
|
|
|
+}
|