add templating engine
This commit is contained in:
parent
827cb66b4e
commit
2aa592252c
83
app.go
83
app.go
@ -21,53 +21,6 @@ import (
|
|||||||
_ "github.com/jackc/pgx/v5/stdlib"
|
_ "github.com/jackc/pgx/v5/stdlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: review consts
|
|
||||||
const (
|
|
||||||
// Handlers keys
|
|
||||||
InvalidRequest = "invalid_request"
|
|
||||||
MalformedJSON = "malformed_json"
|
|
||||||
TokenBlacklisted = "token_blacklisted"
|
|
||||||
TokenInvalid = "token_invalid"
|
|
||||||
ValidationFailed = "validation_failed"
|
|
||||||
UntilBeforeTo = "until_before_to"
|
|
||||||
InternalError = "internal_error"
|
|
||||||
NotFound = "not_found"
|
|
||||||
Created = "created"
|
|
||||||
Updated = "updated"
|
|
||||||
Deleted = "deleted"
|
|
||||||
Enabled = "enabled"
|
|
||||||
Disabled = "disabled"
|
|
||||||
Retrieved = "retrieved"
|
|
||||||
ErrorCreating = "error_creating"
|
|
||||||
ErrorUpdating = "error_updating"
|
|
||||||
ErrorEnabling = "error_enabling"
|
|
||||||
ErrorDisabling = "error_disabling"
|
|
||||||
ErrorGetting = "error_getting"
|
|
||||||
ErrorGettingAll = "error_getting_all"
|
|
||||||
ErrorMailing = "error_mailing"
|
|
||||||
InvalidEntityID = "invalid_entity_id"
|
|
||||||
NotImplemented = "not_implemented"
|
|
||||||
NotPassValidation = "not_pass_validation"
|
|
||||||
NotEnoughBalance = "not_enough_balance"
|
|
||||||
InvalidIdentifier = "invalid_identifier"
|
|
||||||
|
|
||||||
// User keys (DB)
|
|
||||||
UserUsernameKey = "username_key"
|
|
||||||
UserEmailKey = "email_key"
|
|
||||||
UsernameAlreadyExists = "username_already_exists"
|
|
||||||
UserSessionKey = "user_session_key"
|
|
||||||
EmailAlreadyExists = "email_already_exists"
|
|
||||||
PhoneNumberKey = "phone_number_key"
|
|
||||||
PhoneAlreadyExists = "phone_already_exists"
|
|
||||||
NoRowsAffected = "no rows in result set"
|
|
||||||
|
|
||||||
// Auth
|
|
||||||
TokenPayload = "token_payload"
|
|
||||||
LoggedIn = "logged_in"
|
|
||||||
IncorrectPassword = "incorrect_password"
|
|
||||||
ErrorGeneratingToken = "error_generating_token"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
logFile *os.File
|
logFile *os.File
|
||||||
logLevel string
|
logLevel string
|
||||||
@ -90,10 +43,10 @@ type DatabaseConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// default ""
|
// default "no-name-defined"
|
||||||
Name string
|
Name string
|
||||||
|
|
||||||
// default ""
|
// default "v0.0.0"
|
||||||
Version string
|
Version string
|
||||||
|
|
||||||
// default "development"
|
// default "development"
|
||||||
@ -111,21 +64,21 @@ type Config struct {
|
|||||||
// default map[string]DatabaseConfig{}
|
// default map[string]DatabaseConfig{}
|
||||||
Databases map[string]DatabaseConfig
|
Databases map[string]DatabaseConfig
|
||||||
|
|
||||||
|
// default false
|
||||||
|
CreateTemplates bool
|
||||||
|
|
||||||
// default false
|
// default false
|
||||||
CreateSession bool
|
CreateSession bool
|
||||||
|
|
||||||
// default false
|
// default false
|
||||||
CreateMailer bool
|
CreateMailer bool
|
||||||
|
|
||||||
// default false
|
|
||||||
CreateTemplates bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
config Config
|
config Config
|
||||||
Session *scs.SessionManager
|
Templates *Render
|
||||||
Mailer Mailer
|
Session *scs.SessionManager
|
||||||
//Templates *Templates
|
Mailer Mailer
|
||||||
}
|
}
|
||||||
|
|
||||||
type Paseto struct {
|
type Paseto struct {
|
||||||
@ -248,12 +201,20 @@ func New(config ...Config) *App {
|
|||||||
"paseto_public_key", cfg.Paseto.PublicKey.ExportHex(),
|
"paseto_public_key", cfg.Paseto.PublicKey.ExportHex(),
|
||||||
"paseto_duration", cfg.Paseto.Duration.String(),
|
"paseto_duration", cfg.Paseto.Duration.String(),
|
||||||
"databases", cfg.Databases,
|
"databases", cfg.Databases,
|
||||||
|
"create_templates", cfg.CreateTemplates,
|
||||||
|
"create_session", cfg.CreateSession,
|
||||||
|
"create_mailer", cfg.CreateMailer,
|
||||||
)
|
)
|
||||||
|
|
||||||
if cfg.EnvMode != EnvironmentProduction {
|
if cfg.EnvMode != EnvironmentProduction {
|
||||||
slog.Info("paseto_assymetric_key", "key", cfg.Paseto.AsymmetricKey.ExportHex())
|
slog.Info("paseto_assymetric_key", "key", cfg.Paseto.AsymmetricKey.ExportHex())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.CreateTemplates {
|
||||||
|
slog.Debug("creating templates")
|
||||||
|
app.Templates = NewHTMLRender()
|
||||||
|
}
|
||||||
|
|
||||||
if cfg.CreateSession {
|
if cfg.CreateSession {
|
||||||
slog.Debug("creating session")
|
slog.Debug("creating session")
|
||||||
app.Session = scs.New()
|
app.Session = scs.New()
|
||||||
@ -283,14 +244,14 @@ func (a *App) LogLevel() slog.Level {
|
|||||||
return a.config.LogLevel
|
return a.config.LogLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) Paseto() *Paseto {
|
|
||||||
return a.config.Paseto
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) Timezone() string {
|
func (a *App) Timezone() string {
|
||||||
return a.config.Timezone
|
return a.config.Timezone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) Paseto() *Paseto {
|
||||||
|
return a.config.Paseto
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) Datasource(name string) string {
|
func (a *App) Datasource(name string) string {
|
||||||
config, exists := a.config.Databases[name]
|
config, exists := a.config.Databases[name]
|
||||||
if !exists {
|
if !exists {
|
||||||
@ -306,7 +267,7 @@ func (a *App) Datasource(name string) string {
|
|||||||
// cmd/main.go
|
// cmd/main.go
|
||||||
//
|
//
|
||||||
// cmd/database/migrations/*.sql
|
// cmd/database/migrations/*.sql
|
||||||
func (a *App) Migrate(database embed.FS, dbName string) {
|
func (a *App) Migrate(dbName string, database embed.FS) {
|
||||||
dbConfig, exists := a.config.Databases[dbName]
|
dbConfig, exists := a.config.Databases[dbName]
|
||||||
if !exists {
|
if !exists {
|
||||||
slog.Error("database configuration not found", "name", dbName)
|
slog.Error("database configuration not found", "name", dbName)
|
||||||
|
|||||||
48
consts.go
Normal file
48
consts.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package goblocks
|
||||||
|
|
||||||
|
// TODO: review consts
|
||||||
|
const (
|
||||||
|
// Handlers keys
|
||||||
|
InvalidRequest = "invalid_request"
|
||||||
|
MalformedJSON = "malformed_json"
|
||||||
|
TokenBlacklisted = "token_blacklisted"
|
||||||
|
TokenInvalid = "token_invalid"
|
||||||
|
ValidationFailed = "validation_failed"
|
||||||
|
UntilBeforeTo = "until_before_to"
|
||||||
|
InternalError = "internal_error"
|
||||||
|
NotFound = "not_found"
|
||||||
|
Created = "created"
|
||||||
|
Updated = "updated"
|
||||||
|
Deleted = "deleted"
|
||||||
|
Enabled = "enabled"
|
||||||
|
Disabled = "disabled"
|
||||||
|
Retrieved = "retrieved"
|
||||||
|
ErrorCreating = "error_creating"
|
||||||
|
ErrorUpdating = "error_updating"
|
||||||
|
ErrorEnabling = "error_enabling"
|
||||||
|
ErrorDisabling = "error_disabling"
|
||||||
|
ErrorGetting = "error_getting"
|
||||||
|
ErrorGettingAll = "error_getting_all"
|
||||||
|
ErrorMailing = "error_mailing"
|
||||||
|
InvalidEntityID = "invalid_entity_id"
|
||||||
|
NotImplemented = "not_implemented"
|
||||||
|
NotPassValidation = "not_pass_validation"
|
||||||
|
NotEnoughBalance = "not_enough_balance"
|
||||||
|
InvalidIdentifier = "invalid_identifier"
|
||||||
|
|
||||||
|
// User keys (DB)
|
||||||
|
UserUsernameKey = "username_key"
|
||||||
|
UserEmailKey = "email_key"
|
||||||
|
UsernameAlreadyExists = "username_already_exists"
|
||||||
|
UserSessionKey = "user_session_key"
|
||||||
|
EmailAlreadyExists = "email_already_exists"
|
||||||
|
PhoneNumberKey = "phone_number_key"
|
||||||
|
PhoneAlreadyExists = "phone_already_exists"
|
||||||
|
NoRowsAffected = "no rows in result set"
|
||||||
|
|
||||||
|
// Auth
|
||||||
|
TokenPayload = "token_payload"
|
||||||
|
LoggedIn = "logged_in"
|
||||||
|
IncorrectPassword = "incorrect_password"
|
||||||
|
ErrorGeneratingToken = "error_generating_token"
|
||||||
|
)
|
||||||
@ -1,12 +0,0 @@
|
|||||||
package network
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func JSON(w http.ResponseWriter, code int, v any) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(code)
|
|
||||||
json.NewEncoder(w).Encode(v)
|
|
||||||
}
|
|
||||||
24
render.go
Normal file
24
render.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package goblocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *App) JSON(w http.ResponseWriter, code int, v any) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(code)
|
||||||
|
json.NewEncoder(w).Encode(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) HTML(w http.ResponseWriter, code int, name string, td *TemplateData) {
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
err := a.Templates.Template(w, name, td)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("error rendering template", "error", err)
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(code)
|
||||||
|
}
|
||||||
331
templates.go
Normal file
331
templates.go
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
package goblocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"html/template"
|
||||||
|
"io/fs"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
templateCache map[string]*template.Template
|
||||||
|
|
||||||
|
TemplateData struct {
|
||||||
|
Data map[string]any
|
||||||
|
Pages Pages
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderOptions func(*Render)
|
||||||
|
Render struct {
|
||||||
|
EnableCache bool
|
||||||
|
TemplatesPath string
|
||||||
|
Functions template.FuncMap
|
||||||
|
TemplateData TemplateData
|
||||||
|
templateCache templateCache
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func defaultHTMLRender() *Render {
|
||||||
|
return &Render{
|
||||||
|
EnableCache: false,
|
||||||
|
TemplatesPath: "templates",
|
||||||
|
TemplateData: TemplateData{},
|
||||||
|
Functions: template.FuncMap{},
|
||||||
|
templateCache: templateCache{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTMLRender(opts ...RenderOptions) *Render {
|
||||||
|
config := defaultHTMLRender()
|
||||||
|
return config.apply(opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *Render) apply(opts ...RenderOptions) *Render {
|
||||||
|
for _, opt := range opts {
|
||||||
|
if opt != nil {
|
||||||
|
opt(re)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return re
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultIfEmpty(fallback, value string) string {
|
||||||
|
if strings.TrimSpace(value) == "" {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *Render) Template(w http.ResponseWriter, tmpl string, td *TemplateData) error {
|
||||||
|
var tc templateCache
|
||||||
|
var err error
|
||||||
|
|
||||||
|
re.Functions["default"] = defaultIfEmpty
|
||||||
|
|
||||||
|
if td == nil {
|
||||||
|
td = &TemplateData{}
|
||||||
|
}
|
||||||
|
|
||||||
|
tc, err = re.getTemplateCache()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t, ok := tc[tmpl]
|
||||||
|
if !ok {
|
||||||
|
return errors.New("can't get template from cache")
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if err = t.Execute(buf, td); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = buf.WriteTo(w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *Render) getTemplateCache() (templateCache, error) {
|
||||||
|
if len(re.templateCache) == 0 {
|
||||||
|
cachedTemplates, err := re.createTemplateCache()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
re.templateCache = cachedTemplates
|
||||||
|
}
|
||||||
|
if re.EnableCache {
|
||||||
|
return re.templateCache, nil
|
||||||
|
}
|
||||||
|
return re.createTemplateCache()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *Render) findHTMLFiles() ([]string, error) {
|
||||||
|
var files []string
|
||||||
|
|
||||||
|
err := filepath.WalkDir(re.TemplatesPath, func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !d.IsDir() && filepath.Ext(path) == ".gohtml" {
|
||||||
|
files = append(files, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *Render) createTemplateCache() (templateCache, error) {
|
||||||
|
cache := templateCache{}
|
||||||
|
var baseTemplates []string
|
||||||
|
var renderTemplates []string
|
||||||
|
|
||||||
|
templates, err := re.findHTMLFiles()
|
||||||
|
if err != nil {
|
||||||
|
return cache, err
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Debug("templates", "templates", templates)
|
||||||
|
|
||||||
|
for _, file := range templates {
|
||||||
|
filePathBase := filepath.Base(file)
|
||||||
|
if strings.Contains(filePathBase, "layout") || strings.Contains(filePathBase, "fragment") {
|
||||||
|
baseTemplates = append(baseTemplates, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range templates {
|
||||||
|
filePathBase := filepath.Base(file)
|
||||||
|
if strings.Contains(filePathBase, "page") || strings.Contains(filePathBase, "component") {
|
||||||
|
renderTemplates = append(baseTemplates, file)
|
||||||
|
ts, err := template.New(filePathBase).Funcs(re.Functions).ParseFiles(append(baseTemplates, renderTemplates...)...)
|
||||||
|
if err != nil {
|
||||||
|
return cache, err
|
||||||
|
}
|
||||||
|
cache[filePathBase] = ts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cache, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pages contains pagination info.
|
||||||
|
type Pages struct {
|
||||||
|
// TotalElements indicates the total number of elements available for
|
||||||
|
// pagination.
|
||||||
|
TotalElements int
|
||||||
|
// ElementsPerPage defines the number of elements to display per page in
|
||||||
|
// pagination.
|
||||||
|
ElementsPerPage int
|
||||||
|
// ActualPage represents the current page number in pagination.
|
||||||
|
ActualPage int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pages) PaginationParams(r *http.Request) {
|
||||||
|
limit := r.FormValue("limit")
|
||||||
|
page := r.FormValue("page")
|
||||||
|
|
||||||
|
if limit == "" {
|
||||||
|
if p.ElementsPerPage != 0 {
|
||||||
|
limit = strconv.Itoa(p.ElementsPerPage)
|
||||||
|
} else {
|
||||||
|
limit = "20"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if page == "" || page == "0" {
|
||||||
|
if p.ActualPage != 0 {
|
||||||
|
page = strconv.Itoa(p.ActualPage)
|
||||||
|
} else {
|
||||||
|
page = "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
limitInt, _ := strconv.Atoi(limit)
|
||||||
|
pageInt, _ := strconv.Atoi(page)
|
||||||
|
offset := (pageInt - 1) * limitInt
|
||||||
|
currentPage := offset/limitInt + 1
|
||||||
|
|
||||||
|
p.ElementsPerPage = limitInt
|
||||||
|
p.ActualPage = currentPage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Pages) PaginateArray(elements any) any {
|
||||||
|
itemsValue := reflect.ValueOf(elements)
|
||||||
|
|
||||||
|
if p.ActualPage < 1 {
|
||||||
|
p.ActualPage = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.ActualPage > p.TotalPages() {
|
||||||
|
p.ActualPage = p.TotalPages()
|
||||||
|
}
|
||||||
|
|
||||||
|
startIndex := (p.ActualPage - 1) * p.ElementsPerPage
|
||||||
|
endIndex := startIndex + p.ElementsPerPage
|
||||||
|
|
||||||
|
return itemsValue.Slice(startIndex, endIndex).Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Pages) CurrentPage() int {
|
||||||
|
return p.ActualPage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Pages) TotalPages() int {
|
||||||
|
return (p.TotalElements + p.ElementsPerPage - 1) / p.ElementsPerPage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Pages) IsFirst() bool {
|
||||||
|
return p.ActualPage == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Pages) IsLast() bool {
|
||||||
|
return p.ActualPage == p.TotalPages()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Pages) HasPrevious() bool {
|
||||||
|
return p.ActualPage > 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Pages) HasNext() bool {
|
||||||
|
return p.ActualPage < p.TotalPages()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Pages) Previous() int {
|
||||||
|
if p.ActualPage > p.TotalPages() {
|
||||||
|
return p.TotalPages()
|
||||||
|
}
|
||||||
|
return p.ActualPage - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Pages) Next() int {
|
||||||
|
if p.ActualPage < 1 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return p.ActualPage + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Pages) GoToPage(page int) int {
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
} else if page > p.TotalPages() {
|
||||||
|
page = p.TotalPages()
|
||||||
|
}
|
||||||
|
return page
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Pages) First() int {
|
||||||
|
return p.GoToPage(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Pages) Last() int {
|
||||||
|
return p.GoToPage(p.TotalPages())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Page contiene la información de una página. Utilizado para la barra de
|
||||||
|
// paginación que suelen mostrarse en la parte inferior de una lista o tabla.
|
||||||
|
|
||||||
|
// Page represents a single page in pagination, including its number and active
|
||||||
|
// state. Useful for pagination bar.
|
||||||
|
type Page struct {
|
||||||
|
// Number is the numeric identifier of the page in pagination.
|
||||||
|
Number int
|
||||||
|
// Active indicates if the page is the currently selected page.
|
||||||
|
Active bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Page) NumberOfPage() int {
|
||||||
|
return p.Number
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Page) IsActive() bool {
|
||||||
|
return p.Active
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageRange generates a slice of Page instances representing a range of pages
|
||||||
|
// to be displayed in a pagination bar.
|
||||||
|
func (p Pages) PageRange(maxPagesToShow int) []Page {
|
||||||
|
var pages []Page
|
||||||
|
totalPages := p.TotalPages()
|
||||||
|
|
||||||
|
startPage := p.ActualPage - (maxPagesToShow / 2)
|
||||||
|
endPage := p.ActualPage + (maxPagesToShow / 2)
|
||||||
|
|
||||||
|
if startPage < 1 {
|
||||||
|
startPage = 1
|
||||||
|
endPage = maxPagesToShow
|
||||||
|
}
|
||||||
|
|
||||||
|
if endPage > totalPages {
|
||||||
|
endPage = totalPages
|
||||||
|
startPage = totalPages - maxPagesToShow + 1
|
||||||
|
if startPage < 1 {
|
||||||
|
startPage = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := startPage; i <= endPage; i++ {
|
||||||
|
pages = append(pages, Page{
|
||||||
|
Number: i,
|
||||||
|
Active: i == p.ActualPage,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user