Implement support for editing page title

This commit is contained in:
Magnus Hoff 2017-09-21 10:09:57 +02:00
parent e82350e499
commit 3feed530ff
6 changed files with 82 additions and 29 deletions

View file

@ -15,9 +15,9 @@ function queryArgsFromForm(form) {
let hasBeenOpen = false;
function openEditor() {
const article = document.querySelector("article");
const rendered = article.querySelector(".rendered");
const editor = article.querySelector(".editor");
const container = document.querySelector(".container");
const rendered = container.querySelector(".rendered");
const editor = container.querySelector(".editor");
const textarea = editor.querySelector('textarea[name="body"]');
const shadow = editor.querySelector('textarea.shadow-control');
const form = editor.querySelector("form");
@ -29,7 +29,7 @@ function openEditor() {
textarea.style.height = rendered.clientHeight + "px";
article.classList.add('edit');
container.classList.add('edit');
autosizeTextarea(textarea, shadow);
@ -63,11 +63,23 @@ function openEditor() {
if (!response.ok) throw new Error("Unexpected status code (" + response.status + ")");
const result = await response.json();
form.elements.base_revision.value = result.revision;
// Update url-bar, page title and footer
window.history.replaceState(null, result.title, result.slug);
document.querySelector("title").textContent = result.title;
revision.textContent = result.revision;
lastUpdated.textContent = result.created;
// Update body:
rendered.innerHTML = result.rendered;
article.classList.remove('edit');
// Update form:
form.elements.base_revision.value = result.revision;
for (const element of form.elements) {
element.defaultValue = element.value;
}
container.classList.remove('edit');
textarea.disabled = false;
}()
@ -82,7 +94,7 @@ function openEditor() {
ev.preventDefault();
ev.stopPropagation();
article.classList.remove('edit');
container.classList.remove('edit');
form.reset();
});
}

View file

@ -91,7 +91,7 @@ body {
display: flex;
flex-direction: column;
}
article {
.container {
flex: 1;
}
@ -146,6 +146,15 @@ textarea {
overflow: hidden;
}
h1>input {
font: inherit;
border: none;
background: none;
padding: 0;
width: 100%;
}
.shadow-control {
visibility: hidden;
position: fixed;

View file

@ -86,6 +86,7 @@ impl Resource for ArticleResource {
revision: i32,
created: &'a chrono::DateTime<Local>,
slug: &'a str,
title: &'a str,
raw: &'a str,
rendered: String,
@ -106,6 +107,7 @@ impl Resource for ArticleResource {
article_id: data.article_id,
revision: data.revision,
created: &Local.from_utc_datetime(&data.created),
slug: &data.slug,
title: &data.title,
raw: &data.body,
rendered: render_markdown(&data.body),
@ -125,12 +127,22 @@ impl Resource for ArticleResource {
#[derive(Deserialize)]
struct UpdateArticle {
base_revision: i32,
title: String,
body: String,
}
#[derive(BartDisplay)]
#[template="templates/article_revision_contents.html"]
struct Template<'a> {
title: &'a str,
rendered: String,
}
#[derive(Serialize)]
struct PutResponse<'a> {
slug: &'a str,
revision: i32,
title: &'a str,
rendered: &'a str,
created: &'a str,
}
@ -143,15 +155,20 @@ impl Resource for ArticleResource {
.map_err(Into::into)
})
.and_then(move |update: UpdateArticle| {
self.state.update_article(self.article_id, update.base_revision, update.body)
self.state.update_article(self.article_id, update.base_revision, update.title, update.body)
})
.and_then(|updated| {
futures::finished(Response::new()
.with_status(hyper::StatusCode::Ok)
.with_header(ContentType(APPLICATION_JSON.clone()))
.with_body(serde_json::to_string(&PutResponse {
slug: &updated.slug,
revision: updated.revision,
rendered: &render_markdown(&updated.body),
title: &updated.title,
rendered: &Template {
title: &updated.title,
rendered: render_markdown(&updated.body),
}.to_string(),
created: &Local.from_utc_datetime(&updated.created).to_string(),
}).expect("Should never fail"))
)

View file

@ -26,7 +26,7 @@ pub enum SlugLookup {
Redirect(String),
}
fn decide_slug(conn: &SqliteConnection, prev_title: &str, title: &str, prev_slug: &str) -> Result<String, Error> {
fn decide_slug(conn: &SqliteConnection, article_id: i32, prev_title: &str, title: &str, prev_slug: &str) -> Result<String, Error> {
if title == prev_title {
return Ok(prev_slug.to_owned());
}
@ -44,6 +44,7 @@ fn decide_slug(conn: &SqliteConnection, prev_title: &str, title: &str, prev_slug
loop {
let slug_in_use = article_revisions::table
.filter(article_revisions::article_id.ne(article_id))
.filter(article_revisions::slug.eq(&slug))
.filter(article_revisions::latest.eq(true))
.count()
@ -124,7 +125,9 @@ impl State {
})
}
pub fn update_article(&self, article_id: i32, base_revision: i32, body: String) -> CpuFuture<models::ArticleRevision, Error> {
pub fn update_article(&self, article_id: i32, base_revision: i32, title: String, body: String)
-> CpuFuture<models::ArticleRevision, Error>
{
let connection_pool = self.connection_pool.clone();
self.cpu_pool.spawn_fn(move || {
@ -150,8 +153,7 @@ impl State {
}
let new_revision = base_revision + 1;
let title = prev_title.clone(); // TODO Have title be a parameter to this function
let slug = decide_slug(&*conn, &prev_title, &title, &prev_slug)?;
let slug = decide_slug(&*conn, article_id, &prev_title, &title, &prev_slug)?;
#[derive(Insertable)]
#[table_name="article_revisions"]

View file

@ -1,26 +1,32 @@
<script src="_assets/script-{{script_js_checksum}}.js" defer></script>
<div class="container">
<div class="rendered">
{{>article_revision_contents.html}}
</div>
<div class="editor">
<form action="" method="POST">
<header>
<h1>{{title}}</h1>
<h1><input autocomplete=off type=text name=title value="{{title}}"></h1>
</header>
<article>
<div class="rendered">
{{{rendered}}}
</div>
<div class="editor">
<form action="" method="POST">
<input autocomplete=off type=hidden name=base_revision value="{{revision}}">
<textarea autocomplete=off name=body>{{raw}}</textarea>
<textarea autocomplete=off class="shadow-control"></textarea>
<div class="editor-controls">
<a class="cancel" href="{{article_id}}">Cancel</a>
<button type=submit>Save</button>
</div>
</form>
</div>
<input autocomplete=off type=hidden name=base_revision value="{{revision}}">
<textarea autocomplete=off name=body>{{raw}}</textarea>
<textarea autocomplete=off class="shadow-control"></textarea>
</article>
<div class="editor-controls">
<a class="cancel" href="{{slug}}">Cancel</a>
<button type=submit>Save</button>
</div>
</form>
</div>
</div>
<footer>
<p><a id="openEditor" href="?editor">Edit</a></p>
<dl>

View file

@ -0,0 +1,7 @@
<header>
<h1>{{title}}</h1>
</header>
<article>
{{{rendered}}}
</article>