[add] template & page asset api

This commit is contained in:
Max 2023-10-01 17:20:30 +08:00
parent 3b8f1f3a6a
commit 9af92308bb
10 changed files with 488 additions and 8 deletions

View file

@ -92,6 +92,28 @@ var dsl = []byte(`
"process": "sui.Editor.Source",
"in": ["$param.id", "$param.template_id", "$param.route", "$param.kind"],
"out": { "status": 200, "type": "application/json" }
},
{
"path": "/:id/assets/:template_id/@assets/*path",
"method": "GET",
"process": "sui.Template.Asset",
"in": ["$param.id", "$param.template_id", "$param.path"],
"out": {
"status": 200,
"body": "?:content",
"headers": { "Content-Type": "?:type"}
}
},{
"path": "/:id/assets/:template_id/@pages/*path",
"method": "GET",
"process": "sui.Page.Asset",
"in": ["$param.id", "$param.template_id", "$param.path"],
"out": {
"status": 200,
"body": "?:content",
"headers": { "Content-Type": "?:type"}
}
}
],
}

View file

@ -1,6 +1,7 @@
package api
import (
"path/filepath"
"strings"
"github.com/yaoapp/gou/process"
@ -10,8 +11,9 @@ import (
func init() {
process.RegisterGroup("sui", map[string]process.Handler{
"template.get": TemplateGet,
"template.find": TemplateFind,
"template.get": TemplateGet,
"template.find": TemplateFind,
"template.asset": TemplateAsset,
"locale.get": LocaleGet,
"theme.get": ThemeGet,
@ -22,8 +24,9 @@ func init() {
"component.get": ComponentGet,
"component.find": ComponentFind,
"page.tree": PageTree,
"page.get": PageGet,
"page.tree": PageTree,
"page.get": PageGet,
"page.asset": PageAsset,
"editor.render": EditorRender,
"editor.source": EditorSource,
@ -48,12 +51,32 @@ func TemplateFind(process *process.Process) interface{} {
process.ValidateArgNums(2)
sui := get(process)
template, err := sui.GetTemplate(process.ArgsString(1))
tmpl, err := sui.GetTemplate(process.ArgsString(1))
if err != nil {
exception.New(err.Error(), 500).Throw()
}
return template
return tmpl
}
// TemplateAsset handle the find Template request
func TemplateAsset(process *process.Process) interface{} {
process.ValidateArgNums(3)
sui := get(process)
tmpl, err := sui.GetTemplate(process.ArgsString(1))
if err != nil {
exception.New(err.Error(), 500).Throw()
}
asset, err := tmpl.Asset(process.ArgsString(2))
if err != nil {
exception.New(err.Error(), 404).Throw()
}
return map[string]interface{}{
"content": asset.Content,
"type": asset.Type,
}
}
// LocaleGet handle the find Template request
@ -200,6 +223,51 @@ func PageGet(process *process.Process) interface{} {
return tree
}
// PageAsset handle the find Template request
func PageAsset(process *process.Process) interface{} {
process.ValidateArgNums(3)
sui := get(process)
templateID := process.ArgsString(1)
tmpl, err := sui.GetTemplate(templateID)
if err != nil {
exception.New(err.Error(), 500).Throw()
}
file := process.ArgsString(2)
page, err := tmpl.GetPageFromAsset(file)
if err != nil {
exception.New(err.Error(), 500).Throw()
}
var asset *core.Asset
switch filepath.Ext(file) {
case ".css":
asset, err = page.AssetStyle()
if err != nil {
exception.New(err.Error(), 400).Throw()
}
break
case ".js", ".ts":
asset, err = page.AssetScript()
if err != nil {
exception.New(err.Error(), 400).Throw()
}
break
default:
exception.New("does not support the %s file", 400, filepath.Ext(file)).Throw()
}
return map[string]interface{}{
"content": asset.Content,
"type": asset.Type,
}
}
// EditorRender handle the render page request
func EditorRender(process *process.Process) interface{} {
process.ValidateArgNums(3)

View file

@ -48,6 +48,26 @@ func TestTemplateFind(t *testing.T) {
assert.Equal(t, "tech-blue", res.(*local.Template).ID)
}
func TestTemplateAsset(t *testing.T) {
load(t)
defer clean()
// test demo
p, err := process.Of("sui.template.asset", "demo", "tech-blue", "/css/tailwind.css")
if err != nil {
t.Fatal(err)
}
res, err := p.Exec()
if err != nil {
t.Fatal(err)
}
assert.NotEmpty(t, res)
assert.Equal(t, "text/css; charset=utf-8", res.(map[string]interface{})["type"])
assert.NotEmpty(t, res.(map[string]interface{})["content"])
}
func TestTemplateLocaleGet(t *testing.T) {
load(t)
defer clean()
@ -174,7 +194,7 @@ func TestTemplateComponentFind(t *testing.T) {
assert.Contains(t, res.(string), "window.component__Box=")
}
func TestTemplatePageTree(t *testing.T) {
func TestPageTree(t *testing.T) {
load(t)
defer clean()
@ -195,7 +215,7 @@ func TestTemplatePageTree(t *testing.T) {
assert.Equal(t, "index", res.([]*core.PageTreeNode)[1].Name)
}
func TestTemplatePageGet(t *testing.T) {
func TestPageGet(t *testing.T) {
load(t)
defer clean()
@ -218,6 +238,64 @@ func TestTemplatePageGet(t *testing.T) {
}
}
func TestPageAssetJS(t *testing.T) {
load(t)
defer clean()
// test demo
p, err := process.Of("sui.page.asset", "demo", "tech-blue", "/page/404/404.js")
if err != nil {
t.Fatal(err)
}
res, err := p.Exec()
if err != nil {
t.Fatal(err)
}
assert.IsType(t, map[string]interface{}{}, res)
assert.Equal(t, "text/javascript; charset=utf-8", res.(map[string]interface{})["type"])
assert.NotEmpty(t, res.(map[string]interface{})["content"])
}
func TestPageAssetTS(t *testing.T) {
load(t)
defer clean()
// test demo
p, err := process.Of("sui.page.asset", "demo", "tech-blue", "/page/404/404.ts")
if err != nil {
t.Fatal(err)
}
res, err := p.Exec()
if err != nil {
t.Fatal(err)
}
assert.IsType(t, map[string]interface{}{}, res)
assert.Equal(t, "text/javascript; charset=utf-8", res.(map[string]interface{})["type"])
assert.NotEmpty(t, res.(map[string]interface{})["content"])
}
func TestPageAssetCSS(t *testing.T) {
load(t)
defer clean()
// test demo
p, err := process.Of("sui.page.asset", "demo", "tech-blue", "/page/[id]/[id].css")
if err != nil {
t.Fatal(err)
}
res, err := p.Exec()
if err != nil {
t.Fatal(err)
}
assert.IsType(t, map[string]interface{}{}, res)
assert.Equal(t, "text/css; charset=utf-8", res.(map[string]interface{})["type"])
assert.NotEmpty(t, res.(map[string]interface{})["content"])
}
func TestEditorRender(t *testing.T) {
load(t)
defer clean()

View file

@ -1,4 +1,48 @@
package core
import (
"regexp"
"github.com/evanw/esbuild/pkg/api"
"github.com/yaoapp/gou/runtime/transform"
)
// Compile the page
func (page *Page) Compile() {}
// CompileJS compile the javascript
func (page *Page) CompileJS(source []byte, minify bool) ([]byte, error) {
jsCode := regexp.MustCompile(`import\s+.*;`).ReplaceAllString(string(source), "")
if minify {
minified, err := transform.MinifyJS(jsCode)
return []byte(minified), err
}
return []byte(jsCode), nil
}
// CompileTS compile the typescript
func (page *Page) CompileTS(source []byte, minify bool) ([]byte, error) {
tsCode := regexp.MustCompile(`import\s+.*;`).ReplaceAllString(string(source), "")
if minify {
jsCode, err := transform.TypeScript(string(tsCode), api.TransformOptions{
Target: api.ESNext,
MinifyWhitespace: true,
MinifyIdentifiers: true,
MinifySyntax: true,
})
return []byte(jsCode), err
}
jsCode, err := transform.TypeScript(string(tsCode), api.TransformOptions{Target: api.ESNext})
return []byte(jsCode), err
}
// CompileCSS compile the css
func (page *Page) CompileCSS(source []byte, minify bool) ([]byte, error) {
if minify {
cssCode, err := transform.MinifyCSS(string(source))
return []byte(cssCode), err
}
return source, nil
}

View file

@ -15,6 +15,7 @@ type ITemplate interface {
Pages() ([]IPage, error)
PageTree(route string) ([]*PageTreeNode, error)
Page(route string) (IPage, error)
GetPageFromAsset(asset string) (IPage, error)
Blocks() ([]IBlock, error)
Block(name string) (IBlock, error)
@ -25,6 +26,8 @@ type ITemplate interface {
Assets() []string
Locales() []SelectOption
Themes() []SelectOption
Asset(file string) (*Asset, error)
}
// IPage is the interface for the page
@ -39,6 +42,9 @@ type IPage interface {
EditorStyleSource() ResponseSource
EditorDataSource() ResponseSource
AssetScript() (*Asset, error)
AssetStyle() (*Asset, error)
// Render()
// Html()

View file

@ -66,6 +66,13 @@ type SelectOption struct {
Value string `json:"value"`
}
// Asset is the struct for the asset
type Asset struct {
file string
Type string `json:"type"`
Content []byte `json:"content"`
}
// Request is the struct for the request
type Request struct {
Method string `json:"method"`

View file

@ -254,3 +254,89 @@ func (page *Page) Load() error {
page.Document = page.tmpl.Document
return nil
}
// GetPageFromAsset get the page from the asset
func (tmpl *Template) GetPageFromAsset(file string) (core.IPage, error) {
route := filepath.Dir(file)
name := tmpl.getPageBase(route)
return &Page{
tmpl: tmpl,
Page: &core.Page{
Route: route,
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)},
TS: core.Source{File: fmt.Sprintf("%s.ts", name)},
LESS: core.Source{File: fmt.Sprintf("%s.less", name)},
},
},
}, nil
}
// AssetScript get the script
func (page *Page) AssetScript() (*core.Asset, error) {
// Read the Script code
// Type script is the default language
tsFile := filepath.Join(page.Path, page.Codes.TS.File)
if exist, _ := page.tmpl.local.fs.Exists(tsFile); exist {
tsCode, err := page.tmpl.local.fs.ReadFile(tsFile)
if err != nil {
return nil, err
}
jsCode, err := page.CompileTS(tsCode, false)
if err != nil {
return nil, err
}
return &core.Asset{
Type: "text/javascript; charset=utf-8",
Content: []byte(jsCode),
}, nil
}
jsFile := filepath.Join(page.Path, page.Codes.JS.File)
if exist, _ := page.tmpl.local.fs.Exists(jsFile); exist {
jsCode, err := page.tmpl.local.fs.ReadFile(jsFile)
if err != nil {
return nil, err
}
jsCode, err = page.CompileJS(jsCode, false)
if err != nil {
return nil, err
}
return &core.Asset{
Type: "text/javascript; charset=utf-8",
Content: jsCode,
}, nil
}
return nil, fmt.Errorf("Page %s script not found", page.Route)
}
// AssetStyle get the style
func (page *Page) AssetStyle() (*core.Asset, error) {
cssFile := filepath.Join(page.Path, page.Codes.CSS.File)
if exist, _ := page.tmpl.local.fs.Exists(cssFile); exist {
cssCode, err := page.tmpl.local.fs.ReadFile(cssFile)
if err != nil {
return nil, err
}
cssCode, err = page.CompileCSS(cssCode, false)
if err != nil {
return nil, err
}
return &core.Asset{
Type: "text/css; charset=utf-8",
Content: cssCode,
}, nil
}
return nil, fmt.Errorf("Page %s style not found", page.Route)
}

View file

@ -186,3 +186,106 @@ func TestPageRenderEditor(t *testing.T) {
assert.Equal(t, "@assets/css/tailwind.css", res.Styles[3])
assert.Equal(t, "@pages/index/index.css", res.Styles[4])
}
func TestPageGetPageFromAsset(t *testing.T) {
tests := prepare(t)
defer clean()
tmpl, err := tests.Demo.GetTemplate("tech-blue")
if err != nil {
t.Fatalf("GetTemplate error: %v", err)
}
file := "/index/index.css"
page, err := tmpl.GetPageFromAsset(file)
if err != nil {
t.Fatalf("GetPageFromAsset error: %v", err)
}
assert.Equal(t, "/index", page.Get().Route)
assert.Equal(t, "/templates/tech-blue/index", page.Get().Path)
assert.Equal(t, "index", page.Get().Name)
file = "/page/404/404.js"
page, err = tmpl.GetPageFromAsset(file)
if err != nil {
t.Fatalf("GetPageFromAsset error: %v", err)
}
assert.Equal(t, "/page/404", page.Get().Route)
assert.Equal(t, "/templates/tech-blue/page/404", page.Get().Path)
assert.Equal(t, "404", page.Get().Name)
}
func TestPageAssetScriptJS(t *testing.T) {
tests := prepare(t)
defer clean()
tmpl, err := tests.Demo.GetTemplate("tech-blue")
if err != nil {
t.Fatalf("GetTemplate error: %v", err)
}
file := "/page/404/404.js"
page, err := tmpl.GetPageFromAsset(file)
if err != nil {
t.Fatalf("GetPageFromAsset error: %v", err)
}
asset, err := page.AssetScript()
if err != nil {
t.Fatalf("AssetScript error: %v", err)
}
assert.NotEmpty(t, asset.Content)
assert.Equal(t, "text/javascript; charset=utf-8", asset.Type)
}
func TestPageAssetScriptTS(t *testing.T) {
tests := prepare(t)
defer clean()
tmpl, err := tests.Demo.GetTemplate("tech-blue")
if err != nil {
t.Fatalf("GetTemplate error: %v", err)
}
file := "/page/[id]/[id].ts"
page, err := tmpl.GetPageFromAsset(file)
if err != nil {
t.Fatalf("GetPageFromAsset error: %v", err)
}
asset, err := page.AssetScript()
if err != nil {
t.Fatalf("AssetScript error: %v", err)
}
assert.NotEmpty(t, asset.Content)
assert.Equal(t, "text/javascript; charset=utf-8", asset.Type)
}
func TestPageAssetStyle(t *testing.T) {
tests := prepare(t)
defer clean()
tmpl, err := tests.Demo.GetTemplate("tech-blue")
if err != nil {
t.Fatalf("GetTemplate error: %v", err)
}
file := "/page/[id]/[id].css"
page, err := tmpl.GetPageFromAsset(file)
if err != nil {
t.Fatalf("GetPageFromAsset error: %v", err)
}
asset, err := page.AssetStyle()
if err != nil {
t.Fatalf("AssetStyle error: %v", err)
}
assert.NotEmpty(t, asset.Content)
assert.Equal(t, "text/css; charset=utf-8", asset.Type)
}

View file

@ -1,6 +1,7 @@
package local
import (
"fmt"
"path/filepath"
"github.com/yaoapp/yao/sui/core"
@ -42,3 +43,49 @@ func (tmpl *Template) Locales() []core.SelectOption {
func (tmpl *Template) Themes() []core.SelectOption {
return tmpl.Template.Themes
}
// Asset get the asset
func (tmpl *Template) Asset(file string) (*core.Asset, error) {
file = filepath.Join(tmpl.Root, "__assets", file)
if exist, _ := tmpl.local.fs.Exists(file); exist {
content, err := tmpl.local.fs.ReadFile(file)
if err != nil {
return nil, err
}
typ := "text/plain"
switch filepath.Ext(file) {
case ".css":
typ = "text/css; charset=utf-8"
break
case ".js":
typ = "application/javascript; charset=utf-8"
break
case ".ts":
typ = "application/javascript; charset=utf-8"
break
case ".json":
typ = "application/json; charset=utf-8"
break
case ".html":
typ = "text/html; charset=utf-8"
break
default:
typ, err = tmpl.local.fs.MimeType(file)
if err != nil {
return nil, err
}
}
return &core.Asset{Type: typ, Content: content}, nil
}
return nil, fmt.Errorf("Asset %s not found", file)
}

View file

@ -49,3 +49,22 @@ func TestTemplateLocales(t *testing.T) {
assert.Equal(t, "zh-TW", locales[2].Label)
assert.Equal(t, "zh-tw", locales[2].Value)
}
func TestTemplateAsset(t *testing.T) {
tests := prepare(t)
defer clean()
tmpl, err := tests.Demo.GetTemplate("tech-blue")
if err != nil {
t.Fatalf("GetTemplate error: %v", err)
}
asset, err := tmpl.Asset("/css/tailwind.css")
if err != nil {
t.Fatalf("Asset error: %v", err)
}
assert.Equal(t, "text/css; charset=utf-8", asset.Type)
assert.NotEmpty(t, asset.Content)
}