Compare commits
1 Commits
e2bdf2220a
...
2686a9cf22
Author | SHA1 | Date |
---|---|---|
Tim Van Baak | 2686a9cf22 |
|
@ -54,7 +54,9 @@ def try_from_public_id(db: DbContext, public_id: UUID) -> Optional[Article]:
|
|||
).scalar_one_or_none()
|
||||
|
||||
|
||||
def update_state(db: DbContext, article_id: int, title: str, body: str, state: ArticleState):
|
||||
def update_state(
|
||||
db: DbContext, article_id: int, title: str, body: str, state: ArticleState
|
||||
):
|
||||
"""Update an article."""
|
||||
db(
|
||||
update(Article)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import re
|
||||
from typing import Sequence
|
||||
|
||||
from amanuensis.db import *
|
||||
from amanuensis.parser import *
|
||||
|
||||
|
||||
|
@ -9,18 +10,14 @@ class ConstraintCheck(RenderableVisitor):
|
|||
def __init__(self) -> None:
|
||||
self.word_count: int = 0
|
||||
self.signatures: int = 0
|
||||
self.tmp: bool = False
|
||||
|
||||
def TextSpan(self, span):
|
||||
self.word_count += len(re.split(r'\s+', span.innertext.strip()))
|
||||
return self
|
||||
|
||||
def BoldSpan(self, span):
|
||||
self.tmp = True
|
||||
return self
|
||||
|
||||
def SignatureParagraph(self, span):
|
||||
self.signatures += 1
|
||||
span.recurse(self)
|
||||
return self
|
||||
|
||||
|
||||
|
@ -53,21 +50,102 @@ class ConstraintMessage:
|
|||
return {"severity": self.severity, "message": self.message}
|
||||
|
||||
|
||||
def constraint_check(parsed: Renderable) -> Sequence[ConstraintMessage]:
|
||||
def title_constraint_check(title: str) -> Sequence[ConstraintMessage]:
|
||||
"""Perform checks that apply to the article title."""
|
||||
messages = []
|
||||
|
||||
# I: Current index assignments
|
||||
# TODO
|
||||
|
||||
# E: No title
|
||||
if not title:
|
||||
messages.append(ConstraintMessage.error("Missing title"))
|
||||
|
||||
# I: This article is new
|
||||
# TODO
|
||||
# E: And new articles are forbidden
|
||||
# TODO
|
||||
|
||||
# I: This article is a phantom
|
||||
# TODO
|
||||
|
||||
# I: This article is an addendum
|
||||
# TODO
|
||||
# E: And the user is at the addendum limit
|
||||
# TODO
|
||||
|
||||
# E: This article has already been written and addendums are disabled
|
||||
# TODO
|
||||
|
||||
# I: This article's index
|
||||
# TODO
|
||||
|
||||
# E: The article does not fulfill the player's index assignment
|
||||
# TODO
|
||||
|
||||
# E: The index does not have room for a new article
|
||||
# TODO
|
||||
|
||||
# E: The player has cited this phantom article before
|
||||
# TODO
|
||||
|
||||
# W: Another player is writing an article with this title
|
||||
# TODO
|
||||
|
||||
# E: Another player has an approved article with this title
|
||||
# TODO
|
||||
|
||||
# W: The article's title matches a character's name
|
||||
# TODO
|
||||
|
||||
return messages
|
||||
|
||||
|
||||
def content_constraint_check(parsed: Renderable) -> Sequence[ConstraintMessage]:
|
||||
check_result: ConstraintCheck = parsed.render(ConstraintCheck())
|
||||
|
||||
messages = []
|
||||
|
||||
# I: Word count
|
||||
messages.append(ConstraintMessage.info(f"Word count: {check_result.word_count}"))
|
||||
|
||||
# W: Check signature count
|
||||
# E: Self-citation when forbidden
|
||||
# TODO
|
||||
|
||||
# W: A new citation matches a character's name
|
||||
# TODO
|
||||
|
||||
# W: The article cites itself
|
||||
# TODO
|
||||
|
||||
# E: A new citation would create more articles than can be written
|
||||
# TODO
|
||||
|
||||
# E: Extant citation count requirements
|
||||
# TODO
|
||||
|
||||
# E: Phantom citation count requirements
|
||||
# TODO
|
||||
|
||||
# E: New citation count requirements
|
||||
# TODO
|
||||
|
||||
# E: Character citation count requirements
|
||||
# TODO
|
||||
|
||||
# E: Total citation count requirements
|
||||
# TODO
|
||||
|
||||
# E: Exceeded hard word limit
|
||||
# TODO
|
||||
|
||||
# W: Exceeded soft word limit
|
||||
# TODO
|
||||
|
||||
# W: Missing or multiple signatures
|
||||
if check_result.signatures < 1:
|
||||
messages.append(ConstraintMessage.warning("Missing signature paragraph"))
|
||||
if check_result.signatures > 1:
|
||||
messages.append(ConstraintMessage.warning("More than one signature paragraph"))
|
||||
|
||||
# E: tmp to test errors
|
||||
if check_result.tmp:
|
||||
messages.append(ConstraintMessage.error("Temporary error"))
|
||||
|
||||
return messages
|
||||
|
|
|
@ -4,7 +4,7 @@ from flask import Blueprint, render_template, g, abort, request
|
|||
|
||||
from amanuensis.backend import *
|
||||
from amanuensis.db import *
|
||||
from amanuensis.lexicon.constraint import constraint_check
|
||||
from amanuensis.lexicon.constraint import title_constraint_check, content_constraint_check
|
||||
from amanuensis.parser import *
|
||||
from amanuensis.server.helpers import lexicon_param, player_required
|
||||
|
||||
|
@ -14,6 +14,7 @@ bp = Blueprint("editor", __name__, url_prefix="/editor", template_folder=".")
|
|||
|
||||
class PreviewHtmlRenderer(RenderableVisitor):
|
||||
"""Parses stylistic markdown and stores citations as footnotes."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.citations: list = []
|
||||
self.rendered: str = ""
|
||||
|
@ -23,7 +24,7 @@ class PreviewHtmlRenderer(RenderableVisitor):
|
|||
return span.innertext
|
||||
|
||||
def LineBreak(self, span):
|
||||
return '<br>'
|
||||
return "<br>"
|
||||
|
||||
# Translate the simple container spans to text
|
||||
def BoldSpan(self, span):
|
||||
|
@ -35,10 +36,7 @@ class PreviewHtmlRenderer(RenderableVisitor):
|
|||
# Record citations in the visitor, then translate the span to text as an
|
||||
# underline and footnote number
|
||||
def CitationSpan(self, span):
|
||||
self.citations.append({
|
||||
"title": span.cite_target,
|
||||
"type": "phantom"
|
||||
})
|
||||
self.citations.append({"title": span.cite_target, "type": "phantom"})
|
||||
return f'<u>{"".join(span.recurse(self))}</u>[{len(self.citations)}]'
|
||||
|
||||
# Translate the paragraph-level containers to their text contents
|
||||
|
@ -49,13 +47,13 @@ class PreviewHtmlRenderer(RenderableVisitor):
|
|||
return (
|
||||
'<hr><span class="signature"><p>'
|
||||
f'{"".join(span.recurse(self))}'
|
||||
'</p></span>'
|
||||
"</p></span>"
|
||||
)
|
||||
|
||||
# Return the visitor from the top-level article span after saving the full
|
||||
# text parsed from the child spans
|
||||
def ParsedArticle(self, span):
|
||||
self.contents = '\n'.join(span.recurse(self))
|
||||
self.contents = "\n".join(span.recurse(self))
|
||||
return self
|
||||
|
||||
|
||||
|
@ -94,17 +92,18 @@ def load(lexicon_name, article_id):
|
|||
preview_result: PreviewHtmlRenderer = parsed.render(PreviewHtmlRenderer())
|
||||
|
||||
# Check article content against constraints
|
||||
messages = constraint_check(parsed)
|
||||
messages = title_constraint_check(article.title)
|
||||
messages.extend(content_constraint_check(parsed))
|
||||
|
||||
# Return the article information to the editor
|
||||
msg_list = list([msg.json() for msg in messages])
|
||||
return {
|
||||
'title': article.title,
|
||||
'rendered': preview_result.contents,
|
||||
'state': article.state.value,
|
||||
'ersatz': article.ersatz,
|
||||
'citations': preview_result.citations,
|
||||
'messages': msg_list,
|
||||
"title": article.title,
|
||||
"rendered": preview_result.contents,
|
||||
"state": article.state.value,
|
||||
"ersatz": article.ersatz,
|
||||
"citations": preview_result.citations,
|
||||
"messages": msg_list,
|
||||
}
|
||||
|
||||
|
||||
|
@ -116,33 +115,40 @@ def update(lexicon_name, article_id):
|
|||
return abort(404)
|
||||
|
||||
# Extract the submitted content
|
||||
new_title = request.json['title']
|
||||
new_body = request.json['body']
|
||||
new_state = ArticleState(request.json['state'])
|
||||
new_title = request.json["title"]
|
||||
new_body = request.json["body"]
|
||||
new_state = ArticleState(request.json["state"])
|
||||
|
||||
# Generate the preview HTML from the submitted content
|
||||
parsed = parse_raw_markdown(new_body)
|
||||
preview_result: PreviewHtmlRenderer = parsed.render(PreviewHtmlRenderer())
|
||||
|
||||
# Check article content against constraints
|
||||
messages = constraint_check(parsed)
|
||||
messages = title_constraint_check(new_title)
|
||||
messages.extend(content_constraint_check(parsed))
|
||||
|
||||
# Block submission if the article is a draft with errors
|
||||
has_errors = any([msg for msg in messages if msg.is_error])
|
||||
if article.state == ArticleState.DRAFT and new_state != ArticleState.DRAFT and has_errors:
|
||||
if (
|
||||
article.state == ArticleState.DRAFT
|
||||
and new_state != ArticleState.DRAFT
|
||||
and has_errors
|
||||
):
|
||||
new_state = ArticleState.DRAFT
|
||||
|
||||
# Update the article with the submitted information
|
||||
artiq.update_state(g.db, article.id, title=new_title, body=new_body, state=new_state)
|
||||
artiq.update_state(
|
||||
g.db, article.id, title=new_title, body=new_body, state=new_state
|
||||
)
|
||||
updated_article = artiq.try_from_public_id(g.db, article_id)
|
||||
|
||||
# Return the article information to the editor
|
||||
msg_list = list([msg.json() for msg in messages])
|
||||
return {
|
||||
'title': updated_article.title,
|
||||
'rendered': preview_result.contents,
|
||||
'state': updated_article.state.value,
|
||||
'ersatz': updated_article.ersatz,
|
||||
'citations': preview_result.citations,
|
||||
'messages': msg_list,
|
||||
"title": updated_article.title,
|
||||
"rendered": preview_result.contents,
|
||||
"state": updated_article.state.value,
|
||||
"ersatz": updated_article.ersatz,
|
||||
"citations": preview_result.citations,
|
||||
"messages": msg_list,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue