Semantic HTML and post feed #23
|
@ -251,7 +251,7 @@ class Lexicon(ModelBase):
|
||||||
indices = relationship("ArticleIndex", back_populates="lexicon")
|
indices = relationship("ArticleIndex", back_populates="lexicon")
|
||||||
index_rules = relationship("ArticleIndexRule", back_populates="lexicon")
|
index_rules = relationship("ArticleIndexRule", back_populates="lexicon")
|
||||||
content_rules = relationship("ArticleContentRule", back_populates="lexicon")
|
content_rules = relationship("ArticleContentRule", back_populates="lexicon")
|
||||||
posts = relationship("Post", back_populates="lexicon")
|
posts = relationship("Post", back_populates="lexicon", order_by="Post.created.desc()")
|
||||||
|
|
||||||
#######################
|
#######################
|
||||||
# Derived information #
|
# Derived information #
|
||||||
|
|
|
@ -215,6 +215,10 @@ details.setting-help {
|
||||||
#index-definition-table td input[type=number] {
|
#index-definition-table td input[type=number] {
|
||||||
width: 4em;
|
width: 4em;
|
||||||
}
|
}
|
||||||
|
p.post-byline {
|
||||||
|
color: #606060;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
@media only screen and (max-width: 816px) {
|
@media only screen and (max-width: 816px) {
|
||||||
main {
|
main {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
|
|
@ -15,6 +15,10 @@
|
||||||
{% if current_page == "contents" %}class="current-page"
|
{% if current_page == "contents" %}class="current-page"
|
||||||
{% else %}href="{{ url_for('lexicon.contents', lexicon_name=g.lexicon.name) }}"
|
{% else %}href="{{ url_for('lexicon.contents', lexicon_name=g.lexicon.name) }}"
|
||||||
{% endif %}>Contents</a>{% endblock %}
|
{% endif %}>Contents</a>{% endblock %}
|
||||||
|
{% block sb_posts %}<a
|
||||||
|
{% if current_page == "posts" %}class="current-page"
|
||||||
|
{% else %}href="{{ url_for('lexicon.posts.list', lexicon_name=g.lexicon.name) }}"
|
||||||
|
{% endif %}>Posts</a>{% endblock %}
|
||||||
{% block sb_rules %}<a
|
{% block sb_rules %}<a
|
||||||
{% if current_page == "rules" %}class="current-page"
|
{% if current_page == "rules" %}class="current-page"
|
||||||
{% else %}href="{{ url_for('lexicon.rules', lexicon_name=g.lexicon.name) }}"
|
{% else %}href="{{ url_for('lexicon.rules', lexicon_name=g.lexicon.name) }}"
|
||||||
|
@ -31,6 +35,7 @@
|
||||||
{% set template_sidebar_rows = [
|
{% set template_sidebar_rows = [
|
||||||
self.sb_characters(),
|
self.sb_characters(),
|
||||||
self.sb_contents(),
|
self.sb_contents(),
|
||||||
|
self.sb_posts(),
|
||||||
self.sb_rules(),
|
self.sb_rules(),
|
||||||
self.sb_settings(),
|
self.sb_settings(),
|
||||||
self.sb_stats()] %}
|
self.sb_stats()] %}
|
||||||
|
|
|
@ -8,6 +8,7 @@ from amanuensis.server.helpers import lexicon_param, player_required_if_not_publ
|
||||||
|
|
||||||
from .characters import bp as characters_bp
|
from .characters import bp as characters_bp
|
||||||
from .forms import LexiconJoinForm
|
from .forms import LexiconJoinForm
|
||||||
|
from .posts import bp as posts_bp
|
||||||
from .settings import bp as settings_bp
|
from .settings import bp as settings_bp
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,6 +16,7 @@ bp = Blueprint(
|
||||||
"lexicon", __name__, url_prefix="/lexicon/<lexicon_name>", template_folder="."
|
"lexicon", __name__, url_prefix="/lexicon/<lexicon_name>", template_folder="."
|
||||||
)
|
)
|
||||||
bp.register_blueprint(characters_bp)
|
bp.register_blueprint(characters_bp)
|
||||||
|
bp.register_blueprint(posts_bp)
|
||||||
bp.register_blueprint(settings_bp)
|
bp.register_blueprint(settings_bp)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
from flask import Blueprint, render_template, g
|
||||||
|
from flask.helpers import url_for
|
||||||
|
from flask_login import current_user
|
||||||
|
from werkzeug.utils import redirect
|
||||||
|
|
||||||
|
from amanuensis.backend import postq
|
||||||
|
from amanuensis.db import Post
|
||||||
|
from amanuensis.parser import RenderableVisitor, parse_raw_markdown
|
||||||
|
from amanuensis.parser.core import *
|
||||||
|
from amanuensis.server.helpers import lexicon_param, player_required, current_lexicon
|
||||||
|
|
||||||
|
from .forms import CreatePostForm
|
||||||
|
|
||||||
|
|
||||||
|
bp = Blueprint("posts", __name__, url_prefix="/posts", template_folder=".")
|
||||||
|
|
||||||
|
|
||||||
|
class PostFormatter(RenderableVisitor):
|
||||||
|
"""Parses stylistic markdown into HTML without links."""
|
||||||
|
|
||||||
|
def TextSpan(self, span: TextSpan):
|
||||||
|
return span.innertext
|
||||||
|
|
||||||
|
def LineBreak(self, span: LineBreak):
|
||||||
|
return '<br>'
|
||||||
|
|
||||||
|
def ParsedArticle(self, span: ParsedArticle):
|
||||||
|
return '\n'.join(span.recurse(self))
|
||||||
|
|
||||||
|
def BodyParagraph(self, span: BodyParagraph):
|
||||||
|
return f'<p>{"".join(span.recurse(self))}</p>'
|
||||||
|
|
||||||
|
def SignatureParagraph(self, span: SignatureParagraph):
|
||||||
|
return (
|
||||||
|
'<hr><span class="signature"><p>'
|
||||||
|
f'{"".join(span.recurse(self))}'
|
||||||
|
'</p></span>'
|
||||||
|
)
|
||||||
|
|
||||||
|
def BoldSpan(self, span: BoldSpan):
|
||||||
|
return f'<b>{"".join(span.recurse(self))}</b>'
|
||||||
|
|
||||||
|
def ItalicSpan(self, span: ItalicSpan):
|
||||||
|
return f'<i>{"".join(span.recurse(self))}</i>'
|
||||||
|
|
||||||
|
def CitationSpan(self, span: CitationSpan):
|
||||||
|
return "".join(span.recurse(self))
|
||||||
|
|
||||||
|
|
||||||
|
def render_post_body(post: Post) -> str:
|
||||||
|
"""Parse and render the body of a post into post-safe HTML."""
|
||||||
|
renderable: ParsedArticle = parse_raw_markdown(post.body)
|
||||||
|
rendered: str = renderable.render(PostFormatter())
|
||||||
|
return rendered
|
||||||
|
|
||||||
|
|
||||||
|
@bp.get("/")
|
||||||
|
@lexicon_param
|
||||||
|
@player_required
|
||||||
|
def list(lexicon_name):
|
||||||
|
form = CreatePostForm()
|
||||||
|
return render_template(
|
||||||
|
"posts.jinja",
|
||||||
|
lexicon_name=lexicon_name,
|
||||||
|
form=form,
|
||||||
|
render_post_body=render_post_body,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.post("/")
|
||||||
|
@lexicon_param
|
||||||
|
@player_required
|
||||||
|
def create(lexicon_name):
|
||||||
|
form = CreatePostForm()
|
||||||
|
if form.validate():
|
||||||
|
# Data is valid
|
||||||
|
postq.create(g.db, current_lexicon.id, current_user.id, form.body.data)
|
||||||
|
return redirect(url_for("lexicon.posts.list", lexicon_name=lexicon_name))
|
||||||
|
|
||||||
|
else:
|
||||||
|
# POST received invalid data
|
||||||
|
return render_template(
|
||||||
|
"posts.jinja",
|
||||||
|
lexicon_name=lexicon_name,
|
||||||
|
form=form,
|
||||||
|
render_post_body=render_post_body,
|
||||||
|
)
|
|
@ -0,0 +1,10 @@
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import SubmitField, TextAreaField
|
||||||
|
from wtforms.validators import DataRequired
|
||||||
|
|
||||||
|
|
||||||
|
class CreatePostForm(FlaskForm):
|
||||||
|
"""/lexicon/<name>/posts/"""
|
||||||
|
|
||||||
|
body = TextAreaField(validators=[DataRequired()])
|
||||||
|
submit = SubmitField("Post")
|
|
@ -0,0 +1,20 @@
|
||||||
|
{% extends "lexicon.jinja" %}
|
||||||
|
{% set current_page = "characters" %}
|
||||||
|
{% block title %}Character | {{ lexicon_title }}{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<section>
|
||||||
|
<form action="" method="post" novalidate>
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
<p>{{ form.body(class_='fullwidth') }}</p>
|
||||||
|
<p>{{ form.submit() }}</p>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{% for post in current_lexicon.posts %}
|
||||||
|
<section>
|
||||||
|
<p>{{ render_post_body(post) }}</p>
|
||||||
|
<p class="post-byline">Posted {% if post.user_id %}by {{ post.user.display_name }} {% endif %}at {{ post.created }}</p>
|
||||||
|
</section>
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
Loading…
Reference in New Issue