Add basic editor with preview
This commit is contained in:
parent
c47208e384
commit
40e7a0c030
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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, "<");
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 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>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue