From ffaf881707dfb1636d9286a670bd7431b44fff30 Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Mon, 20 Sep 2021 18:43:55 -0700 Subject: [PATCH 01/10] Replace divs with semantic elements --- amanuensis/resources/editor.css | 4 +-- amanuensis/resources/page.css | 36 +++++++++---------- amanuensis/server/page.jinja | 16 ++++----- amanuensis/server/page_2col.jinja | 4 +-- .../server/session/session.editor.jinja | 10 +++--- 5 files changed, 35 insertions(+), 35 deletions(-) diff --git a/amanuensis/resources/editor.css b/amanuensis/resources/editor.css index fb0e52b..2ccbbaa 100644 --- a/amanuensis/resources/editor.css +++ b/amanuensis/resources/editor.css @@ -17,7 +17,7 @@ div#editor-left { display: flex; align-items: stretch; } -div#editor-left div.contentblock { +div#editor-left section { display: flex; flex-direction: column; margin: 10px 5px 10px 10px; @@ -48,7 +48,7 @@ textarea#editor-content { div#editor-right { overflow-y: scroll; } -div#editor-right div.contentblock { +div#editor-right section { margin: 10px 5px 10px 10px; } span.message-warning { diff --git a/amanuensis/resources/page.css b/amanuensis/resources/page.css index bffd77b..8e68a98 100644 --- a/amanuensis/resources/page.css +++ b/amanuensis/resources/page.css @@ -8,14 +8,14 @@ body { line-height: 1.4; font-size: 16px; } -div#wrapper { +main { max-width: 800px; position: absolute; left: 0; right: 0; margin: 0 auto; } -div#header { +header { padding: 5px; margin: 5px; background-color: #ffffff; @@ -24,7 +24,7 @@ div#header { overflow: auto; position: relative; } -div#header p, div#header h2 { +header p, header h2 { margin: 5px; } div#login-status { @@ -33,7 +33,7 @@ div#login-status { top: 10px; right: 10px; } -div#sidebar { +nav { width: 200px; float:left; margin:5px; @@ -46,7 +46,7 @@ div#sidebar { img#logo { max-width: 200px; } -div#sidebar table { +nav table { width: 100%; border-collapse: collapse; } @@ -63,32 +63,32 @@ table a { border-radius: 5px; text-decoration: none; } -div#sidebar table a { +nav table a { justify-content: center; } table a:hover { background-color: var(--button-hover); } -div#sidebar table a.current-page { +nav table a.current-page { background-color: var(--button-current); } -div#sidebar table a.current-page:hover { +nav table a.current-page:hover { background-color: var(--button-current); } -div#sidebar table td { +nav table td { padding: 0px; margin: 3px 0; border-bottom: 8px solid transparent; } -div#content { +article { margin: 5px; } -div.content-2col { +article.content-2col { position: absolute; right: 0px; left: 226px; max-width: 564px; } -div.contentblock { +section { background-color: #ffffff; box-shadow: 2px 2px 10px #888888; margin-bottom: 5px; @@ -216,26 +216,26 @@ details.setting-help { width: 4em; } @media only screen and (max-width: 816px) { - div#wrapper { + main { padding: 5px; } - div#header { + header { max-width: 554px; margin: 0 auto; } - div#sidebar { + nav { max-width: 548px; width: inherit; float: inherit; margin: 10px auto; } - div#content{ + article{ margin: 10px auto; } - div.content-1col { + article.content-1col { max-width: 564px; } - div.content-2col { + article.content-2col { max-width: 564px; position: static; right: inherit; diff --git a/amanuensis/server/page.jinja b/amanuensis/server/page.jinja index 5b45466..df4832e 100644 --- a/amanuensis/server/page.jinja +++ b/amanuensis/server/page.jinja @@ -8,8 +8,8 @@ -
- + {% block sidebar %}{% endblock %} -
+
{% if not template_content_blocks %}{% set template_content_blocks = [] %}{% endif %} {% if not content_blocks %}{% set content_blocks = [] %}{% endif %} - {% for content_block in template_content_blocks + content_blocks %}
- {{ content_block|safe }}
+ {% for content_block in template_content_blocks + content_blocks -%} +
{{ content_block|safe }}
{% endfor %} -
- + + diff --git a/amanuensis/server/page_2col.jinja b/amanuensis/server/page_2col.jinja index dac4d32..6bce71e 100644 --- a/amanuensis/server/page_2col.jinja +++ b/amanuensis/server/page_2col.jinja @@ -1,12 +1,12 @@ {% extends "page.jinja" %} {% block sidebar %} - + {% endblock %} {% block content_class %}content-2col{% endblock %} \ No newline at end of file diff --git a/amanuensis/server/session/session.editor.jinja b/amanuensis/server/session/session.editor.jinja index 0e10d5a..031deb9 100644 --- a/amanuensis/server/session/session.editor.jinja +++ b/amanuensis/server/session/session.editor.jinja @@ -34,7 +34,7 @@
-
+
{# Thin header bar #}
{# Header always includes backlink to lexicon #} @@ -103,16 +103,16 @@ {# #}{{ article.contents }}{# #} {% endif %} -
+
-
+

This editor requires Javascript to function.

-
+

 

-
+

 

-- 2.44.1 From a6399e7e229e6d1c4823f5bb1670985b4b14975b Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Mon, 20 Sep 2021 20:11:40 -0700 Subject: [PATCH 02/10] Refactor template content blocks for greater flexibility There is not much value to be gotten out of creating Jinja blocks and appending them to a list when nothing particularly interesting is done with the list. With changes to move more towards semantic HTML, as well as more ease of access to data in the template engine in the new code, it is preferable to leave block division to the page template by making it a property of the
tag. This also allows creating blocks in Jinja iterators, which is not possible to do cleanly in the idiom being replaced here. --- amanuensis/server/auth/auth.login.jinja | 25 ++++++++++--------- amanuensis/server/home/home.admin.jinja | 3 ++- amanuensis/server/home/home.root.jinja | 17 ++++++------- .../lexicon/characters/characters.edit.jinja | 4 +-- .../lexicon/characters/characters.jinja | 3 ++- .../server/lexicon/lexicon.article.jinja | 11 +++----- .../server/lexicon/lexicon.contents.jinja | 5 ++-- amanuensis/server/lexicon/lexicon.join.jinja | 5 ++-- amanuensis/server/lexicon/lexicon.rules.jinja | 5 ++-- .../server/lexicon/lexicon.statistics.jinja | 5 ++-- .../server/lexicon/settings/settings.jinja | 4 +-- amanuensis/server/page.jinja | 6 +---- 12 files changed, 42 insertions(+), 51 deletions(-) diff --git a/amanuensis/server/auth/auth.login.jinja b/amanuensis/server/auth/auth.login.jinja index f6b6ae2..a06dce7 100644 --- a/amanuensis/server/auth/auth.login.jinja +++ b/amanuensis/server/auth/auth.login.jinja @@ -3,21 +3,22 @@ {% block header %}

Amanuensis - Login

{% endblock %} {% block login_status_attr %}style="display:none"{% endblock %} {% block main %} +
- {{ form.hidden_tag() }} -

{{ form.username.label }}
{{ form.username(size=32) }} - {% for error in form.username.errors %} -
{{ error }} - {% endfor %}

-

{{ form.password.label }}
{{ form.password(size=32) }} - {% for error in form.password.errors %} -
{{ error }} - {% endfor %}

-

{{ form.remember }} {{ form.remember.label }}

-

{{ form.submit() }}

+ {{ form.hidden_tag() }} +

{{ form.username.label }}
{{ form.username(size=32) }} + {% for error in form.username.errors %} +
{{ error }} + {% endfor %}

+

{{ form.password.label }}
{{ form.password(size=32) }} + {% for error in form.password.errors %} +
{{ error }} + {% endfor %}

+

{{ form.remember }} {{ form.remember.label }}

+

{{ form.submit() }}

{% for message in get_flashed_messages() %} {{ message }}
{% endfor %} +
{% endblock %} -{% set template_content_blocks = [self.main()] %} \ No newline at end of file diff --git a/amanuensis/server/home/home.admin.jinja b/amanuensis/server/home/home.admin.jinja index 20ff0f2..ad85b95 100644 --- a/amanuensis/server/home/home.admin.jinja +++ b/amanuensis/server/home/home.admin.jinja @@ -9,6 +9,7 @@ {% set template_sidebar_rows = [self.sb_home(), self.sb_create()] %} {% block main %} +

Users:

{% for user in userq.get_all(db) %} {{ macros.dashboard_user_item(user) }} @@ -17,5 +18,5 @@ {% for lexicon in lexiq.get_all(db) %} {{ macros.dashboard_lexicon_item(lexicon) }} {% endfor %} +
{% endblock %} -{% set template_content_blocks = [self.main()] %} \ No newline at end of file diff --git a/amanuensis/server/home/home.root.jinja b/amanuensis/server/home/home.root.jinja index 83d7f29..5aad068 100644 --- a/amanuensis/server/home/home.root.jinja +++ b/amanuensis/server/home/home.root.jinja @@ -4,6 +4,13 @@ {% block header %}

Amanuensis - Home

{% endblock %} {% block main %} +{% if current_user.is_site_admin %} +
+Admin dashboard +
+{% endif %} + +

Welcome to Amanuensis!

Amanuensis is a hub for playing Lexicon, the encyclopedia RPG. Log in to access your Lexicon games. If you do not have an account, contact the administrator.

@@ -37,13 +44,5 @@ {% else %}

No public games available.

{% endif %} - +
{% endblock %} -{% set template_content_blocks = [self.main()] %} - -{% if current_user.is_site_admin %} -{% block admin_dash %} -Admin dashboard -{% endblock %} -{% set template_content_blocks = [self.admin_dash()] + template_content_blocks %} -{% endif %} \ No newline at end of file diff --git a/amanuensis/server/lexicon/characters/characters.edit.jinja b/amanuensis/server/lexicon/characters/characters.edit.jinja index b9693d7..aaceb44 100644 --- a/amanuensis/server/lexicon/characters/characters.edit.jinja +++ b/amanuensis/server/lexicon/characters/characters.edit.jinja @@ -2,6 +2,7 @@ {% block title %}Edit {{ character.name }} | {{ lexicon_title }}{% endblock %} {% block main %} +
{{ form.hidden_tag() }}

@@ -19,6 +20,5 @@ {% for message in get_flashed_messages() %} {{ message }}
{% endfor %} - +

{% endblock %} -{% set template_content_blocks = [self.main()] %} \ No newline at end of file diff --git a/amanuensis/server/lexicon/characters/characters.jinja b/amanuensis/server/lexicon/characters/characters.jinja index 278324e..bb50ad5 100644 --- a/amanuensis/server/lexicon/characters/characters.jinja +++ b/amanuensis/server/lexicon/characters/characters.jinja @@ -3,6 +3,7 @@ {% block title %}Character | {{ lexicon_title }}{% endblock %} {% block main %} +

Characters

{% set players = memq.get_players_in_lexicon(db, g.lexicon.id)|list %} {% set characters = charq.get_in_lexicon(db, g.lexicon.id)|list %} @@ -30,5 +31,5 @@ {% endfor %} +
{% endblock %} -{% set template_content_blocks = [self.main()] %} \ No newline at end of file diff --git a/amanuensis/server/lexicon/lexicon.article.jinja b/amanuensis/server/lexicon/lexicon.article.jinja index 9fc42d7..5e38840 100644 --- a/amanuensis/server/lexicon/lexicon.article.jinja +++ b/amanuensis/server/lexicon/lexicon.article.jinja @@ -2,17 +2,15 @@ {% block title %}{{ article.title }} | {{ lexicon_title }}{% endblock %} {% block main %} - +
{% for message in get_flashed_messages() %} {{ message }}
{% endfor %}

{{ article.title }}

{{ article.html }} - -{% endblock %} - -{% block citations %} +
+

{% for citation in article.cites %} {{ citation }}{% if not loop.last %} / {% endif %} @@ -23,6 +21,5 @@ {{ citation }}{% if not loop.last %} / {% endif %} {% endfor %}

+
{% endblock %} - -{% set template_content_blocks = [self.main(), self.citations()] %} \ No newline at end of file diff --git a/amanuensis/server/lexicon/lexicon.contents.jinja b/amanuensis/server/lexicon/lexicon.contents.jinja index a61e87c..1415449 100644 --- a/amanuensis/server/lexicon/lexicon.contents.jinja +++ b/amanuensis/server/lexicon/lexicon.contents.jinja @@ -3,7 +3,7 @@ {% block title %}Index | {{ lexicon_title }}{% endblock %} {% block main %} - +
{% for message in get_flashed_messages() %} {{ message }}
{% endfor %} @@ -20,6 +20,5 @@ {% endif %} {% endfor %} - +
{% endblock %} -{% set template_content_blocks = [self.main()] %} \ No newline at end of file diff --git a/amanuensis/server/lexicon/lexicon.join.jinja b/amanuensis/server/lexicon/lexicon.join.jinja index cdd9b24..cf009fd 100644 --- a/amanuensis/server/lexicon/lexicon.join.jinja +++ b/amanuensis/server/lexicon/lexicon.join.jinja @@ -2,7 +2,7 @@ {% block title %}Join | {{ g.lexicon.full_title }}{% endblock %} {% block main %} - +
{{ form.hidden_tag() }} {% if g.lexicon.join_password %} @@ -16,6 +16,5 @@ {% for message in get_flashed_messages() %} {{ message }}
{% endfor %} - +
{% endblock %} -{% set template_content_blocks = [self.main()] %} \ No newline at end of file diff --git a/amanuensis/server/lexicon/lexicon.rules.jinja b/amanuensis/server/lexicon/lexicon.rules.jinja index 20fc070..932ad98 100644 --- a/amanuensis/server/lexicon/lexicon.rules.jinja +++ b/amanuensis/server/lexicon/lexicon.rules.jinja @@ -3,8 +3,7 @@ {% block title %}Rules | {{ lexicon_title }}{% endblock %} {% block main %} - +
Placeholder text - +
{% endblock %} -{% set template_content_blocks = [self.main()] %} \ No newline at end of file diff --git a/amanuensis/server/lexicon/lexicon.statistics.jinja b/amanuensis/server/lexicon/lexicon.statistics.jinja index e816653..3d3f6f5 100644 --- a/amanuensis/server/lexicon/lexicon.statistics.jinja +++ b/amanuensis/server/lexicon/lexicon.statistics.jinja @@ -3,8 +3,7 @@ {% block title %}Session | {{ lexicon_title }}{% endblock %} {% block main %} - +
Placeholder text - +
{% endblock %} -{% set template_content_blocks = [self.main()] %} \ No newline at end of file diff --git a/amanuensis/server/lexicon/settings/settings.jinja b/amanuensis/server/lexicon/settings/settings.jinja index d143b98..bd1a52a 100644 --- a/amanuensis/server/lexicon/settings/settings.jinja +++ b/amanuensis/server/lexicon/settings/settings.jinja @@ -19,6 +19,7 @@ {% endmacro %} {% block main %} +
{% if current_membership.is_editor %}
  • {{ settings_page_link("player", "Player Settings") }}
  • @@ -172,6 +173,5 @@ {% if page_name == "article" %}

    Article Requirements

    {% endif %} +
{% endblock %} - -{% set template_content_blocks = [self.main()] %} diff --git a/amanuensis/server/page.jinja b/amanuensis/server/page.jinja index df4832e..02f7740 100644 --- a/amanuensis/server/page.jinja +++ b/amanuensis/server/page.jinja @@ -27,11 +27,7 @@ {% block sidebar %}{% endblock %}
- {% if not template_content_blocks %}{% set template_content_blocks = [] %}{% endif %} - {% if not content_blocks %}{% set content_blocks = [] %}{% endif %} - {% for content_block in template_content_blocks + content_blocks -%} -
{{ content_block|safe }}
- {% endfor %} + {% block main %}{% endblock %} -- 2.44.1 From 1ca46d47f588bc649bcf56257b44ff4a8fcbab6a Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Mon, 20 Sep 2021 21:25:10 -0700 Subject: [PATCH 03/10] Add post create cli --- amanuensis/backend/post.py | 4 ++-- amanuensis/cli/__init__.py | 2 ++ amanuensis/cli/post.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 amanuensis/cli/post.py diff --git a/amanuensis/backend/post.py b/amanuensis/backend/post.py index 8a4d59d..8a170fb 100644 --- a/amanuensis/backend/post.py +++ b/amanuensis/backend/post.py @@ -2,7 +2,7 @@ Post query interface """ -import re +from typing import Optional from sqlalchemy import select @@ -14,7 +14,7 @@ from amanuensis.errors import ArgumentError, BackendArgumentTypeError def create( db: DbContext, lexicon_id: int, - user_id: int, + user_id: Optional[int], body: str, ) -> Post: """ diff --git a/amanuensis/cli/__init__.py b/amanuensis/cli/__init__.py index e457db4..56af06e 100644 --- a/amanuensis/cli/__init__.py +++ b/amanuensis/cli/__init__.py @@ -8,6 +8,7 @@ import amanuensis.cli.admin import amanuensis.cli.character import amanuensis.cli.index import amanuensis.cli.lexicon +import amanuensis.cli.post import amanuensis.cli.user from amanuensis.db import DbContext @@ -113,6 +114,7 @@ def main(): add_subcommand(subparsers, amanuensis.cli.character) add_subcommand(subparsers, amanuensis.cli.index) add_subcommand(subparsers, amanuensis.cli.lexicon) + add_subcommand(subparsers, amanuensis.cli.post) add_subcommand(subparsers, amanuensis.cli.user) # Parse args and perform top-level arg processing diff --git a/amanuensis/cli/post.py b/amanuensis/cli/post.py new file mode 100644 index 0000000..1dfd7f7 --- /dev/null +++ b/amanuensis/cli/post.py @@ -0,0 +1,31 @@ +import logging + +from amanuensis.backend import * +from amanuensis.db import * + +from .helpers import add_argument + + +COMMAND_NAME = "post" +COMMAND_HELP = "Interact with posts." + +LOG = logging.getLogger(__name__) + + +@add_argument("--lexicon", required=True, help="The lexicon's name") +@add_argument("--by", help="The character's public id") +@add_argument("--text", help="The text of the post") +def command_create(args) -> int: + """ + Create a post in a lexicon. + """ + db: DbContext = args.get_db() + lexicon = lexiq.try_from_name(db, args.lexicon) + if not lexicon: + raise ValueError("Lexicon does not exist") + user = userq.try_from_username(db, args.by) + user_id = user.id if user else None + post: Post = postq.create(db, lexicon.id, user_id, args.text) + preview = post.body[:20] + "..." if len(post.body) > 20 else post.body + LOG.info(f"Posted '{preview}' in {lexicon.full_title}") + return 0 -- 2.44.1 From b749357657548b62f35eeab31993c0d3ff2d4073 Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Mon, 20 Sep 2021 21:35:36 -0700 Subject: [PATCH 04/10] Add post feed page --- amanuensis/db/models.py | 2 +- amanuensis/resources/page.css | 4 + amanuensis/server/lexicon.jinja | 5 ++ amanuensis/server/lexicon/__init__.py | 2 + amanuensis/server/lexicon/posts/__init__.py | 87 +++++++++++++++++++++ amanuensis/server/lexicon/posts/forms.py | 10 +++ amanuensis/server/lexicon/posts/posts.jinja | 20 +++++ 7 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 amanuensis/server/lexicon/posts/__init__.py create mode 100644 amanuensis/server/lexicon/posts/forms.py create mode 100644 amanuensis/server/lexicon/posts/posts.jinja diff --git a/amanuensis/db/models.py b/amanuensis/db/models.py index e951309..cc5d2c1 100644 --- a/amanuensis/db/models.py +++ b/amanuensis/db/models.py @@ -251,7 +251,7 @@ class Lexicon(ModelBase): indices = relationship("ArticleIndex", back_populates="lexicon") index_rules = relationship("ArticleIndexRule", 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 # diff --git a/amanuensis/resources/page.css b/amanuensis/resources/page.css index 8e68a98..8fb534b 100644 --- a/amanuensis/resources/page.css +++ b/amanuensis/resources/page.css @@ -215,6 +215,10 @@ details.setting-help { #index-definition-table td input[type=number] { width: 4em; } +p.post-byline { + color: #606060; + text-align: right; +} @media only screen and (max-width: 816px) { main { padding: 5px; diff --git a/amanuensis/server/lexicon.jinja b/amanuensis/server/lexicon.jinja index 46f18c4..7593601 100644 --- a/amanuensis/server/lexicon.jinja +++ b/amanuensis/server/lexicon.jinja @@ -15,6 +15,10 @@ {% if current_page == "contents" %}class="current-page" {% else %}href="{{ url_for('lexicon.contents', lexicon_name=g.lexicon.name) }}" {% endif %}>Contents{% endblock %} +{% block sb_posts %}Posts{% endblock %} {% block sb_rules %}' + + def ParsedArticle(self, span: ParsedArticle): + return '\n'.join(span.recurse(self)) + + def BodyParagraph(self, span: BodyParagraph): + return f'

{"".join(span.recurse(self))}

' + + def SignatureParagraph(self, span: SignatureParagraph): + return ( + '

' + f'{"".join(span.recurse(self))}' + '

' + ) + + def BoldSpan(self, span: BoldSpan): + return f'{"".join(span.recurse(self))}' + + def ItalicSpan(self, span: ItalicSpan): + return f'{"".join(span.recurse(self))}' + + 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, + ) diff --git a/amanuensis/server/lexicon/posts/forms.py b/amanuensis/server/lexicon/posts/forms.py new file mode 100644 index 0000000..76ebd83 --- /dev/null +++ b/amanuensis/server/lexicon/posts/forms.py @@ -0,0 +1,10 @@ +from flask_wtf import FlaskForm +from wtforms import SubmitField, TextAreaField +from wtforms.validators import DataRequired + + +class CreatePostForm(FlaskForm): + """/lexicon//posts/""" + + body = TextAreaField(validators=[DataRequired()]) + submit = SubmitField("Post") diff --git a/amanuensis/server/lexicon/posts/posts.jinja b/amanuensis/server/lexicon/posts/posts.jinja new file mode 100644 index 0000000..f68b132 --- /dev/null +++ b/amanuensis/server/lexicon/posts/posts.jinja @@ -0,0 +1,20 @@ +{% extends "lexicon.jinja" %} +{% set current_page = "characters" %} +{% block title %}Character | {{ lexicon_title }}{% endblock %} + +{% block main %} +
+ + {{ form.hidden_tag() }} +

{{ form.body(class_='fullwidth') }}

+

{{ form.submit() }}

+ +
+ +{% for post in current_lexicon.posts %} +
+

{{ render_post_body(post) }}

+ +
+{% endfor %} +{% endblock %} -- 2.44.1 From 3cc487f94bff0eee45ee13f0528131af8052eb05 Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Fri, 24 Sep 2021 18:04:27 -0700 Subject: [PATCH 05/10] Distinguish new posts from already-seen posts --- amanuensis/backend/post.py | 46 +++++++++++++++++++-- amanuensis/db/models.py | 6 ++- amanuensis/resources/page.css | 4 ++ amanuensis/server/lexicon/posts/__init__.py | 16 +++++-- amanuensis/server/lexicon/posts/posts.jinja | 17 +++++--- 5 files changed, 75 insertions(+), 14 deletions(-) diff --git a/amanuensis/backend/post.py b/amanuensis/backend/post.py index 8a170fb..38dbea4 100644 --- a/amanuensis/backend/post.py +++ b/amanuensis/backend/post.py @@ -2,12 +2,13 @@ Post query interface """ -from typing import Optional +from typing import Optional, Sequence, Tuple -from sqlalchemy import select +from sqlalchemy import select, update, func +from sqlalchemy.sql.sqltypes import DateTime from amanuensis.db import DbContext, Post -from amanuensis.db.models import Lexicon +from amanuensis.db.models import Lexicon, Membership from amanuensis.errors import ArgumentError, BackendArgumentTypeError @@ -47,3 +48,42 @@ def create( db.session.add(new_post) db.session.commit() return new_post + + +def get_posts_for_membership( + db: DbContext, membership_id: int +) -> Tuple[Sequence[Post], Sequence[Post]]: + """ + Returns posts for the membership's lexicon, split into posts that + are new since the last view and posts that were previously seen. + """ + # Save the current timestamp, so we don't miss posts created between now + # and when we finish looking stuff up + now: DateTime = db(select(func.now())).scalar_one() + + # Save the previous last-seen timestamp for splitting new from old posts, + # then update the membership with the current time + last_seen: DateTime = db( + select(Membership.last_post_seen).where(Membership.id == membership_id) + ).scalar_one() + db( + update(Membership) + .where(Membership.id == membership_id) + .values(last_post_seen=now) + ) + db.session.commit() + + # Fetch posts in two groups, new ones after the last-seen time and old ones + # If last-seen is null, then just return everything as new + new_posts = db( + select(Post) + .where(last_seen is None or Post.created > last_seen) + .order_by(Post.created.desc()) + ).scalars() + old_posts = db( + select(Post) + .where(last_seen is not None and Post.created <= last_seen) + .order_by(Post.created.desc()) + ).scalars() + + return new_posts, old_posts diff --git a/amanuensis/db/models.py b/amanuensis/db/models.py index cc5d2c1..4633e83 100644 --- a/amanuensis/db/models.py +++ b/amanuensis/db/models.py @@ -251,7 +251,9 @@ class Lexicon(ModelBase): indices = relationship("ArticleIndex", back_populates="lexicon") index_rules = relationship("ArticleIndexRule", back_populates="lexicon") content_rules = relationship("ArticleContentRule", back_populates="lexicon") - posts = relationship("Post", back_populates="lexicon", order_by="Post.created.desc()") + posts = relationship( + "Post", back_populates="lexicon", order_by="Post.created.desc()" + ) ####################### # Derived information # @@ -654,7 +656,7 @@ class Post(ModelBase): ################ # The timestamp the post was created - created = Column(DateTime, nullable=False, server_default=func.now()) + created = Column(DateTime, nullable=False, server_default=func.utcnow()) # The body of the post body = Column(Text, nullable=False) diff --git a/amanuensis/resources/page.css b/amanuensis/resources/page.css index 8fb534b..0a9149d 100644 --- a/amanuensis/resources/page.css +++ b/amanuensis/resources/page.css @@ -215,6 +215,10 @@ details.setting-help { #index-definition-table td input[type=number] { width: 4em; } +section.new-post { + padding: 9px; + border: 1px dashed red; +} p.post-byline { color: #606060; text-align: right; diff --git a/amanuensis/server/lexicon/posts/__init__.py b/amanuensis/server/lexicon/posts/__init__.py index 9dee642..3d0a7ec 100644 --- a/amanuensis/server/lexicon/posts/__init__.py +++ b/amanuensis/server/lexicon/posts/__init__.py @@ -7,7 +7,12 @@ 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 amanuensis.server.helpers import ( + lexicon_param, + player_required, + current_lexicon, + current_membership, +) from .forms import CreatePostForm @@ -22,10 +27,10 @@ class PostFormatter(RenderableVisitor): return span.innertext def LineBreak(self, span: LineBreak): - return '
' + return "
" def ParsedArticle(self, span: ParsedArticle): - return '\n'.join(span.recurse(self)) + return "\n".join(span.recurse(self)) def BodyParagraph(self, span: BodyParagraph): return f'

{"".join(span.recurse(self))}

' @@ -34,7 +39,7 @@ class PostFormatter(RenderableVisitor): return ( '

' f'{"".join(span.recurse(self))}' - '

' + "

" ) def BoldSpan(self, span: BoldSpan): @@ -59,11 +64,14 @@ def render_post_body(post: Post) -> str: @player_required def list(lexicon_name): form = CreatePostForm() + new_posts, old_posts = postq.get_posts_for_membership(g.db, current_membership.id) return render_template( "posts.jinja", lexicon_name=lexicon_name, form=form, render_post_body=render_post_body, + new_posts=new_posts, + old_posts=old_posts, ) diff --git a/amanuensis/server/lexicon/posts/posts.jinja b/amanuensis/server/lexicon/posts/posts.jinja index f68b132..6fca8c6 100644 --- a/amanuensis/server/lexicon/posts/posts.jinja +++ b/amanuensis/server/lexicon/posts/posts.jinja @@ -2,6 +2,13 @@ {% set current_page = "characters" %} {% block title %}Character | {{ lexicon_title }}{% endblock %} +{% macro make_post(post, is_new) %} + +

{{ render_post_body(post) }}

+ + +{% endmacro %} + {% block main %}
@@ -11,10 +18,10 @@
-{% for post in current_lexicon.posts %} -
-

{{ render_post_body(post) }}

- -
+{% for post in new_posts %} +{{ make_post(post, True) }} +{% endfor %} +{% for post in old_posts %} +{{ make_post(post, False) }} {% endfor %} {% endblock %} -- 2.44.1 From 19e7af7e583158bf82d9da406d61401ae498e49a Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Fri, 1 Oct 2021 16:40:27 -0700 Subject: [PATCH 06/10] Show unread count in post sidebar link --- amanuensis/backend/post.py | 13 +++++++++++-- amanuensis/db/models.py | 2 +- amanuensis/server/__init__.py | 1 + amanuensis/server/lexicon.jinja | 2 +- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/amanuensis/backend/post.py b/amanuensis/backend/post.py index 38dbea4..a7c38e8 100644 --- a/amanuensis/backend/post.py +++ b/amanuensis/backend/post.py @@ -4,8 +4,7 @@ Post query interface from typing import Optional, Sequence, Tuple -from sqlalchemy import select, update, func -from sqlalchemy.sql.sqltypes import DateTime +from sqlalchemy import select, update, func, or_, DateTime from amanuensis.db import DbContext, Post from amanuensis.db.models import Lexicon, Membership @@ -87,3 +86,13 @@ def get_posts_for_membership( ).scalars() return new_posts, old_posts + + +def get_unread_count(db: DbContext, membership_id: int) -> int: + """Get the number of posts that the member has not seen""" + return db( + select(func.count(Post.id)) + .join(Membership, Membership.lexicon_id == Post.lexicon_id) + .where(or_(Membership.last_post_seen.is_(None), Post.created > Membership.last_post_seen)) + .where(Membership.id == membership_id) + ).scalar() diff --git a/amanuensis/db/models.py b/amanuensis/db/models.py index 4633e83..0f7aead 100644 --- a/amanuensis/db/models.py +++ b/amanuensis/db/models.py @@ -656,7 +656,7 @@ class Post(ModelBase): ################ # The timestamp the post was created - created = Column(DateTime, nullable=False, server_default=func.utcnow()) + created = Column(DateTime, nullable=False, server_default=func.now()) # The body of the post body = Column(Text, nullable=False) diff --git a/amanuensis/server/__init__.py b/amanuensis/server/__init__.py index c8a9827..4f70c90 100644 --- a/amanuensis/server/__init__.py +++ b/amanuensis/server/__init__.py @@ -76,6 +76,7 @@ def get_app( "memq": memq, "charq": charq, "indq": indq, + "postq": postq, "current_lexicon": current_lexicon, "current_membership": current_membership } diff --git a/amanuensis/server/lexicon.jinja b/amanuensis/server/lexicon.jinja index 7593601..1c332bc 100644 --- a/amanuensis/server/lexicon.jinja +++ b/amanuensis/server/lexicon.jinja @@ -18,7 +18,7 @@ {% block sb_posts %}
Posts{% endblock %} + {% endif %}>Posts{% set unread_count = postq.get_unread_count(g.db, current_membership.id) if current_membership else None %}{% if unread_count %} ({{ unread_count }}){% endif %}{% endblock %} {% block sb_rules %} Date: Fri, 1 Oct 2021 16:45:02 -0700 Subject: [PATCH 07/10] Fix errors caused by anonymous user model --- amanuensis/server/helpers.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/amanuensis/server/helpers.py b/amanuensis/server/helpers.py index b622a00..c665f71 100644 --- a/amanuensis/server/helpers.py +++ b/amanuensis/server/helpers.py @@ -99,7 +99,7 @@ def admin_required(route): @wraps(route) def admin_route(*args, **kwargs): user: User = current_user - if not user.is_site_admin: + if not user.is_authenticated or not user.is_site_admin: flash("You must be an admin to view this page") return redirect(url_for('home.home')) return route(*args, **kwargs) @@ -114,7 +114,13 @@ def player_required(route): def player_route(*args, **kwargs): db: DbContext = g.db user: User = current_user - lexicon: Lexicon = g.lexicon + lexicon: Lexicon = current_lexicon + if not user.is_authenticated: + flash("You must be a player to view this page") + if lexicon.public: + return redirect(url_for('lexicon.contents', lexicon_name=lexicon.name)) + else: + return redirect(url_for('home.home')) mem: Optional[Membership] = memq.try_from_ids(db, user.id, lexicon.id) if not mem: flash("You must be a player to view this page") @@ -134,8 +140,8 @@ def player_required_if_not_public(route): def player_route(*args, **kwargs): db: DbContext = g.db user: User = current_user - lexicon: Lexicon = g.lexicon - if not lexicon.public: + lexicon: Lexicon = current_lexicon + if not user.is_authenticated and not lexicon.public: mem: Optional[Membership] = memq.try_from_ids(db, user.id, lexicon.id) if not mem: flash("You must be a player to view this page") @@ -152,7 +158,13 @@ def editor_required(route): def editor_route(*args, **kwargs): db: DbContext = g.db user: User = current_user - lexicon: Lexicon = g.lexicon + lexicon: Lexicon = current_lexicon + if not user.is_authenticated: + flash("You must be a player to view this page") + if lexicon.public: + return redirect(url_for('lexicon.contents', lexicon_name=lexicon.name)) + else: + return redirect(url_for('home.home')) mem: Optional[Membership] = memq.try_from_ids(db, user.id, lexicon.id) if not mem or not mem.is_editor: flash("You must be the editor to view this page") -- 2.44.1 From f88ee3a52658ca0b1c5a35f88f9ba5fbde26785c Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Fri, 1 Oct 2021 16:52:59 -0700 Subject: [PATCH 08/10] Fix some pages not being selected properly in the sidebar --- amanuensis/server/lexicon/posts/posts.jinja | 2 +- amanuensis/server/lexicon/settings/settings.jinja | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/amanuensis/server/lexicon/posts/posts.jinja b/amanuensis/server/lexicon/posts/posts.jinja index 6fca8c6..9683515 100644 --- a/amanuensis/server/lexicon/posts/posts.jinja +++ b/amanuensis/server/lexicon/posts/posts.jinja @@ -1,5 +1,5 @@ {% extends "lexicon.jinja" %} -{% set current_page = "characters" %} +{% set current_page = "posts" %} {% block title %}Character | {{ lexicon_title }}{% endblock %} {% macro make_post(post, is_new) %} diff --git a/amanuensis/server/lexicon/settings/settings.jinja b/amanuensis/server/lexicon/settings/settings.jinja index bd1a52a..ff7b151 100644 --- a/amanuensis/server/lexicon/settings/settings.jinja +++ b/amanuensis/server/lexicon/settings/settings.jinja @@ -1,4 +1,5 @@ {% extends "lexicon.jinja" %} +{% set current_page = "settings" %} {% block title %}Edit | {{ lexicon_title }}{% endblock %} {% macro settings_page_link(page, text) -%} -- 2.44.1 From d1728482315ef4eb7ac60067c965cf8b1ea7f188 Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Fri, 1 Oct 2021 16:53:18 -0700 Subject: [PATCH 09/10] Respect lexicon post enable setting --- amanuensis/server/lexicon/posts/posts.jinja | 3 ++- amanuensis/server/lexicon/settings/__init__.py | 2 ++ amanuensis/server/lexicon/settings/forms.py | 1 + amanuensis/server/lexicon/settings/settings.jinja | 1 + 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/amanuensis/server/lexicon/posts/posts.jinja b/amanuensis/server/lexicon/posts/posts.jinja index 9683515..efc3059 100644 --- a/amanuensis/server/lexicon/posts/posts.jinja +++ b/amanuensis/server/lexicon/posts/posts.jinja @@ -10,6 +10,7 @@ {% endmacro %} {% block main %} +{% if current_lexicon.allow_post %}
{{ form.hidden_tag() }} @@ -17,7 +18,7 @@

{{ form.submit() }}

- +{% endif %} {% for post in new_posts %} {{ make_post(post, True) }} {% endfor %} diff --git a/amanuensis/server/lexicon/settings/__init__.py b/amanuensis/server/lexicon/settings/__init__.py index 12295be..975178f 100644 --- a/amanuensis/server/lexicon/settings/__init__.py +++ b/amanuensis/server/lexicon/settings/__init__.py @@ -89,6 +89,7 @@ def setup(lexicon_name): form.turn_count.data = lexicon.turn_count form.player_limit.data = lexicon.player_limit form.character_limit.data = lexicon.character_limit + form.allow_post.data = lexicon.allow_post return render_template( "settings.jinja", lexicon_name=lexicon_name, @@ -109,6 +110,7 @@ def setup(lexicon_name): lexicon.turn_count = form.turn_count.data lexicon.player_limit = form.player_limit.data lexicon.character_limit = form.character_limit.data + lexicon.allow_post = form.allow_post.data g.db.session.commit() # TODO refactor into backend flash("Settings saved") return redirect( diff --git a/amanuensis/server/lexicon/settings/forms.py b/amanuensis/server/lexicon/settings/forms.py index f7dfab1..e830527 100644 --- a/amanuensis/server/lexicon/settings/forms.py +++ b/amanuensis/server/lexicon/settings/forms.py @@ -49,6 +49,7 @@ class SetupSettingsForm(FlaskForm): widget=NumberInput(), validators=[Optional()], ) + allow_post = BooleanField("Allow players to make posts") submit = SubmitField("Submit") diff --git a/amanuensis/server/lexicon/settings/settings.jinja b/amanuensis/server/lexicon/settings/settings.jinja index ff7b151..cfc71d2 100644 --- a/amanuensis/server/lexicon/settings/settings.jinja +++ b/amanuensis/server/lexicon/settings/settings.jinja @@ -80,6 +80,7 @@

{{ number_setting(form.character_limit) }}

+

{{ flag_setting(form.allow_post) }}

{{ form.submit() }}

{% for message in get_flashed_messages() %} -- 2.44.1 From 875482355693c0787716c9b4930942e3e2e712f4 Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Fri, 1 Oct 2021 17:19:05 -0700 Subject: [PATCH 10/10] Linter pass --- amanuensis/backend/post.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/amanuensis/backend/post.py b/amanuensis/backend/post.py index a7c38e8..6c0913b 100644 --- a/amanuensis/backend/post.py +++ b/amanuensis/backend/post.py @@ -93,6 +93,11 @@ def get_unread_count(db: DbContext, membership_id: int) -> int: return db( select(func.count(Post.id)) .join(Membership, Membership.lexicon_id == Post.lexicon_id) - .where(or_(Membership.last_post_seen.is_(None), Post.created > Membership.last_post_seen)) + .where( + or_( + Membership.last_post_seen.is_(None), + Post.created > Membership.last_post_seen, + ) + ) .where(Membership.id == membership_id) ).scalar() -- 2.44.1