diff --git a/go.mod b/go.mod index eca5c099..09732e3c 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,8 @@ require ( ) require ( + github.com/PuerkitoBio/goquery v1.8.1 // indirect + github.com/andybalholm/cascadia v1.3.1 // indirect github.com/bytedance/sonic v1.10.1 // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/iasm v0.9.0 // indirect diff --git a/go.sum b/go.sum index 8e1f2a17..d964448e 100644 --- a/go.sum +++ b/go.sum @@ -39,8 +39,12 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= +github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 h1:ZBbLwSJqkHBuFDA6DUhhse0IGJ7T5bemHyNILUjvOq4= github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2/go.mod h1:VSw57q4QFiWDbRnjdX8Cb3Ow0SFncRw+bA/ofY6Q83w= +github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= +github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -653,10 +657,12 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= diff --git a/sui/core/compile.go b/sui/core/compile.go new file mode 100644 index 00000000..0867402d --- /dev/null +++ b/sui/core/compile.go @@ -0,0 +1,4 @@ +package core + +// Compile the page +func (page *Page) Compile() {} diff --git a/sui/core/data.go b/sui/core/data.go new file mode 100644 index 00000000..0fb82b0b --- /dev/null +++ b/sui/core/data.go @@ -0,0 +1,11 @@ +package core + +// Data get the data +func (page *Page) Data(request *Request) (map[string]interface{}, error) { + return nil, nil +} + +// RenderHTML render for the html +func (page *Page) renderData(html string, data map[string]interface{}, warnings []string) (string, error) { + return html, nil +} diff --git a/sui/core/interfaces.go b/sui/core/interfaces.go index 7c0561c7..6697f45d 100644 --- a/sui/core/interfaces.go +++ b/sui/core/interfaces.go @@ -19,6 +19,7 @@ type ITemplate interface { // IPage is the interface for the page type IPage interface { Load() error + RenderEditor(request *Request) (*ResponseEditor, error) // Render() diff --git a/sui/core/page.go b/sui/core/page.go new file mode 100644 index 00000000..68138f2c --- /dev/null +++ b/sui/core/page.go @@ -0,0 +1,19 @@ +package core + +// GetHTML get the html +func (page *Page) GetHTML() {} + +// GetScript get the script +func (page *Page) GetScript() {} + +// GetStyle get the style +func (page *Page) GetStyle() {} + +// GetData get the data +func (page *Page) GetData() {} + +// SaveTemp save the temp +func (page *Page) SaveTemp() {} + +// Save the page +func (page *Page) Save() {} diff --git a/sui/core/render.go b/sui/core/render.go new file mode 100644 index 00000000..0ac4f857 --- /dev/null +++ b/sui/core/render.go @@ -0,0 +1,129 @@ +package core + +import ( + "path/filepath" + + "github.com/PuerkitoBio/goquery" +) + +// Render render the page +func (page *Page) Render() {} + +// RenderEditor render for the editor +func (page *Page) RenderEditor(request *Request) (*ResponseEditor, error) { + + html := page.Codes.HTML.Code + res := &ResponseEditor{ + HTML: "", + CSS: page.Codes.CSS.Code, + Scripts: []string{}, + Styles: []string{}, + Warnings: []string{}, + } + + // Get The scripts and styles + // Global scripts + scripts, err := page.GlobalScripts() + if err != nil { + res.Warnings = append(res.Warnings, err.Error()) + } + res.Scripts = append(res.Scripts, scripts...) + + // Global styles + styles, err := page.GlobalStyles() + if err != nil { + res.Warnings = append(res.Warnings, err.Error()) + } + res.Styles = append(res.Styles, styles...) + + // Page Styles + if page.Codes.CSS.Code != "" { + 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")) + } + + data, err := page.Data(request) + if err != nil { + res.Warnings = append(res.Warnings, err.Error()) + } + + if data == nil { + res.HTML = html + return res, nil + } + + if html != "" { + html, err := page.renderData(html, data, res.Warnings) + if err != nil { + res.Warnings = append(res.Warnings, err.Error()) + } + res.HTML = html + } + + return res, nil +} + +// RenderPreview render for the preview +func (page *Page) RenderPreview() {} + +// GlobalScripts get the global scripts +func (page *Page) GlobalScripts() ([]string, error) { + if page.Document == nil { + return []string{}, nil + } + + doc, err := NewDocument(page.Document) + if err != nil { + return []string{}, err + } + + // Global scripts + scripts := []string{} + doc.Find("script").Each(func(i int, s *goquery.Selection) { + src, _ := s.Attr("src") + if src != "" { + scripts = append(scripts, src) + } + }) + + return scripts, nil +} + +// GlobalStyles get the global styles +func (page *Page) GlobalStyles() ([]string, error) { + + if page.Document == nil { + return []string{}, nil + } + + doc, err := NewDocument(page.Document) + if err != nil { + return []string{}, err + } + + // Global styles + styles := []string{} + doc.Find("link[rel=stylesheet]").Each(func(i int, s *goquery.Selection) { + href, _ := s.Attr("href") + if href != "" { + styles = append(styles, href) + } + }) + + return styles, nil +} + +func (page *Page) document() []byte { + if page.Document != nil { + return page.Document + } + return DocumentDefault +} diff --git a/sui/core/types.go b/sui/core/types.go index 8f01cce1..7a8c984d 100644 --- a/sui/core/types.go +++ b/sui/core/types.go @@ -17,10 +17,11 @@ type DSL struct { // Page is the struct for the page type Page struct { - Route string `json:"route"` - Name string `json:"name,omitempty"` - Path string `json:"-"` - Codes SourceCodes `json:"-"` + Route string `json:"route"` + Name string `json:"name,omitempty"` + Path string `json:"-"` + Codes SourceCodes `json:"-"` + Document []byte `json:"-"` } // Component is the struct for the component @@ -47,6 +48,7 @@ type Template struct { Descrption string `json:"description"` Screenshots []string `json:"screenshots"` Themes []SelectOption `json:"themes"` + Document []byte `json:"-"` } // Theme is the struct for the theme @@ -61,6 +63,27 @@ type SelectOption struct { Value string `json:"value"` } +// Request is the struct for the request +type Request struct { + Method string `json:"method"` + Payload map[string]interface{} `json:"payload,omitempty"` + Query map[string][]string `json:"query,omitempty"` + Params map[string]string `json:"params,omitempty"` + Headers []string `json:"headers,omitempty"` + Body []byte `json:"body,omitempty"` + Theme string `json:"theme,omitempty"` + Locale string `json:"locale,omitempty"` +} + +// ResponseEditor is the struct for the response +type ResponseEditor struct { + HTML string `json:"html,omitempty"` + CSS string `json:"css,omitempty"` + Scripts []string `json:"scripts,omitempty"` + Styles []string `json:"styles,omitempty"` + Warnings []string `json:"warnings,omitempty"` +} + // SourceCodes is the struct for the page codes type SourceCodes struct { HTML Source `json:"-"` @@ -89,3 +112,34 @@ type Storage struct { Driver string `json:"driver"` Option map[string]interface{} `json:"option,omitempty"` } + +// DocumentDefault is the default document +var DocumentDefault = []byte(` + + + + + {{ $DATA.head.title || '' }} + + + + + + + + + + + {{ __page }} + + +`) diff --git a/sui/core/utils.go b/sui/core/utils.go new file mode 100644 index 00000000..67dc28de --- /dev/null +++ b/sui/core/utils.go @@ -0,0 +1,18 @@ +package core + +import ( + "bytes" + "strings" + + "github.com/PuerkitoBio/goquery" +) + +// NewDocument create a new document +func NewDocument(html []byte) (*goquery.Document, error) { + return goquery.NewDocumentFromReader(bytes.NewReader(html)) +} + +// NewDocumentString create a new document +func NewDocumentString(html string) (*goquery.Document, error) { + return goquery.NewDocumentFromReader(strings.NewReader(html)) +} diff --git a/sui/storages/local/local.go b/sui/storages/local/local.go index e593d6d4..033a4bf4 100644 --- a/sui/storages/local/local.go +++ b/sui/storages/local/local.go @@ -130,6 +130,16 @@ func (local *Local) getTemplate(id string, path string) (*Template, error) { } } + // load the __document.html + documentFile := filepath.Join(path, "__document.html") + if local.fs.IsFile(documentFile) { + documentBytes, err := local.fs.ReadFile(documentFile) + if err != nil { + return nil, err + } + tmpl.Document = documentBytes + } + return &tmpl, nil } diff --git a/sui/storages/local/local_test.go b/sui/storages/local/local_test.go index fd166db5..0d4adc72 100644 --- a/sui/storages/local/local_test.go +++ b/sui/storages/local/local_test.go @@ -19,7 +19,7 @@ func TestGetTemplates(t *testing.T) { } if len(dempTmpls) < 3 { - t.Fatalf("The demo templates less than %v", len(dempTmpls)) + t.Fatalf("The demo templates less than 3 (%v<3)", len(dempTmpls)) } assert.Equal(t, "tech-blue", dempTmpls[0].(*Template).ID) diff --git a/sui/storages/local/page.go b/sui/storages/local/page.go index b4817840..0e66ac81 100644 --- a/sui/storages/local/page.go +++ b/sui/storages/local/page.go @@ -77,6 +77,7 @@ func (tmpl *Template) getPage(route, file string) (core.IPage, error) { Page: &core.Page{ Route: route, Path: path, + Name: name, Codes: core.SourceCodes{ HTML: core.Source{File: fmt.Sprintf("%s%s", name, filepath.Ext(file))}, CSS: core.Source{File: fmt.Sprintf("%s.css", name)}, @@ -117,11 +118,13 @@ func (page *Page) Load() error { } else { jsFile := filepath.Join(page.Path, page.Codes.JS.File) - jsCode, err := page.tmpl.local.fs.ReadFile(jsFile) - if err != nil { - return err + if exist, _ := page.tmpl.local.fs.Exists(jsFile); exist { + jsCode, err := page.tmpl.local.fs.ReadFile(jsFile) + if err != nil { + return err + } + page.Codes.JS.Code = string(jsCode) } - page.Codes.JS.Code = string(jsCode) } // Read the HTML code @@ -155,5 +158,7 @@ func (page *Page) Load() error { page.Codes.DATA.Code = string(dataCode) } + // Set the page document + page.Document = page.tmpl.Document return nil } diff --git a/sui/storages/local/page_test.go b/sui/storages/local/page_test.go index 46ce2604..2f5e3c70 100644 --- a/sui/storages/local/page_test.go +++ b/sui/storages/local/page_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/yaoapp/yao/sui/core" ) func TestTemplatePages(t *testing.T) { @@ -113,3 +114,43 @@ func TestTemplatePageJS(t *testing.T) { _, err = tmpl.Page("/the/page/could/not/be/found") assert.Contains(t, err.Error(), "Page /the/page/could/not/be/found not found") } + +func TestPageRenderEditor(t *testing.T) { + + tests := prepare(t) + defer clean() + + tmpl, err := tests.Demo.GetTemplate("tech-blue") + if err != nil { + t.Fatalf("GetTemplate error: %v", err) + } + + page, err := tmpl.Page("/index") + if err != nil { + t.Fatalf("Page error: %v", err) + } + + r := &core.Request{Method: "GET"} + res, err := page.RenderEditor(r) + if err != nil { + t.Fatalf("RenderEditor error: %v", err) + } + + assert.NotEmpty(t, res.HTML) + assert.NotEmpty(t, res.CSS) + assert.NotEmpty(t, res.Scripts) + assert.NotEmpty(t, res.Styles) + assert.Equal(t, 4, len(res.Scripts)) + assert.Equal(t, 5, len(res.Styles)) + + assert.Equal(t, "@assets/libs/tiny-slider/min/tiny-slider.js", res.Scripts[0]) + assert.Equal(t, "@assets/libs/feather-icons/feather.min.js", res.Scripts[1]) + assert.Equal(t, "@assets/js/plugins.init.js", res.Scripts[2]) + assert.Equal(t, "@pages/index/index.js", res.Scripts[3]) + + assert.Equal(t, "@assets/libs/tiny-slider/tiny-slider.css", res.Styles[0]) + assert.Equal(t, "@assets/libs/@iconscout/unicons/css/line.css", res.Styles[1]) + assert.Equal(t, "@assets/libs/@mdi/font/css/materialdesignicons.min.css", res.Styles[2]) + assert.Equal(t, "@assets/css/tailwind.css", res.Styles[3]) + assert.Equal(t, "@pages/index/index.css", res.Styles[4]) +}