aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--controllers/preferences.go2
-rw-r--r--imageboard/main.go2
-rw-r--r--middleware/middleware.go7
-rw-r--r--processors/preferences.go76
-rw-r--r--processors/processors.go1
-rw-r--r--router/routes.go1
-rw-r--r--static/css/main.css28
-rw-r--r--static/scripts/preferences.js72
-rw-r--r--static/scripts/theme.js37
-rw-r--r--templates/layouts/main.django5
-rw-r--r--templates/preferences.django91
11 files changed, 248 insertions, 74 deletions
diff --git a/controllers/preferences.go b/controllers/preferences.go
index 86e0fb3..042e810 100644
--- a/controllers/preferences.go
+++ b/controllers/preferences.go
@@ -6,7 +6,7 @@ import (
"github.com/gofiber/fiber/v2"
)
-func PreferencesController(ctx *fiber.Ctx) error {
+func PreferencesPageController(ctx *fiber.Ctx) error {
ctx.Locals("Title", "Site Preferences")
return shortcuts.Render(ctx, "preferences", nil)
}
diff --git a/imageboard/main.go b/imageboard/main.go
index 178fed2..ba7836b 100644
--- a/imageboard/main.go
+++ b/imageboard/main.go
@@ -40,8 +40,8 @@ func main() {
app.Use(cors.New())
processors.Initialize(app)
+ middleware.Initialize(app)
- app.Use(middleware.JSON)
app.Static("/static", "./static")
router.Initialize(app)
diff --git a/middleware/middleware.go b/middleware/middleware.go
new file mode 100644
index 0000000..ee51965
--- /dev/null
+++ b/middleware/middleware.go
@@ -0,0 +1,7 @@
+package middleware
+
+import "github.com/gofiber/fiber/v2"
+
+func Initialize(app *fiber.App) {
+ app.Use(JSON)
+}
diff --git a/processors/preferences.go b/processors/preferences.go
new file mode 100644
index 0000000..a96d1cb
--- /dev/null
+++ b/processors/preferences.go
@@ -0,0 +1,76 @@
+package processors
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "github.com/gofiber/fiber/v2"
+)
+
+type SitePreferences struct {
+ SidebarWidth string `json:"sidebar_width"`
+ MainContentWidth string `json:"main_content_width"`
+ H1FontSize string `json:"h1_font_size"`
+ BodyFontSize string `json:"body_font_size"`
+ SmallFontSize string `json:"small_font_size"`
+ PostsPerPage int `json:"posts_per_page"`
+}
+
+func PreferencesContextProcessor(context *fiber.Ctx) error {
+ defaultPreferences := SitePreferences{
+ SidebarWidth: "180px",
+ MainContentWidth: "1200px",
+ H1FontSize: "16px",
+ BodyFontSize: "13px",
+ SmallFontSize: "11px",
+ PostsPerPage: 42,
+ }
+
+ preferences := defaultPreferences
+
+ preferencesCookie := context.Cookies("preferences")
+ if preferencesCookie != "" {
+ _ = json.Unmarshal([]byte(preferencesCookie), &preferences)
+ }
+
+ bytes, err := json.Marshal(preferences)
+ if err == nil {
+ context.Cookie(&fiber.Cookie{
+ Name: "preferences",
+ Value: string(bytes),
+ Path: "/",
+ SameSite: fiber.CookieSameSiteLaxMode,
+ })
+ }
+
+ context.Locals("Preferences", preferences)
+ context.Locals("PreferencesCSS", preferencesToCSS(preferences))
+ return context.Next()
+}
+
+func preferencesToCSS(preferences SitePreferences) string {
+ return fmt.Sprintf(`
+ <style>
+ main {
+ width: %s;
+ }
+ body {
+ font-size: %s;
+ }
+ h1 {
+ font-size: %s;
+ }
+ small {
+ font-size: %s;
+ }
+ .sidebar {
+ width: %s;
+ }
+ </style>`,
+ preferences.MainContentWidth,
+ preferences.BodyFontSize,
+ preferences.H1FontSize,
+ preferences.SmallFontSize,
+ preferences.SidebarWidth,
+ )
+}
diff --git a/processors/processors.go b/processors/processors.go
index e060abe..bbd23fe 100644
--- a/processors/processors.go
+++ b/processors/processors.go
@@ -6,4 +6,5 @@ func Initialize(app *fiber.App) {
app.Use(RequestContextProcessor)
app.Use(MetaContextProcessor)
app.Use(SidebarContextProcessor)
+ app.Use(PreferencesContextProcessor)
}
diff --git a/router/routes.go b/router/routes.go
index 4541419..91b3816 100644
--- a/router/routes.go
+++ b/router/routes.go
@@ -11,6 +11,7 @@ func Initialize(router *fiber.App) {
main.Get("/", controllers.HomePageController)
main.Get("/login", controllers.LoginPageController)
main.Get("/register", controllers.RegisterPageController)
+ main.Get("/preferences", controllers.PreferencesPageController)
posts := router.Group("/posts")
posts.Get("/", controllers.PostsController)
diff --git a/static/css/main.css b/static/css/main.css
index 89fee52..aa92e1f 100644
--- a/static/css/main.css
+++ b/static/css/main.css
@@ -7,7 +7,6 @@ body {
padding: 0;
box-sizing: border-box;
font-family: "LXGW WenKai Mono TC", monospace;
- font-size: 13px;
}
body {
@@ -79,7 +78,6 @@ nav::before {
main {
display: flex;
- width: 1200px;
margin: 0 auto;
gap: 10px;
padding: 10px;
@@ -87,7 +85,6 @@ main {
}
.sidebar {
- width: 180px;
background-color: #0d0020;
border: 1px solid #4d4d80;
padding: 8px;
@@ -148,10 +145,14 @@ main {
.centered-main h1 {
color: #ffccff;
- font-size: 16px;
margin: 8px 0px;
}
+.content-main h1 {
+ color: #ff99cc;
+ margin-bottom: 8px;
+}
+
.centered-main p {
color: #99ffcc;
}
@@ -177,7 +178,8 @@ main {
input[type="text"],
input[type="email"],
-input[type="password"] {
+input[type="password"],
+input[type="number"] {
background-color: #1a0033;
border: 1px solid #9999ff;
color: #ccccff;
@@ -187,7 +189,8 @@ input[type="password"] {
input[type="text"]:focus,
input[type="email"]:focus,
-input[type="password"]:focus {
+input[type="password"]:focus,
+input[type="number"]:focus {
border-color: #ff99cc;
background-color: #260040;
outline: none;
@@ -298,8 +301,6 @@ footer::before {
margin: 8px 0;
}
-
-
.ibform {
background-color: #0d001a;
border: 1px solid #ff99cc;
@@ -328,7 +329,6 @@ footer::before {
.fg-sub small {
color: #ff99cc;
- font-size: 11px;
}
.fg-main label {
@@ -354,4 +354,14 @@ footer::before {
.q-img {
max-width: 768px;
+}
+
+.error {
+ color: #ffccff;
+ background-color: #330000;
+ border: 1px solid #ff0000;
+ padding: 8px;
+ margin-bottom: 16px;
+ text-align: center;
+ display: none;
} \ No newline at end of file
diff --git a/static/scripts/preferences.js b/static/scripts/preferences.js
new file mode 100644
index 0000000..d02e5cc
--- /dev/null
+++ b/static/scripts/preferences.js
@@ -0,0 +1,72 @@
+function validateCSSFieldValue(value) {
+ const validCSSValuePattern = /^(auto|(\d+(\.\d+)?px|em|rem|%)|calc\(.+\))$/;
+ return validCSSValuePattern.test(value);
+}
+
+function validateCSSFontSize(value) {
+ const validFontSizePattern = /^(small|medium|large|x-large|\d+(\.\d+)?(px|em|rem|%)?)$/;
+ return validFontSizePattern.test(value);
+}
+
+function showError(message) {
+ const errorMessage = document.getElementById('error-message');
+ errorMessage.textContent = message;
+ errorMessage.style.display = 'block';
+}
+
+function hideError() {
+ const errorMessage = document.getElementById('error-message');
+ errorMessage.textContent = '';
+ errorMessage.style.display = 'none';
+}
+
+function setPreferences() {
+ const preferences = {
+ sidebar_width: document.getElementById('sidebar-width').value,
+ main_content_width: document.getElementById('main-width').value,
+ h1_font_size: document.getElementById('h1-font-size').value,
+ body_font_size: document.getElementById('body-font-size').value,
+ small_font_size: document.getElementById('small-font-size').value,
+ posts_per_page: parseInt(document.getElementById('posts-per-page').value, 10)
+ }
+
+ for (const key in preferences) {
+ if (preferences[key] === '') {
+ showError(`Please fill in the ${key.replace(/_/g, ' ')} field.`);
+ return;
+ }
+
+ switch (key) {
+ case 'sidebar_width':
+ case 'main_content_width':
+ if (!validateCSSFieldValue(preferences[key])) {
+ showError(`Invalid value for ${key.replace(/_/g, ' ')}: ${preferences[key]}. Please enter a valid CSS value (e.g., '300px', '50%', 'auto').`);
+ return;
+ }
+ break;
+ case 'h1_font_size':
+ case 'body_font_size':
+ case 'small_font_size':
+ if (!validateCSSFontSize(preferences[key])) {
+ showError(`Invalid font size for ${key.replace(/_/g, ' ')}: ${preferences[key]}. Please enter a valid font size (e.g., 'small', 'medium', 'large', '16px', '1.2em').`);
+ return;
+ }
+ break;
+ case 'posts_per_page':
+ if (isNaN(preferences[key]) || preferences[key] <= 0) {
+ showError('Posts per page must be a positive integer.');
+ return;
+ }
+ break;
+ }
+ }
+
+ document.cookie = `preferences=${JSON.stringify(preferences)}; path=/; SameSite=Lax;`
+ window.location.reload()
+}
+
+function resetPreferences() {
+ hideError();
+ document.cookie = 'preferences=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; SameSite=Lax;'
+ window.location.reload()
+}
diff --git a/static/scripts/theme.js b/static/scripts/theme.js
deleted file mode 100644
index 0ba6625..0000000
--- a/static/scripts/theme.js
+++ /dev/null
@@ -1,37 +0,0 @@
-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/layouts/main.django b/templates/layouts/main.django
index 488f9e4..8e776d3 100644
--- a/templates/layouts/main.django
+++ b/templates/layouts/main.django
@@ -4,6 +4,8 @@
<meta charset="UTF-8" />
<title>{{ Title }} - {{ Appname }}</title>
<link rel="stylesheet" href="/static/css/main.css" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ {{ PreferencesCSS|safe }}
</head>
<body>
{% include 'partials/navbar.django' %}
@@ -22,4 +24,7 @@
<p>&copy; 2025 {{ Appname }}. All rights reserved.</p>
</footer>
</body>
+ {% block scripts %}
+
+ {% endblock %}
</html>
diff --git a/templates/preferences.django b/templates/preferences.django
index 53fc2fc..6b5f0e3 100644
--- a/templates/preferences.django
+++ b/templates/preferences.django
@@ -1,33 +1,72 @@
{% extends 'layouts/main.django' %}
{% block content %}
- <h2>Site Preferences</h2>
-
- <form id="preferences-form">
- <fieldset>
- <legend>Theme Settings</legend>
-
- <div class="form-group">
- <label>Theme</label>
- <div class="radio-group">
- <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 class="content-main">
+ <h1>Site Preferences</h1>
+ <p>Customize your experience on {{ Appname }}. These settings are stored in your cookies and will persist across sessions. However, they are not coupled with your account, so they don't apply to other devices or browsers.</p>
+ <form action="javascript:setPreferences()" method="post" class="ibform">
+ <div class="error" id="error-message"></div>
+ <div class="fgroup">
+ <div class="fg-main">
+ <label for="sidebar-width">Sidebar Width</label>
+ </div>
+ <div class="fg-sub">
+ <input type="text" id="sidebar-width" name="sidebar-width" value="{{ Preferences.SidebarWidth }}" placeholder="e.g., 250px" />
+ <small>Set the width of the sidebar. Use CSS units like px, em, or %.</small>
</div>
</div>
-
- <div class="form-actions">
- <button type="submit">SAVE PREFERENCES</button>
+ <div class="fgroup">
+ <div class="fg-main">
+ <label for="main-width">Main Content Width</label>
+ </div>
+ <div class="fg-sub">
+ <input type="text" id="main-width" name="main-width" value="{{ Preferences.MainContentWidth }}" placeholder="e.g., 800px" />
+ <small>Set the width of the main content area. Use CSS units like px, em, or %.</small>
+ </div>
</div>
- </fieldset>
- </form>
-
- <p>
- <a href="/">Back to Posts</a>
- </p>
+ <div class="fgroup">
+ <div class="fg-main">
+ <label for="h1-font-size">H1 Font Size</label>
+ </div>
+ <div class="fg-sub">
+ <input type="text" id="h1-font-size" name="h1-font-size" value="{{ Preferences.H1FontSize }}" min="10" max="100" />
+ <small>Set the font size for H1 elements. This will affect the main headings across the site. Use a valid CSS size (e.g., 16px, 1.5em, etc.).</small>
+ </div>
+ </div>
+ <div class="fgroup">
+ <div class="fg-main">
+ <label for="body-font-size">Body Font Size</label>
+ </div>
+ <div class="fg-sub">
+ <input type="text" id="body-font-size" name="body-font-size" value="{{ Preferences.BodyFontSize }}" min="10" max="100" />
+ <small>Set the font size for body text. This will affect the readability of the content across the site. Use a valid CSS size (e.g., 13px, 1em, etc.).</small>
+ </div>
+ </div>
+ <div class="fgroup">
+ <div class="fg-main">
+ <label for="small-font-size">Small Font Size</label>
+ </div>
+ <div class="fg-sub">
+ <input type="text" id="small-font-size" name="small-font-size" value="{{ Preferences.SmallFontSize }}" min="8" max="50" />
+ <small>Set the font size for small text elements. This will affect the visibility of smaller text across the site. Use a valid CSS size (e.g., 11px, 0.8em, etc.).</small>
+ </div>
+ </div>
+ <div class="fgroup">
+ <div class="fg-main">
+ <label for="posts-per-page">Posts Per Page</label>
+ </div>
+ <div class="fg-sub">
+ <input type="number" id="posts-per-page" name="posts-per-page" value="{{ Preferences.PostsPerPage }}" min="1" max="100" />
+ <small>Set the number of posts displayed per page. This can help manage load times</small>
+ </div>
+ </div>
+ <div class="fbtngrp">
+ <input type="submit" value="Save Preferences" />
+ <input type="button" value="Reset to Default" onclick="resetPreferences()" />
+ </div>
+ </form>
+ </div>
+{% endblock %}
+{% block scripts %}
+ <script src="/static/scripts/preferences.js" type="text/javascript"></script>
{% endblock %}