mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01:00
Ability to register a view engine per group of routes or for the current a chain of handlers
Example at: https://github.com/kataras/iris/tree/master/_examples/view/context-view-engine
This commit is contained in:
parent
b363492cca
commit
5d480dc801
|
@ -359,6 +359,8 @@ Response:
|
|||
|
||||
Other Improvements:
|
||||
|
||||
- Ability to register a view engine per group of routes or for the current chain of handlers through `Party.RegisterView` and `Context.ViewEngine` respectfully.
|
||||
|
||||
- Add [Blocks](_examples/view/template_blocks_0) template engine. <!-- Reminder for @kataras: follow https://github.com/flosch/pongo2/pull/236#issuecomment-668950566 discussion so we can get back on using the original pongo2 repository as they fixed the issue about an incompatible 3rd party package (although they need more fixes, that's why I commented there) -->
|
||||
|
||||
- Add [Ace](_examples/view/template_ace_0) template parser to the view engine and other minor improvements.
|
||||
|
@ -492,8 +494,9 @@ New Package-level Variables:
|
|||
|
||||
New Context Methods:
|
||||
|
||||
- `Context.SetErr(error)` and `Context.GetErr() error` helpers
|
||||
- `Context.CompressWriter(bool) error` and `Context.CompressReader(bool) error`
|
||||
- `Context.ViewEngine(ViewEngine)` to set a view engine on-fly for the current chain of handlers, responsible to render templates through `ctx.View`. [Example](_examples/view/context-view-engine).
|
||||
- `Context.SetErr(error)` and `Context.GetErr() error` helpers.
|
||||
- `Context.CompressWriter(bool) error` and `Context.CompressReader(bool) error`.
|
||||
- `Context.Clone() Context` returns a copy of the Context.
|
||||
- `Context.IsCanceled() bool` reports whether the request has been canceled by the client.
|
||||
- `Context.IsSSL() bool` reports whether the request is under HTTPS SSL (New `Configuration.SSLProxyHeaders` and `HostProxyHeaders` fields too).
|
||||
|
|
65
_examples/view/context-view-engine/main.go
Normal file
65
_examples/view/context-view-engine/main.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package main
|
||||
|
||||
import "github.com/kataras/iris/v12"
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
// Register a root view engine, as usual,
|
||||
// will be used to render files through Context.View method
|
||||
// when no Party or Handler-specific view engine is available.
|
||||
app.RegisterView(iris.Blocks("./views/public", ".html"))
|
||||
|
||||
// http://localhost:8080
|
||||
app.Get("/", index)
|
||||
|
||||
// Register a view engine per group of routes.
|
||||
adminGroup := app.Party("/admin")
|
||||
adminGroup.RegisterView(iris.Blocks("./views/admin", ".html"))
|
||||
|
||||
// http://localhost:8080/admin
|
||||
adminGroup.Get("/", admin)
|
||||
|
||||
// Register a view engine on-fly for the current chain of handlers.
|
||||
views := iris.Blocks("./views/on-fly", ".html")
|
||||
if err := views.Load(); err != nil {
|
||||
app.Logger().Fatal(err)
|
||||
}
|
||||
|
||||
// http://localhost:8080/on-fly
|
||||
app.Get("/on-fly", setViews(views), onFly)
|
||||
|
||||
app.Listen(":8080")
|
||||
}
|
||||
|
||||
func index(ctx iris.Context) {
|
||||
data := iris.Map{
|
||||
"Title": "Public Index Title",
|
||||
}
|
||||
|
||||
ctx.ViewLayout("main")
|
||||
ctx.View("index", data)
|
||||
}
|
||||
|
||||
func admin(ctx iris.Context) {
|
||||
data := iris.Map{
|
||||
"Title": "Admin Panel",
|
||||
}
|
||||
|
||||
ctx.ViewLayout("main")
|
||||
ctx.View("index", data)
|
||||
}
|
||||
|
||||
func setViews(views iris.ViewEngine) iris.Handler {
|
||||
return func(ctx iris.Context) {
|
||||
ctx.ViewEngine(views)
|
||||
ctx.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func onFly(ctx iris.Context) {
|
||||
data := iris.Map{
|
||||
"Message": "View engine changed through 'setViews' custom middleware.",
|
||||
}
|
||||
|
||||
ctx.View("index", data)
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{{ define "content" }}
|
||||
<h1>Hello, Admin!</h1>
|
||||
{{ end }}
|
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ .Title }}</title>
|
||||
</head>
|
||||
<body>
|
||||
{{ template "content" .}}
|
||||
|
||||
|
||||
<h4>Copyright © 2020 Admin</h4>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,2 @@
|
|||
<h1>On-fly</h1>
|
||||
<h3>{{.Message}}</h3>
|
12
_examples/view/context-view-engine/views/public/500.html
Normal file
12
_examples/view/context-view-engine/views/public/500.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
<!-- You can define more than one block.
|
||||
The default one is "content" which should be the main template's body.
|
||||
So, even if it's missing (see index.html), it's added automatically by the view engine.
|
||||
When you need to define more than one block, you have to be more specific:
|
||||
-->
|
||||
{{ define "content" }}
|
||||
<h1>Internal Server Error</h1>
|
||||
{{ end }}
|
||||
|
||||
{{ define "message" }}
|
||||
<p style="color:red;">{{.Message}}</p>
|
||||
{{ end }}
|
|
@ -0,0 +1 @@
|
|||
<h1>Index Body</h1>
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{.Code}}</title>
|
||||
</head>
|
||||
<body>
|
||||
{{ template "content" .}}
|
||||
|
||||
{{block "message" .}}{{end}}
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ if .Title }}{{ .Title }}{{ else }}Default Main Title{{ end }}</title>
|
||||
</head>
|
||||
<body>
|
||||
{{ template "content" . }}
|
||||
|
||||
<footer>{{ partial "partials/footer" .}}</footer>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1 @@
|
|||
<h3>Footer Partial</h3>
|
|
@ -23,6 +23,9 @@ type (
|
|||
// Developers send responses to the client's request through a Context.
|
||||
// Developers get request information from the client's request by a Context.
|
||||
Context = *context.Context
|
||||
// ViewEngine is an alias of `context.ViewEngine`.
|
||||
// See HTML, Blocks, Django, Jet, Pug, Ace, Handlebars, Amber and e.t.c.
|
||||
ViewEngine = context.ViewEngine
|
||||
// UnmarshalerFunc a shortcut, an alias for the `context#UnmarshalerFunc` type
|
||||
// which implements the `context#Unmarshaler` interface for reading request's body
|
||||
// via custom decoders, most of them already implement the `context#UnmarshalerFunc`
|
||||
|
|
|
@ -756,18 +756,30 @@ type Configuration struct {
|
|||
// via a middleware through `SetVersion` method, e.g. `versioning.SetVersion(ctx, "1.0, 1.1")`.
|
||||
// Defaults to "iris.api.version".
|
||||
VersionContextKey string `json:"versionContextKey" yaml:"VersionContextKey" toml:"VersionContextKey"`
|
||||
// GetViewLayoutContextKey is the key of the context's user values' key
|
||||
// which is being used to set the template
|
||||
// layout from a middleware or the main handler.
|
||||
// Overrides the parent's or the configuration's.
|
||||
// ViewEngineContextKey is the context's values key
|
||||
// responsible to store and retrieve(view.Engine) the current view engine.
|
||||
// A middleware or a Party can modify its associated value to change
|
||||
// a view engine that `ctx.View` will render through.
|
||||
// If not an engine is registered by the end-developer
|
||||
// then its associated value is always nil,
|
||||
// meaning that the default value is nil.
|
||||
// See `Party.RegisterView` and `Context.ViewEngine` methods as well.
|
||||
//
|
||||
// Defaults to "iris.ViewLayout"
|
||||
// Defaults to "iris.view.engine".
|
||||
ViewEngineContextKey string `json:"viewEngineContextKey,omitempty" yaml:"ViewEngineContextKey" toml:"ViewEngineContextKey"`
|
||||
// ViewLayoutContextKey is the context's values key
|
||||
// responsible to store and retrieve(string) the current view layout.
|
||||
// A middleware can modify its associated value to change
|
||||
// the layout that `ctx.View` will use to render a template.
|
||||
//
|
||||
// Defaults to "iris.view.layout".
|
||||
ViewLayoutContextKey string `json:"viewLayoutContextKey,omitempty" yaml:"ViewLayoutContextKey" toml:"ViewLayoutContextKey"`
|
||||
// GetViewDataContextKey is the key of the context's user values' key
|
||||
// which is being used to set the template
|
||||
// binding data from a middleware or the main handler.
|
||||
// ViewDataContextKey is the context's values key
|
||||
// responsible to store and retrieve(interface{}) the current view binding data.
|
||||
// A middleware can modify its associated value to change
|
||||
// the template's data on-fly.
|
||||
//
|
||||
// Defaults to "iris.viewData"
|
||||
// Defaults to "iris.view.data".
|
||||
ViewDataContextKey string `json:"viewDataContextKey,omitempty" yaml:"ViewDataContextKey" toml:"ViewDataContextKey"`
|
||||
// RemoteAddrHeaders are the allowed request headers names
|
||||
// that can be valid to parse the client's IP based on.
|
||||
|
@ -945,6 +957,11 @@ func (c Configuration) GetVersionContextKey() string {
|
|||
return c.VersionContextKey
|
||||
}
|
||||
|
||||
// GetViewEngineContextKey returns the ViewEngineContextKey field.
|
||||
func (c Configuration) GetViewEngineContextKey() string {
|
||||
return c.ViewEngineContextKey
|
||||
}
|
||||
|
||||
// GetViewLayoutContextKey returns the ViewLayoutContextKey field.
|
||||
func (c Configuration) GetViewLayoutContextKey() string {
|
||||
return c.ViewLayoutContextKey
|
||||
|
@ -1094,10 +1111,12 @@ func WithConfiguration(c Configuration) Configurator {
|
|||
main.VersionContextKey = v
|
||||
}
|
||||
|
||||
if v := c.ViewEngineContextKey; v != "" {
|
||||
main.ViewEngineContextKey = v
|
||||
}
|
||||
if v := c.ViewLayoutContextKey; v != "" {
|
||||
main.ViewLayoutContextKey = v
|
||||
}
|
||||
|
||||
if v := c.ViewDataContextKey; v != "" {
|
||||
main.ViewDataContextKey = v
|
||||
}
|
||||
|
@ -1169,8 +1188,9 @@ func DefaultConfiguration() Configuration {
|
|||
LocaleContextKey: "iris.locale",
|
||||
LanguageContextKey: "iris.locale.language",
|
||||
VersionContextKey: "iris.api.version",
|
||||
ViewLayoutContextKey: "iris.viewLayout",
|
||||
ViewDataContextKey: "iris.viewData",
|
||||
ViewEngineContextKey: "iris.view.engine",
|
||||
ViewLayoutContextKey: "iris.view.layout",
|
||||
ViewDataContextKey: "iris.view.data",
|
||||
RemoteAddrHeaders: nil,
|
||||
RemoteAddrHeadersForce: false,
|
||||
RemoteAddrPrivateSubnets: []netutil.IPRange{
|
||||
|
|
|
@ -51,6 +51,9 @@ type ConfigurationReadOnly interface {
|
|||
GetLanguageContextKey() string
|
||||
// GetVersionContextKey returns the VersionContextKey field.
|
||||
GetVersionContextKey() string
|
||||
|
||||
// GetViewEngineContextKey returns the ViewEngineContextKey field.
|
||||
GetViewEngineContextKey() string
|
||||
// GetViewLayoutContextKey returns the ViewLayoutContextKey field.
|
||||
GetViewLayoutContextKey() string
|
||||
// GetViewDataContextKey returns the ViewDataContextKey field.
|
||||
|
|
|
@ -2392,16 +2392,22 @@ func (ctx *Context) CompressReader(enable bool) error {
|
|||
// | Rich Body Content Writers/Renderers |
|
||||
// +------------------------------------------------------------+
|
||||
|
||||
const (
|
||||
// NoLayout to disable layout for a particular template file
|
||||
NoLayout = "iris.nolayout"
|
||||
)
|
||||
// ViewEngine registers a view engine for the current chain of handlers.
|
||||
// It overrides any previously registered engines, including the application's root ones.
|
||||
// Note that, because performance is everything,
|
||||
// the "engine" MUST be already ready-to-use,
|
||||
// meaning that its `Load` method should be called once before this method call.
|
||||
//
|
||||
// To register a view engine per-group of groups too see `Party.RegisterView` instead.
|
||||
func (ctx *Context) ViewEngine(engine ViewEngine) {
|
||||
ctx.values.Set(ctx.app.ConfigurationReadOnly().GetViewEngineContextKey(), engine)
|
||||
}
|
||||
|
||||
// ViewLayout sets the "layout" option if and when .View
|
||||
// is being called afterwards, in the same request.
|
||||
// Useful when need to set or/and change a layout based on the previous handlers in the chain.
|
||||
//
|
||||
// Note that the 'layoutTmplFile' argument can be set to iris.NoLayout || view.NoLayout || context.NoLayout
|
||||
// Note that the 'layoutTmplFile' argument can be set to iris.NoLayout
|
||||
// to disable the layout for a specific view render action,
|
||||
// it disables the engine's configuration's layout property.
|
||||
//
|
||||
|
@ -2418,7 +2424,7 @@ func (ctx *Context) ViewLayout(layoutTmplFile string) {
|
|||
//
|
||||
// If .View's "binding" argument is not nil and it's not a type of map
|
||||
// then these data are being ignored, binding has the priority, so the main route's handler can still decide.
|
||||
// If binding is a map or context.Map then these data are being added to the view data
|
||||
// If binding is a map or iris.Map then these data are being added to the view data
|
||||
// and passed to the template.
|
||||
//
|
||||
// After .View, the data are not destroyed, in order to be re-used if needed (again, in the same request as everything else),
|
||||
|
@ -2457,7 +2463,7 @@ func (ctx *Context) ViewData(key string, value interface{}) {
|
|||
// A check for nil is always a good practise if different
|
||||
// kind of values or no data are registered via `ViewData`.
|
||||
//
|
||||
// Similarly to `viewData := ctx.Values().Get("iris.viewData")` or
|
||||
// Similarly to `viewData := ctx.Values().Get("iris.view.data")` or
|
||||
// `viewData := ctx.Values().Get(ctx.Application().ConfigurationReadOnly().GetViewDataContextKey())`.
|
||||
func (ctx *Context) GetViewData() map[string]interface{} {
|
||||
viewDataContextKey := ctx.app.ConfigurationReadOnly().GetViewDataContextKey()
|
||||
|
@ -2514,7 +2520,21 @@ func (ctx *Context) View(filename string, optionalViewModel ...interface{}) erro
|
|||
bindingData = ctx.values.Get(cfg.GetViewDataContextKey())
|
||||
}
|
||||
|
||||
err := ctx.app.View(ctx, filename, layout, bindingData)
|
||||
if key := cfg.GetViewEngineContextKey(); key != "" {
|
||||
if engineV := ctx.values.Get(key); engineV != nil {
|
||||
if engine, ok := engineV.(ViewEngine); ok {
|
||||
err := engine.ExecuteWriter(ctx, filename, layout, bindingData)
|
||||
if err != nil {
|
||||
ctx.app.Logger().Errorf("View [%v] [%T]: %v", ctx.getLogIdentifier(), engine, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := ctx.app.View(ctx, filename, layout, bindingData) // if failed it logs the error.
|
||||
if err != nil {
|
||||
ctx.StopWithStatus(http.StatusInternalServerError)
|
||||
}
|
||||
|
@ -2522,6 +2542,16 @@ func (ctx *Context) View(filename string, optionalViewModel ...interface{}) erro
|
|||
return err
|
||||
}
|
||||
|
||||
// getLogIdentifier returns the ID, or the client remote IP address,
|
||||
// useful for internal logging of context's method failure.
|
||||
func (ctx *Context) getLogIdentifier() interface{} {
|
||||
if id := ctx.GetID(); id != nil {
|
||||
return id
|
||||
}
|
||||
|
||||
return ctx.RemoteAddr()
|
||||
}
|
||||
|
||||
const (
|
||||
// ContentBinaryHeaderValue header value for binary data.
|
||||
ContentBinaryHeaderValue = "application/octet-stream"
|
||||
|
|
22
context/view.go
Normal file
22
context/view.go
Normal file
|
@ -0,0 +1,22 @@
|
|||
package context
|
||||
|
||||
import "io"
|
||||
|
||||
// ViewEngine is the interface which all view engines should be implemented in order to be registered inside iris.
|
||||
type ViewEngine interface {
|
||||
// Load should load the templates from a physical system directory or by an embedded one (assets/go-bindata).
|
||||
Load() error
|
||||
// ExecuteWriter should execute a template by its filename with an optional layout and bindingData.
|
||||
ExecuteWriter(w io.Writer, filename string, layout string, bindingData interface{}) error
|
||||
// Ext should return the final file extension which this view engine is responsible to render.
|
||||
Ext() string
|
||||
}
|
||||
|
||||
// ViewEngineFuncer is an addition of a view engine,
|
||||
// if a view engine implements that interface
|
||||
// then iris can add some closed-relative iris functions
|
||||
// like {{ url }}, {{ urlpath }} and {{ tr }}.
|
||||
type ViewEngineFuncer interface {
|
||||
// AddFunc should adds a function to the template's function map.
|
||||
AddFunc(funcName string, funcBody interface{})
|
||||
}
|
|
@ -1022,6 +1022,24 @@ func (api *APIBuilder) OnAnyErrorCode(handlers ...context.Handler) (routes []*Ro
|
|||
return
|
||||
}
|
||||
|
||||
// RegisterView registers and loads a view engine middleware for that group of routes.
|
||||
// It overrides any of the application's root registered view engines.
|
||||
// To register a view engine per handler chain see the `Context.ViewEngine` instead.
|
||||
// Read `Configuration.ViewEngineContextKey` documentation for more.
|
||||
func (api *APIBuilder) RegisterView(viewEngine context.ViewEngine) {
|
||||
if err := viewEngine.Load(); err != nil {
|
||||
api.errors.Add(err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Use(func(ctx *context.Context) {
|
||||
ctx.ViewEngine(viewEngine)
|
||||
ctx.Next()
|
||||
})
|
||||
// Note (@kataras): It does not return the Party in order
|
||||
// to keep the iris.Application a compatible Party.
|
||||
}
|
||||
|
||||
// Layout overrides the parent template layout with a more specific layout for this Party.
|
||||
// It returns the current Party.
|
||||
//
|
||||
|
|
|
@ -241,6 +241,11 @@ type Party interface {
|
|||
// Returns the GET *Route.
|
||||
Favicon(favPath string, requestPath ...string) *Route
|
||||
|
||||
// RegisterView registers and loads a view engine middleware for that group of routes.
|
||||
// It overrides any of the application's root registered view engines.
|
||||
// To register a view engine per handler chain see the `Context.ViewEngine` instead.
|
||||
// Read `Configuration.ViewEngineContextKey` documentation for more.
|
||||
RegisterView(viewEngine context.ViewEngine)
|
||||
// Layout overrides the parent template layout with a more specific layout for this Party.
|
||||
// It returns the current Party.
|
||||
//
|
||||
|
|
2
iris.go
2
iris.go
|
@ -280,7 +280,7 @@ func (app *Application) View(writer io.Writer, filename string, layout string, b
|
|||
}
|
||||
|
||||
// ConfigureHost accepts one or more `host#Configuration`, these configurators functions
|
||||
// can access the host created by `app.Run`,
|
||||
// can access the host created by `app.Run` or `app.Listen`,
|
||||
// they're being executed when application is ready to being served to the public.
|
||||
//
|
||||
// It's an alternative way to interact with a host that is automatically created by
|
||||
|
|
|
@ -21,10 +21,11 @@ func Ace(directory, extension string) *HTMLEngine {
|
|||
once := new(sync.Once)
|
||||
s.middleware = func(name string, text []byte) (contents string, err error) {
|
||||
once.Do(func() { // on first template parse, all funcs are given.
|
||||
for k, v := range s.funcs {
|
||||
for k, v := range emptyFuncs {
|
||||
funcs[k] = v
|
||||
}
|
||||
for k, v := range emptyFuncs {
|
||||
|
||||
for k, v := range s.funcs {
|
||||
funcs[k] = v
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
package view
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// NoLayout disables the configuration's layout for a specific execution.
|
||||
const NoLayout = "iris.nolayout"
|
||||
|
||||
// returns empty if it's no layout or empty layout and empty configuration's layout.
|
||||
func getLayout(layout string, globalLayout string) string {
|
||||
if layout == NoLayout {
|
||||
return ""
|
||||
}
|
||||
|
||||
if layout == "" && globalLayout != "" {
|
||||
return globalLayout
|
||||
}
|
||||
|
||||
return layout
|
||||
}
|
||||
|
||||
// Engine is the interface which all view engines should be implemented in order to be registered inside iris.
|
||||
type Engine interface {
|
||||
// Load should load the templates from a physical system directory or by an embedded one (assets/go-bindata).
|
||||
Load() error
|
||||
// ExecuteWriter should execute a template by its filename with an optional layout and bindingData.
|
||||
ExecuteWriter(w io.Writer, filename string, layout string, bindingData interface{}) error
|
||||
// Ext should return the final file extension which this view engine is responsible to render.
|
||||
Ext() string
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
package view
|
||||
|
||||
// EngineFuncer is an addition of a view engine,
|
||||
// if a view engine implements that interface
|
||||
// then iris can add some closed-relative iris functions
|
||||
// like {{ urlpath }} and {{ urlpath }}.
|
||||
type EngineFuncer interface {
|
||||
// AddFunc should adds a function to the template's function map.
|
||||
AddFunc(funcName string, funcBody interface{})
|
||||
}
|
28
view/view.go
28
view/view.go
|
@ -5,6 +5,18 @@ import (
|
|||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/kataras/iris/v12/context"
|
||||
)
|
||||
|
||||
type (
|
||||
// Engine is the interface for a compatible Iris view engine.
|
||||
// It's an alias of context.ViewEngine.
|
||||
Engine = context.ViewEngine
|
||||
// EngineFuncer is the interface for a compatible Iris view engine
|
||||
// which accepts builtin framework functions such as url, urlpath and tr.
|
||||
// It's an alias of context.ViewEngineFuncer.
|
||||
EngineFuncer = context.ViewEngineFuncer
|
||||
)
|
||||
|
||||
// View is responsible to
|
||||
|
@ -73,3 +85,19 @@ func (v *View) Load() error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NoLayout disables the configuration's layout for a specific execution.
|
||||
const NoLayout = "iris.nolayout"
|
||||
|
||||
// returns empty if it's no layout or empty layout and empty configuration's layout.
|
||||
func getLayout(layout string, globalLayout string) string {
|
||||
if layout == NoLayout {
|
||||
return ""
|
||||
}
|
||||
|
||||
if layout == "" && globalLayout != "" {
|
||||
return globalLayout
|
||||
}
|
||||
|
||||
return layout
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user