Compare commits
10 Commits
382080ceaa
...
f1f62fa2c8
Author | SHA1 | Date | |
---|---|---|---|
f1f62fa2c8
|
|||
8c917f6ae2
|
|||
4f88d14b45
|
|||
9238385244
|
|||
cf89070639
|
|||
4a8f87d64a
|
|||
2b1f52a99d
|
|||
d0b702e1e8
|
|||
14b96bf37e
|
|||
cf4bf3caa3
|
@ -25,7 +25,7 @@ Designers: Paul James Miller
|
|||||||
|
|
||||||
## ICONCINO
|
## ICONCINO
|
||||||
|
|
||||||
Affected files: [`data/static/misc/error.svg`](./data/static/misc/error.svg) [`data/static/misc/image.svg`](./data/static/misc/image.svg) [`data/static/misc/info.svg`](./data/static/misc/info.svg) [`data/static/misc/lock.svg`](./data/static/misc/lock.svg) [`data/static/misc/sticky.svg`](./data/static/misc/sticky.svg) [`data/static/misc/warn.svg`](./data/static/misc/warn.svg)
|
Affected files: [`data/static/misc/error.svg`](./data/static/misc/error.svg) [`data/static/misc/image.svg`](./data/static/misc/image.svg) [`data/static/misc/info.svg`](./data/static/misc/info.svg) [`data/static/misc/lock.svg`](./data/static/misc/lock.svg) [`data/static/misc/spoiler.svg`](./data/static/misc/spoiler.svg) [`data/static/misc/sticky.svg`](./data/static/misc/sticky.svg) [`data/static/misc/warn.svg`](./data/static/misc/warn.svg)
|
||||||
URL: https://www.figma.com/community/file/1136337054881623512/iconcino-v2-0-0-free-icons-cc0-1-0-license
|
URL: https://www.figma.com/community/file/1136337054881623512/iconcino-v2-0-0-free-icons-cc0-1-0-license
|
||||||
Copyright: Gabriele Malaspina
|
Copyright: Gabriele Malaspina
|
||||||
Designers: Gabriele Malaspina
|
Designers: Gabriele Malaspina
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from flask import Flask, session
|
from flask import Flask, session
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from .models import Avatars, Users
|
from .models import Avatars, Users, PostHistory, Posts
|
||||||
from .auth import digest
|
from .auth import digest
|
||||||
from .routes.users import is_logged_in, get_active_user
|
from .routes.users import is_logged_in, get_active_user
|
||||||
from .routes.threads import get_post_url
|
from .routes.threads import get_post_url
|
||||||
@ -9,7 +9,7 @@ from .constants import (
|
|||||||
InfoboxKind, InfoboxIcons, InfoboxHTMLClass,
|
InfoboxKind, InfoboxIcons, InfoboxHTMLClass,
|
||||||
REACTION_EMOJI,
|
REACTION_EMOJI,
|
||||||
)
|
)
|
||||||
from .lib.babycode import babycode_to_html, EMOJI
|
from .lib.babycode import babycode_to_html, EMOJI, BABYCODE_VERSION
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
@ -48,6 +48,23 @@ def create_deleted_user():
|
|||||||
"permission": PermissionLevel.SYSTEM.value,
|
"permission": PermissionLevel.SYSTEM.value,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def reparse_posts():
|
||||||
|
from .db import db
|
||||||
|
post_histories = PostHistory.findall([
|
||||||
|
('markup_language', '=', 'babycode'),
|
||||||
|
('format_version', 'IS NOT', BABYCODE_VERSION)
|
||||||
|
])
|
||||||
|
if len(post_histories) == 0:
|
||||||
|
return
|
||||||
|
print('Re-parsing babycode, this may take a while...')
|
||||||
|
with db.transaction():
|
||||||
|
for ph in post_histories:
|
||||||
|
ph.update({
|
||||||
|
'content': babycode_to_html(ph['original_markup']),
|
||||||
|
'format_version': BABYCODE_VERSION,
|
||||||
|
})
|
||||||
|
print('Re-parsing done.')
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config.from_file('../config/pyrom_config.toml', load=tomllib.load, text=False)
|
app.config.from_file('../config/pyrom_config.toml', load=tomllib.load, text=False)
|
||||||
@ -76,6 +93,8 @@ def create_app():
|
|||||||
create_admin()
|
create_admin()
|
||||||
create_deleted_user()
|
create_deleted_user()
|
||||||
|
|
||||||
|
reparse_posts()
|
||||||
|
|
||||||
from app.routes.app import bp as app_bp
|
from app.routes.app import bp as app_bp
|
||||||
from app.routes.topics import bp as topics_bp
|
from app.routes.topics import bp as topics_bp
|
||||||
from app.routes.threads import bp as threads_bp
|
from app.routes.threads import bp as threads_bp
|
||||||
|
@ -202,9 +202,9 @@ class Model:
|
|||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def findall(cls, condition):
|
def findall(cls, condition, operator='='):
|
||||||
rows = db.QueryBuilder(cls.table)\
|
rows = db.QueryBuilder(cls.table)\
|
||||||
.where(condition)\
|
.where(condition, operator)\
|
||||||
.all()
|
.all()
|
||||||
res = []
|
res = []
|
||||||
for row in rows:
|
for row in rows:
|
||||||
|
@ -2,6 +2,8 @@ from .babycode_parser import Parser
|
|||||||
from markupsafe import escape
|
from markupsafe import escape
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
BABYCODE_VERSION = 3
|
||||||
|
|
||||||
NAMED_COLORS = [
|
NAMED_COLORS = [
|
||||||
'black', 'silver', 'gray', 'white', 'maroon', 'red',
|
'black', 'silver', 'gray', 'white', 'maroon', 'red',
|
||||||
'purple', 'fuchsia', 'green', 'lime', 'olive', 'yellow',
|
'purple', 'fuchsia', 'green', 'lime', 'olive', 'yellow',
|
||||||
@ -33,7 +35,23 @@ NAMED_COLORS = [
|
|||||||
'violet', 'wheat', 'white', 'whitesmoke', 'yellow', 'yellowgreen',
|
'violet', 'wheat', 'white', 'whitesmoke', 'yellow', 'yellowgreen',
|
||||||
]
|
]
|
||||||
|
|
||||||
def tag_code(children, attr):
|
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):
|
||||||
|
return isinstance(e, str)
|
||||||
|
|
||||||
|
def tag_code(children, attr, surrounding):
|
||||||
is_inline = children.find('\n') == -1
|
is_inline = children.find('\n') == -1
|
||||||
if is_inline:
|
if is_inline:
|
||||||
return f"<code class=\"inline-code\">{children}</code>"
|
return f"<code class=\"inline-code\">{children}</code>"
|
||||||
@ -47,7 +65,7 @@ def tag_list(children):
|
|||||||
list_body = re.sub(r"\n\n+", "\1", list_body)
|
list_body = re.sub(r"\n\n+", "\1", list_body)
|
||||||
return " ".join([f"<li>{x}</li>" for x in list_body.split("\1") if x])
|
return " ".join([f"<li>{x}</li>" 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})$"
|
hex_re = r"^#?([0-9a-f]{6}|[0-9a-f]{3})$"
|
||||||
potential_color = attr.lower().strip()
|
potential_color = attr.lower().strip()
|
||||||
|
|
||||||
@ -61,25 +79,48 @@ def tag_color(children, attr):
|
|||||||
# return just the way it was if we can't parse it
|
# return just the way it was if we can't parse it
|
||||||
return f"[color={attr}]{children}[/color]"
|
return f"[color={attr}]{children}[/color]"
|
||||||
|
|
||||||
|
def tag_spoiler(children, attr, surrounding):
|
||||||
|
spoiler_name = attr if attr else "Spoiler"
|
||||||
|
content = f"<div class='accordion-content post-accordion-content hidden'>{children}</div>"
|
||||||
|
container = f"""<div class='accordion hidden'><div class='accordion-header'><button type='button' class='accordion-toggle'>+</button><span>{spoiler_name}</span></div>{content}</div>"""
|
||||||
|
return container
|
||||||
|
|
||||||
|
def tag_image(children, attr, surrounding):
|
||||||
|
img = f"<img class=\"post-image\" src=\"{attr}\" alt=\"{children}\">"
|
||||||
|
if not is_tag(surrounding[0], 'img'):
|
||||||
|
img = f"<div class=post-img-container>{img}"
|
||||||
|
if not is_tag(surrounding[1], 'img'):
|
||||||
|
img = f"{img}</div>"
|
||||||
|
return img
|
||||||
|
|
||||||
TAGS = {
|
TAGS = {
|
||||||
"b": lambda children, attr: f"<strong>{children}</strong>",
|
"b": lambda children, attr, _: f"<strong>{children}</strong>",
|
||||||
"i": lambda children, attr: f"<em>{children}</em>",
|
"i": lambda children, attr, _: f"<em>{children}</em>",
|
||||||
"s": lambda children, attr: f"<del>{children}</del>",
|
"s": lambda children, attr, _: f"<del>{children}</del>",
|
||||||
"u": lambda children, attr: f"<u>{children}</u>",
|
"u": lambda children, attr, _: f"<u>{children}</u>",
|
||||||
|
|
||||||
"img": lambda children, attr: f"<div class=\"post-img-container\"><img class=\"block-img\" src=\"{attr}\" alt=\"{children}\"></div>",
|
"img": tag_image,
|
||||||
"url": lambda children, attr: f"<a href={attr}>{children}</a>",
|
"url": lambda children, attr, _: f"<a href={attr}>{children}</a>",
|
||||||
"quote": lambda children, attr: f"<blockquote>{children}</blockquote>",
|
"quote": lambda children, attr, _: f"<blockquote>{children}</blockquote>",
|
||||||
"code": tag_code,
|
"code": tag_code,
|
||||||
"ul": lambda children, attr: f"<ul>{tag_list(children)}</ul>",
|
"ul": lambda children, attr, _: f"<ul>{tag_list(children)}</ul>",
|
||||||
"ol": lambda children, attr: f"<ol>{tag_list(children)}</ol>",
|
"ol": lambda children, attr, _: f"<ol>{tag_list(children)}</ol>",
|
||||||
|
|
||||||
"big": lambda children, attr: f"<span style='font-size: 2rem;'>{children}</span>",
|
"big": lambda children, attr, _: f"<span style='font-size: 2rem;'>{children}</span>",
|
||||||
"small": lambda children, attr: f"<span style='font-size: 0.75rem;'>{children}</span>",
|
"small": lambda children, attr, _: f"<span style='font-size: 0.75rem;'>{children}</span>",
|
||||||
"color": tag_color,
|
"color": tag_color,
|
||||||
|
|
||||||
"center": lambda children, attr: f"<div style='text-align: center;'>{children}</div>",
|
"center": lambda children, attr, _: f"<div style='text-align: center;'>{children}</div>",
|
||||||
"right": lambda children, attr: f"<div style='text-align: right;'>{children}</div>",
|
"right": lambda children, attr, _: f"<div style='text-align: right;'>{children}</div>",
|
||||||
|
|
||||||
|
"spoiler": tag_spoiler,
|
||||||
|
}
|
||||||
|
|
||||||
|
# [img] is considered block for the purposes of collapsing whitespace,
|
||||||
|
# despite being potentially inline (since the resulting <img> tag is inline, but creates a block container around itself and sibling images).
|
||||||
|
# [code] has a special case in is_inline().
|
||||||
|
INLINE_TAGS = {
|
||||||
|
'b', 'i', 's', 'u', 'color', 'big', 'small', 'url'
|
||||||
}
|
}
|
||||||
|
|
||||||
def make_emoji(name, code):
|
def make_emoji(name, code):
|
||||||
@ -138,6 +179,33 @@ def break_lines(text):
|
|||||||
text = re.sub(r"\n\n+", "<br><br>", text)
|
text = re.sub(r"\n\n+", "<br><br>", text)
|
||||||
return 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):
|
||||||
|
if is_tag(e, 'code'): # special case, since [code] can be inline OR block
|
||||||
|
return '\n' not in e['children']
|
||||||
|
|
||||||
|
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):
|
def babycode_to_html(s):
|
||||||
subj = escape(s.strip().replace('\r\n', '\n').replace('\r', '\n'))
|
subj = escape(s.strip().replace('\r\n', '\n').replace('\r', '\n'))
|
||||||
parser = Parser(subj)
|
parser = Parser(subj)
|
||||||
@ -145,10 +213,19 @@ def babycode_to_html(s):
|
|||||||
parser.bbcode_tags_only_text_children = TEXT_ONLY
|
parser.bbcode_tags_only_text_children = TEXT_ONLY
|
||||||
parser.valid_emotes = EMOJI.keys()
|
parser.valid_emotes = EMOJI.keys()
|
||||||
|
|
||||||
elements = parser.parse()
|
uncollapsed = parser.parse()
|
||||||
print(elements)
|
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 = ""
|
out = ""
|
||||||
def fold(element, nobr):
|
def fold(element, nobr, surrounding):
|
||||||
if isinstance(element, str):
|
if isinstance(element, str):
|
||||||
if nobr:
|
if nobr:
|
||||||
return element
|
return element
|
||||||
@ -157,10 +234,15 @@ def babycode_to_html(s):
|
|||||||
match element['type']:
|
match element['type']:
|
||||||
case "bbcode":
|
case "bbcode":
|
||||||
c = ""
|
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"
|
_nobr = element['name'] == "code" or element['name'] == "ul" or element['name'] == "ol"
|
||||||
c = c + fold(child, _nobr)
|
c = c + fold(child, _nobr, _surrounding)
|
||||||
res = TAGS[element['name']](c, element['attr'])
|
res = TAGS[element['name']](c, element['attr'], surrounding)
|
||||||
return res
|
return res
|
||||||
case "link":
|
case "link":
|
||||||
return f"<a href=\"{element['url']}\">{element['url']}</a>"
|
return f"<a href=\"{element['url']}\">{element['url']}</a>"
|
||||||
@ -168,6 +250,12 @@ def babycode_to_html(s):
|
|||||||
return EMOJI[element['name']]
|
return EMOJI[element['name']]
|
||||||
case "rule":
|
case "rule":
|
||||||
return "<hr>"
|
return "<hr>"
|
||||||
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
|
return out
|
||||||
|
@ -4,7 +4,7 @@ import re
|
|||||||
|
|
||||||
PAT_EMOTE = r"[^\s:]"
|
PAT_EMOTE = r"[^\s:]"
|
||||||
PAT_BBCODE_TAG = r"\w"
|
PAT_BBCODE_TAG = r"\w"
|
||||||
PAT_BBCODE_ATTR = r"[^\s\]]"
|
PAT_BBCODE_ATTR = r"[^\]]"
|
||||||
PAT_LINK = r"https?:\/\/[\w\-_.?:\/=&~@#%]+[\w\-\/]"
|
PAT_LINK = r"https?:\/\/[\w\-_.?:\/=&~@#%]+[\w\-\/]"
|
||||||
|
|
||||||
class Parser:
|
class Parser:
|
||||||
|
@ -10,6 +10,7 @@ MIGRATIONS = [
|
|||||||
migrate_old_avatars,
|
migrate_old_avatars,
|
||||||
'DELETE FROM sessions', # delete old lua porom sessions
|
'DELETE FROM sessions', # delete old lua porom sessions
|
||||||
'ALTER TABLE "users" ADD COLUMN "invited_by" INTEGER REFERENCES users(id)', # invitation system
|
'ALTER TABLE "users" ADD COLUMN "invited_by" INTEGER REFERENCES users(id)', # invitation system
|
||||||
|
'ALTER TABLE "post_history" ADD COLUMN "format_version" INTEGER DEFAULT NULL',
|
||||||
]
|
]
|
||||||
|
|
||||||
def run_migrations():
|
def run_migrations():
|
||||||
|
@ -2,7 +2,7 @@ from flask import (
|
|||||||
Blueprint, redirect, url_for, flash, render_template, request
|
Blueprint, redirect, url_for, flash, render_template, request
|
||||||
)
|
)
|
||||||
from .users import login_required, get_active_user
|
from .users import login_required, get_active_user
|
||||||
from ..lib.babycode import babycode_to_html
|
from ..lib.babycode import babycode_to_html, BABYCODE_VERSION
|
||||||
from ..constants import InfoboxKind
|
from ..constants import InfoboxKind
|
||||||
from ..db import db
|
from ..db import db
|
||||||
from ..models import Posts, PostHistory, Threads, Topics
|
from ..models import Posts, PostHistory, Threads, Topics
|
||||||
@ -11,6 +11,7 @@ bp = Blueprint("posts", __name__, url_prefix = "/post")
|
|||||||
|
|
||||||
|
|
||||||
def create_post(thread_id, user_id, content, markup_language="babycode"):
|
def create_post(thread_id, user_id, content, markup_language="babycode"):
|
||||||
|
parsed_content = babycode_to_html(content)
|
||||||
with db.transaction():
|
with db.transaction():
|
||||||
post = Posts.create({
|
post = Posts.create({
|
||||||
"thread_id": thread_id,
|
"thread_id": thread_id,
|
||||||
@ -20,10 +21,11 @@ def create_post(thread_id, user_id, content, markup_language="babycode"):
|
|||||||
|
|
||||||
revision = PostHistory.create({
|
revision = PostHistory.create({
|
||||||
"post_id": post.id,
|
"post_id": post.id,
|
||||||
"content": babycode_to_html(content),
|
"content": parsed_content,
|
||||||
"is_initial_revision": True,
|
"is_initial_revision": True,
|
||||||
"original_markup": content,
|
"original_markup": content,
|
||||||
"markup_language": markup_language,
|
"markup_language": markup_language,
|
||||||
|
"format_version": BABYCODE_VERSION,
|
||||||
})
|
})
|
||||||
|
|
||||||
post.update({"current_revision_id": revision.id})
|
post.update({"current_revision_id": revision.id})
|
||||||
@ -31,14 +33,16 @@ def create_post(thread_id, user_id, content, markup_language="babycode"):
|
|||||||
|
|
||||||
|
|
||||||
def update_post(post_id, new_content, markup_language='babycode'):
|
def update_post(post_id, new_content, markup_language='babycode'):
|
||||||
|
parsed_content = babycode_to_html(new_content)
|
||||||
with db.transaction():
|
with db.transaction():
|
||||||
post = Posts.find({'id': post_id})
|
post = Posts.find({'id': post_id})
|
||||||
new_revision = PostHistory.create({
|
new_revision = PostHistory.create({
|
||||||
'post_id': post.id,
|
'post_id': post.id,
|
||||||
'content': babycode_to_html(new_content),
|
'content': parsed_content,
|
||||||
'is_initial_revision': False,
|
'is_initial_revision': False,
|
||||||
'original_markup': new_content,
|
'original_markup': new_content,
|
||||||
'markup_language': markup_language,
|
'markup_language': markup_language,
|
||||||
|
'format_version': BABYCODE_VERSION,
|
||||||
})
|
})
|
||||||
|
|
||||||
post.update({'current_revision_id': new_revision.id})
|
post.update({'current_revision_id': new_revision.id})
|
||||||
|
@ -126,8 +126,9 @@
|
|||||||
<code class="inline-code">[img=https://forum.poto.cafe/avatars/default.webp]the Python logo with a cowboy hat[/img]</code>
|
<code class="inline-code">[img=https://forum.poto.cafe/avatars/default.webp]the Python logo with a cowboy hat[/img]</code>
|
||||||
{{ '[img=/static/avatars/default.webp]the Python logo with a cowboy hat[/img]' | babycode | safe }}
|
{{ '[img=/static/avatars/default.webp]the Python logo with a cowboy hat[/img]' | babycode | safe }}
|
||||||
</p>
|
</p>
|
||||||
<p>Text inside the tag becomes the alt text. The attribute is the image URL.</p>
|
<p>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.</p>
|
||||||
<p>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.</p>
|
<p>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.</p>
|
||||||
|
<p>Multiple images attached to a post can be clicked to open a dialog to view them.</p>
|
||||||
</section>
|
</section>
|
||||||
<section class="babycode-guide-section">
|
<section class="babycode-guide-section">
|
||||||
<h2 id="adding-code-blocks">Adding code blocks</h2>
|
<h2 id="adding-code-blocks">Adding code blocks</h2>
|
||||||
@ -151,6 +152,15 @@
|
|||||||
Will produce the following list:
|
Will produce the following list:
|
||||||
{{ list | babycode | safe }}
|
{{ list | babycode | safe }}
|
||||||
</section>
|
</section>
|
||||||
|
<section class="babycode-guide-section">
|
||||||
|
<h2 id="spoilers">Spoilers</h2>
|
||||||
|
{% set spoiler = "[spoiler=Major Metal Gear Spoilers]Snake dies[/spoiler]" %}
|
||||||
|
<p>You can make a section collapsible by using the <code class="inline-code">[spoiler]</code> tag:</p>
|
||||||
|
{{ ("[code]\n%s[/code]" % spoiler) | babycode | safe }}
|
||||||
|
Will produce:
|
||||||
|
{{ spoiler | babycode | safe }}
|
||||||
|
All other tags are supported inside spoilers.
|
||||||
|
</section>
|
||||||
{% endset %}
|
{% endset %}
|
||||||
{{ sections | safe }}
|
{{ sections | safe }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -59,6 +59,7 @@
|
|||||||
<button class="babycode-button contain-svg full" type=button id="post-editor-img" title="Insert Image"><img src="/static/misc/image.svg"></button>
|
<button class="babycode-button contain-svg full" type=button id="post-editor-img" title="Insert Image"><img src="/static/misc/image.svg"></button>
|
||||||
<button class="babycode-button" type=button id="post-editor-ol" title="Insert Ordered list">1.</button>
|
<button class="babycode-button" type=button id="post-editor-ol" title="Insert Ordered list">1.</button>
|
||||||
<button class="babycode-button" type=button id="post-editor-ul" title="Insert Unordered list">•</button>
|
<button class="babycode-button" type=button id="post-editor-ul" title="Insert Unordered list">•</button>
|
||||||
|
<button class="babycode-button contain-svg full" type=button id="post-editor-spoiler" title="Insert spoiler"><img src="/static/misc/spoiler.svg"></button>
|
||||||
</span>
|
</span>
|
||||||
<textarea class="babycode-editor" name="{{ ta_name }}" id="babycode-content" placeholder="{{ ta_placeholder }}" {{ "required" if not optional else "" }}>{{ prefill }}</textarea>
|
<textarea class="babycode-editor" name="{{ ta_name }}" id="babycode-content" placeholder="{{ ta_placeholder }}" {{ "required" if not optional else "" }}>{{ prefill }}</textarea>
|
||||||
<a href="{{ url_for("app.babycode_guide") }}" target="_blank">babycode guide</a>
|
<a href="{{ url_for("app.babycode_guide") }}" target="_blank">babycode guide</a>
|
||||||
|
@ -48,6 +48,7 @@
|
|||||||
const buttonImg = document.getElementById("post-editor-img");
|
const buttonImg = document.getElementById("post-editor-img");
|
||||||
const buttonOl = document.getElementById("post-editor-ol");
|
const buttonOl = document.getElementById("post-editor-ol");
|
||||||
const buttonUl = document.getElementById("post-editor-ul");
|
const buttonUl = document.getElementById("post-editor-ul");
|
||||||
|
const buttonSpoiler = document.getElementById("post-editor-spoiler");
|
||||||
|
|
||||||
function insertTag(tagStart, newline = false, prefill = "") {
|
function insertTag(tagStart, newline = false, prefill = "") {
|
||||||
const hasAttr = tagStart[tagStart.length - 1] === "=";
|
const hasAttr = tagStart[tagStart.length - 1] === "=";
|
||||||
@ -130,6 +131,10 @@
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
insertTag("ul", true);
|
insertTag("ul", true);
|
||||||
})
|
})
|
||||||
|
buttonSpoiler.addEventListener("click", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
insertTag("spoiler=", true, "hidden content");
|
||||||
|
})
|
||||||
|
|
||||||
const previewEndpoint = "/api/babycode-preview";
|
const previewEndpoint = "/api/babycode-preview";
|
||||||
let previousMarkup = "";
|
let previousMarkup = "";
|
||||||
@ -173,5 +178,8 @@
|
|||||||
const json_resp = await req.json();
|
const json_resp = await req.json();
|
||||||
previewContainer.innerHTML = json_resp.html;
|
previewContainer.innerHTML = json_resp.html;
|
||||||
previewErrorsContainer.textContent = "";
|
previewErrorsContainer.textContent = "";
|
||||||
|
|
||||||
|
const accordionRefreshEvt = new CustomEvent("refresh_accordions");
|
||||||
|
document.body.dispatchEvent(accordionRefreshEvt);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -110,8 +110,13 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// accordions
|
// accordions
|
||||||
const accordions = document.querySelectorAll(".accordion");
|
const handledAccordions = new Set();
|
||||||
accordions.forEach(accordion => {
|
function attachAccordionHandlers(accordion){
|
||||||
|
if(handledAccordions.has(accordion)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handledAccordions.add(accordion)
|
||||||
const header = accordion.querySelector(".accordion-header");
|
const header = accordion.querySelector(".accordion-header");
|
||||||
const toggleButton = header.querySelector(".accordion-toggle");
|
const toggleButton = header.querySelector(".accordion-toggle");
|
||||||
const content = accordion.querySelector(".accordion-content");
|
const content = accordion.querySelector(".accordion-content");
|
||||||
@ -124,13 +129,35 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggleButton.addEventListener("click", toggle);
|
toggleButton.addEventListener("click", toggle);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
function refreshAccordions(){
|
||||||
|
const accordions = document.querySelectorAll(".accordion");
|
||||||
|
accordions.forEach(attachAccordionHandlers);
|
||||||
|
}
|
||||||
|
refreshAccordions()
|
||||||
|
|
||||||
|
document.body.addEventListener('refresh_accordions', refreshAccordions)
|
||||||
|
|
||||||
//lightboxes
|
//lightboxes
|
||||||
lightboxObj = constructLightbox();
|
lightboxObj = constructLightbox();
|
||||||
document.body.appendChild(lightboxObj.dialog);
|
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 => {
|
postImages.forEach(postImage => {
|
||||||
|
if (postImage.complete) {
|
||||||
|
setImageMaxSize(postImage);
|
||||||
|
} else {
|
||||||
|
postImage.addEventListener("load", () => setImageMaxSize(postImage));
|
||||||
|
}
|
||||||
const belongingTo = postImage.closest(".post-inner");
|
const belongingTo = postImage.closest(".post-inner");
|
||||||
const images = lightboxImages.get(belongingTo) ?? [];
|
const images = lightboxImages.get(belongingTo) ?? [];
|
||||||
images.push({
|
images.push({
|
||||||
|
5
data/static/misc/spoiler.svg
Normal file
5
data/static/misc/spoiler.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg width="24px" height="24px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4 4L9.87868 9.87868M20 20L14.1213 14.1213M9.87868 9.87868C9.33579 10.4216 9 11.1716 9 12C9 13.6569 10.3431 15 12 15C12.8284 15 13.5784 14.6642 14.1213 14.1213M9.87868 9.87868L14.1213 14.1213M6.76821 6.76821C4.72843 8.09899 2.96378 10.026 2 11.9998C3.74646 15.5764 8.12201 19 11.9998 19C13.7376 19 15.5753 18.3124 17.2317 17.2317M9.76138 5.34717C10.5114 5.12316 11.2649 5 12.0005 5C15.8782 5 20.2531 8.42398 22 12.0002C21.448 13.1302 20.6336 14.2449 19.6554 15.2412" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
<!-- https://www.figma.com/community/file/1136337054881623512/iconcino-v2-0-0-free-icons-cc0-1-0-license -->
|
After Width: | Height: | Size: 814 B |
@ -511,10 +511,21 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus, select:focus
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.block-img {
|
.post-img-container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-image {
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
max-width: 400px;
|
max-width: 300px;
|
||||||
max-height: 400px;
|
max-height: 300px;
|
||||||
|
min-width: 200px;
|
||||||
|
min-height: 200px;
|
||||||
|
flex: 1 1 0%;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thread-info-container {
|
.thread-info-container {
|
||||||
@ -786,6 +797,12 @@ ul, ol {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.post-accordion-content {
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
background-color: rgb(173.5214173228, 183.6737007874, 161.0262992126);
|
||||||
|
}
|
||||||
|
|
||||||
.inbox-container {
|
.inbox-container {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
object-fit: contain;
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
max-height: 400px;
|
max-height: 400px;
|
||||||
|
min-width: 200px;
|
||||||
|
min-height: 200px;
|
||||||
|
flex: 1 1 0%;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thread-info-container {
|
.thread-info-container {
|
||||||
@ -781,6 +792,12 @@ ul, ol {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.post-accordion-content {
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
background-color: $main_bg;
|
||||||
|
}
|
||||||
|
|
||||||
.inbox-container {
|
.inbox-container {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user