From 9238385244514766ab1f668d8743e891feb82753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lera=20Elvo=C3=A9?= Date: Sat, 16 Aug 2025 16:59:44 +0300 Subject: [PATCH] collapse whitespace between block babycode tags --- app/lib/babycode.py | 51 ++++++++++++++++++++++++++++++++++--- app/templates/babycode.html | 2 +- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/app/lib/babycode.py b/app/lib/babycode.py index 18a3c56..a41c96b 100644 --- a/app/lib/babycode.py +++ b/app/lib/babycode.py @@ -35,13 +35,17 @@ NAMED_COLORS = [ 'violet', 'wheat', 'white', 'whitesmoke', 'yellow', 'yellowgreen', ] -def is_tag(e, tag): +def is_tag(e, tag=None): if e is None: return False if isinstance(e, str): return False if e['type'] != 'bbcode': return False + + if tag is None: + return True + return e['name'] == tag def is_text(e): @@ -112,6 +116,12 @@ TAGS = { "spoiler": tag_spoiler, } +# [img] and [code] are considered block for the purposes of collapsing whitespace, +# despite being potentially inline ([img] is particularly egregious, since the resulting tag is inline, but creates a block container around itself and sibling images) +INLINE_TAGS = { + 'b', 'i', 's', 'u', 'color', 'big', 'small', 'url' +} + def make_emoji(name, code): return f' {name}' @@ -168,6 +178,30 @@ def break_lines(text): text = re.sub(r"\n\n+", "

", text) return text +def is_inline(e): + if e is None: + return False # i think + + if is_text(e): + return True + + if is_tag(e): + return e['name'] in INLINE_TAGS + + return e['type'] != 'rule' + +def should_collapse(text, surrounding): + if not isinstance(text, str): + return False + + if not text: + return True + + if not text.strip() and '\n' not in text: + return not is_inline(surrounding[0]) and not is_inline(surrounding[1]) + + return False + def babycode_to_html(s): subj = escape(s.strip().replace('\r\n', '\n').replace('\r', '\n')) parser = Parser(subj) @@ -175,7 +209,17 @@ def babycode_to_html(s): parser.bbcode_tags_only_text_children = TEXT_ONLY parser.valid_emotes = EMOJI.keys() - elements = parser.parse() + uncollapsed = parser.parse() + elements = [] + for i in range(len(uncollapsed)): + e = uncollapsed[i] + surrounding = ( + uncollapsed[i - 1] if i-1 >= 0 else None, + uncollapsed[i + 1] if i+1 < len(uncollapsed) else None + ) + if not should_collapse(e, surrounding): + elements.append(e) + out = "" def fold(element, nobr, surrounding): if isinstance(element, str): @@ -203,8 +247,7 @@ def babycode_to_html(s): return EMOJI[element['name']] case "rule": return "
" - # for e in elements: - # out = out + fold(e, False) + for i in range(len(elements)): e = elements[i] surrounding = ( diff --git a/app/templates/babycode.html b/app/templates/babycode.html index bddf839..5775b43 100644 --- a/app/templates/babycode.html +++ b/app/templates/babycode.html @@ -127,7 +127,7 @@ {{ '[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. 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,

+

Images will always break up a paragraph and will get scaled down to a maximum of 400px. However, consecutive image tags will try to stay in one line, wrapping if necessary. Break the paragraph if you wish to keep images on their own paragraph.

Multiple images attached to a post can be clicked to open a dialog to view them.