1
0
Fork 0
mirror of https://github.com/Nutomic/ibis.git synced 2024-11-22 18:51:08 +00:00

Merge branch 'dessalines-add_leptosfmt'

This commit is contained in:
Felix Ableitner 2024-10-02 13:16:51 +02:00
commit 706713be9d
25 changed files with 703 additions and 482 deletions

2
.leptosfmt.toml Normal file
View file

@ -0,0 +1,2 @@
max_width = 100
attr_value_brace_style = "WhenRequired" # "Always", "AlwaysUnlessLit", "WhenRequired" or "Preserve"

View file

@ -1,5 +1,6 @@
variables: variables:
- &rust_image "rust:1.75" - &rust_image "rust:1.75"
- &install_binstall "wget https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz && tar -xvf cargo-binstall-x86_64-unknown-linux-musl.tgz && cp cargo-binstall /usr/local/cargo/bin"
steps: steps:
cargo_fmt: cargo_fmt:
@ -10,6 +11,20 @@ steps:
commands: commands:
- rustup component add rustfmt - rustup component add rustfmt
- cargo +nightly fmt -- --check - cargo +nightly fmt -- --check
leptos_fmt:
image: *rust_image
commands:
- *install_binstall
- cargo binstall -y leptosfmt
- leptosfmt -c .leptosfmt.toml --check src
when:
- event: pull_request
toml_fmt:
image: tamasfe/taplo:0.8.1
commands:
- taplo format --check
when: when:
- event: pull_request - event: pull_request

2
Cargo.lock generated
View file

@ -1587,7 +1587,7 @@ dependencies = [
[[package]] [[package]]
name = "ibis" name = "ibis"
version = "0.1.0" version = "0.1.1"
dependencies = [ dependencies = [
"activitypub_federation", "activitypub_federation",
"anyhow", "anyhow",

View file

@ -45,7 +45,7 @@ diesel = { version = "2.1.4", features = [
"postgres", "postgres",
"chrono", "chrono",
"uuid", "uuid",
"r2d2" "r2d2",
], optional = true } ], optional = true }
diesel-derive-newtype = { version = "2.1.0", optional = true } diesel-derive-newtype = { version = "2.1.0", optional = true }
diesel_migrations = { version = "2.1.0", optional = true } diesel_migrations = { version = "2.1.0", optional = true }

View file

@ -94,7 +94,7 @@ pub fn App() -> impl IntoView {
<Stylesheet id="simple" href="/assets/simple.css"/> <Stylesheet id="simple" href="/assets/simple.css"/>
<Stylesheet id="ibis" href="/assets/ibis.css"/> <Stylesheet id="ibis" href="/assets/ibis.css"/>
<Router> <Router>
<Nav /> <Nav/>
<main> <main>
<Routes> <Routes>
<Route path="/" view=ReadArticle/> <Route path="/" view=ReadArticle/>

View file

@ -13,41 +13,60 @@ use leptos_router::*;
pub fn ArticleNav(article: Resource<Option<String>, ArticleView>) -> impl IntoView { pub fn ArticleNav(article: Resource<Option<String>, ArticleView>) -> impl IntoView {
view! { view! {
<Suspense> <Suspense>
{move || article.get().map(|article_| { {move || {
let instance = create_local_resource(move || article_.article.instance_id, move |instance_id| async move { article
.get()
.map(|article_| {
let instance = create_local_resource(
move || article_.article.instance_id,
move |instance_id| async move {
let form = GetInstance { let form = GetInstance {
id: Some(instance_id) id: Some(instance_id),
}; };
GlobalState::api_client() GlobalState::api_client().get_instance(&form).await.unwrap()
.get_instance(&form) },
.await );
.unwrap()
});
let global_state = use_context::<RwSignal<GlobalState>>().unwrap(); let global_state = use_context::<RwSignal<GlobalState>>().unwrap();
let article_link = article_link(&article_.article); let article_link = article_link(&article_.article);
let article_link_ = article_link.clone(); let article_link_ = article_link.clone();
let protected = article_.article.protected; let protected = article_.article.protected;
view!{ view! {
<nav class="inner"> <nav class="inner">
<A href=article_link.clone()>"Read"</A> <A href=article_link.clone()>"Read"</A>
<A href={format!("{article_link}/history")}>"History"</A> <A href=format!("{article_link}/history")>"History"</A>
<Show when=move || global_state.with(|state| { <Show when=move || {
let is_admin = state.my_profile.as_ref().map(|p| p.local_user.admin).unwrap_or(false); global_state
state.my_profile.is_some() && can_edit_article(&article_.article, is_admin).is_ok() .with(|state| {
})> let is_admin = state
<A href={format!("{article_link}/edit")}>"Edit"</A> .my_profile
.as_ref()
.map(|p| p.local_user.admin)
.unwrap_or(false);
state.my_profile.is_some()
&& can_edit_article(&article_.article, is_admin).is_ok()
})
}>
<A href=format!("{article_link}/edit")>"Edit"</A>
</Show> </Show>
<Show when=move || global_state.with(|state| state.my_profile.is_some())> <Show when=move || global_state.with(|state| state.my_profile.is_some())>
<A href={format!("{article_link_}/actions")}>"Actions"</A> <A href=format!("{article_link_}/actions")>"Actions"</A>
{instance.get().map(|i| {instance
view!{ <InstanceFollowButton instance=i.instance.clone() /> } .get()
)} .map(|i| {
view! { <InstanceFollowButton instance=i.instance.clone()/> }
})}
</Show> </Show>
<Show when=move || protected> <Show when=move || protected>
<span title="Article can only be edited by local admins">"Protected"</span> <span title="Article can only be edited by local admins">
"Protected"
</span>
</Show> </Show>
</nav> </nav>
}})} }
})
}}
</Suspense> </Suspense>
} }
} }

View file

@ -27,6 +27,7 @@ pub fn CredentialsForm(
view! { <p style="color:red;">{err}</p> } view! { <p style="color:red;">{err}</p> }
}) })
}} }}
<input <input
type="text" type="text"
required required
@ -36,11 +37,13 @@ pub fn CredentialsForm(
let val = event_target_value(&ev); let val = event_target_value(&ev);
set_username.update(|v| *v = val); set_username.update(|v| *v = val);
} }
on:change=move |ev| { on:change=move |ev| {
let val = event_target_value(&ev); let val = event_target_value(&ev);
set_username.update(|v| *v = val); set_username.update(|v| *v = val);
} }
/> />
<input <input
type="password" type="password"
required required
@ -57,15 +60,18 @@ pub fn CredentialsForm(
} }
} }
} }
on:change=move |ev| { on:change=move |ev| {
let val = event_target_value(&ev); let val = event_target_value(&ev);
set_password.update(|p| *p = val); set_password.update(|p| *p = val);
} }
/> />
<div> <div>
<button <button
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>
</div> </div>

View file

@ -30,9 +30,11 @@ pub fn InstanceFollowButton(instance: DbInstance) -> impl IntoView {
}; };
view! { view! {
<button on:click=move |_| follow_action.dispatch(instance.id) <button
on:click=move |_| follow_action.dispatch(instance.id)
prop:disabled=move || is_following prop:disabled=move || is_following
prop:hidden=move || instance.local> prop:hidden=move || instance.local
>
{follow_text} {follow_text}
</button> </button>
} }

View file

@ -29,8 +29,7 @@ pub fn Nav() -> impl IntoView {
<li> <li>
<A href="/article/list">"List Articles"</A> <A href="/article/list">"List Articles"</A>
</li> </li>
<Show <Show when=move || global_state.with(|state| state.my_profile.is_some())>
when=move || global_state.with(|state| state.my_profile.is_some())>
<li> <li>
<A href="/article/create">"Create Article"</A> <A href="/article/create">"Create Article"</A>
</li> </li>
@ -47,12 +46,16 @@ pub fn Nav() -> impl IntoView {
navigate(&format!("/search?query={query}"), Default::default()); navigate(&format!("/search?query={query}"), Default::default());
} }
}> }>
<input type="text" placeholder="Search" <input
type="text"
placeholder="Search"
prop:value=search_query prop:value=search_query
on:keyup=move |ev: ev::KeyboardEvent| { on:keyup=move |ev: ev::KeyboardEvent| {
let val = event_target_value(&ev); let val = event_target_value(&ev);
set_search_query.update(|v| *v = val); set_search_query.update(|v| *v = val);
} /> }
/>
<button>Go</button> <button>Go</button>
</form> </form>
</li> </li>
@ -71,20 +74,24 @@ pub fn Nav() -> impl IntoView {
} }
} }
> >
{ {
let my_profile = global_state.with(|state| state.my_profile.clone().unwrap()); let my_profile = global_state.with(|state| state.my_profile.clone().unwrap());
let profile_link = format!("/user/{}", my_profile.person.username); let profile_link = format!("/user/{}", my_profile.person.username);
view ! { view! {
<p>"Logged in as " <p>
<a href=profile_link style="border: none; padding: 0; color: var(--accent) !important;"> "Logged in as "
<a
href=profile_link
style="border: none; padding: 0; color: var(--accent) !important;"
>
{my_profile.person.username} {my_profile.person.username}
</a> </a>
</p> </p>
<button on:click=move |_| logout_action.dispatch(())> <button on:click=move |_| logout_action.dispatch(())>Logout</button>
Logout
</button>
} }
} }
</Show> </Show>
</nav> </nav>
} }

View file

@ -43,7 +43,5 @@ fn user_title(person: &DbPerson) -> String {
fn user_link(person: &DbPerson) -> impl IntoView { fn user_link(person: &DbPerson) -> impl IntoView {
let creator_path = format!("/user/{}", person.username); let creator_path = format!("/user/{}", person.username);
view! { view! { <a href=creator_path>{user_title(person)}</a> }
<a href={creator_path}>{user_title(person)}</a>
}
} }

View file

@ -53,8 +53,13 @@ pub fn ArticleActions() -> impl IntoView {
}); });
view! { view! {
<ArticleNav article=article/> <ArticleNav article=article/>
<Suspense fallback=|| view! { "Loading..." }> { <Suspense fallback=|| {
move || article.get().map(|article| view! { "Loading..." }
}>
{move || {
article
.get()
.map(|article| {
view! { view! {
<div class="item-view"> <div class="item-view">
<h1>{article_title(&article.article)}</h1> <h1>{article_title(&article.article)}</h1>
@ -65,13 +70,21 @@ pub fn ArticleActions() -> impl IntoView {
view! { <p style="color:red;">{err}</p> } view! { <p style="color:red;">{err}</p> }
}) })
}} }}
<Show
when=move || global_state.with(|state| { <Show when=move || {
state.my_profile.as_ref().map(|p| p.local_user.admin).unwrap_or_default() global_state
&& article.article.local .with(|state| {
})> state
<button .my_profile
on:click=move |_| protect_action.dispatch((article.article.id, article.article.protected))>Toggle Article Protection</button> .as_ref()
.map(|p| p.local_user.admin)
.unwrap_or_default() && article.article.local
})
}>
<button on:click=move |_| {
protect_action
.dispatch((article.article.id, article.article.protected))
}>Toggle Article Protection</button>
<p>"Protect a local article so that only admins can edit it"</p> <p>"Protect a local article so that only admins can edit it"</p>
</Show> </Show>
<Show when=move || !article.article.local> <Show when=move || !article.article.local>
@ -80,21 +93,31 @@ pub fn ArticleActions() -> impl IntoView {
on:keyup=move |ev: ev::KeyboardEvent| { on:keyup=move |ev: 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);
} /> }
/>
<button <button
disabled=move || new_title.get().is_empty() disabled=move || new_title.get().is_empty()
on:click=move |_| fork_action.dispatch((article.article.id, new_title.get()))>Fork Article</button> on:click=move |_| {
fork_action.dispatch((article.article.id, new_title.get()))
}
>
Fork Article
</button>
<p> <p>
"You can fork a remote article to the local instance. This is useful if the original "You can fork a remote article to the local instance. This is useful if the original
instance is dead, or if there are disagreements how the article should be written." instance is dead, or if there are disagreements how the article should be written."
</p> </p>
</Show> </Show>
</div> </div>
})
} }
})
}}
</Suspense> </Suspense>
<Show when=move || fork_response.get().is_some()> <Show when=move || fork_response.get().is_some()>
<Redirect path={article_link(&fork_response.get().unwrap())}/> <Redirect path=article_link(&fork_response.get().unwrap())/>
</Show> </Show>
<p>"TODO: add option for admin to delete article etc"</p> <p>"TODO: add option for admin to delete article etc"</p>
} }

View file

@ -56,12 +56,21 @@ pub fn CreateArticle() -> impl IntoView {
set_title.update(|v| *v = val); set_title.update(|v| *v = val);
} }
/> />
<textarea placeholder="Article text..." on:keyup=move |ev| {
<textarea
placeholder="Article text..."
on:keyup=move |ev| {
let val = event_target_value(&ev); let val = event_target_value(&ev);
set_text.update(|p| *p = val); set_text.update(|p| *p = val);
} > }
>
</textarea> </textarea>
<div><a href="https://commonmark.org/help/" target="blank_">Markdown</a>" formatting is supported"</div> <div>
<a href="https://commonmark.org/help/" target="blank_">
Markdown
</a>
" formatting is supported"
</div>
{move || { {move || {
create_error create_error
.get() .get()
@ -69,21 +78,28 @@ pub fn CreateArticle() -> impl IntoView {
view! { <p style="color:red;">{err}</p> } view! { <p style="color:red;">{err}</p> }
}) })
}} }}
<input type="text"
<input
type="text"
placeholder="Edit summary" placeholder="Edit summary"
on:keyup=move |ev| { on:keyup=move |ev| {
let val = event_target_value(&ev); let val = event_target_value(&ev);
set_summary.update(|p| *p = val); set_summary.update(|p| *p = val);
}/> }
/>
<button <button
prop:disabled=move || button_is_disabled.get() prop:disabled=move || button_is_disabled.get()
on:click=move |_| submit_action.dispatch((title.get(), text.get(), summary.get()))> on:click=move |_| submit_action.dispatch((title.get(), text.get(), summary.get()))
>
Submit Submit
</button> </button>
</div> </div>
} }
}> }
<Redirect path={format!("/article/{}", title.get().replace(' ', "_"))} /> >
<Redirect path=format!("/article/{}", title.get().replace(' ', "_"))/>
</Show> </Show>
} }
} }

View file

@ -105,16 +105,21 @@ pub fn EditArticle() -> impl IntoView {
when=move || edit_response.get() == EditResponse::Success when=move || edit_response.get() == EditResponse::Success
fallback=move || { fallback=move || {
view! { view! {
<Suspense fallback=|| view! { "Loading..." }> { <Suspense fallback=|| {
move || article.get().map(|mut article| { view! { "Loading..." }
}>
{move || {
article
.get()
.map(|mut article| {
if let EditResponse::Conflict(conflict) = edit_response.get() { if let EditResponse::Conflict(conflict) = edit_response.get() {
article.article.text = conflict.three_way_merge; article.article.text = conflict.three_way_merge;
set_summary.set(conflict.summary); set_summary.set(conflict.summary);
} }
// set initial text, otherwise submit with no changes results in empty text
set_text.set(article.article.text.clone()); set_text.set(article.article.text.clone());
let article_ = article.clone(); let article_ = article.clone();
view! { view! {
// set initial text, otherwise submit with no changes results in empty text
<div class="item-view"> <div class="item-view">
<h1>{article_title(&article.article)}</h1> <h1>{article_title(&article.article)}</h1>
{move || { {move || {
@ -124,31 +129,52 @@ pub fn EditArticle() -> impl IntoView {
view! { <p style="color:red;">{err}</p> } view! { <p style="color:red;">{err}</p> }
}) })
}} }}
<textarea on:keyup=move |ev| { <textarea on:keyup=move |ev| {
let val = event_target_value(&ev); let val = event_target_value(&ev);
set_text.update(|p| *p = val); set_text.update(|p| *p = val);
}> }>{article.article.text.clone()}</textarea>
{article.article.text.clone()} <div>
</textarea> <a href="https://commonmark.org/help/" target="blank_">
<div><a href="https://commonmark.org/help/" target="blank_">Markdown</a>" formatting is supported"</div> Markdown
<input type="text" </a>
" formatting is supported"
</div>
<input
type="text"
placeholder="Edit summary" placeholder="Edit summary"
value={summary.get_untracked()} value=summary.get_untracked()
on:keyup=move |ev| { on:keyup=move |ev| {
let val = event_target_value(&ev); let val = event_target_value(&ev);
set_summary.update(|p| *p = val); set_summary.update(|p| *p = val);
}/> }
/>
<button <button
prop:disabled=move || button_is_disabled.get() prop:disabled=move || button_is_disabled.get()
on:click=move |_| submit_action.dispatch((text.get(), summary.get(), article_.clone(), edit_response.get()))> on:click=move |_| {
submit_action
.dispatch((
text.get(),
summary.get(),
article_.clone(),
edit_response.get(),
))
}
>
Submit Submit
</button> </button>
</div> </div>
} }
}) })
} }}
</Suspense> </Suspense>
}}> }
}
>
Edit successful! Edit successful!
</Show> </Show>
} }

View file

@ -13,22 +13,48 @@ pub fn ArticleHistory() -> impl IntoView {
view! { view! {
<ArticleNav article=article/> <ArticleNav article=article/>
<Suspense fallback=|| view! { "Loading..." }> { <Suspense fallback=|| {
move || article.get().map(|article| { view! { "Loading..." }
}>
{move || {
article
.get()
.map(|article| {
view! { view! {
<div class="item-view"> <div class="item-view">
<h1>{article_title(&article.article)}</h1> <h1>{article_title(&article.article)}</h1>
{
article.edits.into_iter().rev().map(|edit| { {article
let path = format!("/article/{}@{}/diff/{}", article.article.title, extract_domain(&article.article.ap_id), edit.edit.hash.0); .edits
let label = format!("{} ({})", edit.edit.summary, edit.edit.created.to_rfc2822()); .into_iter()
view! {<li><a href={path}>{label}</a>" by "{user_link(&edit.creator)}</li> } .rev()
}).collect::<Vec<_>>() .map(|edit| {
let path = format!(
"/article/{}@{}/diff/{}",
article.article.title,
extract_domain(&article.article.ap_id),
edit.edit.hash.0,
);
let label = format!(
"{} ({})",
edit.edit.summary,
edit.edit.created.to_rfc2822(),
);
view! {
<li>
<a href=path>{label}</a>
" by "
{user_link(&edit.creator)}
</li>
} }
})
.collect::<Vec<_>>()}
</div> </div>
} }
}) })
} }}
</Suspense> </Suspense>
} }
} }

View file

@ -24,25 +24,33 @@ pub fn ListArticles() -> impl IntoView {
<h1>Most recently edited Articles</h1> <h1>Most recently edited Articles</h1>
<Suspense fallback=|| view! { "Loading..." }> <Suspense fallback=|| view! { "Loading..." }>
<fieldset on:input=move |ev| { <fieldset on:input=move |ev| {
let val = ev let val = ev.target().unwrap().unchecked_into::<web_sys::HtmlInputElement>().id();
.target()
.unwrap()
.unchecked_into::<web_sys::HtmlInputElement>()
.id();
let is_local_only = val == "only-local"; let is_local_only = val == "only-local";
set_only_local.update(|p| *p = is_local_only); set_only_local.update(|p| *p = is_local_only);
}> }>
<input type="radio" name="listing-type" id="only-local" /> <input type="radio" name="listing-type" id="only-local"/>
<label for="only-local">Only Local</label> <label for="only-local">Only Local</label>
<input type="radio" name="listing-type" id="all" checked/> <input type="radio" name="listing-type" id="all" checked/>
<label for="all">All</label> <label for="all">All</label>
</fieldset> </fieldset>
<ul> { <ul>
move || articles.get().map(|a| {move || {
a.into_iter().map(|a| view! { articles
<li><a href=article_link(&a)>{article_title(&a)}</a></li> .get()
}).collect::<Vec<_>>()) .map(|a| {
} </ul> a.into_iter()
.map(|a| {
view! {
<li>
<a href=article_link(&a)>{article_title(&a)}</a>
</li>
}
})
.collect::<Vec<_>>()
})
}}
</ul>
</Suspense> </Suspense>
} }
} }

View file

@ -12,16 +12,26 @@ pub fn ReadArticle() -> impl IntoView {
view! { view! {
<ArticleNav article=article/> <ArticleNav article=article/>
<Suspense fallback=|| view! { "Loading..." }> { <Suspense fallback=|| {
view! { "Loading..." }
}>
{
let parser = markdown_parser(); let parser = markdown_parser();
move || article.get().map(|article| move || {
article
.get()
.map(|article| {
view! { view! {
<div class="item-view"> <div class="item-view">
<h1>{article_title(&article.article)}</h1> <h1>{article_title(&article.article)}</h1>
<div inner_html={parser.parse(&article.article.text).render()}/> <div inner_html=parser.parse(&article.article.text).render()></div>
</div> </div>
}
}) })
} }
}
</Suspense> </Suspense>
} }
} }

View file

@ -11,14 +11,25 @@ pub fn Conflicts() -> impl IntoView {
view! { view! {
<h1>Your unresolved edit conflicts</h1> <h1>Your unresolved edit conflicts</h1>
<Suspense fallback=|| view! { "Loading..." }> <Suspense fallback=|| view! { "Loading..." }>
<ul> { <ul>
move || conflicts.get().map(|c| {move || {
c.into_iter().map(|c| { conflicts
.get()
.map(|c| {
c.into_iter()
.map(|c| {
let link = format!("{}/edit/{}", article_link(&c.article), c.id); let link = format!("{}/edit/{}", article_link(&c.article), c.id);
view! { view! {
<li><a href=link>{article_title(&c.article)}" - "{c.summary}</a></li> <li>
}}).collect::<Vec<_>>()) <a href=link>{article_title(&c.article)} " - " {c.summary}</a>
} </ul> </li>
}
})
.collect::<Vec<_>>()
})
}}
</ul>
</Suspense> </Suspense>
} }
} }

View file

@ -9,25 +9,35 @@ pub fn EditDiff() -> impl IntoView {
view! { view! {
<ArticleNav article=article/> <ArticleNav article=article/>
<Suspense fallback=|| view! { "Loading..." }> { <Suspense fallback=|| {
move || article.get().map(|article| { view! { "Loading..." }
let hash = params }>
.get_untracked() {move || {
.get("hash") article
.cloned().unwrap(); .get()
let edit = article.edits.iter().find(|e| e.edit.hash.0.to_string() == hash).unwrap(); .map(|article| {
let label = format!("{} ({})", edit.edit.summary, edit.edit.created.to_rfc2822()); let hash = params.get_untracked().get("hash").cloned().unwrap();
let edit = article
.edits
.iter()
.find(|e| e.edit.hash.0.to_string() == hash)
.unwrap();
let label = format!(
"{} ({})",
edit.edit.summary,
edit.edit.created.to_rfc2822(),
);
view! { view! {
<div class="item-view"> <div class="item-view">
<h1>{article.article.title.replace('_', " ")}</h1> <h1>{article.article.title.replace('_', " ")}</h1>
<h2>{label}</h2> <h2>{label}</h2>
<p>"by "{user_link(&edit.creator)}</p> <p>"by " {user_link(&edit.creator)}</p>
<pre>{edit.edit.diff.clone()}</pre> <pre>{edit.edit.diff.clone()}</pre>
</div> </div>
} }
}) })
} }}
</Suspense> </Suspense>
} }
} }

View file

@ -20,22 +20,32 @@ pub fn InstanceDetails() -> impl IntoView {
}); });
view! { view! {
<Suspense fallback=|| view! { "Loading..." }> { <Suspense fallback=|| {
move || instance_profile.get().map(|instance: DbInstance| { view! { "Loading..." }
}>
{move || {
instance_profile
.get()
.map(|instance: DbInstance| {
let instance_ = instance.clone(); let instance_ = instance.clone();
view! { view! {
<h1>{instance.domain}</h1> <h1>{instance.domain}</h1>
<Show when=move || global_state.with(|state| state.my_profile.is_some())> <Show when=move || global_state.with(|state| state.my_profile.is_some())>
<InstanceFollowButton instance=instance_.clone() /> <InstanceFollowButton instance=instance_.clone()/>
</Show> </Show>
<p>Follow the instance so that new edits are federated to your instance.</p> <p>Follow the instance so that new edits are federated to your instance.</p>
<p>"TODO: show a list of articles from the instance. For now you can use the "<a href="/article/list">Article list</a>.</p> <p>
"TODO: show a list of articles from the instance. For now you can use the "
<a href="/article/list">Article list</a> .
</p>
<hr/> <hr/>
<h2>"Description:"</h2> <h2>"Description:"</h2>
<div>{instance.description}</div> <div>{instance.description}</div>
} }
}) })
}</Suspense> }}
</Suspense>
} }
} }

View file

@ -52,6 +52,7 @@ pub fn Login() -> impl IntoView {
} }
} }
> >
<Redirect path="/"/> <Redirect path="/"/>
</Show> </Show>
} }

View file

@ -53,6 +53,7 @@ pub fn Register() -> impl IntoView {
} }
} }
> >
<p>"You have successfully registered."</p> <p>"You have successfully registered."</p>
</Show> </Show>
} }

View file

@ -52,45 +52,67 @@ pub fn Search() -> impl IntoView {
}); });
view! { view! {
<h1>"Search results for "{query}</h1> <h1>"Search results for " {query}</h1>
<Suspense fallback=|| view! { "Loading..." }> { <Suspense fallback=|| {
move || search_results.get().map(move |search_results| { view! { "Loading..." }
}>
{move || {
search_results
.get()
.map(move |search_results| {
let is_empty = search_results.is_empty(); let is_empty = search_results.is_empty();
view! { view! {
<Show when=move || !is_empty <Show
when=move || !is_empty
fallback=move || { fallback=move || {
let error_view = move || { let error_view = move || {
error.get().map(|err| { error
.get()
.map(|err| {
view! { <p style="color:red;">{err}</p> } view! { <p style="color:red;">{err}</p> }
}) })
}; };
view! { view! {
{error_view} {error_view}
<p>No results found</p> <p>No results found</p>
}}> }
}
>
<ul> <ul>
{
// render resolved instance // render resolved instance
if let Some(instance) = &search_results.instance { {if let Some(instance) = &search_results.instance {
let domain = &instance.domain; let domain = &instance.domain;
vec![view! { <li> vec![
<a href={format!("/instance/{domain}")}>{domain}</a> view! {
</li>}] <li>
} else { vec![] } <a href=format!("/instance/{domain}")>{domain}</a>
} </li>
{ },
]
} else {
vec![]
}}
// render articles from resolve/search // render articles from resolve/search
search_results.articles {search_results
.articles
.iter() .iter()
.map(|a| view! { <li> .map(|a| {
<a href={article_link(a)}>{article_title(a)}</a> view! {
</li>}) <li>
.collect::<Vec<_>>() <a href=article_link(a)>{article_title(a)}</a>
</li>
} }
})
.collect::<Vec<_>>()}
</ul> </ul>
</Show> </Show>
}})
} }
})
}}
</Suspense> </Suspense>
} }
} }

View file

@ -29,13 +29,21 @@ pub fn UserProfile() -> impl IntoView {
view! { <p style="color:red;">{err}</p> } view! { <p style="color:red;">{err}</p> }
}) })
}} }}
<Suspense fallback=|| view! { "Loading..." }> {
move || user_profile.get().map(|person: DbPerson| { <Suspense fallback=|| {
view! { "Loading..." }
}>
{move || {
user_profile
.get()
.map(|person: DbPerson| {
view! { view! {
<h1>{user_title(&person)}</h1> <h1>{user_title(&person)}</h1>
<p>TODO: create actual user profile</p> <p>TODO: create actual user profile</p>
} }
}) })
}</Suspense> }}
</Suspense>
} }
} }