[add] sui template global data support
This commit is contained in:
parent
b0d9631559
commit
5bce0f6081
12 changed files with 241 additions and 34 deletions
|
|
@ -69,13 +69,20 @@ func (r *Request) Render() (string, int, error) {
|
|||
dataSel.Remove()
|
||||
}
|
||||
|
||||
globalDataText := ""
|
||||
globalDataSel := doc.Find("script[name=global]")
|
||||
if globalDataSel != nil && globalDataSel.Length() > 0 {
|
||||
globalDataText = globalDataSel.Text()
|
||||
globalDataSel.Remove()
|
||||
}
|
||||
|
||||
html, err := doc.Html()
|
||||
if err != nil {
|
||||
return "", 500, fmt.Errorf("parse error, please re-complie the page %s", err.Error())
|
||||
}
|
||||
|
||||
// Save to The Cache
|
||||
c = core.SetCache(r.File, html, dataText)
|
||||
c = core.SetCache(r.File, html, dataText, globalDataText)
|
||||
log.Trace("The page %s is cached", r.File)
|
||||
}
|
||||
|
||||
|
|
@ -88,6 +95,14 @@ func (r *Request) Render() (string, int, error) {
|
|||
}
|
||||
}
|
||||
|
||||
if c.Global != "" {
|
||||
global, err := r.Request.ExecString(c.Global)
|
||||
if err != nil {
|
||||
return "", 500, fmt.Errorf("global data error, please re-complie the page %s", err.Error())
|
||||
}
|
||||
data["$global"] = global
|
||||
}
|
||||
|
||||
parser := core.NewTemplateParser(data, nil)
|
||||
html, err := parser.Render(c.HTML)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ func (page *Page) Compile(option *BuildOption) (string, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Page Data
|
||||
if page.Codes.DATA.Code != "" {
|
||||
doc.Find("body").AppendHtml("\n\n" + `<script name="data" type="json">` + "\n" +
|
||||
page.Codes.DATA.Code +
|
||||
|
|
@ -29,13 +30,17 @@ func (page *Page) Compile(option *BuildOption) (string, error) {
|
|||
)
|
||||
}
|
||||
|
||||
// // add the route data
|
||||
// doc.Find("body").AppendHtml(`<script name="route" type="json">` + "\n" +
|
||||
// fmt.Sprintf(
|
||||
// `{"sui": "%s", "template": "%s", "route": "%s"}`,
|
||||
// page.SuiID, page.TemplateID, page.Route,
|
||||
// ) +
|
||||
// "\n</script>\n")
|
||||
// Page Global Data
|
||||
if page.GlobalData != nil && len(page.GlobalData) > 0 {
|
||||
doc.Find("body").AppendHtml("\n\n" + `<script name="global" type="json">` + "\n" +
|
||||
string(page.GlobalData) +
|
||||
"\n</script>\n\n",
|
||||
)
|
||||
}
|
||||
|
||||
// Replace the document
|
||||
page.Config = page.GetConfig()
|
||||
page.ReplaceDocument(doc)
|
||||
|
||||
html, err := doc.Html()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/antonmedv/expr/ast"
|
||||
"github.com/antonmedv/expr/vm"
|
||||
"github.com/yaoapp/gou/process"
|
||||
"github.com/yaoapp/kun/log"
|
||||
)
|
||||
|
||||
var stmtRe = regexp.MustCompile(`\{\{([^}]+)\}\}`)
|
||||
|
|
@ -56,6 +57,20 @@ func (data Data) ExecString(stmt string) (string, error) {
|
|||
return fmt.Sprintf("%v", res), nil
|
||||
}
|
||||
|
||||
// Replace replace the statement
|
||||
func (data Data) Replace(value string) (string, bool) {
|
||||
hasStmt := false
|
||||
res := stmtRe.ReplaceAllStringFunc(value, func(stmt string) string {
|
||||
hasStmt = true
|
||||
res, err := data.ExecString(stmt)
|
||||
if err != nil {
|
||||
log.Warn("Replace %s: %s", stmt, err)
|
||||
}
|
||||
return res
|
||||
})
|
||||
return res, hasStmt
|
||||
}
|
||||
|
||||
func _process(args ...any) (interface{}, error) {
|
||||
|
||||
if len(args) < 1 {
|
||||
|
|
|
|||
|
|
@ -66,6 +66,13 @@ func (page *Page) EditorRender() (*ResponseEditorRender, error) {
|
|||
}
|
||||
|
||||
res.Render(data)
|
||||
|
||||
// Set the title
|
||||
res.Config.Rendered = &PageConfigRendered{
|
||||
Title: page.RenderTitle(data),
|
||||
Link: page.Link(request),
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
|
|
@ -92,6 +99,7 @@ func (res *ResponseEditorRender) Render(data map[string]interface{}) error {
|
|||
res.Warnings = append(res.Warnings, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ type SUI interface {
|
|||
WithSid(sid string)
|
||||
PublicRootMatcher() *Matcher
|
||||
GetPublic() *Public
|
||||
PublicRootWithSid(sid string) (string, error)
|
||||
}
|
||||
|
||||
// ITemplate is the interface for the ITemplate
|
||||
|
|
|
|||
142
sui/core/page.go
142
sui/core/page.go
|
|
@ -1,9 +1,12 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/yaoapp/kun/log"
|
||||
)
|
||||
|
||||
// Get get the base info
|
||||
|
|
@ -14,12 +17,6 @@ func (page *Page) Get() *Page {
|
|||
// GetConfig get the config
|
||||
func (page *Page) GetConfig() *PageConfig {
|
||||
|
||||
if page.Config == nil {
|
||||
page.Config = &PageConfig{
|
||||
Mock: &PageMock{Method: "GET"},
|
||||
}
|
||||
}
|
||||
|
||||
if page.Codes.CONF.Code != "" {
|
||||
var config PageConfig
|
||||
err := jsoniter.Unmarshal([]byte(page.Codes.CONF.Code), &config)
|
||||
|
|
@ -28,6 +25,12 @@ func (page *Page) GetConfig() *PageConfig {
|
|||
}
|
||||
}
|
||||
|
||||
if page.Config == nil {
|
||||
page.Config = &PageConfig{
|
||||
Mock: &PageMock{Method: "GET"},
|
||||
}
|
||||
}
|
||||
|
||||
if page.Config.Mock == nil {
|
||||
page.Config.Mock = &PageMock{Method: "GET"}
|
||||
}
|
||||
|
|
@ -35,7 +38,7 @@ func (page *Page) GetConfig() *PageConfig {
|
|||
return page.Config
|
||||
}
|
||||
|
||||
// Data get the data
|
||||
// Data get the data (deprecated)
|
||||
func (page *Page) Data(request *Request) (Data, map[string]interface{}, error) {
|
||||
|
||||
setting := map[string]interface{}{
|
||||
|
|
@ -63,5 +66,130 @@ func (page *Page) Exec(request *Request) (Data, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Global data
|
||||
if page.GlobalData != nil {
|
||||
global, err := request.ExecString(string(page.GlobalData))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data["$global"] = global
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// RenderTitle render the title
|
||||
func (page *Page) RenderTitle(data Data) string {
|
||||
|
||||
if page.Config == nil {
|
||||
return "Untitled"
|
||||
}
|
||||
|
||||
if page.Config.Title != "" {
|
||||
title, _ := data.Replace(page.Config.Title)
|
||||
return title
|
||||
}
|
||||
|
||||
return "Untitled"
|
||||
}
|
||||
|
||||
// Link get the link
|
||||
func (page *Page) Link(r *Request) string {
|
||||
sui, has := SUIs[page.SuiID]
|
||||
if !has {
|
||||
log.Error("[sui] get page link %s not found", page.SuiID)
|
||||
return ""
|
||||
}
|
||||
|
||||
root, err := sui.PublicRootWithSid(r.Sid)
|
||||
if err != nil {
|
||||
log.Error("[sui] get page link %s root error %s", page.SuiID, err.Error())
|
||||
return ""
|
||||
}
|
||||
|
||||
parts := strings.Split(page.Route, "/")
|
||||
if len(parts) == 0 {
|
||||
log.Error("[sui] get page link %s path not found", page.SuiID)
|
||||
return ""
|
||||
}
|
||||
|
||||
// Get the route
|
||||
paths := []string{root, "/"}
|
||||
for _, part := range parts {
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(part, "[") && strings.HasSuffix(part, "]") {
|
||||
name := strings.TrimSuffix(strings.TrimPrefix(part, "["), "]")
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if r == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
value, has := r.Params[name]
|
||||
if !has {
|
||||
continue
|
||||
}
|
||||
|
||||
paths = append(paths, value)
|
||||
}
|
||||
paths = append(paths, part)
|
||||
}
|
||||
|
||||
return filepath.Join(paths...)
|
||||
}
|
||||
|
||||
// ReplaceDocument replace the document
|
||||
func (page *Page) ReplaceDocument(doc *goquery.Document) {
|
||||
|
||||
if page.Config == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if doc == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if page.Config.Title != "" {
|
||||
if doc.Find("title") != nil {
|
||||
doc.Find("title").SetText(page.Config.Title)
|
||||
}
|
||||
}
|
||||
|
||||
if page.Config.Description != "" {
|
||||
if doc.Find("meta[name=description]") != nil {
|
||||
doc.Find("meta[name=description]").SetAttr("content", page.Config.Description)
|
||||
}
|
||||
}
|
||||
|
||||
if page.Config.SEO != nil {
|
||||
|
||||
if page.Config.SEO.Title != "" {
|
||||
if doc.Find("meta[property=og:title]") != nil {
|
||||
doc.Find("meta[property=og:title]").SetAttr("content", page.Config.SEO.Title)
|
||||
}
|
||||
}
|
||||
|
||||
if page.Config.SEO.Description != "" {
|
||||
if doc.Find("meta[name=description]") != nil {
|
||||
doc.Find("meta[name=description]").SetAttr("content", page.Config.SEO.Description)
|
||||
}
|
||||
}
|
||||
|
||||
if page.Config.SEO.Image != "" {
|
||||
if doc.Find("meta[property=og:image]") != nil {
|
||||
doc.Find("meta[property=og:image]").SetAttr("content", page.Config.SEO.Image)
|
||||
}
|
||||
}
|
||||
|
||||
if page.Config.SEO.URL != "" {
|
||||
if doc.Find("meta[property=og:url]") != nil {
|
||||
doc.Find("meta[property=og:url]").SetAttr("content", page.Config.SEO.URL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,17 +112,7 @@ func (parser *TemplateParser) parseElementNode(sel *goquery.Selection) {
|
|||
|
||||
func (parser *TemplateParser) parseTextNode(node *html.Node) {
|
||||
parser.sequence = parser.sequence + 1
|
||||
hasStmt := false
|
||||
res := stmtRe.ReplaceAllFunc([]byte(node.Data), func(stmt []byte) []byte {
|
||||
hasStmt = true
|
||||
res, err := parser.data.ExecString(string(stmt))
|
||||
if err != nil {
|
||||
parser.errors = append(parser.errors, err)
|
||||
return []byte(``)
|
||||
}
|
||||
return []byte(res)
|
||||
})
|
||||
|
||||
res, hasStmt := parser.data.Replace(node.Data)
|
||||
// Bind the variable to the parent node
|
||||
if node.Parent != nil && hasStmt {
|
||||
bindings := strings.TrimSpace(node.Data)
|
||||
|
|
@ -134,8 +124,7 @@ func (parser *TemplateParser) parseTextNode(node *html.Node) {
|
|||
}...)
|
||||
}
|
||||
}
|
||||
|
||||
node.Data = string(res)
|
||||
node.Data = res
|
||||
}
|
||||
|
||||
func (parser *TemplateParser) forStatementNode(sel *goquery.Selection) {
|
||||
|
|
|
|||
|
|
@ -11,8 +11,9 @@ import (
|
|||
|
||||
// Cache the cache
|
||||
type Cache struct {
|
||||
Data string
|
||||
HTML string
|
||||
Data string
|
||||
Global string
|
||||
HTML string
|
||||
}
|
||||
|
||||
// Caches the caches
|
||||
|
|
@ -247,10 +248,11 @@ func (r *Request) parseArgs(args []interface{}) ([]interface{}, error) {
|
|||
}
|
||||
|
||||
// SetCache set the cache
|
||||
func SetCache(file string, html string, data string) *Cache {
|
||||
func SetCache(file string, html string, data string, global string) *Cache {
|
||||
Caches[file] = &Cache{
|
||||
Data: data,
|
||||
HTML: html,
|
||||
Data: data,
|
||||
HTML: html,
|
||||
Global: global,
|
||||
}
|
||||
return Caches[file]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,28 @@ func (sui *DSL) PublicRootMatcher() *Matcher {
|
|||
return &Matcher{Exact: pub.Root}
|
||||
}
|
||||
|
||||
// PublicRootWithSid returns the public root path with sid
|
||||
func (sui *DSL) PublicRootWithSid(sid string) (string, error) {
|
||||
ss := session.Global().ID(sid)
|
||||
data, err := ss.Dump()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
vars := map[string]interface{}{"$session": data}
|
||||
var root = sui.Public.Root
|
||||
dot := maps.Of(vars).Dot()
|
||||
output := varRe.ReplaceAllStringFunc(root, func(matched string) string {
|
||||
varName := strings.TrimSpace(matched[2 : len(matched)-2])
|
||||
if value, ok := dot[varName]; ok {
|
||||
return fmt.Sprint(value)
|
||||
}
|
||||
return "__undefined"
|
||||
})
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// PublicRoot returns the public root path
|
||||
func (sui *DSL) PublicRoot() (string, error) {
|
||||
// Cache the public root
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ type Page struct {
|
|||
Path string `json:"-"`
|
||||
Codes SourceCodes `json:"-"`
|
||||
Document []byte `json:"-"`
|
||||
GlobalData []byte `json:"-"`
|
||||
}
|
||||
|
||||
// PageTreeNode is the struct for the page tree node
|
||||
|
|
@ -86,6 +87,7 @@ type Template struct {
|
|||
Screenshots []string `json:"screenshots"`
|
||||
Themes []SelectOption `json:"themes"`
|
||||
Document []byte `json:"-"`
|
||||
GlobalData []byte `json:"-"`
|
||||
}
|
||||
|
||||
// Theme is the struct for the theme
|
||||
|
|
@ -216,7 +218,8 @@ type PageMock struct {
|
|||
// PageConfig is the struct for the page config
|
||||
type PageConfig struct {
|
||||
PageSetting `json:",omitempty"`
|
||||
Mock *PageMock `json:"mock,omitempty"`
|
||||
Mock *PageMock `json:"mock,omitempty"`
|
||||
Rendered *PageConfigRendered `json:"rendered,omitempty"`
|
||||
}
|
||||
|
||||
// PageSetting is the struct for the page setting
|
||||
|
|
@ -226,6 +229,12 @@ type PageSetting struct {
|
|||
SEO *PageSEO `json:"seo,omitempty"`
|
||||
}
|
||||
|
||||
// PageConfigRendered is the struct for the page config rendered
|
||||
type PageConfigRendered struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
Link string `json:"link,omitempty"`
|
||||
}
|
||||
|
||||
// PageSEO is the struct for the page seo
|
||||
type PageSEO struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
|
|
|
|||
|
|
@ -146,6 +146,16 @@ func (local *Local) getTemplate(id string, path string) (*Template, error) {
|
|||
tmpl.Document = documentBytes
|
||||
}
|
||||
|
||||
// load the __data.json
|
||||
dataFile := filepath.Join(path, "__data.json")
|
||||
if local.fs.IsFile(dataFile) {
|
||||
dataBytes, err := local.fs.ReadFile(dataFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tmpl.GlobalData = dataBytes
|
||||
}
|
||||
|
||||
return &tmpl, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -374,6 +374,9 @@ func (page *Page) Load() error {
|
|||
|
||||
// Set the page document
|
||||
page.Document = page.tmpl.Document
|
||||
|
||||
// Set the page global data
|
||||
page.GlobalData = page.tmpl.GlobalData
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue