Sorted out how to do views with Go html/template, how to put code in subdirectories for a namespace, and documented why Go's modules are so weird.
This commit is contained in:
parent
144a76a67a
commit
55f59d88b6
11 changed files with 155 additions and 53 deletions
|
@ -4,7 +4,7 @@ tmp_dir = "tmp"
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
args_bin = []
|
args_bin = []
|
||||||
bin = "fibertest.exe"
|
bin = "webapp"
|
||||||
cmd = "make build"
|
cmd = "make build"
|
||||||
delay = 1000
|
delay = 1000
|
||||||
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
|
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
|
||||||
|
|
3
Makefile
3
Makefile
|
@ -1,6 +1,9 @@
|
||||||
build:
|
build:
|
||||||
go build .
|
go build .
|
||||||
|
|
||||||
|
html:
|
||||||
|
go tool qtc -dir templates
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
go tool godoc -http=localhost:6060 -index -index_files godoc.idx
|
go tool godoc -http=localhost:6060 -index -index_files godoc.idx
|
||||||
|
|
||||||
|
|
40
README.md
40
README.md
|
@ -22,3 +22,43 @@ go tool godoc -http=localhost:6060 -index
|
||||||
> ___NOTE:___ Google doesn't know how the internet works so you have to use `localhost:PORT` and not `127.0.0.1:PORT` when you run this.
|
> ___NOTE:___ Google doesn't know how the internet works so you have to use `localhost:PORT` and not `127.0.0.1:PORT` when you run this.
|
||||||
|
|
||||||
After that it'll take some time to index everything but you can already start browsing the APIs you need, and your project's stuff is in the _Third Party_ section.
|
After that it'll take some time to index everything but you can already start browsing the APIs you need, and your project's stuff is in the _Third Party_ section.
|
||||||
|
|
||||||
|
## Dealing With Module Bullshit
|
||||||
|
|
||||||
|
The way to think about Go's modules is that they don't have modules, they have "projects." Every
|
||||||
|
directory's .go files export all of their functions without any namespacing based on the file's
|
||||||
|
name. So if you put `FooBar` into `tools.go` and `Dipshit` in `fuckyou.go` then your namespace for
|
||||||
|
_all_ files has raw dogged `FooBar` and `Dipshit` without any reference to `tools` or `fuckyou`.
|
||||||
|
|
||||||
|
That's because your root directory is a whole __project__, not a module. To then create a namespace
|
||||||
|
you have to make a directory, place the files in that directory, and add `package mymod` at the top
|
||||||
|
of those files. You then have to import this as if it's an __entire fucking project__ everywhere
|
||||||
|
you want to use it:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import 'mywholewebsite.com/rootproject/subproject'
|
||||||
|
```
|
||||||
|
|
||||||
|
In this case you have a directory `subproject` and in there are a bunch of .go files with `package subproject` at the top to indicate they are in that subproject. Thinking about Go's modules as separate projects helps to sort out this `import` statement.
|
||||||
|
|
||||||
|
1. mkdir tools
|
||||||
|
2. Create files in tools/ with `package tools`
|
||||||
|
3. `import "zedshaw.games/webapp/tools"` to get the subdirectory
|
||||||
|
|
||||||
|
## Why Did Go Do This?
|
||||||
|
|
||||||
|
That's because it comes from Google, and Google is famous for two things:
|
||||||
|
|
||||||
|
1. Using a monorepo.
|
||||||
|
2. Assuming everyone else uses a monorepo.
|
||||||
|
|
||||||
|
Don't believe me? Go look at the official first document [covering modules](https://go.dev/doc/tutorial/create-module) and you'll see they create two _totally separate projects at the root which then link to each other_. That's not how anyone else thinks when they make a single project to work on, but if you're suffering in monorepo hell you'll do it this way.
|
||||||
|
|
||||||
|
I'll also posit that Google has some weird incentive that measures numbers of projects in the
|
||||||
|
monorepo as some kind of metric for employee productivity, so everyone working there is motivated
|
||||||
|
to get as many little projects into the monorepo as possible, thus a great way to get them to adopt
|
||||||
|
Go is to make Go support pulling tons of random projects from the root of a monorepo.
|
||||||
|
|
||||||
|
So that's why you have to put your whole entire domain name into the import, and why you have all
|
||||||
|
the functions just raw dogged into your face when you make multiple files, and why subdirectories
|
||||||
|
are treated like whole little projects.
|
||||||
|
|
9
go.mod
9
go.mod
|
@ -1,12 +1,15 @@
|
||||||
module zedshaw-games/fibertest
|
module zedshaw.games/webapp
|
||||||
|
|
||||||
go 1.24.2
|
go 1.24.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Masterminds/squirrel v1.5.4
|
github.com/Masterminds/squirrel v1.5.4
|
||||||
|
github.com/go-playground/validator/v10 v10.26.0
|
||||||
github.com/gofiber/fiber/v2 v2.52.8
|
github.com/gofiber/fiber/v2 v2.52.8
|
||||||
|
github.com/gofiber/template/html/v2 v2.1.3
|
||||||
github.com/jmoiron/sqlx v1.4.0
|
github.com/jmoiron/sqlx v1.4.0
|
||||||
github.com/mattn/go-sqlite3 v1.14.28
|
github.com/mattn/go-sqlite3 v1.14.28
|
||||||
|
github.com/valyala/quicktemplate v1.8.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
@ -35,9 +38,10 @@ require (
|
||||||
github.com/go-faster/errors v0.7.1 // indirect
|
github.com/go-faster/errors v0.7.1 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
|
||||||
github.com/go-sql-driver/mysql v1.9.2 // indirect
|
github.com/go-sql-driver/mysql v1.9.2 // indirect
|
||||||
github.com/gobwas/glob v0.2.3 // indirect
|
github.com/gobwas/glob v0.2.3 // indirect
|
||||||
|
github.com/gofiber/template v1.8.3 // indirect
|
||||||
|
github.com/gofiber/utils v1.1.0 // indirect
|
||||||
github.com/gohugoio/hugo v0.147.6 // indirect
|
github.com/gohugoio/hugo v0.147.6 // indirect
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||||
|
@ -78,7 +82,6 @@ require (
|
||||||
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d // indirect
|
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fasthttp v1.58.0 // indirect
|
github.com/valyala/fasthttp v1.58.0 // indirect
|
||||||
github.com/valyala/quicktemplate v1.8.0 // indirect
|
|
||||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||||
github.com/vertica/vertica-sql-go v1.3.3 // indirect
|
github.com/vertica/vertica-sql-go v1.3.3 // indirect
|
||||||
github.com/ydb-platform/ydb-go-genproto v0.0.0-20241112172322-ea1f63298f77 // indirect
|
github.com/ydb-platform/ydb-go-genproto v0.0.0-20241112172322-ea1f63298f77 // indirect
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -135,6 +135,8 @@ github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1
|
||||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
@ -150,6 +152,12 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||||
github.com/gofiber/fiber/v2 v2.52.8 h1:xl4jJQ0BV5EJTA2aWiKw/VddRpHrKeZLF0QPUxqn0x4=
|
github.com/gofiber/fiber/v2 v2.52.8 h1:xl4jJQ0BV5EJTA2aWiKw/VddRpHrKeZLF0QPUxqn0x4=
|
||||||
github.com/gofiber/fiber/v2 v2.52.8/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
github.com/gofiber/fiber/v2 v2.52.8/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
||||||
|
github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc=
|
||||||
|
github.com/gofiber/template v1.8.3/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8=
|
||||||
|
github.com/gofiber/template/html/v2 v2.1.3 h1:n1LYBtmr9C0V/k/3qBblXyMxV5B0o/gpb6dFLp8ea+o=
|
||||||
|
github.com/gofiber/template/html/v2 v2.1.3/go.mod h1:U5Fxgc5KpyujU9OqKzy6Kn6Qup6Tm7zdsISR+VpnHRE=
|
||||||
|
github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM=
|
||||||
|
github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e h1:QArsSubW7eDh8APMXkByjQWvuljwPGAGQpJEFn0F0wY=
|
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e h1:QArsSubW7eDh8APMXkByjQWvuljwPGAGQpJEFn0F0wY=
|
||||||
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e/go.mod h1:3Ltoo9Banwq0gOtcOwxuHG6omk+AwsQPADyw2vQYOJQ=
|
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e/go.mod h1:3Ltoo9Banwq0gOtcOwxuHG6omk+AwsQPADyw2vQYOJQ=
|
||||||
|
|
68
main.go
68
main.go
|
@ -1,15 +1,15 @@
|
||||||
package main
|
package webapp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
"zedshaw.games/webapp/tools"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||||
|
"github.com/gofiber/template/html/v2"
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
sq "github.com/Masterminds/squirrel"
|
sq "github.com/Masterminds/squirrel"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Link struct {
|
type Link struct {
|
||||||
|
@ -26,34 +26,6 @@ type Stream struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func SelectJson[T any](db *sqlx.DB, c *fiber.Ctx, err error, sql string, args ...interface{}) error {
|
|
||||||
if(err != nil) {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
var result []T
|
|
||||||
|
|
||||||
err = db.Select(&result, sql, args...)
|
|
||||||
if(err != nil) {
|
|
||||||
log.Fatalln(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(&result);
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetJson[T any](db *sqlx.DB, c *fiber.Ctx, err error, sql string, args ...interface{}) error {
|
|
||||||
if(err != nil) {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
var result T
|
|
||||||
|
|
||||||
err = db.Get(&result, sql, args...)
|
|
||||||
if(err != nil) {
|
|
||||||
log.Fatalln(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(&result);
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||||
|
|
||||||
|
@ -62,41 +34,35 @@ func main() {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
app := fiber.New()
|
engine := html.New("./views", ".html")
|
||||||
|
app := fiber.New(fiber.Config{
|
||||||
|
Views: engine,
|
||||||
|
})
|
||||||
app.Use(logger.New())
|
app.Use(logger.New())
|
||||||
|
|
||||||
// handler that returns one json from a sql database
|
// handler that returns one json from a sql database
|
||||||
app.Get("/api/stream/", func (c *fiber.Ctx) error {
|
app.Get("/api/stream/", func (c *fiber.Ctx) error {
|
||||||
sql, args, err := sq.Select("*").From("stream").ToSql()
|
sql, args, err := sq.Select("*").From("stream").ToSql()
|
||||||
return SelectJson[Stream](db, c, err, sql, args...)
|
return tools.SelectJson[Stream](db, c, err, sql, args...)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.Get("/api/stream/:id", func (c *fiber.Ctx) error {
|
app.Get("/api/stream/:id", func (c *fiber.Ctx) error {
|
||||||
sql, args, err := sq.Select("*").From("stream").Where("id", c.Params("id")).ToSql()
|
sql, args, err := sq.Select("*").From("stream").Where("id", c.Params("id")).ToSql()
|
||||||
return GetJson[Stream](db, c, err, sql, args...)
|
return tools.GetJson[Stream](db, c, err, sql, args...)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.Get("/api/stream/:id/links", func (c *fiber.Ctx) error {
|
app.Get("/api/stream/:id/links", func (c *fiber.Ctx) error {
|
||||||
sql, args, err := sq.Select("*").From("stream_link").Where("stream_id", c.Params("id")).ToSql()
|
sql, args, err := sq.Select("*").From("stream_link").Where("stream_id", c.Params("id")).ToSql()
|
||||||
|
|
||||||
return SelectJson[Link](db, c, err, sql, args...)
|
return tools.SelectJson[Link](db, c, err, sql, args...)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.Post("/api/link", func (c *fiber.Ctx) error {
|
app.Post("/api/link", func (c *fiber.Ctx) error {
|
||||||
link := new(Link)
|
link, err := tools.GetThing[Link](c)
|
||||||
|
|
||||||
if err := c.BodyParser(link); err != nil {
|
if(err != nil) {
|
||||||
log.Println(err);
|
log.Println(err)
|
||||||
return c.Redirect("/live/")
|
c.Redirect("/live/")
|
||||||
}
|
|
||||||
|
|
||||||
var validate *validator.Validate
|
|
||||||
validate = validator.New(validator.WithRequiredStructEnabled())
|
|
||||||
|
|
||||||
if err := validate.Struct(link); err != nil {
|
|
||||||
validationErrors := err.(validator.ValidationErrors)
|
|
||||||
log.Println(validationErrors)
|
|
||||||
return c.Redirect("/live/")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sql, args, err := sq.Insert("stream_link").Columns("stream_id", "url", "description").Values(link.StreamId, link.Url, link.Description).ToSql()
|
sql, args, err := sq.Insert("stream_link").Columns("stream_id", "url", "description").Values(link.StreamId, link.Url, link.Description).ToSql()
|
||||||
|
@ -113,5 +79,11 @@ func main() {
|
||||||
|
|
||||||
app.Static("/", "./public")
|
app.Static("/", "./public")
|
||||||
|
|
||||||
|
app.Get("/test/", func (c *fiber.Ctx) error {
|
||||||
|
return c.Render("index", fiber.Map{
|
||||||
|
"Title": "Hello, World!",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
log.Fatal(app.Listen(":5001"))
|
log.Fatal(app.Listen(":5001"))
|
||||||
}
|
}
|
||||||
|
|
59
tools/tools.go
Normal file
59
tools/tools.go
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SelectJson[T any](db *sqlx.DB, c *fiber.Ctx, err error, sql string, args ...interface{}) error {
|
||||||
|
if(err != nil) {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
var result []T
|
||||||
|
|
||||||
|
err = db.Select(&result, sql, args...)
|
||||||
|
if(err != nil) {
|
||||||
|
log.Fatalln(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetJson[T any](db *sqlx.DB, c *fiber.Ctx, err error, sql string, args ...interface{}) error {
|
||||||
|
if(err != nil) {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
var result T
|
||||||
|
|
||||||
|
err = db.Get(&result, sql, args...)
|
||||||
|
if(err != nil) {
|
||||||
|
log.Fatalln(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetThing[T any](c *fiber.Ctx) (*T, error) {
|
||||||
|
var result *T
|
||||||
|
result = new(T)
|
||||||
|
|
||||||
|
if err := c.BodyParser(result); err != nil {
|
||||||
|
log.Println(err);
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var validate *validator.Validate
|
||||||
|
validate = validator.New(validator.WithRequiredStructEnabled())
|
||||||
|
|
||||||
|
if err := validate.Struct(result); err != nil {
|
||||||
|
validationErrors := err.(validator.ValidationErrors)
|
||||||
|
log.Println(validationErrors)
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
5
views/index.html
Normal file
5
views/index.html
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{{template "partials/header" .}}
|
||||||
|
|
||||||
|
<h1>{{.Title}}</h1>
|
||||||
|
|
||||||
|
{{template "partials/footer" .}}
|
10
views/layouts/main.html
Normal file
10
views/layouts/main.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Main</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
{{embed}}
|
||||||
|
</body>
|
||||||
|
</html>
|
1
views/partials/footer.html
Normal file
1
views/partials/footer.html
Normal file
|
@ -0,0 +1 @@
|
||||||
|
</h2>Footer</h2>
|
1
views/partials/header.html
Normal file
1
views/partials/header.html
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<h1>Header</h1>
|
Loading…
Add table
Add a link
Reference in a new issue