| |
| |
| |
|
|
| package template |
|
|
| import ( |
| "bytes" |
| "errors" |
| "html/template" |
| "path" |
| "plugin" |
| "strconv" |
| "strings" |
| "sync" |
|
|
| "github.com/GoAdminGroup/go-admin/context" |
| c "github.com/GoAdminGroup/go-admin/modules/config" |
| errors2 "github.com/GoAdminGroup/go-admin/modules/errors" |
| "github.com/GoAdminGroup/go-admin/modules/language" |
| "github.com/GoAdminGroup/go-admin/modules/logger" |
| "github.com/GoAdminGroup/go-admin/modules/menu" |
| "github.com/GoAdminGroup/go-admin/modules/system" |
| "github.com/GoAdminGroup/go-admin/modules/utils" |
| "github.com/GoAdminGroup/go-admin/plugins/admin/models" |
| "github.com/GoAdminGroup/go-admin/template/login" |
| "github.com/GoAdminGroup/go-admin/template/types" |
| "golang.org/x/text/cases" |
| textLang "golang.org/x/text/language" |
| ) |
|
|
| |
| |
| type Template interface { |
| Name() string |
|
|
| |
|
|
| |
| Col() types.ColAttribute |
| Row() types.RowAttribute |
|
|
| |
| Form() types.FormAttribute |
| Table() types.TableAttribute |
| DataTable() types.DataTableAttribute |
|
|
| TreeView() types.TreeViewAttribute |
| Tree() types.TreeAttribute |
| Tabs() types.TabsAttribute |
| Alert() types.AlertAttribute |
| Link() types.LinkAttribute |
|
|
| Paginator() types.PaginatorAttribute |
| Popup() types.PopupAttribute |
| Box() types.BoxAttribute |
|
|
| Label() types.LabelAttribute |
| Image() types.ImgAttribute |
|
|
| Button() types.ButtonAttribute |
|
|
| |
| GetTmplList() map[string]string |
| GetAssetList() []string |
| GetAssetImportHTML(exceptComponents ...string) template.HTML |
| GetAsset(string) ([]byte, error) |
| GetTemplate(bool) (*template.Template, string) |
| GetVersion() string |
| GetRequirements() []string |
| GetHeadHTML() template.HTML |
| GetFootJS() template.HTML |
| Get404HTML() template.HTML |
| Get500HTML() template.HTML |
| Get403HTML() template.HTML |
| } |
|
|
| type PageType uint8 |
|
|
| const ( |
| NormalPage PageType = iota |
| Missing404Page |
| Error500Page |
| NoPermission403Page |
| ) |
|
|
| func GetPageTypeFromPageError(err errors2.PageError) PageType { |
| if err == nil { |
| return NormalPage |
| } else if err == errors2.PageError403 { |
| return NoPermission403Page |
| } else if err == errors2.PageError404 { |
| return Missing404Page |
| } else { |
| return Error500Page |
| } |
| } |
|
|
| const ( |
| CompCol = "col" |
| CompRow = "row" |
| CompForm = "form" |
| CompTable = "table" |
| CompDataTable = "datatable" |
| CompTree = "tree" |
| CompTreeView = "treeview" |
| CompTabs = "tabs" |
| CompAlert = "alert" |
| CompLink = "link" |
| CompPaginator = "paginator" |
| CompPopup = "popup" |
| CompBox = "box" |
| CompLabel = "label" |
| CompImage = "image" |
| CompButton = "button" |
| ) |
|
|
| func HTML(s string) template.HTML { |
| return template.HTML(s) |
| } |
|
|
| func CSS(s string) template.CSS { |
| return template.CSS(s) |
| } |
|
|
| func JS(s string) template.JS { |
| return template.JS(s) |
| } |
|
|
| |
| var templateMap = make(map[string]Template) |
|
|
| |
| |
| func Get(ctx *context.Context, theme string) Template { |
| if ctx != nil { |
| queryTheme := ctx.Theme() |
| if queryTheme != "" { |
| if temp, ok := templateMap[queryTheme]; ok { |
| return temp |
| } |
| } |
| } |
| if temp, ok := templateMap[theme]; ok { |
| return temp |
| } |
| panic("wrong theme name") |
| } |
|
|
| |
| |
| func Default(ctx ...*context.Context) Template { |
| if len(ctx) > 0 && ctx[0] != nil { |
| queryTheme := ctx[0].Theme() |
| if queryTheme != "" { |
| if temp, ok := templateMap[queryTheme]; ok { |
| return temp |
| } |
| } |
| } |
| if temp, ok := templateMap[c.GetTheme()]; ok { |
| return temp |
| } |
| panic("wrong theme name") |
| } |
|
|
| var ( |
| templateMu sync.Mutex |
| compMu sync.Mutex |
| ) |
|
|
| |
| |
| |
| func Add(name string, temp Template) { |
| templateMu.Lock() |
| defer templateMu.Unlock() |
| if temp == nil { |
| panic("template is nil") |
| } |
| if _, dup := templateMap[name]; dup { |
| panic("add template twice " + name) |
| } |
| templateMap[name] = temp |
| } |
|
|
| |
| |
| |
| func CheckRequirements() (bool, bool) { |
| if !CheckThemeRequirements() { |
| return false, true |
| } |
| |
| if !utils.InArray(DefaultThemeNames, Default().Name()) { |
| return true, true |
| } |
| return true, VersionCompare(Default().GetVersion(), system.RequireThemeVersion()[Default().Name()]) |
| } |
|
|
| func CheckThemeRequirements() bool { |
| return VersionCompare(system.Version(), Default().GetRequirements()) |
| } |
|
|
| func VersionCompare(toCompare string, versions []string) bool { |
| for _, v := range versions { |
| if v == toCompare || utils.CompareVersion(v, toCompare) { |
| return true |
| } |
| } |
| return false |
| } |
|
|
| func GetPageContentFromPageType(ctx *context.Context, title, desc, msg string, pt PageType) (template.HTML, template.HTML, template.HTML) { |
| if c.GetDebug() { |
| return template.HTML(title), template.HTML(desc), Default(ctx).Alert().SetTitle(errors2.MsgWithIcon).Warning(msg) |
| } |
|
|
| if pt == Missing404Page { |
| if c.GetCustom404HTML() != template.HTML("") { |
| return "", "", c.GetCustom404HTML() |
| } else { |
| return "", "", Default(ctx).Get404HTML() |
| } |
| } else if pt == NoPermission403Page { |
| if c.GetCustom404HTML() != template.HTML("") { |
| return "", "", c.GetCustom403HTML() |
| } else { |
| return "", "", Default(ctx).Get403HTML() |
| } |
| } else { |
| if c.GetCustom500HTML() != template.HTML("") { |
| return "", "", c.GetCustom500HTML() |
| } else { |
| return "", "", Default(ctx).Get500HTML() |
| } |
| } |
| } |
|
|
| var DefaultThemeNames = []string{"sword", "adminlte"} |
|
|
| func Themes() []string { |
| names := make([]string, len(templateMap)) |
| i := 0 |
| for k := range templateMap { |
| names[i] = k |
| i++ |
| } |
| return names |
| } |
|
|
| func AddFromPlugin(name string, mod string) { |
|
|
| plug, err := plugin.Open(mod) |
| if err != nil { |
| logger.Error("AddFromPlugin err", err) |
| panic(err) |
| } |
|
|
| tempPlugin, err := plug.Lookup(cases.Title(textLang.Und).String(name)) |
| if err != nil { |
| logger.Error("AddFromPlugin err", err) |
| panic(err) |
| } |
|
|
| var temp Template |
| temp, ok := tempPlugin.(Template) |
| if !ok { |
| logger.Error("AddFromPlugin err: unexpected type from module symbol") |
| panic(errors.New("AddFromPlugin err: unexpected type from module symbol")) |
| } |
|
|
| Add(name, temp) |
| } |
|
|
| |
| type Component interface { |
| |
| GetTemplate() (*template.Template, string) |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| GetAssetList() []string |
|
|
| |
| |
| |
| |
| GetAsset(string) ([]byte, error) |
|
|
| GetContent() template.HTML |
|
|
| IsAPage() bool |
|
|
| GetName() string |
|
|
| GetJS() template.JS |
| GetCSS() template.CSS |
| GetCallbacks() types.Callbacks |
| } |
|
|
| var compMap = map[string]Component{ |
| "login": login.GetLoginComponent(), |
| } |
|
|
| |
| |
| func GetComp(name string) Component { |
| if comp, ok := compMap[name]; ok { |
| return comp |
| } |
| panic("wrong component name") |
| } |
|
|
| func GetComponentAsset() []string { |
| assets := make([]string, 0) |
| for _, comp := range compMap { |
| assets = append(assets, comp.GetAssetList()...) |
| } |
| return assets |
| } |
|
|
| func GetComponentAssetWithinPage() []string { |
| assets := make([]string, 0) |
| for _, comp := range compMap { |
| if !comp.IsAPage() { |
| assets = append(assets, comp.GetAssetList()...) |
| } |
| } |
| return assets |
| } |
|
|
| func GetComponentAssetImportHTML(ctx *context.Context) (res template.HTML) { |
| res = Default(ctx).GetAssetImportHTML(c.GetExcludeThemeComponents()...) |
| assets := GetComponentAssetWithinPage() |
| for i := 0; i < len(assets); i++ { |
| res += getHTMLFromAssetUrl(assets[i]) |
| } |
| return |
| } |
|
|
| func getHTMLFromAssetUrl(s string) template.HTML { |
| switch path.Ext(s) { |
| case ".css": |
| return template.HTML(`<link rel="stylesheet" href="` + c.GetAssetUrl() + c.Url("/assets"+s) + `">`) |
| case ".js": |
| return template.HTML(`<script src="` + c.GetAssetUrl() + c.Url("/assets"+s) + `"></script>`) |
| default: |
| return "" |
| } |
| } |
|
|
| func GetAsset(path string) ([]byte, error) { |
| for _, comp := range compMap { |
| res, err := comp.GetAsset(path) |
| if err == nil { |
| return res, nil |
| } |
| } |
| return nil, errors.New(path + " not found") |
| } |
|
|
| |
| |
| |
| func AddComp(comp Component) { |
| compMu.Lock() |
| defer compMu.Unlock() |
| if comp == nil { |
| panic("component is nil") |
| } |
| if _, dup := compMap[comp.GetName()]; dup { |
| panic("add component twice " + comp.GetName()) |
| } |
| compMap[comp.GetName()] = comp |
| } |
|
|
| |
| func AddLoginComp(comp Component) { |
| compMu.Lock() |
| defer compMu.Unlock() |
| compMap["login"] = comp |
| } |
|
|
| |
| |
| |
| func SetComp(name string, comp Component) { |
| compMu.Lock() |
| defer compMu.Unlock() |
| if comp == nil { |
| panic("component is nil") |
| } |
| if _, dup := compMap[name]; dup { |
| compMap[name] = comp |
| } |
| } |
|
|
| type ExecuteParam struct { |
| User models.UserModel |
| Tmpl *template.Template |
| TmplName string |
| IsPjax bool |
| Panel types.Panel |
| Logo template.HTML |
| Config *c.Config |
| Menu *menu.Menu |
| Animation bool |
| Buttons types.Buttons |
| NoCompress bool |
| Iframe bool |
| } |
|
|
| func updateNavAndLogoJS(logo template.HTML) template.JS { |
| if logo == template.HTML("") { |
| return "" |
| } |
| return `$(function () { |
| $(".logo-lg").html("` + template.JS(logo) + `"); |
| });` |
| } |
|
|
| func updateNavJS(isPjax bool) template.JS { |
| if !isPjax { |
| return "" |
| } |
| return `$(function () { |
| let lis = $(".user-menu .dropdown-menu li"); |
| for (i = 0; i < lis.length - 2; i++) { |
| $(lis[i]).remove(); |
| } |
| $(".user-menu .dropdown-menu").prepend($("#navbar-nav-custom").html()); |
| });` |
| } |
|
|
| type ExecuteOptions struct { |
| Animation bool |
| NoCompress bool |
| HideSideBar bool |
| HideHeader bool |
| UpdateMenu bool |
| NavDropDownButton []*types.NavDropDownItemButton |
| } |
|
|
| func GetExecuteOptions(options []ExecuteOptions) ExecuteOptions { |
| if len(options) == 0 { |
| return ExecuteOptions{Animation: true} |
| } |
| return options[0] |
| } |
|
|
| func Execute(ctx *context.Context, param *ExecuteParam) *bytes.Buffer { |
|
|
| buf := new(bytes.Buffer) |
| err := param.Tmpl.ExecuteTemplate(buf, param.TmplName, |
| types.NewPage(ctx, &types.NewPageParam{ |
| User: param.User, |
| Menu: param.Menu, |
| Assets: GetComponentAssetImportHTML(ctx), |
| Buttons: param.Buttons, |
| Iframe: param.Iframe, |
| UpdateMenu: param.IsPjax, |
| Panel: param.Panel. |
| GetContent(append([]bool{param.Config.IsProductionEnvironment() && !param.NoCompress}, |
| param.Animation)...).AddJS(param.Menu.GetUpdateJS(param.IsPjax)). |
| AddJS(updateNavAndLogoJS(param.Logo)).AddJS(updateNavJS(param.IsPjax)), |
| TmplHeadHTML: Default(ctx).GetHeadHTML(), |
| TmplFootJS: Default(ctx).GetFootJS(), |
| Logo: param.Logo, |
| })) |
| if err != nil { |
| logger.Error("template execute error", err) |
| } |
| return buf |
| } |
|
|
| func WarningPanel(ctx *context.Context, msg string, pts ...PageType) types.Panel { |
| pt := Error500Page |
| if len(pts) > 0 { |
| pt = pts[0] |
| } |
| pageTitle, description, content := GetPageContentFromPageType(ctx, msg, msg, msg, pt) |
| return types.Panel{ |
| Content: content, |
| Description: description, |
| Title: pageTitle, |
| } |
| } |
|
|
| func WarningPanelWithDescAndTitle(ctx *context.Context, msg, desc, title string, pts ...PageType) types.Panel { |
| pt := Error500Page |
| if len(pts) > 0 { |
| pt = pts[0] |
| } |
| pageTitle, description, content := GetPageContentFromPageType(ctx, msg, desc, title, pt) |
| return types.Panel{ |
| Content: content, |
| Description: description, |
| Title: pageTitle, |
| } |
| } |
|
|
| var DefaultFuncMap = template.FuncMap{ |
| "lang": language.Get, |
| "langHtml": language.GetFromHtml, |
| "link": func(cdnUrl, prefixUrl, assetsUrl string) string { |
| if cdnUrl == "" { |
| return prefixUrl + assetsUrl |
| } |
| return cdnUrl + assetsUrl |
| }, |
| "isLinkUrl": func(s string) bool { |
| return (len(s) > 7 && s[:7] == "http://") || (len(s) > 8 && s[:8] == "https://") |
| }, |
| "render": func(s, old, repl template.HTML) template.HTML { |
| return template.HTML(strings.ReplaceAll(string(s), string(old), string(repl))) |
| }, |
| "renderJS": func(s template.JS, old, repl template.HTML) template.JS { |
| return template.JS(strings.ReplaceAll(string(s), string(old), string(repl))) |
| }, |
| "divide": func(a, b int) int { |
| return a / b |
| }, |
| "renderRowDataHTML": func(id, content template.HTML, value ...map[string]types.InfoItem) template.HTML { |
| return template.HTML(types.ParseTableDataTmplWithID(id, string(content), value...)) |
| }, |
| "renderRowDataJS": func(id template.HTML, content template.JS, value ...map[string]types.InfoItem) template.JS { |
| return template.JS(types.ParseTableDataTmplWithID(id, string(content), value...)) |
| }, |
| "attr": func(s template.HTML) template.HTMLAttr { |
| return template.HTMLAttr(s) |
| }, |
| "js": func(s interface{}) template.JS { |
| if ss, ok := s.(string); ok { |
| return template.JS(ss) |
| } |
| if ss, ok := s.(template.HTML); ok { |
| return template.JS(ss) |
| } |
| return "" |
| }, |
| "changeValue": func(f types.FormField, index int) types.FormField { |
| if len(f.ValueArr) > 0 { |
| f.Value = template.HTML(f.ValueArr[index]) |
| } |
| if len(f.OptionsArr) > 0 { |
| f.Options = f.OptionsArr[index] |
| } |
| if f.FormType.IsSelect() { |
| f.FieldClass += "_" + strconv.Itoa(index) |
| } |
| return f |
| }, |
| } |
|
|
| type BaseComponent struct { |
| Name string |
| HTMLData string |
| CSS template.CSS |
| JS template.JS |
| Callbacks types.Callbacks |
| } |
|
|
| func (b *BaseComponent) IsAPage() bool { return false } |
| func (b *BaseComponent) GetName() string { return b.Name } |
| func (b *BaseComponent) GetAssetList() []string { return make([]string, 0) } |
| func (b *BaseComponent) GetAsset(name string) ([]byte, error) { return nil, nil } |
| func (b *BaseComponent) GetJS() template.JS { return b.JS } |
| func (b *BaseComponent) GetCSS() template.CSS { return b.CSS } |
| func (b *BaseComponent) GetCallbacks() types.Callbacks { return b.Callbacks } |
| func (b *BaseComponent) BindActionTo(ctx *context.Context, action types.Action, id string) { |
| action.SetBtnId(id) |
| b.JS += action.Js() |
| b.HTMLData += string(action.ExtContent(ctx)) |
| b.Callbacks = append(b.Callbacks, action.GetCallbacks()) |
| } |
| func (b *BaseComponent) GetContentWithData(obj interface{}) template.HTML { |
| buffer := new(bytes.Buffer) |
| tmpl, defineName := b.GetTemplate() |
| err := tmpl.ExecuteTemplate(buffer, defineName, obj) |
| if err != nil { |
| logger.Error(b.Name+" GetContent error:", err) |
| } |
| return template.HTML(buffer.String()) |
| } |
|
|
| func (b *BaseComponent) GetTemplate() (*template.Template, string) { |
| tmpl, err := template.New(b.Name). |
| Funcs(DefaultFuncMap). |
| Parse(b.HTMLData) |
|
|
| if err != nil { |
| logger.Error(b.Name+" GetTemplate Error: ", err) |
| } |
|
|
| return tmpl, b.Name |
| } |
|
|