From cf89070639cb04581fada89e2583418cce77673c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lera=20Elvo=C3=A9?=
Date: Sat, 16 Aug 2025 15:50:44 +0300
Subject: [PATCH] put images in their own lane
---
app/lib/babycode.py | 80 ++++++++++++++++++++++++++-----------
app/templates/babycode.html | 4 +-
data/static/js/ui.js | 16 +++++++-
data/static/style.css | 17 ++++++--
sass/style.scss | 13 +++++-
5 files changed, 99 insertions(+), 31 deletions(-)
diff --git a/app/lib/babycode.py b/app/lib/babycode.py
index 8b3c7e2..18a3c56 100644
--- a/app/lib/babycode.py
+++ b/app/lib/babycode.py
@@ -2,7 +2,7 @@ from .babycode_parser import Parser
from markupsafe import escape
import re
-BABYCODE_VERSION = 1
+BABYCODE_VERSION = 2
NAMED_COLORS = [
'black', 'silver', 'gray', 'white', 'maroon', 'red',
@@ -35,7 +35,19 @@ NAMED_COLORS = [
'violet', 'wheat', 'white', 'whitesmoke', 'yellow', 'yellowgreen',
]
-def tag_code(children, attr):
+def is_tag(e, tag):
+ if e is None:
+ return False
+ if isinstance(e, str):
+ return False
+ if e['type'] != 'bbcode':
+ return False
+ return e['name'] == tag
+
+def is_text(e):
+ return isinstance(e, str)
+
+def tag_code(children, attr, surrounding):
is_inline = children.find('\n') == -1
if is_inline:
return f"{children}
"
@@ -49,7 +61,7 @@ def tag_list(children):
list_body = re.sub(r"\n\n+", "\1", list_body)
return " ".join([f"{x}" for x in list_body.split("\1") if x])
-def tag_color(children, attr):
+def tag_color(children, attr, surrounding):
hex_re = r"^#?([0-9a-f]{6}|[0-9a-f]{3})$"
potential_color = attr.lower().strip()
@@ -63,31 +75,39 @@ def tag_color(children, attr):
# return just the way it was if we can't parse it
return f"[color={attr}]{children}[/color]"
-def tag_spoiler(children, attr):
+def tag_spoiler(children, attr, surrounding):
spoiler_name = attr if attr else "Spoiler"
content = f"{children}
"
container = f"""{content}
"""
return container
+def tag_image(children, attr, surrounding):
+ img = f"
"
+ if not is_tag(surrounding[0], 'img'):
+ img = f"{img}"
+ if not is_tag(surrounding[1], 'img'):
+ img = f"{img}
"
+ return img
+
TAGS = {
- "b": lambda children, attr: f"{children}",
- "i": lambda children, attr: f"{children}",
- "s": lambda children, attr: f"{children}",
- "u": lambda children, attr: f"{children}",
+ "b": lambda children, attr, _: f"{children}",
+ "i": lambda children, attr, _: f"{children}",
+ "s": lambda children, attr, _: f"{children}",
+ "u": lambda children, attr, _: f"{children}",
- "img": lambda children, attr: f"",
- "url": lambda children, attr: f"{children}",
- "quote": lambda children, attr: f"{children}
",
+ "img": tag_image,
+ "url": lambda children, attr, _: f"{children}",
+ "quote": lambda children, attr, _: f"{children}
",
"code": tag_code,
- "ul": lambda children, attr: f"",
- "ol": lambda children, attr: f"{tag_list(children)}
",
+ "ul": lambda children, attr, _: f"",
+ "ol": lambda children, attr, _: f"{tag_list(children)}
",
- "big": lambda children, attr: f"{children}",
- "small": lambda children, attr: f"{children}",
+ "big": lambda children, attr, _: f"{children}",
+ "small": lambda children, attr, _: f"{children}",
"color": tag_color,
- "center": lambda children, attr: f"{children}
",
- "right": lambda children, attr: f"{children}
",
+ "center": lambda children, attr, _: f"{children}
",
+ "right": lambda children, attr, _: f"{children}
",
"spoiler": tag_spoiler,
}
@@ -156,9 +176,8 @@ def babycode_to_html(s):
parser.valid_emotes = EMOJI.keys()
elements = parser.parse()
- # print(elements)
out = ""
- def fold(element, nobr):
+ def fold(element, nobr, surrounding):
if isinstance(element, str):
if nobr:
return element
@@ -167,10 +186,16 @@ def babycode_to_html(s):
match element['type']:
case "bbcode":
c = ""
- for child in element['children']:
+ for i in range(len(element['children'])):
+ child = element['children'][i]
+ _surrounding = (
+ element['children'][i - 1] if i-1 >= 0 else None,
+ element['children'][i + 1] if i+1 < len(element['children']) else None
+ )
_nobr = element['name'] == "code" or element['name'] == "ul" or element['name'] == "ol"
- c = c + fold(child, _nobr)
- res = TAGS[element['name']](c, element['attr'])
+ c = c + fold(child, _nobr, _surrounding)
+
+ res = TAGS[element['name']](c, element['attr'], surrounding)
return res
case "link":
return f"{element['url']}"
@@ -178,6 +203,13 @@ def babycode_to_html(s):
return EMOJI[element['name']]
case "rule":
return "
"
- for e in elements:
- out = out + fold(e, False)
+ # for e in elements:
+ # out = out + fold(e, False)
+ for i in range(len(elements)):
+ e = elements[i]
+ surrounding = (
+ elements[i - 1] if i-1 >= 0 else None,
+ elements[i + 1] if i+1 < len(elements) else None
+ )
+ out = out + fold(e, False, surrounding)
return out
diff --git a/app/templates/babycode.html b/app/templates/babycode.html
index e8ca82f..bddf839 100644
--- a/app/templates/babycode.html
+++ b/app/templates/babycode.html
@@ -126,8 +126,8 @@
[img=https://forum.poto.cafe/avatars/default.webp]the Python logo with a cowboy hat[/img]
{{ '[img=/static/avatars/default.webp]the Python logo with a cowboy hat[/img]' | babycode | safe }}
- Text inside the tag becomes the alt text. The attribute is the image URL.
- Images will always break up a paragraph and will get scaled down to a maximum of 400px. The text inside the tag will become the image's alt text.
+ Text inside the tag becomes the alt text. The attribute is the image URL. The text inside the tag will become the image's alt text.
+ Images will always break up a paragraph and will get scaled down to a maximum of 400px. However, if there is no space between consecutive [img]
tags,
Multiple images attached to a post can be clicked to open a dialog to view them.
diff --git a/data/static/js/ui.js b/data/static/js/ui.js
index 0c4d7fd..f874666 100644
--- a/data/static/js/ui.js
+++ b/data/static/js/ui.js
@@ -142,8 +142,22 @@ document.addEventListener("DOMContentLoaded", () => {
//lightboxes
lightboxObj = constructLightbox();
document.body.appendChild(lightboxObj.dialog);
- const postImages = document.querySelectorAll(".post-inner img.block-img");
+
+ function setImageMaxSize(img) {
+ const { maxWidth: origMaxWidth } = getComputedStyle(img);
+ if (img.naturalWidth >= parseInt(origMaxWidth)) {
+ return;
+ }
+ img.style.maxWidth = img.naturalWidth + "px";
+ }
+
+ const postImages = document.querySelectorAll(".post-inner img.post-image");
postImages.forEach(postImage => {
+ if (postImage.complete) {
+ setImageMaxSize(postImage);
+ } else {
+ postImage.addEventListener("load", () => setImageMaxSize(postImage));
+ }
const belongingTo = postImage.closest(".post-inner");
const images = lightboxImages.get(belongingTo) ?? [];
images.push({
diff --git a/data/static/style.css b/data/static/style.css
index 92f5d53..46a983f 100644
--- a/data/static/style.css
+++ b/data/static/style.css
@@ -511,10 +511,21 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus, select:focus
width: 100%;
}
-.block-img {
+.post-img-container {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 5px;
+}
+
+.post-image {
object-fit: contain;
- max-width: 400px;
- max-height: 400px;
+ max-width: 300px;
+ max-height: 300px;
+ min-width: 200px;
+ min-height: 200px;
+ flex: 1 1 0%;
+ width: auto;
+ height: auto;
}
.thread-info-container {
diff --git a/sass/style.scss b/sass/style.scss
index 377ebfe..149af71 100644
--- a/sass/style.scss
+++ b/sass/style.scss
@@ -512,10 +512,21 @@ input[type="text"], input[type="password"], textarea, select {
}
}
-.block-img {
+.post-img-container {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 5px;
+}
+
+.post-image {
object-fit: contain;
max-width: 400px;
max-height: 400px;
+ min-width: 200px;
+ min-height: 200px;
+ flex: 1 1 0%;
+ width: auto;
+ height: auto;
}
.thread-info-container {