diff --git a/app/theme/twentyseventeen/script.go b/app/theme/twentyseventeen/script.go index 3f98a62..611edb0 100644 --- a/app/theme/twentyseventeen/script.go +++ b/app/theme/twentyseventeen/script.go @@ -21,9 +21,9 @@ func pushScripts(h *wp.Handle) { []string{"twentyseventeen-style"}, "20191025", "") } - scriptloader.AddData("twentyseventeen-ie8", "conditional", "lt IE 9") + scriptloader.AddScriptData("twentyseventeen-ie8", "conditional", "lt IE 9") scriptloader.EnqueueScripts("html5", "/assets/js/html5.js", nil, "20161020", false) - scriptloader.AddData("html5", "conditional", "lt IE 9") + scriptloader.AddScriptData("html5", "conditional", "lt IE 9") scriptloader.EnqueueScripts("twentyseventeen-skip-link-focus-fix", "/assets/js/skip-link-focus-fix.js", nil, "20161114", true) diff --git a/app/theme/twentyseventeen/twentyseventeen.go b/app/theme/twentyseventeen/twentyseventeen.go index 0027cdd..0325a0f 100644 --- a/app/theme/twentyseventeen/twentyseventeen.go +++ b/app/theme/twentyseventeen/twentyseventeen.go @@ -43,7 +43,7 @@ func configs(h *wp.Handle) { components.WidgetArea(h) pushScripts(h) h.PushRender(constraints.AllStats, wp.NewHandleFn(calCustomHeader, 10.005, "calCustomHeader")) - h.SetComponentsArgs(widgets.Widget, map[string]string{ + wp.SetComponentsArgs(h, widgets.Widget, map[string]string{ "{$before_widget}": `
`, "{$after_widget}": `
`, }) diff --git a/app/theme/wp/components.go b/app/theme/wp/components.go index 8c59ae4..c91d131 100644 --- a/app/theme/wp/components.go +++ b/app/theme/wp/components.go @@ -190,7 +190,7 @@ func MergeComponentsArgsForMap[K comparable, V any](h *Handle, name string, m ma } } -func (h *Handle) SetComponentsArgs(key string, value any) { +func SetComponentsArgs(h *Handle, key string, value any) { h.componentsArgs[key] = value } diff --git a/app/theme/wp/scriptloader/defaultscriptload.go b/app/theme/wp/scriptloader/defaultscriptload.go index 6cc8a6c..db13a89 100644 --- a/app/theme/wp/scriptloader/defaultscriptload.go +++ b/app/theme/wp/scriptloader/defaultscriptload.go @@ -366,121 +366,121 @@ func defaultTranslate() { } func defaultAddData() { - AddData("json2", "conditional", `lt IE 8`) - AddData("wp-embed-template-ie", "conditional", `lte IE 8`) - AddData("wp-block-library-theme", "path", `wp-includes/css/dist/block-library/theme.min.css`) - AddData("wp-block-editor", "path", `/wp-includes/css/dist/block-editor/style.min.css`) - AddData("wp-block-library", "path", `/wp-includes/css/dist/block-library/style.min.css`) - AddData("wp-block-directory", "path", `/wp-includes/css/dist/block-directory/style.min.css`) - AddData("wp-components", "path", `/wp-includes/css/dist/components/style.min.css`) - AddData("wp-edit-post", "path", `/wp-includes/css/dist/edit-post/style.min.css`) - AddData("wp-editor", "path", `/wp-includes/css/dist/editor/style.min.css`) - AddData("wp-format-library", "path", `/wp-includes/css/dist/format-library/style.min.css`) - AddData("wp-list-reusable-blocks", "path", `/wp-includes/css/dist/list-reusable-blocks/style.min.css`) - AddData("wp-reusable-blocks", "path", `/wp-includes/css/dist/reusable-blocks/style.min.css`) - AddData("wp-nux", "path", `/wp-includes/css/dist/nux/style.min.css`) - AddData("wp-widgets", "path", `/wp-includes/css/dist/widgets/style.min.css`) - AddData("wp-edit-widgets", "path", `/wp-includes/css/dist/edit-widgets/style.min.css`) - AddData("wp-customize-widgets", "path", `/wp-includes/css/dist/customize-widgets/style.min.css`) - AddData("wp-edit-site", "path", `/wp-includes/css/dist/edit-site/style.min.css`) - AddData("common", "rtl", `replace`) - AddData("common", "suffix", `.min`) - AddData("forms", "rtl", `replace`) - AddData("forms", "suffix", `.min`) - AddData("admin-menu", "rtl", `replace`) - AddData("admin-menu", "suffix", `.min`) - AddData("dashboard", "rtl", `replace`) - AddData("dashboard", "suffix", `.min`) - AddData("list-tables", "rtl", `replace`) - AddData("list-tables", "suffix", `.min`) - AddData("edit", "rtl", `replace`) - AddData("edit", "suffix", `.min`) - AddData("revisions", "rtl", `replace`) - AddData("revisions", "suffix", `.min`) - AddData("media", "rtl", `replace`) - AddData("media", "suffix", `.min`) - AddData("themes", "rtl", `replace`) - AddData("themes", "suffix", `.min`) - AddData("about", "rtl", `replace`) - AddData("about", "suffix", `.min`) - AddData("nav-menus", "rtl", `replace`) - AddData("nav-menus", "suffix", `.min`) - AddData("widgets", "rtl", `replace`) - AddData("widgets", "suffix", `.min`) - AddData("site-icon", "rtl", `replace`) - AddData("site-icon", "suffix", `.min`) - AddData("l10n", "rtl", `replace`) - AddData("l10n", "suffix", `.min`) - AddData("install", "rtl", `replace`) - AddData("install", "suffix", `.min`) - AddData("wp-color-picker", "rtl", `replace`) - AddData("wp-color-picker", "suffix", `.min`) - AddData("customize-controls", "rtl", `replace`) - AddData("customize-controls", "suffix", `.min`) - AddData("customize-widgets", "rtl", `replace`) - AddData("customize-widgets", "suffix", `.min`) - AddData("customize-nav-menus", "rtl", `replace`) - AddData("customize-nav-menus", "suffix", `.min`) - AddData("customize-preview", "rtl", `replace`) - AddData("customize-preview", "suffix", `.min`) - AddData("login", "rtl", `replace`) - AddData("login", "suffix", `.min`) - AddData("site-health", "rtl", `replace`) - AddData("site-health", "suffix", `.min`) - AddData("buttons", "rtl", `replace`) - AddData("buttons", "suffix", `.min`) - AddData("admin-bar", "rtl", `replace`) - AddData("admin-bar", "suffix", `.min`) - AddData("wp-auth-check", "rtl", `replace`) - AddData("wp-auth-check", "suffix", `.min`) - AddData("editor-buttons", "rtl", `replace`) - AddData("editor-buttons", "suffix", `.min`) - AddData("media-views", "rtl", `replace`) - AddData("media-views", "suffix", `.min`) - AddData("wp-pointer", "rtl", `replace`) - AddData("wp-pointer", "suffix", `.min`) - AddData("wp-jquery-ui-dialog", "rtl", `replace`) - AddData("wp-jquery-ui-dialog", "suffix", `.min`) - AddData("wp-reset-editor-styles", "rtl", `replace`) - AddData("wp-reset-editor-styles", "suffix", `.min`) - AddData("wp-editor-classic-layout-styles", "rtl", `replace`) - AddData("wp-editor-classic-layout-styles", "suffix", `.min`) - AddData("wp-block-library-theme", "rtl", `replace`) - AddData("wp-block-library-theme", "suffix", `.min`) - AddData("wp-edit-blocks", "rtl", `replace`) - AddData("wp-edit-blocks", "suffix", `.min`) - AddData("wp-block-editor", "rtl", `replace`) - AddData("wp-block-editor", "suffix", `.min`) - AddData("wp-block-library", "rtl", `replace`) - AddData("wp-block-library", "suffix", `.min`) - AddData("wp-block-directory", "rtl", `replace`) - AddData("wp-block-directory", "suffix", `.min`) - AddData("wp-components", "rtl", `replace`) - AddData("wp-components", "suffix", `.min`) - AddData("wp-customize-widgets", "rtl", `replace`) - AddData("wp-customize-widgets", "suffix", `.min`) - AddData("wp-edit-post", "rtl", `replace`) - AddData("wp-edit-post", "suffix", `.min`) - AddData("wp-edit-site", "rtl", `replace`) - AddData("wp-edit-site", "suffix", `.min`) - AddData("wp-edit-widgets", "rtl", `replace`) - AddData("wp-edit-widgets", "suffix", `.min`) - AddData("wp-editor", "rtl", `replace`) - AddData("wp-editor", "suffix", `.min`) - AddData("wp-format-library", "rtl", `replace`) - AddData("wp-format-library", "suffix", `.min`) - AddData("wp-list-reusable-blocks", "rtl", `replace`) - AddData("wp-list-reusable-blocks", "suffix", `.min`) - AddData("wp-reusable-blocks", "rtl", `replace`) - AddData("wp-reusable-blocks", "suffix", `.min`) - AddData("wp-nux", "rtl", `replace`) - AddData("wp-nux", "suffix", `.min`) - AddData("wp-widgets", "rtl", `replace`) - AddData("wp-widgets", "suffix", `.min`) - AddData("deprecated-media", "rtl", `replace`) - AddData("deprecated-media", "suffix", `.min`) - AddData("farbtastic", "rtl", `replace`) - AddData("farbtastic", "suffix", `.min`) + AddScriptData("json2", "conditional", `lt IE 8`) + AddScriptData("wp-embed-template-ie", "conditional", `lte IE 8`) + AddScriptData("wp-block-library-theme", "path", `wp-includes/css/dist/block-library/theme.min.css`) + AddScriptData("wp-block-editor", "path", `/wp-includes/css/dist/block-editor/style.min.css`) + AddScriptData("wp-block-library", "path", `/wp-includes/css/dist/block-library/style.min.css`) + AddScriptData("wp-block-directory", "path", `/wp-includes/css/dist/block-directory/style.min.css`) + AddScriptData("wp-components", "path", `/wp-includes/css/dist/components/style.min.css`) + AddScriptData("wp-edit-post", "path", `/wp-includes/css/dist/edit-post/style.min.css`) + AddScriptData("wp-editor", "path", `/wp-includes/css/dist/editor/style.min.css`) + AddScriptData("wp-format-library", "path", `/wp-includes/css/dist/format-library/style.min.css`) + AddScriptData("wp-list-reusable-blocks", "path", `/wp-includes/css/dist/list-reusable-blocks/style.min.css`) + AddScriptData("wp-reusable-blocks", "path", `/wp-includes/css/dist/reusable-blocks/style.min.css`) + AddScriptData("wp-nux", "path", `/wp-includes/css/dist/nux/style.min.css`) + AddScriptData("wp-widgets", "path", `/wp-includes/css/dist/widgets/style.min.css`) + AddScriptData("wp-edit-widgets", "path", `/wp-includes/css/dist/edit-widgets/style.min.css`) + AddScriptData("wp-customize-widgets", "path", `/wp-includes/css/dist/customize-widgets/style.min.css`) + AddScriptData("wp-edit-site", "path", `/wp-includes/css/dist/edit-site/style.min.css`) + AddScriptData("common", "rtl", `replace`) + AddScriptData("common", "suffix", `.min`) + AddScriptData("forms", "rtl", `replace`) + AddScriptData("forms", "suffix", `.min`) + AddScriptData("admin-menu", "rtl", `replace`) + AddScriptData("admin-menu", "suffix", `.min`) + AddScriptData("dashboard", "rtl", `replace`) + AddScriptData("dashboard", "suffix", `.min`) + AddScriptData("list-tables", "rtl", `replace`) + AddScriptData("list-tables", "suffix", `.min`) + AddScriptData("edit", "rtl", `replace`) + AddScriptData("edit", "suffix", `.min`) + AddScriptData("revisions", "rtl", `replace`) + AddScriptData("revisions", "suffix", `.min`) + AddScriptData("media", "rtl", `replace`) + AddScriptData("media", "suffix", `.min`) + AddScriptData("themes", "rtl", `replace`) + AddScriptData("themes", "suffix", `.min`) + AddScriptData("about", "rtl", `replace`) + AddScriptData("about", "suffix", `.min`) + AddScriptData("nav-menus", "rtl", `replace`) + AddScriptData("nav-menus", "suffix", `.min`) + AddScriptData("widgets", "rtl", `replace`) + AddScriptData("widgets", "suffix", `.min`) + AddScriptData("site-icon", "rtl", `replace`) + AddScriptData("site-icon", "suffix", `.min`) + AddScriptData("l10n", "rtl", `replace`) + AddScriptData("l10n", "suffix", `.min`) + AddScriptData("install", "rtl", `replace`) + AddScriptData("install", "suffix", `.min`) + AddScriptData("wp-color-picker", "rtl", `replace`) + AddScriptData("wp-color-picker", "suffix", `.min`) + AddScriptData("customize-controls", "rtl", `replace`) + AddScriptData("customize-controls", "suffix", `.min`) + AddScriptData("customize-widgets", "rtl", `replace`) + AddScriptData("customize-widgets", "suffix", `.min`) + AddScriptData("customize-nav-menus", "rtl", `replace`) + AddScriptData("customize-nav-menus", "suffix", `.min`) + AddScriptData("customize-preview", "rtl", `replace`) + AddScriptData("customize-preview", "suffix", `.min`) + AddScriptData("login", "rtl", `replace`) + AddScriptData("login", "suffix", `.min`) + AddScriptData("site-health", "rtl", `replace`) + AddScriptData("site-health", "suffix", `.min`) + AddScriptData("buttons", "rtl", `replace`) + AddScriptData("buttons", "suffix", `.min`) + AddScriptData("admin-bar", "rtl", `replace`) + AddScriptData("admin-bar", "suffix", `.min`) + AddScriptData("wp-auth-check", "rtl", `replace`) + AddScriptData("wp-auth-check", "suffix", `.min`) + AddScriptData("editor-buttons", "rtl", `replace`) + AddScriptData("editor-buttons", "suffix", `.min`) + AddScriptData("media-views", "rtl", `replace`) + AddScriptData("media-views", "suffix", `.min`) + AddScriptData("wp-pointer", "rtl", `replace`) + AddScriptData("wp-pointer", "suffix", `.min`) + AddScriptData("wp-jquery-ui-dialog", "rtl", `replace`) + AddScriptData("wp-jquery-ui-dialog", "suffix", `.min`) + AddScriptData("wp-reset-editor-styles", "rtl", `replace`) + AddScriptData("wp-reset-editor-styles", "suffix", `.min`) + AddScriptData("wp-editor-classic-layout-styles", "rtl", `replace`) + AddScriptData("wp-editor-classic-layout-styles", "suffix", `.min`) + AddScriptData("wp-block-library-theme", "rtl", `replace`) + AddScriptData("wp-block-library-theme", "suffix", `.min`) + AddScriptData("wp-edit-blocks", "rtl", `replace`) + AddScriptData("wp-edit-blocks", "suffix", `.min`) + AddScriptData("wp-block-editor", "rtl", `replace`) + AddScriptData("wp-block-editor", "suffix", `.min`) + AddScriptData("wp-block-library", "rtl", `replace`) + AddScriptData("wp-block-library", "suffix", `.min`) + AddScriptData("wp-block-directory", "rtl", `replace`) + AddScriptData("wp-block-directory", "suffix", `.min`) + AddScriptData("wp-components", "rtl", `replace`) + AddScriptData("wp-components", "suffix", `.min`) + AddScriptData("wp-customize-widgets", "rtl", `replace`) + AddScriptData("wp-customize-widgets", "suffix", `.min`) + AddScriptData("wp-edit-post", "rtl", `replace`) + AddScriptData("wp-edit-post", "suffix", `.min`) + AddScriptData("wp-edit-site", "rtl", `replace`) + AddScriptData("wp-edit-site", "suffix", `.min`) + AddScriptData("wp-edit-widgets", "rtl", `replace`) + AddScriptData("wp-edit-widgets", "suffix", `.min`) + AddScriptData("wp-editor", "rtl", `replace`) + AddScriptData("wp-editor", "suffix", `.min`) + AddScriptData("wp-format-library", "rtl", `replace`) + AddScriptData("wp-format-library", "suffix", `.min`) + AddScriptData("wp-list-reusable-blocks", "rtl", `replace`) + AddScriptData("wp-list-reusable-blocks", "suffix", `.min`) + AddScriptData("wp-reusable-blocks", "rtl", `replace`) + AddScriptData("wp-reusable-blocks", "suffix", `.min`) + AddScriptData("wp-nux", "rtl", `replace`) + AddScriptData("wp-nux", "suffix", `.min`) + AddScriptData("wp-widgets", "rtl", `replace`) + AddScriptData("wp-widgets", "suffix", `.min`) + AddScriptData("deprecated-media", "rtl", `replace`) + AddScriptData("deprecated-media", "suffix", `.min`) + AddScriptData("farbtastic", "rtl", `replace`) + AddScriptData("farbtastic", "suffix", `.min`) } diff --git a/app/theme/wp/scriptloader/defaultstyles.go b/app/theme/wp/scriptloader/defaultstyles.go index ca2e026..4c0f9ed 100644 --- a/app/theme/wp/scriptloader/defaultstyles.go +++ b/app/theme/wp/scriptloader/defaultstyles.go @@ -2,6 +2,6 @@ package scriptloader import "github.com/fthvgb1/wp-go/safety" -func defaultStyles(m *safety.Map[string, *Script], suffix string) { +func defaultStyles(m *safety.Map[string, *Style], suffix string) { } diff --git a/app/theme/wp/scriptloader/head.go b/app/theme/wp/scriptloader/head.go index 8dca1e2..906b8a4 100644 --- a/app/theme/wp/scriptloader/head.go +++ b/app/theme/wp/scriptloader/head.go @@ -1,10 +1,15 @@ package scriptloader import ( + "encoding/json" + "github.com/fthvgb1/wp-go/app/cmd/reload" + "github.com/fthvgb1/wp-go/app/pkg/config" "github.com/fthvgb1/wp-go/app/pkg/logs" "github.com/fthvgb1/wp-go/app/theme/wp" "github.com/fthvgb1/wp-go/helper/slice" + str "github.com/fthvgb1/wp-go/helper/strings" "os" + "path/filepath" ) type _style struct { @@ -50,13 +55,62 @@ func MaybeInlineStyles(h *wp.Handle) { if totalInlineSize+i.size > totalInlineLimit { break } - css, err := os.ReadFile(i.path) - if err != nil { - logs.Error(err, "read file ", i.path) - continue - } + path := filepath.Join(i.path) + css := reload.GetAnyValMapBy("script-loader-MaybeInlineStyles", i.handle, path, func(a string) string { + css, err := os.ReadFile(i.path) + if err != nil { + logs.Error(err, "read file ", i.path) + return "" + } + return string(css) + }) + s, _ := __styles.Load(i.handle) s.Src = "" - s.Extra["after"] = append(s.Extra["after"], string(css)) + a := s.Extra["after"] + if a == nil { + a = []string{} + } + slice.Unshift(&a, css) + s.Extra["after"] = a } } + +func emojiDetectionScript(h *wp.Handle) { + settings := map[string]any{ + "baseUrl": "https://s.w.org/images/core/emoji/14.0.0/72x72/", + "ext": ".png", + "svgUrl": "https://s.w.org/images/core/emoji/14.0.0/svg/", "svgExt": ".svg", + "source": map[string]any{ + "concatemoji": "/wp-includes/js/wp-emoji-release.min.js?ver=6.2.2", + }, + } + setting, _ := json.Marshal(settings) + dir := config.GetConfig().WpDir + emotion := reload.GetAnyValBys("script-loader-emoji", struct{}{}, func(_ struct{}) string { + f, err := os.ReadFile(dir) + if err != nil { + logs.Error(err, "load emoji css fail", dir) + return "" + } + return string(f) + }) + s := str.Join("window._wpemojiSettings = ", string(setting), "\n", emotion) + PrintInlineScriptTag(h, s, nil) +} + +func PrintInlineScriptTag(h *wp.Handle, script string, attr map[string]string) { + ss := wp.GetComponentsArgs(h, "inlineScript", "") + s := str.NewBuilder() + s.WriteString(ss) + s.WriteString("%s\n", script) + wp.SetComponentsArgs(h, "inlineScript", s.String()) +} + +func PrintStyles(h *wp.Handle) { + +} diff --git a/app/theme/wp/scriptloader/scriptloader.go b/app/theme/wp/scriptloader/scriptloader.go index 93974a9..349aafd 100644 --- a/app/theme/wp/scriptloader/scriptloader.go +++ b/app/theme/wp/scriptloader/scriptloader.go @@ -3,25 +3,19 @@ package scriptloader import ( "encoding/json" "fmt" - "github.com/dlclark/regexp2" "github.com/fthvgb1/wp-go/app/cmd/reload" "github.com/fthvgb1/wp-go/app/theme/wp" "github.com/fthvgb1/wp-go/app/wpconfig" "github.com/fthvgb1/wp-go/helper" "github.com/fthvgb1/wp-go/helper/maps" - "github.com/fthvgb1/wp-go/helper/number" "github.com/fthvgb1/wp-go/helper/slice" str "github.com/fthvgb1/wp-go/helper/strings" "github.com/fthvgb1/wp-go/safety" - "html" - "math" "path/filepath" - "regexp" - "strconv" "strings" ) -var __styles = reload.MapBy(func(m *safety.Map[string, *Script]) { +var __styles = reload.MapBy(func(m *safety.Map[string, *Style]) { defaultStyles(m, ".css") }) var __scripts = reload.MapBy[string, *Script](func(m *safety.Map[string, *Script]) { @@ -30,6 +24,14 @@ var __scripts = reload.MapBy[string, *Script](func(m *safety.Map[string, *Script }) +type Style struct { + Dependencies +} + +type Script struct { + Dependencies +} + func addScript(handle string, src string, deps []string, ver string, args any) { script := NewScript(handle, src, deps, ver, args) __scripts.Store(handle, script) @@ -52,18 +54,14 @@ func localize(handle, objectname string, l10n map[string]any) string { } func AddStaticLocalize(handle, objectname string, l10n map[string]any) { - AddData(handle, "data", localize(handle, objectname, l10n)) + AddScriptData(handle, "data", localize(handle, objectname, l10n)) } func AddDynamicLocalize(h *wp.Handle, handle, objectname string, l10n map[string]any) { AddDynamicData(h, handle, "data", localize(handle, objectname, l10n)) } -func getData(handle, key string) string { - h, ok := __scripts.Load(handle) - if !ok { - return "" - } - return strings.Join(h.Extra[key], "\n") +func (d *Dependencies) getData(key string) string { + return strings.Join(d.Extra[key], "\n") } func GetData(h *wp.Handle, handle, key string) string { hh, ok := __scripts.Load(handle) @@ -75,14 +73,10 @@ func GetData(h *wp.Handle, handle, key string) string { return strings.Join(d, "\n") } -func AddData(handle, key, data string, t ...int) { +func AddScriptData(handle, key, data string) { var s *Script var ok bool - if t != nil { - s, ok = __styles.Load(handle) - } else { - s, ok = __scripts.Load(handle) - } + s, ok = __scripts.Load(handle) if !ok { s = NewScript(handle, "", nil, "", nil) } @@ -99,18 +93,19 @@ func AddInlineScript(handle, data, position string) { if position != "after" { position = "before" } - AddData(handle, position, data) + AddScriptData(handle, position, data) } func AddInlineStyle(handle, data string) { if handle == "" || data == "" { return } - AddData(handle, "after", data, style) + AddScriptData(handle, "after", data, style) } func InlineScripts(handle, position string, display bool) string { - ss := getData(handle, position) + v, _ := __scripts.Load(handle) + ss := v.getData(position) if ss == "" { return "" } @@ -164,7 +159,7 @@ func EnqueueScript(handle, src string, deps []string, ver string, inFooter bool) AddScript(h[0], src, deps, ver, nil) } if inFooter { - AddData(h[0], "group", "1") + AddScriptData(h[0], "group", "1") } enqueue(handle, script) } @@ -199,7 +194,7 @@ func GetThemeFileUri(file string) string { return filepath.Join("/wp-content/themes", wpconfig.GetOption("template"), file) } -type Script struct { +type Dependencies struct { Handle string `json:"handle,omitempty"` Src string `json:"src,omitempty"` Deps []string `json:"deps,omitempty"` @@ -211,7 +206,7 @@ type Script struct { } func NewScript(handle string, src string, deps []string, ver string, args any) *Script { - return &Script{Handle: handle, Src: src, Deps: deps, Ver: ver, Args: args} + return &Script{Dependencies{Handle: handle, Src: src, Deps: deps, Ver: ver, Args: args}} } func AddDynamicData(h *wp.Handle, handle, key, data string) { @@ -255,995 +250,106 @@ func SetTranslation(handle, domain, path string) { hh.TranslationsPath = path } -var __elements = map[string]string{ - "link": "a:where(:not(.wp-element-button))", // The `where` is needed to lower the specificity. - "heading": "h1, h2, h3, h4, h5, h6", - "h1": "h1", - "h2": "h2", - "h3": "h3", - "h4": "h4", - "h5": "h5", - "h6": "h6", - // We have the .wp-block-button__link class so that this will target older buttons that have been serialized. - "button": ".wp-element-button, .wp-block-button__link", - // The block classes are necessary to target older content that won't use the new class names. - "caption": ".wp-element-caption, .wp-block-audio figcaption, .wp-block-embed figcaption, .wp-block-gallery figcaption, .wp-block-image figcaption, .wp-block-table figcaption, .wp-block-video figcaption", - "cite": "cite", -} - -const RootBlockSelector = "body" - -type node struct { - Path []string - Selector string - Name string -} - -var __validElementPseudoSelectors = map[string][]string{ - "link": {":link", ":any-link", ":visited", ":hover", ":focus", ":active"}, - "button": {":link", ":any-link", ":visited", ":hover", ":focus", ":active"}, -} - -var blockSupportFeatureLevelSelectors = map[string]string{ - "__experimentalBorder": "border", - "color": "color", - "spacing": "spacing", - "typography": "typography", -} - -func appendToSelector(selector, toAppend, position string) string { - s := strings.Split(selector, ",") - if position == "" { - position = "right" - } - return strings.Join(slice.Map(s, func(t string) string { - var l, r string - if position == "right" { - l = t - r = toAppend - } else { - l = toAppend - r = t - } - return str.Join(l, r) - }), ",") -} - -func __removeComment(m map[string]any) { - delete(m, "//") - for _, v := range m { - mm, ok := v.(map[string]any) - if ok { - __removeComment(mm) - } - } -} - -func scopeSelector(scope, selector string) string { - scopes := strings.Split(scope, ",") - selectors := strings.Split(selector, ",") - var a []string - for _, outer := range scopes { - outer = strings.TrimSpace(outer) - for _, inner := range selectors { - inner = strings.TrimSpace(inner) - if outer != "" && inner != "" { - a = append(a, str.Join(outer, " ", inner)) - } else if outer == "" { - a = append(a, inner) - } else if inner == "" { - a = append(a, outer) - } - } - } - return strings.Join(a, ", ") -} - -func getBlockNodes(m map[string]any) []map[string]any { - selectors, _ := maps.GetStrAnyVal[map[string]any](m, "blocks_metadata") - mm, _ := maps.GetStrAnyVal[map[string]any](m, "theme_json.styles.blocks") - var nodes []map[string]any - for k, v := range mm { - vv, ok := v.(map[string]any) - if !ok { +func (item *__parseLoadItem) allDeps(handles []string, recursion bool, group int) bool { + for _, handle := range handles { + parts := strings.Split(handle, "?") + queued := slice.IsContained(item.todo, parts[0]) + handle = parts[0] + moved := item.setGroup(handle, group) + if queued || !moved { continue } - s, _ := maps.GetStrAnyVal[string](selectors, str.Join(k, ".supports.selector")) - d, _ := maps.GetStrAnyVal[string](selectors, str.Join(k, ".duotone")) - f, _ := maps.GetStrAnyVal[string](selectors, str.Join(k, ".features")) - n, ok := maps.GetStrAnyVal[map[string]any](vv, "variations") - var variationSelectors []node - if ok { - for variation := range n { - ss, _ := maps.GetStrAnyVal[string](selectors, str.Join(k, ".styleVariations.", variation)) - variationSelectors = append(variationSelectors, node{ - Path: []string{"styles", "blocks", k, "variations", variation}, - Selector: ss, - }) - } - } - nodes = append(nodes, map[string]any{ - "name": k, - "path": []string{"styles", "blocks", k}, - "selector": s, - "duotone": d, - "features": f, - "variations": variationSelectors, - }) - e, ok := maps.GetStrAnyVal[map[string]any](vv, "elements") + newGroup := item.groups[handle] + keepGoing := true + h, ok := __styles.Load(handle) if !ok { - continue + keepGoing = false } - for element, vvv := range e { - _, ok = vvv.(map[string]any) - if !ok { + if len(h.Deps) > 0 && len(slice.Diff(h.Deps, __styles.Keys())) > 0 { + keepGoing = false + } + if len(h.Deps) > 0 && item.allDeps(h.Deps, true, newGroup) { + keepGoing = false + } + if !keepGoing { + if recursion { + return false + } else { continue } - key := str.Join(k, ".elements.", element) - selector, _ := maps.GetStrAnyVal[string](selectors, key) - nodes = append(nodes, map[string]any{ - "path": []string{"styles", "blocks", k, "elements", element}, - "selector": selector, - }) - if val, ok := __validElementPseudoSelectors[element]; ok { - for _, ss := range val { - _, ok = maps.GetStrAnyVal[string](vv, str.Join("elements.", ss)) - if !ok { - continue - } - nodes = append(nodes, map[string]any{ - "path": []string{"styles", "blocks", k, "elements", element}, - "selector": appendToSelector(selector, ss, ""), - }) - } + } + if len(parts) > 1 { + item.args[handle] = parts[1] + } + item.todo = append(item.todo, handle) + } + return true +} + +type __parseLoadItem struct { + todo []string + done []string + groups map[string]int + args map[string]string +} + +func newParseLoadItem() *__parseLoadItem { + return &__parseLoadItem{ + groups: map[string]int{}, + args: map[string]string{}, + } +} + +func (item *__parseLoadItem) setGroup(handle string, group int) bool { + if v, ok := item.groups[handle]; ok && v <= group { + return false + } + item.groups[handle] = group + return true +} + +func DoStyleItems(h *wp.Handle, handles []string, group int) []string { + item := newParseLoadItem() + item.allDeps(handles, false, 0) + for i, handle := range item.todo { + _, ok := __styles.Load(handle) + if !slice.IsContained(item.done, handle) && ok { + if DoStyleItem(h, item, handle, group) { + item.done = append(item.done, handle) } + slice.Delete(&item.todo, i) } } - return nodes + return item.done } -func getSettingNodes(m, setting map[string]any) []node { - var nodes []node - nodes = append(nodes, node{ - Path: []string{"settings"}, - Selector: RootBlockSelector, - }) - selectors, _ := maps.GetStrAnyVal[map[string]any](m, "blocks_metadata") - s, ok := maps.GetStrAnyVal[map[string]any](setting, "settings.blocks") - if !ok { - return nil +func DoStyleItem(h *wp.Handle, item *__parseLoadItem, handle string, group int) bool { + obj, _ := __styles.Load(handle) + ver := obj.Ver + if v, ok := item.args[handle]; ok { + ver = helper.Or(ver == "", v, str.Join(ver, "&", v)) } - for name := range s { - selector, ok := maps.GetStrAnyVal[string](selectors, str.Join(name, ".supports.selector")) - if ok { - nodes = append(nodes, node{ - Path: []string{"settings", "blocks", name}, - Selector: selector, - }) - } + src := obj.Src + var condBefore, condAfter, conditional string + if v, ok := obj.Extra["conditional"]; ok && v != nil { + conditional = v[0] } - return nodes + if conditional != "" { + condBefore = str.Join("\n") + condAfter = "\n" + } + inlineStyle := PrintInline(item, handle) + + return true } -type ThemeJson struct { - blocksMetaData map[string]any - themeJson map[string]any -} - -var validOrigins = []string{"default", "blocks", "theme", "custom"} - -var layoutSelectorReg = regexp.MustCompile(`^[a-zA-Z0-9\-. *+>:()]*$`) - -var validDisplayModes = []string{"block", "flex", "grid"} - -func (j ThemeJson) getLayoutStyles(nodes node) string { - //todo current theme supports disable-layout-styles - var blockType map[string]any - var s strings.Builder - if nodes.Name != "" { - v, ok := maps.GetStrAnyVal[map[string]any](j.blocksMetaData, nodes.Name) - if ok { - vv, ok := maps.GetStrAnyVal[map[string]any](v, "supports.__experimentalLayout") - if ok && vv == nil { - return "" - } - blockType = vv - } - } - gap, hasBlockGapSupport := maps.GetStrAnyVal[map[string]any](j.themeJson, "settings.spacing.blockGap") - if gap == nil { - hasBlockGapSupport = false - } - _, ok := maps.GetStrAnyVal[map[string]any](j.themeJson, strings.Join(nodes.Path, ".")) - if !ok { +func PrintInline(item *__parseLoadItem, handle string) string { + sty, _ := __styles.Load(handle) + out := sty.getData("after") + if out == "" { return "" } - layoutDefinitions, ok := maps.GetStrAnyVal[map[string]any](j.themeJson, "settings.layout.definitions") - if !ok { - return "" - } - blockGapValue := "" - if !hasBlockGapSupport { - if RootBlockSelector == nodes.Selector { - blockGapValue = "0.5em" - } - if blockType != nil { - blockGapValue, _ = maps.GetStrAnyVal[string](blockType, "supports.spacing.blockGap.__experimentalDefault") - } - } else { - //todo getPropertyValue() - } - if blockGapValue != "" { - for key, v := range layoutDefinitions { - definition, ok := v.(map[string]any) - if !ok { - continue - } - if !hasBlockGapSupport && "flex" != key { - continue - } - className := maps.GetStrAnyValWithDefaults(definition, "className", "") - spacingRules := maps.GetStrAnyValWithDefaults(definition, "spacingStyles", []any{}) - if className == "" || spacingRules == nil { - continue - } - for _, rule := range spacingRules { - var declarations []declaration - spacingRule, ok := rule.(map[string]any) - if !ok { - continue - } - selector := maps.GetStrAnyValWithDefaults(spacingRule, "selector", "") - rules := maps.GetStrAnyValWithDefaults(spacingRule, "rules", map[string]any(nil)) - if selector != "" && !layoutSelectorReg.MatchString(selector) || rules == nil { - continue - } - for property, v := range rules { - value, ok := v.(string) - if !ok || value == "" { - value = blockGapValue - } - if isSafeCssDeclaration(property, value) { - declarations = append(declarations, declaration{property, value}) - } - } - format := "" - if !hasBlockGapSupport { - format = helper.Or(RootBlockSelector == nodes.Selector, ":where(.%[2]s%[3]s)", ":where(%[1]s.%[2]s%[3]s)") - } else { - format = helper.Or(RootBlockSelector == nodes.Selector, "%s .%s%s", "%s.%s%s") - } - layoutSelector := fmt.Sprintf(format, nodes.Selector, className, selector) - s.WriteString(toRuleset(layoutSelector, declarations)) - } - } - } - - if RootBlockSelector == nodes.Selector { - for _, v := range layoutDefinitions { - definition, ok := v.(map[string]any) - if !ok { - continue - } - className := maps.GetStrAnyValWithDefaults(definition, "className", "") - baseStyleRules := maps.GetStrAnyValWithDefaults(definition, "baseStyles", []any{}) - if className == "" || nil == baseStyleRules { - continue - } - displayMode := maps.GetStrAnyValWithDefaults(definition, "displayMode", "") - if displayMode != "" && slice.IsContained(validDisplayModes, displayMode) { - layoutSelector := str.Join(nodes.Selector, " .", className) - s.WriteString(toRuleset(layoutSelector, []declaration{{"display", displayMode}})) - } - for _, rule := range baseStyleRules { - var declarations []declaration - r, ok := rule.(map[string]any) - if !ok { - continue - } - selector := maps.GetStrAnyValWithDefaults(r, "selector", "") - rules := maps.GetStrAnyValWithDefaults(r, "rules", map[string]any(nil)) - if selector != "" && !layoutSelectorReg.MatchString(selector) || rules == nil { - continue - } - for property, value := range rules { - val, ok := value.(string) - if !ok || val == "" { - continue - } - if isSafeCssDeclaration(property, val) { - declarations = append(declarations, declaration{property, val}) - } - } - layoutSelector := str.Join(nodes.Selector, " .", className, selector) - s.WriteString(toRuleset(layoutSelector, declarations)) - } - } - } - + s := str.NewBuilder() + s.Sprintf("\n", handle) return s.String() } - -func isSafeCssDeclaration(name, value string) bool { - css := str.Join(name, ": ", value) - s := safeCSSFilterAttr(css) - return "" != html.EscapeString(s) -} - -var kses = regexp.MustCompile(`[\x00-\x08\x0B\x0C\x0E-\x1F]`) -var ksesop = regexp.MustCompile(`\\\\+0+`) - -func KsesNoNull(s string, op ...bool) string { - s = kses.ReplaceAllString(s, "") - ops := true - if len(op) > 0 { - ops = op[0] - } - if ops { - s = ksesop.ReplaceAllString(s, "") - } - return s -} - -var allowedProtocols = []string{ - "http", "https", "ftp", "ftps", "mailto", "news", "irc", "irc6", "ircs", "gopher", "nntp", - "feed", "telnet", "mms", "rtsp", "sms", "svn", "tel", "fax", "xmpp", "webcal", "urn", -} - -var allowCssAttr = []string{ - "background", "background-color", "background-image", "background-position", "background-size", "background-attachment", "background-blend-mode", - "border", "border-radius", "border-width", "border-color", "border-style", "border-right", "border-right-color", - "border-right-style", "border-right-width", "border-bottom", "border-bottom-color", "border-bottom-left-radius", - "border-bottom-right-radius", "border-bottom-style", "border-bottom-width", "border-bottom-right-radius", - "border-bottom-left-radius", "border-left", "border-left-color", "border-left-style", "border-left-width", - "border-top", "border-top-color", "border-top-left-radius", "border-top-right-radius", "border-top-style", - "border-top-width", "border-top-left-radius", "border-top-right-radius", "border-spacing", "border-collapse", - "caption-side", "columns", "column-count", "column-fill", "column-gap", "column-rule", "column-span", "column-width", - "color", "filter", "font", "font-family", "font-size", "font-style", "font-variant", "font-weight", - "letter-spacing", "line-height", "text-align", "text-decoration", "text-indent", "text-transform", "height", - "min-height", "max-height", "width", "min-width", "max-width", "margin", "margin-right", "margin-bottom", - "margin-left", "margin-top", "margin-block-start", "margin-block-end", "margin-inline-start", "margin-inline-end", - "padding", "padding-right", "padding-bottom", "padding-left", "padding-top", "padding-block-start", - "padding-block-end", "padding-inline-start", "padding-inline-end", "flex", "flex-basis", "flex-direction", - "flex-flow", "flex-grow", "flex-shrink", "flex-wrap", "gap", "column-gap", "row-gap", "grid-template-columns", - "grid-auto-columns", "grid-column-start", "grid-column-end", "grid-column-gap", "grid-template-rows", - "grid-auto-rows", "grid-row-start", "grid-row-end", "grid-row-gap", "grid-gap", "justify-content", "justify-items", - "justify-self", "align-content", "align-items", "align-self", "clear", "cursor", "direction", "float", - "list-style-type", "object-fit", "object-position", "overflow", "vertical-align", "position", "top", "right", - "bottom", "left", "z-index", "aspect-ratio", "--*", -} -var allowCssAttrMap = slice.FilterAndToMap(allowCssAttr, func(t string) (string, struct{}, bool) { - return t, struct{}{}, true -}) - -var __strReg = regexp.MustCompile(`^--[a-zA-Z0-9-_]+$`) -var cssUrlDataTypes = []string{"background", "background-image", "cursor", "list-style", "list-style-image"} -var cssGradientDataTypes = []string{"background", "background-image"} -var __allowCSSReg = regexp2.MustCompile(`\b(?:var|calc|min|max|minmax|clamp)(\((?:[^()]|(?!1))*\))`, regexp2.None) -var __disallowCSSReg = regexp.MustCompile(`[\\(&=}]|/\*`) - -func safeCSSFilterAttr(css string) string { - css = KsesNoNull(css) - css = strings.TrimSpace(css) - css = str.Replaces(css, []string{"\n", "\r", "\t", ""}) - cssArr := strings.Split(css, ";") - var isCustomVar, found, urlAttr, gradientAttr bool - var cssValue string - var ss strings.Builder - for _, s := range cssArr { - if s == "" { - continue - } - if !strings.Contains(s, ":") { - found = true - } else { - parts := strings.SplitN(s, ":", 2) - selector := strings.TrimSpace(parts[0]) - if maps.IsExists(allowCssAttrMap, "--*") { - i := __strReg.FindStringIndex(selector) - if len(i) > 0 && i[0] > 0 { - isCustomVar = true - allowCssAttr = append(allowCssAttr, selector) - } - } - if maps.IsExists(allowCssAttrMap, selector) { - found = true - urlAttr = slice.IsContained(cssUrlDataTypes, selector) - gradientAttr = slice.IsContained(cssGradientDataTypes, selector) - } - if isCustomVar { - cssValue = strings.TrimSpace(parts[1]) - urlAttr = cssValue[0:4] == "url(" - gradientAttr = strings.Contains(cssValue, "-gradient(") - } - } - - if found { - //todo wtf 🤮 - if urlAttr { - - } - if gradientAttr { - - } - cssTest, _ := __allowCSSReg.Replace(s, "", 0, -1) - isAllow := !__disallowCSSReg.MatchString(cssTest) - if isAllow { - ss.WriteString(s) - ss.WriteString(";") - } - } - - } - return strings.TrimRight(ss.String(), ";") -} - -func (j ThemeJson) getStyletSheet(types, origins []string, options map[string]string) string { - if origins == nil { - origins = append(validOrigins) - } - styleNodes := getStyleNodes(j) - settingsNodes := getSettingNodes(j.blocksMetaData, j.themeJson) - rootStyleKey, _ := slice.SearchFirst(styleNodes, func(n node) bool { - return n.Selector == RootBlockSelector - }) - rootSettingsKey, _ := slice.SearchFirst(settingsNodes, func(n node) bool { - return n.Selector == RootBlockSelector - }) - if os, ok := options["scope"]; ok { - for i := range settingsNodes { - settingsNodes[i].Selector = scopeSelector(os, settingsNodes[i].Selector) - } - for i := range styleNodes { - styleNodes[i].Selector = scopeSelector(os, styleNodes[i].Selector) - } - } - if or, ok := options["root_selector"]; ok && or != "" { - if rootSettingsKey > -1 { - settingsNodes[rootSettingsKey].Selector = or - } - if rootStyleKey > -1 && rootStyleKey < len(settingsNodes) { - settingsNodes[rootStyleKey].Selector = or - } - } - stylesSheet := "" - if slice.IsContained(types, "variables") { - stylesSheet = j.getCssVariables(settingsNodes, origins) - } - - if slice.IsContained(types, "styles") { - stylesSheet = j.getCssVariables(settingsNodes, origins) - if rootStyleKey > -1 { - //todo getRootLayoutRules - stylesSheet = str.Join(stylesSheet) - } - } else if slice.IsContained(types, "base-layout-styles") { - rootSelector := RootBlockSelector - columnsSelector := ".wp-block-columns" - scope, ok := options["scope"] - if ok && scope != "" { - rootSelector = scopeSelector(scope, rootSelector) - columnsSelector = scopeSelector(scope, columnsSelector) - } - rs, ok := options["root_selector"] - if ok && rs != "" { - rootSelector = rs - } - baseStylesNodes := []node{ - { - Path: []string{"styles"}, - Selector: rootSelector, - }, - { - Path: []string{"styles", "blocks", "core/columns"}, - Selector: columnsSelector, - Name: "core/columns", - }, - } - for _, stylesNode := range baseStylesNodes { - stylesSheet = str.Join(stylesSheet, j.getLayoutStyles(stylesNode)) - } - } - - if slice.IsContained(types, "presets") { - stylesSheet = str.Join(stylesSheet, j.getPresetClasses(settingsNodes, origins)) - } - - return stylesSheet -} - -func (j ThemeJson) getPresetClasses(nodes []node, origins []string) string { - var presetRules strings.Builder - for _, n := range nodes { - if n.Selector == "" { - continue - } - no, ok := maps.GetStrAnyVal[map[string]any](j.themeJson, strings.Join(n.Path, ".")) - if !ok { - continue - } - presetRules.WriteString(computePresetClasses(no, n.Selector, origins)) - } - return presetRules.String() -} - -func computePresetClasses(m map[string]any, selector string, origins []string) string { - if selector == RootBlockSelector { - selector = "" - } - var s strings.Builder - for _, meta := range presetsMetadata { - slugs := getSettingsSlugs(m, meta, origins) - for class, property := range meta.classes { - for _, slug := range slugs { - cssVar := strings.ReplaceAll(meta.cssVars, "$slug", slug) - className := strings.ReplaceAll(class, "$slug", slug) - s.WriteString(toRuleset(appendToSelector(selector, className, ""), []declaration{ - {property, str.Join("var(", cssVar, ") !important")}, - })) - } - } - } - return s.String() -} - -func getSettingsSlugs(settings map[string]any, meta presetMeta, origins []string) map[string]string { - if origins == nil { - origins = validOrigins - } - - presetPerOrigin, ok := maps.GetStrAnyVal[map[string]any](settings, strings.Join(meta.path, ".")) - if !ok { - return nil - } - m := map[string]string{} - for _, origin := range origins { - o, ok := maps.GetStrAnyVal[[]map[string]string](presetPerOrigin, origin) - if !ok { - continue - } - for _, mm := range o { - slug := toKebabCase(mm["slug"]) - m[slug] = slug - } - } - return m -} - -func toKebabCase(s string) string { - s = strings.ReplaceAll(s, "'", "") - r, err := __kebabCaseReg.FindStringMatch(s) - if err != nil { - return s - } - var ss []string - for r != nil { - if r.GroupCount() < 1 { - break - } - - ss = append(ss, r.Groups()[0].String()) - r, _ = __kebabCaseReg.FindNextMatch(r) - } - - return strings.ToLower(strings.Join(ss, "-")) -} - -var __kebabCaseReg = func() *regexp2.Regexp { - rsLowerRange := "a-z\\xdf-\\xf6\\xf8-\\xff" - rsNonCharRange := "\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf" - rsPunctuationRange := "\\x{2000}-\\x{206f}" - rsSpaceRange := " \\t\\x0b\\f\\xa0\\x{feff}\\n\\r\\x{2028}\\x{2029}\\x{1680}\\x{180e}\\x{2000}\\x{2001}\\x{2002}\\x{2003}\\x{2004}\\x{2005}\\x{2006}\\x{2007}\\x{2008}\\x{2009}\\x{200a}\\x{202f}\\x{205f}\\x{3000}" - rsUpperRange := "A-Z\\xc0-\\xd6\\xd8-\\xde" - rsBreakRange := rsNonCharRange + rsPunctuationRange + rsSpaceRange - - /** Used to compose unicode capture groups. */ - rsBreak := "[" + rsBreakRange + "]" - rsDigits := "\\d+" // The last lodash version in GitHub uses a single digit here and expands it when in use. - rsLower := "[" + rsLowerRange + "]" - rsMisc := "[^" + rsBreakRange + rsDigits + rsLowerRange + rsUpperRange + "]" - rsUpper := "[" + rsUpperRange + "]" - - /** Used to compose unicode regexes. */ - rsMiscLower := "(?:" + rsLower + "|" + rsMisc + ")" - rsMiscUpper := "(?:" + rsUpper + "|" + rsMisc + ")" - rsOrdLower := "\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])" - rsOrdUpper := "\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])" - - reg := strings.Join([]string{ - rsUpper + "?" + rsLower + "+(?=" + strings.Join([]string{rsBreak, rsUpper, "$"}, "|") + ")", - rsMiscUpper + "+(?=" + strings.Join([]string{rsBreak, rsUpper + rsMiscLower, "$"}, "|") + ")", - rsUpper + "?" + rsMiscLower + "+", - rsUpper + "+", - rsOrdUpper, - rsOrdLower, - rsDigits, - }, "|") - return regexp2.MustCompile(reg, regexp2.Unicode) -}() - -var presetsMetadata = []presetMeta{ - { - path: []string{"color", "palette"}, - preventOverride: []string{"color", "defaultPalette"}, - useDefaultNames: false, - valueKey: "color", - valueFunc: nil, - cssVars: "--wp--preset--color--$slug", - classes: map[string]string{ - ".has-$slug-color": "color", - ".has-$slug-background-color": "background-color", - ".has-$slug-border-color": "border-color", - }, - properties: []string{"color", "background-color", "border-color"}, - }, { - path: []string{"color", "gradients"}, - preventOverride: []string{"color", "defaultGradients"}, - useDefaultNames: false, - valueKey: "gradient", - valueFunc: nil, - cssVars: "--wp--preset--gradient--$slug", - classes: map[string]string{ - ".has-$slug-gradient-background": "background", - }, - properties: []string{"background"}, - }, { - path: []string{"color", "duotone"}, - preventOverride: []string{"color", "defaultDuotone"}, - useDefaultNames: false, - valueKey: "", - valueFunc: wpGetDuotoneFilterProperty, - cssVars: "--wp--preset--duotone--$slug", - classes: map[string]string{}, - properties: []string{"filter"}, - }, { - path: []string{"typography", "fontSizes"}, - preventOverride: []string{}, - useDefaultNames: true, - valueKey: "", - valueFunc: wpGetTypographyFontSizeValue, - cssVars: "--wp--preset--font-size--$slug", - classes: map[string]string{ - ".has-$slug-font-size": "font-size", - }, - properties: []string{"font-size"}, - }, { - path: []string{"typography", "fontFamilies"}, - preventOverride: []string{}, - useDefaultNames: false, - valueKey: "fontFamily", - valueFunc: nil, - cssVars: "--wp--preset--font-family--$slug", - classes: map[string]string{ - ".has-$slug-font-family": "font-family", - }, - properties: []string{"font-family"}, - }, { - path: []string{"spacing", "spacingSizes"}, - preventOverride: []string{}, - useDefaultNames: true, - valueKey: "size", - valueFunc: nil, - cssVars: "--wp--preset--spacing--$slug", - classes: map[string]string{}, - properties: []string{"padding", "margin"}, - }, { - path: []string{"shadow", "presets"}, - preventOverride: []string{"shadow", "defaultPresets"}, - useDefaultNames: false, - valueKey: "shadow", - valueFunc: nil, - cssVars: "--wp--preset--shadow--$slug", - classes: map[string]string{}, - properties: []string{"box-shadow"}, - }, -} - -type presetMeta struct { - path []string - preventOverride []string - useDefaultNames bool - valueFunc func(map[string]string, map[string]any) string - valueKey string - cssVars string - classes map[string]string - properties []string -} - -type declaration struct { - name string - value string -} - -func wpGetDuotoneFilterProperty(preset map[string]string, _ map[string]any) string { - v, ok := preset["colors"] - if ok && "unset" == v { - return "none" - } - id, ok := preset["slug"] - if ok { - id = str.Join("wp-duotone-", id) - } - return str.Join(`url('#`, id, "')") -} - -func wpGetTypographyFontSizeValue(preset map[string]string, m map[string]any) string { - size, ok := preset["size"] - if !ok { - return "" - } - if size == "" || size == "0" { - return size - } - origin := "custom" - if !wpconfig.HasThemeJson() { - origin = "theme" - } - typographySettings, ok := maps.GetStrAnyVal[map[string]any](m, "typography") - if !ok { - return size - } - fluidSettings, ok := maps.GetStrAnyVal[map[string]any](typographySettings, "fluid") - - //todo so complex dying 👻 - _ = fluidSettings - _ = origin - return size -} - -var __themeJson = reload.Vars(ThemeJson{}) - -func GetThemeJson() ThemeJson { - return __themeJson.Load() -} - -func wpGetTypographyValueAndUnit(value string, options map[string]any) { - /*options := maps.Merge(options, map[string]any{ - "coerce_to": "", - "root_size_value": 16, - "acceptable_units": []string{"rem", "px", "em"}, - }) - u, _ := maps.GetStrAnyVal[[]string](options, "acceptable_units") - acceptableUnitsGroup := strings.Join(u, "|")*/ - -} - -func computeThemeVars(m map[string]any) []declaration { - //todo ...... - return nil -} - -func computePresetVars(m map[string]any, origins []string) []declaration { - var declarations []declaration - for _, metadatum := range presetsMetadata { - slug := getSettingsValuesBySlug(m, metadatum, origins) - for k, v := range slug { - declarations = append(declarations, declaration{ - name: strings.Replace(metadatum.cssVars, "$slug", k, -1), - value: v, - }) - } - } - return declarations -} - -func getSettingsValuesBySlug(m map[string]any, meta presetMeta, origins []string) map[string]string { - perOrigin := maps.GetStrAnyValWithDefaults[map[string]any](m, strings.Join(meta.path, "."), nil) - r := map[string]string{} - for _, origin := range origins { - if vv, ok := maps.GetStrAnyVal[[]map[string]string](perOrigin, origin); ok { - for _, preset := range vv { - slug := preset["slug"] - value := "" - if vv, ok := preset[meta.valueKey]; ok && vv != "" { - value = vv - } else if meta.valueFunc != nil { - value = meta.valueFunc(preset, m) - } - r[slug] = value - } - } - } - return r -} - -func setSpacingSizes(t ThemeJson) { - m, _ := maps.GetStrAnyVal[map[string]any](t.themeJson, "settings.spacing.spacingScale") - unit, _ := maps.GetStrAnyVal[string](m, "unit") - currentStep, _ := maps.GetStrAnyVal[float64](m, "mediumStep") - mediumStep := currentStep - steps, _ := maps.GetStrAnyVal[float64](m, "steps") - operator, _ := maps.GetStrAnyVal[string](m, "operator") - increment, _ := maps.GetStrAnyVal[float64](m, "increment") - stepsMidPoint := math.Round(steps / 2) - reminder := float64(0) - xSmallCount := "" - var sizes []map[string]string - slug := 40 - for i := stepsMidPoint - 1; i > 0 && slug > 0 && steps > 1; i-- { - if "+" == operator { - currentStep -= increment - } else if increment > 1 { - currentStep /= increment - } else { - currentStep *= increment - } - if currentStep <= 0 { - reminder = i - break - } - name := "small" - if i != stepsMidPoint-1 { - name = str.Join(xSmallCount, "X-Small") - } - sizes = append(sizes, map[string]string{ - "name": name, - "slug": number.IntToString(slug), - "size": fmt.Sprintf("%v%s", number.Round(currentStep, 2), unit), - }) - if i == stepsMidPoint-2 { - xSmallCount = strconv.Itoa(2) - } - if i < stepsMidPoint-2 { - n := str.ToInt[int](xSmallCount) - n++ - xSmallCount = strconv.Itoa(n) - } - slug -= 10 - } - slice.ReverseSelf(sizes) - sizes = append(sizes, map[string]string{ - "name": "Medium", - "slug": "50", - "size": str.Join(number.ToString(mediumStep), unit), - }) - currentStep = mediumStep - slug = 60 - xLargeCount := "" - stepsAbove := steps - stepsMidPoint + reminder - for aboveMidpointCount := float64(0); aboveMidpointCount < stepsAbove; aboveMidpointCount++ { - if "+" == operator { - currentStep += increment - } else if increment >= 1 { - currentStep *= increment - } else { - currentStep /= increment - } - name := "Large" - if 0 != aboveMidpointCount { - name = str.Join(xLargeCount, "X-Large") - } - sizes = append(sizes, map[string]string{ - "name": name, - "slug": strconv.Itoa(slug), - "size": fmt.Sprintf("%v%s", number.Round(currentStep, 2), unit), - }) - if aboveMidpointCount == 1 { - xLargeCount = strconv.Itoa(2) - } - if aboveMidpointCount > 1 { - x := str.ToInt[int](xLargeCount) - x++ - xLargeCount = strconv.Itoa(x) - } - slug += 10 - } - if steps <= 7 { - for i := 0; i < len(sizes); i++ { - sizes[i]["name"] = strconv.Itoa(i + 1) - } - } - maps.SetStrAnyVal(t.themeJson, "settings.spacing.spacingSizes.default", sizes) -} - -func (j ThemeJson) getCssVariables(settingNodes []node, origins []string) string { - var s strings.Builder - for _, settingNode := range settingNodes { - if "" == settingNode.Selector { - continue - } - n := maps.GetStrAnyValWithDefaults[map[string]any](j.themeJson, strings.Join(settingNode.Path, "."), nil) - declarations := computePresetVars(n, origins) - declarations = append(declarations, computeThemeVars(j.themeJson)...) - s.WriteString(toRuleset(settingNode.Selector, declarations)) - } - return s.String() -} - -func toRuleset(selector string, declarations []declaration) string { - if len(declarations) < 1 { - return "" - } - s := slice.Reduce(declarations, func(t declaration, r string) string { - return str.Join(r, t.name, ":", t.value, ";") - }, "") - return str.Join(selector, "{", s, "}") -} - -func getStyleNodes(t ThemeJson) []node { - var styleNodes = []node{ - {[]string{"styles"}, "body", ""}, - } - m := maps.GetStrAnyValWithDefaults[map[string]any](t.themeJson, "styles.elements", nil) - if len(m) < 1 { - return nil - } - for e, s := range __elements { - _, ok := m[e] - if !ok { - continue - } - styleNodes = append(styleNodes, node{[]string{"styles", "elements", e}, s, ""}) - ss, ok := __validElementPseudoSelectors[e] - if ok { - for _, sel := range ss { - if maps.IsExists(__elements, sel) { - styleNodes = append(styleNodes, node{ - Path: []string{"styles", "elements", e}, - Selector: appendToSelector(s, sel, ""), - }) - } - } - } - } - blocks, _ := maps.GetStrAnyVal[map[string]any](t.blocksMetaData, "theme_json.styles.blocks") - maps.SetStrAnyVal(t.themeJson, "styles.blocks", blocks) - blockNodes := getBlockNodes(t.blocksMetaData) - for _, blockNode := range blockNodes { - p, ok := maps.GetStrAnyVal[[]string](blockNode, "path") - s, oo := maps.GetStrAnyVal[string](blockNode, "selector") - if ok && oo { - _ = append(styleNodes, node{Path: p, Selector: s}) - //styleNodes = append(styleNodes, node{Path: p, Selector: s}) - } - } - - return styleNodes -} - -func GetGlobalStyletSheet() string { - t := __themeJson.Load() - var types, origins []string - if types == nil && !wpconfig.HasThemeJson() { - types = []string{"variables", "presets", "base-layout-styles"} - } else if types == nil { - types = []string{"variables", "styles", "presets"} - } - styleSheet := "" - if slice.IsContained(types, "variables") { - origins = []string{"default", "theme", "custom"} - styleSheet = t.getStyletSheet([]string{"variables"}, origins, nil) - slice.Delete(&types, slice.IndexOf(types, "variables")) - } - - if len(types) > 0 { - origins = []string{"default", "theme", "custm"} - if !wpconfig.HasThemeJson() { - origins = []string{"default"} - } - styleSheet = str.Join(styleSheet, t.getStyletSheet(types, origins, nil)) - } - - return styleSheet -} - -/*func (j ThemeJson) getStylesForBlock(blockMeta map[string]any) { - path, _ := maps.GetStrAnyVal[[]string](blockMeta, "path") - node, _ := maps.GetStrAnyVal[map[string]any](j.themeJson, strings.Join(path, ".")) - useRootPadding := maps.GetStrAnyValWithDefaults(j.themeJson, "settings.useRootPaddingAwareAlignments", false) - settings, _ := maps.GetStrAnyVal(j.themeJson, "settings") - is_processing_element := slice.IsContained(path, "elements") - currentElement := "" - if is_processing_element { - currentElement = path[len(path)-1] - } - element_pseudo_allowed := __validElementPseudoSelectors[currentElement] - -} - -func computeStyleProperties(styles, settings, properties, themeJson map[string]any, selector string, useRootPadding bool) { - if properties == nil { - //properties = - } -} -*/ diff --git a/app/theme/wp/scriptloader/wtf.go b/app/theme/wp/scriptloader/wtf.go new file mode 100644 index 0000000..ccd3d5b --- /dev/null +++ b/app/theme/wp/scriptloader/wtf.go @@ -0,0 +1,1011 @@ +package scriptloader + +import ( + "fmt" + "github.com/dlclark/regexp2" + "github.com/fthvgb1/wp-go/app/cmd/reload" + "github.com/fthvgb1/wp-go/app/wpconfig" + "github.com/fthvgb1/wp-go/helper" + "github.com/fthvgb1/wp-go/helper/maps" + "github.com/fthvgb1/wp-go/helper/number" + "github.com/fthvgb1/wp-go/helper/slice" + str "github.com/fthvgb1/wp-go/helper/strings" + "html" + "math" + "regexp" + "strconv" + "strings" +) + +var __elements = map[string]string{ + "link": "a:where(:not(.wp-element-button))", // The `where` is needed to lower the specificity. + "heading": "h1, h2, h3, h4, h5, h6", + "h1": "h1", + "h2": "h2", + "h3": "h3", + "h4": "h4", + "h5": "h5", + "h6": "h6", + // We have the .wp-block-button__link class so that this will target older buttons that have been serialized. + "button": ".wp-element-button, .wp-block-button__link", + // The block classes are necessary to target older content that won't use the new class names. + "caption": ".wp-element-caption, .wp-block-audio figcaption, .wp-block-embed figcaption, .wp-block-gallery figcaption, .wp-block-image figcaption, .wp-block-table figcaption, .wp-block-video figcaption", + "cite": "cite", +} + +const RootBlockSelector = "body" + +type node struct { + Path []string + Selector string + Name string +} + +var __validElementPseudoSelectors = map[string][]string{ + "link": {":link", ":any-link", ":visited", ":hover", ":focus", ":active"}, + "button": {":link", ":any-link", ":visited", ":hover", ":focus", ":active"}, +} + +var blockSupportFeatureLevelSelectors = map[string]string{ + "__experimentalBorder": "border", + "color": "color", + "spacing": "spacing", + "typography": "typography", +} + +func appendToSelector(selector, toAppend, position string) string { + s := strings.Split(selector, ",") + if position == "" { + position = "right" + } + return strings.Join(slice.Map(s, func(t string) string { + var l, r string + if position == "right" { + l = t + r = toAppend + } else { + l = toAppend + r = t + } + return str.Join(l, r) + }), ",") +} + +func __removeComment(m map[string]any) { + delete(m, "//") + for _, v := range m { + mm, ok := v.(map[string]any) + if ok { + __removeComment(mm) + } + } +} + +func scopeSelector(scope, selector string) string { + scopes := strings.Split(scope, ",") + selectors := strings.Split(selector, ",") + var a []string + for _, outer := range scopes { + outer = strings.TrimSpace(outer) + for _, inner := range selectors { + inner = strings.TrimSpace(inner) + if outer != "" && inner != "" { + a = append(a, str.Join(outer, " ", inner)) + } else if outer == "" { + a = append(a, inner) + } else if inner == "" { + a = append(a, outer) + } + } + } + return strings.Join(a, ", ") +} + +func getBlockNodes(m map[string]any) []map[string]any { + selectors, _ := maps.GetStrAnyVal[map[string]any](m, "blocks_metadata") + mm, _ := maps.GetStrAnyVal[map[string]any](m, "theme_json.styles.blocks") + var nodes []map[string]any + for k, v := range mm { + vv, ok := v.(map[string]any) + if !ok { + continue + } + s, _ := maps.GetStrAnyVal[string](selectors, str.Join(k, ".supports.selector")) + d, _ := maps.GetStrAnyVal[string](selectors, str.Join(k, ".duotone")) + f, _ := maps.GetStrAnyVal[string](selectors, str.Join(k, ".features")) + n, ok := maps.GetStrAnyVal[map[string]any](vv, "variations") + var variationSelectors []node + if ok { + for variation := range n { + ss, _ := maps.GetStrAnyVal[string](selectors, str.Join(k, ".styleVariations.", variation)) + variationSelectors = append(variationSelectors, node{ + Path: []string{"styles", "blocks", k, "variations", variation}, + Selector: ss, + }) + } + } + nodes = append(nodes, map[string]any{ + "name": k, + "path": []string{"styles", "blocks", k}, + "selector": s, + "duotone": d, + "features": f, + "variations": variationSelectors, + }) + e, ok := maps.GetStrAnyVal[map[string]any](vv, "elements") + if !ok { + continue + } + for element, vvv := range e { + _, ok = vvv.(map[string]any) + if !ok { + continue + } + key := str.Join(k, ".elements.", element) + selector, _ := maps.GetStrAnyVal[string](selectors, key) + nodes = append(nodes, map[string]any{ + "path": []string{"styles", "blocks", k, "elements", element}, + "selector": selector, + }) + if val, ok := __validElementPseudoSelectors[element]; ok { + for _, ss := range val { + _, ok = maps.GetStrAnyVal[string](vv, str.Join("elements.", ss)) + if !ok { + continue + } + nodes = append(nodes, map[string]any{ + "path": []string{"styles", "blocks", k, "elements", element}, + "selector": appendToSelector(selector, ss, ""), + }) + } + } + } + } + return nodes +} + +func getSettingNodes(m, setting map[string]any) []node { + var nodes []node + nodes = append(nodes, node{ + Path: []string{"settings"}, + Selector: RootBlockSelector, + }) + selectors, _ := maps.GetStrAnyVal[map[string]any](m, "blocks_metadata") + s, ok := maps.GetStrAnyVal[map[string]any](setting, "settings.blocks") + if !ok { + return nil + } + for name := range s { + selector, ok := maps.GetStrAnyVal[string](selectors, str.Join(name, ".supports.selector")) + if ok { + nodes = append(nodes, node{ + Path: []string{"settings", "blocks", name}, + Selector: selector, + }) + } + } + return nodes +} + +type ThemeJson struct { + blocksMetaData map[string]any + themeJson map[string]any +} + +var validOrigins = []string{"default", "blocks", "theme", "custom"} + +var layoutSelectorReg = regexp.MustCompile(`^[a-zA-Z0-9\-. *+>:()]*$`) + +var validDisplayModes = []string{"block", "flex", "grid"} + +func (j ThemeJson) getLayoutStyles(nodes node) string { + //todo current theme supports disable-layout-styles + var blockType map[string]any + var s strings.Builder + if nodes.Name != "" { + v, ok := maps.GetStrAnyVal[map[string]any](j.blocksMetaData, nodes.Name) + if ok { + vv, ok := maps.GetStrAnyVal[map[string]any](v, "supports.__experimentalLayout") + if ok && vv == nil { + return "" + } + blockType = vv + } + } + gap, hasBlockGapSupport := maps.GetStrAnyVal[map[string]any](j.themeJson, "settings.spacing.blockGap") + if gap == nil { + hasBlockGapSupport = false + } + _, ok := maps.GetStrAnyVal[map[string]any](j.themeJson, strings.Join(nodes.Path, ".")) + if !ok { + return "" + } + layoutDefinitions, ok := maps.GetStrAnyVal[map[string]any](j.themeJson, "settings.layout.definitions") + if !ok { + return "" + } + blockGapValue := "" + if !hasBlockGapSupport { + if RootBlockSelector == nodes.Selector { + blockGapValue = "0.5em" + } + if blockType != nil { + blockGapValue, _ = maps.GetStrAnyVal[string](blockType, "supports.spacing.blockGap.__experimentalDefault") + } + } else { + //todo getPropertyValue() + } + if blockGapValue != "" { + for key, v := range layoutDefinitions { + definition, ok := v.(map[string]any) + if !ok { + continue + } + if !hasBlockGapSupport && "flex" != key { + continue + } + className := maps.GetStrAnyValWithDefaults(definition, "className", "") + spacingRules := maps.GetStrAnyValWithDefaults(definition, "spacingStyles", []any{}) + if className == "" || spacingRules == nil { + continue + } + for _, rule := range spacingRules { + var declarations []declaration + spacingRule, ok := rule.(map[string]any) + if !ok { + continue + } + selector := maps.GetStrAnyValWithDefaults(spacingRule, "selector", "") + rules := maps.GetStrAnyValWithDefaults(spacingRule, "rules", map[string]any(nil)) + if selector != "" && !layoutSelectorReg.MatchString(selector) || rules == nil { + continue + } + for property, v := range rules { + value, ok := v.(string) + if !ok || value == "" { + value = blockGapValue + } + if isSafeCssDeclaration(property, value) { + declarations = append(declarations, declaration{property, value}) + } + } + format := "" + if !hasBlockGapSupport { + format = helper.Or(RootBlockSelector == nodes.Selector, ":where(.%[2]s%[3]s)", ":where(%[1]s.%[2]s%[3]s)") + } else { + format = helper.Or(RootBlockSelector == nodes.Selector, "%s .%s%s", "%s.%s%s") + } + layoutSelector := fmt.Sprintf(format, nodes.Selector, className, selector) + s.WriteString(toRuleset(layoutSelector, declarations)) + } + } + } + + if RootBlockSelector == nodes.Selector { + for _, v := range layoutDefinitions { + definition, ok := v.(map[string]any) + if !ok { + continue + } + className := maps.GetStrAnyValWithDefaults(definition, "className", "") + baseStyleRules := maps.GetStrAnyValWithDefaults(definition, "baseStyles", []any{}) + if className == "" || nil == baseStyleRules { + continue + } + displayMode := maps.GetStrAnyValWithDefaults(definition, "displayMode", "") + if displayMode != "" && slice.IsContained(validDisplayModes, displayMode) { + layoutSelector := str.Join(nodes.Selector, " .", className) + s.WriteString(toRuleset(layoutSelector, []declaration{{"display", displayMode}})) + } + for _, rule := range baseStyleRules { + var declarations []declaration + r, ok := rule.(map[string]any) + if !ok { + continue + } + selector := maps.GetStrAnyValWithDefaults(r, "selector", "") + rules := maps.GetStrAnyValWithDefaults(r, "rules", map[string]any(nil)) + if selector != "" && !layoutSelectorReg.MatchString(selector) || rules == nil { + continue + } + for property, value := range rules { + val, ok := value.(string) + if !ok || val == "" { + continue + } + if isSafeCssDeclaration(property, val) { + declarations = append(declarations, declaration{property, val}) + } + } + layoutSelector := str.Join(nodes.Selector, " .", className, selector) + s.WriteString(toRuleset(layoutSelector, declarations)) + } + } + } + + return s.String() +} + +func isSafeCssDeclaration(name, value string) bool { + css := str.Join(name, ": ", value) + s := safeCSSFilterAttr(css) + return "" != html.EscapeString(s) +} + +var kses = regexp.MustCompile(`[\x00-\x08\x0B\x0C\x0E-\x1F]`) +var ksesop = regexp.MustCompile(`\\\\+0+`) + +func KsesNoNull(s string, op ...bool) string { + s = kses.ReplaceAllString(s, "") + ops := true + if len(op) > 0 { + ops = op[0] + } + if ops { + s = ksesop.ReplaceAllString(s, "") + } + return s +} + +var allowedProtocols = []string{ + "http", "https", "ftp", "ftps", "mailto", "news", "irc", "irc6", "ircs", "gopher", "nntp", + "feed", "telnet", "mms", "rtsp", "sms", "svn", "tel", "fax", "xmpp", "webcal", "urn", +} + +var allowCssAttr = []string{ + "background", "background-color", "background-image", "background-position", "background-size", "background-attachment", "background-blend-mode", + "border", "border-radius", "border-width", "border-color", "border-style", "border-right", "border-right-color", + "border-right-style", "border-right-width", "border-bottom", "border-bottom-color", "border-bottom-left-radius", + "border-bottom-right-radius", "border-bottom-style", "border-bottom-width", "border-bottom-right-radius", + "border-bottom-left-radius", "border-left", "border-left-color", "border-left-style", "border-left-width", + "border-top", "border-top-color", "border-top-left-radius", "border-top-right-radius", "border-top-style", + "border-top-width", "border-top-left-radius", "border-top-right-radius", "border-spacing", "border-collapse", + "caption-side", "columns", "column-count", "column-fill", "column-gap", "column-rule", "column-span", "column-width", + "color", "filter", "font", "font-family", "font-size", "font-style", "font-variant", "font-weight", + "letter-spacing", "line-height", "text-align", "text-decoration", "text-indent", "text-transform", "height", + "min-height", "max-height", "width", "min-width", "max-width", "margin", "margin-right", "margin-bottom", + "margin-left", "margin-top", "margin-block-start", "margin-block-end", "margin-inline-start", "margin-inline-end", + "padding", "padding-right", "padding-bottom", "padding-left", "padding-top", "padding-block-start", + "padding-block-end", "padding-inline-start", "padding-inline-end", "flex", "flex-basis", "flex-direction", + "flex-flow", "flex-grow", "flex-shrink", "flex-wrap", "gap", "column-gap", "row-gap", "grid-template-columns", + "grid-auto-columns", "grid-column-start", "grid-column-end", "grid-column-gap", "grid-template-rows", + "grid-auto-rows", "grid-row-start", "grid-row-end", "grid-row-gap", "grid-gap", "justify-content", "justify-items", + "justify-self", "align-content", "align-items", "align-self", "clear", "cursor", "direction", "float", + "list-style-type", "object-fit", "object-position", "overflow", "vertical-align", "position", "top", "right", + "bottom", "left", "z-index", "aspect-ratio", "--*", +} +var allowCssAttrMap = slice.FilterAndToMap(allowCssAttr, func(t string) (string, struct{}, bool) { + return t, struct{}{}, true +}) + +var __strReg = regexp.MustCompile(`^--[a-zA-Z0-9-_]+$`) +var cssUrlDataTypes = []string{"background", "background-image", "cursor", "list-style", "list-style-image"} +var cssGradientDataTypes = []string{"background", "background-image"} +var __allowCSSReg = regexp2.MustCompile(`\b(?:var|calc|min|max|minmax|clamp)(\((?:[^()]|(?!1))*\))`, regexp2.None) +var __disallowCSSReg = regexp.MustCompile(`[\\(&=}]|/\*`) + +func safeCSSFilterAttr(css string) string { + css = KsesNoNull(css) + css = strings.TrimSpace(css) + css = str.Replaces(css, []string{"\n", "\r", "\t", ""}) + cssArr := strings.Split(css, ";") + var isCustomVar, found, urlAttr, gradientAttr bool + var cssValue string + var ss strings.Builder + for _, s := range cssArr { + if s == "" { + continue + } + if !strings.Contains(s, ":") { + found = true + } else { + parts := strings.SplitN(s, ":", 2) + selector := strings.TrimSpace(parts[0]) + if maps.IsExists(allowCssAttrMap, "--*") { + i := __strReg.FindStringIndex(selector) + if len(i) > 0 && i[0] > 0 { + isCustomVar = true + allowCssAttr = append(allowCssAttr, selector) + } + } + if maps.IsExists(allowCssAttrMap, selector) { + found = true + urlAttr = slice.IsContained(cssUrlDataTypes, selector) + gradientAttr = slice.IsContained(cssGradientDataTypes, selector) + } + if isCustomVar { + cssValue = strings.TrimSpace(parts[1]) + urlAttr = cssValue[0:4] == "url(" + gradientAttr = strings.Contains(cssValue, "-gradient(") + } + } + + if found { + //todo wtf 🤮 + if urlAttr { + + } + if gradientAttr { + + } + cssTest, _ := __allowCSSReg.Replace(s, "", 0, -1) + isAllow := !__disallowCSSReg.MatchString(cssTest) + if isAllow { + ss.WriteString(s) + ss.WriteString(";") + } + } + + } + return strings.TrimRight(ss.String(), ";") +} + +func (j ThemeJson) getStyletSheet(types, origins []string, options map[string]string) string { + if origins == nil { + origins = append(validOrigins) + } + styleNodes := getStyleNodes(j) + settingsNodes := getSettingNodes(j.blocksMetaData, j.themeJson) + rootStyleKey, _ := slice.SearchFirst(styleNodes, func(n node) bool { + return n.Selector == RootBlockSelector + }) + rootSettingsKey, _ := slice.SearchFirst(settingsNodes, func(n node) bool { + return n.Selector == RootBlockSelector + }) + if os, ok := options["scope"]; ok { + for i := range settingsNodes { + settingsNodes[i].Selector = scopeSelector(os, settingsNodes[i].Selector) + } + for i := range styleNodes { + styleNodes[i].Selector = scopeSelector(os, styleNodes[i].Selector) + } + } + if or, ok := options["root_selector"]; ok && or != "" { + if rootSettingsKey > -1 { + settingsNodes[rootSettingsKey].Selector = or + } + if rootStyleKey > -1 && rootStyleKey < len(settingsNodes) { + settingsNodes[rootStyleKey].Selector = or + } + } + stylesSheet := "" + if slice.IsContained(types, "variables") { + stylesSheet = j.getCssVariables(settingsNodes, origins) + } + + if slice.IsContained(types, "styles") { + stylesSheet = j.getCssVariables(settingsNodes, origins) + if rootStyleKey > -1 { + //todo getRootLayoutRules + stylesSheet = str.Join(stylesSheet) + } + } else if slice.IsContained(types, "base-layout-styles") { + rootSelector := RootBlockSelector + columnsSelector := ".wp-block-columns" + scope, ok := options["scope"] + if ok && scope != "" { + rootSelector = scopeSelector(scope, rootSelector) + columnsSelector = scopeSelector(scope, columnsSelector) + } + rs, ok := options["root_selector"] + if ok && rs != "" { + rootSelector = rs + } + baseStylesNodes := []node{ + { + Path: []string{"styles"}, + Selector: rootSelector, + }, + { + Path: []string{"styles", "blocks", "core/columns"}, + Selector: columnsSelector, + Name: "core/columns", + }, + } + for _, stylesNode := range baseStylesNodes { + stylesSheet = str.Join(stylesSheet, j.getLayoutStyles(stylesNode)) + } + } + + if slice.IsContained(types, "presets") { + stylesSheet = str.Join(stylesSheet, j.getPresetClasses(settingsNodes, origins)) + } + + return stylesSheet +} + +func (j ThemeJson) getPresetClasses(nodes []node, origins []string) string { + var presetRules strings.Builder + for _, n := range nodes { + if n.Selector == "" { + continue + } + no, ok := maps.GetStrAnyVal[map[string]any](j.themeJson, strings.Join(n.Path, ".")) + if !ok { + continue + } + presetRules.WriteString(computePresetClasses(no, n.Selector, origins)) + } + return presetRules.String() +} + +func computePresetClasses(m map[string]any, selector string, origins []string) string { + if selector == RootBlockSelector { + selector = "" + } + var s strings.Builder + for _, meta := range presetsMetadata { + slugs := getSettingsSlugs(m, meta, origins) + for class, property := range meta.classes { + for _, slug := range slugs { + cssVar := strings.ReplaceAll(meta.cssVars, "$slug", slug) + className := strings.ReplaceAll(class, "$slug", slug) + s.WriteString(toRuleset(appendToSelector(selector, className, ""), []declaration{ + {property, str.Join("var(", cssVar, ") !important")}, + })) + } + } + } + return s.String() +} + +func getSettingsSlugs(settings map[string]any, meta presetMeta, origins []string) map[string]string { + if origins == nil { + origins = validOrigins + } + + presetPerOrigin, ok := maps.GetStrAnyVal[map[string]any](settings, strings.Join(meta.path, ".")) + if !ok { + return nil + } + m := map[string]string{} + for _, origin := range origins { + o, ok := maps.GetStrAnyVal[[]map[string]string](presetPerOrigin, origin) + if !ok { + continue + } + for _, mm := range o { + slug := toKebabCase(mm["slug"]) + m[slug] = slug + } + } + return m +} + +func toKebabCase(s string) string { + s = strings.ReplaceAll(s, "'", "") + r, err := __kebabCaseReg.FindStringMatch(s) + if err != nil { + return s + } + var ss []string + for r != nil { + if r.GroupCount() < 1 { + break + } + + ss = append(ss, r.Groups()[0].String()) + r, _ = __kebabCaseReg.FindNextMatch(r) + } + + return strings.ToLower(strings.Join(ss, "-")) +} + +var __kebabCaseReg = func() *regexp2.Regexp { + rsLowerRange := "a-z\\xdf-\\xf6\\xf8-\\xff" + rsNonCharRange := "\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf" + rsPunctuationRange := "\\x{2000}-\\x{206f}" + rsSpaceRange := " \\t\\x0b\\f\\xa0\\x{feff}\\n\\r\\x{2028}\\x{2029}\\x{1680}\\x{180e}\\x{2000}\\x{2001}\\x{2002}\\x{2003}\\x{2004}\\x{2005}\\x{2006}\\x{2007}\\x{2008}\\x{2009}\\x{200a}\\x{202f}\\x{205f}\\x{3000}" + rsUpperRange := "A-Z\\xc0-\\xd6\\xd8-\\xde" + rsBreakRange := rsNonCharRange + rsPunctuationRange + rsSpaceRange + + /** Used to compose unicode capture groups. */ + rsBreak := "[" + rsBreakRange + "]" + rsDigits := "\\d+" // The last lodash version in GitHub uses a single digit here and expands it when in use. + rsLower := "[" + rsLowerRange + "]" + rsMisc := "[^" + rsBreakRange + rsDigits + rsLowerRange + rsUpperRange + "]" + rsUpper := "[" + rsUpperRange + "]" + + /** Used to compose unicode regexes. */ + rsMiscLower := "(?:" + rsLower + "|" + rsMisc + ")" + rsMiscUpper := "(?:" + rsUpper + "|" + rsMisc + ")" + rsOrdLower := "\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])" + rsOrdUpper := "\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])" + + reg := strings.Join([]string{ + rsUpper + "?" + rsLower + "+(?=" + strings.Join([]string{rsBreak, rsUpper, "$"}, "|") + ")", + rsMiscUpper + "+(?=" + strings.Join([]string{rsBreak, rsUpper + rsMiscLower, "$"}, "|") + ")", + rsUpper + "?" + rsMiscLower + "+", + rsUpper + "+", + rsOrdUpper, + rsOrdLower, + rsDigits, + }, "|") + return regexp2.MustCompile(reg, regexp2.Unicode) +}() + +var presetsMetadata = []presetMeta{ + { + path: []string{"color", "palette"}, + preventOverride: []string{"color", "defaultPalette"}, + useDefaultNames: false, + valueKey: "color", + valueFunc: nil, + cssVars: "--wp--preset--color--$slug", + classes: map[string]string{ + ".has-$slug-color": "color", + ".has-$slug-background-color": "background-color", + ".has-$slug-border-color": "border-color", + }, + properties: []string{"color", "background-color", "border-color"}, + }, { + path: []string{"color", "gradients"}, + preventOverride: []string{"color", "defaultGradients"}, + useDefaultNames: false, + valueKey: "gradient", + valueFunc: nil, + cssVars: "--wp--preset--gradient--$slug", + classes: map[string]string{ + ".has-$slug-gradient-background": "background", + }, + properties: []string{"background"}, + }, { + path: []string{"color", "duotone"}, + preventOverride: []string{"color", "defaultDuotone"}, + useDefaultNames: false, + valueKey: "", + valueFunc: wpGetDuotoneFilterProperty, + cssVars: "--wp--preset--duotone--$slug", + classes: map[string]string{}, + properties: []string{"filter"}, + }, { + path: []string{"typography", "fontSizes"}, + preventOverride: []string{}, + useDefaultNames: true, + valueKey: "", + valueFunc: wpGetTypographyFontSizeValue, + cssVars: "--wp--preset--font-size--$slug", + classes: map[string]string{ + ".has-$slug-font-size": "font-size", + }, + properties: []string{"font-size"}, + }, { + path: []string{"typography", "fontFamilies"}, + preventOverride: []string{}, + useDefaultNames: false, + valueKey: "fontFamily", + valueFunc: nil, + cssVars: "--wp--preset--font-family--$slug", + classes: map[string]string{ + ".has-$slug-font-family": "font-family", + }, + properties: []string{"font-family"}, + }, { + path: []string{"spacing", "spacingSizes"}, + preventOverride: []string{}, + useDefaultNames: true, + valueKey: "size", + valueFunc: nil, + cssVars: "--wp--preset--spacing--$slug", + classes: map[string]string{}, + properties: []string{"padding", "margin"}, + }, { + path: []string{"shadow", "presets"}, + preventOverride: []string{"shadow", "defaultPresets"}, + useDefaultNames: false, + valueKey: "shadow", + valueFunc: nil, + cssVars: "--wp--preset--shadow--$slug", + classes: map[string]string{}, + properties: []string{"box-shadow"}, + }, +} + +type presetMeta struct { + path []string + preventOverride []string + useDefaultNames bool + valueFunc func(map[string]string, map[string]any) string + valueKey string + cssVars string + classes map[string]string + properties []string +} + +type declaration struct { + name string + value string +} + +func wpGetDuotoneFilterProperty(preset map[string]string, _ map[string]any) string { + v, ok := preset["colors"] + if ok && "unset" == v { + return "none" + } + id, ok := preset["slug"] + if ok { + id = str.Join("wp-duotone-", id) + } + return str.Join(`url('#`, id, "')") +} + +func wpGetTypographyFontSizeValue(preset map[string]string, m map[string]any) string { + size, ok := preset["size"] + if !ok { + return "" + } + if size == "" || size == "0" { + return size + } + origin := "custom" + if !wpconfig.HasThemeJson() { + origin = "theme" + } + typographySettings, ok := maps.GetStrAnyVal[map[string]any](m, "typography") + if !ok { + return size + } + fluidSettings, ok := maps.GetStrAnyVal[map[string]any](typographySettings, "fluid") + + //todo so complex dying 👻 + _ = fluidSettings + _ = origin + return size +} + +var __themeJson = reload.Vars(ThemeJson{}) + +func GetThemeJson() ThemeJson { + return __themeJson.Load() +} + +func computeThemeVars(m map[string]any) []declaration { + //todo ...... + return nil +} + +func computePresetVars(m map[string]any, origins []string) []declaration { + var declarations []declaration + for _, metadatum := range presetsMetadata { + slug := getSettingsValuesBySlug(m, metadatum, origins) + for k, v := range slug { + declarations = append(declarations, declaration{ + name: strings.Replace(metadatum.cssVars, "$slug", k, -1), + value: v, + }) + } + } + return declarations +} + +func getSettingsValuesBySlug(m map[string]any, meta presetMeta, origins []string) map[string]string { + perOrigin := maps.GetStrAnyValWithDefaults[map[string]any](m, strings.Join(meta.path, "."), nil) + r := map[string]string{} + for _, origin := range origins { + if vv, ok := maps.GetStrAnyVal[[]map[string]string](perOrigin, origin); ok { + for _, preset := range vv { + slug := preset["slug"] + value := "" + if vv, ok := preset[meta.valueKey]; ok && vv != "" { + value = vv + } else if meta.valueFunc != nil { + value = meta.valueFunc(preset, m) + } + r[slug] = value + } + } + } + return r +} + +func setSpacingSizes(t ThemeJson) { + m, _ := maps.GetStrAnyVal[map[string]any](t.themeJson, "settings.spacing.spacingScale") + unit, _ := maps.GetStrAnyVal[string](m, "unit") + currentStep, _ := maps.GetStrAnyVal[float64](m, "mediumStep") + mediumStep := currentStep + steps, _ := maps.GetStrAnyVal[float64](m, "steps") + operator, _ := maps.GetStrAnyVal[string](m, "operator") + increment, _ := maps.GetStrAnyVal[float64](m, "increment") + stepsMidPoint := math.Round(steps / 2) + reminder := float64(0) + xSmallCount := "" + var sizes []map[string]string + slug := 40 + for i := stepsMidPoint - 1; i > 0 && slug > 0 && steps > 1; i-- { + if "+" == operator { + currentStep -= increment + } else if increment > 1 { + currentStep /= increment + } else { + currentStep *= increment + } + if currentStep <= 0 { + reminder = i + break + } + name := "small" + if i != stepsMidPoint-1 { + name = str.Join(xSmallCount, "X-Small") + } + sizes = append(sizes, map[string]string{ + "name": name, + "slug": number.IntToString(slug), + "size": fmt.Sprintf("%v%s", number.Round(currentStep, 2), unit), + }) + if i == stepsMidPoint-2 { + xSmallCount = strconv.Itoa(2) + } + if i < stepsMidPoint-2 { + n := str.ToInt[int](xSmallCount) + n++ + xSmallCount = strconv.Itoa(n) + } + slug -= 10 + } + slice.ReverseSelf(sizes) + sizes = append(sizes, map[string]string{ + "name": "Medium", + "slug": "50", + "size": str.Join(number.ToString(mediumStep), unit), + }) + currentStep = mediumStep + slug = 60 + xLargeCount := "" + stepsAbove := steps - stepsMidPoint + reminder + for aboveMidpointCount := float64(0); aboveMidpointCount < stepsAbove; aboveMidpointCount++ { + if "+" == operator { + currentStep += increment + } else if increment >= 1 { + currentStep *= increment + } else { + currentStep /= increment + } + name := "Large" + if 0 != aboveMidpointCount { + name = str.Join(xLargeCount, "X-Large") + } + sizes = append(sizes, map[string]string{ + "name": name, + "slug": strconv.Itoa(slug), + "size": fmt.Sprintf("%v%s", number.Round(currentStep, 2), unit), + }) + if aboveMidpointCount == 1 { + xLargeCount = strconv.Itoa(2) + } + if aboveMidpointCount > 1 { + x := str.ToInt[int](xLargeCount) + x++ + xLargeCount = strconv.Itoa(x) + } + slug += 10 + } + if steps <= 7 { + for i := 0; i < len(sizes); i++ { + sizes[i]["name"] = strconv.Itoa(i + 1) + } + } + maps.SetStrAnyVal(t.themeJson, "settings.spacing.spacingSizes.default", sizes) +} + +func (j ThemeJson) getCssVariables(settingNodes []node, origins []string) string { + var s strings.Builder + for _, settingNode := range settingNodes { + if "" == settingNode.Selector { + continue + } + n := maps.GetStrAnyValWithDefaults[map[string]any](j.themeJson, strings.Join(settingNode.Path, "."), nil) + declarations := computePresetVars(n, origins) + declarations = append(declarations, computeThemeVars(j.themeJson)...) + s.WriteString(toRuleset(settingNode.Selector, declarations)) + } + return s.String() +} + +func toRuleset(selector string, declarations []declaration) string { + if len(declarations) < 1 { + return "" + } + s := slice.Reduce(declarations, func(t declaration, r string) string { + return str.Join(r, t.name, ":", t.value, ";") + }, "") + return str.Join(selector, "{", s, "}") +} + +func getStyleNodes(t ThemeJson) []node { + var styleNodes = []node{ + {[]string{"styles"}, "body", ""}, + } + m := maps.GetStrAnyValWithDefaults[map[string]any](t.themeJson, "styles.elements", nil) + if len(m) < 1 { + return nil + } + for e, s := range __elements { + _, ok := m[e] + if !ok { + continue + } + styleNodes = append(styleNodes, node{[]string{"styles", "elements", e}, s, ""}) + ss, ok := __validElementPseudoSelectors[e] + if ok { + for _, sel := range ss { + if maps.IsExists(__elements, sel) { + styleNodes = append(styleNodes, node{ + Path: []string{"styles", "elements", e}, + Selector: appendToSelector(s, sel, ""), + }) + } + } + } + } + blocks, _ := maps.GetStrAnyVal[map[string]any](t.blocksMetaData, "theme_json.styles.blocks") + maps.SetStrAnyVal(t.themeJson, "styles.blocks", blocks) + blockNodes := getBlockNodes(t.blocksMetaData) + for _, blockNode := range blockNodes { + p, ok := maps.GetStrAnyVal[[]string](blockNode, "path") + s, oo := maps.GetStrAnyVal[string](blockNode, "selector") + if ok && oo { + _ = append(styleNodes, node{Path: p, Selector: s}) + //styleNodes = append(styleNodes, node{Path: p, Selector: s}) + } + } + + return styleNodes +} + +func GetGlobalStyletSheet() string { + t := __themeJson.Load() + var types, origins []string + if types == nil && !wpconfig.HasThemeJson() { + types = []string{"variables", "presets", "base-layout-styles"} + } else if types == nil { + types = []string{"variables", "styles", "presets"} + } + styleSheet := "" + if slice.IsContained(types, "variables") { + origins = []string{"default", "theme", "custom"} + styleSheet = t.getStyletSheet([]string{"variables"}, origins, nil) + slice.Delete(&types, slice.IndexOf(types, "variables")) + } + + if len(types) > 0 { + origins = []string{"default", "theme", "custm"} + if !wpconfig.HasThemeJson() { + origins = []string{"default"} + } + styleSheet = str.Join(styleSheet, t.getStyletSheet(types, origins, nil)) + } + + return styleSheet +} + +func wpGetTypographyValueAndUnit(value string, options map[string]any) { + /*options := maps.Merge(options, map[string]any{ + "coerce_to": "", + "root_size_value": 16, + "acceptable_units": []string{"rem", "px", "em"}, + }) + u, _ := maps.GetStrAnyVal[[]string](options, "acceptable_units") + acceptableUnitsGroup := strings.Join(u, "|")*/ + +} + +/*func (j ThemeJson) getStylesForBlock(blockMeta map[string]any) { + path, _ := maps.GetStrAnyVal[[]string](blockMeta, "path") + node, _ := maps.GetStrAnyVal[map[string]any](j.themeJson, strings.Join(path, ".")) + useRootPadding := maps.GetStrAnyValWithDefaults(j.themeJson, "settings.useRootPaddingAwareAlignments", false) + settings, _ := maps.GetStrAnyVal(j.themeJson, "settings") + is_processing_element := slice.IsContained(path, "elements") + currentElement := "" + if is_processing_element { + currentElement = path[len(path)-1] + } + element_pseudo_allowed := __validElementPseudoSelectors[currentElement] + +} + +func computeStyleProperties(styles, settings, properties, themeJson map[string]any, selector string, useRootPadding bool) { + if properties == nil { + //properties = + } +} +*/ diff --git a/safety/safemap.go b/safety/safemap.go index 06a54bf..fcf9b6c 100644 --- a/safety/safemap.go +++ b/safety/safemap.go @@ -390,6 +390,15 @@ func (m *Map[K, V]) Range(f func(key K, value V) bool) { } } +func (m *Map[K, V]) Keys() []K { + var r []K + m.Range(func(key K, _ V) bool { + r = append(r, key) + return true + }) + return r +} + func (m *Map[K, V]) missLocked() { m.misses++ if m.misses < len(m.dirty) {