Add basic editor with preview

This commit is contained in:
Tim Van Baak 2020-02-06 13:05:11 -08:00
parent c47208e384
commit 40e7a0c030
4 changed files with 274 additions and 0 deletions

View File

@ -0,0 +1,64 @@
html, body {
height: 100%;
margin: 0px;
}
div#wrapper {
max-width: 1128px;
height: 100%;
display:flex;
flex-direction: row;
align-items: stretch;
}
div.column {
width: 50%;
}
div#editor-left {
display: flex;
align-items: stretch;
}
div#editor-left div.contentblock {
display: flex;
flex-direction: column;
margin: 10px 10px 10px 5px;
width: 100%;
padding: 5px;
}
div#editor-left div#editor-header {
margin: 5px;
display: flex;
justify-content: space-between;
}
div#editor-left input#editor-title {
font-size: 2em;
margin: 5px;
}
textarea#editor-content {
margin: 5px;
resize: none;
flex-grow: 1;
width: initial;
}
div#editor-right {
overflow-y: scroll;
}
div#editor-right div.contentblock {
margin: 10px 5px 10px 10px;
}
@media only screen and (max-width: 816px) {
div#wrapper {
max-width: 564px;
margin: 0 auto;
padding: inherit;
flex-direction: column;
}
div.column {
width: 100%;
}
div#editor-left {
height: 100%;
}
div#editor-right {
overflow-y: inherit;
}
}

View File

@ -0,0 +1,152 @@
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 citeTexts = []
for (var i = 0; i < parseResult.citations.length; i++) {
var cite = parseResult.citations[i];
citeTexts.push("[" + cite.id.toString() + "] " + cite.citeTitle);
}
var citeblockContent = citeTexts.join(" / ");
// Compute warnings and build the control block
var flagMissingSignature = !parseResult.hasSignature;
var wordCount = (parseResult.html
// Delete all HTML tags
.replace(/<[^>]+>/g, "")
.trim()
.split(/\s+/)
.length);
var controlContent = "";
controlContent += "<p>Signature: " + (!flagMissingSignature).toString() + "</p>";
controlContent += "<p>Word count: " + wordCount.toString() + "</p>";
// Fill in the content blocks
document.getElementById("preview").innerHTML = (
"<h1>" + articleTitle + "</h1>\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, "&lt;");
para = para.replace(/>/g, "&gt;");
// 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";
}
}
// Calculate approximate word count
// var wordCount = text.trim().split(/\s+/).length;
// if (text.trim().length < 1)
// wordCount = 0;
// content += "<p><i>Article length: approx. " + wordCount + " words</p>";
return result;
}
// 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.defaultSignature;
this.onContentChange();
};
window.addEventListener("beforeunload", function(e) {
var content = document.getElementById("editor-content").value
var hasText = content.length > 0 && content != "\n\n" + params.defaultSignature;
if (hasText) {
e.returnValue = "Are you sure?";
}
});
window.addEventListener("keydown", function(event) {
if (event.ctrlKey || event.metaKey)
{
if (String.fromCharCode(event.which).toLowerCase() == 's')
{
event.preventDefault();
document.getElementById("preview").innerHTML += "<p>wrong</p>";
}
}
});

View File

@ -134,4 +134,10 @@ def get_bp():
def stats(name): def stats(name):
return render_template('lexicon/statistics.html') return render_template('lexicon/statistics.html')
@bp.route('/session/editor/', methods=['GET'])
@lexicon_param
@player_required
def editor(name):
return render_template('lexicon/editor.html')
return bp return bp

View File

@ -0,0 +1,52 @@
{% set characters = g.lexicon.get_characters_for_player(current_user.id) %}
{% if not characters %}
{% set characters = [g.lexicon.character.default ] %}
{% endif %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='page.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='editor.css') }}">
<script>
params = {
currentTurn: "{{ g.lexicon.turn.current }}",
defaultSignature: "{{ characters[0].signature }}",
}
</script>
<script type="text/javascript" src="{{ url_for('static', filename='editor.js') }}"></script>
</head>
<body>
<div id="wrapper">
<div id="editor-left" class="column">
<div class="contentblock">
<div id="editor-header">
<select id="editor-character">
{% for char in g.lexicon.get_characters_for_player(current_user.id) %}
<option value="{{ char.cid }}">{{ char.name }}</option>
{% endfor %}
</select>
<span>
<b>{{ current_user.username }}</b>
</span>
</div>
<input id="editor-title" placeholder="Title" oninput="onContentChange()">
<textarea id="editor-content" class="fullwidth" oninput="onContentChange()"></textarea>
</div>
</div>
<div id="editor-right" class="column">
<div id="preview" class="contentblock">
<p>This editor requires Javascript to function.</p>
</div>
<div id="preview-citations" class="contentblock">
<p>[1]</p>
</div>
<div id="preview-control" class="contentblock">
<p>Article length:</p>
</div>
</div>
</div>
</body>
</html>