增加模板
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -147,6 +148,67 @@ func (c *TenantSiteController) forceHTTPSStorageURLs(html string) string {
|
||||
return strings.ReplaceAll(html, "http://"+host, "https://"+host)
|
||||
}
|
||||
|
||||
// rewriteThemeAssets 将模板内书写的相对资源路径(href/src/url() 等)
|
||||
// 统一改写为 /themes/{code}/ 下的绝对路径,保证任意页面深度(/、/news、
|
||||
// /news/:id、/page/:path)下静态资源都能命中 beego 的 /themes 静态映射。
|
||||
// 第三方模板通常直接写相对路径(如 assets/css/xxx.css),不改写会 404。
|
||||
// 注:Go regexp(RE2)不支持 \1/\2 反向引用,闭合引号用 ["'] 宽松匹配;
|
||||
// 值部分 [^"']* 不含引号,正常 HTML 下首尾引号必然一致,行为等价。
|
||||
var (
|
||||
reThemeAssetAttr = regexp.MustCompile(`(?i)\b(href|src|poster|data-src|srcset)\s*=\s*(["'])([^"']*)["']`)
|
||||
reThemeAssetURL = regexp.MustCompile(`(?i)url\(\s*(["']?)([^"')]+)["']?\s*\)`)
|
||||
)
|
||||
|
||||
func rewriteThemeAssets(html, code string) string {
|
||||
prefix := "/themes/" + code + "/"
|
||||
fix := func(u string) string {
|
||||
u = strings.TrimSpace(u)
|
||||
if u == "" {
|
||||
return u
|
||||
}
|
||||
lower := strings.ToLower(u)
|
||||
// 绝对路径/协议链接/锚点等一律不动(含引擎输出的 /uploads、存储 CDN 地址)
|
||||
if strings.HasPrefix(u, "/") || strings.HasPrefix(u, "#") || strings.HasPrefix(u, "?") ||
|
||||
strings.HasPrefix(lower, "http://") || strings.HasPrefix(lower, "https://") ||
|
||||
strings.HasPrefix(lower, "//") || strings.HasPrefix(lower, "data:") ||
|
||||
strings.HasPrefix(lower, "javascript:") || strings.HasPrefix(lower, "mailto:") ||
|
||||
strings.HasPrefix(lower, "tel:") {
|
||||
return u
|
||||
}
|
||||
u = strings.TrimPrefix(u, "./")
|
||||
for strings.HasPrefix(u, "../") {
|
||||
u = strings.TrimPrefix(u, "../")
|
||||
}
|
||||
return prefix + u
|
||||
}
|
||||
|
||||
html = reThemeAssetAttr.ReplaceAllStringFunc(html, func(m string) string {
|
||||
sub := reThemeAssetAttr.FindStringSubmatch(m)
|
||||
attr, quote, val := sub[1], sub[2], sub[3]
|
||||
if strings.EqualFold(attr, "srcset") {
|
||||
parts := strings.Split(val, ",")
|
||||
for i, p := range parts {
|
||||
seg := strings.Fields(strings.TrimSpace(p))
|
||||
if len(seg) > 0 {
|
||||
seg[0] = fix(seg[0])
|
||||
parts[i] = strings.Join(seg, " ")
|
||||
}
|
||||
}
|
||||
val = strings.Join(parts, ", ")
|
||||
} else {
|
||||
val = fix(val)
|
||||
}
|
||||
return attr + "=" + quote + val + quote
|
||||
})
|
||||
|
||||
// 行内 style / <style> 块里的 url() 引用
|
||||
html = reThemeAssetURL.ReplaceAllStringFunc(html, func(m string) string {
|
||||
sub := reThemeAssetURL.FindStringSubmatch(m)
|
||||
return "url(" + sub[1] + fix(sub[2]) + sub[1] + ")"
|
||||
})
|
||||
return html
|
||||
}
|
||||
|
||||
// render 解析并输出模板文件
|
||||
func (c *TenantSiteController) render(file string, mod func(ctx *tagengine.RenderCtx)) {
|
||||
tid, ok := c.resolveTid()
|
||||
@@ -171,7 +233,7 @@ func (c *TenantSiteController) render(file string, mod func(ctx *tagengine.Rende
|
||||
c.writeHTML(500, fmt.Sprintf(renderErrorPage, "模板渲染失败"))
|
||||
return
|
||||
}
|
||||
c.writeHTML(200, out)
|
||||
c.writeHTML(200, rewriteThemeAssets(out, code))
|
||||
}
|
||||
|
||||
// Index GET / 首页
|
||||
|
||||
Reference in New Issue
Block a user