mirror of
https://github.com/Nutomic/ibis.git
synced 2025-01-11 08:25: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 = [
|
||||
"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 }
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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 />
|
||||
// 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 />
|
||||
<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=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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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."
|
||||
>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
))
|
||||
));
|
||||
}
|
||||
>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::frontend::{
|
|||
render_date_time,
|
||||
user_link,
|
||||
};
|
||||
use leptos::*;
|
||||
use leptos::prelude::*;
|
||||
|
||||
#[component]
|
||||
pub fn ArticleHistory() -> impl IntoView {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::frontend::{
|
|||
markdown::render_markdown,
|
||||
pages::article_resource,
|
||||
};
|
||||
use leptos::*;
|
||||
use leptos::prelude::*;
|
||||
|
||||
#[component]
|
||||
pub fn ReadArticle() -> impl IntoView {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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('@') {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
},
|
||||
|
|
|
@ -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('@') {
|
||||
|
|
Loading…
Reference in a new issue