diff --git a/assets/script.js b/assets/script.js index 25e8c66..3620ffa 100644 --- a/assets/script.js +++ b/assets/script.js @@ -78,15 +78,27 @@ function openEditor() { // Update body: rendered.innerHTML = result.rendered; + if (result.conflict) { + form.elements.title.value = result.title; + form.elements.body.value = result.body; + } + // Update form: form.elements.base_revision.value = result.revision; for (const element of form.elements) { element.defaultValue = element.value; } - container.classList.remove('edit'); + if (!result.conflict) { + container.classList.remove('edit'); + } textarea.disabled = false; + + if (result.conflict) { + alert("Your edit came into conflict with another change and has not been saved.\n" + + "Please resolve the merge conflict and save again."); + } }).catch(err => { textarea.disabled = false; console.error(err); diff --git a/src/resources/article_resource.rs b/src/resources/article_resource.rs index 464eb5d..cdda16a 100644 --- a/src/resources/article_resource.rs +++ b/src/resources/article_resource.rs @@ -133,9 +133,11 @@ impl Resource for ArticleResource { #[derive(Serialize)] struct PutResponse<'a> { + conflict: bool, slug: &'a str, revision: i32, title: &'a str, + body: Option<&'a str>, rendered: &'a str, last_updated: &'a str, } @@ -150,26 +152,54 @@ impl Resource for ArticleResource { .and_then(move |update: UpdateArticle| { self.state.update_article(self.article_id, update.base_revision, update.title, update.body, identity) }) - .and_then(|updated| { - let updated = updated.unwrap(); - 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, - title: &updated.title, - rendered: &Template { + .and_then(|updated| match updated { + UpdateResult::Success(updated) => + Ok(Response::new() + .with_status(hyper::StatusCode::Ok) + .with_header(ContentType(APPLICATION_JSON.clone())) + .with_body(serde_json::to_string(&PutResponse { + conflict: false, + slug: &updated.slug, + revision: updated.revision, title: &updated.title, - rendered: render_markdown(&updated.body), - }.to_string(), - last_updated: &last_updated( - updated.article_id, - &Local.from_utc_datetime(&updated.created), - updated.author.as_ref().map(|x| &**x) - ), - }).expect("Should never fail")) - ) + body: None, + rendered: &Template { + title: &updated.title, + rendered: render_markdown(&updated.body), + }.to_string(), + last_updated: &last_updated( + updated.article_id, + &Local.from_utc_datetime(&updated.created), + updated.author.as_ref().map(|x| &**x) + ), + }).expect("Should never fail")) + ), + UpdateResult::RebaseConflict(RebaseConflict { + base_article, title, body + }) => { + let title = title.flatten(); + let body = body.flatten(); + Ok(Response::new() + .with_status(hyper::StatusCode::Ok) + .with_header(ContentType(APPLICATION_JSON.clone())) + .with_body(serde_json::to_string(&PutResponse { + conflict: true, + slug: &base_article.slug, + revision: base_article.revision, + title: &title, + body: Some(&body), + rendered: &Template { + title: &title, + rendered: render_markdown(&body), + }.to_string(), + last_updated: &last_updated( + base_article.article_id, + &Local.from_utc_datetime(&base_article.created), + base_article.author.as_ref().map(|x| &**x) + ), + }).expect("Should never fail")) + ) + } }) ) } diff --git a/src/state.rs b/src/state.rs index d78f206..d25c1c2 100644 --- a/src/state.rs +++ b/src/state.rs @@ -58,16 +58,6 @@ pub enum UpdateResult { RebaseConflict(RebaseConflict), } -impl UpdateResult { - // TODO Move to mod tests below - pub fn unwrap(self) -> models::ArticleRevision { - match self { - UpdateResult::Success(x) => x, - _ => panic!("Expected success") - } - } -} - fn decide_slug(conn: &SqliteConnection, article_id: i32, prev_title: &str, title: &str, prev_slug: Option<&str>) -> Result { let base_slug = ::slug::slugify(title); @@ -485,6 +475,15 @@ mod test { use super::*; use db; + impl UpdateResult { + pub fn unwrap(self) -> models::ArticleRevision { + match self { + UpdateResult::Success(x) => x, + _ => panic!("Expected success") + } + } + } + macro_rules! init { ($state:ident) => { let db = db::test_connection();