diff --git a/amanuensis/resources/editor.css b/amanuensis/resources/editor.css index 2ccbbaa..450fa08 100644 --- a/amanuensis/resources/editor.css +++ b/amanuensis/resources/editor.css @@ -1,76 +1,75 @@ html, body { - height: 100%; - margin: 0px; + height: 100%; + margin: 0px; } div#wrapper { - max-width: 1128px; - height: 100%; - display:flex; - flex-direction: row; - align-items: stretch; + height: 100%; + display: flex; + flex-direction: row; + align-items: stretch; } div.column { - width: 50%; + width: 50%; } div#editor-left { - display: flex; - align-items: stretch; + display: flex; + align-items: stretch; } div#editor-left section { - display: flex; - flex-direction: column; - margin: 10px 5px 10px 10px; - width: 100%; - padding: 5px; + display: flex; + flex-direction: column; + margin: 10px 5px 10px 10px; + width: 100%; + padding: 5px; } div#editor-left div#editor-header { - margin: 5px; - display: flex; - justify-content: space-between; + margin: 5px; + display: flex; + justify-content: space-between; } div#editor-left div#editor-charselect { - margin: 5px; + margin: 5px; } div#editor-left div#editor-buttons { - margin: 5px; + margin: 5px; } div#editor-left input#editor-title { - font-size: 2em; - margin: 5px; + font-size: 2em; + margin: 5px; } textarea#editor-content { - margin: 5px; - resize: none; - flex-grow: 1; - width: initial; + margin: 5px; + resize: none; + flex-grow: 1; + width: initial; } div#editor-right { - overflow-y: scroll; + overflow-y: scroll; } div#editor-right section { - margin: 10px 5px 10px 10px; + margin: 10px 5px 10px 10px; } span.message-warning { - color: #808000; + color: #808000; } span.message-error { - color: #ff0000; + color: #ff0000; } @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; - } + 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; + } } diff --git a/amanuensis/resources/editor.js b/amanuensis/resources/editor.js index ef663bc..fcdbcf2 100644 --- a/amanuensis/resources/editor.js +++ b/amanuensis/resources/editor.js @@ -1,126 +1,152 @@ -// Reduce unnecessary requests by checking for no further changes being made -// before updating in response to a change. -var nonce = 0; +(function(){ + /** Article submission state. */ + const ArticleState = { + DRAFT: 0, + SUBMITTED: 1, + APPROVED: 2 + }; -function ifNoFurtherChanges(callback, timeout=2000) { - var nonce_local = Math.random(); - nonce = nonce_local; - setTimeout(() => { - if (nonce == nonce_local) { - callback(); - nonce = 0; - } - }, timeout); -} + /** Article state to be tracked in addition to the editable content. */ + var article = { + state: undefined, + ersatz: false + }; -// Read data out of params and initialize editor -window.onload = function() { - // Kill noscript message first - document.getElementById("preview").innerHTML = "
"; + /** Article content as last received from the server. */ + var preview = { + title: undefined, + rendered: undefined, + citations: [], + errors: [] + } - if (document.body.contains(document.getElementById("editor-content"))) { - onContentChange(0); - } -}; + /** The nonce of the last-made update request. */ + let nonce = 0; -function buildArticleObject() { - var title = document.getElementById("editor-title").value; - var contents = document.getElementById("editor-content").value; - return { - aid: params.article.aid, - title: title, - status: params.article.status, - contents: contents - }; -} + /** + * Update request debounce wrapper that executes the callback if no further + * calls are made during the timeout period. If a new call is made, any + * previous calls are skipped. + */ + function ifNoFurtherChanges(callback, timeout) + { + // Stake a claim on the nonce, potentially overwriting a previous + // nonce value. + const nonce_local = 1 + Math.random(); + nonce = nonce_local; + // Wait to see if this call is overwritten in turn. + setTimeout(() => { + if (nonce == nonce_local) + { + callback(); + nonce = 0; + } + }, timeout); + } -function update(article) { - var req = new XMLHttpRequest(); - req.open("POST", params.updateURL, true); - req.setRequestHeader("Content-type", "application/json"); - req.responseType = "json"; - req.onreadystatechange = function () { - if (req.readyState == 4 && req.status == 200) { - // Update internal state with the returned article object - params.status = req.response.status; - params.errors = req.response.error.length; - document.getElementById("editor-title").value = req.response.title; - // Set editor editability based on article status - updateEditorStatus(); - // Update the preview with the parse information - updatePreview(req.response); - } - }; - var payload = { article: article }; - req.send(JSON.stringify(payload)); -} + /** Update the editor controls and preview to match the current state. */ + function refreshEditor() + { + // Enable or disable controls + const isEditable = article.state == ArticleState.DRAFT; + const blocked = preview.errors.filter(err => err.severity == 2).length > 0; + document.getElementById("editor-title").disabled = !isEditable; + document.getElementById("editor-content").disabled = !isEditable; + document.getElementById("button-submit").innerText = isEditable ? "Submit article" : "Edit article"; + document.getElementById("button-submit").disabled = blocked; -function updateEditorStatus() { - var ready = !!params.status.ready || !!params.status.approved; - document.getElementById("editor-title").disabled = ready; - document.getElementById("editor-content").disabled = ready; - var hasErrors = params.errors > 0; - var submitButton = document.getElementById("button-submit"); - submitButton.innerText = ready ? "Edit article" : "Submit article"; - submitButton.disabled = hasErrors; -} + // Update the preview + const previewHtml = "
Loading...
"; + document.getElementById("preview-citations").innerHTML = "Loading...
"; + document.getElementById("preview-control").innerHTML = "Loading...
"; + + // Hook up the controls + document.getElementById("button-submit").onclick = submitArticle; + document.getElementById("editor-title").oninput = onContentChange; + document.getElementById("editor-content").oninput = onContentChange; + window.addEventListener("beforeunload", e => + { + if (nonce > 0) + { + e.returnValue = "Are you sure?"; + } + }); + window.addEventListener("keydown", e => + { + if (e.ctrlKey && e.key == 's') + { + e.preventDefault(); + onContentChange(e, 0); + } + }); + + // Get the article status information. + const updateUrl = document.body.dataset.amanuensisUpdateUrl; + fetch(updateUrl).then(response => response.json()).then(updateState); + } + window.onload = initializeEditor; +})(); diff --git a/amanuensis/server/lexicon.jinja b/amanuensis/server/lexicon.jinja index 1c332bc..333f8f4 100644 --- a/amanuensis/server/lexicon.jinja +++ b/amanuensis/server/lexicon.jinja @@ -15,6 +15,9 @@ {% if current_page == "contents" %}class="current-page" {% else %}href="{{ url_for('lexicon.contents', lexicon_name=g.lexicon.name) }}" {% endif %}>Contents{% endblock %} +{% block sb_editor %}Editor{% endblock %} {% block sb_posts %}") +@lexicon_param +@player_required +def open(lexicon_name, article_id): + db: DbContext = g.db + article: Article = db( + select(Article).where(Article.public_id == article_id) + ).scalar_one() + return render_template( + "session.editor.jinja", + lexicon_name=lexicon_name, + article=article, + ) + + +@bp.get("/This editor requires Javascript to function.
--
- - - + +
This editor requires Javascript to function.
++
+ +
{{ article.title }} - {{ article.public_id }}
+{{ article.body }}
{% endfor %}