Refactor lexicon submodule into two
This commit is contained in:
parent
4e0bc56dcd
commit
4fc5f80538
|
@ -216,10 +216,10 @@ class LexiconCreateForm(FlaskForm):
|
||||||
# return True
|
# return True
|
||||||
|
|
||||||
|
|
||||||
# class LexiconJoinForm(FlaskForm):
|
class LexiconJoinForm(FlaskForm):
|
||||||
# """/lexicon/<name>/join/"""
|
"""/lexicon/<name>/join/"""
|
||||||
# password = StringField('Password')
|
password = StringField('Password')
|
||||||
# submit = SubmitField("Submit")
|
submit = SubmitField("Submit")
|
||||||
|
|
||||||
|
|
||||||
# class LexiconCharacterForm(FlaskForm):
|
# class LexiconCharacterForm(FlaskForm):
|
||||||
|
|
|
@ -36,20 +36,18 @@ def register_custom_filters(app):
|
||||||
title=filesafe_title(title))
|
title=filesafe_title(title))
|
||||||
|
|
||||||
|
|
||||||
# def lexicon_param(route):
|
def lexicon_param(route):
|
||||||
# """Wrapper for loading a route's lexicon"""
|
"""Wrapper for loading a route's lexicon"""
|
||||||
# @wraps(route)
|
@wraps(route)
|
||||||
# def with_lexicon(**kwargs):
|
def with_lexicon(**kwargs):
|
||||||
# name = kwargs.get('name')
|
name = kwargs.get('name')
|
||||||
# g.lexicon = LexiconModel.by(name=name)
|
model_factory: ModelFactory = current_app.config['model_factory']
|
||||||
# if g.lexicon is None:
|
g.lexicon = model_factory.lexicon(name)
|
||||||
# flash("Couldn't find a lexicon with the name '{}'".format(name))
|
if g.lexicon is None:
|
||||||
# return redirect(url_for("home.home"))
|
flash(f'Couldn\'t find a lexicon with the name "{name}"')
|
||||||
# # TODO transition to new model
|
return redirect(url_for("home.home"))
|
||||||
# model_factory: ModelFactory = current_app.config['model_factory']
|
return route(**kwargs)
|
||||||
# g.lexicon_ = model_factory.lexicon(name)
|
return with_lexicon
|
||||||
# return route(**kwargs)
|
|
||||||
# return with_lexicon
|
|
||||||
|
|
||||||
|
|
||||||
def admin_required(route):
|
def admin_required(route):
|
||||||
|
|
|
@ -55,6 +55,6 @@ def admin_create():
|
||||||
lexicon = create_lexicon(lexicon_name, editor)
|
lexicon = create_lexicon(lexicon_name, editor)
|
||||||
with lexicon.ctx.edit_config() as cfg:
|
with lexicon.ctx.edit_config() as cfg:
|
||||||
cfg.prompt = prompt
|
cfg.prompt = prompt
|
||||||
return redirect(url_for('lexicon.session', name=lexicon_name))
|
return redirect(url_for('session.session', name=lexicon_name))
|
||||||
|
|
||||||
return render_template('home.create.jinja', form=form)
|
return render_template('home.create.jinja', form=form)
|
||||||
|
|
|
@ -1,335 +0,0 @@
|
||||||
import json
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
from flask import (
|
|
||||||
Blueprint, render_template, url_for, redirect, g, flash, request, Markup)
|
|
||||||
from flask_login import login_required, current_user
|
|
||||||
|
|
||||||
from amanuensis.config import root
|
|
||||||
from amanuensis.config.loader import ReadOnlyOrderedDict
|
|
||||||
from amanuensis.errors import MissingConfigError
|
|
||||||
from amanuensis.lexicon.manage import valid_add, add_player, add_character, attempt_publish
|
|
||||||
from amanuensis.parser import parse_raw_markdown, PreviewHtmlRenderer, FeatureCounter, filesafe_title
|
|
||||||
from amanuensis.server.forms import (
|
|
||||||
LexiconConfigForm, LexiconJoinForm,LexiconCharacterForm, LexiconReviewForm)
|
|
||||||
from amanuensis.server.helpers import (
|
|
||||||
lexicon_param, player_required, editor_required,
|
|
||||||
player_required_if_not_public)
|
|
||||||
|
|
||||||
|
|
||||||
def jsonfmt(obj):
|
|
||||||
return Markup(json.dumps(obj))
|
|
||||||
|
|
||||||
|
|
||||||
def get_bp():
|
|
||||||
"""Create a blueprint for lexicon pages"""
|
|
||||||
bp = Blueprint('lexicon', __name__, url_prefix='/lexicon/<name>')
|
|
||||||
|
|
||||||
@bp.route("/join/", methods=['GET', 'POST'])
|
|
||||||
@lexicon_param
|
|
||||||
@login_required
|
|
||||||
def join(name):
|
|
||||||
if not g.lexicon.join.open:
|
|
||||||
flash("This game isn't open for joining")
|
|
||||||
return redirect(url_for('home.home'))
|
|
||||||
|
|
||||||
form = LexiconJoinForm()
|
|
||||||
|
|
||||||
if form.validate_on_submit():
|
|
||||||
# Gate on password if one is required
|
|
||||||
if (g.lexicon.join.password
|
|
||||||
and form.password.data != g.lexicon.join.password):
|
|
||||||
return redirect(url_for("lexicon.join", name=name))
|
|
||||||
# Gate on join validity
|
|
||||||
if valid_add(g.lexicon, current_user, form.password.data):
|
|
||||||
add_player(g.lexicon, current_user)
|
|
||||||
return redirect(url_for("lexicon.session", name=name))
|
|
||||||
else:
|
|
||||||
flash("Could not join game")
|
|
||||||
return redirect(url_for("home.home", name=name))
|
|
||||||
|
|
||||||
return render_template('lexicon/join.jinja', form=form)
|
|
||||||
|
|
||||||
@bp.route('/contents/', methods=['GET'])
|
|
||||||
@lexicon_param
|
|
||||||
@player_required_if_not_public
|
|
||||||
def contents(name):
|
|
||||||
articles = []
|
|
||||||
filenames = g.lexicon_.ctx.article.ls()
|
|
||||||
for filename in filenames:
|
|
||||||
with g.lexicon_.ctx.article.read(filename) as a:
|
|
||||||
articles.append({
|
|
||||||
'title': a.title,
|
|
||||||
'link': url_for('lexicon.article', name=name, title=filesafe_title(a.title)),
|
|
||||||
})
|
|
||||||
return render_template('lexicon/contents.jinja', articles=articles)
|
|
||||||
|
|
||||||
@bp.route('/article/<title>')
|
|
||||||
@lexicon_param
|
|
||||||
@player_required_if_not_public
|
|
||||||
def article(name, title):
|
|
||||||
with g.lexicon_.ctx.article.read(title) as a:
|
|
||||||
article = { **a, 'html': Markup(a['html']) }
|
|
||||||
return render_template('lexicon/article.jinja', article=article)
|
|
||||||
|
|
||||||
@bp.route('/rules/', methods=['GET'])
|
|
||||||
@lexicon_param
|
|
||||||
@player_required_if_not_public
|
|
||||||
def rules(name):
|
|
||||||
return render_template('lexicon/rules.jinja')
|
|
||||||
|
|
||||||
@bp.route('/session/', methods=['GET'])
|
|
||||||
@lexicon_param
|
|
||||||
@player_required
|
|
||||||
def session(name):
|
|
||||||
drafts = []
|
|
||||||
approved = []
|
|
||||||
draft_ctx = g.lexicon.ctx.draft
|
|
||||||
draft_filenames = draft_ctx.ls()
|
|
||||||
for draft_filename in draft_filenames:
|
|
||||||
with draft_ctx.read(draft_filename) as draft:
|
|
||||||
if draft.status.ready and not draft.status.approved:
|
|
||||||
drafts.append(draft)
|
|
||||||
if draft.status.approved:
|
|
||||||
approved.append(draft)
|
|
||||||
return render_template(
|
|
||||||
'lexicon/session.jinja',
|
|
||||||
ready_articles=drafts,
|
|
||||||
approved_articles=approved)
|
|
||||||
|
|
||||||
def edit_character(name, form, cid):
|
|
||||||
if form.validate_on_submit():
|
|
||||||
# Update character
|
|
||||||
form.update_character(g.lexicon, cid)
|
|
||||||
flash('Character updated')
|
|
||||||
return redirect(url_for('lexicon.session', name=name))
|
|
||||||
|
|
||||||
if not form.is_submitted():
|
|
||||||
# On GET, populate with the character
|
|
||||||
form.for_character(g.lexicon, cid)
|
|
||||||
return render_template('lexicon/character.jinja', form=form, action='edit')
|
|
||||||
|
|
||||||
def create_character(name, form):
|
|
||||||
if form.validate_on_submit():
|
|
||||||
# On POST, verify character can be added
|
|
||||||
if not g.lexicon.can_add_character(current_user.id):
|
|
||||||
flash('Operation not permitted')
|
|
||||||
return redirect(url_for('lexicon.session', name=name))
|
|
||||||
# Add the character
|
|
||||||
form.add_character(g.lexicon, current_user)
|
|
||||||
flash('Character created')
|
|
||||||
return redirect(url_for('lexicon.session', name=name))
|
|
||||||
|
|
||||||
if not form.is_submitted():
|
|
||||||
# On GET, populate form for new character
|
|
||||||
form.for_new()
|
|
||||||
return render_template('lexicon/character.jinja', form=form, action='create')
|
|
||||||
|
|
||||||
@bp.route('/session/character/', methods=['GET', 'POST'])
|
|
||||||
@lexicon_param
|
|
||||||
@player_required
|
|
||||||
def character(name):
|
|
||||||
form = LexiconCharacterForm()
|
|
||||||
cid = request.args.get('cid')
|
|
||||||
if cid:
|
|
||||||
if cid not in g.lexicon.character:
|
|
||||||
flash('Character not found')
|
|
||||||
return redirect(url_for('lexicon.session', name=name))
|
|
||||||
if (g.lexicon.character.get(cid).player != current_user.id
|
|
||||||
and g.lexicon.editor != current_user.id):
|
|
||||||
flash('Access denied')
|
|
||||||
return redirect(url_for('lexicon.session', name=name))
|
|
||||||
return edit_character(name, form, cid)
|
|
||||||
return create_character(name, form)
|
|
||||||
|
|
||||||
@bp.route('/session/settings/', methods=['GET', 'POST'])
|
|
||||||
@lexicon_param
|
|
||||||
@editor_required
|
|
||||||
def settings(name):
|
|
||||||
form = LexiconConfigForm()
|
|
||||||
form.set_options(g.lexicon)
|
|
||||||
|
|
||||||
# Load the config for the lexicon on load
|
|
||||||
if not form.is_submitted():
|
|
||||||
form.populate_from_lexicon(g.lexicon)
|
|
||||||
return render_template("lexicon/settings.jinja", form=form)
|
|
||||||
|
|
||||||
if form.validate():
|
|
||||||
if not form.update_lexicon(g.lexicon):
|
|
||||||
flash("Error updating settings")
|
|
||||||
return render_template("lexicon/settings.jinja", form=form)
|
|
||||||
flash("Settings updated")
|
|
||||||
return redirect(url_for('lexicon.session', name=name))
|
|
||||||
|
|
||||||
flash("Validation error")
|
|
||||||
return render_template("lexicon/settings.jinja", form=form)
|
|
||||||
|
|
||||||
@bp.route('/session/review/', methods=['GET', 'POST'])
|
|
||||||
@lexicon_param
|
|
||||||
@editor_required
|
|
||||||
def review(name):
|
|
||||||
aid = request.args.get('aid')
|
|
||||||
if not aid:
|
|
||||||
flash("Unknown article id")
|
|
||||||
return redirect(url_for('lexicon.session', name=name))
|
|
||||||
|
|
||||||
draft_ctx = g.lexicon.ctx.draft
|
|
||||||
draft_filename = [fn for fn in draft_ctx.ls() if aid in fn][0]
|
|
||||||
with draft_ctx.edit(draft_filename) as draft:
|
|
||||||
# If the article was unreadied in the meantime, abort
|
|
||||||
if not draft.status.ready:
|
|
||||||
flash("Article was rescinded")
|
|
||||||
return redirect(url_for('lexicon.session', name=name))
|
|
||||||
|
|
||||||
parsed_draft = parse_raw_markdown(draft.contents)
|
|
||||||
rendered_html = parsed_draft.render(PreviewHtmlRenderer(g.lexicon))
|
|
||||||
|
|
||||||
# If the article is ready and awaiting review
|
|
||||||
if not draft.status.approved:
|
|
||||||
form = LexiconReviewForm()
|
|
||||||
if form.validate_on_submit():
|
|
||||||
if form.approved.data == 'Y':
|
|
||||||
draft.status.ready = True
|
|
||||||
draft.status.approved = True
|
|
||||||
g.lexicon.add_log(f"Article '{draft.title}' approved ({draft.aid})")
|
|
||||||
if g.lexicon.publish.asap:
|
|
||||||
attempt_publish(g.lexicon)
|
|
||||||
else:
|
|
||||||
draft.status.ready = False
|
|
||||||
draft.status.approved = False
|
|
||||||
g.lexicon.add_log(f"Article '{draft.title}' rejected ({draft.aid})")
|
|
||||||
return redirect(url_for('lexicon.session', name=name))
|
|
||||||
|
|
||||||
# If the article was already reviewed and this is just the preview
|
|
||||||
else:
|
|
||||||
form = None
|
|
||||||
|
|
||||||
return render_template(
|
|
||||||
"lexicon/review.jinja",
|
|
||||||
form=form,
|
|
||||||
article_html=Markup(rendered_html))
|
|
||||||
|
|
||||||
@bp.route('/statistics/', methods=['GET'])
|
|
||||||
@lexicon_param
|
|
||||||
@player_required_if_not_public
|
|
||||||
def stats(name):
|
|
||||||
return render_template('lexicon/statistics.jinja')
|
|
||||||
|
|
||||||
@bp.route('/session/editor/', methods=['GET'])
|
|
||||||
@lexicon_param
|
|
||||||
@player_required
|
|
||||||
def editor(name):
|
|
||||||
"""
|
|
||||||
cases:
|
|
||||||
- neither cid nor aid: load all chars and articles
|
|
||||||
- cid: list articles just for cid
|
|
||||||
- aid:
|
|
||||||
"""
|
|
||||||
cid = request.args.get('cid')
|
|
||||||
if not cid:
|
|
||||||
# Character not specified, load all characters and articles
|
|
||||||
# and return render_template
|
|
||||||
characters = [
|
|
||||||
char for char in g.lexicon.character.values()
|
|
||||||
if char.player == current_user.id
|
|
||||||
]
|
|
||||||
articles = [
|
|
||||||
article for article in g.lexicon.get_drafts_for_player(uid=current_user.id)
|
|
||||||
if any([article.character == char.cid for char in characters])
|
|
||||||
]
|
|
||||||
return render_template(
|
|
||||||
'lexicon/editor.jinja',
|
|
||||||
characters=characters,
|
|
||||||
articles=articles,
|
|
||||||
jsonfmt=jsonfmt)
|
|
||||||
|
|
||||||
character = g.lexicon.character.get(cid)
|
|
||||||
if not character:
|
|
||||||
# Character was specified, but id was invalid
|
|
||||||
flash("Character not found")
|
|
||||||
return redirect(url_for('lexicon.session', name=name))
|
|
||||||
if character.player != current_user.id:
|
|
||||||
# Player doesn't control this character
|
|
||||||
flash("Access forbidden")
|
|
||||||
return redirect(url_for('lexicon.session', name=name))
|
|
||||||
|
|
||||||
aid = request.args.get('aid')
|
|
||||||
if not aid:
|
|
||||||
# Character specified but not article, load character articles
|
|
||||||
# and retuen r_t
|
|
||||||
articles = [
|
|
||||||
article for article in g.lexicon.get_drafts_for_player(uid=current_user.id)
|
|
||||||
if article.character == character.cid
|
|
||||||
]
|
|
||||||
return render_template(
|
|
||||||
'lexicon/editor.jinja',
|
|
||||||
character=character,
|
|
||||||
articles=articles,
|
|
||||||
jsonfmt=jsonfmt)
|
|
||||||
|
|
||||||
filename = f'{cid}.{aid}'
|
|
||||||
try:
|
|
||||||
with g.lexicon.ctx.draft.read(filename) as a:
|
|
||||||
article = a
|
|
||||||
except MissingConfigError:
|
|
||||||
flash("Draft not found")
|
|
||||||
return redirect(url_for('lexicon.session', name=name))
|
|
||||||
|
|
||||||
return render_template(
|
|
||||||
'lexicon/editor.jinja',
|
|
||||||
character=character,
|
|
||||||
article=article,
|
|
||||||
jsonfmt=jsonfmt)
|
|
||||||
|
|
||||||
@bp.route('/session/editor/new', methods=['GET'])
|
|
||||||
@lexicon_param
|
|
||||||
@player_required
|
|
||||||
def editor_new(name):
|
|
||||||
new_aid = uuid.uuid4().hex
|
|
||||||
# TODO harden this
|
|
||||||
cid = request.args.get("cid")
|
|
||||||
character = g.lexicon.character.get(cid)
|
|
||||||
article = {
|
|
||||||
"version": "0",
|
|
||||||
"aid": new_aid,
|
|
||||||
"lexicon": g.lexicon.id,
|
|
||||||
"character": cid,
|
|
||||||
"title": "",
|
|
||||||
"turn": 1,
|
|
||||||
"status": {
|
|
||||||
"ready": False,
|
|
||||||
"approved": False
|
|
||||||
},
|
|
||||||
"contents": f"\n\n{character.signature}",
|
|
||||||
}
|
|
||||||
filename = f"{cid}.{new_aid}"
|
|
||||||
with g.lexicon.ctx.draft.new(filename) as j:
|
|
||||||
j.update(article)
|
|
||||||
return redirect(url_for('lexicon.editor', name=name, cid=cid, aid=new_aid))
|
|
||||||
|
|
||||||
@bp.route('/session/editor/update', methods=['POST'])
|
|
||||||
@lexicon_param
|
|
||||||
@player_required
|
|
||||||
def editor_update(name):
|
|
||||||
article = request.json['article']
|
|
||||||
# TODO verification
|
|
||||||
# check if article was previously approved
|
|
||||||
# check extrinsic constraints for blocking errors
|
|
||||||
parsed_draft = parse_raw_markdown(article['contents'])
|
|
||||||
rendered_html = parsed_draft.render(PreviewHtmlRenderer(g.lexicon))
|
|
||||||
features = parsed_draft.render(FeatureCounter())
|
|
||||||
|
|
||||||
filename = f'{article["character"]}.{article["aid"]}'
|
|
||||||
with g.lexicon.ctx.draft.edit(filename) as a:
|
|
||||||
a.update(article)
|
|
||||||
|
|
||||||
# TODO return more info
|
|
||||||
return {
|
|
||||||
'article': article,
|
|
||||||
'info': {
|
|
||||||
'rendered': rendered_html,
|
|
||||||
'word_count': features.word_count,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return bp
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
from flask import (
|
||||||
|
Blueprint,
|
||||||
|
flash,
|
||||||
|
redirect,
|
||||||
|
url_for,
|
||||||
|
g,
|
||||||
|
render_template,
|
||||||
|
Markup)
|
||||||
|
from flask_login import login_required, current_user
|
||||||
|
|
||||||
|
from amanuensis.lexicon import player_can_join_lexicon, add_player_to_lexicon
|
||||||
|
from amanuensis.parser import filesafe_title
|
||||||
|
from amanuensis.server.forms import LexiconJoinForm
|
||||||
|
from amanuensis.server.helpers import (
|
||||||
|
lexicon_param,
|
||||||
|
player_required_if_not_public)
|
||||||
|
|
||||||
|
|
||||||
|
bp_lexicon = Blueprint('lexicon', __name__,
|
||||||
|
url_prefix='/lexicon/<name>',
|
||||||
|
template_folder='.')
|
||||||
|
|
||||||
|
|
||||||
|
@bp_lexicon.route("/join/", methods=['GET', 'POST'])
|
||||||
|
@lexicon_param
|
||||||
|
@login_required
|
||||||
|
def join(name):
|
||||||
|
if not g.lexicon.cfg.join.open:
|
||||||
|
flash("This game isn't open for joining")
|
||||||
|
return redirect(url_for('home.home'))
|
||||||
|
|
||||||
|
form = LexiconJoinForm()
|
||||||
|
|
||||||
|
if form.validate_on_submit():
|
||||||
|
# Gate on password if one is required
|
||||||
|
if (g.lexicon.cfg.join.password
|
||||||
|
and form.password.data != g.lexicon.cfg.join.password):
|
||||||
|
return redirect(url_for("lexicon.join", name=name))
|
||||||
|
# Gate on join validity
|
||||||
|
if player_can_join_lexicon(current_user, g.lexicon, form.password.data):
|
||||||
|
add_player_to_lexicon(current_user, g.lexicon)
|
||||||
|
return redirect(url_for("lexicon.contents", name=name)) # SESSION
|
||||||
|
else:
|
||||||
|
flash("Could not join game")
|
||||||
|
return redirect(url_for("home.home", name=name))
|
||||||
|
|
||||||
|
return render_template('lexicon.join.jinja', form=form)
|
||||||
|
|
||||||
|
|
||||||
|
@bp_lexicon.route('/contents/', methods=['GET'])
|
||||||
|
@lexicon_param
|
||||||
|
@player_required_if_not_public
|
||||||
|
def contents(name):
|
||||||
|
articles = []
|
||||||
|
filenames = g.lexicon.ctx.article.ls()
|
||||||
|
for filename in filenames:
|
||||||
|
with g.lexicon.ctx.article.read(filename) as a:
|
||||||
|
articles.append({
|
||||||
|
'title': a.title,
|
||||||
|
'link': url_for('lexicon.article',
|
||||||
|
name=name,
|
||||||
|
title=filesafe_title(a.title)),
|
||||||
|
})
|
||||||
|
return render_template('lexicon.contents.jinja', articles=articles)
|
||||||
|
|
||||||
|
|
||||||
|
@bp_lexicon.route('/article/<title>')
|
||||||
|
@lexicon_param
|
||||||
|
@player_required_if_not_public
|
||||||
|
def article(name, title):
|
||||||
|
with g.lexicon.ctx.article.read(title) as a:
|
||||||
|
article = {**a, 'html': Markup(a['html'])}
|
||||||
|
return render_template('lexicon.article.jinja', article=article)
|
||||||
|
|
||||||
|
|
||||||
|
@bp_lexicon.route('/rules/', methods=['GET'])
|
||||||
|
@lexicon_param
|
||||||
|
@player_required_if_not_public
|
||||||
|
def rules(name):
|
||||||
|
return render_template('lexicon.rules.jinja')
|
||||||
|
|
||||||
|
|
||||||
|
@bp_lexicon.route('/statistics/', methods=['GET'])
|
||||||
|
@lexicon_param
|
||||||
|
@player_required_if_not_public
|
||||||
|
def stats(name):
|
||||||
|
return render_template('lexicon.statistics.jinja')
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "lexicon/lexicon.jinja" %}
|
{% extends "lexicon.jinja" %}
|
||||||
{% block title %}{{ article.title }} | {{ lexicon_title }}{% endblock %}
|
{% block title %}{{ article.title }} | {{ lexicon_title }}{% endblock %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "lexicon/lexicon.jinja" %}
|
{% extends "lexicon.jinja" %}
|
||||||
{% set current_page = "contents" %}
|
{% set current_page = "contents" %}
|
||||||
{% block title %}Index | {{ lexicon_title }}{% endblock %}
|
{% block title %}Index | {{ lexicon_title }}{% endblock %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "lexicon/lexicon.jinja" %}
|
{% extends "lexicon.jinja" %}
|
||||||
{% block title %}Join | {{ lexicon_title }}{% endblock %}
|
{% block title %}Join | {{ lexicon_title }}{% endblock %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "lexicon/lexicon.jinja" %}
|
{% extends "lexicon.jinja" %}
|
||||||
{% set current_page = "rules" %}
|
{% set current_page = "rules" %}
|
||||||
{% block title %}Rules | {{ lexicon_title }}{% endblock %}
|
{% block title %}Rules | {{ lexicon_title }}{% endblock %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "lexicon/lexicon.jinja" %}
|
{% extends "lexicon.jinja" %}
|
||||||
{% set current_page = "statistics" %}
|
{% set current_page = "statistics" %}
|
||||||
{% block title %}Session | {{ lexicon_title }}{% endblock %}
|
{% block title %}Session | {{ lexicon_title }}{% endblock %}
|
||||||
|
|
|
@ -0,0 +1,303 @@
|
||||||
|
import json
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from flask import (
|
||||||
|
Blueprint,
|
||||||
|
render_template,
|
||||||
|
url_for,
|
||||||
|
redirect,
|
||||||
|
g,
|
||||||
|
flash,
|
||||||
|
request,
|
||||||
|
Markup)
|
||||||
|
from flask_login import login_required, current_user
|
||||||
|
|
||||||
|
from amanuensis.config import root
|
||||||
|
from amanuensis.config.loader import ReadOnlyOrderedDict
|
||||||
|
from amanuensis.errors import MissingConfigError
|
||||||
|
from amanuensis.lexicon.manage import (
|
||||||
|
valid_add,
|
||||||
|
add_player,
|
||||||
|
add_character,
|
||||||
|
attempt_publish)
|
||||||
|
from amanuensis.parser import (
|
||||||
|
parse_raw_markdown,
|
||||||
|
PreviewHtmlRenderer,
|
||||||
|
FeatureCounter,
|
||||||
|
filesafe_title)
|
||||||
|
from amanuensis.server.forms import (
|
||||||
|
LexiconConfigForm,
|
||||||
|
LexiconJoinForm,
|
||||||
|
LexiconCharacterForm,
|
||||||
|
LexiconReviewForm)
|
||||||
|
from amanuensis.server.helpers import (
|
||||||
|
lexicon_param,
|
||||||
|
player_required,
|
||||||
|
editor_required,
|
||||||
|
player_required_if_not_public)
|
||||||
|
|
||||||
|
|
||||||
|
def jsonfmt(obj):
|
||||||
|
return Markup(json.dumps(obj))
|
||||||
|
|
||||||
|
|
||||||
|
bp_session = Blueprint('lexicon', __name__,
|
||||||
|
url_prefix='/lexicon/<name>/session',
|
||||||
|
template_folder='.')
|
||||||
|
|
||||||
|
|
||||||
|
@bp_session.route('/', methods=['GET'])
|
||||||
|
@lexicon_param
|
||||||
|
@player_required
|
||||||
|
def session(name):
|
||||||
|
drafts = []
|
||||||
|
approved = []
|
||||||
|
draft_ctx = g.lexicon.ctx.draft
|
||||||
|
draft_filenames = draft_ctx.ls()
|
||||||
|
for draft_filename in draft_filenames:
|
||||||
|
with draft_ctx.read(draft_filename) as draft:
|
||||||
|
if draft.status.ready and not draft.status.approved:
|
||||||
|
drafts.append(draft)
|
||||||
|
if draft.status.approved:
|
||||||
|
approved.append(draft)
|
||||||
|
return render_template(
|
||||||
|
'session.session.jinja',
|
||||||
|
ready_articles=drafts,
|
||||||
|
approved_articles=approved)
|
||||||
|
|
||||||
|
|
||||||
|
def edit_character(name, form, cid):
|
||||||
|
if form.validate_on_submit():
|
||||||
|
# Update character
|
||||||
|
form.update_character(g.lexicon, cid)
|
||||||
|
flash('Character updated')
|
||||||
|
return redirect(url_for('session.session', name=name))
|
||||||
|
|
||||||
|
if not form.is_submitted():
|
||||||
|
# On GET, populate with the character
|
||||||
|
form.for_character(g.lexicon, cid)
|
||||||
|
return render_template('session.character.jinja', form=form, action='edit')
|
||||||
|
|
||||||
|
|
||||||
|
def create_character(name, form):
|
||||||
|
if form.validate_on_submit():
|
||||||
|
# On POST, verify character can be added
|
||||||
|
if not g.lexicon.can_add_character(current_user.id):
|
||||||
|
flash('Operation not permitted')
|
||||||
|
return redirect(url_for('session.session', name=name))
|
||||||
|
# Add the character
|
||||||
|
form.add_character(g.lexicon, current_user)
|
||||||
|
flash('Character created')
|
||||||
|
return redirect(url_for('session.session', name=name))
|
||||||
|
|
||||||
|
if not form.is_submitted():
|
||||||
|
# On GET, populate form for new character
|
||||||
|
form.for_new()
|
||||||
|
return render_template('session.character.jinja', form=form, action='create')
|
||||||
|
|
||||||
|
|
||||||
|
@bp_session.route('/character/', methods=['GET', 'POST'])
|
||||||
|
@lexicon_param
|
||||||
|
@player_required
|
||||||
|
def character(name):
|
||||||
|
form = LexiconCharacterForm()
|
||||||
|
cid = request.args.get('cid')
|
||||||
|
if cid:
|
||||||
|
if cid not in g.lexicon.character:
|
||||||
|
flash('Character not found')
|
||||||
|
return redirect(url_for('session.session', name=name))
|
||||||
|
if (g.lexicon.character.get(cid).player != current_user.id
|
||||||
|
and g.lexicon.editor != current_user.id):
|
||||||
|
flash('Access denied')
|
||||||
|
return redirect(url_for('session.session', name=name))
|
||||||
|
return edit_character(name, form, cid)
|
||||||
|
return create_character(name, form)
|
||||||
|
|
||||||
|
|
||||||
|
@bp_session.route('/settings/', methods=['GET', 'POST'])
|
||||||
|
@lexicon_param
|
||||||
|
@editor_required
|
||||||
|
def settings(name):
|
||||||
|
form = LexiconConfigForm()
|
||||||
|
form.set_options(g.lexicon)
|
||||||
|
|
||||||
|
# Load the config for the lexicon on load
|
||||||
|
if not form.is_submitted():
|
||||||
|
form.populate_from_lexicon(g.lexicon)
|
||||||
|
return render_template('session.settings.jinja', form=form)
|
||||||
|
|
||||||
|
if form.validate():
|
||||||
|
if not form.update_lexicon(g.lexicon):
|
||||||
|
flash("Error updating settings")
|
||||||
|
return render_template("lexicon.settings.jinja", form=form)
|
||||||
|
flash("Settings updated")
|
||||||
|
return redirect(url_for('session.session', name=name))
|
||||||
|
|
||||||
|
flash("Validation error")
|
||||||
|
return render_template('session.settings.jinja', form=form)
|
||||||
|
|
||||||
|
|
||||||
|
@bp_session.route('/review/', methods=['GET', 'POST'])
|
||||||
|
@lexicon_param
|
||||||
|
@editor_required
|
||||||
|
def review(name):
|
||||||
|
aid = request.args.get('aid')
|
||||||
|
if not aid:
|
||||||
|
flash("Unknown article id")
|
||||||
|
return redirect(url_for('session.session', name=name))
|
||||||
|
|
||||||
|
draft_ctx = g.lexicon.ctx.draft
|
||||||
|
draft_filename = [fn for fn in draft_ctx.ls() if aid in fn][0]
|
||||||
|
with draft_ctx.edit(draft_filename) as draft:
|
||||||
|
# If the article was unreadied in the meantime, abort
|
||||||
|
if not draft.status.ready:
|
||||||
|
flash("Article was rescinded")
|
||||||
|
return redirect(url_for('session.session', name=name))
|
||||||
|
|
||||||
|
parsed_draft = parse_raw_markdown(draft.contents)
|
||||||
|
rendered_html = parsed_draft.render(PreviewHtmlRenderer(g.lexicon))
|
||||||
|
|
||||||
|
# If the article is ready and awaiting review
|
||||||
|
if not draft.status.approved:
|
||||||
|
form = LexiconReviewForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
if form.approved.data == 'Y':
|
||||||
|
draft.status.ready = True
|
||||||
|
draft.status.approved = True
|
||||||
|
g.lexicon.add_log(f"Article '{draft.title}' approved ({draft.aid})")
|
||||||
|
if g.lexicon.publish.asap:
|
||||||
|
attempt_publish(g.lexicon)
|
||||||
|
else:
|
||||||
|
draft.status.ready = False
|
||||||
|
draft.status.approved = False
|
||||||
|
g.lexicon.add_log(f"Article '{draft.title}' rejected ({draft.aid})")
|
||||||
|
return redirect(url_for('session.session', name=name))
|
||||||
|
|
||||||
|
# If the article was already reviewed and this is just the preview
|
||||||
|
else:
|
||||||
|
form = None
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"session.review.jinja",
|
||||||
|
form=form,
|
||||||
|
article_html=Markup(rendered_html))
|
||||||
|
|
||||||
|
|
||||||
|
@bp_session.route('/editor/', methods=['GET'])
|
||||||
|
@lexicon_param
|
||||||
|
@player_required
|
||||||
|
def editor(name):
|
||||||
|
"""
|
||||||
|
cases:
|
||||||
|
- neither cid nor aid: load all chars and articles
|
||||||
|
- cid: list articles just for cid
|
||||||
|
- aid:
|
||||||
|
"""
|
||||||
|
cid = request.args.get('cid')
|
||||||
|
if not cid:
|
||||||
|
# Character not specified, load all characters and articles
|
||||||
|
# and return render_template
|
||||||
|
characters = [
|
||||||
|
char for char in g.lexicon.character.values()
|
||||||
|
if char.player == current_user.id
|
||||||
|
]
|
||||||
|
articles = [
|
||||||
|
article for article in g.lexicon.get_drafts_for_player(uid=current_user.id)
|
||||||
|
if any([article.character == char.cid for char in characters])
|
||||||
|
]
|
||||||
|
return render_template(
|
||||||
|
'session.editor.jinja',
|
||||||
|
characters=characters,
|
||||||
|
articles=articles,
|
||||||
|
jsonfmt=jsonfmt)
|
||||||
|
|
||||||
|
character = g.lexicon.character.get(cid)
|
||||||
|
if not character:
|
||||||
|
# Character was specified, but id was invalid
|
||||||
|
flash("Character not found")
|
||||||
|
return redirect(url_for('session.session', name=name))
|
||||||
|
if character.player != current_user.id:
|
||||||
|
# Player doesn't control this character
|
||||||
|
flash("Access forbidden")
|
||||||
|
return redirect(url_for('session.session', name=name))
|
||||||
|
|
||||||
|
aid = request.args.get('aid')
|
||||||
|
if not aid:
|
||||||
|
# Character specified but not article, load character articles
|
||||||
|
# and retuen r_t
|
||||||
|
articles = [
|
||||||
|
article for article in g.lexicon.get_drafts_for_player(uid=current_user.id)
|
||||||
|
if article.character == character.cid
|
||||||
|
]
|
||||||
|
return render_template(
|
||||||
|
'session.editor.jinja',
|
||||||
|
character=character,
|
||||||
|
articles=articles,
|
||||||
|
jsonfmt=jsonfmt)
|
||||||
|
|
||||||
|
filename = f'{cid}.{aid}'
|
||||||
|
try:
|
||||||
|
with g.lexicon.ctx.draft.read(filename) as a:
|
||||||
|
article = a
|
||||||
|
except MissingConfigError:
|
||||||
|
flash("Draft not found")
|
||||||
|
return redirect(url_for('session.session', name=name))
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
'session.editor.jinja',
|
||||||
|
character=character,
|
||||||
|
article=article,
|
||||||
|
jsonfmt=jsonfmt)
|
||||||
|
|
||||||
|
|
||||||
|
@bp_session.route('/editor/new', methods=['GET'])
|
||||||
|
@lexicon_param
|
||||||
|
@player_required
|
||||||
|
def editor_new(name):
|
||||||
|
new_aid = uuid.uuid4().hex
|
||||||
|
# TODO harden this
|
||||||
|
cid = request.args.get("cid")
|
||||||
|
character = g.lexicon.character.get(cid)
|
||||||
|
article = {
|
||||||
|
"version": "0",
|
||||||
|
"aid": new_aid,
|
||||||
|
"lexicon": g.lexicon.id,
|
||||||
|
"character": cid,
|
||||||
|
"title": "",
|
||||||
|
"turn": 1,
|
||||||
|
"status": {
|
||||||
|
"ready": False,
|
||||||
|
"approved": False
|
||||||
|
},
|
||||||
|
"contents": f"\n\n{character.signature}",
|
||||||
|
}
|
||||||
|
filename = f"{cid}.{new_aid}"
|
||||||
|
with g.lexicon.ctx.draft.new(filename) as j:
|
||||||
|
j.update(article)
|
||||||
|
return redirect(url_for('session.editor', name=name, cid=cid, aid=new_aid))
|
||||||
|
|
||||||
|
|
||||||
|
@bp_session.route('/editor/update', methods=['POST'])
|
||||||
|
@lexicon_param
|
||||||
|
@player_required
|
||||||
|
def editor_update(name):
|
||||||
|
article = request.json['article']
|
||||||
|
# TODO verification
|
||||||
|
# check if article was previously approved
|
||||||
|
# check extrinsic constraints for blocking errors
|
||||||
|
parsed_draft = parse_raw_markdown(article['contents'])
|
||||||
|
rendered_html = parsed_draft.render(PreviewHtmlRenderer(g.lexicon))
|
||||||
|
features = parsed_draft.render(FeatureCounter())
|
||||||
|
|
||||||
|
filename = f'{article["character"]}.{article["aid"]}'
|
||||||
|
with g.lexicon.ctx.draft.edit(filename) as a:
|
||||||
|
a.update(article)
|
||||||
|
|
||||||
|
# TODO return more info
|
||||||
|
return {
|
||||||
|
'article': article,
|
||||||
|
'info': {
|
||||||
|
'rendered': rendered_html,
|
||||||
|
'word_count': features.word_count,
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "lexicon/lexicon.jinja" %}
|
{% extends "lexicon.jinja" %}
|
||||||
{% block title %}Character | {{ lexicon_title }}{% endblock %}
|
{% block title %}Character | {{ lexicon_title }}{% endblock %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "lexicon/lexicon.jinja" %}
|
{% extends "lexicon.jinja" %}
|
||||||
{% block title %}Review | {{ lexicon_title }}{% endblock %}
|
{% block title %}Review | {{ lexicon_title }}{% endblock %}
|
||||||
|
|
||||||
{% block article %}
|
{% block article %}
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "lexicon/lexicon.jinja" %}
|
{% extends "lexicon.jinja" %}
|
||||||
{% set current_page = "session" %}
|
{% set current_page = "session" %}
|
||||||
{% block title %}Session | {{ lexicon_title }}{% endblock %}
|
{% block title %}Session | {{ lexicon_title }}{% endblock %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "lexicon/lexicon.jinja" %}
|
{% extends "lexicon.jinja" %}
|
||||||
{% block title %}Edit | {{ lexicon_title }}{% endblock %}
|
{% block title %}Edit | {{ lexicon_title }}{% endblock %}
|
||||||
|
|
||||||
{% block info %}
|
{% block info %}
|
Loading…
Reference in New Issue