1
0
Fork 0
mirror of https://github.com/Nutomic/ibis.git synced 2024-11-28 23:11:09 +00:00

Implement fork article in frontend

This commit is contained in:
Felix Ableitner 2024-02-14 12:22:24 +01:00
parent 911fadb94b
commit da2c382cb0
17 changed files with 107 additions and 35 deletions

View file

@ -49,7 +49,7 @@ pub(in crate::backend::api) async fn create_article(
instance_id: local_instance.id, instance_id: local_instance.id,
local: true, local: true,
}; };
let article = DbArticle::create(&form, &data.db_connection)?; let article = DbArticle::create(form, &data.db_connection)?;
let edit_data = EditArticleData { let edit_data = EditArticleData {
article_id: article.id, article_id: article.id,
@ -178,16 +178,16 @@ pub(in crate::backend::api) async fn fork_article(
"http://{}:{}/article/{}", "http://{}:{}/article/{}",
local_instance.ap_id.inner().domain().unwrap(), local_instance.ap_id.inner().domain().unwrap(),
local_instance.ap_id.inner().port().unwrap(), local_instance.ap_id.inner().port().unwrap(),
original_article.title &fork_form.new_title
))?; ))?;
let form = DbArticleForm { let form = DbArticleForm {
title: original_article.title.clone(), title: fork_form.new_title,
text: original_article.text.clone(), text: original_article.text.clone(),
ap_id, ap_id,
instance_id: local_instance.id, instance_id: local_instance.id,
local: true, local: true,
}; };
let article = DbArticle::create(&form, &data.db_connection)?; let article = DbArticle::create(form, &data.db_connection)?;
// copy edits to new article // copy edits to new article
// this could also be done in sql // this could also be done in sql

View file

@ -32,20 +32,22 @@ impl DbArticle {
Ok(CollectionId::parse(&format!("{}/edits", self.ap_id))?) Ok(CollectionId::parse(&format!("{}/edits", self.ap_id))?)
} }
pub fn create(form: &DbArticleForm, conn: &Mutex<PgConnection>) -> MyResult<Self> { pub fn create(mut form: DbArticleForm, conn: &Mutex<PgConnection>) -> MyResult<Self> {
form.title = form.title.replace(' ', "_");
let mut conn = conn.lock().unwrap(); let mut conn = conn.lock().unwrap();
Ok(insert_into(article::table) Ok(insert_into(article::table)
.values(form) .values(form)
.get_result(conn.deref_mut())?) .get_result(conn.deref_mut())?)
} }
pub fn create_or_update(form: &DbArticleForm, conn: &Mutex<PgConnection>) -> MyResult<Self> { pub fn create_or_update(mut form: DbArticleForm, conn: &Mutex<PgConnection>) -> MyResult<Self> {
form.title = form.title.replace(' ', "_");
let mut conn = conn.lock().unwrap(); let mut conn = conn.lock().unwrap();
Ok(insert_into(article::table) Ok(insert_into(article::table)
.values(form) .values(&form)
.on_conflict(article::dsl::ap_id) .on_conflict(article::dsl::ap_id)
.do_update() .do_update()
.set(form) .set(&form)
.get_result(conn.deref_mut())?) .get_result(conn.deref_mut())?)
} }

View file

@ -77,7 +77,7 @@ impl Object for DbArticle {
local: false, local: false,
instance_id: instance.id, instance_id: instance.id,
}; };
let article = DbArticle::create_or_update(&form, &data.db_connection)?; let article = DbArticle::create_or_update(form, &data.db_connection)?;
json.edits.dereference(&article, data).await?; json.edits.dereference(&article, data).await?;

View file

@ -144,7 +144,7 @@ async fn setup(data: &Data<IbisData>) -> Result<(), Error> {
instance_id: instance.id, instance_id: instance.id,
local: true, local: true,
}; };
let article = DbArticle::create(&form, &data.db_connection)?; let article = DbArticle::create(form, &data.db_connection)?;
// also create an article so its included in most recently edited list // also create an article so its included in most recently edited list
submit_article_update( submit_article_update(
MAIN_PAGE_DEFAULT_TEXT.to_string(), MAIN_PAGE_DEFAULT_TEXT.to_string(),

View file

@ -192,10 +192,8 @@ pub struct EditArticleData {
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub struct ForkArticleData { pub struct ForkArticleData {
// TODO: could add optional param new_title so there is no problem with title collision
// in case local article with same title exists. however that makes it harder to discover
// variants of same article.
pub article_id: i32, pub article_id: i32,
pub new_title: String,
} }
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]

View file

@ -1,6 +1,7 @@
use crate::common::LocalUserView; use crate::common::LocalUserView;
use crate::frontend::api::ApiClient; use crate::frontend::api::ApiClient;
use crate::frontend::components::nav::Nav; use crate::frontend::components::nav::Nav;
use crate::frontend::pages::article::actions::ArticleActions;
use crate::frontend::pages::article::create::CreateArticle; use crate::frontend::pages::article::create::CreateArticle;
use crate::frontend::pages::article::edit::EditArticle; use crate::frontend::pages::article::edit::EditArticle;
use crate::frontend::pages::article::history::ArticleHistory; use crate::frontend::pages::article::history::ArticleHistory;
@ -92,8 +93,9 @@ pub fn App() -> impl IntoView {
<Routes> <Routes>
<Route path="/" view=ReadArticle/> <Route path="/" view=ReadArticle/>
<Route path="/article/:title" view=ReadArticle/> <Route path="/article/:title" view=ReadArticle/>
<Route path="/article/:title/edit" view=EditArticle/>
<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/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/>
<Route path="/article/list" view=ListArticles/> <Route path="/article/list" view=ListArticles/>

View file

@ -12,6 +12,7 @@ pub fn ArticleNav(article: Resource<Option<String>, ArticleView>) -> impl IntoVi
<Suspense> <Suspense>
{move || article.get().map(|article| { {move || article.get().map(|article| {
let article_link = article_link(&article.article); let article_link = article_link(&article.article);
let article_link_ = article_link.clone();
view!{ view!{
<nav class="inner"> <nav class="inner">
<A href=article_link.clone()>"Read"</A> <A href=article_link.clone()>"Read"</A>
@ -22,6 +23,9 @@ pub fn ArticleNav(article: Resource<Option<String>, ArticleView>) -> impl IntoVi
})> })>
<A href={format!("{article_link}/edit")}>"Edit"</A> <A href={format!("{article_link}/edit")}>"Edit"</A>
</Show> </Show>
<Show when=move || global_state.with(|state| state.my_profile.is_some())>
<A href={format!("{article_link_}/actions")}>"Actions"</A>
</Show>
</nav> </nav>
}})} }})}
</Suspense> </Suspense>

View file

@ -1,7 +1,6 @@
use crate::common::utils::extract_domain; use crate::common::utils::extract_domain;
use crate::common::{DbArticle, DbPerson}; use crate::common::{DbArticle, DbPerson};
use leptos::IntoAttribute; use leptos::*;
use leptos::{view, IntoView};
pub mod api; pub mod api;
pub mod app; pub mod app;

View file

@ -0,0 +1,73 @@
use crate::common::ForkArticleData;
use crate::frontend::app::GlobalState;
use crate::frontend::article_link;
use crate::frontend::article_title;
use crate::frontend::components::article_nav::ArticleNav;
use crate::frontend::pages::article_resource;
use crate::frontend::DbArticle;
use leptos::*;
use leptos_router::Redirect;
#[component]
pub fn ArticleActions() -> impl IntoView {
let article = article_resource();
let (new_title, set_new_title) = create_signal(String::new());
let (fork_response, set_fork_response) = create_signal(Option::<DbArticle>::None);
let (error, set_error) = create_signal(None::<String>);
let fork_action = create_action(move |(article_id, new_title): &(i32, String)| {
let params = ForkArticleData {
article_id: *article_id,
new_title: new_title.to_string(),
};
async move {
set_error.update(|e| *e = None);
let result = GlobalState::api_client().fork_article(&params).await;
match result {
Ok(res) => set_fork_response.set(Some(res.article)),
Err(err) => {
set_error.update(|e| *e = Some(err.0.to_string()));
}
}
}
});
// TODO: show fork article option (with option to set different title). after forking do redirect
view! {
<ArticleNav article=article/>
<Suspense fallback=|| view! { "Loading..." }> {
move || article.get().map(|article|
view! {
<div class="item-view">
<h1>{article_title(&article.article)}</h1>
{move || {
error
.get()
.map(|err| {
view! { <p style="color:red;">{err}</p> }
})
}}
<Show when=move || !article.article.local>
<input
placeholder="New Title"
on:keyup=move |ev: ev::KeyboardEvent| {
let val = event_target_value(&ev);
set_new_title.update(|v| *v = val);
} />
<button
disabled=move || new_title.get().is_empty()
on:click=move |_| fork_action.dispatch((article.article.id, new_title.get()))>Fork Article</button>
<p>
"You can fork a remote article to the local instance. This is useful if the original
instance is dead, or if there are disagreements how the article should be written."
</p>
</Show>
</div>
})
}
</Suspense>
<Show when=move || fork_response.get().is_some()>
<Redirect path={article_link(&fork_response.get().unwrap())}/>
</Show>
<p>"TODO: add option for admin to delete article etc"</p>
}
}

View file

@ -4,13 +4,10 @@ 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;
#[component] #[component]
pub fn EditArticle() -> impl IntoView { pub fn EditArticle() -> impl IntoView {
let params = use_params_map(); let article = article_resource();
let title = move || params.get().get("title").cloned();
let article = article_resource(title);
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());

View file

@ -2,13 +2,10 @@ use crate::frontend::components::article_nav::ArticleNav;
use crate::frontend::pages::article_resource; use crate::frontend::pages::article_resource;
use crate::frontend::{article_title, user_link}; use crate::frontend::{article_title, user_link};
use leptos::*; use leptos::*;
use leptos_router::*;
#[component] #[component]
pub fn ArticleHistory() -> impl IntoView { pub fn ArticleHistory() -> impl IntoView {
let params = use_params_map(); let article = article_resource();
let title = move || params.get().get("title").cloned();
let article = article_resource(title);
view! { view! {
<ArticleNav article=article/> <ArticleNav article=article/>

View file

@ -6,7 +6,7 @@ use web_sys::wasm_bindgen::JsCast;
#[component] #[component]
pub fn ListArticles() -> impl IntoView { pub fn ListArticles() -> impl IntoView {
let (only_local, set_only_local) = create_signal(true); let (only_local, set_only_local) = create_signal(false);
let articles = create_resource( let articles = create_resource(
move || only_local.get(), move || only_local.get(),
|only_local| async move { |only_local| async move {

View file

@ -1,3 +1,4 @@
pub mod actions;
pub mod create; pub mod create;
pub mod edit; pub mod edit;
pub mod history; pub mod history;

View file

@ -2,14 +2,12 @@ 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 markdown_it::MarkdownIt; use markdown_it::MarkdownIt;
#[component] #[component]
pub fn ReadArticle() -> impl IntoView { pub fn ReadArticle() -> impl IntoView {
let params = use_params_map(); let article = article_resource();
let title = move || params.get().get("title").cloned();
let article = article_resource(title);
view! { view! {
<ArticleNav article=article/> <ArticleNav article=article/>

View file

@ -7,8 +7,7 @@ use leptos_router::*;
#[component] #[component]
pub fn EditDiff() -> impl IntoView { pub fn EditDiff() -> impl IntoView {
let params = use_params_map(); let params = use_params_map();
let title = move || params.get().get("title").cloned(); let article = article_resource();
let article = article_resource(title);
view! { view! {
<ArticleNav article=article/> <ArticleNav article=article/>

View file

@ -1,6 +1,7 @@
use crate::common::{ArticleView, GetArticleData, MAIN_PAGE_NAME}; use crate::common::{ArticleView, GetArticleData, MAIN_PAGE_NAME};
use crate::frontend::app::GlobalState; use crate::frontend::app::GlobalState;
use leptos::{create_resource, Resource}; use leptos::{create_resource, Resource, SignalGet};
use leptos_router::use_params_map;
pub(crate) mod article; pub(crate) mod article;
pub(crate) mod diff; pub(crate) mod diff;
@ -10,9 +11,9 @@ pub(crate) mod register;
pub(crate) mod search; pub(crate) mod search;
pub(crate) mod user_profile; pub(crate) mod user_profile;
fn article_resource( fn article_resource() -> Resource<Option<String>, ArticleView> {
title: impl Fn() -> Option<String> + 'static, let params = use_params_map();
) -> Resource<Option<String>, ArticleView> { let title = move || params.get().get("title").cloned();
create_resource(title, move |title| async move { create_resource(title, move |title| async move {
let mut title = title.unwrap_or(MAIN_PAGE_NAME.to_string()); let mut title = title.unwrap_or(MAIN_PAGE_NAME.to_string());
let mut domain = None; let mut domain = None;

View file

@ -525,6 +525,7 @@ async fn test_fork_article() -> MyResult<()> {
// fork the article to local instance // fork the article to local instance
let fork_form = ForkArticleData { let fork_form = ForkArticleData {
article_id: resolved_article.id, article_id: resolved_article.id,
new_title: resolved_article.title.clone(),
}; };
let fork_res = data.beta.fork_article(&fork_form).await?; let fork_res = data.beta.fork_article(&fork_form).await?;
let forked_article = fork_res.article; let forked_article = fork_res.article;