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,
|
||||
InfoboxKind, InfoboxIcons, InfoboxHTMLClass
|
||||
)
|
||||
from .lib.babycode import babycode_to_html, EMOJI
|
||||
from datetime import datetime
|
||||
import os
|
||||
import time
|
||||
@ -103,6 +104,7 @@ def create_app():
|
||||
"InfoboxHTMLClass": InfoboxHTMLClass,
|
||||
"InfoboxKind": InfoboxKind,
|
||||
"__commit": commit,
|
||||
"__emoji": EMOJI,
|
||||
}
|
||||
|
||||
@app.context_processor
|
||||
@ -124,4 +126,18 @@ def create_app():
|
||||
def permission_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
|
||||
|
@ -5,7 +5,7 @@ import re
|
||||
def tag_code(children, attr):
|
||||
is_inline = children.find('\n') == -1
|
||||
if is_inline:
|
||||
return f"<code class=\"inline_code\">{children}</code>"
|
||||
return f"<code class=\"inline-code\">{children}</code>"
|
||||
else:
|
||||
t = children.strip()
|
||||
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>",
|
||||
}
|
||||
|
||||
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"]
|
||||
|
||||
def break_lines(text):
|
||||
@ -40,8 +87,10 @@ def babycode_to_html(s):
|
||||
parser = Parser(subj)
|
||||
parser.valid_bbcode_tags = TAGS.keys()
|
||||
parser.bbcode_tags_only_text_children = TEXT_ONLY
|
||||
parser.valid_emotes = EMOJI.keys()
|
||||
|
||||
elements = parser.parse()
|
||||
print(elements)
|
||||
out = ""
|
||||
def fold(element, nobr):
|
||||
if isinstance(element, str):
|
||||
@ -59,6 +108,8 @@ def babycode_to_html(s):
|
||||
return res
|
||||
case "link":
|
||||
return f"<a href=\"{element['url']}\">{element['url']}</a>"
|
||||
case 'emote':
|
||||
return EMOJI[element['name']]
|
||||
case "rule":
|
||||
return "<hr>"
|
||||
for e in elements:
|
||||
|
@ -71,6 +71,18 @@ class Users(Model):
|
||||
subscriptions.user_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):
|
||||
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 = "/")
|
||||
|
||||
@ -9,4 +9,4 @@ def index():
|
||||
|
||||
@bp.route("/babycode")
|
||||
def babycode_guide():
|
||||
return "not yet"
|
||||
return render_template('babycode.html')
|
||||
|
@ -18,7 +18,7 @@ def thread(slug):
|
||||
POSTS_PER_PAGE = 10
|
||||
thread = Threads.find({"slug": slug})
|
||||
if not thread:
|
||||
return "no"
|
||||
return redirect(url_for('topics.all_topics'))
|
||||
|
||||
post_count = Posts.count({"thread_id": thread.id})
|
||||
page_count = max(math.ceil(post_count / POSTS_PER_PAGE), 1)
|
||||
@ -69,12 +69,12 @@ def thread(slug):
|
||||
def reply(slug):
|
||||
thread = Threads.find({"slug": slug})
|
||||
if not thread:
|
||||
return "no"
|
||||
return redirect(url_for('topics.all_topics'))
|
||||
user = get_active_user()
|
||||
if user.is_guest():
|
||||
return "no"
|
||||
return redirect(url_for('.thread', slug=slug))
|
||||
if thread.locked() and not user.is_mod():
|
||||
return "no"
|
||||
return redirect(url_for('.thread', slug=slug))
|
||||
|
||||
post_content = request.form['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']})
|
||||
user = get_active_user()
|
||||
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():
|
||||
return "no"
|
||||
flash(f'Topic "{topic.name}" is locked', InfoboxKind.ERROR)
|
||||
return redirect(url_for('.create'))
|
||||
|
||||
title = request.form['title'].strip()
|
||||
now = int(time.time())
|
||||
@ -128,8 +130,10 @@ def create_form():
|
||||
def lock(slug):
|
||||
user = get_active_user()
|
||||
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()):
|
||||
return 'no'
|
||||
return redirect(url_for('.thread', slug=slug))
|
||||
target_op = request.form.get('target_op')
|
||||
thread.update({
|
||||
'is_locked': target_op
|
||||
@ -143,8 +147,10 @@ def lock(slug):
|
||||
def sticky(slug):
|
||||
user = get_active_user()
|
||||
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()):
|
||||
return 'no'
|
||||
return redirect(url_for('.thread', slug=slug))
|
||||
target_op = request.form.get('target_op')
|
||||
thread.update({
|
||||
'is_stickied': target_op
|
||||
@ -167,12 +173,12 @@ def move(slug):
|
||||
'id': new_topic_id
|
||||
})
|
||||
if not new_topic:
|
||||
return 'no'
|
||||
return redirect(url_for('topics.all_topics'))
|
||||
thread = Threads.find({
|
||||
'slug': slug
|
||||
})
|
||||
if not thread:
|
||||
return 'no'
|
||||
return redirect(url_for('topics.all_topics'))
|
||||
if new_topic.id == thread.topic_id:
|
||||
flash('Thread is already in this topic.', InfoboxKind.ERROR)
|
||||
return redirect(url_for('.thread', slug=slug))
|
||||
@ -189,7 +195,7 @@ def subscribe(slug):
|
||||
user = get_active_user()
|
||||
thread = Threads.find({'slug': slug})
|
||||
if not thread:
|
||||
return 'no'
|
||||
return redirect(url_for('topics.all_topics'))
|
||||
subscription = Subscriptions.find({
|
||||
'user_id': user.id,
|
||||
'thread_id': thread.id,
|
||||
@ -204,11 +210,11 @@ def subscribe(slug):
|
||||
})
|
||||
elif request.form['subscribe'] == 'unsubscribe':
|
||||
if not subscription:
|
||||
return 'no'
|
||||
return redirect(url_for('.thread', slug=slug))
|
||||
subscription.delete()
|
||||
elif request.form['subscribe'] == 'read':
|
||||
if not subscription:
|
||||
return 'no'
|
||||
return redirect(url_for('.thread', slug=slug))
|
||||
subscription.update({
|
||||
'last_seen': int(time.time())
|
||||
})
|
||||
|
@ -51,7 +51,7 @@ def topic(slug):
|
||||
"slug": slug
|
||||
})
|
||||
if not target_topic:
|
||||
return "no"
|
||||
return redirect(url_for('.all_topics'))
|
||||
|
||||
threads_count = Threads.count({
|
||||
"topic_id": target_topic.id
|
||||
@ -77,7 +77,7 @@ def topic(slug):
|
||||
def edit(slug):
|
||||
topic = Topics.find({"slug": slug})
|
||||
if not topic:
|
||||
return "no"
|
||||
return redirect(url_for('.all_topics'))
|
||||
return render_template("topics/edit.html", topic=topic)
|
||||
|
||||
|
||||
@ -87,7 +87,7 @@ def edit(slug):
|
||||
def edit_post(slug):
|
||||
topic = Topics.find({"slug": slug})
|
||||
if not topic:
|
||||
return "no"
|
||||
return redirect(url_for('.all_topics'))
|
||||
|
||||
topic.update({
|
||||
"name": request.form.get('name', default = topic.name).strip(),
|
||||
@ -104,7 +104,7 @@ def edit_post(slug):
|
||||
def delete(slug):
|
||||
topic = Topics.find({"slug": slug})
|
||||
if not topic:
|
||||
return "no"
|
||||
return redirect(url_for('.all_topics'))
|
||||
|
||||
topic.delete()
|
||||
|
||||
|
@ -9,6 +9,7 @@ from ..constants import InfoboxKind, PermissionLevel
|
||||
from ..auth import digest, verify
|
||||
from wand.image import Image
|
||||
from wand.exceptions import WandException
|
||||
from datetime import datetime, timedelta
|
||||
import secrets
|
||||
import time
|
||||
import re
|
||||
@ -64,7 +65,18 @@ def create_session(user_id):
|
||||
return Sessions.create({
|
||||
"key": secrets.token_hex(16),
|
||||
"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):
|
||||
user = get_active_user()
|
||||
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:
|
||||
return 'no!...'
|
||||
flash('Avatar missing.', InfoboxKind.ERROR)
|
||||
return redirect(url_for('.settings', user.username))
|
||||
|
||||
file = request.files['avatar']
|
||||
|
||||
if file.filename == '':
|
||||
return 'no..?'
|
||||
flash('Avatar missing.', InfoboxKind.ERROR)
|
||||
return redirect(url_for('.settings', user.username))
|
||||
|
||||
file_bytes = file.read()
|
||||
|
||||
@ -300,7 +315,30 @@ def set_avatar(username):
|
||||
old_avatar.delete()
|
||||
return redirect(url_for('.settings', username=user.username))
|
||||
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')
|
||||
@ -308,7 +346,7 @@ def set_avatar(username):
|
||||
def clear_avatar(username):
|
||||
user = get_active_user()
|
||||
if user.is_default_avatar():
|
||||
return 'no'
|
||||
return redirect(url_for('.settings', user.username))
|
||||
|
||||
old_avatar = Avatars.find({'id': user.avatar_id})
|
||||
user.update({'avatar_id': 1})
|
||||
@ -336,9 +374,9 @@ def log_out():
|
||||
def confirm_user(user_id):
|
||||
target_user = Users.find({"id": user_id})
|
||||
if not target_user:
|
||||
return "no"
|
||||
return redirect(url_for('.all_topics'))
|
||||
if int(target_user.permission) > PermissionLevel.GUEST.value:
|
||||
return "no"
|
||||
return redirect(url_for('.page', username=target_user.username))
|
||||
|
||||
target_user.update({
|
||||
"permission": PermissionLevel.USER.value,
|
||||
@ -353,9 +391,9 @@ def confirm_user(user_id):
|
||||
def mod_user(user_id):
|
||||
target_user = Users.find({"id": user_id})
|
||||
if not target_user:
|
||||
return "no"
|
||||
return redirect(url_for('.all_topics'))
|
||||
if target_user.is_mod():
|
||||
return "no"
|
||||
return redirect(url_for('.page', username=target_user.username))
|
||||
|
||||
target_user.update({
|
||||
"permission": PermissionLevel.MODERATOR.value,
|
||||
@ -369,9 +407,9 @@ def mod_user(user_id):
|
||||
def demod_user(user_id):
|
||||
target_user = Users.find({"id": user_id})
|
||||
if not target_user:
|
||||
return "no"
|
||||
return redirect(url_for('.all_topics'))
|
||||
if not target_user.is_mod():
|
||||
return "no"
|
||||
return redirect(url_for('.page', username=target_user.username))
|
||||
|
||||
target_user.update({
|
||||
"permission": PermissionLevel.USER.value,
|
||||
@ -385,9 +423,9 @@ def demod_user(user_id):
|
||||
def guest_user(user_id):
|
||||
target_user = Users.find({"id": user_id})
|
||||
if not target_user:
|
||||
return "no"
|
||||
return redirect(url_for('.all_topics'))
|
||||
if target_user.is_mod():
|
||||
return "no"
|
||||
return redirect(url_for('.page', username=target_user.username))
|
||||
|
||||
target_user.update({
|
||||
"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>
|
||||
<select name="topic_id" id="topic_id" autocomplete="off">
|
||||
{% 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 %}
|
||||
</select><br>
|
||||
<label for="title">Thread title</label>
|
||||
|
@ -28,5 +28,12 @@
|
||||
<label for='subscribe_by_default'>Subscribe to thread by default when responding</label><br>
|
||||
<input type='submit' value='Save settings'>
|
||||
</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>
|
||||
{% endblock %}
|
||||
|
Loading…
Reference in New Issue
Block a user