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"" container = f"""""" return container +def tag_image(children, attr, surrounding): + img = f"\"{children}\"" + 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"
    \"{children}\"
    ", - "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 {