Also allow resolving federated edit conflicts

This commit is contained in:
Felix Ableitner 2024-02-14 16:44:48 +01:00
parent a15a42b977
commit e325de6352
7 changed files with 60 additions and 11 deletions

View File

@ -99,7 +99,7 @@ impl DbConflict {
hash: self.hash.clone(), hash: self.hash.clone(),
three_way_merge, three_way_merge,
summary: self.summary.clone(), summary: self.summary.clone(),
article_id: original_article.id, article: original_article.clone(),
previous_version_id: original_article previous_version_id: original_article
.latest_edit_version(&data.db_connection)?, .latest_edit_version(&data.db_connection)?,
})) }))

View File

@ -217,7 +217,7 @@ pub struct ApiConflict {
pub hash: EditVersion, pub hash: EditVersion,
pub three_way_merge: String, pub three_way_merge: String,
pub summary: String, pub summary: String,
pub article_id: i32, pub article: DbArticle,
pub previous_version_id: EditVersion, pub previous_version_id: EditVersion,
} }

View File

@ -7,6 +7,7 @@ use crate::frontend::pages::article::edit::EditArticle;
use crate::frontend::pages::article::history::ArticleHistory; use crate::frontend::pages::article::history::ArticleHistory;
use crate::frontend::pages::article::list::ListArticles; use crate::frontend::pages::article::list::ListArticles;
use crate::frontend::pages::article::read::ReadArticle; use crate::frontend::pages::article::read::ReadArticle;
use crate::frontend::pages::conflicts::Conflicts;
use crate::frontend::pages::diff::EditDiff; use crate::frontend::pages::diff::EditDiff;
use crate::frontend::pages::instance_details::InstanceDetails; use crate::frontend::pages::instance_details::InstanceDetails;
use crate::frontend::pages::login::Login; use crate::frontend::pages::login::Login;
@ -94,7 +95,7 @@ pub fn App() -> impl IntoView {
<Route path="/" view=ReadArticle/> <Route path="/" view=ReadArticle/>
<Route path="/article/:title" view=ReadArticle/> <Route path="/article/:title" view=ReadArticle/>
<Route path="/article/:title/history" view=ArticleHistory/> <Route path="/article/:title/history" view=ArticleHistory/>
<Route path="/article/:title/edit" view=EditArticle/> <Route path="/article/:title/edit/:conflict_id?" view=EditArticle/>
<Route path="/article/:title/actions" view=ArticleActions/> <Route path="/article/:title/actions" view=ArticleActions/>
<Route path="/article/:title/diff/:hash" view=EditDiff/> <Route path="/article/:title/diff/:hash" view=EditDiff/>
<Route path="/article/create" view=CreateArticle/> <Route path="/article/create" view=CreateArticle/>
@ -104,6 +105,7 @@ pub fn App() -> impl IntoView {
<Route path="/login" view=Login/> <Route path="/login" view=Login/>
<Route path="/register" view=Register/> <Route path="/register" view=Register/>
<Route path="/search" view=Search/> <Route path="/search" view=Search/>
<Route path="/conflicts" view=Conflicts/>
</Routes> </Routes>
</main> </main>
</Router> </Router>

View File

@ -35,6 +35,9 @@ pub fn Nav() -> impl IntoView {
<li> <li>
<A href="/article/create">"Create Article"</A> <A href="/article/create">"Create Article"</A>
</li> </li>
<li>
<A href="/conflicts">"Edit Conflicts"</A>
</li>
</Show> </Show>
<li> <li>
<form on:submit=move |ev| { <form on:submit=move |ev| {

View File

@ -4,6 +4,7 @@ use crate::frontend::article_title;
use crate::frontend::components::article_nav::ArticleNav; use crate::frontend::components::article_nav::ArticleNav;
use crate::frontend::pages::article_resource; use crate::frontend::pages::article_resource;
use leptos::*; use leptos::*;
use leptos_router::use_params_map;
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
enum EditResponse { enum EditResponse {
@ -12,14 +13,35 @@ enum EditResponse {
Conflict(ApiConflict), Conflict(ApiConflict),
} }
const CONFLICT_MESSAGE: &str = "There was an edit conflict. Resolve it manually and resubmit.";
#[component] #[component]
pub fn EditArticle() -> impl IntoView { pub fn EditArticle() -> impl IntoView {
let article = article_resource(); let article = article_resource();
let (edit_response, set_edit_response) = create_signal(EditResponse::None);
let (edit_error, set_edit_error) = create_signal(None::<String>);
let conflict_id = move || use_params_map().get().get("conflict_id").cloned();
if let Some(conflict_id) = conflict_id() {
create_action(move |conflict_id: &String| {
let conflict_id: i32 = conflict_id.parse().unwrap();
async move {
let conflict = GlobalState::api_client()
.get_conflicts()
.await
.unwrap()
.into_iter()
.find(|c| c.id == conflict_id)
.unwrap();
set_edit_response.set(EditResponse::Conflict(conflict));
set_edit_error.set(Some(CONFLICT_MESSAGE.to_string()));
}
})
.dispatch(conflict_id);
}
let (text, set_text) = create_signal(String::new()); let (text, set_text) = create_signal(String::new());
let (summary, set_summary) = create_signal(String::new()); let (summary, set_summary) = create_signal(String::new());
let (edit_response, set_edit_response) = create_signal(EditResponse::None);
let (edit_error, set_edit_error) = create_signal(None::<String>);
let (wait_for_response, set_wait_for_response) = create_signal(false); let (wait_for_response, set_wait_for_response) = create_signal(false);
let button_is_disabled = let button_is_disabled =
Signal::derive(move || wait_for_response.get() || summary.get().is_empty()); Signal::derive(move || wait_for_response.get() || summary.get().is_empty());
@ -58,12 +80,7 @@ pub fn EditArticle() -> impl IntoView {
match res { match res {
Ok(Some(conflict)) => { Ok(Some(conflict)) => {
set_edit_response.update(|v| *v = EditResponse::Conflict(conflict)); set_edit_response.update(|v| *v = EditResponse::Conflict(conflict));
set_edit_error.update(|e| { set_edit_error.set(Some(CONFLICT_MESSAGE.to_string()));
*e = Some(
"There was an edit conflict. Resolve it manually and resubmit."
.to_string(),
)
});
} }
Ok(None) => { Ok(None) => {
set_edit_response.update(|v| *v = EditResponse::Success); set_edit_response.update(|v| *v = EditResponse::Success);

View File

@ -0,0 +1,26 @@
use crate::frontend::app::GlobalState;
use crate::frontend::article_link;
use crate::frontend::article_title;
use leptos::*;
#[component]
pub fn Conflicts() -> impl IntoView {
let conflicts = create_resource(
move || {},
|_| async move { GlobalState::api_client().get_conflicts().await.unwrap() },
);
view! {
<h1>Your unresolved edit conflicts</h1>
<Suspense fallback=|| view! { "Loading..." }>
<ul> {
move || conflicts.get().map(|c|
c.into_iter().map(|c| {
let link = format!("{}/edit/{}", article_link(&c.article), c.id);
view! {
<li><a href=link>{article_title(&c.article)}" - "{c.summary}</a></li>
}}).collect::<Vec<_>>())
} </ul>
</Suspense>
}
}

View File

@ -4,6 +4,7 @@ use leptos::{create_resource, Resource, SignalGet};
use leptos_router::use_params_map; use leptos_router::use_params_map;
pub(crate) mod article; pub(crate) mod article;
pub(crate) mod conflicts;
pub(crate) mod diff; pub(crate) mod diff;
pub(crate) mod instance_details; pub(crate) mod instance_details;
pub(crate) mod login; pub(crate) mod login;