mirror of
https://github.com/Nutomic/ibis.git
synced 2025-01-11 12:35:48 +00:00
wip: upgrade leptos
This commit is contained in:
parent
0d93ac31a3
commit
54951621e4
30 changed files with 599 additions and 531 deletions
760
Cargo.lock
generated
760
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
12
Cargo.toml
12
Cargo.toml
|
@ -32,8 +32,6 @@ ssr = [
|
||||||
]
|
]
|
||||||
hydrate = [
|
hydrate = [
|
||||||
"leptos/hydrate",
|
"leptos/hydrate",
|
||||||
"leptos_meta/hydrate",
|
|
||||||
"leptos_router/hydrate",
|
|
||||||
"katex/wasm-js",
|
"katex/wasm-js",
|
||||||
]
|
]
|
||||||
diesel-derive-newtype = ["dep:diesel-derive-newtype"]
|
diesel-derive-newtype = ["dep:diesel-derive-newtype"]
|
||||||
|
@ -61,9 +59,9 @@ unwrap_used = "deny"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.89"
|
anyhow = "1.0.89"
|
||||||
leptos = "0.6.15"
|
leptos = "0.7.0-rc1"
|
||||||
leptos_meta = "0.6.15"
|
leptos_meta = "0.7.0-rc1"
|
||||||
leptos_router = "0.6.15"
|
leptos_router = "0.7.0-rc1"
|
||||||
chrono = { version = "0.4.38", features = ["serde"] }
|
chrono = { version = "0.4.38", features = ["serde"] }
|
||||||
env_logger = { version = "0.11.5", default-features = false }
|
env_logger = { version = "0.11.5", default-features = false }
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
|
@ -88,7 +86,7 @@ markdown-it-heading-anchors = "0.3.0"
|
||||||
markdown-it-footnote = "0.2.0"
|
markdown-it-footnote = "0.2.0"
|
||||||
markdown-it-sub = "1.0.0"
|
markdown-it-sub = "1.0.0"
|
||||||
markdown-it-sup = "1.0.0"
|
markdown-it-sup = "1.0.0"
|
||||||
leptos-use = "0.13.6"
|
leptos-use = "0.14.0-rc2"
|
||||||
codee = "0.2.0"
|
codee = "0.2.0"
|
||||||
|
|
||||||
# backend-only features
|
# backend-only features
|
||||||
|
@ -111,7 +109,7 @@ diesel-derive-newtype = { version = "2.1.2", optional = true }
|
||||||
diesel_migrations = { version = "2.2.0", optional = true }
|
diesel_migrations = { version = "2.2.0", optional = true }
|
||||||
doku = { version = "0.21.1", optional = true }
|
doku = { version = "0.21.1", optional = true }
|
||||||
jsonwebtoken = { version = "9.3.0", 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 }
|
bcrypt = { version = "0.15.1", optional = true }
|
||||||
diffy = { version = "0.4.0", optional = true }
|
diffy = { version = "0.4.0", optional = true }
|
||||||
enum_delegate = { version = "0.2.0", optional = true }
|
enum_delegate = { version = "0.2.0", optional = true }
|
||||||
|
|
|
@ -5,7 +5,7 @@ use axum::{
|
||||||
response::{IntoResponse, Response},
|
response::{IntoResponse, Response},
|
||||||
};
|
};
|
||||||
use axum_macros::debug_handler;
|
use axum_macros::debug_handler;
|
||||||
use leptos::LeptosOptions;
|
use leptos::prelude::*;
|
||||||
use tower::ServiceExt;
|
use tower::ServiceExt;
|
||||||
use tower_http::services::ServeDir;
|
use tower_http::services::ServeDir;
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ use federation::objects::{
|
||||||
articles_collection::local_articles_url,
|
articles_collection::local_articles_url,
|
||||||
instance_collection::linked_instances_url,
|
instance_collection::linked_instances_url,
|
||||||
};
|
};
|
||||||
use leptos::*;
|
use leptos::prelude::*;
|
||||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||||
use log::info;
|
use log::info;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
@ -93,7 +93,7 @@ pub async fn start(config: IbisConfig, override_hostname: Option<SocketAddr>) ->
|
||||||
setup(&data.to_request_data()).await?;
|
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;
|
let mut addr = leptos_options.site_addr;
|
||||||
if let Some(override_hostname) = override_hostname {
|
if let Some(override_hostname) = override_hostname {
|
||||||
addr = override_hostname;
|
addr = override_hostname;
|
||||||
|
|
|
@ -3,6 +3,7 @@ pub mod utils;
|
||||||
pub mod validation;
|
pub mod validation;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
use codee::{Decoder, Encoder};
|
||||||
use newtypes::{ArticleId, ConflictId, EditId, InstanceId, PersonId};
|
use newtypes::{ArticleId, ConflictId, EditId, InstanceId, PersonId};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
@ -351,6 +352,25 @@ pub struct SiteView {
|
||||||
pub config: Options,
|
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]
|
#[test]
|
||||||
fn test_edit_versions() {
|
fn test_edit_versions() {
|
||||||
let default = EditVersion::default();
|
let default = EditVersion::default();
|
||||||
|
|
|
@ -56,7 +56,7 @@ impl ApiClient {
|
||||||
}
|
}
|
||||||
#[cfg(feature = "ssr")]
|
#[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();
|
let leptos_options = get_config_from_str(include_str!("../../Cargo.toml")).unwrap();
|
||||||
hostname = leptos_options.site_addr.to_string();
|
hostname = leptos_options.site_addr.to_string();
|
||||||
ssl = false;
|
ssl = false;
|
||||||
|
@ -287,7 +287,7 @@ where
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
{
|
{
|
||||||
use crate::common::{Auth, AUTH_COOKIE};
|
use crate::common::{Auth, AUTH_COOKIE};
|
||||||
use leptos::use_context;
|
use leptos::prelude::use_context;
|
||||||
use reqwest::header::HeaderName;
|
use reqwest::header::HeaderName;
|
||||||
|
|
||||||
let auth = use_context::<Auth>();
|
let auth = use_context::<Auth>();
|
||||||
|
|
|
@ -23,9 +23,12 @@ use crate::{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use leptos::*;
|
use leptos::prelude::*;
|
||||||
use leptos_meta::{provide_meta_context, *};
|
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> {
|
pub fn site() -> Resource<(), SiteView> {
|
||||||
use_context::<Resource<(), SiteView>>().unwrap()
|
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> {
|
pub trait DefaultResource<T> {
|
||||||
fn with_default<O>(&self, f: impl FnOnce(&T) -> O) -> O;
|
fn with_default<O>(&self, f: impl FnOnce(&T) -> O) -> O;
|
||||||
}
|
}
|
||||||
|
@ -58,9 +62,8 @@ impl<T: Default> DefaultResource<T> for Resource<(), T> {
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn App() -> impl IntoView {
|
pub fn App() -> impl IntoView {
|
||||||
// TODO: should create_resource() but then things break
|
// TODO: should Resource::new() but then things break
|
||||||
let site_resource =
|
let site_resource = LocalResource::new(|| async move { CLIENT.site().await.unwrap() });
|
||||||
create_local_resource(move || (), |_| async move { CLIENT.site().await.unwrap() });
|
|
||||||
provide_context(site_resource);
|
provide_context(site_resource);
|
||||||
provide_meta_context();
|
provide_meta_context();
|
||||||
|
|
||||||
|
@ -68,33 +71,33 @@ pub fn App() -> impl IntoView {
|
||||||
provide_context(darkmode.clone());
|
provide_context(darkmode.clone());
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<Html attr:data-theme=darkmode.theme class="h-full" />
|
<Html attr:data-theme=darkmode.theme {..} class="h-full" />
|
||||||
<Body class="h-full max-sm:flex max-sm:flex-col" />
|
<Body {..} class="h-full max-sm:flex max-sm:flex-col" />
|
||||||
<>
|
<>
|
||||||
<Stylesheet id="ibis" href="/pkg/ibis.css" />
|
<Stylesheet id="ibis" href="/pkg/ibis.css" />
|
||||||
<Stylesheet id="katex" href="/katex.min.css" />
|
<Stylesheet id="katex" href="/katex.min.css" />
|
||||||
<Router>
|
<Router>
|
||||||
<Nav />
|
<Nav />
|
||||||
<main class="p-4 md:ml-64">
|
<main class="p-4 md:ml-64">
|
||||||
<Routes>
|
<Routes fallback=|| "Page not found.".into_view()>
|
||||||
<Route path="/" view=ReadArticle />
|
<Route path=path!("/") view=ReadArticle />
|
||||||
<Route path="/article/:title" view=ReadArticle />
|
<Route path=path!("/article/:title") view=ReadArticle />
|
||||||
<Route path="/article/:title/history" view=ArticleHistory />
|
<Route path=path!("/article/:title/history") view=ArticleHistory />
|
||||||
<Route path="/article/:title/edit/:conflict_id?" view=EditArticle />
|
<Route path=path!("/article/:title/edit/:conflict_id?") view=EditArticle />
|
||||||
<Route path="/article/:title/actions" view=ArticleActions />
|
<Route path=path!("/article/:title/actions") view=ArticleActions />
|
||||||
<Route path="/article/:title/diff/:hash" view=EditDiff />
|
<Route path=path!("/article/:title/diff/:hash") view=EditDiff />
|
||||||
// TODO: use protected route, otherwise user can view
|
// TODO: use protected route, otherwise user can view
|
||||||
// /article/create without login
|
// /article/create without login
|
||||||
//https://github.com/leptos-rs/leptos/blob/leptos_0.7/examples/router/src/lib.rs#L51
|
// https://github.com/leptos-rs/leptos/blob/leptos_0.7/examples/router/src/lib.rs#L51
|
||||||
<Route path="/article/create" view=CreateArticle />
|
<Route path=path!("/article/create") view=CreateArticle />
|
||||||
<Route path="/article/list" view=ListArticles />
|
<Route path=path!("/article/list") view=ListArticles />
|
||||||
<Route path="/instance/:hostname" view=InstanceDetails />
|
<Route path=path!("/instance/:hostname") view=InstanceDetails />
|
||||||
<Route path="/instance/list" view=ListInstances />
|
<Route path=path!("/instance/list") view=ListInstances />
|
||||||
<Route path="/user/:name" view=UserProfile />
|
<Route path=path!("/user/:name") view=UserProfile />
|
||||||
<Route path="/login" view=Login />
|
<Route path=path!("/login") view=Login />
|
||||||
<Route path="/register" view=Register />
|
<Route path=path!("/register") view=Register />
|
||||||
<Route path="/search" view=Search />
|
<Route path=path!("/search") view=Search />
|
||||||
<Route path="/notifications" view=Notifications />
|
<Route path=path!("/notifications") view=Notifications />
|
||||||
</Routes>
|
</Routes>
|
||||||
</main>
|
</main>
|
||||||
</Router>
|
</Router>
|
||||||
|
|
|
@ -8,8 +8,8 @@ use crate::{
|
||||||
components::instance_follow_button::InstanceFollowButton,
|
components::instance_follow_button::InstanceFollowButton,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use leptos::*;
|
use leptos::prelude::*;
|
||||||
use leptos_router::*;
|
use leptos_router::components::A;
|
||||||
|
|
||||||
pub enum ActiveTab {
|
pub enum ActiveTab {
|
||||||
Read,
|
Read,
|
||||||
|
@ -19,10 +19,7 @@ pub enum ActiveTab {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ArticleNav(
|
pub fn ArticleNav(article: Resource<ArticleView>, active_tab: ActiveTab) -> impl IntoView {
|
||||||
article: Resource<Option<String>, ArticleView>,
|
|
||||||
active_tab: ActiveTab,
|
|
||||||
) -> impl IntoView {
|
|
||||||
let tab_classes = tab_classes(&active_tab);
|
let tab_classes = tab_classes(&active_tab);
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
|
@ -32,7 +29,7 @@ pub fn ArticleNav(
|
||||||
.get()
|
.get()
|
||||||
.map(|article_| {
|
.map(|article_| {
|
||||||
let title = article_title(&article_.article);
|
let title = article_title(&article_.article);
|
||||||
let instance = create_resource(
|
let instance = Resource::new(
|
||||||
move || article_.article.instance_id,
|
move || article_.article.instance_id,
|
||||||
move |instance_id| async move {
|
move |instance_id| async move {
|
||||||
let form = GetInstance {
|
let form = GetInstance {
|
||||||
|
@ -46,24 +43,33 @@ pub fn ArticleNav(
|
||||||
let protected = article_.article.protected;
|
let protected = article_.article.protected;
|
||||||
view! {
|
view! {
|
||||||
<div role="tablist" class="tabs tabs-lifted">
|
<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"
|
"Read"
|
||||||
</A>
|
</A>
|
||||||
<A class=tab_classes.history href=format!("{article_link}/history")>
|
<A
|
||||||
|
href=format!("{article_link}/history")
|
||||||
|
{..}
|
||||||
|
class=tab_classes.history
|
||||||
|
>
|
||||||
"History"
|
"History"
|
||||||
</A>
|
</A>
|
||||||
<Show when=move || {
|
<Show when=move || {
|
||||||
is_logged_in()
|
is_logged_in()
|
||||||
&& can_edit_article(&article_.article, is_admin()).is_ok()
|
&& 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"
|
"Edit"
|
||||||
</A>
|
</A>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when=is_logged_in>
|
<Show when=is_logged_in>
|
||||||
<A
|
<A
|
||||||
class=tab_classes.actions
|
|
||||||
href=format!("{article_link_}/actions")
|
href=format!("{article_link_}/actions")
|
||||||
|
{..}
|
||||||
|
class=tab_classes.actions
|
||||||
>
|
>
|
||||||
"Actions"
|
"Actions"
|
||||||
</A>
|
</A>
|
||||||
|
|
|
@ -1,10 +1,21 @@
|
||||||
use crate::frontend::api::CLIENT;
|
use crate::frontend::api::CLIENT;
|
||||||
use leptos::{component, *};
|
use codee::{Decoder, Encoder};
|
||||||
|
use leptos::prelude::*;
|
||||||
|
use std::fmt::Debug;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ConnectView<T: Clone + 'static, R: 'static>(res: Resource<T, R>) -> impl IntoView {
|
pub fn ConnectView<T, R>(res: Resource<T, R>) -> impl IntoView
|
||||||
let connect_ibis_wiki = create_action(move |_: &()| async move {
|
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
|
CLIENT
|
||||||
.resolve_instance(Url::parse("https://ibis.wiki").unwrap())
|
.resolve_instance(Url::parse("https://ibis.wiki").unwrap())
|
||||||
.await
|
.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">
|
<div class="flex justify-center h-screen">
|
||||||
<button
|
<button
|
||||||
class="btn btn-primary place-self-center"
|
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
|
Connect with ibis.wiki
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use leptos::{ev, *};
|
use leptos::{ev::KeyboardEvent, prelude::*};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn CredentialsForm(
|
pub fn CredentialsForm(
|
||||||
|
@ -8,8 +8,8 @@ pub fn CredentialsForm(
|
||||||
error: Signal<Option<String>>,
|
error: Signal<Option<String>>,
|
||||||
disabled: Signal<bool>,
|
disabled: Signal<bool>,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
let (password, set_password) = create_signal(String::new());
|
let (password, set_password) = signal(String::new());
|
||||||
let (username, set_username) = create_signal(String::new());
|
let (username, set_username) = signal(String::new());
|
||||||
|
|
||||||
let dispatch_action = move || action.dispatch((username.get(), password.get()));
|
let dispatch_action = move || action.dispatch((username.get(), password.get()));
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ pub fn CredentialsForm(
|
||||||
required
|
required
|
||||||
placeholder="Username"
|
placeholder="Username"
|
||||||
prop:disabled=move || disabled.get()
|
prop:disabled=move || disabled.get()
|
||||||
on:keyup=move |ev: ev::KeyboardEvent| {
|
on:keyup=move |ev: KeyboardEvent| {
|
||||||
let val = event_target_value(&ev);
|
let val = event_target_value(&ev);
|
||||||
set_username.update(|v| *v = val);
|
set_username.update(|v| *v = val);
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ pub fn CredentialsForm(
|
||||||
required
|
required
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
prop:disabled=move || disabled.get()
|
prop:disabled=move || disabled.get()
|
||||||
on:keyup=move |ev: ev::KeyboardEvent| {
|
on:keyup=move |ev: KeyboardEvent| {
|
||||||
match &*ev.key() {
|
match &*ev.key() {
|
||||||
"Enter" => {
|
"Enter" => {
|
||||||
dispatch_action();
|
dispatch_action();
|
||||||
|
@ -73,7 +73,9 @@ pub fn CredentialsForm(
|
||||||
<button
|
<button
|
||||||
class="btn btn-primary my-2"
|
class="btn btn-primary my-2"
|
||||||
prop:disabled=move || button_is_disabled.get()
|
prop:disabled=move || button_is_disabled.get()
|
||||||
on:click=move |_| dispatch_action()
|
on:click=move |_| {
|
||||||
|
dispatch_action();
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{action_label}
|
{action_label}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
use crate::frontend::markdown::render_markdown;
|
use crate::frontend::markdown::render_markdown;
|
||||||
use html::Textarea;
|
use leptos::{html::Textarea, prelude::*};
|
||||||
use leptos::*;
|
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn EditorView(
|
pub fn EditorView(
|
||||||
// this param gives a false warning about being unused, ignore that
|
textarea_ref: NodeRef<Textarea>,
|
||||||
#[allow(unused)] textarea_ref: NodeRef<Textarea>,
|
|
||||||
content: Signal<String>,
|
content: Signal<String>,
|
||||||
set_content: WriteSignal<String>,
|
set_content: WriteSignal<String>,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
let (preview, set_preview) = create_signal(render_markdown(&content.get_untracked()));
|
let (preview, set_preview) = signal(render_markdown(&content.get_untracked()));
|
||||||
let (show_preview, set_show_preview) = create_signal(false);
|
let (show_preview, set_show_preview) = signal(false);
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -5,11 +5,11 @@ use crate::{
|
||||||
app::{site, DefaultResource},
|
app::{site, DefaultResource},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use leptos::{component, *};
|
use leptos::prelude::*;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn InstanceFollowButton(instance: DbInstance) -> impl IntoView {
|
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;
|
let instance_id = *instance_id;
|
||||||
async move {
|
async move {
|
||||||
let form = FollowInstance { id: instance_id };
|
let form = FollowInstance { id: instance_id };
|
||||||
|
@ -38,7 +38,9 @@ pub fn InstanceFollowButton(instance: DbInstance) -> impl IntoView {
|
||||||
view! {
|
view! {
|
||||||
<button
|
<button
|
||||||
class=class_
|
class=class_
|
||||||
on:click=move |_| follow_action.dispatch(instance.id)
|
on:click=move |_| {
|
||||||
|
follow_action.dispatch(instance.id);
|
||||||
|
}
|
||||||
prop:disabled=move || is_following
|
prop:disabled=move || is_following
|
||||||
title="Follow the instance so that new edits are synchronized to your instance."
|
title="Follow the instance so that new edits are synchronized to your instance."
|
||||||
>
|
>
|
||||||
|
|
|
@ -3,21 +3,21 @@ use crate::frontend::{
|
||||||
app::{is_logged_in, site, DefaultResource},
|
app::{is_logged_in, site, DefaultResource},
|
||||||
dark_mode::DarkMode,
|
dark_mode::DarkMode,
|
||||||
};
|
};
|
||||||
use leptos::{component, view, IntoView, *};
|
use leptos::{component, prelude::*, view, IntoView, *};
|
||||||
use leptos_router::*;
|
use leptos_router::{components::A, hooks::use_navigate};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Nav() -> impl IntoView {
|
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();
|
CLIENT.logout().await.unwrap();
|
||||||
site().refetch();
|
site().refetch();
|
||||||
});
|
});
|
||||||
let notification_count = create_resource(
|
let notification_count = Resource::new(
|
||||||
|| (),
|
|| (),
|
||||||
move |_| async move { CLIENT.notifications_count().await.unwrap_or_default() },
|
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>();
|
let mut dark_mode = expect_context::<DarkMode>();
|
||||||
view! {
|
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">
|
<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"
|
class="form-control m-0 p-1"
|
||||||
on:submit=move |ev| {
|
on:submit=move |ev| {
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
let navigate = leptos_router::use_navigate();
|
let navigate = use_navigate();
|
||||||
let query = search_query.get();
|
let query = search_query.get();
|
||||||
if !query.is_empty() {
|
if !query.is_empty() {
|
||||||
navigate(
|
navigate(
|
||||||
|
@ -127,7 +127,9 @@ pub fn Nav() -> impl IntoView {
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
class="btn btn-outline btn-xs w-min self-center"
|
class="btn btn-outline btn-xs w-min self-center"
|
||||||
on:click=move |_| logout_action.dispatch(())
|
on:click=move |_| {
|
||||||
|
logout_action.dispatch(());
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Logout
|
Logout
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use chrono::{Duration, Local};
|
use chrono::{Duration, Local};
|
||||||
use codee::string::FromToStringCodec;
|
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};
|
use leptos_use::{use_cookie_with_options, use_preferred_dark, SameSite, UseCookieOptions};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::common::{utils::extract_domain, DbArticle, DbPerson};
|
use crate::common::{utils::extract_domain, DbArticle, DbPerson};
|
||||||
use chrono::{DateTime, Local, Utc};
|
use chrono::{DateTime, Local, Utc};
|
||||||
use leptos::*;
|
use leptos::prelude::*;
|
||||||
|
|
||||||
pub mod api;
|
pub mod api;
|
||||||
pub mod app;
|
pub mod app;
|
||||||
|
|
|
@ -9,16 +9,16 @@ use crate::{
|
||||||
DbArticle,
|
DbArticle,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use leptos::*;
|
use leptos::{ev::KeyboardEvent, prelude::*};
|
||||||
use leptos_router::Redirect;
|
use leptos_router::components::Redirect;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ArticleActions() -> impl IntoView {
|
pub fn ArticleActions() -> impl IntoView {
|
||||||
let article = article_resource();
|
let article = article_resource();
|
||||||
let (new_title, set_new_title) = create_signal(String::new());
|
let (new_title, set_new_title) = signal(String::new());
|
||||||
let (fork_response, set_fork_response) = create_signal(Option::<DbArticle>::None);
|
let (fork_response, set_fork_response) = signal(Option::<DbArticle>::None);
|
||||||
let (error, set_error) = create_signal(None::<String>);
|
let (error, set_error) = signal(None::<String>);
|
||||||
let fork_action = create_action(move |(article_id, new_title): &(ArticleId, String)| {
|
let fork_action = Action::new(move |(article_id, new_title): &(ArticleId, String)| {
|
||||||
let params = ForkArticleForm {
|
let params = ForkArticleForm {
|
||||||
article_id: *article_id,
|
article_id: *article_id,
|
||||||
new_title: new_title.to_string(),
|
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 {
|
let params = ProtectArticleForm {
|
||||||
article_id: *id,
|
article_id: *id,
|
||||||
protected: !protected,
|
protected: !protected,
|
||||||
|
@ -72,7 +72,7 @@ pub fn ArticleActions() -> impl IntoView {
|
||||||
class="btn btn-secondary"
|
class="btn btn-secondary"
|
||||||
on:click=move |_| {
|
on:click=move |_| {
|
||||||
protect_action
|
protect_action
|
||||||
.dispatch((article.article.id, article.article.protected))
|
.dispatch((article.article.id, article.article.protected));
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Toggle Article Protection
|
Toggle Article Protection
|
||||||
|
@ -82,7 +82,7 @@ pub fn ArticleActions() -> impl IntoView {
|
||||||
<input
|
<input
|
||||||
class="input"
|
class="input"
|
||||||
placeholder="New Title"
|
placeholder="New Title"
|
||||||
on:keyup=move |ev: ev::KeyboardEvent| {
|
on:keyup=move |ev: KeyboardEvent| {
|
||||||
let val = event_target_value(&ev);
|
let val = event_target_value(&ev);
|
||||||
set_new_title.update(|v| *v = val);
|
set_new_title.update(|v| *v = val);
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,7 @@ pub fn ArticleActions() -> impl IntoView {
|
||||||
class="btn"
|
class="btn"
|
||||||
disabled=move || new_title.get().is_empty()
|
disabled=move || new_title.get().is_empty()
|
||||||
on:click=move |_| {
|
on:click=move |_| {
|
||||||
fork_action.dispatch((article.article.id, new_title.get()))
|
fork_action.dispatch((article.article.id, new_title.get()));
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
||||||
|
|
|
@ -2,27 +2,26 @@ use crate::{
|
||||||
common::CreateArticleForm,
|
common::CreateArticleForm,
|
||||||
frontend::{api::CLIENT, components::editor::EditorView},
|
frontend::{api::CLIENT, components::editor::EditorView},
|
||||||
};
|
};
|
||||||
use html::Textarea;
|
use leptos::{html::Textarea, prelude::*};
|
||||||
use leptos::*;
|
use leptos_router::components::Redirect;
|
||||||
use leptos_router::Redirect;
|
|
||||||
use leptos_use::{use_textarea_autosize, UseTextareaAutosizeReturn};
|
use leptos_use::{use_textarea_autosize, UseTextareaAutosizeReturn};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn CreateArticle() -> impl IntoView {
|
pub fn CreateArticle() -> impl IntoView {
|
||||||
let (title, set_title) = create_signal(String::new());
|
let (title, set_title) = signal(String::new());
|
||||||
let textarea_ref = create_node_ref::<Textarea>();
|
let textarea_ref = NodeRef::<Textarea>::new();
|
||||||
let UseTextareaAutosizeReturn {
|
let UseTextareaAutosizeReturn {
|
||||||
content,
|
content,
|
||||||
set_content,
|
set_content,
|
||||||
trigger_resize: _,
|
trigger_resize: _,
|
||||||
} = use_textarea_autosize(textarea_ref);
|
} = use_textarea_autosize(textarea_ref);
|
||||||
let (summary, set_summary) = create_signal(String::new());
|
let (summary, set_summary) = signal(String::new());
|
||||||
let (create_response, set_create_response) = create_signal(None::<()>);
|
let (create_response, set_create_response) = signal(None::<()>);
|
||||||
let (create_error, set_create_error) = create_signal(None::<String>);
|
let (create_error, set_create_error) = signal(None::<String>);
|
||||||
let (wait_for_response, set_wait_for_response) = create_signal(false);
|
let (wait_for_response, set_wait_for_response) = signal(false);
|
||||||
let button_is_disabled =
|
let button_is_disabled =
|
||||||
Signal::derive(move || wait_for_response.get() || summary.get().is_empty());
|
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 title = title.clone();
|
||||||
let text = text.clone();
|
let text = text.clone();
|
||||||
let summary = summary.clone();
|
let summary = summary.clone();
|
||||||
|
@ -94,7 +93,7 @@ pub fn CreateArticle() -> impl IntoView {
|
||||||
prop:disabled=move || button_is_disabled.get()
|
prop:disabled=move || button_is_disabled.get()
|
||||||
on:click=move |_| {
|
on:click=move |_| {
|
||||||
submit_action
|
submit_action
|
||||||
.dispatch((title.get(), content.get(), summary.get()))
|
.dispatch((title.get(), content.get(), summary.get()));
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
|
|
|
@ -9,9 +9,8 @@ use crate::{
|
||||||
pages::article_resource,
|
pages::article_resource,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use html::Textarea;
|
use leptos::{html::Textarea, prelude::*};
|
||||||
use leptos::*;
|
use leptos_router::hooks::use_params_map;
|
||||||
use leptos_router::use_params_map;
|
|
||||||
use leptos_use::{use_textarea_autosize, UseTextareaAutosizeReturn};
|
use leptos_use::{use_textarea_autosize, UseTextareaAutosizeReturn};
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
|
@ -26,12 +25,12 @@ const CONFLICT_MESSAGE: &str = "There was an edit conflict. Resolve it manually
|
||||||
#[component]
|
#[component]
|
||||||
pub fn EditArticle() -> impl IntoView {
|
pub fn EditArticle() -> impl IntoView {
|
||||||
let article = article_resource();
|
let article = article_resource();
|
||||||
let (edit_response, set_edit_response) = create_signal(EditResponse::None);
|
let (edit_response, set_edit_response) = signal(EditResponse::None);
|
||||||
let (edit_error, set_edit_error) = create_signal(None::<String>);
|
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() {
|
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());
|
let conflict_id = ConflictId(conflict_id.parse().unwrap());
|
||||||
async move {
|
async move {
|
||||||
let conflict = CLIENT
|
let conflict = CLIENT
|
||||||
|
@ -52,17 +51,17 @@ pub fn EditArticle() -> impl IntoView {
|
||||||
.dispatch(conflict_id);
|
.dispatch(conflict_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
let textarea_ref = create_node_ref::<Textarea>();
|
let textarea_ref = NodeRef::<Textarea>::new();
|
||||||
let UseTextareaAutosizeReturn {
|
let UseTextareaAutosizeReturn {
|
||||||
content,
|
content,
|
||||||
set_content,
|
set_content,
|
||||||
trigger_resize: _,
|
trigger_resize: _,
|
||||||
} = use_textarea_autosize(textarea_ref);
|
} = use_textarea_autosize(textarea_ref);
|
||||||
let (summary, set_summary) = create_signal(String::new());
|
let (summary, set_summary) = signal(String::new());
|
||||||
let (wait_for_response, set_wait_for_response) = create_signal(false);
|
let (wait_for_response, set_wait_for_response) = signal(false);
|
||||||
let button_is_disabled =
|
let button_is_disabled =
|
||||||
Signal::derive(move || wait_for_response.get() || summary.get().is_empty());
|
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): &(
|
move |(new_text, summary, article, edit_response): &(
|
||||||
String,
|
String,
|
||||||
String,
|
String,
|
||||||
|
@ -161,7 +160,7 @@ pub fn EditArticle() -> impl IntoView {
|
||||||
summary.get(),
|
summary.get(),
|
||||||
article_.clone(),
|
article_.clone(),
|
||||||
edit_response.get(),
|
edit_response.get(),
|
||||||
))
|
));
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::frontend::{
|
||||||
render_date_time,
|
render_date_time,
|
||||||
user_link,
|
user_link,
|
||||||
};
|
};
|
||||||
use leptos::*;
|
use leptos::prelude::*;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ArticleHistory() -> impl IntoView {
|
pub fn ArticleHistory() -> impl IntoView {
|
||||||
|
|
|
@ -2,15 +2,14 @@ use crate::{
|
||||||
common::ListArticlesForm,
|
common::ListArticlesForm,
|
||||||
frontend::{api::CLIENT, article_link, article_title, components::connect::ConnectView},
|
frontend::{api::CLIENT, article_link, article_title, components::connect::ConnectView},
|
||||||
};
|
};
|
||||||
use html::Input;
|
use leptos::{html::Input, prelude::*};
|
||||||
use leptos::*;
|
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ListArticles() -> impl IntoView {
|
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_only_local = create_node_ref::<Input>();
|
||||||
let button_all = create_node_ref::<Input>();
|
let button_all = create_node_ref::<Input>();
|
||||||
let articles = create_resource(
|
let articles = Resource::new(
|
||||||
move || only_local.get(),
|
move || only_local.get(),
|
||||||
|only_local| async move {
|
|only_local| async move {
|
||||||
CLIENT
|
CLIENT
|
||||||
|
|
|
@ -3,7 +3,7 @@ use crate::frontend::{
|
||||||
markdown::render_markdown,
|
markdown::render_markdown,
|
||||||
pages::article_resource,
|
pages::article_resource,
|
||||||
};
|
};
|
||||||
use leptos::*;
|
use leptos::prelude::*;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ReadArticle() -> impl IntoView {
|
pub fn ReadArticle() -> impl IntoView {
|
||||||
|
|
|
@ -4,8 +4,8 @@ use crate::frontend::{
|
||||||
render_date_time,
|
render_date_time,
|
||||||
user_link,
|
user_link,
|
||||||
};
|
};
|
||||||
use leptos::*;
|
use leptos::prelude::*;
|
||||||
use leptos_router::*;
|
use leptos_router::hooks::use_params_map;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn EditDiff() -> impl IntoView {
|
pub fn EditDiff() -> impl IntoView {
|
||||||
|
@ -21,7 +21,7 @@ pub fn EditDiff() -> impl IntoView {
|
||||||
article
|
article
|
||||||
.get()
|
.get()
|
||||||
.map(|article| {
|
.map(|article| {
|
||||||
let hash = params.get_untracked().get("hash").cloned().unwrap();
|
let hash = params.get_untracked().get("hash").clone().unwrap();
|
||||||
let edit = article
|
let edit = article
|
||||||
.edits
|
.edits
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -7,15 +7,15 @@ use crate::{
|
||||||
components::instance_follow_button::InstanceFollowButton,
|
components::instance_follow_button::InstanceFollowButton,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use leptos::*;
|
use leptos::prelude::*;
|
||||||
use leptos_router::use_params_map;
|
use leptos_router::hooks::use_params_map;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn InstanceDetails() -> impl IntoView {
|
pub fn InstanceDetails() -> impl IntoView {
|
||||||
let params = use_params_map();
|
let params = use_params_map();
|
||||||
let hostname = move || params.get().get("hostname").cloned().unwrap();
|
let hostname = move || params.get().get("hostname").clone().unwrap();
|
||||||
let instance_profile = create_resource(hostname, move |hostname| async move {
|
let instance_profile = Resource::new(hostname, move |hostname| async move {
|
||||||
let url = Url::parse(&format!("{}://{hostname}", http_protocol_str())).unwrap();
|
let url = Url::parse(&format!("{}://{hostname}", http_protocol_str())).unwrap();
|
||||||
CLIENT.resolve_instance(url).await.unwrap()
|
CLIENT.resolve_instance(url).await.unwrap()
|
||||||
});
|
});
|
||||||
|
@ -28,7 +28,7 @@ pub fn InstanceDetails() -> impl IntoView {
|
||||||
instance_profile
|
instance_profile
|
||||||
.get()
|
.get()
|
||||||
.map(|instance: DbInstance| {
|
.map(|instance: DbInstance| {
|
||||||
let articles = create_resource(
|
let articles = Resource::new(
|
||||||
move || instance.id,
|
move || instance.id,
|
||||||
|instance_id| async move {
|
|instance_id| async move {
|
||||||
CLIENT
|
CLIENT
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use crate::frontend::{api::CLIENT, components::connect::ConnectView};
|
use crate::frontend::{api::CLIENT, components::connect::ConnectView};
|
||||||
use leptos::*;
|
use leptos::prelude::*;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ListInstances() -> impl IntoView {
|
pub fn ListInstances() -> impl IntoView {
|
||||||
let instances = create_resource(
|
let instances = Resource::new(
|
||||||
move || (),
|
move || (),
|
||||||
|_| async move { CLIENT.list_instances().await.unwrap() },
|
|_| async move { CLIENT.list_instances().await.unwrap() },
|
||||||
);
|
);
|
||||||
|
@ -21,14 +21,14 @@ pub fn ListInstances() -> impl IntoView {
|
||||||
.get()
|
.get()
|
||||||
.map(|a| {
|
.map(|a| {
|
||||||
a.into_iter()
|
a.into_iter()
|
||||||
.map(|i| {
|
.map(|ref i| {
|
||||||
view! {
|
view! {
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
class="link text-lg"
|
class="link text-lg"
|
||||||
href=format!("/instance/{}", i.domain)
|
href=format!("/instance/{}", i.domain)
|
||||||
>
|
>
|
||||||
{i.domain}
|
{i.domain.to_string()}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,16 @@ use crate::{
|
||||||
common::LoginUserForm,
|
common::LoginUserForm,
|
||||||
frontend::{api::CLIENT, app::site, components::credentials::*},
|
frontend::{api::CLIENT, app::site, components::credentials::*},
|
||||||
};
|
};
|
||||||
use leptos::*;
|
use leptos::prelude::*;
|
||||||
use leptos_router::Redirect;
|
use leptos_router::components::Redirect;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Login() -> impl IntoView {
|
pub fn Login() -> impl IntoView {
|
||||||
let (login_response, set_login_response) = create_signal(None::<()>);
|
let (login_response, set_login_response) = signal(None::<()>);
|
||||||
let (login_error, set_login_error) = create_signal(None::<String>);
|
let (login_error, set_login_error) = signal(None::<String>);
|
||||||
let (wait_for_response, set_wait_for_response) = create_signal(false);
|
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 username = email.to_string();
|
||||||
let password = password.to_string();
|
let password = password.to_string();
|
||||||
let credentials = LoginUserForm { username, password };
|
let credentials = LoginUserForm { username, password };
|
||||||
|
|
|
@ -2,8 +2,8 @@ use crate::{
|
||||||
common::{ArticleView, GetArticleForm, MAIN_PAGE_NAME},
|
common::{ArticleView, GetArticleForm, MAIN_PAGE_NAME},
|
||||||
frontend::api::CLIENT,
|
frontend::api::CLIENT,
|
||||||
};
|
};
|
||||||
use leptos::{create_resource, Resource, SignalGet};
|
use leptos::prelude::*;
|
||||||
use leptos_router::use_params_map;
|
use leptos_router::hooks::use_params_map;
|
||||||
|
|
||||||
pub(crate) mod article;
|
pub(crate) mod article;
|
||||||
pub(crate) mod diff;
|
pub(crate) mod diff;
|
||||||
|
@ -14,10 +14,10 @@ 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() -> Resource<Option<String>, ArticleView> {
|
fn article_resource() -> Resource<ArticleView> {
|
||||||
let params = use_params_map();
|
let params = use_params_map();
|
||||||
let title = move || params.get().get("title").cloned();
|
let title = move || params.get().get("title").clone();
|
||||||
create_resource(title, move |title| async move {
|
Resource::new(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;
|
||||||
if let Some((title_, domain_)) = title.clone().split_once('@') {
|
if let Some((title_, domain_)) = title.clone().split_once('@') {
|
||||||
|
|
|
@ -2,11 +2,11 @@ use crate::{
|
||||||
common::Notification,
|
common::Notification,
|
||||||
frontend::{api::CLIENT, article_link, article_title},
|
frontend::{api::CLIENT, article_link, article_title},
|
||||||
};
|
};
|
||||||
use leptos::*;
|
use leptos::prelude::*;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Notifications() -> impl IntoView {
|
pub fn Notifications() -> impl IntoView {
|
||||||
let notifications = create_resource(
|
let notifications = Resource::new(
|
||||||
move || {},
|
move || {},
|
||||||
|_| async move { CLIENT.notifications_list().await.unwrap() },
|
|_| async move { CLIENT.notifications_list().await.unwrap() },
|
||||||
);
|
);
|
||||||
|
@ -43,7 +43,7 @@ pub fn Notifications() -> impl IntoView {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let notif_ = notif.clone();
|
let notif_ = notif.clone();
|
||||||
let click_approve = create_action(move |_: &()| {
|
let click_approve = Action::new(move |_: &()| {
|
||||||
let notif_ = notif_.clone();
|
let notif_ = notif_.clone();
|
||||||
async move {
|
async move {
|
||||||
if let ArticleApprovalRequired(a) = notif_ {
|
if let ArticleApprovalRequired(a) = notif_ {
|
||||||
|
@ -53,7 +53,7 @@ pub fn Notifications() -> impl IntoView {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let notif_ = notif.clone();
|
let notif_ = notif.clone();
|
||||||
let click_reject = create_action(move |_: &()| {
|
let click_reject = Action::new(move |_: &()| {
|
||||||
let notif_ = notif_.clone();
|
let notif_ = notif_.clone();
|
||||||
async move {
|
async move {
|
||||||
match notif_ {
|
match notif_ {
|
||||||
|
@ -76,13 +76,17 @@ pub fn Notifications() -> impl IntoView {
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-outline"
|
class="btn btn-sm btn-outline"
|
||||||
style=my_style
|
style=my_style
|
||||||
on:click=move |_| click_approve.dispatch(())
|
on:click=move |_| {
|
||||||
|
click_approve.dispatch(());
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Approve
|
Approve
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-outline"
|
class="btn btn-sm btn-outline"
|
||||||
on:click=move |_| click_reject.dispatch(())
|
on:click=move |_| {
|
||||||
|
click_reject.dispatch(());
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Reject
|
Reject
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -2,19 +2,20 @@ use crate::{
|
||||||
common::{LocalUserView, RegisterUserForm},
|
common::{LocalUserView, RegisterUserForm},
|
||||||
frontend::{api::CLIENT, app::site, components::credentials::*, error::MyResult},
|
frontend::{api::CLIENT, app::site, components::credentials::*, error::MyResult},
|
||||||
};
|
};
|
||||||
use leptos::{logging::log, *};
|
use leptos::prelude::*;
|
||||||
|
use log::info;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Register() -> impl IntoView {
|
pub fn Register() -> impl IntoView {
|
||||||
let (register_response, set_register_response) = create_signal(None::<()>);
|
let (register_response, set_register_response) = signal(None::<()>);
|
||||||
let (register_error, set_register_error) = create_signal(None::<String>);
|
let (register_error, set_register_error) = signal(None::<String>);
|
||||||
let (wait_for_response, set_wait_for_response) = create_signal(false);
|
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 username = email.to_string();
|
||||||
let password = password.to_string();
|
let password = password.to_string();
|
||||||
let credentials = RegisterUserForm { username, password };
|
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 {
|
async move {
|
||||||
set_wait_for_response.update(|w| *w = true);
|
set_wait_for_response.update(|w| *w = true);
|
||||||
let result: MyResult<LocalUserView> = CLIENT.register(credentials).await;
|
let result: MyResult<LocalUserView> = CLIENT.register(credentials).await;
|
||||||
|
|
|
@ -2,8 +2,8 @@ use crate::{
|
||||||
common::{DbArticle, DbInstance, SearchArticleForm},
|
common::{DbArticle, DbInstance, SearchArticleForm},
|
||||||
frontend::{api::CLIENT, article_link, article_title},
|
frontend::{api::CLIENT, article_link, article_title},
|
||||||
};
|
};
|
||||||
use leptos::*;
|
use leptos::prelude::*;
|
||||||
use leptos_router::use_query_map;
|
use leptos_router::hooks::use_query_map;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
@ -22,9 +22,9 @@ impl SearchResults {
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Search() -> impl IntoView {
|
pub fn Search() -> impl IntoView {
|
||||||
let params = use_query_map();
|
let params = use_query_map();
|
||||||
let query = move || params.get().get("query").cloned().unwrap();
|
let query = move || params.get().get("query").clone().unwrap();
|
||||||
let (error, set_error) = create_signal(None::<String>);
|
let (error, set_error) = signal(None::<String>);
|
||||||
let search_results = create_resource(query, move |query| async move {
|
let search_results = Resource::new(query, move |query| async move {
|
||||||
set_error.set(None);
|
set_error.set(None);
|
||||||
let mut search_results = SearchResults::default();
|
let mut search_results = SearchResults::default();
|
||||||
let url = Url::parse(&query);
|
let url = Url::parse(&query);
|
||||||
|
@ -89,7 +89,7 @@ pub fn Search() -> impl IntoView {
|
||||||
view! {
|
view! {
|
||||||
<li>
|
<li>
|
||||||
<a class="link text-lg" href=format!("/instance/{domain}")>
|
<a class="link text-lg" href=format!("/instance/{domain}")>
|
||||||
{domain}
|
{domain.to_string()}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,15 +2,15 @@ use crate::{
|
||||||
common::{DbPerson, GetUserForm},
|
common::{DbPerson, GetUserForm},
|
||||||
frontend::{api::CLIENT, user_title},
|
frontend::{api::CLIENT, user_title},
|
||||||
};
|
};
|
||||||
use leptos::*;
|
use leptos::prelude::*;
|
||||||
use leptos_router::use_params_map;
|
use leptos_router::hooks::use_params_map;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn UserProfile() -> impl IntoView {
|
pub fn UserProfile() -> impl IntoView {
|
||||||
let params = use_params_map();
|
let params = use_params_map();
|
||||||
let name = move || params.get().get("name").cloned().unwrap();
|
let name = move || params.get().get("name").clone().unwrap();
|
||||||
let (error, set_error) = create_signal(None::<String>);
|
let (error, set_error) = signal(None::<String>);
|
||||||
let user_profile = create_resource(name, move |mut name| async move {
|
let user_profile = Resource::new(name, move |mut name| async move {
|
||||||
set_error.set(None);
|
set_error.set(None);
|
||||||
let mut domain = None;
|
let mut domain = None;
|
||||||
if let Some((title_, domain_)) = name.clone().split_once('@') {
|
if let Some((title_, domain_)) = name.clone().split_once('@') {
|
||||||
|
|
Loading…
Reference in a new issue