[add] page preview api
This commit is contained in:
parent
2e774534d1
commit
7943453c46
13 changed files with 334 additions and 67 deletions
|
|
@ -156,6 +156,14 @@ var dsl = []byte(`
|
|||
"body": "?:content",
|
||||
"headers": { "Content-Type": "?:type"}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"path": "/:id/preview/:template_id/*route",
|
||||
"method": "GET",
|
||||
"process": "sui.Preview.Render",
|
||||
"in": ["$param.id", "$param.template_id", "$param.path", "$header.Referer", "$query.r", "$query.t"],
|
||||
"out": {"status": 200, "type": "text/html; charset=utf-8"}
|
||||
}
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ func init() {
|
|||
"editor.source": EditorSource,
|
||||
"editor.renderaftersavetemp": EditorRenderAfterSaveTemp,
|
||||
"editor.sourceaftersavetemp": EditorSourceAfterSaveTemp,
|
||||
|
||||
"preview.render": PreviewRender,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -531,6 +533,44 @@ func EditorSourceAfterSaveTemp(process *process.Process) interface{} {
|
|||
return EditorSource(process)
|
||||
}
|
||||
|
||||
// PreviewRender handle the render page request
|
||||
func PreviewRender(process *process.Process) interface{} {
|
||||
|
||||
process.ValidateArgNums(3)
|
||||
sui := get(process)
|
||||
id := process.ArgsString(0)
|
||||
templateID := process.ArgsString(1)
|
||||
route := route(process, 2)
|
||||
referer := process.ArgsString(3, "")
|
||||
|
||||
// reqData := process.ArgsString(4)
|
||||
// timestamp := process.Args[4].(*timestamp.Timestamp)
|
||||
|
||||
req := &core.Request{
|
||||
Method: "GET",
|
||||
AssetRoot: fmt.Sprintf("/api/__yao/sui/v1/%s/asset/%s/@assets", id, templateID),
|
||||
Referer: referer,
|
||||
}
|
||||
|
||||
tmpl, err := sui.GetTemplate(templateID)
|
||||
if err != nil {
|
||||
exception.New(err.Error(), 500).Throw()
|
||||
}
|
||||
|
||||
page, err := tmpl.Page(route)
|
||||
if err != nil {
|
||||
exception.New(err.Error(), 500).Throw()
|
||||
}
|
||||
|
||||
// Request data
|
||||
html, err := page.PreviewRender(req)
|
||||
if err != nil {
|
||||
exception.New(err.Error(), 500).Throw()
|
||||
}
|
||||
|
||||
return html
|
||||
}
|
||||
|
||||
// get the sui
|
||||
func get(process *process.Process) core.SUI {
|
||||
sui, has := core.SUIs[process.ArgsString(0)]
|
||||
|
|
|
|||
|
|
@ -568,6 +568,25 @@ func TestEditorRenderWithQuery(t *testing.T) {
|
|||
assert.NotEmpty(t, res.(*core.ResponseEditorRender).HTML)
|
||||
}
|
||||
|
||||
func TestPreviewRender(t *testing.T) {
|
||||
load(t)
|
||||
defer clean()
|
||||
|
||||
// test demo
|
||||
p, err := process.Of("sui.preview.render", "demo", "tech-blue", "/index")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := p.Exec()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.IsType(t, "", res)
|
||||
assert.NotEmpty(t, res)
|
||||
}
|
||||
|
||||
func TestEditorPageSource(t *testing.T) {
|
||||
load(t)
|
||||
defer clean()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/evanw/esbuild/pkg/api"
|
||||
"github.com/yaoapp/gou/runtime/transform"
|
||||
|
|
@ -46,3 +48,65 @@ func (page *Page) CompileCSS(source []byte, minify bool) ([]byte, error) {
|
|||
}
|
||||
return source, nil
|
||||
}
|
||||
|
||||
// CompileHTML compile the html
|
||||
func (page *Page) CompileHTML(source []byte, minify bool) ([]byte, error) {
|
||||
return source, nil
|
||||
}
|
||||
|
||||
// BuildHTML build the html
|
||||
func (page *Page) BuildHTML(assetRoot string) (string, error) {
|
||||
|
||||
html := string(page.Document)
|
||||
if page.Codes.HTML.Code != "" {
|
||||
html = strings.Replace(html, "{{ __page }}", page.Codes.HTML.Code, 1)
|
||||
}
|
||||
|
||||
code := strings.ReplaceAll(html, "@assets", assetRoot)
|
||||
res, err := page.CompileHTML([]byte(code), false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(res), nil
|
||||
}
|
||||
|
||||
// BuildStyle build the style
|
||||
func (page *Page) BuildStyle(assetRoot string) (string, error) {
|
||||
if page.Codes.CSS.Code == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
code := strings.ReplaceAll(page.Codes.CSS.Code, "@assets", assetRoot)
|
||||
res, err := page.CompileCSS([]byte(code), false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("<style>\n%s\n</style>\n", res), nil
|
||||
}
|
||||
|
||||
// BuildScript build the script
|
||||
func (page *Page) BuildScript(assetRoot string) (string, error) {
|
||||
|
||||
if page.Codes.JS.Code == "" && page.Codes.TS.Code == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if page.Codes.TS.Code != "" {
|
||||
res, err := page.CompileTS([]byte(page.Codes.TS.Code), false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("<script>\n%s\n</script>\n", res), nil
|
||||
}
|
||||
|
||||
code := strings.ReplaceAll(page.Codes.JS.Code, "@assets", assetRoot)
|
||||
res, err := page.CompileCSS([]byte(code), false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("<script>\n%s\n</script>\n", res), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ func Load(file string, id string) (*DSL, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
dsl := DSL{}
|
||||
dsl := DSL{ID: id}
|
||||
err = application.Parse(file, data, &dsl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ func (page *Page) Data(request *Request) (map[string]interface{}, map[string]int
|
|||
return nil, setting, nil
|
||||
}
|
||||
|
||||
// RenderHTML render for the html
|
||||
func (page *Page) renderData(html string, data map[string]interface{}, warnings []string) (string, error) {
|
||||
// Render render for the html
|
||||
func (page *Page) Render(html string, data map[string]interface{}, warnings []string) (string, error) {
|
||||
return html, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ func (page *Page) EditorRender(request *Request) (*ResponseEditorRender, error)
|
|||
}
|
||||
|
||||
if html != "" {
|
||||
html, err := page.renderData(html, data, res.Warnings)
|
||||
html, err := page.Render(html, data, res.Warnings)
|
||||
if err != nil {
|
||||
res.Warnings = append(res.Warnings, err.Error())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ type IPage interface {
|
|||
EditorStyleSource() SourceData
|
||||
EditorDataSource() SourceData
|
||||
|
||||
PreviewRender(request *Request) (string, error)
|
||||
|
||||
AssetScript() (*Asset, error)
|
||||
AssetStyle() (*Asset, error)
|
||||
}
|
||||
|
|
|
|||
84
sui/core/preview.go
Normal file
84
sui/core/preview.go
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// PreviewRender render HTML for the preview
|
||||
func (page *Page) PreviewRender(request *Request) (string, error) {
|
||||
|
||||
warnings := []string{}
|
||||
html, err := page.BuildHTML(request.AssetRoot)
|
||||
if err != nil {
|
||||
warnings = append(warnings, err.Error())
|
||||
}
|
||||
|
||||
data, _, err := page.Data(request)
|
||||
if err != nil {
|
||||
warnings = append(warnings, err.Error())
|
||||
}
|
||||
|
||||
html, err = page.Render(html, data, warnings)
|
||||
if err != nil {
|
||||
warnings = append(warnings, err.Error())
|
||||
}
|
||||
|
||||
// Add Style & Script & Warning
|
||||
doc, err := NewDocument([]byte(html))
|
||||
if err != nil {
|
||||
warnings = append(warnings, err.Error())
|
||||
}
|
||||
|
||||
// Add Style
|
||||
style, err := page.BuildStyle(request.AssetRoot)
|
||||
if err != nil {
|
||||
warnings = append(warnings, err.Error())
|
||||
}
|
||||
doc.Selection.Find("head").AppendHtml(style)
|
||||
|
||||
// Add Script
|
||||
script, err := page.BuildScript(request.AssetRoot)
|
||||
if err != nil {
|
||||
warnings = append(warnings, err.Error())
|
||||
}
|
||||
doc.Selection.Find("body").AppendHtml(script)
|
||||
|
||||
// Add Frame Height
|
||||
if request.Referer != "" {
|
||||
doc.Selection.Find("body").AppendHtml(`
|
||||
<script>
|
||||
function setIframeHeight(height) {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
messageType: "setIframeHeight",
|
||||
iframeHeight: height,
|
||||
},
|
||||
"` + request.Referer + `"
|
||||
);
|
||||
}
|
||||
|
||||
window.onload = function () {
|
||||
const contentHeight = document.documentElement.scrollHeight;
|
||||
console.log("window.onload: setIframeHeight", contentHeight);
|
||||
try {
|
||||
setIframeHeight(contentHeight + "px");
|
||||
} catch (err) {
|
||||
console.log(` + "`" + `setIframeHeight error: ${err}` + "`" + `);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
`)
|
||||
}
|
||||
|
||||
// Add Warning
|
||||
if len(warnings) > 0 {
|
||||
warningHTML := "<div class=\"sui-warning\">"
|
||||
for _, warning := range warnings {
|
||||
warningHTML += fmt.Sprintf("<div>%s</div>", warning)
|
||||
}
|
||||
warningHTML += "</div>"
|
||||
doc.Selection.Find("body").AppendHtml(warningHTML)
|
||||
}
|
||||
|
||||
return doc.Html()
|
||||
}
|
||||
|
|
@ -10,12 +10,14 @@ type DSL struct {
|
|||
|
||||
// Page is the struct for the page
|
||||
type Page struct {
|
||||
Route string `json:"route"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Config *PageConfig `json:"-"`
|
||||
Path string `json:"-"`
|
||||
Codes SourceCodes `json:"-"`
|
||||
Document []byte `json:"-"`
|
||||
Route string `json:"route"`
|
||||
Name string `json:"name,omitempty"`
|
||||
TemplateID string `json:"-"`
|
||||
SuiID string `json:"-"`
|
||||
Config *PageConfig `json:"-"`
|
||||
Path string `json:"-"`
|
||||
Codes SourceCodes `json:"-"`
|
||||
Document []byte `json:"-"`
|
||||
}
|
||||
|
||||
// PageTreeNode is the struct for the page tree node
|
||||
|
|
@ -76,14 +78,16 @@ type Asset struct {
|
|||
|
||||
// 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 map[string][]string `json:"headers,omitempty"`
|
||||
Body interface{} `json:"body,omitempty"`
|
||||
Theme string `json:"theme,omitempty"`
|
||||
Locale string `json:"locale,omitempty"`
|
||||
Method string `json:"method"`
|
||||
AssetRoot string `json:"asset_root,omitempty"`
|
||||
Referer string `json:"referer,omitempty"`
|
||||
Payload map[string]interface{} `json:"payload,omitempty"`
|
||||
Query map[string][]string `json:"query,omitempty"`
|
||||
Params map[string]string `json:"params,omitempty"`
|
||||
Headers map[string][]string `json:"headers,omitempty"`
|
||||
Body interface{} `json:"body,omitempty"`
|
||||
Theme string `json:"theme,omitempty"`
|
||||
Locale string `json:"locale,omitempty"`
|
||||
}
|
||||
|
||||
// RequestSource is the struct for the request
|
||||
|
|
|
|||
|
|
@ -213,9 +213,11 @@ func (tmpl *Template) CreatePage(route string) (core.IPage, error) {
|
|||
return &Page{
|
||||
tmpl: tmpl,
|
||||
Page: &core.Page{
|
||||
Route: route,
|
||||
Path: filepath.Join(tmpl.Root, route),
|
||||
Name: name,
|
||||
Route: route,
|
||||
TemplateID: tmpl.ID,
|
||||
SuiID: tmpl.local.ID,
|
||||
Path: filepath.Join(tmpl.Root, route),
|
||||
Name: name,
|
||||
Codes: core.SourceCodes{
|
||||
HTML: core.Source{File: fmt.Sprintf("%s.html", name)},
|
||||
CSS: core.Source{File: fmt.Sprintf("%s.css", name)},
|
||||
|
|
@ -240,9 +242,11 @@ func (tmpl *Template) GetPageFromAsset(file string) (core.IPage, error) {
|
|||
return &Page{
|
||||
tmpl: tmpl,
|
||||
Page: &core.Page{
|
||||
Route: route,
|
||||
Path: filepath.Join(tmpl.Root, route),
|
||||
Name: name,
|
||||
Route: route,
|
||||
TemplateID: tmpl.ID,
|
||||
SuiID: tmpl.local.ID,
|
||||
Path: filepath.Join(tmpl.Root, route),
|
||||
Name: name,
|
||||
Codes: core.SourceCodes{
|
||||
CSS: core.Source{File: fmt.Sprintf("%s.css", name)},
|
||||
JS: core.Source{File: fmt.Sprintf("%s.js", name)},
|
||||
|
|
@ -264,9 +268,11 @@ func (tmpl *Template) getPage(route, file string) (core.IPage, error) {
|
|||
return &Page{
|
||||
tmpl: tmpl,
|
||||
Page: &core.Page{
|
||||
Route: route,
|
||||
Path: path,
|
||||
Name: name,
|
||||
Route: route,
|
||||
Path: path,
|
||||
Name: name,
|
||||
TemplateID: tmpl.ID,
|
||||
SuiID: tmpl.local.ID,
|
||||
Codes: core.SourceCodes{
|
||||
HTML: core.Source{File: fmt.Sprintf("%s%s", name, filepath.Ext(file))},
|
||||
CSS: core.Source{File: fmt.Sprintf("%s.css", name)},
|
||||
|
|
|
|||
80
sui/storages/local/page_render_test.go
Normal file
80
sui/storages/local/page_render_test.go
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
package local
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/yaoapp/yao/sui/core"
|
||||
)
|
||||
|
||||
func TestPageEditorRender(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.EditorRender(r)
|
||||
if err != nil {
|
||||
t.Fatalf("EditorRender 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])
|
||||
}
|
||||
|
||||
func TestPagePreviewRender(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",
|
||||
AssetRoot: fmt.Sprintf("/api/__yao/sui/v1/%s/asset/%s/@assets", page.Get().SuiID, page.Get().TemplateID),
|
||||
}
|
||||
|
||||
html, err := page.PreviewRender(r)
|
||||
if err != nil {
|
||||
t.Fatalf("PreviewRender error: %v", err)
|
||||
}
|
||||
|
||||
assert.NotEmpty(t, html)
|
||||
assert.Contains(t, html, "function Hello()")
|
||||
assert.Contains(t, html, "color: #2c3e50;")
|
||||
assert.Contains(t, html, "/api/__yao/sui/v1/demo/asset/tech-blue/@assets")
|
||||
}
|
||||
|
|
@ -149,46 +149,6 @@ func TestTemplatePageJS(t *testing.T) {
|
|||
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.EditorRender(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])
|
||||
}
|
||||
|
||||
func TestPageSaveTempBoard(t *testing.T) {
|
||||
|
||||
tests := prepare(t)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue