add motd schema and motd editor
This commit is contained in:
@@ -218,10 +218,14 @@ def should_collapse(text, surrounding):
|
||||
|
||||
return False
|
||||
|
||||
def babycode_to_html(s):
|
||||
def babycode_to_html(s, banned_tags=None):
|
||||
allowed_tags = list(TAGS.keys())
|
||||
if banned_tags is not None:
|
||||
for tag in banned_tags:
|
||||
allowed_tags.remove(tag)
|
||||
subj = escape(s.strip().replace('\r\n', '\n').replace('\r', '\n'))
|
||||
parser = Parser(subj)
|
||||
parser.valid_bbcode_tags = TAGS.keys()
|
||||
parser.valid_bbcode_tags = allowed_tags
|
||||
parser.bbcode_tags_only_text_children = TEXT_ONLY
|
||||
parser.valid_emotes = EMOJI.keys()
|
||||
|
||||
|
||||
@@ -394,3 +394,19 @@ class BookmarkedThreads(Model):
|
||||
|
||||
def get_thread(self):
|
||||
return Threads.find({'id': self.thread_id})
|
||||
|
||||
|
||||
class MOTD(Model):
|
||||
table = 'motd'
|
||||
|
||||
@classmethod
|
||||
def has_motd(cls):
|
||||
q = 'SELECT EXISTS(SELECT 1 FROM motd) as e'
|
||||
res = db.fetch_one(q)['e']
|
||||
return int(res) == 1
|
||||
|
||||
@classmethod
|
||||
def get_all(cls):
|
||||
q = 'SELECT id FROM motd'
|
||||
res = db.query(q)
|
||||
return [MOTD.find({'id': i['id']}) for i in res]
|
||||
|
||||
@@ -40,7 +40,8 @@ def babycode_preview():
|
||||
markup = request.json.get('markup')
|
||||
if not markup or not isinstance(markup, str):
|
||||
return {'error': 'markup field missing or invalid type'}, 400
|
||||
rendered = babycode_to_html(markup)
|
||||
banned_tags = request.json.get('banned_tags', [])
|
||||
rendered = babycode_to_html(markup, banned_tags)
|
||||
return {'html': rendered}
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
from flask import (
|
||||
Blueprint, render_template, request, redirect, url_for
|
||||
Blueprint, render_template, request, redirect, url_for,
|
||||
flash
|
||||
)
|
||||
from .users import get_active_user, is_logged_in
|
||||
from ..models import Users, PasswordResetLinks
|
||||
from ..models import Users, PasswordResetLinks, MOTD
|
||||
from ..constants import InfoboxKind
|
||||
from ..lib.babycode import babycode_to_html, BABYCODE_VERSION
|
||||
from ..db import db
|
||||
import secrets
|
||||
import time
|
||||
@@ -55,3 +58,47 @@ def create_reset_pass(user_id):
|
||||
@bp.get('/panel')
|
||||
def panel():
|
||||
return render_template('mod/panel.html')
|
||||
|
||||
|
||||
@bp.get('/motd')
|
||||
def motd_editor():
|
||||
current = MOTD.get_all()[0] if MOTD.has_motd() else None
|
||||
return render_template('mod/motd.html', current=current)
|
||||
|
||||
|
||||
@bp.post('/motd')
|
||||
def motd_editor_form():
|
||||
orig_body = request.form.get('body', default='')
|
||||
title = request.form.get('title', default='')
|
||||
data = {
|
||||
'title': title,
|
||||
'body_original_markup': orig_body,
|
||||
'body_rendered': babycode_to_html(orig_body, banned_tags=['img', 'spoiler']),
|
||||
'format_version': BABYCODE_VERSION,
|
||||
'edited_at': int(time.time()),
|
||||
}
|
||||
|
||||
if MOTD.has_motd():
|
||||
motd = MOTD.get_all()[0]
|
||||
motd.update(data)
|
||||
message = 'MOTD updated.'
|
||||
else:
|
||||
data['created_at'] = int(time.time())
|
||||
data['user_id'] = get_active_user().id
|
||||
motd = MOTD.create(data)
|
||||
message = 'MOTD created.'
|
||||
|
||||
flash(message, InfoboxKind.INFO)
|
||||
return redirect(url_for('.motd_editor'))
|
||||
|
||||
|
||||
@bp.post('/motd/delete')
|
||||
def motd_delete():
|
||||
if not MOTD.has_motd():
|
||||
flash('No MOTD to delete.', InfoboxKind.WARN)
|
||||
return redirect(url_for('.motd_editor'))
|
||||
|
||||
current = MOTD.get_all()[0]
|
||||
current.delete()
|
||||
flash('MOTD deleted.', InfoboxKind.INFO)
|
||||
return redirect(url_for('.motd_editor'))
|
||||
|
||||
@@ -120,6 +120,18 @@ SCHEMA = [
|
||||
UNIQUE(collection_id, thread_id)
|
||||
)""",
|
||||
|
||||
"""CREATE TABLE IF NOT EXISTS "motd" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY,
|
||||
"title" TEXT NOT NULL,
|
||||
"body_original_markup" TEXT NOT NULL,
|
||||
"body_rendered" TEXT NOT NULL,
|
||||
"markup_language" TEXT NOT NULL DEFAULT 'babycode',
|
||||
"format_version" INTEGER DEFAULT NULL,
|
||||
"created_at" INTEGER DEFAULT (unixepoch(CURRENT_TIMESTAMP)),
|
||||
"edited_at" INTEGER DEFAULT (unixepoch(CURRENT_TIMESTAMP)),
|
||||
"user_id" REFERENCES users(id) ON DELETE CASCADE
|
||||
)""",
|
||||
|
||||
# INDEXES
|
||||
"CREATE INDEX IF NOT EXISTS idx_post_history_post_id ON post_history(post_id)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_posts_thread ON posts(thread_id, created_at, id)",
|
||||
|
||||
@@ -47,3 +47,9 @@
|
||||
<path d="M13 20H6C4.89543 20 4 19.1046 4 18V6C4 4.89543 4.89543 4 6 4H18C19.1046 4 20 4.89543 20 6V13M13 20L20 13M13 20V14C13 13.4477 13.4477 13 14 13H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro icn_megaphone(width=60) -%}
|
||||
<svg width="{{width}}px" height="{{width}}px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 18V14M6 14H8L13 17V7L8 10H5C3.89543 10 3 10.8954 3 12V12C3 13.1046 3.89543 14 5 14H6ZM17 7L19 5M17 17L19 19M19 12H21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
{%- endmacro %}
|
||||
|
||||
@@ -39,18 +39,24 @@
|
||||
{% macro infobox(message, kind=InfoboxKind.INFO) %}
|
||||
<div class="{{ "infobox " + InfoboxHTMLClass[kind] }}">
|
||||
<span>
|
||||
<div class="infobox-icon-container">
|
||||
{%- if kind == InfoboxKind.INFO -%}
|
||||
{{- icn_info() -}}
|
||||
{%- elif kind == InfoboxKind.LOCK -%}
|
||||
{{- icn_lock() -}}
|
||||
{%- elif kind == InfoboxKind.WARN -%}
|
||||
{{- icn_warn() -}}
|
||||
{%- elif kind == InfoboxKind.ERROR -%}
|
||||
{{- icn_error() -}}
|
||||
{%- endif -%}
|
||||
</div>
|
||||
{{ message }}
|
||||
<div class="infobox-icon-container">
|
||||
{%- if kind == InfoboxKind.INFO -%}
|
||||
{{- icn_info() -}}
|
||||
{%- elif kind == InfoboxKind.LOCK -%}
|
||||
{{- icn_lock() -}}
|
||||
{%- elif kind == InfoboxKind.WARN -%}
|
||||
{{- icn_warn() -}}
|
||||
{%- elif kind == InfoboxKind.ERROR -%}
|
||||
{{- icn_error() -}}
|
||||
{%- endif -%}
|
||||
</div>
|
||||
<span>
|
||||
{% set m = message.split(';', maxsplit=1) %}
|
||||
<strong>{{ m[0] }}</strong>
|
||||
{%- if m[1] %}
|
||||
{{ m[1] -}}
|
||||
{%- endif -%}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -59,27 +65,38 @@
|
||||
<span class="timestamp" data-utc="{{ unix_ts }}">{{ unix_ts | ts_datetime('%Y-%m-%d %H:%M')}} <abbr title="Server Time">ST</abbr></span>
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro babycode_editor_component(ta_name, ta_placeholder="Post body", optional=False, prefill="") %}
|
||||
{% macro babycode_editor_component(ta_name, ta_placeholder="Post body", optional=False, prefill="", banned_tags=[]) %}
|
||||
<div class="babycode-editor-container">
|
||||
<input type="hidden" id="babycode-banned-tags" value="{{banned_tags | unique | list | tojson | forceescape}}">
|
||||
<div class="tab-buttons">
|
||||
<button type=button class="tab-button active" data-target-id="tab-edit">Write</button>
|
||||
<button type=button class="tab-button" data-target-id="tab-preview">Preview</button>
|
||||
</div>
|
||||
<div class="tab-content active" id="tab-edit">
|
||||
<span class="babycode-button-container">
|
||||
<button class="babycode-button" type=button id="post-editor-bold" title="Insert Bold"><strong>B</strong></button>
|
||||
<button class="babycode-button" type=button id="post-editor-italics" title="Insert Italics"><em>I</em></button>
|
||||
<button class="babycode-button" type=button id="post-editor-strike" title="Insert Strikethrough"><del>S</del></button>
|
||||
<button class="babycode-button" type=button id="post-editor-underline" title="Insert Underline"><u>U</u></button>
|
||||
<button class="babycode-button" type=button id="post-editor-url" title="Insert Link"><code>://</code></button>
|
||||
<button class="babycode-button" type=button id="post-editor-code" title="Insert Code block"><code></></code></button>
|
||||
<button class="babycode-button contain-svg" type=button id="post-editor-img" title="Insert Image">{{ icn_image() }}</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 contain-svg" type=button id="post-editor-spoiler" title="Insert spoiler">{{ icn_spoiler() }}</button>
|
||||
<button class="babycode-button" type=button id="post-editor-bold" title="Insert Bold" {{"disabled" if "b" in banned_tags else ""}}><strong>B</strong></button>
|
||||
<button class="babycode-button" type=button id="post-editor-italics" title="Insert Italics" {{"disabled" if "i" in banned_tags else ""}}><em>I</em></button>
|
||||
<button class="babycode-button" type=button id="post-editor-strike" title="Insert Strikethrough" {{"disabled" if "s" in banned_tags else ""}}><del>S</del></button>
|
||||
<button class="babycode-button" type=button id="post-editor-underline" title="Insert Underline" {{"disabled" if "u" in banned_tags else ""}}><u>U</u></button>
|
||||
<button class="babycode-button" type=button id="post-editor-url" title="Insert Link" {{"disabled" if "url" in banned_tags else ""}}><code>://</code></button>
|
||||
<button class="babycode-button" type=button id="post-editor-code" title="Insert Code block" {{"disabled" if "code" in banned_tags else ""}}><code></></code></button>
|
||||
<button class="babycode-button contain-svg" type=button id="post-editor-img" title="Insert Image" {{"disabled" if "img" in banned_tags else ""}}>{{ icn_image() }}</button>
|
||||
<button class="babycode-button" type=button id="post-editor-ol" title="Insert Ordered list" {{"disabled" if "ol" in banned_tags else ""}}>1.</button>
|
||||
<button class="babycode-button" type=button id="post-editor-ul" title="Insert Unordered list" {{"disabled" if "u;" in banned_tags else ""}}>•</button>
|
||||
<button class="babycode-button contain-svg" type=button id="post-editor-spoiler" title="Insert spoiler" {{"disabled" if "spoiler" in banned_tags else ""}}>{{ icn_spoiler() }}</button>
|
||||
</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 "" }} autocomplete="off">{{ prefill }}</textarea>
|
||||
<a href="{{ url_for("app.babycode_guide") }}" target="_blank">babycode guide</a>
|
||||
{% if banned_tags %}
|
||||
<div>Forbidden tags:</div>
|
||||
<div>
|
||||
<ul class="horizontal">
|
||||
{% for tag in banned_tags | unique %}
|
||||
<li><code class="inline-code">[{{ tag }}]</code></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="tab-content" id="tab-preview">
|
||||
<div id="babycode-preview-errors-container">Type something!</div>
|
||||
|
||||
17
app/templates/mod/motd.html
Normal file
17
app/templates/mod/motd.html
Normal file
@@ -0,0 +1,17 @@
|
||||
{% from 'common/macros.html' import babycode_editor_component %}
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}editing MOTD{% endblock %}
|
||||
{% block content %}
|
||||
<div class="darkbg settings-container">
|
||||
<h1>Edit Message of the Day</h1>
|
||||
<p>The Message of the Day will show up on the main page and in every topic.</p>
|
||||
<form method="POST">
|
||||
<label for="title">Title</label>
|
||||
<input name="title" id="title" type="text" required autocomplete="off" placeholder="Required" value="{{ current.title }}"><br>
|
||||
<label for="body">Body</label>
|
||||
{{ babycode_editor_component('body', ta_placeholder='MOTD body (required)', banned_tags=['img', 'spoiler'], prefill=current.body_original_markup) }}
|
||||
<input type="submit" value="Save">
|
||||
<input class="critical" type="submit" formaction="{{ url_for('mod.motd_delete') }}" value="Delete MOTD" formnovalidate {{"disabled" if not current else ""}}>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -6,6 +6,7 @@
|
||||
<ul>
|
||||
<li><a href="{{ url_for('mod.user_list') }}">User list</a></li>
|
||||
<li><a href="{{ url_for('mod.sort_topics') }}">Sort topics</a></li>
|
||||
<li><a href="{{ url_for('mod.motd_editor') }}">Message of the Day</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
</nav>
|
||||
|
||||
{% if topic['is_locked'] %}
|
||||
{{ infobox("This topic is locked. Only moderators can create new threads.", InfoboxKind.INFO) }}
|
||||
{{ infobox("This topic is locked.;Only moderators can create new threads.", InfoboxKind.INFO) }}
|
||||
{% endif %}
|
||||
|
||||
{% if threads_list | length == 0 %}
|
||||
|
||||
@@ -147,13 +147,17 @@
|
||||
if (markup === "" || markup === previousMarkup) {
|
||||
return;
|
||||
}
|
||||
const bannedTags = JSON.parse(document.getElementById('babycode-banned-tags').value);
|
||||
previousMarkup = markup;
|
||||
const req = await fetch(previewEndpoint, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({markup: markup})
|
||||
body: JSON.stringify({
|
||||
markup: markup,
|
||||
banned_tags: bannedTags,
|
||||
})
|
||||
})
|
||||
if (!req.ok) {
|
||||
switch (req.status) {
|
||||
|
||||
@@ -1082,7 +1082,6 @@ ul.horizontal, ol.horizontal {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.new-concept-notification.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user