diff options
| author | Bobby <[email protected]> | 2025-07-18 14:18:39 +0530 |
|---|---|---|
| committer | Bobby <[email protected]> | 2025-07-18 14:18:39 +0530 |
| commit | 821773b12c07a4bc23628e7d98ac4b34da1eb9e1 (patch) | |
| tree | c37711d86bd893a9bc5a829653890ee75c29ee09 | |
| parent | 01e730c68a79862112798d4816625ddcd00350d9 (diff) | |
| download | imageboard-821773b12c07a4bc23628e7d98ac4b34da1eb9e1.tar.xz imageboard-821773b12c07a4bc23628e7d98ac4b34da1eb9e1.zip | |
template filters and image resizer
| -rw-r--r-- | filters/filters.go | 19 | ||||
| -rw-r--r-- | filters/naturaltime.go | 86 | ||||
| -rw-r--r-- | imageboard/main.go | 2 | ||||
| -rw-r--r-- | models/image.go | 20 | ||||
| -rw-r--r-- | static/css/main.css | 8 | ||||
| -rw-r--r-- | static/scripts/resize.js | 11 | ||||
| -rw-r--r-- | templates/layouts/main.django | 2 | ||||
| -rw-r--r-- | templates/login.django | 2 | ||||
| -rw-r--r-- | templates/partials/search.django | 2 | ||||
| -rw-r--r-- | templates/posts/list.django | 2 | ||||
| -rw-r--r-- | templates/posts/single.django | 116 | ||||
| -rw-r--r-- | templates/preferences.django | 10 | ||||
| -rw-r--r-- | templates/register.django | 2 |
13 files changed, 228 insertions, 54 deletions
diff --git a/filters/filters.go b/filters/filters.go new file mode 100644 index 0000000..9c1704a --- /dev/null +++ b/filters/filters.go @@ -0,0 +1,19 @@ +package filters + +import ( + "log" + + "github.com/flosch/pongo2/v6" +) + +func Initialize() { + filters := map[string]func(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error){ + "naturaltime": naturaltimeFilter, + } + + for name, filterFunc := range filters { + if err := pongo2.RegisterFilter(name, filterFunc); err != nil { + log.Println("Failed to register filter:", name, "Error:", err) + } + } +} diff --git a/filters/naturaltime.go b/filters/naturaltime.go new file mode 100644 index 0000000..4b7e773 --- /dev/null +++ b/filters/naturaltime.go @@ -0,0 +1,86 @@ +package filters + +import ( + "fmt" + "time" + + "github.com/flosch/pongo2/v6" +) + +func naturaltimeFilter(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) { + timeValue, ok := in.Interface().(time.Time) + if !ok { + return nil, &pongo2.Error{ + Sender: "filter:naturaltime", + OrigError: fmt.Errorf("input must be a time.Time, got %T", in.Interface()), + } + } + + now := time.Now() + diff := now.Sub(timeValue) + + if diff < 0 { + diff = -diff + if diff < time.Minute { + return pongo2.AsValue("in a few seconds"), nil + } else if diff < time.Hour { + minutes := int(diff.Minutes()) + if minutes == 1 { + return pongo2.AsValue("in 1 minute"), nil + } + return pongo2.AsValue(fmt.Sprintf("in %d minutes", minutes)), nil + } else if diff < 24*time.Hour { + hours := int(diff.Hours()) + if hours == 1 { + return pongo2.AsValue("in 1 hour"), nil + } + return pongo2.AsValue(fmt.Sprintf("in %d hours", hours)), nil + } else { + days := int(diff.Hours() / 24) + if days == 1 { + return pongo2.AsValue("in 1 day"), nil + } + return pongo2.AsValue(fmt.Sprintf("in %d days", days)), nil + } + } + + if diff < time.Minute { + return pongo2.AsValue("just now"), nil + } else if diff < time.Hour { + minutes := int(diff.Minutes()) + if minutes == 1 { + return pongo2.AsValue("1 minute ago"), nil + } + return pongo2.AsValue(fmt.Sprintf("%d minutes ago", minutes)), nil + } else if diff < 24*time.Hour { + hours := int(diff.Hours()) + if hours == 1 { + return pongo2.AsValue("1 hour ago"), nil + } + return pongo2.AsValue(fmt.Sprintf("%d hours ago", hours)), nil + } else if diff < 7*24*time.Hour { + days := int(diff.Hours() / 24) + if days == 1 { + return pongo2.AsValue("1 day ago"), nil + } + return pongo2.AsValue(fmt.Sprintf("%d days ago", days)), nil + } else if diff < 30*24*time.Hour { + weeks := int(diff.Hours() / (24 * 7)) + if weeks == 1 { + return pongo2.AsValue("1 week ago"), nil + } + return pongo2.AsValue(fmt.Sprintf("%d weeks ago", weeks)), nil + } else if diff < 365*24*time.Hour { + months := int(diff.Hours() / (24 * 30)) + if months == 1 { + return pongo2.AsValue("1 month ago"), nil + } + return pongo2.AsValue(fmt.Sprintf("%d months ago", months)), nil + } else { + years := int(diff.Hours() / (24 * 365)) + if years == 1 { + return pongo2.AsValue("1 year ago"), nil + } + return pongo2.AsValue(fmt.Sprintf("%d years ago", years)), nil + } +} diff --git a/imageboard/main.go b/imageboard/main.go index 511269b..13327f2 100644 --- a/imageboard/main.go +++ b/imageboard/main.go @@ -3,6 +3,7 @@ package main import (
"fmt"
"imageboard/config"
+ "imageboard/filters"
"imageboard/middleware"
"imageboard/processors"
"imageboard/router"
@@ -24,6 +25,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.")
}
+ filters.Initialize()
engine := django.New("./templates", ".django")
engine.Reload(config.Server.IsDevMode)
app := fiber.New(fiber.Config{
diff --git a/models/image.go b/models/image.go index d902ec7..17f369d 100644 --- a/models/image.go +++ b/models/image.go @@ -137,6 +137,26 @@ func (i *Image) GetOriginalDimensions() string { return "Unknown" } +func (i *Image) GetOriginalSize() *ImageSize { + return i.GetSize(config.ImageSizeTypeOriginal) +} + +func (i *Image) GetSmallSize() *ImageSize { + return i.GetSize(config.ImageSizeTypeSmall) +} + +func (i *Image) GetMediumSize() *ImageSize { + return i.GetSize(config.ImageSizeTypeMedium) +} + +func (i *Image) GetLargeSize() *ImageSize { + return i.GetSize(config.ImageSizeTypeLarge) +} + +func (i *Image) GetThumbnailSize() *ImageSize { + return i.GetSize(config.ImageSizeTypeThumbnail) +} + func (i *Image) GetAspectRatio() string { if fullSize := i.GetSize(config.ImageSizeTypeOriginal); fullSize != nil { if fullSize.Height == 0 { diff --git a/static/css/main.css b/static/css/main.css index 018ca2a..c6cb393 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -180,7 +180,8 @@ main { text-align: center; } -.search-container input[type="text"] { +.search-container input[type="text"], +.search-container .itext { width: 100%; flex: 1; } @@ -195,6 +196,7 @@ main { align-items: center; } +.itext, input[type="text"], input[type="email"], input[type="password"], @@ -206,6 +208,7 @@ input[type="url"] { padding: 3px 5px; } +.itext:focus, input[type="text"]:focus, input[type="email"]:focus, input[type="password"]:focus, @@ -359,7 +362,8 @@ footer::before { } -.fg-sub input { +.fg-sub input, +.fg-sub.itext { border-style: double; border-width: 3px; border-color: #9999ff; diff --git a/static/scripts/resize.js b/static/scripts/resize.js index 95b75c1..10ea02e 100644 --- a/static/scripts/resize.js +++ b/static/scripts/resize.js @@ -27,7 +27,6 @@ function handleResize() { function switchSize(size) { currentSize = size const img = document.getElementById('post-image') - const container = document.querySelector('.post-image-container') const sizeData = sizes[size] img.className = '' @@ -69,12 +68,10 @@ function switchSize(size) { img.classList.add('fixed-size') } - img.src = sizeData.src + if (img.src !== sizeData.src) { + img.src = sizeData.src + } updateSizeSelection(size) } -window.addEventListener('resize', handleResize) - -window.addEventListener('load', function () { - switchSize(currentSize) -})
\ No newline at end of file +window.addEventListener('resize', handleResize)
\ No newline at end of file diff --git a/templates/layouts/main.django b/templates/layouts/main.django index 8673b21..bb35e00 100644 --- a/templates/layouts/main.django +++ b/templates/layouts/main.django @@ -10,6 +10,8 @@ <link rel="icon" type="image/png" sizes="16x16" href="/static/images/icons/favicon-16x16.png" /> <link rel="manifest" href="/static/extra/site.webmanifest" /> {{ PreferencesCSS|safe }} + {% block head %} + {% endblock %} </head> <body> {% include 'partials/navbar.django' %} diff --git a/templates/login.django b/templates/login.django index f2136fc..4d23375 100644 --- a/templates/login.django +++ b/templates/login.django @@ -18,7 +18,7 @@ <label for="username">Username</label> </div> <div class="fg-sub"> - <input type="text" id="username" name="username" required value="{{ Username }}" minlength="3" maxlength="72" autocomplete="username" pattern="[a-zA-Z0-9_-]+" /> + <input type="text" class="itext" id="username" name="username" required value="{{ Username }}" minlength="3" maxlength="72" autocomplete="username" pattern="[a-zA-Z0-9_-]+" /> <small>3-72 characters, letters, numbers, underscores, and hyphens only</small> </div> </div> diff --git a/templates/partials/search.django b/templates/partials/search.django index 86a85b8..fc1ace4 100644 --- a/templates/partials/search.django +++ b/templates/partials/search.django @@ -1,7 +1,7 @@ <div class="search-container"> <form action="/posts" method="GET"> <h3>« Search Posts »</h3> - <input type="text" name="tags" class="search-tags-input" placeholder="enter some tags..." value="{{ QueryTags|default_if_none:'' }}" autocomplete="off" aria-label="Search tags" /> + <input type="text" class="itext" name="tags" class="search-tags-input" placeholder="enter some tags..." value="{{ QueryTags|default_if_none:'' }}" autocomplete="off" aria-label="Search tags" /> <input type="submit" value="Search" /> <input type="button" value="Clear" onclick="this.form.tags.value='';" /> <div class="rating-toggles"> diff --git a/templates/posts/list.django b/templates/posts/list.django index 3603cb7..3aca8d2 100644 --- a/templates/posts/list.django +++ b/templates/posts/list.django @@ -10,7 +10,7 @@ <div class="post-list"> {% for image in Posts %} <a href="/posts/{{ image.ID }}" class="post-item"> - <img src="{{ CDNURL }}/thumbnail/{{ image.FileName }}" alt="{{ image.Title }}" width="{{ image.Sizes.1.Width }}" height="{{ image.Sizes.1.Height }}" /> + <img src="{{ CDNURL }}/thumbnail/{{ image.FileName }}" alt="{{ image.Title }}" width="{{ image.GetThumbnailSize.Width }}" height="{{ image.GetThumbnailSize.Height }}" /> <div class="post-overlay"> <div class="post-overlay-top"> <div class="post-id">ID: {{ image.ID }}</div> diff --git a/templates/posts/single.django b/templates/posts/single.django index 8657ffe..ef6bc35 100644 --- a/templates/posts/single.django +++ b/templates/posts/single.django @@ -1,4 +1,66 @@ {% extends 'layouts/main.django' %} +{% block head %} + <script type="text/javascript" src="/static/scripts/resize.js"></script> + <script type="text/javascript"> + const sizes = { + fitBoth: { + src: '{{ CDNURL }}/medium/{{ Post.FileName }}', + width: '{{ Post.GetMediumSize.Width }}', + height: '{{ Post.GetMediumSize.Height }}' + }, + fitWidth: { + src: '{{ CDNURL }}/medium/{{ Post.FileName }}', + width: '{{ Post.GetMediumSize.Width }}', + height: '{{ Post.GetMediumSize.Height }}' + }, + fitHeight: { + src: '{{ CDNURL }}/medium/{{ Post.FileName }}', + width: '{{ Post.GetMediumSize.Width }}', + height: '{{ Post.GetMediumSize.Height }}' + }, + small: { + src: '{{ CDNURL }}/small/{{ Post.FileName }}', + width: '{{ Post.GetSmallSize.Width }}', + height: '{{ Post.GetSmallSize.Height }}' + }, + medium: { + src: '{{ CDNURL }}/medium/{{ Post.FileName }}', + width: '{{ Post.GetMediumSize.Width }}', + height: '{{ Post.GetMediumSize.Height }}' + }, + large: { + src: '{{ CDNURL }}/large/{{ Post.FileName }}', + width: '{{ Post.GetLargeSize.Width }}', + height: '{{ Post.GetLargeSize.Height }}' + }, + original: { + src: '{{ CDNURL }}/original/{{ Post.FileName }}', + width: '{{ Post.GetOriginalSize.Width }}', + height: '{{ Post.GetOriginalSize.Height }}' + } + } + document.addEventListener('DOMContentLoaded', function () { + const imageWidth = parseInt('{{ Post.GetMediumSize.Width }}') + const imageHeight = parseInt('{{ Post.GetMediumSize.Height }}') + const img = document.getElementById('post-image') + + if (img) { + const availableHeight = getAvailableHeight() + + if (imageHeight > imageWidth) { + const ratio = availableHeight / imageHeight + img.style.width = Math.floor(imageWidth * ratio) + 'px' + img.style.height = availableHeight + 'px' + } else { + const containerWidth = document.querySelector('.post-image-container').offsetWidth + const ratio = containerWidth / imageWidth + img.style.width = containerWidth + 'px' + img.style.height = Math.floor(imageHeight * ratio) + 'px' + } + } + }) + </script> +{% endblock %} {% block content %} {% if Error %} <div class="centered-main"> @@ -25,42 +87,24 @@ <div class="post-image-container"> <img src="{{ CDNURL }}/medium/{{ Post.FileName }}" alt="{{ Post.Title }}" id="post-image" /> </div> + <div class="post-details"> + <div class="post-detail-item"> + <span class="post-detail-label">ID:</span> + <span class="post-detail-value">{{ Post.ID }}</span> + </div> + <div class="post-detail-item"> + <span class="post-detail-label">Uploader:</span> + <span class="post-detail-value"><a href="/u/{{ Post.Uploader.Username }}">{{ Post.Uploader.Username }}</a></span> + </div> + <div class="post-detail-item"> + <span class="post-detail-label">Created:</span> + <span class="post-detail-value">{{ Post.CreatedAt|naturaltime }}</span> + </div> + <div class="post-detail-item"> + <span class="post-detail-label">Original Size:</span> + <span class="post-detail-value">{{ Post.GetOriginalSize.Width }}x{{ Post.GetOriginalSize.Height }} ({{ Post.GetOriginalSize.GetFileSizeFormatted }})</span> + </div> + </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/templates/preferences.django b/templates/preferences.django index 691cbdf..59245da 100644 --- a/templates/preferences.django +++ b/templates/preferences.django @@ -11,7 +11,7 @@ <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" /> + <input type="text" class="itext" 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> @@ -20,7 +20,7 @@ <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" /> + <input type="text" class="itext" 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> @@ -29,7 +29,7 @@ <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" /> + <input type="text" class="itext" 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> @@ -38,7 +38,7 @@ <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" /> + <input type="text" class="itext" 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> @@ -47,7 +47,7 @@ <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" /> + <input type="text" class="itext" 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> diff --git a/templates/register.django b/templates/register.django index 2990ba5..808f15a 100644 --- a/templates/register.django +++ b/templates/register.django @@ -18,7 +18,7 @@ <label for="username">Username</label> </div> <div class="fg-sub"> - <input type="text" id="username" name="username" required value="{{ Username }}" minlength="3" maxlength="72" autocomplete="username" pattern="[a-zA-Z0-9_-]+" /> + <input type="text" class="itext" id="username" name="username" required value="{{ Username }}" minlength="3" maxlength="72" autocomplete="username" pattern="[a-zA-Z0-9_-]+" /> <small>3-72 characters, letters, numbers, underscores, and hyphens only</small> </div> </div> |
