diff --git a/amanuensis/lexicon/__init__.py b/amanuensis/lexicon/__init__.py
index ce46d2a..86ad1af 100644
--- a/amanuensis/lexicon/__init__.py
+++ b/amanuensis/lexicon/__init__.py
@@ -81,3 +81,16 @@ class LexiconModel():
return [
char for char in self.character.values()
if uid is None or char.player == uid]
+
+ def get_drafts_for_player(self, uid):
+ chars = self.get_characters_for_player(uid=uid)
+ drafts_path = prepend('lexicon', self.id, 'draft')
+ drafts = []
+ for filename in os.listdir(drafts_path):
+ for char in chars:
+ if filename.startswith(str(char.cid)):
+ drafts.append(filename)
+ for i in range(len(drafts)):
+ with json_ro(drafts_path, drafts[i]) as a:
+ drafts[i] = a
+ return drafts
diff --git a/amanuensis/resources/editor.css b/amanuensis/resources/editor.css
index 1944b64..c049764 100644
--- a/amanuensis/resources/editor.css
+++ b/amanuensis/resources/editor.css
@@ -20,7 +20,7 @@ div#editor-left {
div#editor-left div.contentblock {
display: flex;
flex-direction: column;
- margin: 10px 10px 10px 5px;
+ margin: 10px 5px 10px 10px;
width: 100%;
padding: 5px;
}
@@ -29,6 +29,9 @@ div#editor-left div#editor-header {
display: flex;
justify-content: space-between;
}
+div#editor-left div#editor-charselect {
+ margin: 5px;
+}
div#editor-left input#editor-title {
font-size: 2em;
margin: 5px;
diff --git a/amanuensis/resources/editor.js b/amanuensis/resources/editor.js
index 4052bea..1c45372 100644
--- a/amanuensis/resources/editor.js
+++ b/amanuensis/resources/editor.js
@@ -1,181 +1,85 @@
-function onContentChange() {
- // Get the new content
- var articleTitle = document.getElementById("editor-title").value;
- var articleBody = document.getElementById("editor-content").value;
- // Pass the draft text to the parser to get the preview html and citations
- var parseResult = parseLexipythonMarkdown(articleBody);
- // Build the citation block
- var citeblockContent = makeCiteblock(parseResult);
- // Compute warnings and build the control block
- var controlContent = checkWarnings(parseResult);
- // Fill in the content blocks
- document.getElementById("preview").innerHTML = (
- "
" + articleTitle + "
\n"
- + parseResult.html);
- document.getElementById("preview-citations").innerHTML = citeblockContent;
- document.getElementById("preview-control").innerHTML = controlContent;
-}
-
-function parseLexipythonMarkdown(text) {
- // Prepare return values
- var result = {
- html: "",
- citations: [],
- hasSignature: false,
- };
- // Parse the content by paragraph, extracting the citations
- var paras = text.trim().split(/\n\n+/);
- citationList = [];
- formatId = 1;
- for (var i = 0; i < paras.length; i++) {
- var para = paras[i];
- // Escape angle brackets
- para = para.replace(//g, ">");
- // Replace bold and italic marks with tags
- para = para.replace(/\/\/([^\/]+)\/\//g, "$1");
- para = para.replace(/\*\*([^*]+)\*\*/g, "$1");
- // Replace \\LF with
LF
- para = para.replace(/\\\\\n/g, "
\n");
- // Abstract citations into the citation record
- linkMatch = para.match(/\[\[(([^|\[\]]+)\|)?([^|\[\]]+)\]\]/);
- while (linkMatch != null) {
- // Identify the citation text and cited article
- citeText = linkMatch[2] != null ? linkMatch[2] : linkMatch[3];
- citeTitle = linkMatch[3].charAt(0).toUpperCase() + linkMatch[3].slice(1);
- // Record the citation
- result.citations.push({
- id: formatId,
- citeText: citeText,
- citeTitle: citeTitle,
- });
- // Stitch the cite text in place of the citation, plus a cite number
- para =
- para.slice(0, linkMatch.index) +
- "" +
- citeText +
- "" +
- "" +
- formatId.toString() +
- "" +
- para.slice(linkMatch.index + linkMatch[0].length);
- formatId += 1; // Increment to the next format id
- linkMatch = para.match(/\[\[(([^|\[\]]+)\|)?([^|\[\]]+)\]\]/);
- }
- // Mark signature paragraphs with a classed span
- if (para.length > 0 && para[0] == "~") {
- para = "
" + para.slice(1) + "
";
- result.hasSignature = true;
- } else {
- para = "" + para + "
\n";
- }
- result.html += para;
- }
- if (citationList.length > 0) {
- content += "The following articles will be cited:
\n";
- for (var i = 0; i < citationList.length; i++) {
- content += "" + citationList[i] + "
\n";
- }
- }
- return result;
-}
-
-function makeCiteblock(parseResult) {
- var citeTexts = []
- for (var i = 0; i < parseResult.citations.length; i++) {
- var cite = parseResult.citations[i];
- citeTexts.push("[" + cite.id.toString() + "] " + cite.citeTitle);
- }
- return citeTexts.join(" / ");
-}
-
-function checkWarnings(parseResult) {
- var result = {
- errors: [],
- warnings: [],
- };
- if (!parseResult.hasSignature) {
- result.warnings.push("Article has no signature.");
- }
- // Self-citation
- // TODO
- // Citation targets
- // TODO
- if (params.citation.min_total != null &&
- parseResult.citations.length < params.citation.min_total) {
- result.errors.push("Article must have a minimum of " +
- params.citation.min_total + " citations.");
- }
- if (params.citation.max_total != null &&
- parseResult.citations.length > params.citation.max_total) {
- result.errors.push("Article cannot have more than " +
- params.citation.max_total + " citations.");
- }
- // TODO
- // Word limits
- var wordCount = (parseResult.html
- // Delete all HTML tags
- .replace(/<[^>]+>/g, "")
- .trim()
- .split(/\s+/)
- .length);
- if (params.wordLimit.hard != null && wordCount > params.wordLimit.hard) {
- result.errors.push("Article must be shorter than " + params.wordLimit.hard + " words.");
- } else if (params.wordLimit.soft != null && wordCount > params.wordLimit.soft) {
- result.warnings.push("Article should be shorter than " + params.wordLimit.soft + " words.");
- }
-
- var controlContent = "";
- controlContent += "Word count: " + wordCount + "
";
- if (result.errors.length > 0) {
- controlContent += "";
- for (var i = 0; i < result.errors.length; i++) {
- controlContent += result.errors[i] + "
";
- }
- controlContent += "
";
- }
- if (result.warnings.length > 0) {
- controlContent += "";
- for (var i = 0; i < result.warnings.length; i++) {
- controlContent += result.warnings[i] + "
";
- }
- controlContent += "
";
- }
- return controlContent;
-}
-
-// Parse the article content and update the preview pane
-
-
-// function download() {
-// var articlePlayer = document.getElementById("article-player").value;
-// var articleTurn = document.getElementById("article-turn").value;
-// var articleTitle = document.getElementById("article-title").value;
-// var articleBody = document.getElementById("article-body").value;
-// var articleText =
-// "# Player: " + articlePlayer + "\n" +
-// "# Turn: " + articleTurn + "\n" +
-// "# Title: " + articleTitle + "\n" +
-// "\n" +
-// articleBody;
-// var articleFilename = articleTitle.toLowerCase().replace(/[^a-z0-9- ]/g, "").replace(/ +/g, "-");
-// var downloader = document.createElement("a");
-// downloader.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(articleText));
-// downloader.setAttribute("download", articleFilename);
-// if (document.createEvent) {
-// var event = document.createEvent("MouseEvents");
-// event.initEvent("click", true, true);
-// downloader.dispatchEvent(event);
-// } else {
-// downloader.click();
-// }
-// }
-
-window.onload = function() {
- document.getElementById("editor-content").value = "\n\n" + params.default_signature;
- this.onContentChange();
+// Editor state
+var loadedArticleInfo = {
+ aid: null,
+ lexicon: null,
+ character: null,
+ title: null,
+ turn: null,
+ status: {
+ ready: null,
+ approved: null,
+ },
+ contents: null,
};
+// Reduce unnecessary requests by checking for no further changes being made
+// before updating in response to a change.
+var nonce = 0;
+
+function ifNoFurtherChanges(callback) {
+ var nonce_local = Math.random();
+ nonce = nonce_local;
+ setTimeout(() => {
+ if (nonce == nonce_local) {
+ callback()
+ }
+ }, 2500);
+}
+
+// Initialize editor
+window.onload = function() {
+ loadedArticleInfo.character = params.characterId;
+
+ document.getElementById("preview").innerHTML = "
";
+
+ // document.getElementById("editor-content").value = "\n\n" + params.default_signature;
+
+ // this.onContentChange();
+};
+
+function getArticleObj() {
+ // aid
+ // lexicon
+ // character
+ // title
+ // turn
+ // status
+ // contents
+}
+
+function update(article) {
+ var req = new XMLHttpRequest();
+ req.open("POST", params.updateURL, true);
+ req.setRequestHeader("Content-type", "application/json");
+ req.onreadystatechange = function () {
+ if (req.readyState == 4 && req.status == 200)
+ return;
+ };
+ req.send(article)
+}
+
+function onContentChange() {
+ setTimeout(() => {
+ if (nonce == nonce_local) {
+ // Get the new content
+ var articleTitle = document.getElementById("editor-title").value;
+ var articleBody = document.getElementById("editor-content").value;
+ // Pass the draft text to the parser to get the preview html and citations
+ var parseResult = parseLexipythonMarkdown(articleBody);
+ // Build the citation block
+ var citeblockContent = makeCiteblock(parseResult);
+ // Compute warnings and build the control block
+ var controlContent = checkWarnings(parseResult);
+ // Fill in the content blocks
+ document.getElementById("preview").innerHTML = (
+ "" + articleTitle + "
\n"
+ + parseResult.html);
+ document.getElementById("preview-citations").innerHTML = citeblockContent;
+ document.getElementById("preview-control").innerHTML = controlContent;
+ }
+ }, 3000);
+}
+
window.addEventListener("beforeunload", function(e) {
var content = document.getElementById("editor-content").value
var hasText = content.length > 0 && content != "\n\n" + params.default_signature;
diff --git a/amanuensis/resources/page.css b/amanuensis/resources/page.css
index 8102ad2..b22db7b 100644
--- a/amanuensis/resources/page.css
+++ b/amanuensis/resources/page.css
@@ -93,6 +93,7 @@ div.contentblock {
padding: 10px;
width: auto;
border-radius: 5px;
+ word-break: break-word;
}
div.contentblock h3 {
margin: 0.3em 0;
diff --git a/amanuensis/server/lexicon.py b/amanuensis/server/lexicon.py
index dbbf423..ce1cd1c 100644
--- a/amanuensis/server/lexicon.py
+++ b/amanuensis/server/lexicon.py
@@ -4,7 +4,7 @@ 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 json_ro, open_ex
+from amanuensis.config import json_ro, open_ex, prepend
from amanuensis.config.loader import ReadOnlyOrderedDict
from amanuensis.lexicon.manage import valid_add, add_player, add_character
from amanuensis.server.forms import (
@@ -14,6 +14,10 @@ from amanuensis.server.helpers import (
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/')
@@ -138,11 +142,98 @@ def get_bp():
@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.html',
+ 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.html',
+ character=character,
+ articles=articles,
+ jsonfmt=jsonfmt)
+
+ filename = f'{cid}.{aid}.json'
+ path = prepend('lexicon', g.lexicon.id, 'draft', filename)
+ import os
+ if not os.path.isfile(path):
+ flash("Draft not found")
+ return redirect(url_for('lexicon.session', name=name))
+ with json_ro(path) as a:
+ article = a
+
return render_template(
'lexicon/editor.html',
- current_turn=Markup(json.dumps(g.lexicon.turn.current)),
- citation=Markup(json.dumps(dict(g.lexicon.article.citation))),
- word_limit=Markup(json.dumps(dict(g.lexicon.article.word_limit))),
- addendum=Markup(json.dumps(dict(g.lexicon.article.citation))))
+ character=character,
+ article=article,
+ jsonfmt=jsonfmt)
+
+ @bp.route('/session/editor/new', methods=['GET'])
+ @lexicon_param
+ @player_required
+ def editor_new(name):
+ import uuid
+ new_aid = uuid.uuid4().hex
+ cid = request.args.get("cid")
+ article = {
+ "version": "0",
+ "aid": new_aid,
+ "lexicon": g.lexicon.id,
+ "character": cid,
+ "title": "",
+ "turn": 1,
+ "status": {
+ "ready": False,
+ "approved": False
+ },
+ "contents": ""
+ }
+ filename = f"{cid}.{new_aid}.json"
+ with open_ex('lexicon', g.lexicon.id, 'draft', filename, mode='w') as f:
+ json.dump(article, f)
+ 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):
+ pass
return bp
diff --git a/amanuensis/templates/lexicon/editor.html b/amanuensis/templates/lexicon/editor.html
index 6b0a2c2..db2b0ed 100644
--- a/amanuensis/templates/lexicon/editor.html
+++ b/amanuensis/templates/lexicon/editor.html
@@ -1,6 +1,5 @@
-{% set characters = g.lexicon.get_characters_for_player(current_user.id) %}
-{% if not characters %}
-{% set characters = [g.lexicon.character.default ] %}
+{% if character and not article %}
+{% set characters = [character] %}
{% endif %}
@@ -12,11 +11,17 @@
@@ -26,17 +31,51 @@
+ {% for char in characters %}
+
+ {% endfor %}
+ {% if article %}
-
+
+ {% endif %}
@@ -44,10 +83,10 @@
This editor requires Javascript to function.
diff --git a/amanuensis/templates/lexicon/session.html b/amanuensis/templates/lexicon/session.html
index e5dcb1d..981eaa2 100644
--- a/amanuensis/templates/lexicon/session.html
+++ b/amanuensis/templates/lexicon/session.html
@@ -45,6 +45,11 @@
Create a character
{% endif %}
+
+
+ Article editor
+
+
{% endblock %}
{% set template_content_blocks = template_content_blocks + [self.main()] %}
\ No newline at end of file