Strip down editor a bit and add basic article creation
This commit is contained in:
parent
c6cfe25e6d
commit
1e152851d0
|
@ -81,3 +81,16 @@ class LexiconModel():
|
||||||
return [
|
return [
|
||||||
char for char in self.character.values()
|
char for char in self.character.values()
|
||||||
if uid is None or char.player == uid]
|
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
|
||||||
|
|
|
@ -20,7 +20,7 @@ div#editor-left {
|
||||||
div#editor-left div.contentblock {
|
div#editor-left div.contentblock {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin: 10px 10px 10px 5px;
|
margin: 10px 5px 10px 10px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,9 @@ div#editor-left div#editor-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
div#editor-left div#editor-charselect {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
div#editor-left input#editor-title {
|
div#editor-left input#editor-title {
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
|
|
|
@ -1,4 +1,66 @@
|
||||||
|
// 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 = "<p> </p>";
|
||||||
|
|
||||||
|
// 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() {
|
function onContentChange() {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (nonce == nonce_local) {
|
||||||
// Get the new content
|
// Get the new content
|
||||||
var articleTitle = document.getElementById("editor-title").value;
|
var articleTitle = document.getElementById("editor-title").value;
|
||||||
var articleBody = document.getElementById("editor-content").value;
|
var articleBody = document.getElementById("editor-content").value;
|
||||||
|
@ -15,166 +77,8 @@ function onContentChange() {
|
||||||
document.getElementById("preview-citations").innerHTML = citeblockContent;
|
document.getElementById("preview-citations").innerHTML = citeblockContent;
|
||||||
document.getElementById("preview-control").innerHTML = controlContent;
|
document.getElementById("preview-control").innerHTML = controlContent;
|
||||||
}
|
}
|
||||||
|
}, 3000);
|
||||||
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, "<");
|
|
||||||
para = para.replace(/>/g, ">");
|
|
||||||
// Replace bold and italic marks with tags
|
|
||||||
para = para.replace(/\/\/([^\/]+)\/\//g, "<i>$1</i>");
|
|
||||||
para = para.replace(/\*\*([^*]+)\*\*/g, "<b>$1</b>");
|
|
||||||
// Replace \\LF with <br>LF
|
|
||||||
para = para.replace(/\\\\\n/g, "<br>\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) +
|
|
||||||
"<a href=\"#\">" +
|
|
||||||
citeText +
|
|
||||||
"</a>" +
|
|
||||||
"<sup>" +
|
|
||||||
formatId.toString() +
|
|
||||||
"</sup>" +
|
|
||||||
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 = "<hr><span class=\"signature\"><p>" + para.slice(1) + "</p></span>";
|
|
||||||
result.hasSignature = true;
|
|
||||||
} else {
|
|
||||||
para = "<p>" + para + "</p>\n";
|
|
||||||
}
|
|
||||||
result.html += para;
|
|
||||||
}
|
|
||||||
if (citationList.length > 0) {
|
|
||||||
content += "<p><i>The following articles will be cited:</i></p>\n";
|
|
||||||
for (var i = 0; i < citationList.length; i++) {
|
|
||||||
content += "<p>" + citationList[i] + "</p>\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 += "<p>Word count: " + wordCount + "</p>";
|
|
||||||
if (result.errors.length > 0) {
|
|
||||||
controlContent += "<p id=\"editor-errors\">";
|
|
||||||
for (var i = 0; i < result.errors.length; i++) {
|
|
||||||
controlContent += result.errors[i] + "<br>";
|
|
||||||
}
|
|
||||||
controlContent += "</p>";
|
|
||||||
}
|
|
||||||
if (result.warnings.length > 0) {
|
|
||||||
controlContent += "<p id=\"editor-warnings\">";
|
|
||||||
for (var i = 0; i < result.warnings.length; i++) {
|
|
||||||
controlContent += result.warnings[i] + "<br>";
|
|
||||||
}
|
|
||||||
controlContent += "</p>";
|
|
||||||
}
|
|
||||||
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();
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("beforeunload", function(e) {
|
window.addEventListener("beforeunload", function(e) {
|
||||||
var content = document.getElementById("editor-content").value
|
var content = document.getElementById("editor-content").value
|
||||||
|
|
|
@ -93,6 +93,7 @@ div.contentblock {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
width: auto;
|
width: auto;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
div.contentblock h3 {
|
div.contentblock h3 {
|
||||||
margin: 0.3em 0;
|
margin: 0.3em 0;
|
||||||
|
|
|
@ -4,7 +4,7 @@ from flask import (
|
||||||
Blueprint, render_template, url_for, redirect, g, flash, request, Markup)
|
Blueprint, render_template, url_for, redirect, g, flash, request, Markup)
|
||||||
from flask_login import login_required, current_user
|
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.config.loader import ReadOnlyOrderedDict
|
||||||
from amanuensis.lexicon.manage import valid_add, add_player, add_character
|
from amanuensis.lexicon.manage import valid_add, add_player, add_character
|
||||||
from amanuensis.server.forms import (
|
from amanuensis.server.forms import (
|
||||||
|
@ -14,6 +14,10 @@ from amanuensis.server.helpers import (
|
||||||
player_required_if_not_public)
|
player_required_if_not_public)
|
||||||
|
|
||||||
|
|
||||||
|
def jsonfmt(obj):
|
||||||
|
return Markup(json.dumps(obj))
|
||||||
|
|
||||||
|
|
||||||
def get_bp():
|
def get_bp():
|
||||||
"""Create a blueprint for lexicon pages"""
|
"""Create a blueprint for lexicon pages"""
|
||||||
bp = Blueprint('lexicon', __name__, url_prefix='/lexicon/<name>')
|
bp = Blueprint('lexicon', __name__, url_prefix='/lexicon/<name>')
|
||||||
|
@ -138,11 +142,98 @@ def get_bp():
|
||||||
@lexicon_param
|
@lexicon_param
|
||||||
@player_required
|
@player_required
|
||||||
def editor(name):
|
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(
|
return render_template(
|
||||||
'lexicon/editor.html',
|
'lexicon/editor.html',
|
||||||
current_turn=Markup(json.dumps(g.lexicon.turn.current)),
|
characters=characters,
|
||||||
citation=Markup(json.dumps(dict(g.lexicon.article.citation))),
|
articles=articles,
|
||||||
word_limit=Markup(json.dumps(dict(g.lexicon.article.word_limit))),
|
jsonfmt=jsonfmt)
|
||||||
addendum=Markup(json.dumps(dict(g.lexicon.article.citation))))
|
|
||||||
|
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',
|
||||||
|
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
|
return bp
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
{% set characters = g.lexicon.get_characters_for_player(current_user.id) %}
|
{% if character and not article %}
|
||||||
{% if not characters %}
|
{% set characters = [character] %}
|
||||||
{% set characters = [g.lexicon.character.default ] %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
@ -12,11 +11,17 @@
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='editor.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='editor.css') }}">
|
||||||
<script>
|
<script>
|
||||||
params = {
|
params = {
|
||||||
current_turn: {{ current_turn }},
|
updateURL: "{{ url_for('lexicon.editor_update', name=g.lexicon.name) }}",
|
||||||
default_signature: "{{ characters[0].signature }}",
|
{% if character %}
|
||||||
citation: {{ citation }},
|
character: {{ jsonfmt(character) }},
|
||||||
wordLimit: {{ word_limit }},
|
{% else %}
|
||||||
addendum: {{ addendum }},
|
character: null,
|
||||||
|
{% endif %}
|
||||||
|
{% if article %}
|
||||||
|
article: {{ jsonfmt(character) }},
|
||||||
|
{% else %}
|
||||||
|
article: null,
|
||||||
|
{% endif %}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<script type="text/javascript" src="{{ url_for('static', filename='editor.js') }}"></script>
|
<script type="text/javascript" src="{{ url_for('static', filename='editor.js') }}"></script>
|
||||||
|
@ -26,17 +31,51 @@
|
||||||
<div id="editor-left" class="column">
|
<div id="editor-left" class="column">
|
||||||
<div class="contentblock">
|
<div class="contentblock">
|
||||||
<div id="editor-header">
|
<div id="editor-header">
|
||||||
<select id="editor-character">
|
<a href="{{ url_for('lexicon.session', name=g.lexicon.name) }}">
|
||||||
|
{{ g.lexicon.title }}
|
||||||
|
</a>
|
||||||
|
<!-- <select id="editor-character">
|
||||||
{% for char in g.lexicon.get_characters_for_player(current_user.id) %}
|
{% for char in g.lexicon.get_characters_for_player(current_user.id) %}
|
||||||
<option value="{{ char.cid }}">{{ char.name }}</option>
|
<option value="{{ char.cid }}">{{ char.name }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select> -->
|
||||||
<span>
|
<span>
|
||||||
<b>{{ current_user.username }}</b>
|
<b>
|
||||||
|
{% if character %}
|
||||||
|
{{ character.name }} /
|
||||||
|
{% endif %}
|
||||||
|
{{ current_user.username }}
|
||||||
|
</b>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
{% for char in characters %}
|
||||||
|
<div id="editor-charselect">
|
||||||
|
<a href="{{ url_for('lexicon.editor', name=g.lexicon.name, cid=char.cid) }}">
|
||||||
|
<b>{{ char.name }}</b>
|
||||||
|
</a>
|
||||||
|
<ul>
|
||||||
|
{% for article in articles %}
|
||||||
|
{% if article.character == char.cid %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ url_for('lexicon.editor', name=g.lexicon.name, cid=char.cid, aid=article.aid) }}">
|
||||||
|
{{ article.title if article.title.strip() else "Untitled" }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ url_for('lexicon.editor_new', name=g.lexicon.name, cid=char.cid) }}">
|
||||||
|
New
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% if article %}
|
||||||
<input id="editor-title" placeholder="Title" oninput="onContentChange()">
|
<input id="editor-title" placeholder="Title" oninput="onContentChange()">
|
||||||
<textarea id="editor-content" class="fullwidth" oninput="onContentChange()"></textarea>
|
<textarea id="editor-content" class="fullwidth" oninput="onContentChange()">
|
||||||
|
</textarea>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="editor-right" class="column">
|
<div id="editor-right" class="column">
|
||||||
|
@ -44,10 +83,10 @@
|
||||||
<p>This editor requires Javascript to function.</p>
|
<p>This editor requires Javascript to function.</p>
|
||||||
</div>
|
</div>
|
||||||
<div id="preview-citations" class="contentblock">
|
<div id="preview-citations" class="contentblock">
|
||||||
<p>[1]</p>
|
<p> </p>
|
||||||
</div>
|
</div>
|
||||||
<div id="preview-control" class="contentblock">
|
<div id="preview-control" class="contentblock">
|
||||||
<p>Article length:</p>
|
<p> </p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -45,6 +45,11 @@
|
||||||
<a href="{{ url_for('lexicon.character', name=g.lexicon.name) }}">Create a character</a>
|
<a href="{{ url_for('lexicon.character', name=g.lexicon.name) }}">Create a character</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ url_for('lexicon.editor', name=g.lexicon.name) }}">
|
||||||
|
Article editor
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% set template_content_blocks = template_content_blocks + [self.main()] %}
|
{% set template_content_blocks = template_content_blocks + [self.main()] %}
|
Loading…
Reference in New Issue