mirror of
https://github.com/Nutomic/ibis.git
synced 2024-12-25 17:51:23 +00:00
Implement fork article in frontend
This commit is contained in:
parent
911fadb94b
commit
da2c382cb0
17 changed files with 107 additions and 35 deletions
|
@ -49,7 +49,7 @@ pub(in crate::backend::api) async fn create_article(
|
|||
instance_id: local_instance.id,
|
||||
local: true,
|
||||
};
|
||||
let article = DbArticle::create(&form, &data.db_connection)?;
|
||||
let article = DbArticle::create(form, &data.db_connection)?;
|
||||
|
||||
let edit_data = EditArticleData {
|
||||
article_id: article.id,
|
||||
|
@ -178,16 +178,16 @@ pub(in crate::backend::api) async fn fork_article(
|
|||
"http://{}:{}/article/{}",
|
||||
local_instance.ap_id.inner().domain().unwrap(),
|
||||
local_instance.ap_id.inner().port().unwrap(),
|
||||
original_article.title
|
||||
&fork_form.new_title
|
||||
))?;
|
||||
let form = DbArticleForm {
|
||||
title: original_article.title.clone(),
|
||||
title: fork_form.new_title,
|
||||
text: original_article.text.clone(),
|
||||
ap_id,
|
||||
instance_id: local_instance.id,
|
||||
local: true,
|
||||
};
|
||||
let article = DbArticle::create(&form, &data.db_connection)?;
|
||||
let article = DbArticle::create(form, &data.db_connection)?;
|
||||
|
||||
// copy edits to new article
|
||||
// this could also be done in sql
|
||||
|
|
|
@ -32,20 +32,22 @@ impl DbArticle {
|
|||
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();
|
||||
Ok(insert_into(article::table)
|
||||
.values(form)
|
||||
.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();
|
||||
Ok(insert_into(article::table)
|
||||
.values(form)
|
||||
.values(&form)
|
||||
.on_conflict(article::dsl::ap_id)
|
||||
.do_update()
|
||||
.set(form)
|
||||
.set(&form)
|
||||
.get_result(conn.deref_mut())?)
|
||||
}
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@ impl Object for DbArticle {
|
|||
local: false,
|
||||
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?;
|
||||
|
||||
|
|
|
@ -144,7 +144,7 @@ async fn setup(data: &Data<IbisData>) -> Result<(), Error> {
|
|||
instance_id: instance.id,
|
||||
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
|
||||
submit_article_update(
|
||||
MAIN_PAGE_DEFAULT_TEXT.to_string(),
|
||||
|
|
|
@ -192,10 +192,8 @@ pub struct EditArticleData {
|
|||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
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 new_title: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::common::LocalUserView;
|
||||
use crate::frontend::api::ApiClient;
|
||||
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::edit::EditArticle;
|
||||
use crate::frontend::pages::article::history::ArticleHistory;
|
||||
|
@ -92,8 +93,9 @@ pub fn App() -> impl IntoView {
|
|||
<Routes>
|
||||
<Route path="/" 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/edit" view=EditArticle/>
|
||||
<Route path="/article/:title/actions" view=ArticleActions/>
|
||||
<Route path="/article/:title/diff/:hash" view=EditDiff/>
|
||||
<Route path="/article/create" view=CreateArticle/>
|
||||
<Route path="/article/list" view=ListArticles/>
|
||||
|
|
|
@ -12,6 +12,7 @@ pub fn ArticleNav(article: Resource<Option<String>, ArticleView>) -> impl IntoVi
|
|||
<Suspense>
|
||||
{move || article.get().map(|article| {
|
||||
let article_link = article_link(&article.article);
|
||||
let article_link_ = article_link.clone();
|
||||
view!{
|
||||
<nav class="inner">
|
||||
<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>
|
||||
</Show>
|
||||
<Show when=move || global_state.with(|state| state.my_profile.is_some())>
|
||||
<A href={format!("{article_link_}/actions")}>"Actions"</A>
|
||||
</Show>
|
||||
</nav>
|
||||
}})}
|
||||
</Suspense>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use crate::common::utils::extract_domain;
|
||||
use crate::common::{DbArticle, DbPerson};
|
||||
use leptos::IntoAttribute;
|
||||
use leptos::{view, IntoView};
|
||||
use leptos::*;
|
||||
|
||||
pub mod api;
|
||||
pub mod app;
|
||||
|
|
73
src/frontend/pages/article/actions.rs
Normal file
73
src/frontend/pages/article/actions.rs
Normal 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(¶ms).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>
|
||||
}
|
||||
}
|
|
@ -4,13 +4,10 @@ use crate::frontend::article_title;
|
|||
use crate::frontend::components::article_nav::ArticleNav;
|
||||
use crate::frontend::pages::article_resource;
|
||||
use leptos::*;
|
||||
use leptos_router::use_params_map;
|
||||
|
||||
#[component]
|
||||
pub fn EditArticle() -> impl IntoView {
|
||||
let params = use_params_map();
|
||||
let title = move || params.get().get("title").cloned();
|
||||
let article = article_resource(title);
|
||||
let article = article_resource();
|
||||
|
||||
let (text, set_text) = create_signal(String::new());
|
||||
let (summary, set_summary) = create_signal(String::new());
|
||||
|
|
|
@ -2,13 +2,10 @@ use crate::frontend::components::article_nav::ArticleNav;
|
|||
use crate::frontend::pages::article_resource;
|
||||
use crate::frontend::{article_title, user_link};
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
|
||||
#[component]
|
||||
pub fn ArticleHistory() -> impl IntoView {
|
||||
let params = use_params_map();
|
||||
let title = move || params.get().get("title").cloned();
|
||||
let article = article_resource(title);
|
||||
let article = article_resource();
|
||||
|
||||
view! {
|
||||
<ArticleNav article=article/>
|
||||
|
|
|
@ -6,7 +6,7 @@ use web_sys::wasm_bindgen::JsCast;
|
|||
|
||||
#[component]
|
||||
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(
|
||||
move || only_local.get(),
|
||||
|only_local| async move {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub mod actions;
|
||||
pub mod create;
|
||||
pub mod edit;
|
||||
pub mod history;
|
||||
|
|
|
@ -2,14 +2,12 @@ use crate::frontend::article_title;
|
|||
use crate::frontend::components::article_nav::ArticleNav;
|
||||
use crate::frontend::pages::article_resource;
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
|
||||
use markdown_it::MarkdownIt;
|
||||
|
||||
#[component]
|
||||
pub fn ReadArticle() -> impl IntoView {
|
||||
let params = use_params_map();
|
||||
let title = move || params.get().get("title").cloned();
|
||||
let article = article_resource(title);
|
||||
let article = article_resource();
|
||||
|
||||
view! {
|
||||
<ArticleNav article=article/>
|
||||
|
|
|
@ -7,8 +7,7 @@ use leptos_router::*;
|
|||
#[component]
|
||||
pub fn EditDiff() -> impl IntoView {
|
||||
let params = use_params_map();
|
||||
let title = move || params.get().get("title").cloned();
|
||||
let article = article_resource(title);
|
||||
let article = article_resource();
|
||||
|
||||
view! {
|
||||
<ArticleNav article=article/>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::common::{ArticleView, GetArticleData, MAIN_PAGE_NAME};
|
||||
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 diff;
|
||||
|
@ -10,9 +11,9 @@ pub(crate) mod register;
|
|||
pub(crate) mod search;
|
||||
pub(crate) mod user_profile;
|
||||
|
||||
fn article_resource(
|
||||
title: impl Fn() -> Option<String> + 'static,
|
||||
) -> Resource<Option<String>, ArticleView> {
|
||||
fn article_resource() -> Resource<Option<String>, ArticleView> {
|
||||
let params = use_params_map();
|
||||
let title = move || params.get().get("title").cloned();
|
||||
create_resource(title, move |title| async move {
|
||||
let mut title = title.unwrap_or(MAIN_PAGE_NAME.to_string());
|
||||
let mut domain = None;
|
||||
|
|
|
@ -525,6 +525,7 @@ async fn test_fork_article() -> MyResult<()> {
|
|||
// fork the article to local instance
|
||||
let fork_form = ForkArticleData {
|
||||
article_id: resolved_article.id,
|
||||
new_title: resolved_article.title.clone(),
|
||||
};
|
||||
let fork_res = data.beta.fork_article(&fork_form).await?;
|
||||
let forked_article = fork_res.article;
|
||||
|
|
Loading…
Reference in a new issue