Merge pull request #507 from trheyi/main
[add] sui editor render with mock request and global data support
This commit is contained in:
commit
5ab5361e68
15 changed files with 360 additions and 79 deletions
|
|
@ -655,16 +655,7 @@ func EditorRender(process *process.Process) interface{} {
|
||||||
exception.New(err.Error(), 500).Throw()
|
exception.New(err.Error(), 500).Throw()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request data
|
res, err := page.EditorRender()
|
||||||
urlQuery := url.Values{}
|
|
||||||
if process.NumOfArgs() > 3 {
|
|
||||||
if v, ok := process.Args[3].(url.Values); ok {
|
|
||||||
urlQuery = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
req := &core.Request{Method: "GET", Query: urlQuery}
|
|
||||||
res, err := page.EditorRender(req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
exception.New(err.Error(), 500).Throw()
|
exception.New(err.Error(), 500).Throw()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,13 +69,20 @@ func (r *Request) Render() (string, int, error) {
|
||||||
dataSel.Remove()
|
dataSel.Remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
globalDataText := ""
|
||||||
|
globalDataSel := doc.Find("script[name=global]")
|
||||||
|
if globalDataSel != nil && globalDataSel.Length() > 0 {
|
||||||
|
globalDataText = globalDataSel.Text()
|
||||||
|
globalDataSel.Remove()
|
||||||
|
}
|
||||||
|
|
||||||
html, err := doc.Html()
|
html, err := doc.Html()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", 500, fmt.Errorf("parse error, please re-complie the page %s", err.Error())
|
return "", 500, fmt.Errorf("parse error, please re-complie the page %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save to The Cache
|
// 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)
|
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)
|
parser := core.NewTemplateParser(data, nil)
|
||||||
html, err := parser.Render(c.HTML)
|
html, err := parser.Render(c.HTML)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import (
|
||||||
func (page *Page) Build(option *BuildOption) (*goquery.Document, []string, error) {
|
func (page *Page) Build(option *BuildOption) (*goquery.Document, []string, error) {
|
||||||
|
|
||||||
warnings := []string{}
|
warnings := []string{}
|
||||||
html, err := page.BuildHTML(option.AssetRoot)
|
html, err := page.BuildHTML(option)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
warnings = append(warnings, err.Error())
|
warnings = append(warnings, err.Error())
|
||||||
}
|
}
|
||||||
|
|
@ -23,14 +23,14 @@ func (page *Page) Build(option *BuildOption) (*goquery.Document, []string, error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add Style
|
// Add Style
|
||||||
style, err := page.BuildStyle(option.AssetRoot)
|
style, err := page.BuildStyle(option)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
warnings = append(warnings, err.Error())
|
warnings = append(warnings, err.Error())
|
||||||
}
|
}
|
||||||
doc.Selection.Find("head").AppendHtml(style)
|
doc.Selection.Find("head").AppendHtml(style)
|
||||||
|
|
||||||
// Add Script
|
// Add Script
|
||||||
script, err := page.BuildScript(option.AssetRoot)
|
script, err := page.BuildScript(option)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
warnings = append(warnings, err.Error())
|
warnings = append(warnings, err.Error())
|
||||||
}
|
}
|
||||||
|
|
@ -39,15 +39,18 @@ func (page *Page) Build(option *BuildOption) (*goquery.Document, []string, error
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildHTML build the html
|
// BuildHTML build the html
|
||||||
func (page *Page) BuildHTML(assetRoot string) (string, error) {
|
func (page *Page) BuildHTML(option *BuildOption) (string, error) {
|
||||||
|
|
||||||
html := string(page.Document)
|
html := string(page.Document)
|
||||||
if page.Codes.HTML.Code != "" {
|
if page.Codes.HTML.Code != "" {
|
||||||
html = strings.Replace(html, "{{ __page }}", page.Codes.HTML.Code, 1)
|
html = strings.Replace(html, "{{ __page }}", page.Codes.HTML.Code, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
code := strings.ReplaceAll(html, "@assets", assetRoot)
|
if !option.IgnoreAssetRoot {
|
||||||
res, err := page.CompileHTML([]byte(code), false)
|
html = strings.ReplaceAll(html, "@assets", option.AssetRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := page.CompileHTML([]byte(html), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
@ -56,12 +59,16 @@ func (page *Page) BuildHTML(assetRoot string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildStyle build the style
|
// BuildStyle build the style
|
||||||
func (page *Page) BuildStyle(assetRoot string) (string, error) {
|
func (page *Page) BuildStyle(option *BuildOption) (string, error) {
|
||||||
if page.Codes.CSS.Code == "" {
|
if page.Codes.CSS.Code == "" {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
code := strings.ReplaceAll(page.Codes.CSS.Code, "@assets", assetRoot)
|
code := page.Codes.CSS.Code
|
||||||
|
if !option.IgnoreAssetRoot {
|
||||||
|
code = strings.ReplaceAll(page.Codes.CSS.Code, "@assets", option.AssetRoot)
|
||||||
|
}
|
||||||
|
|
||||||
res, err := page.CompileCSS([]byte(code), false)
|
res, err := page.CompileCSS([]byte(code), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
@ -71,7 +78,7 @@ func (page *Page) BuildStyle(assetRoot string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildScript build the script
|
// BuildScript build the script
|
||||||
func (page *Page) BuildScript(assetRoot string) (string, error) {
|
func (page *Page) BuildScript(option *BuildOption) (string, error) {
|
||||||
|
|
||||||
if page.Codes.JS.Code == "" && page.Codes.TS.Code == "" {
|
if page.Codes.JS.Code == "" && page.Codes.TS.Code == "" {
|
||||||
return "", nil
|
return "", nil
|
||||||
|
|
@ -86,7 +93,11 @@ func (page *Page) BuildScript(assetRoot string) (string, error) {
|
||||||
return fmt.Sprintf("<script>\n%s\n</script>\n", res), nil
|
return fmt.Sprintf("<script>\n%s\n</script>\n", res), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
code := strings.ReplaceAll(page.Codes.JS.Code, "@assets", assetRoot)
|
code := page.Codes.JS.Code
|
||||||
|
if !option.IgnoreAssetRoot {
|
||||||
|
code = strings.ReplaceAll(page.Codes.JS.Code, "@assets", option.AssetRoot)
|
||||||
|
}
|
||||||
|
|
||||||
res, err := page.CompileJS([]byte(code), false)
|
res, err := page.CompileJS([]byte(code), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ func (page *Page) Compile(option *BuildOption) (string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Page Data
|
||||||
if page.Codes.DATA.Code != "" {
|
if page.Codes.DATA.Code != "" {
|
||||||
doc.Find("body").AppendHtml("\n\n" + `<script name="data" type="json">` + "\n" +
|
doc.Find("body").AppendHtml("\n\n" + `<script name="data" type="json">` + "\n" +
|
||||||
page.Codes.DATA.Code +
|
page.Codes.DATA.Code +
|
||||||
|
|
@ -29,13 +30,17 @@ func (page *Page) Compile(option *BuildOption) (string, error) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// // add the route data
|
// Page Global Data
|
||||||
// doc.Find("body").AppendHtml(`<script name="route" type="json">` + "\n" +
|
if page.GlobalData != nil && len(page.GlobalData) > 0 {
|
||||||
// fmt.Sprintf(
|
doc.Find("body").AppendHtml("\n\n" + `<script name="global" type="json">` + "\n" +
|
||||||
// `{"sui": "%s", "template": "%s", "route": "%s"}`,
|
string(page.GlobalData) +
|
||||||
// page.SuiID, page.TemplateID, page.Route,
|
"\n</script>\n\n",
|
||||||
// ) +
|
)
|
||||||
// "\n</script>\n")
|
}
|
||||||
|
|
||||||
|
// Replace the document
|
||||||
|
page.Config = page.GetConfig()
|
||||||
|
page.ReplaceDocument(doc)
|
||||||
|
|
||||||
html, err := doc.Html()
|
html, err := doc.Html()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/antonmedv/expr/ast"
|
"github.com/antonmedv/expr/ast"
|
||||||
"github.com/antonmedv/expr/vm"
|
"github.com/antonmedv/expr/vm"
|
||||||
"github.com/yaoapp/gou/process"
|
"github.com/yaoapp/gou/process"
|
||||||
|
"github.com/yaoapp/kun/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var stmtRe = regexp.MustCompile(`\{\{([^}]+)\}\}`)
|
var stmtRe = regexp.MustCompile(`\{\{([^}]+)\}\}`)
|
||||||
|
|
@ -56,6 +57,20 @@ func (data Data) ExecString(stmt string) (string, error) {
|
||||||
return fmt.Sprintf("%v", res), nil
|
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) {
|
func _process(args ...any) (interface{}, error) {
|
||||||
|
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// EditorRender render HTML for the editor
|
// EditorRender render HTML for the editor
|
||||||
func (page *Page) EditorRender(request *Request) (*ResponseEditorRender, error) {
|
func (page *Page) EditorRender() (*ResponseEditorRender, error) {
|
||||||
|
|
||||||
res := &ResponseEditorRender{
|
res := &ResponseEditorRender{
|
||||||
HTML: "",
|
HTML: "",
|
||||||
|
|
@ -32,19 +32,8 @@ func (page *Page) EditorRender(request *Request) (*ResponseEditorRender, error)
|
||||||
}
|
}
|
||||||
res.Styles = append(res.Styles, styles...)
|
res.Styles = append(res.Styles, styles...)
|
||||||
|
|
||||||
// // Page Styles
|
// Render the page
|
||||||
// if page.Codes.CSS.Code != "" {
|
request := NewRequestMock(page.Config.Mock)
|
||||||
// res.Styles = append(res.Styles, filepath.Join("@pages", page.Route, page.Name+".css"))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Render the HTML with the data
|
|
||||||
// // Page Scripts
|
|
||||||
// if page.Codes.JS.Code != "" {
|
|
||||||
// res.Scripts = append(res.Scripts, filepath.Join("@pages", page.Route, page.Name+".js"))
|
|
||||||
// }
|
|
||||||
// if page.Codes.TS.Code != "" {
|
|
||||||
// res.Scripts = append(res.Scripts, filepath.Join("@pages", page.Route, page.Name+".ts"))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Render tools
|
// Render tools
|
||||||
// res.Scripts = append(res.Scripts, filepath.Join("@assets", "__render.js"))
|
// res.Scripts = append(res.Scripts, filepath.Join("@assets", "__render.js"))
|
||||||
|
|
@ -52,7 +41,7 @@ func (page *Page) EditorRender(request *Request) (*ResponseEditorRender, error)
|
||||||
|
|
||||||
doc, warnings, err := page.Build(&BuildOption{
|
doc, warnings, err := page.Build(&BuildOption{
|
||||||
SSR: true,
|
SSR: true,
|
||||||
AssetRoot: request.AssetRoot,
|
IgnoreAssetRoot: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -77,6 +66,13 @@ func (page *Page) EditorRender(request *Request) (*ResponseEditorRender, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
res.Render(data)
|
res.Render(data)
|
||||||
|
|
||||||
|
// Set the title
|
||||||
|
res.Config.Rendered = &PageConfigRendered{
|
||||||
|
Title: page.RenderTitle(data),
|
||||||
|
Link: page.Link(request),
|
||||||
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,7 +87,8 @@ func (res *ResponseEditorRender) Render(data map[string]interface{}) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
parser := NewTemplateParser(data, nil)
|
parser := NewTemplateParser(data, &ParserOption{Editor: true})
|
||||||
|
|
||||||
res.HTML, err = parser.Render(res.HTML)
|
res.HTML, err = parser.Render(res.HTML)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -102,6 +99,7 @@ func (res *ResponseEditorRender) Render(data map[string]interface{}) error {
|
||||||
res.Warnings = append(res.Warnings, err.Error())
|
res.Warnings = append(res.Warnings, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ type SUI interface {
|
||||||
WithSid(sid string)
|
WithSid(sid string)
|
||||||
PublicRootMatcher() *Matcher
|
PublicRootMatcher() *Matcher
|
||||||
GetPublic() *Public
|
GetPublic() *Public
|
||||||
|
PublicRootWithSid(sid string) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ITemplate is the interface for the ITemplate
|
// ITemplate is the interface for the ITemplate
|
||||||
|
|
@ -70,7 +71,7 @@ type IPage interface {
|
||||||
SaveTemp(request *RequestSource) error
|
SaveTemp(request *RequestSource) error
|
||||||
Remove() error
|
Remove() error
|
||||||
|
|
||||||
EditorRender(request *Request) (*ResponseEditorRender, error)
|
EditorRender() (*ResponseEditorRender, error)
|
||||||
EditorPageSource() SourceData
|
EditorPageSource() SourceData
|
||||||
EditorScriptSource() SourceData
|
EditorScriptSource() SourceData
|
||||||
EditorStyleSource() SourceData
|
EditorStyleSource() SourceData
|
||||||
|
|
|
||||||
154
sui/core/page.go
154
sui/core/page.go
|
|
@ -1,9 +1,12 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/PuerkitoBio/goquery"
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
"github.com/yaoapp/kun/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Get get the base info
|
// Get get the base info
|
||||||
|
|
@ -13,17 +16,29 @@ func (page *Page) Get() *Page {
|
||||||
|
|
||||||
// GetConfig get the config
|
// GetConfig get the config
|
||||||
func (page *Page) GetConfig() *PageConfig {
|
func (page *Page) GetConfig() *PageConfig {
|
||||||
if page.Config == nil && page.Codes.CONF.Code != "" {
|
|
||||||
|
if page.Codes.CONF.Code != "" {
|
||||||
var config PageConfig
|
var config PageConfig
|
||||||
err := jsoniter.Unmarshal([]byte(page.Codes.CONF.Code), &config)
|
err := jsoniter.Unmarshal([]byte(page.Codes.CONF.Code), &config)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
page.Config = &config
|
page.Config = &config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if page.Config == nil {
|
||||||
|
page.Config = &PageConfig{
|
||||||
|
Mock: &PageMock{Method: "GET"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if page.Config.Mock == nil {
|
||||||
|
page.Config.Mock = &PageMock{Method: "GET"}
|
||||||
|
}
|
||||||
|
|
||||||
return page.Config
|
return page.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// Data get the data
|
// Data get the data (deprecated)
|
||||||
func (page *Page) Data(request *Request) (Data, map[string]interface{}, error) {
|
func (page *Page) Data(request *Request) (Data, map[string]interface{}, error) {
|
||||||
|
|
||||||
setting := map[string]interface{}{
|
setting := map[string]interface{}{
|
||||||
|
|
@ -51,5 +66,140 @@ func (page *Page) Exec(request *Request) (Data, error) {
|
||||||
return nil, err
|
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
|
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 {
|
||||||
|
paths = append(paths, name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
paths = append(paths, value)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
paths = append(paths, part)
|
||||||
|
}
|
||||||
|
|
||||||
|
url := filepath.Join(paths...)
|
||||||
|
if r.Query != nil {
|
||||||
|
query := r.Query.Encode()
|
||||||
|
if query != "" {
|
||||||
|
url = url + "?" + query
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ type TemplateParser struct {
|
||||||
sequence int // sequence for the rendering
|
sequence int // sequence for the rendering
|
||||||
errors []error // errors
|
errors []error // errors
|
||||||
replace map[*goquery.Selection][]*html.Node // replace nodes
|
replace map[*goquery.Selection][]*html.Node // replace nodes
|
||||||
|
option *ParserOption // parser option
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mapping mapping for the template
|
// Mapping mapping for the template
|
||||||
|
|
@ -26,16 +27,24 @@ type Mapping struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParserOption parser option
|
// ParserOption parser option
|
||||||
type ParserOption struct{}
|
type ParserOption struct {
|
||||||
|
Editor bool `json:"editor,omitempty"`
|
||||||
|
Preview bool `json:"preview,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// NewTemplateParser create a new template parser
|
// NewTemplateParser create a new template parser
|
||||||
func NewTemplateParser(data Data, option *ParserOption) *TemplateParser {
|
func NewTemplateParser(data Data, option *ParserOption) *TemplateParser {
|
||||||
|
if option == nil {
|
||||||
|
option = &ParserOption{}
|
||||||
|
}
|
||||||
|
|
||||||
return &TemplateParser{
|
return &TemplateParser{
|
||||||
data: data,
|
data: data,
|
||||||
mapping: map[string]Mapping{},
|
mapping: map[string]Mapping{},
|
||||||
sequence: 0,
|
sequence: 0,
|
||||||
errors: []error{},
|
errors: []error{},
|
||||||
replace: map[*goquery.Selection][]*html.Node{},
|
replace: map[*goquery.Selection][]*html.Node{},
|
||||||
|
option: option,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,17 +112,7 @@ func (parser *TemplateParser) parseElementNode(sel *goquery.Selection) {
|
||||||
|
|
||||||
func (parser *TemplateParser) parseTextNode(node *html.Node) {
|
func (parser *TemplateParser) parseTextNode(node *html.Node) {
|
||||||
parser.sequence = parser.sequence + 1
|
parser.sequence = parser.sequence + 1
|
||||||
hasStmt := false
|
res, hasStmt := parser.data.Replace(node.Data)
|
||||||
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)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Bind the variable to the parent node
|
// Bind the variable to the parent node
|
||||||
if node.Parent != nil && hasStmt {
|
if node.Parent != nil && hasStmt {
|
||||||
bindings := strings.TrimSpace(node.Data)
|
bindings := strings.TrimSpace(node.Data)
|
||||||
|
|
@ -125,8 +124,7 @@ func (parser *TemplateParser) parseTextNode(node *html.Node) {
|
||||||
}...)
|
}...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
node.Data = res
|
||||||
node.Data = string(res)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (parser *TemplateParser) forStatementNode(sel *goquery.Selection) {
|
func (parser *TemplateParser) forStatementNode(sel *goquery.Selection) {
|
||||||
|
|
@ -153,6 +151,12 @@ func (parser *TemplateParser) forStatementNode(sel *goquery.Selection) {
|
||||||
indexVarName := sel.AttrOr("s:for-index", "index")
|
indexVarName := sel.AttrOr("s:for-index", "index")
|
||||||
itemNodes := []*html.Node{}
|
itemNodes := []*html.Node{}
|
||||||
|
|
||||||
|
// Keep the node if the editor is enabled
|
||||||
|
if parser.option.Editor {
|
||||||
|
clone := sel.Clone()
|
||||||
|
itemNodes = append(itemNodes, clone.Nodes...)
|
||||||
|
}
|
||||||
|
|
||||||
for idx, item := range items {
|
for idx, item := range items {
|
||||||
|
|
||||||
// Create a new node
|
// Create a new node
|
||||||
|
|
@ -168,6 +172,10 @@ func (parser *TemplateParser) forStatementNode(sel *goquery.Selection) {
|
||||||
parser.data[itemVarName] = item
|
parser.data[itemVarName] = item
|
||||||
parser.data[indexVarName] = idx
|
parser.data[indexVarName] = idx
|
||||||
|
|
||||||
|
if parser.option.Editor {
|
||||||
|
parser.setSuiAttr(new, "generate", "true")
|
||||||
|
}
|
||||||
|
|
||||||
// Process the new node
|
// Process the new node
|
||||||
for i := range new.Nodes {
|
for i := range new.Nodes {
|
||||||
parser.parseNode(new.Nodes[i])
|
parser.parseNode(new.Nodes[i])
|
||||||
|
|
@ -259,7 +267,23 @@ func (parser *TemplateParser) elseStatementNode(sel *goquery.Selection) ([]*goqu
|
||||||
return elifNodes, elseNode
|
return elifNodes, elseNode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (parser *TemplateParser) setSuiAttr(sel *goquery.Selection, key, value string) *goquery.Selection {
|
||||||
|
key = fmt.Sprintf("data-sui-%s", key)
|
||||||
|
return sel.SetAttr(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (parser *TemplateParser) removeSuiAttr(sel *goquery.Selection, key string) *goquery.Selection {
|
||||||
|
key = fmt.Sprintf("data-sui-%s", key)
|
||||||
|
return sel.RemoveAttr(key)
|
||||||
|
}
|
||||||
|
|
||||||
func (parser *TemplateParser) hide(sel *goquery.Selection) {
|
func (parser *TemplateParser) hide(sel *goquery.Selection) {
|
||||||
|
|
||||||
|
if parser.option.Editor {
|
||||||
|
parser.setSuiAttr(sel, "hide", "true")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
style := sel.AttrOr("style", "")
|
style := sel.AttrOr("style", "")
|
||||||
if strings.Contains(style, "display: none") {
|
if strings.Contains(style, "display: none") {
|
||||||
return
|
return
|
||||||
|
|
@ -274,6 +298,12 @@ func (parser *TemplateParser) hide(sel *goquery.Selection) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (parser *TemplateParser) show(sel *goquery.Selection) {
|
func (parser *TemplateParser) show(sel *goquery.Selection) {
|
||||||
|
|
||||||
|
if parser.option.Editor {
|
||||||
|
parser.removeSuiAttr(sel, "hide")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
style := sel.AttrOr("style", "")
|
style := sel.AttrOr("style", "")
|
||||||
if !strings.Contains(style, "display: none") {
|
if !strings.Contains(style, "display: none") {
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,29 @@ import (
|
||||||
// Cache the cache
|
// Cache the cache
|
||||||
type Cache struct {
|
type Cache struct {
|
||||||
Data string
|
Data string
|
||||||
|
Global string
|
||||||
HTML string
|
HTML string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Caches the caches
|
// Caches the caches
|
||||||
var Caches = map[string]*Cache{}
|
var Caches = map[string]*Cache{}
|
||||||
|
|
||||||
|
// NewRequestMock is the constructor for Request.
|
||||||
|
func NewRequestMock(mock *PageMock) *Request {
|
||||||
|
if mock == nil {
|
||||||
|
mock = &PageMock{Method: "GET"}
|
||||||
|
}
|
||||||
|
return &Request{
|
||||||
|
Method: mock.Method,
|
||||||
|
Query: mock.Query,
|
||||||
|
Body: mock.Body,
|
||||||
|
Payload: mock.Payload,
|
||||||
|
Referer: mock.Referer,
|
||||||
|
Headers: mock.Headers,
|
||||||
|
Params: mock.Params,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ExecString get the data
|
// ExecString get the data
|
||||||
func (r *Request) ExecString(data string) (Data, error) {
|
func (r *Request) ExecString(data string) (Data, error) {
|
||||||
var res Data
|
var res Data
|
||||||
|
|
@ -231,10 +248,11 @@ func (r *Request) parseArgs(args []interface{}) ([]interface{}, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCache set the cache
|
// 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{
|
Caches[file] = &Cache{
|
||||||
Data: data,
|
Data: data,
|
||||||
HTML: html,
|
HTML: html,
|
||||||
|
Global: global,
|
||||||
}
|
}
|
||||||
return Caches[file]
|
return Caches[file]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,28 @@ func (sui *DSL) PublicRootMatcher() *Matcher {
|
||||||
return &Matcher{Exact: pub.Root}
|
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
|
// PublicRoot returns the public root path
|
||||||
func (sui *DSL) PublicRoot() (string, error) {
|
func (sui *DSL) PublicRoot() (string, error) {
|
||||||
// Cache the public root
|
// Cache the public root
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ type Page struct {
|
||||||
Path string `json:"-"`
|
Path string `json:"-"`
|
||||||
Codes SourceCodes `json:"-"`
|
Codes SourceCodes `json:"-"`
|
||||||
Document []byte `json:"-"`
|
Document []byte `json:"-"`
|
||||||
|
GlobalData []byte `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PageTreeNode is the struct for the page tree node
|
// PageTreeNode is the struct for the page tree node
|
||||||
|
|
@ -86,6 +87,7 @@ type Template struct {
|
||||||
Screenshots []string `json:"screenshots"`
|
Screenshots []string `json:"screenshots"`
|
||||||
Themes []SelectOption `json:"themes"`
|
Themes []SelectOption `json:"themes"`
|
||||||
Document []byte `json:"-"`
|
Document []byte `json:"-"`
|
||||||
|
GlobalData []byte `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Theme is the struct for the theme
|
// Theme is the struct for the theme
|
||||||
|
|
@ -137,6 +139,7 @@ type BuildOption struct {
|
||||||
CDN bool `json:"cdn"`
|
CDN bool `json:"cdn"`
|
||||||
UpdateAll bool `json:"update_all"`
|
UpdateAll bool `json:"update_all"`
|
||||||
AssetRoot string `json:"asset_root,omitempty"`
|
AssetRoot string `json:"asset_root,omitempty"`
|
||||||
|
IgnoreAssetRoot bool `json:"ignore_asset_root,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request is the struct for the request
|
// Request is the struct for the request
|
||||||
|
|
@ -203,16 +206,20 @@ type BoardSourceData struct {
|
||||||
// PageMock is the struct for the request
|
// PageMock is the struct for the request
|
||||||
type PageMock struct {
|
type PageMock struct {
|
||||||
Method string `json:"method,omitempty"`
|
Method string `json:"method,omitempty"`
|
||||||
|
Referer string `json:"referer,omitempty"`
|
||||||
|
Payload map[string]interface{} `json:"payload,omitempty"`
|
||||||
|
Query url.Values `json:"query,omitempty"`
|
||||||
Params map[string]string `json:"params,omitempty"`
|
Params map[string]string `json:"params,omitempty"`
|
||||||
Query map[string][]string `json:"query,omitempty"`
|
Headers url.Values `json:"headers,omitempty"`
|
||||||
Headers map[string][]string `json:"headers,omitempty"`
|
|
||||||
Body interface{} `json:"body,omitempty"`
|
Body interface{} `json:"body,omitempty"`
|
||||||
|
Sid string `json:"sid,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PageConfig is the struct for the page config
|
// PageConfig is the struct for the page config
|
||||||
type PageConfig struct {
|
type PageConfig struct {
|
||||||
PageSetting `json:",omitempty"`
|
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
|
// PageSetting is the struct for the page setting
|
||||||
|
|
@ -222,6 +229,12 @@ type PageSetting struct {
|
||||||
SEO *PageSEO `json:"seo,omitempty"`
|
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
|
// PageSEO is the struct for the page seo
|
||||||
type PageSEO struct {
|
type PageSEO struct {
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,16 @@ func (local *Local) getTemplate(id string, path string) (*Template, error) {
|
||||||
tmpl.Document = documentBytes
|
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
|
return &tmpl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -374,6 +374,9 @@ func (page *Page) Load() error {
|
||||||
|
|
||||||
// Set the page document
|
// Set the page document
|
||||||
page.Document = page.tmpl.Document
|
page.Document = page.tmpl.Document
|
||||||
|
|
||||||
|
// Set the page global data
|
||||||
|
page.GlobalData = page.tmpl.GlobalData
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,7 @@ func TestPageEditorRender(t *testing.T) {
|
||||||
t.Fatalf("Page error: %v", err)
|
t.Fatalf("Page error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
r := &core.Request{Method: "GET"}
|
res, err := page.EditorRender()
|
||||||
res, err := page.EditorRender(r)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("EditorRender error: %v", err)
|
t.Fatalf("EditorRender error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue