Merge branch 'feature/context' into develop
/ docker (push) Successful in 1m8s
Details
/ docker (push) Successful in 1m8s
Details
This commit is contained in:
commit
8a8372bdf0
|
@ -82,11 +82,10 @@ Any static files can be hosted and published in two ways:
|
||||||
* In `$HOME_STATIC_DIR` as publicly available static, "as is" without any restrictions, in the same path in which the file is located in the directory;
|
* In `$HOME_STATIC_DIR` as publicly available static, "as is" without any restrictions, in the same path in which the file is located in the directory;
|
||||||
* In `$HOME_CONTENT_DIR` next to the specific page: the file will be available in the `.Site.Resources` and `.Page.Resources` collections and restricted by the access settings of the parent page;
|
* In `$HOME_CONTENT_DIR` next to the specific page: the file will be available in the `.Site.Resources` and `.Page.Resources` collections and restricted by the access settings of the parent page;
|
||||||
|
|
||||||
Templates are required to render page content in browsers and must be placed in `$HOME_THEME_DIR`. At least one template with a `baseof` block that will be called for each page is required, for example:
|
Templates are required to render page content in browsers and must be placed in `$HOME_THEME_DIR`. At least one template `baseof.html` that will be called for each page is required, for example:
|
||||||
```html
|
```html
|
||||||
<!-- theme/baseof.html -->
|
<!-- theme/baseof.html -->
|
||||||
|
|
||||||
{{ define "baseof" }}
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{{ or .Page.Language.Lang .Site.Language.Lang }}"
|
<html lang="{{ or .Page.Language.Lang .Site.Language.Lang }}"
|
||||||
dir="{{ or .Page.Language.Dir .Site.Language.Dir }}">
|
dir="{{ or .Page.Language.Dir .Site.Language.Dir }}">
|
||||||
|
@ -102,7 +101,6 @@ Templates are required to render page content in browsers and must be placed in
|
||||||
{{ block "body" . }}{{ .Page.Content | transform.Markdownify }}{{ end }}
|
{{ block "body" . }}{{ .Page.Content | transform.Markdownify }}{{ end }}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
{{ end }}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Templating works according to [the instructions of the standard Go templating engine](https://pkg.go.dev/html/template) with minor additions in the form of [additional utilities](../internal/templateutil/templateutil.go). In `$HOME_THEME_DIR`, any combination and hierarchy of templates and their relationships is allowed as long as there is a `baseof` as a single parent block.
|
Templating works according to [the instructions of the standard Go templating engine](https://pkg.go.dev/html/template) with minor additions in the form of [additional utilities](../internal/templateutil/templateutil.go). In `$HOME_THEME_DIR`, any combination and hierarchy of templates and their relationships is allowed as long as there is a `baseof` as a single parent block.
|
||||||
|
|
|
@ -83,11 +83,10 @@ This is a sample page content for demo.
|
||||||
* В `$HOME_STATIC_DIR` как публично доступная статика, без каких-либо ограничений "как есть", по тому же пути в котором файл расположен в директории;
|
* В `$HOME_STATIC_DIR` как публично доступная статика, без каких-либо ограничений "как есть", по тому же пути в котором файл расположен в директории;
|
||||||
* В `$HOME_CONTENT_DIR` рядом с нужной страницей: файл будет доступен в коллекциях `.Site.Resources` и `.Page.Resources` и ограничен настройками доступа родительской страницы;
|
* В `$HOME_CONTENT_DIR` рядом с нужной страницей: файл будет доступен в коллекциях `.Site.Resources` и `.Page.Resources` и ограничен настройками доступа родительской страницы;
|
||||||
|
|
||||||
Для рендера содержимого страниц в браузерах необходимы шаблоны, которые должны размещаться в `$HOME_THEME_DIR`. Для работы необходим хотя бы один шаблон с блоком `baseof` который будет вызываться для каждой страницы, например:
|
Для рендера содержимого страниц в браузерах необходимы шаблоны, которые должны размещаться в `$HOME_THEME_DIR`. Для работы необходим хотя бы один шаблон `baseof.html` который будет вызываться для каждой страницы, например:
|
||||||
```html
|
```html
|
||||||
<!-- theme/baseof.html -->
|
<!-- theme/baseof.html -->
|
||||||
|
|
||||||
{{ define "baseof" }}
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{{ or .Page.Language.Lang .Site.Language.Lang }}"
|
<html lang="{{ or .Page.Language.Lang .Site.Language.Lang }}"
|
||||||
dir="{{ or .Page.Language.Dir .Site.Language.Dir }}">
|
dir="{{ or .Page.Language.Dir .Site.Language.Dir }}">
|
||||||
|
@ -103,7 +102,6 @@ This is a sample page content for demo.
|
||||||
{{ block "body" . }}{{ .Page.Content | transform.Markdownify }}{{ end }}
|
{{ block "body" . }}{{ .Page.Content | transform.Markdownify }}{{ end }}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
{{ end }}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Шаблонизация работает по [инструкциям стандартного шаблонизатора Go](https://pkg.go.dev/html/template) с небольшими дополнениями в виде [дополнительных утилит](../internal/templateutil/templateutil.go). В `$HOME_THEME_DIR` разрешена любая комбинация и иеархия шаблонов и их взаимосвязей до тех пор, пока существует `baseof` в качестве единого родительского блока.
|
Шаблонизация работает по [инструкциям стандартного шаблонизатора Go](https://pkg.go.dev/html/template) с небольшими дополнениями в виде [дополнительных утилит](../internal/templateutil/templateutil.go). В `$HOME_THEME_DIR` разрешена любая комбинация и иеархия шаблонов и их взаимосвязей до тех пор, пока существует `baseof` в качестве единого родительского блока.
|
||||||
|
|
|
@ -28,42 +28,32 @@ import (
|
||||||
siteucase "source.toby3d.me/toby3d/home/internal/site/usecase"
|
siteucase "source.toby3d.me/toby3d/home/internal/site/usecase"
|
||||||
staticfsrepo "source.toby3d.me/toby3d/home/internal/static/repository/fs"
|
staticfsrepo "source.toby3d.me/toby3d/home/internal/static/repository/fs"
|
||||||
staticucase "source.toby3d.me/toby3d/home/internal/static/usecase"
|
staticucase "source.toby3d.me/toby3d/home/internal/static/usecase"
|
||||||
"source.toby3d.me/toby3d/home/internal/templateutil"
|
|
||||||
themefsrepo "source.toby3d.me/toby3d/home/internal/theme/repository/fs"
|
themefsrepo "source.toby3d.me/toby3d/home/internal/theme/repository/fs"
|
||||||
themeucase "source.toby3d.me/toby3d/home/internal/theme/usecase"
|
themeucase "source.toby3d.me/toby3d/home/internal/theme/usecase"
|
||||||
"source.toby3d.me/toby3d/home/internal/urlutil"
|
"source.toby3d.me/toby3d/home/internal/urlutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type App struct {
|
||||||
App struct {
|
server *http.Server
|
||||||
server *http.Server
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(toby3d): move this into separated package.
|
|
||||||
Context struct {
|
|
||||||
Site *domain.Site
|
|
||||||
Page *domain.Page
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewApp(logger *log.Logger, config *domain.Config) (*App, error) {
|
func NewApp(logger *log.Logger, config *domain.Config) (*App, error) {
|
||||||
themeDir := os.DirFS(config.ThemeDir)
|
themeDir := os.DirFS(config.ThemeDir)
|
||||||
|
|
||||||
|
partialsDir, err := fs.Sub(themeDir, "partials")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot substitute into partials subdirectory: %w", err)
|
||||||
|
}
|
||||||
contentDir := os.DirFS(config.ContentDir)
|
contentDir := os.DirFS(config.ContentDir)
|
||||||
resources := resourcefsrepo.NewFileServerResourceRepository(contentDir)
|
resources := resourcefsrepo.NewFileServerResourceRepository(contentDir)
|
||||||
sites := sitefsrepo.NewFileSystemSiteRepository(contentDir)
|
sites := sitefsrepo.NewFileSystemSiteRepository(contentDir)
|
||||||
siter := siteucase.NewSiteUseCase(sites, resources)
|
siter := siteucase.NewSiteUseCase(sites, resources)
|
||||||
|
|
||||||
funcMap, err := templateutil.New(themeDir, siter)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("cannot setup template.FuncMap for templates: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
staticDir := os.DirFS(config.StaticDir)
|
staticDir := os.DirFS(config.StaticDir)
|
||||||
statics := staticfsrepo.NewFileServerStaticRepository(staticDir)
|
statics := staticfsrepo.NewFileServerStaticRepository(staticDir)
|
||||||
staticer := staticucase.NewStaticUseCase(statics)
|
staticer := staticucase.NewStaticUseCase(statics)
|
||||||
resourcer := resourceucase.NewResourceUseCase(resources)
|
resourcer := resourceucase.NewResourceUseCase(resources)
|
||||||
themes := themefsrepo.NewFileSystemThemeRepository(themeDir, funcMap)
|
themes := themefsrepo.NewFileSystemThemeRepository(themeDir)
|
||||||
themer := themeucase.NewThemeUseCase(themes)
|
themer := themeucase.NewThemeUseCase(partialsDir, themes)
|
||||||
pages := pagefsrepo.NewFileSystemPageRepository(contentDir)
|
pages := pagefsrepo.NewFileSystemPageRepository(contentDir)
|
||||||
pager := pageucase.NewPageUseCase(pages, resources)
|
pager := pageucase.NewPageUseCase(pages, resources)
|
||||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -175,7 +165,7 @@ func NewApp(logger *log.Logger, config *domain.Config) (*App, error) {
|
||||||
|
|
||||||
w.Header().Set(common.HeaderContentLanguage, strings.Join(contentLanguage, ", "))
|
w.Header().Set(common.HeaderContentLanguage, strings.Join(contentLanguage, ", "))
|
||||||
|
|
||||||
tpl, err := themer.Do(r.Context())
|
template, err := themer.Do(r.Context(), s, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
|
@ -183,10 +173,7 @@ func NewApp(logger *log.Logger, config *domain.Config) (*App, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set(common.HeaderContentType, common.MIMETextHTMLCharsetUTF8)
|
w.Header().Set(common.HeaderContentType, common.MIMETextHTMLCharsetUTF8)
|
||||||
if err = tpl.Execute(w, &Context{
|
if err = template(w); err != nil {
|
||||||
Site: s,
|
|
||||||
Page: p,
|
|
||||||
}); err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
// Context represent a all-in-one theme template context.
|
||||||
|
type Context struct {
|
||||||
|
Site *Site
|
||||||
|
*Page
|
||||||
|
}
|
|
@ -1,11 +1,10 @@
|
||||||
package templateutil
|
package templateutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"html/template"
|
"html/template"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/home/internal/site"
|
"source.toby3d.me/toby3d/home/internal/domain"
|
||||||
"source.toby3d.me/toby3d/home/internal/templateutil/collections"
|
"source.toby3d.me/toby3d/home/internal/templateutil/collections"
|
||||||
"source.toby3d.me/toby3d/home/internal/templateutil/partials"
|
"source.toby3d.me/toby3d/home/internal/templateutil/partials"
|
||||||
"source.toby3d.me/toby3d/home/internal/templateutil/safe"
|
"source.toby3d.me/toby3d/home/internal/templateutil/safe"
|
||||||
|
@ -20,12 +19,7 @@ type Function struct {
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(dir fs.FS, siter site.UseCase) (template.FuncMap, error) {
|
func New(partialsDir fs.FS, site *domain.Site) template.FuncMap {
|
||||||
partialDir, err := fs.Sub(dir, "partials")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("cannot substitute into partials subdirectory: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
funcMap := make(template.FuncMap)
|
funcMap := make(template.FuncMap)
|
||||||
funcs := make([]Function, 0)
|
funcs := make([]Function, 0)
|
||||||
stringsNamespace := strings.New()
|
stringsNamespace := strings.New()
|
||||||
|
@ -45,7 +39,7 @@ func New(dir fs.FS, siter site.UseCase) (template.FuncMap, error) {
|
||||||
"safeHTML": safeNamespace.HTML,
|
"safeHTML": safeNamespace.HTML,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
partialsNamespace := partials.New(partialDir, funcMap)
|
partialsNamespace := partials.New(partialsDir, funcMap)
|
||||||
funcs = append(funcs, Function{
|
funcs = append(funcs, Function{
|
||||||
Name: "partials",
|
Name: "partials",
|
||||||
Handler: func(v ...any) any { return partialsNamespace },
|
Handler: func(v ...any) any { return partialsNamespace },
|
||||||
|
@ -53,13 +47,15 @@ func New(dir fs.FS, siter site.UseCase) (template.FuncMap, error) {
|
||||||
"partial": partialsNamespace.Include,
|
"partial": partialsNamespace.Include,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
urlsNamespace := urls.New(siter)
|
urlsNamespace := urls.New(site)
|
||||||
funcs = append(funcs, Function{
|
funcs = append(funcs, Function{
|
||||||
Name: "urls",
|
Name: "urls",
|
||||||
Handler: func(v ...any) any { return urlsNamespace },
|
Handler: func(v ...any) any { return urlsNamespace },
|
||||||
Methods: template.FuncMap{
|
Methods: template.FuncMap{
|
||||||
"absURL": urlsNamespace.AbsURL,
|
"absLangURL": urlsNamespace.AbsLangURL,
|
||||||
"relURL": urlsNamespace.RelURL,
|
"absURL": urlsNamespace.AbsURL,
|
||||||
|
"relLangURL": urlsNamespace.RelLangURL,
|
||||||
|
"relURL": urlsNamespace.RelURL,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
transformNamespace := transform.New()
|
transformNamespace := transform.New()
|
||||||
|
@ -75,9 +71,9 @@ func New(dir fs.FS, siter site.UseCase) (template.FuncMap, error) {
|
||||||
Name: "collections",
|
Name: "collections",
|
||||||
Handler: func(v ...any) any { return collectionsNamespace },
|
Handler: func(v ...any) any { return collectionsNamespace },
|
||||||
Methods: template.FuncMap{
|
Methods: template.FuncMap{
|
||||||
"slice": collectionsNamespace.Slice,
|
|
||||||
"seq": collectionsNamespace.Seq,
|
|
||||||
"index": collectionsNamespace.Index,
|
"index": collectionsNamespace.Index,
|
||||||
|
"seq": collectionsNamespace.Seq,
|
||||||
|
"slice": collectionsNamespace.Slice,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -89,5 +85,5 @@ func New(dir fs.FS, siter site.UseCase) (template.FuncMap, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return funcMap, nil
|
return funcMap
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,24 @@
|
||||||
package urls
|
package urls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/home/internal/domain"
|
"source.toby3d.me/toby3d/home/internal/domain"
|
||||||
"source.toby3d.me/toby3d/home/internal/site"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Namespace struct {
|
type Namespace struct {
|
||||||
siter site.UseCase
|
site *domain.Site
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrAbsURL error = errors.New("unsupported input type for AbsURL")
|
func New(site *domain.Site) *Namespace {
|
||||||
|
|
||||||
func New(siter site.UseCase) *Namespace {
|
|
||||||
return &Namespace{
|
return &Namespace{
|
||||||
siter: siter,
|
site: site,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ns *Namespace) AbsURL(p string) (string, error) {
|
func (ns *Namespace) AbsURL(p string) string {
|
||||||
site, err := ns.siter.Do(context.Background(), domain.LanguageUnd)
|
return ns.site.BaseURL.JoinPath(p).String()
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("cannot fetch site root for AbsURL processing: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return site.BaseURL.JoinPath(p).String(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ns *Namespace) RelURL(p string) string {
|
func (ns *Namespace) RelURL(p string) string {
|
||||||
|
@ -39,3 +28,11 @@ func (ns *Namespace) RelURL(p string) string {
|
||||||
func (ns *Namespace) Parse(rawUrl string) (*url.URL, error) {
|
func (ns *Namespace) Parse(rawUrl string) (*url.URL, error) {
|
||||||
return url.Parse(rawUrl)
|
return url.Parse(rawUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ns *Namespace) RelLangURL(p string) string {
|
||||||
|
return path.Join(ns.site.BaseURL.Path, ns.site.LanguagePrefix(), p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *Namespace) AbsLangURL(p string) string {
|
||||||
|
return ns.site.BaseURL.JoinPath(ns.site.LanguagePrefix(), p).String()
|
||||||
|
}
|
||||||
|
|
|
@ -7,5 +7,5 @@ import (
|
||||||
|
|
||||||
type Repository interface {
|
type Repository interface {
|
||||||
// TODO(toby3d): use Page context to find it's specific template.
|
// TODO(toby3d): use Page context to find it's specific template.
|
||||||
Get(ctx context.Context) (*template.Template, error)
|
Get(ctx context.Context, funcMap template.FuncMap) (*template.Template, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,19 +10,17 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type fileSystemThemeRepository struct {
|
type fileSystemThemeRepository struct {
|
||||||
dir fs.FS
|
dir fs.FS
|
||||||
funcMap template.FuncMap
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileSystemThemeRepository(dir fs.FS, funcMap template.FuncMap) theme.Repository {
|
func NewFileSystemThemeRepository(dir fs.FS) theme.Repository {
|
||||||
return &fileSystemThemeRepository{
|
return &fileSystemThemeRepository{
|
||||||
dir: dir,
|
dir: dir,
|
||||||
funcMap: funcMap,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *fileSystemThemeRepository) Get(ctx context.Context) (*template.Template, error) {
|
func (repo *fileSystemThemeRepository) Get(ctx context.Context, funcMap template.FuncMap) (*template.Template, error) {
|
||||||
tpl, err := template.New("").Funcs(repo.funcMap).ParseFS(repo.dir, "*.html")
|
tpl, err := template.New("").Funcs(funcMap).ParseFS(repo.dir, "*.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot find baseof template: %w", err)
|
return nil, fmt.Errorf("cannot find baseof template: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,15 @@ package theme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"html/template"
|
"io"
|
||||||
|
|
||||||
|
"source.toby3d.me/toby3d/home/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UseCase interface {
|
type (
|
||||||
Do(ctx context.Context) (*template.Template, error)
|
Writer func(w io.Writer) error
|
||||||
}
|
|
||||||
|
UseCase interface {
|
||||||
|
Do(ctx context.Context, site *domain.Site, page *domain.Page) (Writer, error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
|
@ -3,26 +3,36 @@ package usecase
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
|
||||||
|
"source.toby3d.me/toby3d/home/internal/domain"
|
||||||
|
"source.toby3d.me/toby3d/home/internal/templateutil"
|
||||||
"source.toby3d.me/toby3d/home/internal/theme"
|
"source.toby3d.me/toby3d/home/internal/theme"
|
||||||
)
|
)
|
||||||
|
|
||||||
type themeUseCase struct {
|
type themeUseCase struct {
|
||||||
themes theme.Repository
|
partials fs.FS
|
||||||
|
themes theme.Repository
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewThemeUseCase(themes theme.Repository) theme.UseCase {
|
func NewThemeUseCase(partials fs.FS, themes theme.Repository) theme.UseCase {
|
||||||
return &themeUseCase{
|
return &themeUseCase{
|
||||||
themes: themes,
|
partials: partials,
|
||||||
|
themes: themes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ucase *themeUseCase) Do(ctx context.Context) (*template.Template, error) {
|
func (ucase *themeUseCase) Do(ctx context.Context, site *domain.Site, page *domain.Page) (theme.Writer, error) {
|
||||||
out, err := ucase.themes.Get(ctx)
|
out, err := ucase.themes.Get(ctx, templateutil.New(ucase.partials, site))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot find theme: %w", err)
|
return nil, fmt.Errorf("cannot find theme: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return out.Lookup("baseof"), nil
|
return func(w io.Writer) error {
|
||||||
|
return out.Lookup("baseof.html").Execute(w, &domain.Context{
|
||||||
|
Site: site,
|
||||||
|
Page: page,
|
||||||
|
})
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue