mirror of
https://github.com/Nutomic/ibis.git
synced 2024-11-22 07:31:09 +00:00
basic article editing and history
This commit is contained in:
parent
e0cc23c0bc
commit
8305680fda
25 changed files with 303 additions and 93 deletions
|
@ -49,13 +49,16 @@ create table edit (
|
|||
hash uuid not null,
|
||||
ap_id varchar(255) not null unique,
|
||||
diff text not null,
|
||||
summary text not null,
|
||||
article_id int REFERENCES article ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
previous_version_id uuid not null
|
||||
previous_version_id uuid not null,
|
||||
created timestamptz not null
|
||||
);
|
||||
|
||||
create table conflict (
|
||||
id uuid primary key,
|
||||
diff text not null,
|
||||
summary text not null,
|
||||
creator_id int REFERENCES local_user ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
article_id int REFERENCES article ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
previous_version_id uuid not null
|
||||
|
|
|
@ -20,6 +20,7 @@ use axum::Extension;
|
|||
use axum::Form;
|
||||
use axum::Json;
|
||||
use axum_macros::debug_handler;
|
||||
use chrono::Utc;
|
||||
use diffy::create_patch;
|
||||
|
||||
/// Create a new article with empty text, and federate it to followers.
|
||||
|
@ -70,11 +71,19 @@ pub(in crate::backend::api) async fn edit_article(
|
|||
DbConflict::delete(resolve_conflict_id, &data.db_connection)?;
|
||||
}
|
||||
let original_article = DbArticle::read_view(edit_form.article_id, &data.db_connection)?;
|
||||
dbg!(&edit_form.new_text, &original_article.article.text);
|
||||
if edit_form.new_text == original_article.article.text {
|
||||
return Err(anyhow!("Edit contains no changes").into());
|
||||
}
|
||||
if edit_form.summary.is_empty() {
|
||||
return Err(anyhow!("No summary given").into());
|
||||
}
|
||||
|
||||
if edit_form.previous_version_id == original_article.latest_version {
|
||||
// No intermediate changes, simply submit new version
|
||||
submit_article_update(
|
||||
edit_form.new_text.clone(),
|
||||
edit_form.summary.clone(),
|
||||
edit_form.previous_version_id,
|
||||
&original_article.article,
|
||||
user.person.id,
|
||||
|
@ -93,6 +102,7 @@ pub(in crate::backend::api) async fn edit_article(
|
|||
let form = DbConflictForm {
|
||||
id: EditVersion::new(&patch.to_string())?,
|
||||
diff: patch.to_string(),
|
||||
summary: edit_form.summary.clone(),
|
||||
creator_id: user.local_user.id,
|
||||
article_id: original_article.article.id,
|
||||
previous_version_id: previous_version.hash,
|
||||
|
@ -159,10 +169,12 @@ pub(in crate::backend::api) async fn fork_article(
|
|||
let form = DbEditForm {
|
||||
ap_id,
|
||||
diff: e.diff,
|
||||
summary: e.summary,
|
||||
creator_id: e.creator_id,
|
||||
article_id: article.id,
|
||||
hash: e.hash,
|
||||
previous_version_id: e.previous_version_id,
|
||||
created: Utc::now(),
|
||||
};
|
||||
DbEdit::create(&form, &data.db_connection)?;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ use std::sync::Mutex;
|
|||
pub struct DbConflict {
|
||||
pub id: EditVersion,
|
||||
pub diff: String,
|
||||
pub summary: String,
|
||||
pub creator_id: i32,
|
||||
pub article_id: i32,
|
||||
pub previous_version_id: EditVersion,
|
||||
|
@ -35,6 +36,7 @@ pub struct DbConflict {
|
|||
pub struct DbConflictForm {
|
||||
pub id: EditVersion,
|
||||
pub diff: String,
|
||||
pub summary: String,
|
||||
pub creator_id: i32,
|
||||
pub article_id: i32,
|
||||
pub previous_version_id: EditVersion,
|
||||
|
@ -82,6 +84,7 @@ impl DbConflict {
|
|||
// federate the change
|
||||
submit_article_update(
|
||||
new_text,
|
||||
self.summary.clone(),
|
||||
self.previous_version_id.clone(),
|
||||
&original_article,
|
||||
self.creator_id,
|
||||
|
|
|
@ -3,6 +3,7 @@ use crate::backend::error::MyResult;
|
|||
use crate::common::EditVersion;
|
||||
use crate::common::{DbArticle, DbEdit};
|
||||
use activitypub_federation::fetch::object_id::ObjectId;
|
||||
use chrono::{DateTime, Utc};
|
||||
use diesel::ExpressionMethods;
|
||||
use diesel::{insert_into, AsChangeset, Insertable, PgConnection, QueryDsl, RunQueryDsl};
|
||||
use diffy::create_patch;
|
||||
|
@ -16,8 +17,10 @@ pub struct DbEditForm {
|
|||
pub hash: EditVersion,
|
||||
pub ap_id: ObjectId<DbEdit>,
|
||||
pub diff: String,
|
||||
pub summary: String,
|
||||
pub article_id: i32,
|
||||
pub previous_version_id: EditVersion,
|
||||
pub created: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl DbEditForm {
|
||||
|
@ -25,6 +28,7 @@ impl DbEditForm {
|
|||
original_article: &DbArticle,
|
||||
creator_id: i32,
|
||||
updated_text: &str,
|
||||
summary: String,
|
||||
previous_version_id: EditVersion,
|
||||
) -> MyResult<Self> {
|
||||
let diff = create_patch(&original_article.text, updated_text);
|
||||
|
@ -37,6 +41,8 @@ impl DbEditForm {
|
|||
creator_id,
|
||||
article_id: original_article.id,
|
||||
previous_version_id,
|
||||
summary,
|
||||
created: Utc::now(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ diesel::table! {
|
|||
conflict (id) {
|
||||
id -> Uuid,
|
||||
diff -> Text,
|
||||
summary -> Text,
|
||||
creator_id -> Int4,
|
||||
article_id -> Int4,
|
||||
previous_version_id -> Uuid,
|
||||
|
@ -30,8 +31,10 @@ diesel::table! {
|
|||
#[max_length = 255]
|
||||
ap_id -> Varchar,
|
||||
diff -> Text,
|
||||
summary -> Text,
|
||||
article_id -> Int4,
|
||||
previous_version_id -> Uuid,
|
||||
created -> Timestamptz,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,13 +91,6 @@ diesel::table! {
|
|||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
secret (id) {
|
||||
id -> Int4,
|
||||
jwt_secret -> Varchar,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::joinable!(article -> instance (instance_id));
|
||||
diesel::joinable!(conflict -> article (article_id));
|
||||
diesel::joinable!(conflict -> local_user (creator_id));
|
||||
|
@ -113,5 +109,4 @@ diesel::allow_tables_to_appear_in_same_query!(
|
|||
jwt_secret,
|
||||
local_user,
|
||||
person,
|
||||
secret,
|
||||
);
|
||||
|
|
|
@ -7,6 +7,7 @@ use crate::common::DbInstance;
|
|||
use crate::common::EditVersion;
|
||||
use crate::common::{DbArticle, DbEdit};
|
||||
use activitypub_federation::config::Data;
|
||||
use chrono::Utc;
|
||||
|
||||
pub mod accept;
|
||||
pub mod create_article;
|
||||
|
@ -17,12 +18,19 @@ pub mod update_remote_article;
|
|||
|
||||
pub async fn submit_article_update(
|
||||
new_text: String,
|
||||
summary: String,
|
||||
previous_version: EditVersion,
|
||||
original_article: &DbArticle,
|
||||
creator_id: i32,
|
||||
data: &Data<MyDataHandle>,
|
||||
) -> Result<(), Error> {
|
||||
let form = DbEditForm::new(original_article, creator_id, &new_text, previous_version)?;
|
||||
let form = DbEditForm::new(
|
||||
original_article,
|
||||
creator_id,
|
||||
&new_text,
|
||||
summary,
|
||||
previous_version,
|
||||
)?;
|
||||
if original_article.local {
|
||||
let edit = DbEdit::create(&form, &data.db_connection)?;
|
||||
let updated_article =
|
||||
|
@ -37,8 +45,10 @@ pub async fn submit_article_update(
|
|||
hash: form.hash,
|
||||
ap_id: form.ap_id,
|
||||
diff: form.diff,
|
||||
summary: form.summary,
|
||||
article_id: form.article_id,
|
||||
previous_version_id: form.previous_version_id,
|
||||
created: Utc::now(),
|
||||
};
|
||||
let instance = DbInstance::read(original_article.instance_id, &data.db_connection)?;
|
||||
UpdateRemoteArticle::send(edit, instance, data).await?;
|
||||
|
|
|
@ -77,6 +77,7 @@ impl ActivityHandler for RejectEdit {
|
|||
let form = DbConflictForm {
|
||||
id: EditVersion::new(&self.object.content)?,
|
||||
diff: self.object.content,
|
||||
summary: self.object.summary,
|
||||
creator_id: creator.id,
|
||||
article_id: article.id,
|
||||
previous_version_id: self.object.previous_version,
|
||||
|
|
|
@ -7,6 +7,7 @@ use crate::common::{DbArticle, DbEdit};
|
|||
use activitypub_federation::config::Data;
|
||||
use activitypub_federation::fetch::object_id::ObjectId;
|
||||
use activitypub_federation::traits::Object;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
|
@ -24,10 +25,12 @@ pub struct ApubEdit {
|
|||
kind: PatchType,
|
||||
pub id: ObjectId<DbEdit>,
|
||||
pub content: String,
|
||||
pub summary: String,
|
||||
pub version: EditVersion,
|
||||
pub previous_version: EditVersion,
|
||||
pub object: ObjectId<DbArticle>,
|
||||
pub attributed_to: ObjectId<DbPerson>,
|
||||
pub published: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
|
@ -50,10 +53,12 @@ impl Object for DbEdit {
|
|||
kind: PatchType::Patch,
|
||||
id: self.ap_id,
|
||||
content: self.diff,
|
||||
summary: self.summary,
|
||||
version: self.hash,
|
||||
previous_version: self.previous_version_id,
|
||||
object: article.ap_id,
|
||||
attributed_to: creator.ap_id,
|
||||
published: self.created,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -72,9 +77,11 @@ impl Object for DbEdit {
|
|||
creator_id: creator.id,
|
||||
ap_id: json.id,
|
||||
diff: json.content,
|
||||
summary: json.summary,
|
||||
article_id: article.id,
|
||||
hash: json.version,
|
||||
previous_version_id: json.previous_version,
|
||||
created: json.published,
|
||||
};
|
||||
let edit = DbEdit::create(&form, &data.db_connection)?;
|
||||
Ok(edit)
|
||||
|
|
|
@ -9,7 +9,7 @@ use url::{ParseError, Url};
|
|||
|
||||
pub fn generate_activity_id(domain: &Url) -> Result<Url, ParseError> {
|
||||
let port = domain.port().unwrap();
|
||||
let domain = domain.domain().unwrap();
|
||||
let domain = domain.host_str().unwrap();
|
||||
let id: String = thread_rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(7)
|
||||
|
|
|
@ -60,9 +60,11 @@ pub struct DbEdit {
|
|||
#[cfg(not(feature = "ssr"))]
|
||||
pub ap_id: String,
|
||||
pub diff: String,
|
||||
pub summary: String,
|
||||
pub article_id: i32,
|
||||
/// First edit of an article always has `EditVersion::default()` here
|
||||
pub previous_version_id: EditVersion,
|
||||
pub created: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// The version hash of a specific edit. Generated by taking an SHA256 hash of the diff
|
||||
|
@ -134,6 +136,8 @@ pub struct EditArticleData {
|
|||
/// Full, new text of the article. A diff against `previous_version` is generated on the backend
|
||||
/// side to handle conflicts.
|
||||
pub new_text: String,
|
||||
/// What was changed
|
||||
pub summary: String,
|
||||
/// The version that this edit is based on, ie [DbArticle.latest_version] or
|
||||
/// [ApiConflict.previous_version]
|
||||
pub previous_version_id: EditVersion,
|
||||
|
|
|
@ -71,6 +71,7 @@ impl ApiClient {
|
|||
let edit_form = EditArticleData {
|
||||
article_id: article.article.id,
|
||||
new_text,
|
||||
summary: "initial text".to_string(),
|
||||
previous_version_id: article.latest_version,
|
||||
resolve_conflict_id: None,
|
||||
};
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use crate::common::LocalUserView;
|
||||
use crate::frontend::api::ApiClient;
|
||||
use crate::frontend::components::nav::Nav;
|
||||
use crate::frontend::pages::edit_article::EditArticle;
|
||||
use crate::frontend::pages::article::edit::EditArticle;
|
||||
use crate::frontend::pages::article::history::ArticleHistory;
|
||||
use crate::frontend::pages::article::read::ReadArticle;
|
||||
use crate::frontend::pages::diff::EditDiff;
|
||||
use crate::frontend::pages::login::Login;
|
||||
use crate::frontend::pages::read_article::ReadArticle;
|
||||
use crate::frontend::pages::register::Register;
|
||||
use crate::frontend::pages::Page;
|
||||
use leptos::{
|
||||
|
@ -77,6 +79,8 @@ pub fn App() -> impl IntoView {
|
|||
<Route path={Page::Home.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/diff/:hash" view=EditDiff/>
|
||||
<Route path={Page::Login.path()} view=Login/>
|
||||
<Route path={Page::Register.path()} view=Register/>
|
||||
</Routes>
|
||||
|
|
24
src/frontend/components/article_nav.rs
Normal file
24
src/frontend/components/article_nav.rs
Normal file
|
@ -0,0 +1,24 @@
|
|||
use crate::common::ArticleView;
|
||||
use crate::frontend::app::GlobalState;
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
|
||||
#[component]
|
||||
pub fn ArticleNav(article: Resource<String, ArticleView>) -> impl IntoView {
|
||||
let global_state = use_context::<RwSignal<GlobalState>>().unwrap();
|
||||
view! {
|
||||
<Suspense fallback=|| view! { "Loading..." }>
|
||||
{move || article.get().map(|article| {
|
||||
let title = article.article.title;
|
||||
view!{
|
||||
<nav class="inner">
|
||||
<A href={format!("/article/{title}")}>"Read"</A>
|
||||
<A href={format!("/article/{title}/history")}>"History"</A>
|
||||
<Show when=move || global_state.with(|state| state.my_profile.is_some())>
|
||||
<A href={format!("/article/{title}/edit")}>"Edit"</A>
|
||||
</Show>
|
||||
</nav>
|
||||
}})}
|
||||
</Suspense>
|
||||
}
|
||||
}
|
|
@ -65,10 +65,9 @@ pub fn CredentialsForm(
|
|||
<div>
|
||||
<button
|
||||
prop:disabled=move || button_is_disabled.get()
|
||||
on:click=move |_| dispatch_action()
|
||||
>
|
||||
on:click=move |_| dispatch_action()>
|
||||
{action_label}
|
||||
</button>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
pub mod article_nav;
|
||||
pub(crate) mod credentials;
|
||||
pub mod nav;
|
||||
|
|
|
@ -3,3 +3,7 @@ pub mod app;
|
|||
mod components;
|
||||
pub mod error;
|
||||
pub mod pages;
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
#[wasm_bindgen::prelude::wasm_bindgen]
|
||||
pub fn hydrate() {}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
|
||||
#[component]
|
||||
pub fn Article() -> impl IntoView {
|
||||
view! {
|
||||
<nav class="inner">
|
||||
<li>
|
||||
<A href="read">"Read"</A>
|
||||
</li>
|
||||
<li>
|
||||
<A href="edit">"Edit"</A>
|
||||
</li>
|
||||
</nav>
|
||||
}
|
||||
}
|
93
src/frontend/pages/article/edit.rs
Normal file
93
src/frontend/pages/article/edit.rs
Normal file
|
@ -0,0 +1,93 @@
|
|||
use crate::common::EditArticleData;
|
||||
use crate::frontend::app::GlobalState;
|
||||
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 = params.get_untracked().get("title").cloned();
|
||||
let article = article_resource(title.unwrap());
|
||||
|
||||
let (text, set_text) = create_signal(String::new());
|
||||
// TODO: set initial text, otherwise submit with no changes results in empty text
|
||||
//article.with(|article| set_text.update(article.as_ref().unwrap().article.text.clone()));
|
||||
let (summary, set_summary) = create_signal(String::new());
|
||||
let (edit_response, set_edit_response) = create_signal(None::<()>);
|
||||
let (edit_error, set_edit_error) = create_signal(None::<String>);
|
||||
let (wait_for_response, set_wait_for_response) = create_signal(false);
|
||||
let button_is_disabled =
|
||||
Signal::derive(move || wait_for_response.get() || summary.get().is_empty());
|
||||
let submit_action = create_action(move |(new_text, summary): &(String, String)| {
|
||||
let new_text = new_text.clone();
|
||||
let summary = summary.clone();
|
||||
async move {
|
||||
let form = EditArticleData {
|
||||
article_id: article.get().unwrap().article.id,
|
||||
new_text,
|
||||
summary,
|
||||
previous_version_id: article.get().unwrap().latest_version,
|
||||
resolve_conflict_id: None,
|
||||
};
|
||||
set_wait_for_response.update(|w| *w = true);
|
||||
let res = GlobalState::api_client().edit_article(&form).await;
|
||||
set_wait_for_response.update(|w| *w = false);
|
||||
match res {
|
||||
Ok(_res) => {
|
||||
set_edit_response.update(|v| *v = Some(()));
|
||||
set_edit_error.update(|e| *e = None);
|
||||
}
|
||||
Err(err) => {
|
||||
let msg = err.0.to_string();
|
||||
log::warn!("Unable to edit: {msg}");
|
||||
set_edit_error.update(|e| *e = Some(msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
view! {
|
||||
<ArticleNav article=article.clone()/>
|
||||
<Show
|
||||
when=move || edit_response.get().is_some()
|
||||
fallback=move || {
|
||||
view! {
|
||||
<Suspense fallback=|| view! { "Loading..." }> {
|
||||
move || article.get().map(|article|
|
||||
view! {
|
||||
<div class="item-view">
|
||||
<h1>{article.article.title.replace('_', " ")}</h1>
|
||||
<textarea on:keyup=move |ev| {
|
||||
let val = event_target_value(&ev);
|
||||
set_text.update(|p| *p = val);
|
||||
}>
|
||||
{article.article.text}
|
||||
</textarea>
|
||||
</div>
|
||||
{move || {
|
||||
edit_error
|
||||
.get()
|
||||
.map(|err| {
|
||||
view! { <p style="color:red;">{err}</p> }
|
||||
})
|
||||
}}
|
||||
<input type="text" on:keyup=move |ev| {
|
||||
let val = event_target_value(&ev);
|
||||
set_summary.update(|p| *p = val);
|
||||
}/>
|
||||
<button
|
||||
prop:disabled=move || button_is_disabled.get()
|
||||
on:click=move |_| submit_action.dispatch((text.get(), summary.get()))>
|
||||
Submit
|
||||
</button>
|
||||
}
|
||||
)
|
||||
}
|
||||
</Suspense>
|
||||
}}>
|
||||
Edit successful!
|
||||
</Show>
|
||||
}
|
||||
}
|
34
src/frontend/pages/article/history.rs
Normal file
34
src/frontend/pages/article/history.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
use crate::frontend::components::article_nav::ArticleNav;
|
||||
use crate::frontend::pages::article_resource;
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
|
||||
#[component]
|
||||
pub fn ArticleHistory() -> impl IntoView {
|
||||
let params = use_params_map();
|
||||
let title = params.get().get("title").cloned();
|
||||
let article = article_resource(title.unwrap());
|
||||
|
||||
view! {
|
||||
<ArticleNav article=article.clone()/>
|
||||
<Suspense fallback=|| view! { "Loading..." }> {
|
||||
move || article.get().map(|article| {
|
||||
let title = article.article.title;
|
||||
view! {
|
||||
<div class="item-view">
|
||||
<h1>{title.replace('_', " ")}</h1>
|
||||
{
|
||||
article.edits.into_iter().rev().map(|edit| {
|
||||
let path = format!("/article/{title}/diff/{}", edit.hash.0);
|
||||
// TODO: need to return username from backend and show it
|
||||
let label = format!("{} ({})", edit.summary, edit.created.to_rfc2822());
|
||||
view! {<li><a href={path}>{label}</a></li> }
|
||||
}).collect::<Vec<_>>()
|
||||
}
|
||||
</div>
|
||||
}
|
||||
})
|
||||
}
|
||||
</Suspense>
|
||||
}
|
||||
}
|
3
src/frontend/pages/article/mod.rs
Normal file
3
src/frontend/pages/article/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
pub mod edit;
|
||||
pub mod history;
|
||||
pub mod read;
|
29
src/frontend/pages/article/read.rs
Normal file
29
src/frontend/pages/article/read.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
use crate::frontend::components::article_nav::ArticleNav;
|
||||
use crate::frontend::pages::article_resource;
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
|
||||
#[component]
|
||||
pub fn ReadArticle() -> impl IntoView {
|
||||
let params = use_params_map();
|
||||
let title = params
|
||||
.get_untracked()
|
||||
.get("title")
|
||||
.cloned()
|
||||
.unwrap_or("Main_Page".to_string());
|
||||
let article = article_resource(title);
|
||||
|
||||
view! {
|
||||
<ArticleNav article=article.clone()/>
|
||||
<Suspense fallback=|| view! { "Loading..." }> {
|
||||
move || article.get().map(|article|
|
||||
view! {
|
||||
<div class="item-view">
|
||||
<h1>{article.article.title.replace('_', " ")}</h1>
|
||||
<div>{article.article.text}</div>
|
||||
</div>
|
||||
})
|
||||
}
|
||||
</Suspense>
|
||||
}
|
||||
}
|
32
src/frontend/pages/diff.rs
Normal file
32
src/frontend/pages/diff.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
use crate::frontend::components::article_nav::ArticleNav;
|
||||
use crate::frontend::pages::article_resource;
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
|
||||
#[component]
|
||||
pub fn EditDiff() -> impl IntoView {
|
||||
let params = use_params_map();
|
||||
let title = params.get_untracked().get("title").cloned().unwrap();
|
||||
let article = article_resource(title);
|
||||
|
||||
view! {
|
||||
<ArticleNav article=article.clone()/>
|
||||
<Suspense fallback=|| view! { "Loading..." }> {
|
||||
move || article.get().map(|article| {
|
||||
let hash = params
|
||||
.get_untracked()
|
||||
.get("hash")
|
||||
.cloned().unwrap();
|
||||
let edit = article.edits.iter().find(|e| e.hash.0.to_string() == hash).unwrap();
|
||||
// TODO: need to show username
|
||||
view! {
|
||||
<div class="item-view">
|
||||
<h1>{article.article.title.replace('_', " ")}</h1>
|
||||
<pre>{edit.diff.clone()}</pre>
|
||||
</div>
|
||||
}
|
||||
})
|
||||
}
|
||||
</Suspense>
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
use leptos::*;
|
||||
|
||||
#[component]
|
||||
pub fn EditArticle() -> impl IntoView {
|
||||
view! {
|
||||
<div class="item-view">
|
||||
<h1>Title...</h1>
|
||||
<textarea></textarea>
|
||||
</div>
|
||||
}
|
||||
}
|
|
@ -1,7 +1,10 @@
|
|||
pub mod article;
|
||||
pub mod edit_article;
|
||||
use crate::common::{ArticleView, GetArticleData};
|
||||
use crate::frontend::app::GlobalState;
|
||||
use leptos::{create_resource, Resource};
|
||||
|
||||
pub(crate) mod article;
|
||||
pub(crate) mod diff;
|
||||
pub mod login;
|
||||
pub mod read_article;
|
||||
pub mod register;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
|
@ -21,3 +24,19 @@ impl Page {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn article_resource(title: String) -> Resource<String, ArticleView> {
|
||||
create_resource(
|
||||
move || title.clone(),
|
||||
move |title| async move {
|
||||
GlobalState::api_client()
|
||||
.get_article(GetArticleData {
|
||||
title: Some(title),
|
||||
instance_id: None,
|
||||
id: None,
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
use crate::common::GetArticleData;
|
||||
use crate::frontend::app::GlobalState;
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
|
||||
#[component]
|
||||
pub fn ReadArticle() -> impl IntoView {
|
||||
let params = use_params_map();
|
||||
let article = create_resource(
|
||||
move || {
|
||||
params
|
||||
.get()
|
||||
.get("title")
|
||||
.cloned()
|
||||
.unwrap_or("Main_Page".to_string())
|
||||
},
|
||||
move |title| async move {
|
||||
GlobalState::api_client()
|
||||
.get_article(GetArticleData {
|
||||
title: Some(title),
|
||||
instance_id: None,
|
||||
id: None,
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
},
|
||||
);
|
||||
|
||||
let global_state = use_context::<RwSignal<GlobalState>>().unwrap();
|
||||
let (count, set_count) = create_signal(0);
|
||||
view! {
|
||||
<Suspense fallback=|| view! { "Loading..." }>
|
||||
{move || article.get().map(|article|
|
||||
view! {
|
||||
<div class="item-view">
|
||||
<h1>{article.article.title.replace('_', " ")}</h1>
|
||||
<Show when=move || global_state.with(|state| state.my_profile.is_some())>
|
||||
<button on:click=move |_| {
|
||||
set_count.update(|n| *n += 1);
|
||||
}>Edit {move || count.get()}</button>
|
||||
</Show>
|
||||
<div>{article.article.text}</div>
|
||||
</div>
|
||||
})}
|
||||
</Suspense>
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue