This commit is contained in:
parent
2dc3d97029
commit
d77d2cc94c
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,4 +5,5 @@ tijl.dev-core
|
|||||||
web/static/js/interactive.js
|
web/static/js/interactive.js
|
||||||
web/static/css/styles.css
|
web/static/css/styles.css
|
||||||
config.yaml
|
config.yaml
|
||||||
|
blog/
|
||||||
|
|
||||||
|
3
go.mod
3
go.mod
@ -10,6 +10,8 @@ require (
|
|||||||
github.com/golang-migrate/migrate/v4 v4.17.1
|
github.com/golang-migrate/migrate/v4 v4.17.1
|
||||||
github.com/jackc/pgx/v5 v5.6.0
|
github.com/jackc/pgx/v5 v5.6.0
|
||||||
github.com/rs/zerolog v1.33.0
|
github.com/rs/zerolog v1.33.0
|
||||||
|
github.com/yuin/goldmark v1.7.4
|
||||||
|
github.com/yuin/goldmark-meta v1.1.0
|
||||||
golang.org/x/oauth2 v0.21.0
|
golang.org/x/oauth2 v0.21.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
@ -41,4 +43,5 @@ require (
|
|||||||
golang.org/x/sync v0.7.0 // indirect
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
golang.org/x/sys v0.22.0 // indirect
|
golang.org/x/sys v0.22.0 // indirect
|
||||||
golang.org/x/text v0.16.0 // indirect
|
golang.org/x/text v0.16.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.3.0 // indirect
|
||||||
)
|
)
|
||||||
|
6
go.sum
6
go.sum
@ -104,6 +104,10 @@ github.com/valyala/fasthttp v1.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8
|
|||||||
github.com/valyala/fasthttp v1.55.0/go.mod h1:NkY9JtkrpPKmgwV3HTaS2HWaJss9RSIsRVfcxxoHiOM=
|
github.com/valyala/fasthttp v1.55.0/go.mod h1:NkY9JtkrpPKmgwV3HTaS2HWaJss9RSIsRVfcxxoHiOM=
|
||||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||||
|
github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg=
|
||||||
|
github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||||
|
github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc=
|
||||||
|
github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0=
|
||||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||||
@ -128,6 +132,8 @@ golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxb
|
|||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
@ -29,12 +29,13 @@ func Load() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ConfigType struct {
|
type ConfigType struct {
|
||||||
Host string `yaml:"host"`
|
Host string `yaml:"host"`
|
||||||
Port int `yaml:"port"`
|
Port int `yaml:"port"`
|
||||||
UrlBase string `yaml:"url_base"`
|
BlogLocation string `yaml:"blog_location"`
|
||||||
JsonLogging bool `yaml:"log_json"`
|
UrlBase string `yaml:"url_base"`
|
||||||
DB string `yaml:"database"`
|
JsonLogging bool `yaml:"log_json"`
|
||||||
Oidc `yaml:"oidc"`
|
DB string `yaml:"database"`
|
||||||
|
Oidc `yaml:"oidc"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Oidc struct {
|
type Oidc struct {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"git.tijl.dev/tijl/tijl.dev-core/internal/i18n"
|
"git.tijl.dev/tijl/tijl.dev-core/internal/i18n"
|
||||||
"git.tijl.dev/tijl/tijl.dev-core/internal/oidc"
|
"git.tijl.dev/tijl/tijl.dev-core/internal/oidc"
|
||||||
"git.tijl.dev/tijl/tijl.dev-core/internal/user"
|
"git.tijl.dev/tijl/tijl.dev-core/internal/user"
|
||||||
@ -43,8 +45,8 @@ func logoutHandler(c *fiber.Ctx) error {
|
|||||||
_, err := user.GetSession(c)
|
_, err := user.GetSession(c)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.ClearCookie("session")
|
c.ClearCookie("session")
|
||||||
return c.Redirect("/login")
|
return c.Redirect("/login", http.StatusOK)
|
||||||
} else {
|
} else {
|
||||||
return c.Redirect("/login")
|
return c.Redirect("/login", http.StatusUnauthorized)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
142
internal/handlers/blog.go
Normal file
142
internal/handlers/blog.go
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
// blog implementation concept
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.tijl.dev/tijl/tijl.dev-core/internal/i18n"
|
||||||
|
log "git.tijl.dev/tijl/tijl.dev-core/modules/logger"
|
||||||
|
"git.tijl.dev/tijl/tijl.dev-core/modules/web"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/yuin/goldmark"
|
||||||
|
"github.com/yuin/goldmark-meta"
|
||||||
|
"github.com/yuin/goldmark/extension"
|
||||||
|
"github.com/yuin/goldmark/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Post struct {
|
||||||
|
Meta PostMeta
|
||||||
|
Content string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostMeta struct {
|
||||||
|
Title string
|
||||||
|
Slug string
|
||||||
|
Description string
|
||||||
|
Author string
|
||||||
|
Language string
|
||||||
|
PublishDate time.Time
|
||||||
|
UpdatedDate time.Time
|
||||||
|
Tags []string
|
||||||
|
}
|
||||||
|
|
||||||
|
var posts []Post
|
||||||
|
|
||||||
|
func LoadPosts() {
|
||||||
|
posts = []Post{}
|
||||||
|
|
||||||
|
md := goldmark.New(
|
||||||
|
goldmark.WithExtensions(extension.Table, extension.Strikethrough, extension.Linkify, extension.TaskList, extension.GFM, extension.DefinitionList, extension.Footnote, extension.Typographer, meta.Meta),
|
||||||
|
goldmark.WithParserOptions(
|
||||||
|
parser.WithAutoHeadingID(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
files, err := filepath.Glob("blog/*.md")
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("handlers.LoadPosts(): error reading folder")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
content, err := os.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("handlers.LoadPosts(): error reading file")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
context := parser.NewContext()
|
||||||
|
if err := md.Convert(content, &buf, parser.WithContext(context)); err != nil {
|
||||||
|
log.Fatal().Err(err)
|
||||||
|
}
|
||||||
|
metaData := meta.Get(context)
|
||||||
|
postMeta, err := MapToPostMeta(metaData)
|
||||||
|
posts = append(posts, Post{
|
||||||
|
Content: buf.String(),
|
||||||
|
Meta: postMeta,
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().Msg("loaded blog posts")
|
||||||
|
}
|
||||||
|
|
||||||
|
func blogIndexHandler(c *fiber.Ctx) error {
|
||||||
|
data := *web.Common(c)
|
||||||
|
data["Title"] = i18n.Translate(c, "blog")
|
||||||
|
data["Posts"] = posts
|
||||||
|
return c.Render("blog", data, "layouts/base")
|
||||||
|
}
|
||||||
|
|
||||||
|
func MapToPostMeta(m map[string]interface{}) (PostMeta, error) {
|
||||||
|
var post PostMeta
|
||||||
|
var ok bool
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if post.Title, ok = m["title"].(string); !ok {
|
||||||
|
return post, fmt.Errorf("missing or invalid type for Title")
|
||||||
|
}
|
||||||
|
|
||||||
|
if post.Slug, ok = m["slug"].(string); !ok {
|
||||||
|
return post, fmt.Errorf("missing or invalid type for Title")
|
||||||
|
}
|
||||||
|
|
||||||
|
if post.Description, ok = m["description"].(string); !ok {
|
||||||
|
return post, fmt.Errorf("missing or invalid type for Description")
|
||||||
|
}
|
||||||
|
|
||||||
|
if post.Author, ok = m["author"].(string); !ok {
|
||||||
|
return post, fmt.Errorf("missing or invalid type for Author")
|
||||||
|
}
|
||||||
|
|
||||||
|
if post.Language, ok = m["language"].(string); !ok {
|
||||||
|
return post, fmt.Errorf("missing or invalid type for Language")
|
||||||
|
}
|
||||||
|
|
||||||
|
if publishDate, ok := m["publish_date"].(string); ok {
|
||||||
|
post.PublishDate, err = time.Parse("02 Jan 2006", publishDate)
|
||||||
|
if err != nil {
|
||||||
|
return post, fmt.Errorf("invalid format for PublishDate: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return post, fmt.Errorf("missing or invalid type for PublishDate")
|
||||||
|
}
|
||||||
|
|
||||||
|
if updatedDate, ok := m["updated_date"].(string); ok {
|
||||||
|
post.UpdatedDate, err = time.Parse("02 Jan 2006", updatedDate)
|
||||||
|
if err != nil {
|
||||||
|
return post, fmt.Errorf("invalid format for PublishDate: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return post, fmt.Errorf("missing or invalid type for PublishDate")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tags, ok := m["tags"].([]interface{}); ok {
|
||||||
|
for _, tag := range tags {
|
||||||
|
if strTag, ok := tag.(string); ok {
|
||||||
|
post.Tags = append(post.Tags, strTag)
|
||||||
|
} else {
|
||||||
|
return post, fmt.Errorf("invalid type in Tags slice")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return post, fmt.Errorf("missing or invalid type for Tags")
|
||||||
|
}
|
||||||
|
|
||||||
|
return post, nil
|
||||||
|
}
|
@ -14,6 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Setup() {
|
func Setup() {
|
||||||
|
LoadPosts()
|
||||||
web.RegisterAppSetupFunc(routes)
|
web.RegisterAppSetupFunc(routes)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,11 +25,7 @@ func routes(app *fiber.App) {
|
|||||||
data["Title"] = i18n.Translate(c, "home")
|
data["Title"] = i18n.Translate(c, "home")
|
||||||
return c.Render("index", data, "layouts/base")
|
return c.Render("index", data, "layouts/base")
|
||||||
})
|
})
|
||||||
app.Get("/blog", func(c *fiber.Ctx) error {
|
app.Get("/blog", blogIndexHandler)
|
||||||
data := *web.Common(c)
|
|
||||||
data["Title"] = i18n.Translate(c, "blog")
|
|
||||||
return c.Render("blog", data, "layouts/base")
|
|
||||||
})
|
|
||||||
app.Get("/projects", func(c *fiber.Ctx) error {
|
app.Get("/projects", func(c *fiber.Ctx) error {
|
||||||
data := *web.Common(c)
|
data := *web.Common(c)
|
||||||
data["Title"] = i18n.Translate(c, "projects")
|
data["Title"] = i18n.Translate(c, "projects")
|
||||||
|
@ -15,6 +15,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func HandleRedirect(c *fiber.Ctx) error {
|
func HandleRedirect(c *fiber.Ctx) error {
|
||||||
|
if c.Query("redirect") != "" {
|
||||||
|
setCallbackCookie(c, "internal_redirect", c.Query("redirect"))
|
||||||
|
}
|
||||||
state := utils.RandString(16)
|
state := utils.RandString(16)
|
||||||
setCallbackCookie(c, "state", state)
|
setCallbackCookie(c, "state", state)
|
||||||
return c.Redirect(Config.AuthCodeURL(state), http.StatusFound)
|
return c.Redirect(Config.AuthCodeURL(state), http.StatusFound)
|
||||||
@ -78,5 +81,12 @@ func HandleCallback(c *fiber.Ctx) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
redirect := c.Cookies("internal_redirect")
|
||||||
|
log.Debug().Msg(redirect)
|
||||||
|
if redirect != "" {
|
||||||
|
c.ClearCookie("internal_redirect")
|
||||||
|
return c.Redirect(redirect)
|
||||||
|
}
|
||||||
|
|
||||||
return c.Redirect("/loggedin")
|
return c.Redirect("/loggedin")
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.tijl.dev/tijl/tijl.dev-core/internal/assets"
|
"git.tijl.dev/tijl/tijl.dev-core/internal/assets"
|
||||||
"git.tijl.dev/tijl/tijl.dev-core/internal/config"
|
"git.tijl.dev/tijl/tijl.dev-core/internal/config"
|
||||||
@ -24,6 +25,8 @@ func Listen() {
|
|||||||
// Load initial context
|
// Load initial context
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
// setup logger
|
// setup logger
|
||||||
log.Load()
|
log.Load()
|
||||||
// Load config
|
// Load config
|
||||||
@ -58,8 +61,10 @@ func Listen() {
|
|||||||
// Setup routes
|
// Setup routes
|
||||||
web.Setup(app)
|
web.Setup(app)
|
||||||
|
|
||||||
|
log.Info().Int64("ms", time.Since(start).Milliseconds()).Msg("started internal services")
|
||||||
|
|
||||||
// Listen web server
|
// Listen web server
|
||||||
log.Info().Int("port", config.Config.Port).Str("host", config.Config.Host).Msg("listening...")
|
log.Info().Int("port", config.Config.Port).Str("host", config.Config.Host).Msg("listening")
|
||||||
if err := app.Listen(fmt.Sprintf("%v:%v", config.Config.Host, config.Config.Port)); err != nil {
|
if err := app.Listen(fmt.Sprintf("%v:%v", config.Config.Host, config.Config.Port)); err != nil {
|
||||||
log.Fatal().Err(err).Msg("Fiber app error")
|
log.Fatal().Err(err).Msg("Fiber app error")
|
||||||
}
|
}
|
||||||
|
@ -9,5 +9,7 @@
|
|||||||
"projects": "Projects",
|
"projects": "Projects",
|
||||||
"account": "Account",
|
"account": "Account",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"settings": "Settings"
|
"settings": "Settings",
|
||||||
|
"page_not_found": "Page not found",
|
||||||
|
"logged_in": "You are now logged in!"
|
||||||
}
|
}
|
||||||
|
@ -9,5 +9,7 @@
|
|||||||
"projects": "Projecten",
|
"projects": "Projecten",
|
||||||
"account": "Account",
|
"account": "Account",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"settings": "Instellingen"
|
"settings": "Instellingen",
|
||||||
|
"page_not_found": "Pagina niet gevonden",
|
||||||
|
"logged_in": "Je bent nu ingelogd!"
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: ["./views/**/*.html", "./web/**/*.{js,ts,css}"],
|
content: ["./web/views/**/*.html", "./web/**/*.{js,ts,css}"],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||
|
@ -1 +1,6 @@
|
|||||||
<h2>404 not found</h2>
|
<div class="text-center">
|
||||||
|
<div class="text-[5rem] font-semibold">
|
||||||
|
<h1>404</h1>
|
||||||
|
</div>
|
||||||
|
<p>{{.T.page_not_found}}</p>
|
||||||
|
</div>
|
||||||
|
@ -1 +0,0 @@
|
|||||||
<h2>TODO About</h2>
|
|
@ -1,4 +1,5 @@
|
|||||||
<div>
|
<div>
|
||||||
|
<div>Account</div>
|
||||||
<div>id: {{.User.Uid}}</div>
|
<div>id: {{.User.Uid}}</div>
|
||||||
<div>username: {{.User.Username}}</div>
|
<div>username: {{.User.Username}}</div>
|
||||||
<div>email: {{.User.Email}}</div>
|
<div>email: {{.User.Email}}</div>
|
||||||
|
@ -1 +1,5 @@
|
|||||||
<h2>TODO Blog</h2>
|
<div>Posts</div>
|
||||||
|
|
||||||
|
{{range .Posts}}
|
||||||
|
<div><a href="/blog/{{.Meta.Slug}}">{{.Meta.Title}}</a></div>
|
||||||
|
{{end}}
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
<div>index</div>
|
|
||||||
<div>Signed In: {{.SignedIn}}</div>
|
|
@ -1 +1,2 @@
|
|||||||
<div>you are now logged in</div>
|
<div>{{.T.logged_in}}</div>
|
||||||
|
<a hx-boost="true" class="btn" href="/account">{{.T.account}}</a>
|
||||||
|
@ -92,7 +92,7 @@
|
|||||||
<span class="text-base">{{.T.account}}</span>
|
<span class="text-base">{{.T.account}}</span>
|
||||||
</a>
|
</a>
|
||||||
{{else}}
|
{{else}}
|
||||||
<a class="active flex gap-4" href="/login">
|
<a hx-boost="false" class="active flex gap-4" href="/auth?redirect={{.Path}}">
|
||||||
<span class="w-5 text-center">
|
<span class="w-5 text-center">
|
||||||
{{icon "login"}}
|
{{icon "login"}}
|
||||||
</span>
|
</span>
|
||||||
|
@ -1 +0,0 @@
|
|||||||
<h2>TODO Projects</h2>
|
|
@ -1 +0,0 @@
|
|||||||
<div>hello</div>
|
|
Loading…
Reference in New Issue
Block a user