1
0
Fork 0
mirror of https://github.com/Nutomic/ibis.git synced 2025-01-11 10:35:49 +00:00

wip: upgrade leptos

This commit is contained in:
Felix Ableitner 2024-11-15 12:20:55 +01:00
parent 0d93ac31a3
commit 54951621e4
30 changed files with 599 additions and 531 deletions

760
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -32,8 +32,6 @@ ssr = [
]
hydrate = [
"leptos/hydrate",
"leptos_meta/hydrate",
"leptos_router/hydrate",
"katex/wasm-js",
]
diesel-derive-newtype = ["dep:diesel-derive-newtype"]
@ -61,9 +59,9 @@ unwrap_used = "deny"
[dependencies]
anyhow = "1.0.89"
leptos = "0.6.15"
leptos_meta = "0.6.15"
leptos_router = "0.6.15"
leptos = "0.7.0-rc1"
leptos_meta = "0.7.0-rc1"
leptos_router = "0.7.0-rc1"
chrono = { version = "0.4.38", features = ["serde"] }
env_logger = { version = "0.11.5", default-features = false }
futures = "0.3.30"
@ -88,7 +86,7 @@ markdown-it-heading-anchors = "0.3.0"
markdown-it-footnote = "0.2.0"
markdown-it-sub = "1.0.0"
markdown-it-sup = "1.0.0"
leptos-use = "0.13.6"
leptos-use = "0.14.0-rc2"
codee = "0.2.0"
# backend-only features
@ -111,7 +109,7 @@ diesel-derive-newtype = { version = "2.1.2", optional = true }
diesel_migrations = { version = "2.2.0", optional = true }
doku = { version = "0.21.1", optional = true }
jsonwebtoken = { version = "9.3.0", optional = true }
leptos_axum = { version = "0.6.15", optional = true }
leptos_axum = { version = "0.7.0-rc1", optional = true }
bcrypt = { version = "0.15.1", optional = true }
diffy = { version = "0.4.0", optional = true }
enum_delegate = { version = "0.2.0", optional = true }

View file

@ -5,7 +5,7 @@ use axum::{
response::{IntoResponse, Response},
};
use axum_macros::debug_handler;
use leptos::LeptosOptions;
use leptos::prelude::*;
use tower::ServiceExt;
use tower_http::services::ServeDir;

View file

@ -47,7 +47,7 @@ use federation::objects::{
articles_collection::local_articles_url,
instance_collection::linked_instances_url,
};
use leptos::*;
use leptos::prelude::*;
use leptos_axum::{generate_route_list, LeptosRoutes};
use log::info;
use std::net::SocketAddr;
@ -93,7 +93,7 @@ pub async fn start(config: IbisConfig, override_hostname: Option<SocketAddr>) ->
setup(&data.to_request_data()).await?;
}
let leptos_options = get_configuration(Some("Cargo.toml")).await?.leptos_options;
let leptos_options = get_configuration(Some("Cargo.toml"))?.leptos_options;
let mut addr = leptos_options.site_addr;
if let Some(override_hostname) = override_hostname {
addr = override_hostname;

View file

@ -3,6 +3,7 @@ pub mod utils;
pub mod validation;
use chrono::{DateTime, Utc};
use codee::{Decoder, Encoder};
use newtypes::{ArticleId, ConflictId, EditId, InstanceId, PersonId};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
@ -351,6 +352,25 @@ pub struct SiteView {
pub config: Options,
}
// TODO: using () doesnt make much sense
impl Encoder<()> for SiteView {
type Error = serde_json::Error;
type Encoded = String;
fn encode(val: &()) -> Result<Self::Encoded, Self::Error> {
serde_json::to_string(val)
}
}
impl Decoder<()> for SiteView {
type Error = serde_json::Error;
type Encoded = str;
fn decode(stored_value: &Self::Encoded) -> Result<(), Self::Error> {
serde_json::from_str(stored_value)
}
}
#[test]
fn test_edit_versions() {
let default = EditVersion::default();

View file

@ -56,7 +56,7 @@ impl ApiClient {
}
#[cfg(feature = "ssr")]
{
use leptos::leptos_config::get_config_from_str;
use leptos::config::get_config_from_str;
let leptos_options = get_config_from_str(include_str!("../../Cargo.toml")).unwrap();
hostname = leptos_options.site_addr.to_string();
ssl = false;
@ -287,7 +287,7 @@ where
#[cfg(feature = "ssr")]
{
use crate::common::{Auth, AUTH_COOKIE};
use leptos::use_context;
use leptos::prelude::use_context;
use reqwest::header::HeaderName;
let auth = use_context::<Auth>();

View file

@ -23,9 +23,12 @@ use crate::{
},
},
};
use leptos::*;
use leptos::prelude::*;
use leptos_meta::{provide_meta_context, *};
use leptos_router::{Route, Router, Routes};
use leptos_router::{
components::{Route, Router, Routes},
path,
};
pub fn site() -> Resource<(), SiteView> {
use_context::<Resource<(), SiteView>>().unwrap()
@ -43,6 +46,7 @@ pub fn is_admin() -> bool {
})
}
// TODO: can probably get rid of this
pub trait DefaultResource<T> {
fn with_default<O>(&self, f: impl FnOnce(&T) -> O) -> O;
}
@ -58,9 +62,8 @@ impl<T: Default> DefaultResource<T> for Resource<(), T> {
#[component]
pub fn App() -> impl IntoView {
// TODO: should create_resource() but then things break
let site_resource =
create_local_resource(move || (), |_| async move { CLIENT.site().await.unwrap() });
// TODO: should Resource::new() but then things break
let site_resource = LocalResource::new(|| async move { CLIENT.site().await.unwrap() });
provide_context(site_resource);
provide_meta_context();
@ -68,33 +71,33 @@ pub fn App() -> impl IntoView {
provide_context(darkmode.clone());
view! {
<Html attr:data-theme=darkmode.theme class="h-full" />
<Body class="h-full max-sm:flex max-sm:flex-col" />
<Html attr:data-theme=darkmode.theme {..} class="h-full" />
<Body {..} class="h-full max-sm:flex max-sm:flex-col" />
<>
<Stylesheet id="ibis" href="/pkg/ibis.css" />
<Stylesheet id="katex" href="/katex.min.css" />
<Router>
<Nav />
<main class="p-4 md:ml-64">
<Routes>
<Route path="/" view=ReadArticle />
<Route path="/article/:title" view=ReadArticle />
<Route path="/article/:title/history" view=ArticleHistory />
<Route path="/article/:title/edit/:conflict_id?" view=EditArticle />
<Route path="/article/:title/actions" view=ArticleActions />
<Route path="/article/:title/diff/:hash" view=EditDiff />
<Routes fallback=|| "Page not found.".into_view()>
<Route path=path!("/") view=ReadArticle />
<Route path=path!("/article/:title") view=ReadArticle />
<Route path=path!("/article/:title/history") view=ArticleHistory />
<Route path=path!("/article/:title/edit/:conflict_id?") view=EditArticle />
<Route path=path!("/article/:title/actions") view=ArticleActions />
<Route path=path!("/article/:title/diff/:hash") view=EditDiff />
// TODO: use protected route, otherwise user can view
// /article/create without login
//https://github.com/leptos-rs/leptos/blob/leptos_0.7/examples/router/src/lib.rs#L51
<Route path="/article/create" view=CreateArticle />
<Route path="/article/list" view=ListArticles />
<Route path="/instance/:hostname" view=InstanceDetails />
<Route path="/instance/list" view=ListInstances />
<Route path="/user/:name" view=UserProfile />
<Route path="/login" view=Login />
<Route path="/register" view=Register />
<Route path="/search" view=Search />
<Route path="/notifications" view=Notifications />
// https://github.com/leptos-rs/leptos/blob/leptos_0.7/examples/router/src/lib.rs#L51
<Route path=path!("/article/create") view=CreateArticle />
<Route path=path!("/article/list") view=ListArticles />
<Route path=path!("/instance/:hostname") view=InstanceDetails />
<Route path=path!("/instance/list") view=ListInstances />
<Route path=path!("/user/:name") view=UserProfile />
<Route path=path!("/login") view=Login />
<Route path=path!("/register") view=Register />
<Route path=path!("/search") view=Search />
<Route path=path!("/notifications") view=Notifications />
</Routes>
</main>
</Router>

View file

@ -8,8 +8,8 @@ use crate::{
components::instance_follow_button::InstanceFollowButton,
},
};
use leptos::*;
use leptos_router::*;
use leptos::prelude::*;
use leptos_router::components::A;
pub enum ActiveTab {
Read,
@ -19,10 +19,7 @@ pub enum ActiveTab {
}
#[component]
pub fn ArticleNav(
article: Resource<Option<String>, ArticleView>,
active_tab: ActiveTab,
) -> impl IntoView {
pub fn ArticleNav(article: Resource<ArticleView>, active_tab: ActiveTab) -> impl IntoView {
let tab_classes = tab_classes(&active_tab);
view! {
@ -32,7 +29,7 @@ pub fn ArticleNav(
.get()
.map(|article_| {
let title = article_title(&article_.article);
let instance = create_resource(
let instance = Resource::new(
move || article_.article.instance_id,
move |instance_id| async move {
let form = GetInstance {
@ -46,24 +43,33 @@ pub fn ArticleNav(
let protected = article_.article.protected;
view! {
<div role="tablist" class="tabs tabs-lifted">
<A class=tab_classes.read href=article_link.clone()>
<A href=article_link.clone() {..} class=tab_classes.read>
"Read"
</A>
<A class=tab_classes.history href=format!("{article_link}/history")>
<A
href=format!("{article_link}/history")
{..}
class=tab_classes.history
>
"History"
</A>
<Show when=move || {
is_logged_in()
&& can_edit_article(&article_.article, is_admin()).is_ok()
}>
<A class=tab_classes.edit href=format!("{article_link}/edit")>
<A
href=format!("{article_link}/edit")
{..}
class=tab_classes.edit
>
"Edit"
</A>
</Show>
<Show when=is_logged_in>
<A
class=tab_classes.actions
href=format!("{article_link_}/actions")
{..}
class=tab_classes.actions
>
"Actions"
</A>

View file

@ -1,10 +1,21 @@
use crate::frontend::api::CLIENT;
use leptos::{component, *};
use codee::{Decoder, Encoder};
use leptos::prelude::*;
use std::fmt::Debug;
use url::Url;
#[component]
pub fn ConnectView<T: Clone + 'static, R: 'static>(res: Resource<T, R>) -> impl IntoView {
let connect_ibis_wiki = create_action(move |_: &()| async move {
pub fn ConnectView<T, R>(res: Resource<T, R>) -> impl IntoView
where
T: Clone + Send + Sync + 'static,
R: Encoder<T> + Decoder<T> + Send + Sync + 'static,
<R as Encoder<T>>::Error: Debug,
<R as Encoder<T>>::Encoded: IntoEncodedString,
<R as Decoder<T>>::Encoded: FromEncodedStr,
<R as Decoder<T>>::Error: Debug,
<<R as Decoder<T>>::Encoded as leptos::prelude::FromEncodedStr>::DecodingError: Debug,
{
let connect_ibis_wiki = Action::new(move |_: &()| async move {
CLIENT
.resolve_instance(Url::parse("https://ibis.wiki").unwrap())
.await
@ -16,7 +27,9 @@ pub fn ConnectView<T: Clone + 'static, R: 'static>(res: Resource<T, R>) -> impl
<div class="flex justify-center h-screen">
<button
class="btn btn-primary place-self-center"
on:click=move |_| connect_ibis_wiki.dispatch(())
on:click=move |_| {
connect_ibis_wiki.dispatch(());
}
>
Connect with ibis.wiki
</button>

View file

@ -1,4 +1,4 @@
use leptos::{ev, *};
use leptos::{ev::KeyboardEvent, prelude::*};
#[component]
pub fn CredentialsForm(
@ -8,8 +8,8 @@ pub fn CredentialsForm(
error: Signal<Option<String>>,
disabled: Signal<bool>,
) -> impl IntoView {
let (password, set_password) = create_signal(String::new());
let (username, set_username) = create_signal(String::new());
let (password, set_password) = signal(String::new());
let (username, set_username) = signal(String::new());
let dispatch_action = move || action.dispatch((username.get(), password.get()));
@ -34,7 +34,7 @@ pub fn CredentialsForm(
required
placeholder="Username"
prop:disabled=move || disabled.get()
on:keyup=move |ev: ev::KeyboardEvent| {
on:keyup=move |ev: KeyboardEvent| {
let val = event_target_value(&ev);
set_username.update(|v| *v = val);
}
@ -51,7 +51,7 @@ pub fn CredentialsForm(
required
placeholder="Password"
prop:disabled=move || disabled.get()
on:keyup=move |ev: ev::KeyboardEvent| {
on:keyup=move |ev: KeyboardEvent| {
match &*ev.key() {
"Enter" => {
dispatch_action();
@ -73,7 +73,9 @@ pub fn CredentialsForm(
<button
class="btn btn-primary my-2"
prop:disabled=move || button_is_disabled.get()
on:click=move |_| dispatch_action()
on:click=move |_| {
dispatch_action();
}
>
{action_label}
</button>

View file

@ -1,16 +1,14 @@
use crate::frontend::markdown::render_markdown;
use html::Textarea;
use leptos::*;
use leptos::{html::Textarea, prelude::*};
#[component]
pub fn EditorView(
// this param gives a false warning about being unused, ignore that
#[allow(unused)] textarea_ref: NodeRef<Textarea>,
textarea_ref: NodeRef<Textarea>,
content: Signal<String>,
set_content: WriteSignal<String>,
) -> impl IntoView {
let (preview, set_preview) = create_signal(render_markdown(&content.get_untracked()));
let (show_preview, set_show_preview) = create_signal(false);
let (preview, set_preview) = signal(render_markdown(&content.get_untracked()));
let (show_preview, set_show_preview) = signal(false);
view! {
<div>

View file

@ -5,11 +5,11 @@ use crate::{
app::{site, DefaultResource},
},
};
use leptos::{component, *};
use leptos::prelude::*;
#[component]
pub fn InstanceFollowButton(instance: DbInstance) -> impl IntoView {
let follow_action = create_action(move |instance_id: &InstanceId| {
let follow_action = Action::new(move |instance_id: &InstanceId| {
let instance_id = *instance_id;
async move {
let form = FollowInstance { id: instance_id };
@ -38,7 +38,9 @@ pub fn InstanceFollowButton(instance: DbInstance) -> impl IntoView {
view! {
<button
class=class_
on:click=move |_| follow_action.dispatch(instance.id)
on:click=move |_| {
follow_action.dispatch(instance.id);
}
prop:disabled=move || is_following
title="Follow the instance so that new edits are synchronized to your instance."
>

View file

@ -3,21 +3,21 @@ use crate::frontend::{
app::{is_logged_in, site, DefaultResource},
dark_mode::DarkMode,
};
use leptos::{component, view, IntoView, *};
use leptos_router::*;
use leptos::{component, prelude::*, view, IntoView, *};
use leptos_router::{components::A, hooks::use_navigate};
#[component]
pub fn Nav() -> impl IntoView {
let logout_action = create_action(move |_| async move {
let logout_action = Action::new(move |_| async move {
CLIENT.logout().await.unwrap();
site().refetch();
});
let notification_count = create_resource(
let notification_count = Resource::new(
|| (),
move |_| async move { CLIENT.notifications_count().await.unwrap_or_default() },
);
let (search_query, set_search_query) = create_signal(String::new());
let (search_query, set_search_query) = signal(String::new());
let mut dark_mode = expect_context::<DarkMode>();
view! {
<nav class="max-sm:navbar p-2.5 h-full md:fixed md:w-64 max-sm: border-b md:border-e border-slate-400 border-solid">
@ -70,7 +70,7 @@ pub fn Nav() -> impl IntoView {
class="form-control m-0 p-1"
on:submit=move |ev| {
ev.prevent_default();
let navigate = leptos_router::use_navigate();
let navigate = use_navigate();
let query = search_query.get();
if !query.is_empty() {
navigate(
@ -127,7 +127,9 @@ pub fn Nav() -> impl IntoView {
</p>
<button
class="btn btn-outline btn-xs w-min self-center"
on:click=move |_| logout_action.dispatch(())
on:click=move |_| {
logout_action.dispatch(());
}
>
Logout
</button>

View file

@ -1,6 +1,6 @@
use chrono::{Duration, Local};
use codee::string::FromToStringCodec;
use leptos::{Signal, SignalGet, SignalGetUntracked, SignalSet, WriteSignal};
use leptos::prelude::*;
use leptos_use::{use_cookie_with_options, use_preferred_dark, SameSite, UseCookieOptions};
#[derive(Debug, Clone)]

View file

@ -1,6 +1,6 @@
use crate::common::{utils::extract_domain, DbArticle, DbPerson};
use chrono::{DateTime, Local, Utc};
use leptos::*;
use leptos::prelude::*;
pub mod api;
pub mod app;

View file

@ -9,16 +9,16 @@ use crate::{
DbArticle,
},
};
use leptos::*;
use leptos_router::Redirect;
use leptos::{ev::KeyboardEvent, prelude::*};
use leptos_router::components::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): &(ArticleId, String)| {
let (new_title, set_new_title) = signal(String::new());
let (fork_response, set_fork_response) = signal(Option::<DbArticle>::None);
let (error, set_error) = signal(None::<String>);
let fork_action = Action::new(move |(article_id, new_title): &(ArticleId, String)| {
let params = ForkArticleForm {
article_id: *article_id,
new_title: new_title.to_string(),
@ -34,7 +34,7 @@ pub fn ArticleActions() -> impl IntoView {
}
}
});
let protect_action = create_action(move |(id, protected): &(ArticleId, bool)| {
let protect_action = Action::new(move |(id, protected): &(ArticleId, bool)| {
let params = ProtectArticleForm {
article_id: *id,
protected: !protected,
@ -72,7 +72,7 @@ pub fn ArticleActions() -> impl IntoView {
class="btn btn-secondary"
on:click=move |_| {
protect_action
.dispatch((article.article.id, article.article.protected))
.dispatch((article.article.id, article.article.protected));
}
>
Toggle Article Protection
@ -82,7 +82,7 @@ pub fn ArticleActions() -> impl IntoView {
<input
class="input"
placeholder="New Title"
on:keyup=move |ev: ev::KeyboardEvent| {
on:keyup=move |ev: KeyboardEvent| {
let val = event_target_value(&ev);
set_new_title.update(|v| *v = val);
}
@ -92,7 +92,7 @@ pub fn ArticleActions() -> impl IntoView {
class="btn"
disabled=move || new_title.get().is_empty()
on:click=move |_| {
fork_action.dispatch((article.article.id, new_title.get()))
fork_action.dispatch((article.article.id, new_title.get()));
}
>

View file

@ -2,27 +2,26 @@ use crate::{
common::CreateArticleForm,
frontend::{api::CLIENT, components::editor::EditorView},
};
use html::Textarea;
use leptos::*;
use leptos_router::Redirect;
use leptos::{html::Textarea, prelude::*};
use leptos_router::components::Redirect;
use leptos_use::{use_textarea_autosize, UseTextareaAutosizeReturn};
#[component]
pub fn CreateArticle() -> impl IntoView {
let (title, set_title) = create_signal(String::new());
let textarea_ref = create_node_ref::<Textarea>();
let (title, set_title) = signal(String::new());
let textarea_ref = NodeRef::<Textarea>::new();
let UseTextareaAutosizeReturn {
content,
set_content,
trigger_resize: _,
} = use_textarea_autosize(textarea_ref);
let (summary, set_summary) = create_signal(String::new());
let (create_response, set_create_response) = create_signal(None::<()>);
let (create_error, set_create_error) = create_signal(None::<String>);
let (wait_for_response, set_wait_for_response) = create_signal(false);
let (summary, set_summary) = signal(String::new());
let (create_response, set_create_response) = signal(None::<()>);
let (create_error, set_create_error) = signal(None::<String>);
let (wait_for_response, set_wait_for_response) = signal(false);
let button_is_disabled =
Signal::derive(move || wait_for_response.get() || summary.get().is_empty());
let submit_action = create_action(move |(title, text, summary): &(String, String, String)| {
let submit_action = Action::new(move |(title, text, summary): &(String, String, String)| {
let title = title.clone();
let text = text.clone();
let summary = summary.clone();
@ -94,7 +93,7 @@ pub fn CreateArticle() -> impl IntoView {
prop:disabled=move || button_is_disabled.get()
on:click=move |_| {
submit_action
.dispatch((title.get(), content.get(), summary.get()))
.dispatch((title.get(), content.get(), summary.get()));
}
>
Submit

View file

@ -9,9 +9,8 @@ use crate::{
pages::article_resource,
},
};
use html::Textarea;
use leptos::*;
use leptos_router::use_params_map;
use leptos::{html::Textarea, prelude::*};
use leptos_router::hooks::use_params_map;
use leptos_use::{use_textarea_autosize, UseTextareaAutosizeReturn};
#[derive(Clone, PartialEq)]
@ -26,12 +25,12 @@ const CONFLICT_MESSAGE: &str = "There was an edit conflict. Resolve it manually
#[component]
pub fn EditArticle() -> impl IntoView {
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 (edit_response, set_edit_response) = signal(EditResponse::None);
let (edit_error, set_edit_error) = signal(None::<String>);
let conflict_id = move || use_params_map().get_untracked().get("conflict_id").cloned();
let conflict_id = move || use_params_map().get_untracked().get("conflict_id").clone();
if let Some(conflict_id) = conflict_id() {
create_action(move |conflict_id: &String| {
Action::new(move |conflict_id: &String| {
let conflict_id = ConflictId(conflict_id.parse().unwrap());
async move {
let conflict = CLIENT
@ -52,17 +51,17 @@ pub fn EditArticle() -> impl IntoView {
.dispatch(conflict_id);
}
let textarea_ref = create_node_ref::<Textarea>();
let textarea_ref = NodeRef::<Textarea>::new();
let UseTextareaAutosizeReturn {
content,
set_content,
trigger_resize: _,
} = use_textarea_autosize(textarea_ref);
let (summary, set_summary) = create_signal(String::new());
let (wait_for_response, set_wait_for_response) = create_signal(false);
let (summary, set_summary) = signal(String::new());
let (wait_for_response, set_wait_for_response) = signal(false);
let button_is_disabled =
Signal::derive(move || wait_for_response.get() || summary.get().is_empty());
let submit_action = create_action(
let submit_action = Action::new(
move |(new_text, summary, article, edit_response): &(
String,
String,
@ -161,7 +160,7 @@ pub fn EditArticle() -> impl IntoView {
summary.get(),
article_.clone(),
edit_response.get(),
))
));
}
>

View file

@ -5,7 +5,7 @@ use crate::frontend::{
render_date_time,
user_link,
};
use leptos::*;
use leptos::prelude::*;
#[component]
pub fn ArticleHistory() -> impl IntoView {

View file

@ -2,15 +2,14 @@ use crate::{
common::ListArticlesForm,
frontend::{api::CLIENT, article_link, article_title, components::connect::ConnectView},
};
use html::Input;
use leptos::*;
use leptos::{html::Input, prelude::*};
#[component]
pub fn ListArticles() -> impl IntoView {
let (only_local, set_only_local) = create_signal(false);
let (only_local, set_only_local) = signal(false);
let button_only_local = create_node_ref::<Input>();
let button_all = create_node_ref::<Input>();
let articles = create_resource(
let articles = Resource::new(
move || only_local.get(),
|only_local| async move {
CLIENT

View file

@ -3,7 +3,7 @@ use crate::frontend::{
markdown::render_markdown,
pages::article_resource,
};
use leptos::*;
use leptos::prelude::*;
#[component]
pub fn ReadArticle() -> impl IntoView {

View file

@ -4,8 +4,8 @@ use crate::frontend::{
render_date_time,
user_link,
};
use leptos::*;
use leptos_router::*;
use leptos::prelude::*;
use leptos_router::hooks::use_params_map;
#[component]
pub fn EditDiff() -> impl IntoView {
@ -21,7 +21,7 @@ pub fn EditDiff() -> impl IntoView {
article
.get()
.map(|article| {
let hash = params.get_untracked().get("hash").cloned().unwrap();
let hash = params.get_untracked().get("hash").clone().unwrap();
let edit = article
.edits
.iter()

View file

@ -7,15 +7,15 @@ use crate::{
components::instance_follow_button::InstanceFollowButton,
},
};
use leptos::*;
use leptos_router::use_params_map;
use leptos::prelude::*;
use leptos_router::hooks::use_params_map;
use url::Url;
#[component]
pub fn InstanceDetails() -> impl IntoView {
let params = use_params_map();
let hostname = move || params.get().get("hostname").cloned().unwrap();
let instance_profile = create_resource(hostname, move |hostname| async move {
let hostname = move || params.get().get("hostname").clone().unwrap();
let instance_profile = Resource::new(hostname, move |hostname| async move {
let url = Url::parse(&format!("{}://{hostname}", http_protocol_str())).unwrap();
CLIENT.resolve_instance(url).await.unwrap()
});
@ -28,7 +28,7 @@ pub fn InstanceDetails() -> impl IntoView {
instance_profile
.get()
.map(|instance: DbInstance| {
let articles = create_resource(
let articles = Resource::new(
move || instance.id,
|instance_id| async move {
CLIENT

View file

@ -1,9 +1,9 @@
use crate::frontend::{api::CLIENT, components::connect::ConnectView};
use leptos::*;
use leptos::prelude::*;
#[component]
pub fn ListInstances() -> impl IntoView {
let instances = create_resource(
let instances = Resource::new(
move || (),
|_| async move { CLIENT.list_instances().await.unwrap() },
);
@ -21,14 +21,14 @@ pub fn ListInstances() -> impl IntoView {
.get()
.map(|a| {
a.into_iter()
.map(|i| {
.map(|ref i| {
view! {
<li>
<a
class="link text-lg"
href=format!("/instance/{}", i.domain)
>
{i.domain}
{i.domain.to_string()}
</a>
</li>
}

View file

@ -2,16 +2,16 @@ use crate::{
common::LoginUserForm,
frontend::{api::CLIENT, app::site, components::credentials::*},
};
use leptos::*;
use leptos_router::Redirect;
use leptos::prelude::*;
use leptos_router::components::Redirect;
#[component]
pub fn Login() -> impl IntoView {
let (login_response, set_login_response) = create_signal(None::<()>);
let (login_error, set_login_error) = create_signal(None::<String>);
let (wait_for_response, set_wait_for_response) = create_signal(false);
let (login_response, set_login_response) = signal(None::<()>);
let (login_error, set_login_error) = signal(None::<String>);
let (wait_for_response, set_wait_for_response) = signal(false);
let login_action = create_action(move |(email, password): &(String, String)| {
let login_action = Action::new(move |(email, password): &(String, String)| {
let username = email.to_string();
let password = password.to_string();
let credentials = LoginUserForm { username, password };

View file

@ -2,8 +2,8 @@ use crate::{
common::{ArticleView, GetArticleForm, MAIN_PAGE_NAME},
frontend::api::CLIENT,
};
use leptos::{create_resource, Resource, SignalGet};
use leptos_router::use_params_map;
use leptos::prelude::*;
use leptos_router::hooks::use_params_map;
pub(crate) mod article;
pub(crate) mod diff;
@ -14,10 +14,10 @@ pub(crate) mod register;
pub(crate) mod search;
pub(crate) mod user_profile;
fn article_resource() -> Resource<Option<String>, ArticleView> {
fn article_resource() -> Resource<ArticleView> {
let params = use_params_map();
let title = move || params.get().get("title").cloned();
create_resource(title, move |title| async move {
let title = move || params.get().get("title").clone();
Resource::new(title, move |title| async move {
let mut title = title.unwrap_or(MAIN_PAGE_NAME.to_string());
let mut domain = None;
if let Some((title_, domain_)) = title.clone().split_once('@') {

View file

@ -2,11 +2,11 @@ use crate::{
common::Notification,
frontend::{api::CLIENT, article_link, article_title},
};
use leptos::*;
use leptos::prelude::*;
#[component]
pub fn Notifications() -> impl IntoView {
let notifications = create_resource(
let notifications = Resource::new(
move || {},
|_| async move { CLIENT.notifications_list().await.unwrap() },
);
@ -43,7 +43,7 @@ pub fn Notifications() -> impl IntoView {
}
};
let notif_ = notif.clone();
let click_approve = create_action(move |_: &()| {
let click_approve = Action::new(move |_: &()| {
let notif_ = notif_.clone();
async move {
if let ArticleApprovalRequired(a) = notif_ {
@ -53,7 +53,7 @@ pub fn Notifications() -> impl IntoView {
}
});
let notif_ = notif.clone();
let click_reject = create_action(move |_: &()| {
let click_reject = Action::new(move |_: &()| {
let notif_ = notif_.clone();
async move {
match notif_ {
@ -76,13 +76,17 @@ pub fn Notifications() -> impl IntoView {
<button
class="btn btn-sm btn-outline"
style=my_style
on:click=move |_| click_approve.dispatch(())
on:click=move |_| {
click_approve.dispatch(());
}
>
Approve
</button>
<button
class="btn btn-sm btn-outline"
on:click=move |_| click_reject.dispatch(())
on:click=move |_| {
click_reject.dispatch(());
}
>
Reject
</button>

View file

@ -2,19 +2,20 @@ use crate::{
common::{LocalUserView, RegisterUserForm},
frontend::{api::CLIENT, app::site, components::credentials::*, error::MyResult},
};
use leptos::{logging::log, *};
use leptos::prelude::*;
use log::info;
#[component]
pub fn Register() -> impl IntoView {
let (register_response, set_register_response) = create_signal(None::<()>);
let (register_error, set_register_error) = create_signal(None::<String>);
let (wait_for_response, set_wait_for_response) = create_signal(false);
let (register_response, set_register_response) = signal(None::<()>);
let (register_error, set_register_error) = signal(None::<String>);
let (wait_for_response, set_wait_for_response) = signal(false);
let register_action = create_action(move |(email, password): &(String, String)| {
let register_action = Action::new(move |(email, password): &(String, String)| {
let username = email.to_string();
let password = password.to_string();
let credentials = RegisterUserForm { username, password };
log!("Try to register new account for {}", credentials.username);
info!("Try to register new account for {}", credentials.username);
async move {
set_wait_for_response.update(|w| *w = true);
let result: MyResult<LocalUserView> = CLIENT.register(credentials).await;

View file

@ -2,8 +2,8 @@ use crate::{
common::{DbArticle, DbInstance, SearchArticleForm},
frontend::{api::CLIENT, article_link, article_title},
};
use leptos::*;
use leptos_router::use_query_map;
use leptos::prelude::*;
use leptos_router::hooks::use_query_map;
use serde::{Deserialize, Serialize};
use url::Url;
@ -22,9 +22,9 @@ impl SearchResults {
#[component]
pub fn Search() -> impl IntoView {
let params = use_query_map();
let query = move || params.get().get("query").cloned().unwrap();
let (error, set_error) = create_signal(None::<String>);
let search_results = create_resource(query, move |query| async move {
let query = move || params.get().get("query").clone().unwrap();
let (error, set_error) = signal(None::<String>);
let search_results = Resource::new(query, move |query| async move {
set_error.set(None);
let mut search_results = SearchResults::default();
let url = Url::parse(&query);
@ -89,7 +89,7 @@ pub fn Search() -> impl IntoView {
view! {
<li>
<a class="link text-lg" href=format!("/instance/{domain}")>
{domain}
{domain.to_string()}
</a>
</li>
},

View file

@ -2,15 +2,15 @@ use crate::{
common::{DbPerson, GetUserForm},
frontend::{api::CLIENT, user_title},
};
use leptos::*;
use leptos_router::use_params_map;
use leptos::prelude::*;
use leptos_router::hooks::use_params_map;
#[component]
pub fn UserProfile() -> impl IntoView {
let params = use_params_map();
let name = move || params.get().get("name").cloned().unwrap();
let (error, set_error) = create_signal(None::<String>);
let user_profile = create_resource(name, move |mut name| async move {
let name = move || params.get().get("name").clone().unwrap();
let (error, set_error) = signal(None::<String>);
let user_profile = Resource::new(name, move |mut name| async move {
set_error.set(None);
let mut domain = None;
if let Some((title_, domain_)) = name.clone().split_once('@') {