aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--controllers/home.go17
-rw-r--r--controllers/login.go12
-rw-r--r--controllers/posts.go22
-rw-r--r--controllers/preferences.go12
-rw-r--r--controllers/register.go12
-rw-r--r--go.mod4
-rw-r--r--go.sum14
-rw-r--r--imageboard/main.go4
-rw-r--r--router/routes.go5
-rw-r--r--static/css/main.css324
-rw-r--r--static/scripts/theme.js37
-rw-r--r--templates/home.html7
-rw-r--r--templates/layout.html13
-rw-r--r--templates/layouts/main.django19
-rw-r--r--templates/login.django33
-rw-r--r--templates/partials/navbar.django23
-rw-r--r--templates/partials/search.django16
-rw-r--r--templates/posts.django39
-rw-r--r--templates/preferences.django21
-rw-r--r--templates/register.django35
20 files changed, 626 insertions, 43 deletions
diff --git a/controllers/home.go b/controllers/home.go
deleted file mode 100644
index 1b513c8..0000000
--- a/controllers/home.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package controllers
-
-import (
- "imageboard/utils/shortcuts"
-
- "github.com/gofiber/fiber/v2"
-)
-
-func HomeController(ctx *fiber.Ctx) error {
- ctx.Locals("Title", "Home Page")
- customdata := struct {
- Custommessage string
- }{
- Custommessage: "Welcome to the Imageboard!",
- }
- return shortcuts.Render(ctx, "home", customdata)
-}
diff --git a/controllers/login.go b/controllers/login.go
new file mode 100644
index 0000000..1d6bc5e
--- /dev/null
+++ b/controllers/login.go
@@ -0,0 +1,12 @@
+package controllers
+
+import (
+ "imageboard/utils/shortcuts"
+
+ "github.com/gofiber/fiber/v2"
+)
+
+func LoginController(ctx *fiber.Ctx) error {
+ ctx.Locals("Title", "Login")
+ return shortcuts.Render(ctx, "login", nil)
+}
diff --git a/controllers/posts.go b/controllers/posts.go
new file mode 100644
index 0000000..6fdcd26
--- /dev/null
+++ b/controllers/posts.go
@@ -0,0 +1,22 @@
+package controllers
+
+import (
+ "imageboard/utils/shortcuts"
+
+ "github.com/gofiber/fiber/v2"
+)
+
+func PostsController(ctx *fiber.Ctx) error {
+ ctx.Locals("Title", "Posts")
+
+ searchQuery := ctx.Query("tags", "")
+
+ customdata := struct {
+ SearchQuery string
+ Posts []interface{}
+ }{
+ SearchQuery: searchQuery,
+ Posts: []interface{}{},
+ }
+ return shortcuts.Render(ctx, "posts", customdata)
+}
diff --git a/controllers/preferences.go b/controllers/preferences.go
new file mode 100644
index 0000000..86e0fb3
--- /dev/null
+++ b/controllers/preferences.go
@@ -0,0 +1,12 @@
+package controllers
+
+import (
+ "imageboard/utils/shortcuts"
+
+ "github.com/gofiber/fiber/v2"
+)
+
+func PreferencesController(ctx *fiber.Ctx) error {
+ ctx.Locals("Title", "Site Preferences")
+ return shortcuts.Render(ctx, "preferences", nil)
+}
diff --git a/controllers/register.go b/controllers/register.go
new file mode 100644
index 0000000..3be4e64
--- /dev/null
+++ b/controllers/register.go
@@ -0,0 +1,12 @@
+package controllers
+
+import (
+ "imageboard/utils/shortcuts"
+
+ "github.com/gofiber/fiber/v2"
+)
+
+func RegisterController(ctx *fiber.Ctx) error {
+ ctx.Locals("Title", "Register")
+ return shortcuts.Render(ctx, "register", nil)
+}
diff --git a/go.mod b/go.mod
index b56a0c4..dc538e8 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@ go 1.24.4
require (
github.com/gofiber/fiber/v2 v2.52.8
github.com/gofiber/storage/postgres/v2 v2.0.3
- github.com/gofiber/template/html/v2 v2.1.3
+ github.com/gofiber/template/django/v3 v3.1.14
github.com/joho/godotenv v1.5.1
golang.org/x/crypto v0.31.0
gorm.io/driver/postgres v1.6.0
@@ -14,6 +14,7 @@ require (
require (
github.com/andybalholm/brotli v1.1.0 // indirect
+ github.com/flosch/pongo2/v6 v6.0.0 // indirect
github.com/gofiber/template v1.8.3 // indirect
github.com/gofiber/utils v1.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect
@@ -28,6 +29,7 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
+ github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
diff --git a/go.sum b/go.sum
index f1c5471..9799da1 100644
--- a/go.sum
+++ b/go.sum
@@ -3,14 +3,16 @@ github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer5
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/flosch/pongo2/v6 v6.0.0 h1:lsGru8IAzHgIAw6H2m4PCyleO58I40ow6apih0WprMU=
+github.com/flosch/pongo2/v6 v6.0.0/go.mod h1:CuDpFm47R0uGGE7z13/tTlt1Y6zdxvr2RLT5LJhsHEU=
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/storage/postgres/v2 v2.0.3 h1:pN2PAKZMhy7oUkyZ3zS4fPZOVYa8gH/pciBkCw150K0=
github.com/gofiber/storage/postgres/v2 v2.0.3/go.mod h1:6Hr+F+1/gslAsdpiJY2jwSJaJe368oTIJoCrUewfbRo=
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/template/django/v3 v3.1.14 h1:SvTvs+u5vTZuu1Y2pMUD2NhaGIjBj9FmDA3XD50QBvw=
+github.com/gofiber/template/django/v3 v3.1.14/go.mod h1:gP4vH+T1ajZw7yaejqG1dZVdHQkMC/jPoQbmlG812I0=
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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@@ -31,6 +33,10 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@@ -42,6 +48,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
+github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -64,6 +72,8 @@ golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
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/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/imageboard/main.go b/imageboard/main.go
index fbba38c..a8dc2c0 100644
--- a/imageboard/main.go
+++ b/imageboard/main.go
@@ -15,7 +15,7 @@ import (
"github.com/gofiber/fiber/v2/middleware/helmet"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/fiber/v2/middleware/recover"
- "github.com/gofiber/template/html/v2"
+ "github.com/gofiber/template/django/v3"
)
func main() {
@@ -23,7 +23,7 @@ func main() {
log.Println("Warning: AppSecret is set to a default value which is not secure. Please set a strong random secret in your APP_SECRET environment variable or .env file.")
}
- engine := html.New("./templates", ".html")
+ engine := django.New("./templates", ".django")
engine.Reload(config.Server.IsDevMode)
app := fiber.New(fiber.Config{
Views: engine,
diff --git a/router/routes.go b/router/routes.go
index 814b767..216719f 100644
--- a/router/routes.go
+++ b/router/routes.go
@@ -7,7 +7,10 @@ import (
)
func Initialize(router *fiber.App) {
- router.Get("/", controllers.HomeController)
+ router.Get("/", controllers.PostsController)
+ router.Get("/register", controllers.RegisterController)
+ router.Get("/login", controllers.LoginController)
+ router.Get("/preferences", controllers.PreferencesController)
router.Use(func(c *fiber.Ctx) error {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
diff --git a/static/css/main.css b/static/css/main.css
new file mode 100644
index 0000000..47a393e
--- /dev/null
+++ b/static/css/main.css
@@ -0,0 +1,324 @@
+:root {
+ --bg-main: #ffffff;
+ --bg-section: #f8f8f8;
+ --bg-nav: #e0e0e0;
+ --text-main: #000000;
+ --text-dim: #666666;
+ --text-active: #ff0000;
+ --link-default: #0000ee;
+ --link-visited: #551a8b;
+ --link-hover: #ff0000;
+ --border-main: #c0c0c0;
+ --border-dark: #808080;
+ --button-bg: #e0e0e0;
+ --button-shadow: #808080;
+ --input-bg: #ffffff;
+ --error-bg: #ffe0e0;
+ --error-border: #ff0000;
+ --success-bg: #e0ffe0;
+ --success-border: #00aa00;
+}
+
+[data-theme="dark"] {
+ --bg-main: #000000;
+ --bg-section: #1a1a1a;
+ --bg-nav: #333333;
+ --text-main: #c0c0c0;
+ --text-dim: #808080;
+ --text-active: #ff6666;
+ --link-default: #6699ff;
+ --link-visited: #cc99ff;
+ --link-hover: #ffff66;
+ --border-main: #666666;
+ --border-dark: #999999;
+ --button-bg: #404040;
+ --button-shadow: #202020;
+ --input-bg: #1a1a1a;
+ --error-bg: #330000;
+ --error-border: #ff6666;
+ --success-bg: #003300;
+ --success-border: #66ff66;
+}
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: "MS Gothic", "MS ゴシック", "Courier New", monospace;
+ font-size: 12px;
+ line-height: 1.2;
+ background: var(--bg-main);
+ color: var(--text-main);
+ width: 800px;
+ margin: 0 auto;
+}
+
+nav {
+ background: var(--bg-nav);
+ border: 2px outset var(--border-main);
+ padding: 6px 8px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 8px;
+}
+
+nav div {
+ display: flex;
+ gap: 12px;
+ align-items: center;
+}
+
+nav a {
+ color: var(--link-default);
+ text-decoration: underline;
+ font-size: 12px;
+ font-weight: normal;
+}
+
+nav a:visited {
+ color: var(--link-visited);
+}
+
+nav a:hover {
+ color: var(--link-hover);
+}
+
+nav a.active {
+ color: var(--text-active);
+ font-weight: bold;
+ text-decoration: none;
+}
+
+main {
+ padding: 8px;
+ min-height: 400px;
+}
+
+h1 {
+ font-size: 14px;
+ font-weight: bold;
+ text-align: center;
+ margin-bottom: 12px;
+ color: var(--text-main);
+}
+
+h2 {
+ font-size: 13px;
+ font-weight: bold;
+ margin-bottom: 8px;
+ color: var(--text-main);
+}
+
+h3,
+h4 {
+ font-size: 12px;
+ font-weight: bold;
+ margin-bottom: 6px;
+ color: var(--text-main);
+}
+
+form {
+ margin: 8px 0;
+}
+
+label {
+ display: block;
+ font-weight: bold;
+ font-size: 12px;
+ margin: 4px 0 2px 0;
+ color: var(--text-main);
+}
+
+input,
+textarea,
+select {
+ background: var(--input-bg);
+ color: var(--text-main);
+ border: 2px inset var(--border-main);
+ font-family: inherit;
+ font-size: 12px;
+ padding: 2px 4px;
+ margin-bottom: 6px;
+}
+
+input[type="text"],
+input[type="password"],
+input[type="email"],
+textarea {
+ width: 180px;
+}
+
+input[type="checkbox"],
+input[type="radio"] {
+ width: auto;
+ margin-right: 4px;
+}
+
+button,
+input[type="submit"] {
+ background: var(--button-bg);
+ color: var(--text-main);
+ border: 2px outset var(--border-main);
+ font-family: inherit;
+ font-size: 12px;
+ padding: 3px 8px;
+ cursor: pointer;
+ margin: 2px 4px 2px 0;
+}
+
+button:hover,
+input[type="submit"]:hover {
+ background: var(--bg-section);
+}
+
+button:active,
+input[type="submit"]:active {
+ border: 2px inset var(--border-main);
+}
+
+.posts-container {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 4px;
+ margin: 8px 0;
+}
+
+article {
+ background: var(--bg-section);
+ border: 1px solid var(--border-main);
+ padding: 4px;
+ text-align: center;
+}
+
+article img {
+ width: 100%;
+ height: 120px;
+ object-fit: cover;
+ border: 1px solid var(--border-dark);
+ margin-bottom: 4px;
+}
+
+article h4 {
+ font-size: 11px;
+ font-weight: bold;
+ margin: 2px 0;
+ color: var(--text-main);
+}
+
+article p {
+ font-size: 10px;
+ color: var(--text-dim);
+ margin: 1px 0;
+}
+
+aside {
+ background: var(--bg-section);
+ border: 2px inset var(--border-main);
+ padding: 6px;
+ margin: 8px 0;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+aside input[type="text"] {
+ flex: 1;
+ margin-bottom: 0;
+}
+
+.empty-state {
+ background: var(--bg-section);
+ border: 2px inset var(--border-main);
+ padding: 24px;
+ text-align: center;
+ margin: 12px 0;
+}
+
+.empty-state h3 {
+ color: var(--text-main);
+ margin-bottom: 8px;
+}
+
+.error-message,
+.error {
+ background: var(--error-bg);
+ color: var(--text-main);
+ border: 1px solid var(--error-border);
+ padding: 6px;
+ margin: 6px 0;
+ text-align: center;
+}
+
+.success-message,
+.success {
+ background: var(--success-bg);
+ color: var(--text-main);
+ border: 1px solid var(--success-border);
+ padding: 6px;
+ margin: 6px 0;
+ text-align: center;
+}
+
+footer {
+ background: var(--bg-nav);
+ border: 2px outset var(--border-main);
+ padding: 8px;
+ text-align: center;
+ font-size: 10px;
+ color: var(--text-dim);
+ margin-top: 16px;
+}
+
+footer p {
+ margin: 1px 0;
+}
+
+a {
+ color: var(--link-default);
+ text-decoration: underline;
+}
+
+a:visited {
+ color: var(--link-visited);
+}
+
+a:hover {
+ color: var(--link-hover);
+}
+
+p {
+ margin: 4px 0;
+ line-height: 1.3;
+}
+
+small {
+ font-size: 10px;
+ color: var(--text-dim);
+}
+
+.button-group {
+ margin: 8px 0;
+ display: flex;
+ gap: 4px;
+}
+
+section {
+ background: var(--bg-section);
+ border: 2px inset var(--border-main);
+ padding: 12px;
+ margin: 12px 0;
+}
+
+section h2 {
+ text-align: center;
+ margin-bottom: 12px;
+ color: var(--text-main);
+}
+
+.center {
+ text-align: center;
+} \ No newline at end of file
diff --git a/static/scripts/theme.js b/static/scripts/theme.js
new file mode 100644
index 0000000..0ba6625
--- /dev/null
+++ b/static/scripts/theme.js
@@ -0,0 +1,37 @@
+document.addEventListener('DOMContentLoaded', function () {
+ const savedTheme = localStorage.getItem('theme') || 'light';
+ document.documentElement.setAttribute('data-theme', savedTheme);
+
+ const preferencesForm = document.getElementById('preferences-form');
+ if (preferencesForm) {
+ const themeRadios = document.querySelectorAll('input[name="theme"]');
+ themeRadios.forEach(radio => {
+ if (radio.value === savedTheme) {
+ radio.checked = true;
+ }
+ });
+
+ preferencesForm.addEventListener('submit', function (e) {
+ e.preventDefault();
+ const selectedTheme = document.querySelector('input[name="theme"]:checked').value;
+ localStorage.setItem('theme', selectedTheme);
+ document.documentElement.setAttribute('data-theme', selectedTheme);
+
+ let successMsg = document.querySelector('.success-message');
+ if (successMsg) {
+ successMsg.remove();
+ }
+
+ const message = document.createElement('div');
+ message.className = 'success-message';
+ message.textContent = 'Preferences saved successfully!';
+ preferencesForm.parentNode.insertBefore(message, preferencesForm);
+
+ setTimeout(() => {
+ if (message.parentNode) {
+ message.remove();
+ }
+ }, 3000);
+ });
+ }
+}); \ No newline at end of file
diff --git a/templates/home.html b/templates/home.html
deleted file mode 100644
index 708a02e..0000000
--- a/templates/home.html
+++ /dev/null
@@ -1,7 +0,0 @@
-{{define "content"}}
-<main>
- <h2>{{.Title}}</h2>
- <p>Welcome to {{.Appname}}</p>
- <p>Custom Data: {{ .Custommessage }}</p>
-</main>
-{{end}} {{template "layout" .}}
diff --git a/templates/layout.html b/templates/layout.html
deleted file mode 100644
index d2a5b98..0000000
--- a/templates/layout.html
+++ /dev/null
@@ -1,13 +0,0 @@
-{{define "layout"}}
-<!DOCTYPE html>
-<html lang="en">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>{{.Title}} - {{.Appname}}</title>
- </head>
- <body>
- {{template "content" .}}
- </body>
-</html>
-{{end}}
diff --git a/templates/layouts/main.django b/templates/layouts/main.django
new file mode 100644
index 0000000..5c9eeb6
--- /dev/null
+++ b/templates/layouts/main.django
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8" />
+ <title>{{ Title }} - {{ Appname }}</title>
+ <link rel="stylesheet" href="/css/main.css" />
+ </head>
+ <body>
+ {% include 'partials/navbar.django' %}
+
+ <main>{{ embed }}</main>
+
+ <footer>
+ <p>&copy; 2025 {{ Appname }}. All rights reserved.</p>
+ </footer>
+
+ <script src="/scripts/theme.js"></script>
+ </body>
+</html>
diff --git a/templates/login.django b/templates/login.django
new file mode 100644
index 0000000..e3d632a
--- /dev/null
+++ b/templates/login.django
@@ -0,0 +1,33 @@
+<h2>Login to {{ Appname }}</h2>
+
+{% if Error %}
+ <div class="error">{{ Error }}</div>
+{% endif %}
+
+<form action="/login" method="POST">
+ <table>
+ <tr>
+ <th colspan="2">User Login</th>
+ </tr>
+ <tr>
+ <td><label for="username">Username or Email:</label></td>
+ <td><input type="text" id="username" name="username" required value="{{ Username }}" /></td>
+ </tr>
+ <tr>
+ <td><label for="password">Password:</label></td>
+ <td><input type="password" id="password" name="password" required /></td>
+ </tr>
+ <tr>
+ <td colspan="2" class="center">
+ <input type="submit" value="LOGIN" />
+ </td>
+ </tr>
+ </table>
+</form>
+
+<p>
+ Don't have an account? <a href="/register">Register here</a>
+</p>
+<p>
+ <a href="/forgot-password">Forgot your password?</a>
+</p>
diff --git a/templates/partials/navbar.django b/templates/partials/navbar.django
new file mode 100644
index 0000000..52596ee
--- /dev/null
+++ b/templates/partials/navbar.django
@@ -0,0 +1,23 @@
+<nav>
+ <div class="nav-left">
+ <a href="/">{{ Appname }}</a>
+ <a href="/" class="{% if request.path == '/' %}active{% endif %}">POSTS</a>
+ <a href="/comments" class="{% if request.path == '/comments' %}active{% endif %}">COMMENTS</a>
+ <a href="/tags" class="{% if request.path == '/tags' %}active{% endif %}">TAGS</a>
+ {% if User %}{% if User.IsAdmin %}
+ <a href="/users" class="{% if request.path == '/users' %}active{% endif %}">USERS</a>
+ {% endif %}{% endif %}
+ </div>
+
+ <div class="nav-right">
+ {% if User %}
+ <a href="/account">{{ User.Username }}</a>
+ <a href="/preferences" class="{% if request.path == '/preferences' %}active{% endif %}">⚙</a>
+ <a href="/logout">LOGOUT</a>
+ {% else %}
+ <a href="/login" class="{% if request.path == '/login' %}active{% endif %}">LOGIN</a>
+ <a href="/register" class="{% if request.path == '/register' %}active{% endif %}">REGISTER</a>
+ <a href="/preferences" class="{% if request.path == '/preferences' %}active{% endif %}">⚙</a>
+ {% endif %}
+ </div>
+</nav>
diff --git a/templates/partials/search.django b/templates/partials/search.django
new file mode 100644
index 0000000..58c2480
--- /dev/null
+++ b/templates/partials/search.django
@@ -0,0 +1,16 @@
+<form action="/" method="GET">
+ <div class="search-box">
+ <table>
+ <tr>
+ <th>Search Posts</th>
+ </tr>
+ <tr>
+ <td>
+ <input type="text" name="tags" placeholder="tags..." value="{{ SearchQuery }}" />
+ <input type="submit" value="SEARCH" />
+ <input type="button" value="CLEAR" onclick="this.form.reset(); window.location.href='/';" />
+ </td>
+ </tr>
+ </table>
+ </div>
+</form>
diff --git a/templates/posts.django b/templates/posts.django
new file mode 100644
index 0000000..1a22b97
--- /dev/null
+++ b/templates/posts.django
@@ -0,0 +1,39 @@
+{% include 'partials/search.django' %}
+
+<h2>{{ Title }}</h2>
+
+<div class="posts-grid">
+ {% if Posts %}
+ {% for post in Posts %}
+ <div class="post-cell">
+ <img src="/uploads/thumbnails/{{ post.FileName }}" alt="{{ post.Title }}" class="post-img" />
+ <div class="post-title">
+ {% if post.Title %}
+ {{ post.Title }}
+ {% else %}
+ Post #{{ post.ID }}
+ {% endif %}
+ </div>
+ <div class="post-info">{{ post.Tags|length }} tags</div>
+ </div>
+ {% endfor %}
+ {% else %}
+ <table class="post-table">
+ <tr>
+ <th>NO POSTS FOUND!</th>
+ </tr>
+ <tr>
+ <td class="center">
+ Be the first to share something awesome!<br>
+ <input type="button" value="UPLOAD IMAGE" onclick="location.href='/upload'">
+ </td>
+ </tr>
+ </table>
+ {% endif %}
+</div>
+
+<p class="center">
+ <input type="button" value="UPLOAD IMAGE" onclick="location.href='/upload'">
+</p>
+ <button class="secondary" onclick="location.href='/tags'">BROWSE TAGS</button>
+</div>
diff --git a/templates/preferences.django b/templates/preferences.django
new file mode 100644
index 0000000..837d94a
--- /dev/null
+++ b/templates/preferences.django
@@ -0,0 +1,21 @@
+<h2>Site Preferences</h2>
+
+<form id="preferences-form">
+ <div>
+ <label>Theme</label>
+ <div>
+ <input type="radio" name="theme" value="light" id="theme-light" />
+ <label for="theme-light">Light Mode</label>
+ </div>
+ <div>
+ <input type="radio" name="theme" value="dark" id="theme-dark" />
+ <label for="theme-dark">Dark Mode</label>
+ </div>
+ </div>
+
+ <button type="submit">SAVE PREFERENCES</button>
+</form>
+
+<p>
+ <a href="/">Back to Posts</a>
+</p>
diff --git a/templates/register.django b/templates/register.django
new file mode 100644
index 0000000..c096104
--- /dev/null
+++ b/templates/register.django
@@ -0,0 +1,35 @@
+<h2>Join {{ Appname }}</h2>
+
+{% if Error %}
+ <div class="error-message">{{ Error }}</div>
+{% endif %}
+
+<form action="/register" method="POST">
+ <div>
+ <label for="username">Username</label>
+ <input type="text" id="username" name="username" required value="{{ Username }}" minlength="3" maxlength="72" pattern="[a-zA-Z0-9_-]+" />
+ <small>3-72 characters, letters, numbers, underscores, and hyphens only</small>
+ </div>
+
+ <div>
+ <label for="email">Email Address</label>
+ <input type="email" id="email" name="email" required value="{{ Email }}" />
+ </div>
+
+ <div>
+ <label for="password">Password</label>
+ <input type="password" id="password" name="password" required minlength="8" />
+ <small>Minimum 8 characters</small>
+ </div>
+
+ <div>
+ <label for="confirm_password">Confirm Password</label>
+ <input type="password" id="confirm_password" name="confirm_password" required />
+ </div>
+
+ <button type="submit">CREATE ACCOUNT</button>
+</form>
+
+<p>
+ Already have an account? <a href="/login">Login here</a>
+</p>