Compare commits
5 Commits
13c89cbde2
...
59e40ed5fd
Author | SHA1 | Date | |
---|---|---|---|
59e40ed5fd | |||
692a1d6b2b | |||
443c25c09b | |||
4cbc66d9aa | |||
7ab1c8745f |
@ -7,6 +7,7 @@ from .constants import (
|
|||||||
PermissionLevel, permission_level_string,
|
PermissionLevel, permission_level_string,
|
||||||
InfoboxKind, InfoboxIcons, InfoboxHTMLClass
|
InfoboxKind, InfoboxIcons, InfoboxHTMLClass
|
||||||
)
|
)
|
||||||
|
from .lib.babycode import babycode_to_html, EMOJI
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
@ -103,6 +104,7 @@ def create_app():
|
|||||||
"InfoboxHTMLClass": InfoboxHTMLClass,
|
"InfoboxHTMLClass": InfoboxHTMLClass,
|
||||||
"InfoboxKind": InfoboxKind,
|
"InfoboxKind": InfoboxKind,
|
||||||
"__commit": commit,
|
"__commit": commit,
|
||||||
|
"__emoji": EMOJI,
|
||||||
}
|
}
|
||||||
|
|
||||||
@app.context_processor
|
@app.context_processor
|
||||||
@ -124,4 +126,18 @@ def create_app():
|
|||||||
def permission_string(term):
|
def permission_string(term):
|
||||||
return permission_level_string(term)
|
return permission_level_string(term)
|
||||||
|
|
||||||
|
@app.template_filter('babycode')
|
||||||
|
def babycode_filter(markup):
|
||||||
|
return babycode_to_html(markup)
|
||||||
|
|
||||||
|
@app.template_filter('extract_h2')
|
||||||
|
def extract_h2(content):
|
||||||
|
import re
|
||||||
|
pattern = r'<h2\s+id="([^"]+)"[^>]*>(.*?)<\/h2>'
|
||||||
|
matches = re.findall(pattern, content, re.IGNORECASE | re.DOTALL)
|
||||||
|
return [
|
||||||
|
{'id': id_.strip(), 'text': text.strip()}
|
||||||
|
for id_, text in matches
|
||||||
|
]
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
@ -5,7 +5,7 @@ import re
|
|||||||
def tag_code(children, attr):
|
def tag_code(children, attr):
|
||||||
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>"
|
||||||
else:
|
else:
|
||||||
t = children.strip()
|
t = children.strip()
|
||||||
button = f"<button type=button class=\"copy-code\" value={t}>Copy</button>"
|
button = f"<button type=button class=\"copy-code\" value={t}>Copy</button>"
|
||||||
@ -28,6 +28,53 @@ TAGS = {
|
|||||||
"ol": lambda children, attr: f"<ol>{tag_list(children)}</ol>",
|
"ol": lambda children, attr: f"<ol>{tag_list(children)}</ol>",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def make_emoji(name, code):
|
||||||
|
return f' <img class=emoji src="/static/emoji/{name}.png" alt="{name}" title=":{code}:">'
|
||||||
|
|
||||||
|
EMOJI = {
|
||||||
|
'angry': make_emoji('angry', 'angry'),
|
||||||
|
|
||||||
|
'(': make_emoji('frown', '('),
|
||||||
|
|
||||||
|
'D': make_emoji('grin', 'D'),
|
||||||
|
|
||||||
|
'imp': make_emoji('imp', 'imp'),
|
||||||
|
|
||||||
|
'angryimp': make_emoji('impangry', 'angryimp'),
|
||||||
|
'impangry': make_emoji('impangry', 'impangry'),
|
||||||
|
|
||||||
|
'lobster': make_emoji('lobster', 'lobster'),
|
||||||
|
|
||||||
|
'|': make_emoji('neutral', '|'),
|
||||||
|
|
||||||
|
'pensive': make_emoji('pensive', 'pensive'),
|
||||||
|
|
||||||
|
')': make_emoji('smile', ')'),
|
||||||
|
|
||||||
|
'smiletear': make_emoji('smiletear', 'smiletear'),
|
||||||
|
'crytear': make_emoji('smiletear', 'crytear'),
|
||||||
|
|
||||||
|
',': make_emoji('sob', ','),
|
||||||
|
'T': make_emoji('sob', 'T'),
|
||||||
|
'cry': make_emoji('sob', 'cry'),
|
||||||
|
'sob': make_emoji('sob', 'sob'),
|
||||||
|
|
||||||
|
'o': make_emoji('surprised', 'o'),
|
||||||
|
'O': make_emoji('surprised', 'O'),
|
||||||
|
|
||||||
|
'hmm': make_emoji('think', 'hmm'),
|
||||||
|
'think': make_emoji('think', 'think'),
|
||||||
|
'thinking': make_emoji('think', 'thinking'),
|
||||||
|
|
||||||
|
'P': make_emoji('tongue', 'P'),
|
||||||
|
'p': make_emoji('tongue', 'p'),
|
||||||
|
|
||||||
|
'weary': make_emoji('weary', 'weary'),
|
||||||
|
|
||||||
|
';': make_emoji('wink', ';'),
|
||||||
|
'wink': make_emoji('wink', 'wink'),
|
||||||
|
}
|
||||||
|
|
||||||
TEXT_ONLY = ["code"]
|
TEXT_ONLY = ["code"]
|
||||||
|
|
||||||
def break_lines(text):
|
def break_lines(text):
|
||||||
@ -40,8 +87,10 @@ def babycode_to_html(s):
|
|||||||
parser = Parser(subj)
|
parser = Parser(subj)
|
||||||
parser.valid_bbcode_tags = TAGS.keys()
|
parser.valid_bbcode_tags = TAGS.keys()
|
||||||
parser.bbcode_tags_only_text_children = TEXT_ONLY
|
parser.bbcode_tags_only_text_children = TEXT_ONLY
|
||||||
|
parser.valid_emotes = EMOJI.keys()
|
||||||
|
|
||||||
elements = parser.parse()
|
elements = parser.parse()
|
||||||
|
print(elements)
|
||||||
out = ""
|
out = ""
|
||||||
def fold(element, nobr):
|
def fold(element, nobr):
|
||||||
if isinstance(element, str):
|
if isinstance(element, str):
|
||||||
@ -59,6 +108,8 @@ def babycode_to_html(s):
|
|||||||
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>"
|
||||||
|
case 'emote':
|
||||||
|
return EMOJI[element['name']]
|
||||||
case "rule":
|
case "rule":
|
||||||
return "<hr>"
|
return "<hr>"
|
||||||
for e in elements:
|
for e in elements:
|
||||||
|
@ -71,6 +71,18 @@ class Users(Model):
|
|||||||
subscriptions.user_id = ?"""
|
subscriptions.user_id = ?"""
|
||||||
return db.query(q, self.id)
|
return db.query(q, self.id)
|
||||||
|
|
||||||
|
def can_post_to_topic(self, topic):
|
||||||
|
if self.is_guest():
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.is_mod():
|
||||||
|
return True
|
||||||
|
|
||||||
|
if topic['is_locked']:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class Topics(Model):
|
class Topics(Model):
|
||||||
table = "topics"
|
table = "topics"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from flask import Blueprint, redirect, url_for
|
from flask import Blueprint, redirect, url_for, render_template
|
||||||
|
|
||||||
bp = Blueprint("app", __name__, url_prefix = "/")
|
bp = Blueprint("app", __name__, url_prefix = "/")
|
||||||
|
|
||||||
@ -9,4 +9,4 @@ def index():
|
|||||||
|
|
||||||
@bp.route("/babycode")
|
@bp.route("/babycode")
|
||||||
def babycode_guide():
|
def babycode_guide():
|
||||||
return "not yet"
|
return render_template('babycode.html')
|
||||||
|
@ -18,7 +18,7 @@ def thread(slug):
|
|||||||
POSTS_PER_PAGE = 10
|
POSTS_PER_PAGE = 10
|
||||||
thread = Threads.find({"slug": slug})
|
thread = Threads.find({"slug": slug})
|
||||||
if not thread:
|
if not thread:
|
||||||
return "no"
|
return redirect(url_for('topics.all_topics'))
|
||||||
|
|
||||||
post_count = Posts.count({"thread_id": thread.id})
|
post_count = Posts.count({"thread_id": thread.id})
|
||||||
page_count = max(math.ceil(post_count / POSTS_PER_PAGE), 1)
|
page_count = max(math.ceil(post_count / POSTS_PER_PAGE), 1)
|
||||||
@ -69,12 +69,12 @@ def thread(slug):
|
|||||||
def reply(slug):
|
def reply(slug):
|
||||||
thread = Threads.find({"slug": slug})
|
thread = Threads.find({"slug": slug})
|
||||||
if not thread:
|
if not thread:
|
||||||
return "no"
|
return redirect(url_for('topics.all_topics'))
|
||||||
user = get_active_user()
|
user = get_active_user()
|
||||||
if user.is_guest():
|
if user.is_guest():
|
||||||
return "no"
|
return redirect(url_for('.thread', slug=slug))
|
||||||
if thread.locked() and not user.is_mod():
|
if thread.locked() and not user.is_mod():
|
||||||
return "no"
|
return redirect(url_for('.thread', slug=slug))
|
||||||
|
|
||||||
post_content = request.form['post_content']
|
post_content = request.form['post_content']
|
||||||
post = create_post(thread.id, user.id, post_content)
|
post = create_post(thread.id, user.id, post_content)
|
||||||
@ -102,10 +102,12 @@ def create_form():
|
|||||||
topic = Topics.find({"id": request.form['topic_id']})
|
topic = Topics.find({"id": request.form['topic_id']})
|
||||||
user = get_active_user()
|
user = get_active_user()
|
||||||
if not topic:
|
if not topic:
|
||||||
return "no"
|
flash('Invalid topic', InfoboxKind.ERROR)
|
||||||
|
return redirect(url_for('.create'))
|
||||||
|
|
||||||
if topic.is_locked and not get_active_user().is_mod():
|
if topic.is_locked and not get_active_user().is_mod():
|
||||||
return "no"
|
flash(f'Topic "{topic.name}" is locked', InfoboxKind.ERROR)
|
||||||
|
return redirect(url_for('.create'))
|
||||||
|
|
||||||
title = request.form['title'].strip()
|
title = request.form['title'].strip()
|
||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
@ -128,8 +130,10 @@ def create_form():
|
|||||||
def lock(slug):
|
def lock(slug):
|
||||||
user = get_active_user()
|
user = get_active_user()
|
||||||
thread = Threads.find({'slug': slug})
|
thread = Threads.find({'slug': slug})
|
||||||
|
if not thread:
|
||||||
|
return redirect(url_for('topics.all_topics'))
|
||||||
if not ((thread.user_id == user.id) or user.is_mod()):
|
if not ((thread.user_id == user.id) or user.is_mod()):
|
||||||
return 'no'
|
return redirect(url_for('.thread', slug=slug))
|
||||||
target_op = request.form.get('target_op')
|
target_op = request.form.get('target_op')
|
||||||
thread.update({
|
thread.update({
|
||||||
'is_locked': target_op
|
'is_locked': target_op
|
||||||
@ -143,8 +147,10 @@ def lock(slug):
|
|||||||
def sticky(slug):
|
def sticky(slug):
|
||||||
user = get_active_user()
|
user = get_active_user()
|
||||||
thread = Threads.find({'slug': slug})
|
thread = Threads.find({'slug': slug})
|
||||||
|
if not thread:
|
||||||
|
return redirect(url_for('topics.all_topics'))
|
||||||
if not ((thread.user_id == user.id) or user.is_mod()):
|
if not ((thread.user_id == user.id) or user.is_mod()):
|
||||||
return 'no'
|
return redirect(url_for('.thread', slug=slug))
|
||||||
target_op = request.form.get('target_op')
|
target_op = request.form.get('target_op')
|
||||||
thread.update({
|
thread.update({
|
||||||
'is_stickied': target_op
|
'is_stickied': target_op
|
||||||
@ -167,12 +173,12 @@ def move(slug):
|
|||||||
'id': new_topic_id
|
'id': new_topic_id
|
||||||
})
|
})
|
||||||
if not new_topic:
|
if not new_topic:
|
||||||
return 'no'
|
return redirect(url_for('topics.all_topics'))
|
||||||
thread = Threads.find({
|
thread = Threads.find({
|
||||||
'slug': slug
|
'slug': slug
|
||||||
})
|
})
|
||||||
if not thread:
|
if not thread:
|
||||||
return 'no'
|
return redirect(url_for('topics.all_topics'))
|
||||||
if new_topic.id == thread.topic_id:
|
if new_topic.id == thread.topic_id:
|
||||||
flash('Thread is already in this topic.', InfoboxKind.ERROR)
|
flash('Thread is already in this topic.', InfoboxKind.ERROR)
|
||||||
return redirect(url_for('.thread', slug=slug))
|
return redirect(url_for('.thread', slug=slug))
|
||||||
@ -189,7 +195,7 @@ def subscribe(slug):
|
|||||||
user = get_active_user()
|
user = get_active_user()
|
||||||
thread = Threads.find({'slug': slug})
|
thread = Threads.find({'slug': slug})
|
||||||
if not thread:
|
if not thread:
|
||||||
return 'no'
|
return redirect(url_for('topics.all_topics'))
|
||||||
subscription = Subscriptions.find({
|
subscription = Subscriptions.find({
|
||||||
'user_id': user.id,
|
'user_id': user.id,
|
||||||
'thread_id': thread.id,
|
'thread_id': thread.id,
|
||||||
@ -204,11 +210,11 @@ def subscribe(slug):
|
|||||||
})
|
})
|
||||||
elif request.form['subscribe'] == 'unsubscribe':
|
elif request.form['subscribe'] == 'unsubscribe':
|
||||||
if not subscription:
|
if not subscription:
|
||||||
return 'no'
|
return redirect(url_for('.thread', slug=slug))
|
||||||
subscription.delete()
|
subscription.delete()
|
||||||
elif request.form['subscribe'] == 'read':
|
elif request.form['subscribe'] == 'read':
|
||||||
if not subscription:
|
if not subscription:
|
||||||
return 'no'
|
return redirect(url_for('.thread', slug=slug))
|
||||||
subscription.update({
|
subscription.update({
|
||||||
'last_seen': int(time.time())
|
'last_seen': int(time.time())
|
||||||
})
|
})
|
||||||
|
@ -51,7 +51,7 @@ def topic(slug):
|
|||||||
"slug": slug
|
"slug": slug
|
||||||
})
|
})
|
||||||
if not target_topic:
|
if not target_topic:
|
||||||
return "no"
|
return redirect(url_for('.all_topics'))
|
||||||
|
|
||||||
threads_count = Threads.count({
|
threads_count = Threads.count({
|
||||||
"topic_id": target_topic.id
|
"topic_id": target_topic.id
|
||||||
@ -77,7 +77,7 @@ def topic(slug):
|
|||||||
def edit(slug):
|
def edit(slug):
|
||||||
topic = Topics.find({"slug": slug})
|
topic = Topics.find({"slug": slug})
|
||||||
if not topic:
|
if not topic:
|
||||||
return "no"
|
return redirect(url_for('.all_topics'))
|
||||||
return render_template("topics/edit.html", topic=topic)
|
return render_template("topics/edit.html", topic=topic)
|
||||||
|
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ def edit(slug):
|
|||||||
def edit_post(slug):
|
def edit_post(slug):
|
||||||
topic = Topics.find({"slug": slug})
|
topic = Topics.find({"slug": slug})
|
||||||
if not topic:
|
if not topic:
|
||||||
return "no"
|
return redirect(url_for('.all_topics'))
|
||||||
|
|
||||||
topic.update({
|
topic.update({
|
||||||
"name": request.form.get('name', default = topic.name).strip(),
|
"name": request.form.get('name', default = topic.name).strip(),
|
||||||
@ -104,7 +104,7 @@ def edit_post(slug):
|
|||||||
def delete(slug):
|
def delete(slug):
|
||||||
topic = Topics.find({"slug": slug})
|
topic = Topics.find({"slug": slug})
|
||||||
if not topic:
|
if not topic:
|
||||||
return "no"
|
return redirect(url_for('.all_topics'))
|
||||||
|
|
||||||
topic.delete()
|
topic.delete()
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ from ..constants import InfoboxKind, PermissionLevel
|
|||||||
from ..auth import digest, verify
|
from ..auth import digest, verify
|
||||||
from wand.image import Image
|
from wand.image import Image
|
||||||
from wand.exceptions import WandException
|
from wand.exceptions import WandException
|
||||||
|
from datetime import datetime, timedelta
|
||||||
import secrets
|
import secrets
|
||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
@ -64,7 +65,18 @@ def create_session(user_id):
|
|||||||
return Sessions.create({
|
return Sessions.create({
|
||||||
"key": secrets.token_hex(16),
|
"key": secrets.token_hex(16),
|
||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
"expires_at": int(time.time()) + 32 * 24 * 60 * 60,
|
"expires_at": int(time.time()) + 31 * 24 * 60 * 60,
|
||||||
|
})
|
||||||
|
|
||||||
|
def extend_session(user_id):
|
||||||
|
session_obj = Sessions.find({'key': session['pyrom_session_key']})
|
||||||
|
if not session_obj:
|
||||||
|
return
|
||||||
|
new_duration = timedelta(31)
|
||||||
|
current_app.permanent_session_lifetime = new_duration
|
||||||
|
session.modified = True
|
||||||
|
session_obj.update({
|
||||||
|
'expires_at': int(time.time()) + 31 * 24 * 60 * 60
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@ -269,14 +281,17 @@ def settings_form(username):
|
|||||||
def set_avatar(username):
|
def set_avatar(username):
|
||||||
user = get_active_user()
|
user = get_active_user()
|
||||||
if user.is_guest():
|
if user.is_guest():
|
||||||
return 'no'
|
flash('You must be logged in to perform this action.', InfoboxKind.ERROR)
|
||||||
|
return redirect(url_for('.settings', user.username))
|
||||||
if 'avatar' not in request.files:
|
if 'avatar' not in request.files:
|
||||||
return 'no!...'
|
flash('Avatar missing.', InfoboxKind.ERROR)
|
||||||
|
return redirect(url_for('.settings', user.username))
|
||||||
|
|
||||||
file = request.files['avatar']
|
file = request.files['avatar']
|
||||||
|
|
||||||
if file.filename == '':
|
if file.filename == '':
|
||||||
return 'no..?'
|
flash('Avatar missing.', InfoboxKind.ERROR)
|
||||||
|
return redirect(url_for('.settings', user.username))
|
||||||
|
|
||||||
file_bytes = file.read()
|
file_bytes = file.read()
|
||||||
|
|
||||||
@ -300,7 +315,30 @@ def set_avatar(username):
|
|||||||
old_avatar.delete()
|
old_avatar.delete()
|
||||||
return redirect(url_for('.settings', username=user.username))
|
return redirect(url_for('.settings', username=user.username))
|
||||||
else:
|
else:
|
||||||
return 'uhhhh no'
|
flash('Something went wrong. Please try again later.', InfoboxKind.WARN)
|
||||||
|
return redirect(url_for('.settings', user.username))
|
||||||
|
|
||||||
|
|
||||||
|
@bp.post('/<username>/change_password')
|
||||||
|
@login_required
|
||||||
|
def change_password(username):
|
||||||
|
user = get_active_user()
|
||||||
|
password = request.form.get('new_password')
|
||||||
|
password2 = request.form.get('new_password2')
|
||||||
|
|
||||||
|
if not validate_password(password):
|
||||||
|
flash("Invalid password.", InfoboxKind.ERROR)
|
||||||
|
return redirect(url_for('.settings', username=user.username))
|
||||||
|
|
||||||
|
if password != password2:
|
||||||
|
flash("Passwords do not match.", InfoboxKind.ERROR)
|
||||||
|
return redirect(url_for('.settings', username=user.username))
|
||||||
|
|
||||||
|
hashed = digest(password)
|
||||||
|
user.update({'password_hash': hashed})
|
||||||
|
extend_session(user.id)
|
||||||
|
flash('Password updated.', InfoboxKind.INFO)
|
||||||
|
return redirect(url_for('.settings', username=user.username))
|
||||||
|
|
||||||
|
|
||||||
@bp.post('/<username>/clear_avatar')
|
@bp.post('/<username>/clear_avatar')
|
||||||
@ -308,7 +346,7 @@ def set_avatar(username):
|
|||||||
def clear_avatar(username):
|
def clear_avatar(username):
|
||||||
user = get_active_user()
|
user = get_active_user()
|
||||||
if user.is_default_avatar():
|
if user.is_default_avatar():
|
||||||
return 'no'
|
return redirect(url_for('.settings', user.username))
|
||||||
|
|
||||||
old_avatar = Avatars.find({'id': user.avatar_id})
|
old_avatar = Avatars.find({'id': user.avatar_id})
|
||||||
user.update({'avatar_id': 1})
|
user.update({'avatar_id': 1})
|
||||||
@ -336,9 +374,9 @@ def log_out():
|
|||||||
def confirm_user(user_id):
|
def confirm_user(user_id):
|
||||||
target_user = Users.find({"id": user_id})
|
target_user = Users.find({"id": user_id})
|
||||||
if not target_user:
|
if not target_user:
|
||||||
return "no"
|
return redirect(url_for('.all_topics'))
|
||||||
if int(target_user.permission) > PermissionLevel.GUEST.value:
|
if int(target_user.permission) > PermissionLevel.GUEST.value:
|
||||||
return "no"
|
return redirect(url_for('.page', username=target_user.username))
|
||||||
|
|
||||||
target_user.update({
|
target_user.update({
|
||||||
"permission": PermissionLevel.USER.value,
|
"permission": PermissionLevel.USER.value,
|
||||||
@ -353,9 +391,9 @@ def confirm_user(user_id):
|
|||||||
def mod_user(user_id):
|
def mod_user(user_id):
|
||||||
target_user = Users.find({"id": user_id})
|
target_user = Users.find({"id": user_id})
|
||||||
if not target_user:
|
if not target_user:
|
||||||
return "no"
|
return redirect(url_for('.all_topics'))
|
||||||
if target_user.is_mod():
|
if target_user.is_mod():
|
||||||
return "no"
|
return redirect(url_for('.page', username=target_user.username))
|
||||||
|
|
||||||
target_user.update({
|
target_user.update({
|
||||||
"permission": PermissionLevel.MODERATOR.value,
|
"permission": PermissionLevel.MODERATOR.value,
|
||||||
@ -369,9 +407,9 @@ def mod_user(user_id):
|
|||||||
def demod_user(user_id):
|
def demod_user(user_id):
|
||||||
target_user = Users.find({"id": user_id})
|
target_user = Users.find({"id": user_id})
|
||||||
if not target_user:
|
if not target_user:
|
||||||
return "no"
|
return redirect(url_for('.all_topics'))
|
||||||
if not target_user.is_mod():
|
if not target_user.is_mod():
|
||||||
return "no"
|
return redirect(url_for('.page', username=target_user.username))
|
||||||
|
|
||||||
target_user.update({
|
target_user.update({
|
||||||
"permission": PermissionLevel.USER.value,
|
"permission": PermissionLevel.USER.value,
|
||||||
@ -385,9 +423,9 @@ def demod_user(user_id):
|
|||||||
def guest_user(user_id):
|
def guest_user(user_id):
|
||||||
target_user = Users.find({"id": user_id})
|
target_user = Users.find({"id": user_id})
|
||||||
if not target_user:
|
if not target_user:
|
||||||
return "no"
|
return redirect(url_for('.all_topics'))
|
||||||
if target_user.is_mod():
|
if target_user.is_mod():
|
||||||
return "no"
|
return redirect(url_for('.page', username=target_user.username))
|
||||||
|
|
||||||
target_user.update({
|
target_user.update({
|
||||||
"permission": PermissionLevel.GUEST.value,
|
"permission": PermissionLevel.GUEST.value,
|
||||||
|
114
app/templates/babycode.html
Normal file
114
app/templates/babycode.html
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
<!-- kate: remove-trailing-space off; -->
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% block title %}babycode guide{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div class=darkbg>
|
||||||
|
<h1 class="thread-title">Babycode guide</h1>
|
||||||
|
</div>
|
||||||
|
<div class="babycode-guide-container">
|
||||||
|
<div class="guide-topics">
|
||||||
|
{% set sections %}
|
||||||
|
<section class="babycode-guide-section">
|
||||||
|
<h2 id="what-is-babycode">What is babycode?</h2>
|
||||||
|
<p>You may be familiar with BBCode, a loosely related family of markup languages popular on forums. Babycode is another, simplified, dialect of those languages. It is a way of formatting text by enclosing parts of it in special tags.</p>
|
||||||
|
</section>
|
||||||
|
<section class="babycode-guide-section">
|
||||||
|
<h2 id="text-formatting-tags">Text formatting tags</h2>
|
||||||
|
<ul>
|
||||||
|
<li>To make some text <strong>bold</strong>, enclose it in <code class="inline-code">[b][/b]</code>:<br>
|
||||||
|
[b]Hello World[/b]<br>
|
||||||
|
Will become<br>
|
||||||
|
<strong>Hello World</strong>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li>To <em>italicize</em> text, enclose it in <code class="inline-code">[i][/i]</code>:<br>
|
||||||
|
[i]Hello World[/i]<br>
|
||||||
|
Will become<br>
|
||||||
|
<em>Hello World</em>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li>To make some text <del>strikethrough</del>, enclose it in <code class="inline-code">[s][/s]</code>:<br>
|
||||||
|
[s]Hello World[/s]<br>
|
||||||
|
Will become<br>
|
||||||
|
<del>Hello World</del>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
<section class="babycode-guide-section">
|
||||||
|
<h2 id="emoji">Emoji</h2>
|
||||||
|
<p>There are a few emoji in the style of old forum emotes:</p>
|
||||||
|
<table class="emoji-table">
|
||||||
|
<tr>
|
||||||
|
<th>Short code</th>
|
||||||
|
<th>Emoji result</th>
|
||||||
|
</tr>
|
||||||
|
{% for emoji in __emoji %}
|
||||||
|
<tr>
|
||||||
|
<td>:{{ emoji }}:</td>
|
||||||
|
<td>{{ __emoji[emoji] | safe }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
<p>Special thanks to the <a href="https://gh.vercte.net/forumoji/">Forumoji project</a> and its contributors for these graphics.</p>
|
||||||
|
</section>
|
||||||
|
<section class="babycode-guide-section">
|
||||||
|
<h2 id="paragraph-rules">Paragraph rules</h2>
|
||||||
|
<p>Line breaks in babycode work like Markdown: to start a new paragraph, use two line breaks:</p>
|
||||||
|
{{ '[code]paragraph 1\n\nparagraph 2[/code]' | babycode | safe }}
|
||||||
|
Will produce:<br>
|
||||||
|
{{ 'paragraph 1\n\nparagraph 2' | babycode | safe }}
|
||||||
|
<p>To break a line without starting a new paragraph, end a line with two spaces:</p>
|
||||||
|
{{ '[code]paragraph 1 \nstill paragraph 1[/code]' | babycode | safe }}
|
||||||
|
That will produce:<br>
|
||||||
|
{{ 'paragraph 1 \nstill paragraph 1' | babycode | safe }}
|
||||||
|
</section>
|
||||||
|
<section class="babycode-guide-section">
|
||||||
|
<h2 id="links">Links</h2>
|
||||||
|
<p>Loose links (starting with http:// or https://) will automatically get converted to clickable links. To add a label to a link, use<br><code class="inline-code">[url=https://example.com]Link label[/url]</code>:<br>
|
||||||
|
<a href="https://example.com">Link label</a></p>
|
||||||
|
</section>
|
||||||
|
<section class="babycode-guide-section">
|
||||||
|
<h2 id="attaching-an-image">Attaching an image</h2>
|
||||||
|
<p>To add an image to your post, use the <code class="inline-code">[img]</code> tag:<br>
|
||||||
|
<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 }}
|
||||||
|
</p>
|
||||||
|
<p>Text inside the tag becomes the alt text. The attribute is the image URL.</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>
|
||||||
|
</section>
|
||||||
|
<section class="babycode-guide-section">
|
||||||
|
<h2 id="adding-code-blocks">Adding code blocks</h2>
|
||||||
|
{% set code = 'func _ready() -> void:\n\tprint("hello world!")' %}
|
||||||
|
<p>There are two kinds of code blocks recognized by babycode: inline and block. Inline code blocks do not break a paragraph. They can be added with <code class="inline-code">[code]your code here[/code]</code>. As long as there are no line breaks inside the code block, it is considered inline. If there are any, it will produce this:</p>
|
||||||
|
{{ ('[code]%s[/code]' % code) | babycode | safe }}
|
||||||
|
<br>
|
||||||
|
<p>Inline code tags look like this: {{ '[code]Inline code[/code]' | babycode | safe }}</p>
|
||||||
|
<p>Babycodes are not parsed inside code blocks.</p>
|
||||||
|
</section>
|
||||||
|
<section class="babycode-guide-section">
|
||||||
|
<h2 id="quoting">Quoting</h2>
|
||||||
|
<p>Text enclosed within <code class="inline-code">[quote][/quote]</code> will look like a quote:</p>
|
||||||
|
<blockquote>A man provided with paper, pencil, and rubber, and subject to strict discipline, is in effect a universal machine.</blockquote>
|
||||||
|
</section>
|
||||||
|
<section class="babycode-guide-section">
|
||||||
|
<h2 id="lists">Lists</h2>
|
||||||
|
{% set list = '[ul]\nitem 1\n\nitem 2\n\nitem 3 \nstill item 3 (break line without inserting a new item by using two spaces at the end of a line)\n[/ul]' %}
|
||||||
|
<p>There are two kinds of lists, ordered (1, 2, 3, ...) and unordered (bullet points). Ordered lists are made with <code class="inline-code">[ol][/ol]</code> tags, and unordered with <code class="inline-code">[ul][/ul]</code>. Every new paragraph according to the <a href="#paragraph-rules">usual paragraph rules</a> will create a new list item. For example:</p>
|
||||||
|
{{ ('[code]%s[/code]' % list) | babycode | safe }}
|
||||||
|
Will produce the following list:
|
||||||
|
{{ list | babycode | safe }}
|
||||||
|
</section>
|
||||||
|
{% endset %}
|
||||||
|
{{ sections | safe }}
|
||||||
|
</div>
|
||||||
|
<div class="guide-toc">
|
||||||
|
<h2>Table of contents</h2>
|
||||||
|
{% set toc = sections | extract_h2 %}
|
||||||
|
<ul>
|
||||||
|
{% for heading in toc %}
|
||||||
|
<li><a href='#{{ heading.id }}'>{{ heading.text }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -8,7 +8,8 @@
|
|||||||
<label for="topic_id">Topic</label>
|
<label for="topic_id">Topic</label>
|
||||||
<select name="topic_id" id="topic_id" autocomplete="off">
|
<select name="topic_id" id="topic_id" autocomplete="off">
|
||||||
{% for topic in all_topics %}
|
{% for topic in all_topics %}
|
||||||
<option value="{{ topic['id'] }}" {{"selected" if (request.args.get('topic_id')) == (topic['id'] | string) else ""}}>{{ topic['name'] }}</option>
|
{% set disable_topic = active_user and not active_user.can_post_to_topic(topic) %}
|
||||||
|
<option value="{{ topic['id'] }}" {{"selected" if (request.args.get('topic_id')) == (topic['id'] | string) else ""}} {{'disabled' if disable_topic else ''}} >{{ topic['name'] }}{{ ' (locked)' if topic.is_locked }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select><br>
|
</select><br>
|
||||||
<label for="title">Thread title</label>
|
<label for="title">Thread title</label>
|
||||||
|
@ -28,5 +28,12 @@
|
|||||||
<label for='subscribe_by_default'>Subscribe to thread by default when responding</label><br>
|
<label for='subscribe_by_default'>Subscribe to thread by default when responding</label><br>
|
||||||
<input type='submit' value='Save settings'>
|
<input type='submit' value='Save settings'>
|
||||||
</form>
|
</form>
|
||||||
|
<form method='post' action='{{ url_for('users.change_password', username=active_user.username) }}'>
|
||||||
|
<label for="new_password">Change password</label><br>
|
||||||
|
<input type="password" id="new_password" name="new_password" pattern="(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_])(?!.*\s).{10,}" title="10+ chars with: 1 uppercase, 1 lowercase, 1 number, 1 special char, and no spaces" required autocomplete="new-password"><br>
|
||||||
|
<label for="new_password2">Confirm new password</label><br>
|
||||||
|
<input type="password" id="new_password2" name="new_password2" pattern="(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_])(?!.*\s).{10,}" title="10+ chars with: 1 uppercase, 1 lowercase, 1 number, 1 special char, and no spaces" required autocomplete="new-password"><br>
|
||||||
|
<input class="warn" type="submit" value="Change password">
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
Loading…
Reference in New Issue
Block a user