mirror of
https://github.com/Nutomic/ibis.git
synced 2024-11-25 15:51:10 +00:00
Add follow instance button in article nav (fixes #31)
This commit is contained in:
parent
3a8560ce37
commit
01c0175f4c
11 changed files with 92 additions and 42 deletions
|
@ -1,17 +1,18 @@
|
|||
use crate::{
|
||||
backend::{database::IbisData, error::MyResult, federation::activities::follow::Follow},
|
||||
common::{DbInstance, FollowInstance, InstanceView, LocalUserView, ResolveObject},
|
||||
common::{DbInstance, FollowInstance, GetInstance, InstanceView, LocalUserView, ResolveObject},
|
||||
};
|
||||
use activitypub_federation::{config::Data, fetch::object_id::ObjectId};
|
||||
use axum::{extract::Query, Extension, Form, Json};
|
||||
use axum_macros::debug_handler;
|
||||
|
||||
/// Retrieve the local instance info.
|
||||
/// Retrieve details about an instance. If no id is provided, return local instance.
|
||||
#[debug_handler]
|
||||
pub(in crate::backend::api) async fn get_local_instance(
|
||||
pub(in crate::backend::api) async fn get_instance(
|
||||
data: Data<IbisData>,
|
||||
Form(query): Form<GetInstance>,
|
||||
) -> MyResult<Json<InstanceView>> {
|
||||
let local_instance = DbInstance::read_local_view(&data)?;
|
||||
let local_instance = DbInstance::read_view(query.id, &data)?;
|
||||
Ok(Json(local_instance))
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ use crate::{
|
|||
resolve_article,
|
||||
search_article,
|
||||
},
|
||||
instance::{follow_instance, get_local_instance, resolve_instance},
|
||||
instance::{follow_instance, get_instance, resolve_instance},
|
||||
user::{
|
||||
get_user,
|
||||
login_user,
|
||||
|
@ -56,7 +56,7 @@ pub fn api_routes() -> Router {
|
|||
.route("/article/resolve", get(resolve_article))
|
||||
.route("/article/protect", post(protect_article))
|
||||
.route("/edit_conflicts", get(edit_conflicts))
|
||||
.route("/instance", get(get_local_instance))
|
||||
.route("/instance", get(get_instance))
|
||||
.route("/instance/follow", post(follow_instance))
|
||||
.route("/instance/resolve", get(resolve_instance))
|
||||
.route("/search", get(search_article))
|
||||
|
|
|
@ -72,8 +72,11 @@ impl DbInstance {
|
|||
.get_result(conn.deref_mut())?)
|
||||
}
|
||||
|
||||
pub fn read_local_view(data: &Data<IbisData>) -> MyResult<InstanceView> {
|
||||
let instance = DbInstance::read_local_instance(data)?;
|
||||
pub fn read_view(id: Option<i32>, data: &Data<IbisData>) -> MyResult<InstanceView> {
|
||||
let instance = match id {
|
||||
Some(id) => DbInstance::read(id, data),
|
||||
None => DbInstance::read_local_instance(data),
|
||||
}?;
|
||||
let followers = DbInstance::read_followers(instance.id, data)?;
|
||||
|
||||
Ok(InstanceView {
|
||||
|
|
|
@ -210,6 +210,11 @@ pub struct ForkArticleForm {
|
|||
pub new_title: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct GetInstance {
|
||||
pub id: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct FollowInstance {
|
||||
pub id: i32,
|
||||
|
|
|
@ -11,6 +11,7 @@ use crate::{
|
|||
FollowInstance,
|
||||
ForkArticleForm,
|
||||
GetArticleForm,
|
||||
GetInstance,
|
||||
GetUserForm,
|
||||
InstanceView,
|
||||
ListArticlesForm,
|
||||
|
@ -138,6 +139,10 @@ impl ApiClient {
|
|||
self.get_query("/api/v1/instance", None::<i32>).await
|
||||
}
|
||||
|
||||
pub async fn get_instance(&self, get_form: &GetInstance) -> MyResult<InstanceView> {
|
||||
self.get_query("/api/v1/instance", Some(get_form)).await
|
||||
}
|
||||
|
||||
pub async fn follow_instance_with_resolve(
|
||||
&self,
|
||||
follow_instance: &str,
|
||||
|
|
|
@ -81,13 +81,13 @@ impl GlobalState {
|
|||
#[component]
|
||||
pub fn App() -> impl IntoView {
|
||||
provide_meta_context();
|
||||
let backend_hostname = GlobalState {
|
||||
let global_state = GlobalState {
|
||||
api_client: ApiClient::new(Client::new(), None),
|
||||
my_profile: None,
|
||||
};
|
||||
// Load user profile in case we are already logged in
|
||||
GlobalState::update_my_profile();
|
||||
provide_context(create_rw_signal(backend_hostname));
|
||||
provide_context(create_rw_signal(global_state));
|
||||
|
||||
view! {
|
||||
<>
|
||||
|
|
|
@ -1,31 +1,47 @@
|
|||
use crate::{
|
||||
common::{validation::can_edit_article, ArticleView},
|
||||
frontend::{app::GlobalState, article_link},
|
||||
common::{validation::can_edit_article, ArticleView, GetInstance},
|
||||
frontend::{
|
||||
app::GlobalState,
|
||||
article_link,
|
||||
components::instance_follow_button::InstanceFollowButton,
|
||||
},
|
||||
};
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
|
||||
#[component]
|
||||
pub fn ArticleNav(article: Resource<Option<String>, ArticleView>) -> impl IntoView {
|
||||
let global_state = use_context::<RwSignal<GlobalState>>().unwrap();
|
||||
view! {
|
||||
<Suspense>
|
||||
{move || article.get().map(|article| {
|
||||
let article_link = article_link(&article.article);
|
||||
{move || article.get().map(|article_| {
|
||||
let instance = create_local_resource(move || article_.article.instance_id, move |instance_id| async move {
|
||||
let form = GetInstance {
|
||||
id: Some(instance_id)
|
||||
};
|
||||
GlobalState::api_client()
|
||||
.get_instance(&form)
|
||||
.await
|
||||
.unwrap()
|
||||
});
|
||||
let global_state = use_context::<RwSignal<GlobalState>>().unwrap();
|
||||
let article_link = article_link(&article_.article);
|
||||
let article_link_ = article_link.clone();
|
||||
let protected = article.article.protected;
|
||||
let protected = article_.article.protected;
|
||||
view!{
|
||||
<nav class="inner">
|
||||
<A href=article_link.clone()>"Read"</A>
|
||||
<A href={format!("{article_link}/history")}>"History"</A>
|
||||
<Show when=move || global_state.with(|state| {
|
||||
let is_admin = state.my_profile.as_ref().map(|p| p.local_user.admin).unwrap_or(false);
|
||||
state.my_profile.is_some() && can_edit_article(&article.article, is_admin).is_ok()
|
||||
state.my_profile.is_some() && can_edit_article(&article_.article, is_admin).is_ok()
|
||||
})>
|
||||
<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>
|
||||
{instance.get().map(|i|
|
||||
view!{ <InstanceFollowButton instance=i.instance.clone() /> }
|
||||
)}
|
||||
</Show>
|
||||
<Show when=move || protected>
|
||||
<span title="Article can only be edited by local admins">"Protected"</span>
|
||||
|
|
39
src/frontend/components/instance_follow_button.rs
Normal file
39
src/frontend/components/instance_follow_button.rs
Normal file
|
@ -0,0 +1,39 @@
|
|||
use crate::{
|
||||
common::{DbInstance, FollowInstance},
|
||||
frontend::app::GlobalState,
|
||||
};
|
||||
use leptos::{component, *};
|
||||
|
||||
#[component]
|
||||
pub fn InstanceFollowButton(instance: DbInstance) -> impl IntoView {
|
||||
let global_state = use_context::<RwSignal<GlobalState>>().unwrap();
|
||||
let follow_action = create_action(move |instance_id: &i32| {
|
||||
let instance_id = *instance_id;
|
||||
async move {
|
||||
let form = FollowInstance { id: instance_id };
|
||||
GlobalState::api_client()
|
||||
.follow_instance(form)
|
||||
.await
|
||||
.unwrap();
|
||||
GlobalState::update_my_profile();
|
||||
}
|
||||
});
|
||||
let is_following = global_state
|
||||
.get_untracked()
|
||||
.my_profile
|
||||
.map(|p| p.following.contains(&instance))
|
||||
.unwrap_or_default();
|
||||
let follow_text = if is_following {
|
||||
"Following instance"
|
||||
} else {
|
||||
"Follow instance"
|
||||
};
|
||||
|
||||
view! {
|
||||
<button on:click=move |_| follow_action.dispatch(instance.id)
|
||||
prop:disabled=move || is_following
|
||||
prop:hidden=move || instance.local>
|
||||
{follow_text}
|
||||
</button>
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
pub mod article_nav;
|
||||
pub(crate) mod credentials;
|
||||
pub mod credentials;
|
||||
pub mod instance_follow_button;
|
||||
pub mod nav;
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
use crate::frontend::{
|
||||
article_title,
|
||||
components::article_nav::ArticleNav,
|
||||
extract_domain,
|
||||
pages::article_resource,
|
||||
user_link,
|
||||
};
|
||||
use leptos::*;
|
||||
use crate::frontend::extract_domain;
|
||||
|
||||
#[component]
|
||||
pub fn ArticleHistory() -> impl IntoView {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
common::{utils::http_protocol_str, DbInstance, FollowInstance},
|
||||
frontend::app::GlobalState,
|
||||
common::{utils::http_protocol_str, DbInstance},
|
||||
frontend::{app::GlobalState, components::instance_follow_button::InstanceFollowButton},
|
||||
};
|
||||
use leptos::*;
|
||||
use leptos_router::use_params_map;
|
||||
|
@ -18,36 +18,16 @@ pub fn InstanceDetails() -> impl IntoView {
|
|||
.await
|
||||
.unwrap()
|
||||
});
|
||||
let follow_action = create_action(move |instance_id: &i32| {
|
||||
let instance_id = *instance_id;
|
||||
async move {
|
||||
let form = FollowInstance { id: instance_id };
|
||||
GlobalState::api_client()
|
||||
.follow_instance(form)
|
||||
.await
|
||||
.unwrap();
|
||||
GlobalState::update_my_profile();
|
||||
}
|
||||
});
|
||||
|
||||
view! {
|
||||
<Suspense fallback=|| view! { "Loading..." }> {
|
||||
move || instance_profile.get().map(|instance: DbInstance| {
|
||||
let instance_ = instance.clone();
|
||||
let is_following = global_state.get().my_profile.map(|p| p.following.contains(&instance_)).unwrap_or_default();
|
||||
let follow_text = if is_following {
|
||||
"Following"
|
||||
} else {
|
||||
"Follow"
|
||||
};
|
||||
view! {
|
||||
<h1>{instance.domain}</h1>
|
||||
|
||||
<Show when=move || global_state.with(|state| state.my_profile.is_some())>
|
||||
<button on:click=move |_| follow_action.dispatch(instance.id)
|
||||
prop:disabled=move || is_following>
|
||||
{follow_text}
|
||||
</button>
|
||||
<InstanceFollowButton instance=instance_.clone() />
|
||||
</Show>
|
||||
<p>Follow the instance so that new edits are federated to your instance.</p>
|
||||
<p>"TODO: show a list of articles from the instance. For now you can use the "<a href="/article/list">Article list</a>.</p>
|
||||
|
|
Loading…
Reference in a new issue