aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--config/enums.go2
-rw-r--r--go.mod2
-rw-r--r--go.sum5
-rw-r--r--imageboard/main.go2
-rw-r--r--middleware/middleware.go1
-rw-r--r--middleware/minifier.go79
-rw-r--r--router/routes.go2
-rw-r--r--static/css/main.css68
-rw-r--r--static/scripts/resize.js80
-rw-r--r--static/scripts/upload.js5
-rw-r--r--templates/partials/search.django8
-rw-r--r--templates/posts/single.django58
-rw-r--r--utils/handlers/req_map.go10
13 files changed, 307 insertions, 15 deletions
diff --git a/config/enums.go b/config/enums.go
index e825d61..b6282e1 100644
--- a/config/enums.go
+++ b/config/enums.go
@@ -51,8 +51,8 @@ type Rating string
const (
RatingSafe Rating = "Safe"
- RatingQuestionable Rating = "Questionable"
RatingSensitive Rating = "Sensitive"
+ RatingQuestionable Rating = "Questionable"
RatingExplicit Rating = "Explicit"
)
diff --git a/go.mod b/go.mod
index 6b30c0e..0a2c04c 100644
--- a/go.mod
+++ b/go.mod
@@ -41,6 +41,8 @@ require (
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/rs/xid v1.6.0 // indirect
+ github.com/tdewolff/minify/v2 v2.23.8 // indirect
+ github.com/tdewolff/parse/v2 v2.8.1 // indirect
github.com/tinylib/msgp v1.3.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect
diff --git a/go.sum b/go.sum
index d77af21..3d9df22 100644
--- a/go.sum
+++ b/go.sum
@@ -76,6 +76,11 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/tdewolff/minify/v2 v2.23.8 h1:tvjHzRer46kwOfpdCBCWsDblCw3QtnLJRd61pTVkyZ8=
+github.com/tdewolff/minify/v2 v2.23.8/go.mod h1:VW3ISUd3gDOZuQ/jwZr4sCzsuX+Qvsx87FDMjk6Rvno=
+github.com/tdewolff/parse/v2 v2.8.1 h1:J5GSHru6o3jF1uLlEKVXkDxxcVx6yzOlIVIotK4w2po=
+github.com/tdewolff/parse/v2 v2.8.1/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo=
+github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww=
github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
diff --git a/imageboard/main.go b/imageboard/main.go
index 8e6fef5..511269b 100644
--- a/imageboard/main.go
+++ b/imageboard/main.go
@@ -42,8 +42,6 @@ func main() {
processors.Initialize(app)
middleware.Initialize(app)
- app.Static("/static", "./static")
-
router.Initialize(app)
log.Fatalf("Server failed to start: %v", app.Listen(fmt.Sprintf("%s:%d", config.Server.Host, config.Server.Port)))
diff --git a/middleware/middleware.go b/middleware/middleware.go
index ee51965..171e107 100644
--- a/middleware/middleware.go
+++ b/middleware/middleware.go
@@ -4,4 +4,5 @@ import "github.com/gofiber/fiber/v2"
func Initialize(app *fiber.App) {
app.Use(JSON)
+ app.Use(Minifier)
}
diff --git a/middleware/minifier.go b/middleware/minifier.go
new file mode 100644
index 0000000..09d8813
--- /dev/null
+++ b/middleware/minifier.go
@@ -0,0 +1,79 @@
+package middleware
+
+import (
+ "bytes"
+ "regexp"
+
+ "github.com/gofiber/fiber/v2"
+ "github.com/tdewolff/minify/v2"
+ "github.com/tdewolff/minify/v2/css"
+ "github.com/tdewolff/minify/v2/html"
+ "github.com/tdewolff/minify/v2/js"
+ "github.com/tdewolff/minify/v2/json"
+ "github.com/tdewolff/minify/v2/xml"
+)
+
+func Minifier(context *fiber.Ctx) error {
+ var (
+ minifyHTML = true
+ minifyCSS = true
+ minifyJS = true
+ minifyXML = true
+ minifyJSON = true
+ )
+
+ m := minify.New()
+
+ if minifyHTML {
+ m.Add("text/html", &html.Minifier{
+ KeepEndTags: true,
+ KeepDocumentTags: true,
+ })
+ }
+
+ if minifyCSS {
+ m.Add("text/css", &css.Minifier{})
+ }
+
+ if minifyJS {
+ m.Add("application/javascript", &js.Minifier{})
+ m.Add("application/x-javascript", &js.Minifier{})
+ m.Add("text/javascript", &js.Minifier{})
+ m.AddRegexp(regexp.MustCompile("^(application|text)/(x-)?(java|ecma)script$"), &js.Minifier{})
+ }
+
+ if minifyXML {
+ m.AddRegexp(regexp.MustCompile("xml$"), &xml.Minifier{})
+ }
+
+ if minifyJSON {
+ m.AddRegexp(regexp.MustCompile("json$"), &json.Minifier{})
+ }
+
+ if err := context.Next(); err != nil {
+ return err
+ }
+
+ statusCode := context.Response().StatusCode()
+ if statusCode != fiber.StatusOK && statusCode != fiber.StatusNotModified {
+ return nil
+ }
+
+ if statusCode == fiber.StatusNotModified {
+ return nil
+ }
+ origBody := context.Response().Body()
+ if len(origBody) == 0 {
+ return nil
+ }
+
+ context.Response().ResetBody()
+
+ mimetype := string(context.Response().Header.Peek("Content-Type"))
+
+ if err := m.Minify(mimetype, context.Response().BodyWriter(), bytes.NewReader(origBody)); err != nil {
+ context.Response().BodyWriter().Write(origBody)
+ }
+
+ return nil
+}
diff --git a/router/routes.go b/router/routes.go
index bac5237..786e592 100644
--- a/router/routes.go
+++ b/router/routes.go
@@ -7,6 +7,8 @@ import (
)
func Initialize(router *fiber.App) {
+ router.Static("/static", "./static")
+
main := router.Group("/")
main.Get("/", controllers.HomePageController)
diff --git a/static/css/main.css b/static/css/main.css
index f253f7c..018ca2a 100644
--- a/static/css/main.css
+++ b/static/css/main.css
@@ -89,6 +89,7 @@ main {
border: 1px solid #4d4d80;
padding: 8px;
height: fit-content;
+ flex-shrink: 0;
}
.sidebar h3 {
@@ -128,6 +129,7 @@ main {
}
.content {
+ overflow: hidden;
flex: 1;
background-color: #0a0015;
border: 1px solid #4d4d80;
@@ -795,4 +797,70 @@ footer::before {
.post-tags .post-tag:hover {
background-color: rgba(255, 255, 255, 0.2);
transform: scale(1.1);
+}
+
+.single-post {
+ width: 100%;
+ max-width: 100%;
+ overflow: hidden;
+ min-width: 0;
+}
+
+.post-image-container {
+ width: 100%;
+ max-width: 100%;
+ overflow: auto;
+}
+
+.post-image-container img {
+ display: block;
+ max-width: none;
+ height: auto;
+}
+
+.post-title {
+ color: #ff99cc;
+}
+
+.single-post-bar {
+ display: flex;
+ margin-bottom: 12px;
+ width: 100%;
+ padding: 8px;
+ border: 1px solid #4d4d80;
+ background-color: #0d001a;
+ flex-direction: row;
+ justify-content: space-between;
+}
+
+.size-selected {
+ color: #ff99cc;
+ font-weight: 700;
+}
+
+#post-image {
+ display: block;
+ height: auto;
+}
+
+#post-image.fit-width {
+ width: 100%;
+ height: auto;
+ max-width: 100%;
+}
+
+#post-image.fit-height {
+ width: auto;
+ max-width: none;
+}
+
+#post-image.fit-both {
+ max-width: 100%;
+ height: auto;
+}
+
+#post-image.fixed-size {
+ width: auto;
+ height: auto;
+ max-width: none;
} \ No newline at end of file
diff --git a/static/scripts/resize.js b/static/scripts/resize.js
new file mode 100644
index 0000000..95b75c1
--- /dev/null
+++ b/static/scripts/resize.js
@@ -0,0 +1,80 @@
+function getAvailableHeight() {
+ const navbar = document.querySelector('nav')
+ const postBar = document.querySelector('.single-post-bar')
+ const navbarHeight = navbar ? navbar.offsetHeight : 0
+ const postBarHeight = postBar ? postBar.offsetHeight : 0
+ const padding = 40
+ return window.innerHeight - navbarHeight - postBarHeight - padding
+}
+
+function updateSizeSelection(selectedSize) {
+ document.querySelectorAll('.single-post-bar-size-actions a').forEach((link) => {
+ link.classList.remove('size-selected')
+ if (link.onclick && link.onclick.toString().includes(selectedSize)) {
+ link.classList.add('size-selected')
+ }
+ })
+}
+
+let currentSize = 'fitBoth'
+
+function handleResize() {
+ if (currentSize === 'fitHeight' || currentSize === 'fitBoth') {
+ switchSize(currentSize)
+ }
+}
+
+function switchSize(size) {
+ currentSize = size
+ const img = document.getElementById('post-image')
+ const container = document.querySelector('.post-image-container')
+ const sizeData = sizes[size]
+
+ img.className = ''
+ img.style.width = ''
+ img.style.height = ''
+ img.style.maxWidth = ''
+ img.style.maxHeight = ''
+
+ if (size === 'fitHeight') {
+ const availableHeight = getAvailableHeight()
+ img.style.maxHeight = availableHeight + 'px'
+ img.style.width = 'auto'
+ img.style.maxWidth = 'none'
+ img.classList.add('fit-height')
+ } else if (size === 'fitWidth') {
+ img.style.width = '100%'
+ img.style.height = 'auto'
+ img.style.maxWidth = '100%'
+ img.classList.add('fit-width')
+ } else if (size === 'fitBoth') {
+ const availableHeight = getAvailableHeight()
+ const imageWidth = parseInt(sizeData.width || img.naturalWidth)
+ const imageHeight = parseInt(sizeData.height || img.naturalHeight)
+
+ if (imageHeight > imageWidth) {
+ img.style.maxHeight = availableHeight + 'px'
+ img.style.width = 'auto'
+ img.style.maxWidth = 'none'
+ } else {
+ img.style.width = '100%'
+ img.style.height = 'auto'
+ img.style.maxWidth = '100%'
+ }
+ img.classList.add('fit-both')
+ } else {
+ img.style.maxWidth = 'none'
+ if (sizeData.width) img.style.width = sizeData.width + 'px'
+ if (sizeData.height) img.style.height = sizeData.height + 'px'
+ img.classList.add('fixed-size')
+ }
+
+ img.src = sizeData.src
+ updateSizeSelection(size)
+}
+
+window.addEventListener('resize', handleResize)
+
+window.addEventListener('load', function () {
+ switchSize(currentSize)
+}) \ No newline at end of file
diff --git a/static/scripts/upload.js b/static/scripts/upload.js
index fe4c4ef..0a6cc17 100644
--- a/static/scripts/upload.js
+++ b/static/scripts/upload.js
@@ -77,7 +77,7 @@ function createPreviewElement(key, blob, type, nameOrUrl) {
const previewRatingForm = document.createElement('form');
previewRatingForm.className = 'preview-rating-form';
- ['Safe', 'Questionable', 'Sensitive', 'Explicit'].forEach((rating, idx) => {
+ ['Safe', 'Sensitive', 'Questionable', 'Explicit'].forEach((rating, idx) => {
const inputId = `rating-${rating.toLowerCase()}-${key}`;
const input = document.createElement('input');
input.type = 'radio';
@@ -625,6 +625,7 @@ function showImageProgress(previewElement) {
height: 100%;
width: 0%;
background-color: #4a9eff;
+ transition: width 0.3s ease;
`;
progressContainer.appendChild(progressBar);
@@ -663,7 +664,7 @@ function animateProgress(progressBar, duration) {
if (isCompleted) return;
const elapsed = Date.now() - startTime;
- const timeRatio = elapsed / (duration * 4);
+ const timeRatio = elapsed / duration;
let targetProgress;
if (timeRatio < 0.2) {
diff --git a/templates/partials/search.django b/templates/partials/search.django
index 4798717..86a85b8 100644
--- a/templates/partials/search.django
+++ b/templates/partials/search.django
@@ -10,14 +10,14 @@
<span class="checkbox-custom safe"></span>
</label>
<label class="rating-checkbox">
- <input type="checkbox" name="rating" value="questionable" {{ QueryRatings.Questionable|yesno:'checked,' }} />
- <span class="checkbox-custom questionable"></span>
- </label>
- <label class="rating-checkbox">
<input type="checkbox" name="rating" value="sensitive" {{ QueryRatings.Sensitive|yesno:'checked,' }} />
<span class="checkbox-custom sensitive"></span>
</label>
<label class="rating-checkbox">
+ <input type="checkbox" name="rating" value="questionable" {{ QueryRatings.Questionable|yesno:'checked,' }} />
+ <span class="checkbox-custom questionable"></span>
+ </label>
+ <label class="rating-checkbox">
<input type="checkbox" name="rating" value="explicit" {{ QueryRatings.Explicit|yesno:'checked,' }} />
<span class="checkbox-custom explicit"></span>
</label>
diff --git a/templates/posts/single.django b/templates/posts/single.django
index 4769309..8657ffe 100644
--- a/templates/posts/single.django
+++ b/templates/posts/single.django
@@ -5,6 +5,62 @@
<div class="error">{{ Error }}</div>
</div>
{% else %}
- Single Post
+ <div class="single-post">
+ <div class="single-post-bar">
+ {% if Post.Title %}
+ <h1 class="post-title">{{ Post.Title }}</h1>
+ {% else %}
+ <h1 class="post-title">Post #{{ Post.ID }}</h1>
+ {% endif %}
+ <div class="single-post-bar-size-actions">
+ <a href="javascript:void(0);" onclick="switchSize('fitBoth');" class="size-selected">Fit-Both</a>
+ <a href="javascript:void(0);" onclick="switchSize('fitHeight');">Fit-Height</a>
+ <a href="javascript:void(0);" onclick="switchSize('fitWidth');">Fit-Width</a>
+ <a href="javascript:void(0);" onclick="switchSize('small');">Small</a>
+ <a href="javascript:void(0);" onclick="switchSize('medium');">Medium</a>
+ <a href="javascript:void(0);" onclick="switchSize('large');">Large</a>
+ <a href="javascript:void(0);" onclick="switchSize('original');">Original</a>
+ </div>
+ </div>
+ <div class="post-image-container">
+ <img src="{{ CDNURL }}/medium/{{ Post.FileName }}" alt="{{ Post.Title }}" id="post-image" />
+ </div>
+ </div>
{% endif %}
{% endblock %}
+{% block scripts %}
+ <script type="text/javascript">
+ const sizes = {
+ fitBoth: {
+ src: '{{ CDNURL }}/medium/{{ Post.FileName }}'
+ },
+ fitWidth: {
+ src: '{{ CDNURL }}/medium/{{ Post.FileName }}'
+ },
+ fitHeight: {
+ src: '{{ CDNURL }}/medium/{{ Post.FileName }}'
+ },
+ small: {
+ src: '{{ CDNURL }}/small/{{ Post.FileName }}',
+ width: '{{ Post.Sizes.2.Width }}',
+ height: '{{ Post.Sizes.2.Height }}'
+ },
+ medium: {
+ src: '{{ CDNURL }}/medium/{{ Post.FileName }}',
+ width: '{{ Post.Sizes.3.Width }}',
+ height: '{{ Post.Sizes.3.Height }}'
+ },
+ large: {
+ src: '{{ CDNURL }}/large/{{ Post.FileName }}',
+ width: '{{ Post.Sizes.4.Width }}',
+ height: '{{ Post.Sizes.4.Height }}'
+ },
+ original: {
+ src: '{{ CDNURL }}/original/{{ Post.FileName }}',
+ width: '{{ Post.Sizes.5.Width }}',
+ height: '{{ Post.Sizes.5.Height }}'
+ }
+ }
+ </script>
+ <script type="text/javascript" src="/static/scripts/resize.js"></script>
+{% endblock %}
diff --git a/utils/handlers/req_map.go b/utils/handlers/req_map.go
index 2b1145c..700b00a 100644
--- a/utils/handlers/req_map.go
+++ b/utils/handlers/req_map.go
@@ -14,12 +14,12 @@ func ExtractRatingsAndMap(queryParams []config.QueryParam) ([]config.Rating, map
case "safe":
ratings = append(ratings, config.RatingSafe)
ratingsMap["Safe"] = true
- case "questionable":
- ratings = append(ratings, config.RatingQuestionable)
- ratingsMap["Questionable"] = true
case "sensitive":
ratings = append(ratings, config.RatingSensitive)
ratingsMap["Sensitive"] = true
+ case "questionable":
+ ratings = append(ratings, config.RatingQuestionable)
+ ratingsMap["Questionable"] = true
case "explicit":
ratings = append(ratings, config.RatingExplicit)
ratingsMap["Explicit"] = true
@@ -29,12 +29,12 @@ func ExtractRatingsAndMap(queryParams []config.QueryParam) ([]config.Rating, map
if len(ratings) == 0 {
ratings = []config.Rating{
config.RatingSafe,
- config.RatingQuestionable,
config.RatingSensitive,
+ config.RatingQuestionable,
}
ratingsMap["Safe"] = true
- ratingsMap["Questionable"] = true
ratingsMap["Sensitive"] = true
+ ratingsMap["Questionable"] = true
}
return ratings, ratingsMap
}