Merge pull request #726 from trheyi/main
Add s:render feature for data update rendering
This commit is contained in:
commit
d9769af569
21 changed files with 1103 additions and 375 deletions
3
Makefile
3
Makefile
|
|
@ -130,7 +130,8 @@ bindata:
|
|||
cp -r ui .tmp/data/public
|
||||
cp -r xgen .tmp/data/
|
||||
cp -r yao .tmp/data/
|
||||
cp -r builder .tmp/data/
|
||||
cp -r sui/libsui .tmp/data/
|
||||
find .tmp/data -name ".DS_Store" -type f -delete
|
||||
go-bindata -fs -pkg data -o data/bindata.go -prefix ".tmp/data/" .tmp/data/...
|
||||
rm -rf .tmp/data
|
||||
rm -rf .tmp/yao-init
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
function foo() {
|
||||
return "foo";
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
<div>Yao Builder</div>
|
||||
233
data/bindata.go
233
data/bindata.go
File diff suppressed because one or more lines are too long
12
data/data.go
12
data/data.go
|
|
@ -46,18 +46,6 @@ func Setup() *assetfs.AssetFS {
|
|||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Builder Builder ui
|
||||
func Builder() *assetfs.AssetFS {
|
||||
assetInfo := func(path string) (os.FileInfo, error) {
|
||||
return os.Stat(path)
|
||||
}
|
||||
for k := range _bintree.Children {
|
||||
k = "builder"
|
||||
return &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, AssetInfo: assetInfo, Prefix: k, Fallback: "index.html"}
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// ReplaceXGen bindata file
|
||||
func ReplaceXGen(search, replace string) error {
|
||||
err := replaceXGenIndex(search, replace)
|
||||
|
|
|
|||
|
|
@ -18,9 +18,6 @@ var AppFileServer http.Handler
|
|||
// XGenFileServerV1 XGen v1.0
|
||||
var XGenFileServerV1 http.Handler = http.FileServer(data.XgenV1())
|
||||
|
||||
// BuilderFileServer Builder ui
|
||||
var BuilderFileServer http.Handler = http.FileServer(data.Builder())
|
||||
|
||||
// AdminRoot cache
|
||||
var AdminRoot = ""
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,26 @@ var dsl = []byte(`
|
|||
"guard": "bearer-jwt",
|
||||
"group": "__yao/sui/v1",
|
||||
"paths": [
|
||||
{
|
||||
"label": "Render",
|
||||
"description": "Render the frontend page",
|
||||
"path": "/render/*route",
|
||||
"method": "POST",
|
||||
"guard": "-",
|
||||
"process": "sui.Render",
|
||||
"in": [":context", "$param.route", ":payload"],
|
||||
"out": { "status": 200, "type": "text/html; charset=utf-8" }
|
||||
},
|
||||
{
|
||||
"label": "Run",
|
||||
"description": "Run the backend script, with Api prefix method",
|
||||
"path": "/run/*route",
|
||||
"guard": "-",
|
||||
"method": "POST",
|
||||
"process": "sui.Run",
|
||||
"in": [":context", "$param.route", ":payload"],
|
||||
"out": { "status": 200, "type": "application/json" }
|
||||
},
|
||||
{
|
||||
"path": "/:id/setting",
|
||||
"method": "GET",
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ func init() {
|
|||
process.RegisterGroup("sui", map[string]process.Handler{
|
||||
"setting": Setting,
|
||||
|
||||
"render": Render,
|
||||
"run": Run,
|
||||
|
||||
"template.get": TemplateGet,
|
||||
"template.find": TemplateFind,
|
||||
"template.asset": TemplateAsset,
|
||||
|
|
|
|||
111
sui/api/render.go
Normal file
111
sui/api/render.go
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/yaoapp/gou/process"
|
||||
"github.com/yaoapp/yao/sui/core"
|
||||
)
|
||||
|
||||
// Render the frontend page
|
||||
func Render(process *process.Process) interface{} {
|
||||
process.ValidateArgNums(3)
|
||||
ctx, ok := process.Args[0].(*gin.Context)
|
||||
if !ok {
|
||||
return "The context is required"
|
||||
}
|
||||
|
||||
ctx.Header("Content-Type", "text/html; charset=utf-8")
|
||||
route := process.ArgsString(1)
|
||||
payload := process.ArgsMap(2)
|
||||
|
||||
if route == "" {
|
||||
return "The route is required"
|
||||
}
|
||||
|
||||
if payload["name"] == nil {
|
||||
return "The render name is required"
|
||||
}
|
||||
|
||||
ctx.Request.URL.Path = route
|
||||
r, _, err := NewRequestContext(ctx)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("<div class='text-danger'> %s </div>", err.Error())
|
||||
}
|
||||
|
||||
var c *core.Cache = nil
|
||||
if !r.Request.DisableCache() {
|
||||
c = core.GetCache(r.File)
|
||||
}
|
||||
|
||||
if c == nil {
|
||||
c, _, err = r.MakeCache()
|
||||
if err != nil {
|
||||
return fmt.Sprintf("<div class='text-danger'> %s </div>", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if c == nil {
|
||||
return fmt.Sprintf("<div class='text-danger'> Cache not found </div>")
|
||||
}
|
||||
|
||||
data, ok := payload["data"].(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Sprintf("<div class='text-danger'> Data not found </div>")
|
||||
}
|
||||
|
||||
name, ok := payload["name"].(string)
|
||||
if !ok {
|
||||
return fmt.Sprintf("<div class='text-danger'> Name not found </div>")
|
||||
}
|
||||
|
||||
html, err := r.renderHTML(c, name, c.HTML, core.Data(data))
|
||||
if err != nil {
|
||||
return fmt.Sprintf("<div class='text-danger'> %s </div>", err.Error())
|
||||
}
|
||||
|
||||
return html
|
||||
}
|
||||
|
||||
func (r *Request) renderHTML(c *core.Cache, name string, html string, data core.Data) (string, error) {
|
||||
|
||||
doc, err := core.NewDocument([]byte(html))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Document error: %w", err)
|
||||
}
|
||||
|
||||
sel := doc.Find(fmt.Sprintf("[s\\:render='%s']", name))
|
||||
if sel.Length() == 0 {
|
||||
return "", fmt.Errorf("Render %s not found", name)
|
||||
}
|
||||
|
||||
// Set the page request data
|
||||
option := core.ParserOption{
|
||||
Theme: r.Request.Theme,
|
||||
Locale: r.Request.Locale,
|
||||
Debug: r.Request.DebugMode(),
|
||||
DisableCache: r.Request.DisableCache(),
|
||||
Route: r.Request.URL.Path,
|
||||
Root: c.Root,
|
||||
Script: c.Script,
|
||||
Imports: c.Imports,
|
||||
Request: r.Request,
|
||||
}
|
||||
|
||||
// Parse the template
|
||||
parser := core.NewTemplateParser(data, &option)
|
||||
err = parser.RenderSelection(sel)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Parser error: %w", err)
|
||||
}
|
||||
|
||||
sel.Find("[sui-hide]").Remove()
|
||||
parser.Tidy(sel)
|
||||
html, err = sel.Html()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Html error: %w", err)
|
||||
}
|
||||
|
||||
return html, nil
|
||||
}
|
||||
8
sui/api/run.go
Normal file
8
sui/api/run.go
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package api
|
||||
|
||||
import "github.com/yaoapp/gou/process"
|
||||
|
||||
// Run the backend script, with Api prefix method
|
||||
func Run(process *process.Process) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -71,6 +71,19 @@ func (page *Page) Build(ctx *BuildContext, option *BuildOption) (*goquery.Docume
|
|||
ctx.warnings = append(ctx.warnings, warnings...)
|
||||
}
|
||||
|
||||
// Prepend the libsui.min.js
|
||||
if !option.IgnoreLibSUI {
|
||||
script := ScriptNode{
|
||||
Parent: "head",
|
||||
Attrs: []html.Attribute{
|
||||
{Key: "src", Val: fmt.Sprintf("%s/libsui.min.js", option.AssetRoot)},
|
||||
{Key: "type", Val: "text/javascript"},
|
||||
{Key: "name", Val: "libsui"},
|
||||
},
|
||||
}
|
||||
ctx.scripts = append([]ScriptNode{script}, ctx.scripts...)
|
||||
}
|
||||
|
||||
// Scripts
|
||||
scripts, err := page.BuildScripts(ctx, option, "__page", namespace)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -58,13 +58,6 @@ func (page *Page) Compile(ctx *BuildContext, option *BuildOption) (string, []str
|
|||
|
||||
}
|
||||
|
||||
// SUI lib
|
||||
lib, err := libsui(option.ScriptMinify)
|
||||
if err != nil {
|
||||
return "", warnings, err
|
||||
}
|
||||
head.AppendHtml("\n\n" + `<script name="sui" type="text/javascript">` + lib + `</script>` + "\n\n")
|
||||
|
||||
// Page Config
|
||||
page.Config = page.GetConfig()
|
||||
|
||||
|
|
|
|||
|
|
@ -94,10 +94,12 @@ func GetEventScript(sequence int, sel *goquery.Selection, ns string, cn string,
|
|||
for name, handler := range events {
|
||||
if ispage {
|
||||
source += pageEventInjectScript(id, name, dataRaw, jsonRaw, handler) + "\n"
|
||||
sel.SetAttr("s:event-cn", "__page")
|
||||
} else {
|
||||
source += compEventInjectScript(id, name, cn, dataRaw, jsonRaw, handler) + "\n"
|
||||
sel.SetAttr("s:event-cn", cn)
|
||||
}
|
||||
sel.RemoveAttr(fmt.Sprintf("s:on-%s", name))
|
||||
// sel.RemoveAttr(fmt.Sprintf("s:on-%s", name))
|
||||
}
|
||||
|
||||
sel.SetAttr("s:event", id)
|
||||
|
|
|
|||
|
|
@ -5,226 +5,45 @@ import (
|
|||
|
||||
"github.com/evanw/esbuild/pkg/api"
|
||||
"github.com/yaoapp/gou/runtime/transform"
|
||||
"github.com/yaoapp/yao/data"
|
||||
)
|
||||
|
||||
var libsuicode = ""
|
||||
|
||||
func libsui(minify bool) (string, error) {
|
||||
if libsuicode != "" {
|
||||
return libsuicode, nil
|
||||
}
|
||||
// LibSUI return the libsui code
|
||||
func LibSUI() ([]byte, []byte, error) {
|
||||
|
||||
option := api.TransformOptions{Target: api.ES2015}
|
||||
if minify {
|
||||
option.MinifyIdentifiers = true
|
||||
option.MinifySyntax = true
|
||||
option.MinifyWhitespace = true
|
||||
}
|
||||
var err error
|
||||
libsuicode, err = transform.JavaScript(libsuisource, option)
|
||||
// Read source code from bindata
|
||||
index, err := data.Read("libsui/index.ts")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("libsui error: %w", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
return libsuicode, nil
|
||||
|
||||
utils, err := data.Read("libsui/utils.ts")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
yao, err := data.Read("libsui/yao.ts")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Merge the source code
|
||||
source := fmt.Sprintf("%s\n%s\n%s", index, utils, yao)
|
||||
|
||||
// Build the source code
|
||||
js, sm, err := transform.TypeScriptWithSourceMap(string(source), api.TransformOptions{
|
||||
Target: api.ES2015,
|
||||
MinifyIdentifiers: true,
|
||||
MinifySyntax: true,
|
||||
MinifyWhitespace: true,
|
||||
Sourcefile: "libsui.ts",
|
||||
})
|
||||
|
||||
return js, sm, nil
|
||||
}
|
||||
|
||||
const libsuisource = `
|
||||
|
||||
function $$(selector) {
|
||||
elm = null;
|
||||
if (typeof selector === "string" ){
|
||||
elm = document.querySelector(selector);
|
||||
}
|
||||
|
||||
if (selector instanceof HTMLElement) {
|
||||
elm = selector;
|
||||
}
|
||||
|
||||
if (elm) {
|
||||
cn = elm.getAttribute("s:cn");
|
||||
if (cn != "" && typeof window[cn] === "function") {
|
||||
const component = new window[cn](elm);
|
||||
return new __sui_component(elm, component);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const $utils = {
|
||||
|
||||
RemoveClass: (element, className) => {
|
||||
const classes = Array.isArray(className) ? className : className.split(" ");
|
||||
classes.forEach((c) => {
|
||||
const v = c.replace(/[\n\r\s]/g, "");
|
||||
if (v === "") return;
|
||||
element.classList.remove(v);
|
||||
});
|
||||
},
|
||||
|
||||
AddClass: (element, className) => {
|
||||
const classes = Array.isArray(className) ? className : className.split(" ");
|
||||
classes.forEach((c) => {
|
||||
const v = c.replace(/[\n\r\s]/g, "");
|
||||
if (v === "") return;
|
||||
element.classList.add(v);
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
function __sui_component_root(elm, name) {
|
||||
while (elm && elm.getAttribute("s:cn") !== name) {
|
||||
elm = elm.parentElement;
|
||||
}
|
||||
return elm;
|
||||
}
|
||||
|
||||
function __sui_state(component) {
|
||||
this.handlers = component.watch || {};
|
||||
this.Set = async function (key, value, target) {
|
||||
const handler = this.handlers[key];
|
||||
target = target || component.root;
|
||||
if (handler && typeof handler === "function") {
|
||||
const stateObj = {
|
||||
target: target,
|
||||
stopPropagation: function () {
|
||||
target.setAttribute("state-propagation", "true");
|
||||
},
|
||||
}
|
||||
await handler(value, stateObj);
|
||||
const isStopPropagation = target ? target.getAttribute("state-propagation") === "true" : false;
|
||||
if (isStopPropagation) {
|
||||
return;
|
||||
}
|
||||
|
||||
let parent = component.root.parentElement;
|
||||
while (parent && !parent.getAttribute("s:cn")) {
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
if ( parent == document.body || parent == null) {
|
||||
return;
|
||||
}
|
||||
// Dispatch the state change custom event to parent component
|
||||
const event = new CustomEvent("state:change", { detail: { key: key, value: value, target:component.root } });
|
||||
parent.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function __sui_props(elm) {
|
||||
this.Get = function (key) {
|
||||
if (!elm || typeof elm.getAttribute !== "function") {
|
||||
return null;
|
||||
}
|
||||
const k = "prop:" + key;
|
||||
const v = elm.getAttribute(k);
|
||||
const json = elm.getAttribute("json-attr-prop:" + key) === "true";
|
||||
if (json) {
|
||||
try {
|
||||
return JSON.parse(v);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
this.List = function () {
|
||||
const props = {};
|
||||
if (!elm || typeof elm.getAttribute !== "function") {
|
||||
return props;
|
||||
}
|
||||
|
||||
const attrs = elm.attributes;
|
||||
for (let i = 0; i < attrs.length; i++) {
|
||||
const attr = attrs[i];
|
||||
if (attr.name.startsWith("prop:")) {
|
||||
const k = attr.name.replace("prop:", "");
|
||||
const json = elm.getAttribute("json-attr-prop:" + k) === "true";
|
||||
if (json) {
|
||||
try {
|
||||
props[k] = JSON.parse(attr.value);
|
||||
} catch (e) {
|
||||
props[k] = null;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
props[k] = attr.value;
|
||||
}
|
||||
}
|
||||
return props;
|
||||
}
|
||||
}
|
||||
|
||||
function __sui_component(elm, component) {
|
||||
this.root = elm;
|
||||
this.store = new __sui_store(elm);
|
||||
this.props = new __sui_props(elm);
|
||||
this.state = component ? new __sui_state(component) : {};
|
||||
}
|
||||
|
||||
function __sui_event_handler(event, dataKeys, jsonKeys, target, root, handler) {
|
||||
const data = {};
|
||||
target = target || null;
|
||||
if (target) {
|
||||
dataKeys.forEach(function (key) {
|
||||
const value = target.getAttribute("data:" + key);
|
||||
data[key] = value;
|
||||
})
|
||||
jsonKeys.forEach(function (key) {
|
||||
const value = target.getAttribute("json:" + key);
|
||||
data[key] = null;
|
||||
if (value && value != "") {
|
||||
try {
|
||||
data[key] = JSON.parse(value);
|
||||
} catch (e) {
|
||||
const message = e.message || e || "An error occurred";
|
||||
console.error(` + "`[SUI] Event Handler Error: ${message}`" + `, target);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
handler && handler(event, data, {
|
||||
rootElement: root,
|
||||
targetElement: target
|
||||
});
|
||||
};
|
||||
|
||||
function __sui_store(elm) {
|
||||
elm = elm || document.body;
|
||||
|
||||
this.Get = function (key) {
|
||||
return elm.getAttribute("data:" + key);
|
||||
}
|
||||
|
||||
this.Set = function (key, value) {
|
||||
elm.setAttribute("data:" + key, value);
|
||||
}
|
||||
|
||||
this.GetJSON = function (key) {
|
||||
const value = elm.getAttribute("json:" + key);
|
||||
if (value && value != "") {
|
||||
try {
|
||||
const res = JSON.parse(value);
|
||||
return res;
|
||||
} catch (e) {
|
||||
const message = e.message || e || "An error occurred";
|
||||
console.error(` + "`[SUI] Event Handler Error: ${message}`" + `, elm);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
this.SetJSON = function (key, value) {
|
||||
elm.setAttribute("json:" + key, JSON.stringify(value));
|
||||
}
|
||||
|
||||
this.GetData = function () {
|
||||
return this.GetJSON("__component_data") || {};
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const initScriptTmpl = `
|
||||
try {
|
||||
var __sui_data = %s;
|
||||
|
|
@ -237,7 +56,7 @@ const initScriptTmpl = `
|
|||
const cn = element.getAttribute("s:cn");
|
||||
if (method && typeof window[cn] === "function") {
|
||||
try {
|
||||
window[cn](element);
|
||||
new window[cn](element);
|
||||
} catch (e) {
|
||||
const message = e.message || e || "An error occurred";
|
||||
console.error(` + "`[SUI] ${cn} Error: ${message}`" + `);
|
||||
|
|
@ -264,24 +83,30 @@ const i118nScriptTmpl = `
|
|||
`
|
||||
|
||||
const pageEventScriptTmpl = `
|
||||
document.querySelector("[s\\:event=%s]").addEventListener("%s", function (event) {
|
||||
const dataKeys = %s;
|
||||
const jsonKeys = %s;
|
||||
const root = document.body;
|
||||
const target = this;
|
||||
__sui_event_handler(event, dataKeys, jsonKeys, target, root, %s);
|
||||
});
|
||||
if (document.querySelector("[s\\:event=%s]")) {
|
||||
let elms = document.querySelectorAll("[s\\:event=%s]");
|
||||
elms.forEach(function (element) {
|
||||
element.addEventListener("%s", function (event) {
|
||||
const dataKeys = %s;
|
||||
const jsonKeys = %s;
|
||||
const root = document.body;
|
||||
__sui_event_handler(event, dataKeys, jsonKeys, element, root, window.%s);
|
||||
});
|
||||
});
|
||||
}
|
||||
`
|
||||
|
||||
const compEventScriptTmpl = `
|
||||
if (document.querySelector("[s\\:event=%s]")) {
|
||||
document.querySelector("[s\\:event=%s]").addEventListener("%s", function (event) {
|
||||
const dataKeys = %s;
|
||||
const jsonKeys = %s;
|
||||
const root = __sui_component_root(this, "%s");
|
||||
handler = new %s(root).%s;
|
||||
const target = event.target || null;
|
||||
__sui_event_handler(event, dataKeys, jsonKeys, target, root, handler);
|
||||
let elms = document.querySelectorAll("[s\\:event=%s]");
|
||||
elms.forEach(function (element) {
|
||||
element.addEventListener("%s", function (event) {
|
||||
const dataKeys = %s;
|
||||
const jsonKeys = %s;
|
||||
const root = __sui_component_root(element, "%s");
|
||||
handler = new %s(root).%s;
|
||||
__sui_event_handler(event, dataKeys, jsonKeys, element, root, handler);
|
||||
});
|
||||
});
|
||||
}
|
||||
`
|
||||
|
|
@ -333,7 +158,7 @@ func headInjectionScript(jsonRaw string) string {
|
|||
}
|
||||
|
||||
func pageEventInjectScript(eventID, eventName, dataKeys, jsonKeys, handler string) string {
|
||||
return fmt.Sprintf(pageEventScriptTmpl, eventID, eventName, dataKeys, jsonKeys, handler)
|
||||
return fmt.Sprintf(pageEventScriptTmpl, eventID, eventID, eventName, dataKeys, jsonKeys, handler)
|
||||
}
|
||||
|
||||
func compEventInjectScript(eventID, eventName, component, dataKeys, jsonKeys, handler string) string {
|
||||
|
|
|
|||
|
|
@ -68,17 +68,21 @@ var keepWords = map[string]bool{
|
|||
}
|
||||
|
||||
var allowUsePropAttrs = map[string]bool{
|
||||
"s:if": true,
|
||||
"s:elif": true,
|
||||
"s:for": true,
|
||||
"s:event": true,
|
||||
"s:if": true,
|
||||
"s:elif": true,
|
||||
"s:for": true,
|
||||
"s:event": true,
|
||||
"s:event-cn": true,
|
||||
"s:render": true,
|
||||
}
|
||||
|
||||
var keepAttrs = map[string]bool{
|
||||
"s:ns": true,
|
||||
"s:cn": true,
|
||||
"s:ready": true,
|
||||
"s:event": true,
|
||||
"s:ns": true,
|
||||
"s:cn": true,
|
||||
"s:ready": true,
|
||||
"s:event": true,
|
||||
"s:event-cn": true,
|
||||
"s:render": true,
|
||||
}
|
||||
|
||||
// NewTemplateParser create a new template parser
|
||||
|
|
@ -157,7 +161,7 @@ func (parser *TemplateParser) Render(html string) (string, error) {
|
|||
if parser.option != nil && (parser.option.Request != nil || parser.option.Preview) {
|
||||
// Remove the sui-hide attribute
|
||||
doc.Find("[sui-hide]").Remove()
|
||||
parser.tidy(doc.Selection)
|
||||
parser.Tidy(doc.Selection)
|
||||
}
|
||||
|
||||
// fmt.Println(doc.Html())
|
||||
|
|
@ -837,13 +841,13 @@ func (parser *TemplateParser) show(sel *goquery.Selection) {
|
|||
// sel.SetAttr("style", style)
|
||||
}
|
||||
|
||||
func (parser *TemplateParser) tidy(s *goquery.Selection) {
|
||||
func (parser *TemplateParser) Tidy(s *goquery.Selection) {
|
||||
|
||||
s.Contents().Each(func(i int, child *goquery.Selection) {
|
||||
|
||||
node := child.Get(0)
|
||||
if _, exist := child.Attr("s:jit"); node.Data == "slot" || exist {
|
||||
parser.tidy(child)
|
||||
parser.Tidy(child)
|
||||
parser.removeWrapper(child)
|
||||
return
|
||||
}
|
||||
|
|
@ -861,7 +865,7 @@ func (parser *TemplateParser) tidy(s *goquery.Selection) {
|
|||
// Remove the parsed attribute
|
||||
attrs := []html.Attribute{}
|
||||
for _, attr := range node.Attr {
|
||||
if strings.HasPrefix(attr.Key, "s:") && !keepAttrs[attr.Key] {
|
||||
if strings.HasPrefix(attr.Key, "s:") && !keepAttrs[attr.Key] && !strings.HasPrefix(attr.Key, "s:on-") {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -872,7 +876,7 @@ func (parser *TemplateParser) tidy(s *goquery.Selection) {
|
|||
}
|
||||
|
||||
node.Attr = attrs
|
||||
parser.tidy(child)
|
||||
parser.Tidy(child)
|
||||
})
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -262,6 +262,7 @@ type BuildOption struct {
|
|||
PublicRoot string `json:"public_root,omitempty"`
|
||||
AssetRoot string `json:"asset_root,omitempty"`
|
||||
IgnoreAssetRoot bool `json:"ignore_asset_root,omitempty"`
|
||||
IgnoreLibSUI bool `json:"ignore_lib_sui,omitempty"`
|
||||
IgnoreDocument bool `json:"ignore_document,omitempty"`
|
||||
JitMode bool `json:"jit_mode,omitempty"`
|
||||
WithWrapper bool `json:"with_wrapper,omitempty"`
|
||||
|
|
|
|||
340
sui/libsui/index.ts
Normal file
340
sui/libsui/index.ts
Normal file
|
|
@ -0,0 +1,340 @@
|
|||
function $$(selector) {
|
||||
let elm: HTMLElement | null = null;
|
||||
if (typeof selector === "string") {
|
||||
elm = document.querySelector(selector);
|
||||
}
|
||||
|
||||
if (selector instanceof HTMLElement) {
|
||||
elm = selector;
|
||||
}
|
||||
|
||||
if (elm) {
|
||||
const cn = elm.getAttribute("s:cn");
|
||||
if (cn && cn != "" && typeof window[cn] === "function") {
|
||||
const component = new window[cn](elm);
|
||||
return new __sui_component(elm, component);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function __sui_component_root(elm: Element, name: string) {
|
||||
return elm.closest(`[s\\:cn=${name}]`);
|
||||
}
|
||||
|
||||
function __sui_state(component) {
|
||||
this.handlers = component.watch || {};
|
||||
this.Set = async function (key, value, target) {
|
||||
const handler = this.handlers[key];
|
||||
target = target || component.root;
|
||||
if (handler && typeof handler === "function") {
|
||||
const stateObj = {
|
||||
target: target,
|
||||
stopPropagation: function () {
|
||||
target.setAttribute("state-propagation", "true");
|
||||
},
|
||||
};
|
||||
await handler(value, stateObj);
|
||||
const isStopPropagation = target
|
||||
? target.getAttribute("state-propagation") === "true"
|
||||
: false;
|
||||
if (isStopPropagation) {
|
||||
return;
|
||||
}
|
||||
|
||||
let parent = component.root.parentElement;
|
||||
while (parent && !parent.getAttribute("s:cn")) {
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
if (parent == document.body || parent == null) {
|
||||
return;
|
||||
}
|
||||
// Dispatch the state change custom event to parent component
|
||||
const event = new CustomEvent("state:change", {
|
||||
detail: { key: key, value: value, target: component.root },
|
||||
});
|
||||
parent.dispatchEvent(event);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function __sui_props(elm) {
|
||||
this.Get = function (key) {
|
||||
if (!elm || typeof elm.getAttribute !== "function") {
|
||||
return null;
|
||||
}
|
||||
const k = "prop:" + key;
|
||||
const v = elm.getAttribute(k);
|
||||
const json = elm.getAttribute("json-attr-prop:" + key) === "true";
|
||||
if (json) {
|
||||
try {
|
||||
return JSON.parse(v);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return v;
|
||||
};
|
||||
|
||||
this.List = function () {
|
||||
const props = {};
|
||||
if (!elm || typeof elm.getAttribute !== "function") {
|
||||
return props;
|
||||
}
|
||||
|
||||
const attrs = elm.attributes;
|
||||
for (let i = 0; i < attrs.length; i++) {
|
||||
const attr = attrs[i];
|
||||
if (attr.name.startsWith("prop:")) {
|
||||
const k = attr.name.replace("prop:", "");
|
||||
const json = elm.getAttribute("json-attr-prop:" + k) === "true";
|
||||
if (json) {
|
||||
try {
|
||||
props[k] = JSON.parse(attr.value);
|
||||
} catch (e) {
|
||||
props[k] = null;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
props[k] = attr.value;
|
||||
}
|
||||
}
|
||||
return props;
|
||||
};
|
||||
}
|
||||
|
||||
function __sui_component(elm, component) {
|
||||
this.root = elm;
|
||||
this.store = new __sui_store(elm);
|
||||
this.props = new __sui_props(elm);
|
||||
this.state = component ? new __sui_state(component) : {};
|
||||
}
|
||||
|
||||
function __sui_event_handler(event, dataKeys, jsonKeys, target, root, handler) {
|
||||
const data = {};
|
||||
target = target || null;
|
||||
if (target) {
|
||||
dataKeys.forEach(function (key) {
|
||||
const value = target.getAttribute("data:" + key);
|
||||
data[key] = value;
|
||||
});
|
||||
jsonKeys.forEach(function (key) {
|
||||
const value = target.getAttribute("json:" + key);
|
||||
data[key] = null;
|
||||
if (value && value != "") {
|
||||
try {
|
||||
data[key] = JSON.parse(value);
|
||||
} catch (e) {
|
||||
const message = e.message || e || "An error occurred";
|
||||
console.error(`[SUI] Event Handler Error: ${message} `, target);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
handler &&
|
||||
handler(event, data, {
|
||||
rootElement: root,
|
||||
targetElement: target,
|
||||
});
|
||||
}
|
||||
|
||||
function __sui_event_init(elm: Element) {
|
||||
const eventElms = elm.querySelectorAll("[s\\:event]");
|
||||
eventElms.forEach((eventElm) => {
|
||||
const cn = eventElm.getAttribute("s:event-cn") || "";
|
||||
|
||||
// Data keys
|
||||
const events: Record<string, string> = {};
|
||||
const dataKeys: string[] = [];
|
||||
const jsonKeys: string[] = [];
|
||||
for (let i = 0; i < eventElm.attributes.length; i++) {
|
||||
if (eventElm.attributes[i].name.startsWith("data:")) {
|
||||
dataKeys.push(eventElm.attributes[i].name.replace("data:", ""));
|
||||
}
|
||||
if (eventElm.attributes[i].name.startsWith("json:")) {
|
||||
jsonKeys.push(eventElm.attributes[i].name.replace("json:", ""));
|
||||
}
|
||||
if (eventElm.attributes[i].name.startsWith("s:on-")) {
|
||||
const key = eventElm.attributes[i].name.replace("s:on-", "");
|
||||
events[key] = eventElm.attributes[i].value;
|
||||
}
|
||||
}
|
||||
|
||||
// Bind the event
|
||||
for (const name in events) {
|
||||
const bind = events[name];
|
||||
if (cn == "__page") {
|
||||
const handler = window[bind];
|
||||
const root = document.body;
|
||||
const target = eventElm;
|
||||
eventElm.addEventListener(name, (event) => {
|
||||
__sui_event_handler(event, dataKeys, jsonKeys, target, root, handler);
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const comp = new window[cn](eventElm.closest(`[s\\:cn=${cn}]`));
|
||||
const handler = comp[bind];
|
||||
const root = comp.root;
|
||||
const target = eventElm;
|
||||
eventElm.addEventListener(name, (event) => {
|
||||
__sui_event_handler(event, dataKeys, jsonKeys, target, root, handler);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function __sui_store(elm) {
|
||||
elm = elm || document.body;
|
||||
|
||||
this.Get = function (key) {
|
||||
return elm.getAttribute("data:" + key);
|
||||
};
|
||||
|
||||
this.Set = function (key, value) {
|
||||
elm.setAttribute("data:" + key, value);
|
||||
};
|
||||
|
||||
this.GetJSON = function (key) {
|
||||
const value = elm.getAttribute("json:" + key);
|
||||
if (value && value != "") {
|
||||
try {
|
||||
const res = JSON.parse(value);
|
||||
return res;
|
||||
} catch (e) {
|
||||
const message = e.message || e || "An error occurred";
|
||||
console.error(`[SUI] Event Handler Error: ${message}`, elm);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
this.SetJSON = function (key, value) {
|
||||
elm.setAttribute("json:" + key, JSON.stringify(value));
|
||||
};
|
||||
|
||||
this.GetData = function () {
|
||||
return this.GetJSON("__component_data") || {};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* SUI Render
|
||||
* @param component
|
||||
* @param name
|
||||
*/
|
||||
async function __sui_render(
|
||||
component: Component | string,
|
||||
name: string,
|
||||
data: Record<string, any>,
|
||||
option?: RenderOption
|
||||
): Promise<string> {
|
||||
const comp = (
|
||||
typeof component === "object" ? component : $$(component)
|
||||
) as Component;
|
||||
|
||||
if (comp == null) {
|
||||
console.error(`[SUI] Component not found: ${component}`);
|
||||
return Promise.reject("Component not found");
|
||||
}
|
||||
|
||||
const elms = comp.root.querySelectorAll(`[s\\:render=${name}]`);
|
||||
if (!elms.length) {
|
||||
console.error(`[SUI] No element found with s:render=${name}`);
|
||||
return Promise.reject("No element found");
|
||||
}
|
||||
|
||||
// Set default options
|
||||
option = option || {};
|
||||
option.replace = option.replace === undefined ? true : option.replace;
|
||||
option.showLoader =
|
||||
option.showLoader === undefined ? false : option.showLoader;
|
||||
option.withPageData =
|
||||
option.withPageData === undefined ? false : option.withPageData;
|
||||
|
||||
// Prepare loader
|
||||
let loader = `<span class="sui-render-loading">Loading...</span>`;
|
||||
if (option.showLoader && option.replace) {
|
||||
if (typeof option.showLoader === "string") {
|
||||
loader = option.showLoader;
|
||||
} else if (option.showLoader instanceof HTMLElement) {
|
||||
loader = option.showLoader.outerHTML;
|
||||
}
|
||||
elms.forEach((elm) => (elm.innerHTML = loader));
|
||||
}
|
||||
|
||||
// Prepare data
|
||||
let _data = comp.store.GetData() || {};
|
||||
if (option.withPageData) {
|
||||
// @ts-ignore
|
||||
_data = { ..._data, ...__sui_data };
|
||||
}
|
||||
|
||||
const route = window.location.pathname;
|
||||
const url = `/api/__yao/sui/v1/render${route}`;
|
||||
const payload = { name, data: { ..._data, ...data }, option };
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
Cookie: document.cookie,
|
||||
};
|
||||
|
||||
// Native post request to the server
|
||||
try {
|
||||
const body = JSON.stringify(payload);
|
||||
const response = await fetch(url, { method: "POST", headers, body: body });
|
||||
const text = await response.text();
|
||||
if (!option.replace) {
|
||||
return Promise.resolve(text);
|
||||
}
|
||||
|
||||
// Set the response text to the elements
|
||||
elms.forEach((elm) => {
|
||||
elm.innerHTML = text;
|
||||
__sui_event_init(elm);
|
||||
});
|
||||
|
||||
return Promise.resolve(text);
|
||||
} catch (e) {
|
||||
//Set the error message
|
||||
elms.forEach((elm) => {
|
||||
elm.innerHTML = `<span class="sui-render-error">Failed to render</span>`;
|
||||
console.error("Failed to render", e);
|
||||
});
|
||||
return Promise.reject("Failed to render");
|
||||
}
|
||||
}
|
||||
|
||||
export type Component = {
|
||||
root: HTMLElement;
|
||||
state: ComponentState;
|
||||
store: ComponentStore;
|
||||
watch?: Record<string, (value: any, state?: State) => void>;
|
||||
Constants?: Record<string, any>;
|
||||
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export type RenderOption = {
|
||||
target?: HTMLElement; // default is same with s:render target
|
||||
showLoader?: HTMLElement | string | boolean; // default is false
|
||||
replace?: boolean; // default is true
|
||||
withPageData?: boolean; // default is false
|
||||
};
|
||||
|
||||
export type ComponentState = {
|
||||
Set: (key: string, value: any) => void;
|
||||
};
|
||||
|
||||
export type ComponentStore = {
|
||||
Get: (key: string) => string;
|
||||
Set: (key: string, value: any) => void;
|
||||
GetJSON: (key: string) => any;
|
||||
SetJSON: (key: string, value: any) => void;
|
||||
GetData: () => Record<string, any>;
|
||||
};
|
||||
|
||||
export type State = {
|
||||
target: HTMLElement;
|
||||
stopPropagation();
|
||||
};
|
||||
177
sui/libsui/utils.ts
Normal file
177
sui/libsui/utils.ts
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
function $Store(elm) {
|
||||
if (!elm) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof elm === "string") {
|
||||
elm = document.querySelectorAll(elm);
|
||||
if (elm.length == 0) {
|
||||
return null;
|
||||
}
|
||||
elm = elm[0];
|
||||
}
|
||||
// @ts-ignore
|
||||
return new __sui_store(elm);
|
||||
}
|
||||
|
||||
function $Query(selector: string): __Query {
|
||||
return new __Query(selector);
|
||||
}
|
||||
|
||||
class __Query {
|
||||
selector: string | Element = "";
|
||||
elements: NodeListOf<Element> | null = null;
|
||||
element: Element | null = null;
|
||||
constructor(selector: string | Element) {
|
||||
if (typeof selector === "string") {
|
||||
this.selector = selector;
|
||||
this.elements = document.querySelectorAll(selector);
|
||||
if (this.elements.length > 0) {
|
||||
this.element = this.elements[0];
|
||||
}
|
||||
} else {
|
||||
this.element = selector;
|
||||
}
|
||||
this.selector = selector;
|
||||
}
|
||||
|
||||
elm(): Element | null {
|
||||
return this.element;
|
||||
}
|
||||
|
||||
elms(): NodeListOf<Element> | null {
|
||||
return this.elements;
|
||||
}
|
||||
|
||||
$$() {
|
||||
if (!this.element) {
|
||||
return null;
|
||||
}
|
||||
const root = this.element.closest("[s\\:cn]");
|
||||
if (!root) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
return $$(root);
|
||||
}
|
||||
|
||||
each(callback: (element: __Query, index: number) => void) {
|
||||
if (!this.elements) {
|
||||
return;
|
||||
}
|
||||
this.elements.forEach((element, index) => {
|
||||
callback(new __Query(element), index);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
store() {
|
||||
if (!this.element || typeof this.element.getAttribute !== "function") {
|
||||
return null;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
return new __sui_store(this.element);
|
||||
}
|
||||
|
||||
attr(key) {
|
||||
if (!this.element || typeof this.element.getAttribute !== "function") {
|
||||
return null;
|
||||
}
|
||||
return this.element.getAttribute(key);
|
||||
}
|
||||
|
||||
data(key) {
|
||||
if (!this.element || typeof this.element.getAttribute !== "function") {
|
||||
return null;
|
||||
}
|
||||
return this.element.getAttribute("data:" + key);
|
||||
}
|
||||
|
||||
json(key) {
|
||||
if (!this.element || typeof this.element.getAttribute !== "function") {
|
||||
return null;
|
||||
}
|
||||
const v = this.element.getAttribute("json:" + key);
|
||||
if (!v) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(v);
|
||||
} catch (e) {
|
||||
console.error(`Error parsing JSON for key ${key}: ${e}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
hasClass(className) {
|
||||
return this.element?.classList.contains(className);
|
||||
}
|
||||
|
||||
prop(key) {
|
||||
if (!this.element || typeof this.element.getAttribute !== "function") {
|
||||
return null;
|
||||
}
|
||||
const k = "prop:" + key;
|
||||
const v = this.element.getAttribute(k);
|
||||
const json = this.element.getAttribute("json-attr-prop:" + key) === "true";
|
||||
if (json && v) {
|
||||
try {
|
||||
return JSON.parse(v);
|
||||
} catch (e) {
|
||||
console.error(`Error parsing JSON for prop ${key}: ${e}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
removeClass(className) {
|
||||
const classes = Array.isArray(className) ? className : className.split(" ");
|
||||
classes.forEach((c) => {
|
||||
const v = c.replace(/[\n\r\s]/g, "");
|
||||
if (v === "") return;
|
||||
this.element?.classList.remove(v);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
addClass(className) {
|
||||
const classes = Array.isArray(className) ? className : className.split(" ");
|
||||
classes.forEach((c) => {
|
||||
const v = c.replace(/[\n\r\s]/g, "");
|
||||
if (v === "") return;
|
||||
this.element?.classList.add(v);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
html(html?: string): __Query | string {
|
||||
if (html === undefined) {
|
||||
return this.element?.innerHTML || "";
|
||||
}
|
||||
if (this.element) {
|
||||
this.element.innerHTML = html;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
function $Render(comp, option): __Render {
|
||||
const r = new __Render(comp, option);
|
||||
return r;
|
||||
}
|
||||
|
||||
class __Render {
|
||||
comp = null;
|
||||
option = null;
|
||||
constructor(comp, option) {
|
||||
this.comp = comp;
|
||||
this.option = option;
|
||||
}
|
||||
async Render(name, data): Promise<string> {
|
||||
// @ts-ignore
|
||||
return __sui_render(this.comp, name, data, this.option);
|
||||
}
|
||||
}
|
||||
182
sui/libsui/yao.ts
Normal file
182
sui/libsui/yao.ts
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
/**
|
||||
* YAO Pure JavaScript SDK
|
||||
* @author Max<max@iqka.com>
|
||||
* @maintainer https://yaoapps.com
|
||||
*/
|
||||
|
||||
/**
|
||||
* Yao Object
|
||||
* @param {*} host
|
||||
*/
|
||||
function Yao(host) {
|
||||
this.host = `${
|
||||
host || window.location.protocol + "//" + window.location.host
|
||||
}/api`;
|
||||
this.query = {};
|
||||
new URLSearchParams(window.location.search).forEach((key, value) => {
|
||||
this.query[key] = value;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get API
|
||||
* @param {*} path
|
||||
* @param {*} params
|
||||
*/
|
||||
Yao.prototype.Get = async function (path, params, headers) {
|
||||
return this.Fetch("GET", path, params, null, headers);
|
||||
};
|
||||
|
||||
/**
|
||||
* Post API
|
||||
* @param {*} path
|
||||
* @param {*} data
|
||||
* @param {*} params
|
||||
* @param {*} headers
|
||||
*/
|
||||
Yao.prototype.Post = async function (path, data, params, headers) {
|
||||
return this.Fetch("POST", path, params, data, headers);
|
||||
};
|
||||
|
||||
/**
|
||||
* Download API
|
||||
* @param {*} path
|
||||
* @param {*} params
|
||||
*/
|
||||
Yao.prototype.Download = async function (path, params, savefile, headers) {
|
||||
try {
|
||||
const blob = await this.Fetch("GET", path, params, null, headers, true);
|
||||
|
||||
var objectUrl = window.URL.createObjectURL(blob);
|
||||
let anchor = document.createElement("a");
|
||||
document.body.appendChild(anchor);
|
||||
anchor.href = objectUrl;
|
||||
anchor.download = savefile;
|
||||
anchor.click();
|
||||
window.URL.revokeObjectURL(objectUrl);
|
||||
} catch (err) {
|
||||
alert("成功创建导出任务!");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch API
|
||||
* @param {*} method
|
||||
* @param {*} path
|
||||
* @param {*} params
|
||||
* @param {*} data
|
||||
* @param {*} headers
|
||||
*/
|
||||
Yao.prototype.Fetch = async function (
|
||||
method,
|
||||
path,
|
||||
params,
|
||||
data,
|
||||
headers,
|
||||
isblob
|
||||
) {
|
||||
params = params || {};
|
||||
headers = headers || {};
|
||||
data = data || null;
|
||||
var url = `${this.host}${path}`;
|
||||
var queryString = this.Serialize(params);
|
||||
if (queryString != "") {
|
||||
url = url.includes("?") ? `${url}&${queryString}` : `${url}?${queryString}`;
|
||||
}
|
||||
|
||||
const token = this.Token();
|
||||
if (token != "") {
|
||||
headers["authorization"] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
if (!headers["Content-Type"]) {
|
||||
headers["Content-Type"] = "application/json";
|
||||
}
|
||||
|
||||
var options: any = {
|
||||
method: method,
|
||||
mode: "cors", // no-cors, *cors, same-origin
|
||||
cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
|
||||
credentials: "same-origin", // include, *same-origin, omit
|
||||
headers: headers,
|
||||
redirect: "follow", // manual, *follow, error
|
||||
};
|
||||
|
||||
if (data != null) {
|
||||
options["body"] = JSON.stringify(data);
|
||||
}
|
||||
|
||||
const resp = await fetch(url, options);
|
||||
const type = resp.headers.get("Content-Type") || "";
|
||||
if (type.includes("application/json")) {
|
||||
try {
|
||||
const data = await resp.json();
|
||||
return data;
|
||||
} catch (err) {
|
||||
return { code: resp.status, message: "empty return" };
|
||||
}
|
||||
} else if (isblob) {
|
||||
return resp.blob();
|
||||
} else if (type.includes("text/html") || type.includes("text/plain")) {
|
||||
return resp.text();
|
||||
}
|
||||
return resp.text();
|
||||
};
|
||||
|
||||
/**
|
||||
* Token API
|
||||
* @param {*} path
|
||||
* @param {*} params
|
||||
*/
|
||||
Yao.prototype.Token = function () {
|
||||
var token = sessionStorage.getItem("token") || "";
|
||||
if (token == "") {
|
||||
return this.Cookie("__tk") || "";
|
||||
}
|
||||
return token;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get Cookie
|
||||
* @param {*} cookieName
|
||||
* @returns
|
||||
*/
|
||||
Yao.prototype.Cookie = function (cookieName) {
|
||||
var name = cookieName + "=";
|
||||
var decodedCookie = decodeURIComponent(document.cookie);
|
||||
var cookieArray = decodedCookie.split(";");
|
||||
|
||||
for (var i = 0; i < cookieArray.length; i++) {
|
||||
var cookie = cookieArray[i].trim();
|
||||
if (cookie.indexOf(name) === 0) {
|
||||
return cookie.substring(name.length, cookie.length);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
Yao.prototype.SetCookie = function (cookieName, cookieValue, expireDays) {
|
||||
expireDays = expireDays || 30;
|
||||
var d = new Date();
|
||||
d.setTime(d.getTime() + expireDays * 24 * 60 * 60 * 1000);
|
||||
var expires = "expires=" + d.toUTCString();
|
||||
document.cookie = `${cookieName}=${cookieValue};${expires};path=/`;
|
||||
};
|
||||
|
||||
Yao.prototype.DeleteCookie = function (cookieName) {
|
||||
document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Serialize To Query String
|
||||
* @param {*} obj
|
||||
* @returns
|
||||
*/
|
||||
Yao.prototype.Serialize = function (obj) {
|
||||
const str: string[] = [];
|
||||
for (const p in obj)
|
||||
if (obj.hasOwnProperty(p)) {
|
||||
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
|
||||
}
|
||||
return str.join("&");
|
||||
};
|
||||
|
|
@ -124,6 +124,12 @@ func (tmpl *Template) Build(option *core.BuildOption) ([]string, error) {
|
|||
return warnings, err
|
||||
}
|
||||
|
||||
// Add sui lib to the global
|
||||
err = tmpl.UpdateJSSDK(option)
|
||||
if err != nil {
|
||||
return warnings, err
|
||||
}
|
||||
|
||||
// Execute the build after hook
|
||||
if option.ExecScripts {
|
||||
res := tmpl.ExecAfterBuildScripts()
|
||||
|
|
@ -200,6 +206,44 @@ func (tmpl *Template) SyncAssetFile(file string, option *core.BuildOption) error
|
|||
return copy(sourceFile, targetFile)
|
||||
}
|
||||
|
||||
// UpdateJSSDK update the js sdk
|
||||
func (tmpl *Template) UpdateJSSDK(option *core.BuildOption) error {
|
||||
|
||||
jsCode, sourceMap, err := core.LibSUI()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get source abs path
|
||||
root, err := tmpl.local.DSL.PublicRoot(option.Data)
|
||||
if err != nil {
|
||||
log.Error("SyncAssets: Get the public root error: %s. use %s", err.Error(), tmpl.local.DSL.Public.Root)
|
||||
root = tmpl.local.DSL.Public.Root
|
||||
}
|
||||
|
||||
targetRoot := filepath.Join(application.App.Root(), "public", root, "assets")
|
||||
|
||||
file := filepath.Join(targetRoot, "libsui.min.js")
|
||||
mapFile := filepath.Join(targetRoot, "libsui.min.js.map")
|
||||
|
||||
// create the target directory
|
||||
if exist, _ := os.Stat(targetRoot); exist == nil {
|
||||
os.MkdirAll(targetRoot, os.ModePerm)
|
||||
}
|
||||
|
||||
// write the js sdk
|
||||
// add source map url
|
||||
jsCode = append(jsCode, []byte("\n//# sourceMappingURL=libsui.min.js.map")...)
|
||||
err = os.WriteFile(file, jsCode, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// write the source map
|
||||
err = os.WriteFile(mapFile, sourceMap, 0644)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tmpl *Template) writeGlobalScript(data map[string]interface{}) error {
|
||||
file, source, err := tmpl.backendScriptSource("__global.backend")
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -28,10 +28,10 @@ func TestPageEditorRender(t *testing.T) {
|
|||
|
||||
assert.NotEmpty(t, res.HTML)
|
||||
assert.NotEmpty(t, res.CSS)
|
||||
assert.NotEmpty(t, res.Scripts)
|
||||
// assert.NotEmpty(t, res.Scripts)
|
||||
assert.NotEmpty(t, res.Styles)
|
||||
assert.GreaterOrEqual(t, len(res.Styles), 1)
|
||||
assert.GreaterOrEqual(t, len(res.Scripts), 1)
|
||||
// assert.GreaterOrEqual(t, len(res.Scripts), 1)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue