Expose merge conflicts in the JavaScript editor. This fixes #23

This commit is contained in:
Magnus Hoff 2017-11-20 16:37:52 +01:00
parent b685139d5b
commit a09aa4b601
3 changed files with 71 additions and 30 deletions

View file

@ -78,15 +78,27 @@ function openEditor() {
// Update body: // Update body:
rendered.innerHTML = result.rendered; rendered.innerHTML = result.rendered;
if (result.conflict) {
form.elements.title.value = result.title;
form.elements.body.value = result.body;
}
// Update form: // Update form:
form.elements.base_revision.value = result.revision; form.elements.base_revision.value = result.revision;
for (const element of form.elements) { for (const element of form.elements) {
element.defaultValue = element.value; element.defaultValue = element.value;
} }
container.classList.remove('edit'); if (!result.conflict) {
container.classList.remove('edit');
}
textarea.disabled = false; 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 => { }).catch(err => {
textarea.disabled = false; textarea.disabled = false;
console.error(err); console.error(err);

View file

@ -133,9 +133,11 @@ impl Resource for ArticleResource {
#[derive(Serialize)] #[derive(Serialize)]
struct PutResponse<'a> { struct PutResponse<'a> {
conflict: bool,
slug: &'a str, slug: &'a str,
revision: i32, revision: i32,
title: &'a str, title: &'a str,
body: Option<&'a str>,
rendered: &'a str, rendered: &'a str,
last_updated: &'a str, last_updated: &'a str,
} }
@ -150,26 +152,54 @@ impl Resource for ArticleResource {
.and_then(move |update: UpdateArticle| { .and_then(move |update: UpdateArticle| {
self.state.update_article(self.article_id, update.base_revision, update.title, update.body, identity) self.state.update_article(self.article_id, update.base_revision, update.title, update.body, identity)
}) })
.and_then(|updated| { .and_then(|updated| match updated {
let updated = updated.unwrap(); UpdateResult::Success(updated) =>
futures::finished(Response::new() Ok(Response::new()
.with_status(hyper::StatusCode::Ok) .with_status(hyper::StatusCode::Ok)
.with_header(ContentType(APPLICATION_JSON.clone())) .with_header(ContentType(APPLICATION_JSON.clone()))
.with_body(serde_json::to_string(&PutResponse { .with_body(serde_json::to_string(&PutResponse {
slug: &updated.slug, conflict: false,
revision: updated.revision, slug: &updated.slug,
title: &updated.title, revision: updated.revision,
rendered: &Template {
title: &updated.title, title: &updated.title,
rendered: render_markdown(&updated.body), body: None,
}.to_string(), rendered: &Template {
last_updated: &last_updated( title: &updated.title,
updated.article_id, rendered: render_markdown(&updated.body),
&Local.from_utc_datetime(&updated.created), }.to_string(),
updated.author.as_ref().map(|x| &**x) last_updated: &last_updated(
), updated.article_id,
}).expect("Should never fail")) &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"))
)
}
}) })
) )
} }

View file

@ -58,16 +58,6 @@ pub enum UpdateResult {
RebaseConflict(RebaseConflict), 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<String, Error> { fn decide_slug(conn: &SqliteConnection, article_id: i32, prev_title: &str, title: &str, prev_slug: Option<&str>) -> Result<String, Error> {
let base_slug = ::slug::slugify(title); let base_slug = ::slug::slugify(title);
@ -485,6 +475,15 @@ mod test {
use super::*; use super::*;
use db; use db;
impl UpdateResult {
pub fn unwrap(self) -> models::ArticleRevision {
match self {
UpdateResult::Success(x) => x,
_ => panic!("Expected success")
}
}
}
macro_rules! init { macro_rules! init {
($state:ident) => { ($state:ident) => {
let db = db::test_connection(); let db = db::test_connection();