This commit is contained in:
Felix Ableitner 2024-10-16 16:40:32 +02:00
parent 8033a00b4e
commit 68f7f31b8e
10 changed files with 152 additions and 94 deletions

View File

@ -3,20 +3,34 @@ use crate::{
frontend::{
app::GlobalState,
article_link,
article_title,
components::instance_follow_button::InstanceFollowButton,
},
};
use leptos::*;
use leptos_router::*;
pub enum ActiveTab {
Read,
History,
Edit,
Actions,
}
#[component]
pub fn ArticleNav(article: Resource<Option<String>, ArticleView>) -> impl IntoView {
pub fn ArticleNav(
article: Resource<Option<String>, ArticleView>,
active_tab: ActiveTab,
) -> impl IntoView {
let tab_classes = tab_classes(&active_tab);
view! {
<Suspense>
{move || {
article
.get()
.map(|article_| {
let title = article_title(&article_.article);
let instance = create_local_resource(
move || article_.article.instance_id,
move |instance_id| async move {
@ -32,8 +46,12 @@ pub fn ArticleNav(article: Resource<Option<String>, ArticleView>) -> impl IntoVi
let protected = article_.article.protected;
view! {
<div role="tablist" class="tabs tabs-lifted">
<A class="tab tab-active" href=article_link.clone()>"Read"</A>
<A class="tab" href=format!("{article_link}/history")>"History"</A>
<A class=tab_classes.read href=article_link.clone()>
"Read"
</A>
<A class=tab_classes.history href=format!("{article_link}/history")>
"History"
</A>
<Show when=move || {
global_state
.with(|state| {
@ -46,12 +64,19 @@ pub fn ArticleNav(article: Resource<Option<String>, ArticleView>) -> impl IntoVi
&& can_edit_article(&article_.article, is_admin).is_ok()
})
}>
<A class="tab" href=format!("{article_link}/edit")>"Edit"</A>
<A class=tab_classes.edit href=format!("{article_link}/edit")>
"Edit"
</A>
</Show>
<Show when=move || {
global_state.with(|state| state.my_profile.is_some())
}>
<A class="tab" href=format!("{article_link_}/actions")>"Actions"</A>
<A
class=tab_classes.actions
href=format!("{article_link_}/actions")
>
"Actions"
</A>
{instance
.get()
.map(|i| {
@ -61,12 +86,15 @@ pub fn ArticleNav(article: Resource<Option<String>, ArticleView>) -> impl IntoVi
})}
</Show>
<Show when=move || protected>
<span title="Article can only be edited by local admins">
"Protected"
</span>
</Show>
</div>
<div class="flex flex-row">
<h1 class="text-4xl font-bold font-serif my-4 grow max-w-fit">{title}</h1>
<Show when=move || protected>
<span title="Article can only be edited by local admins">
"Protected"
</span>
</Show>
</div>
}
})
}}
@ -74,3 +102,28 @@ pub fn ArticleNav(article: Resource<Option<String>, ArticleView>) -> impl IntoVi
</Suspense>
}
}
struct ActiveTabClasses {
read: &'static str,
history: &'static str,
edit: &'static str,
actions: &'static str,
}
fn tab_classes(active_tab: &ActiveTab) -> ActiveTabClasses {
const TAB_INACTIVE: &str = "tab";
const TAB_ACTIVE: &str = "tab tab-active";
let mut classes = ActiveTabClasses {
read: TAB_INACTIVE,
history: TAB_INACTIVE,
edit: TAB_INACTIVE,
actions: TAB_INACTIVE,
};
match active_tab {
ActiveTab::Read => classes.read = TAB_ACTIVE,
ActiveTab::History => classes.history = TAB_ACTIVE,
ActiveTab::Edit => classes.edit = TAB_ACTIVE,
ActiveTab::Actions => classes.actions = TAB_ACTIVE,
}
classes
}

View File

@ -24,12 +24,13 @@ pub fn CredentialsForm(
error
.get()
.map(|err| {
view! { <p style="color:red;">{err}</p> }
view! { <p class="alert alert-error">{err}</p> }
})
}}
<input
type="text"
class="input"
required
placeholder="Username"
prop:disabled=move || disabled.get()
@ -46,6 +47,7 @@ pub fn CredentialsForm(
<input
type="password"
class="input"
required
placeholder="Password"
prop:disabled=move || disabled.get()
@ -69,6 +71,7 @@ pub fn CredentialsForm(
<div>
<button
class="btn"
prop:disabled=move || button_is_disabled.get()
on:click=move |_| dispatch_action()
>

View File

@ -38,16 +38,17 @@ pub fn Nav() -> impl IntoView {
</li>
</Show>
<li>
<form
class="form-control m-0 p-1"
on:submit=move |ev| {
ev.prevent_default();
let navigate = leptos_router::use_navigate();
let query = search_query.get();
if !query.is_empty() {
navigate(&format!("/search?query={query}"), Default::default());
<form
class="form-control m-0 p-1"
on:submit=move |ev| {
ev.prevent_default();
let navigate = leptos_router::use_navigate();
let query = search_query.get();
if !query.is_empty() {
navigate(&format!("/search?query={query}"), Default::default());
}
}
}>
>
<input
type="text"
class="input input-secondary input-bordered input-xs w-full rounded"
@ -84,14 +85,13 @@ pub fn Nav() -> impl IntoView {
let profile_link = format!("/user/{}", my_profile.person.username);
view! {
<p class="self-center pb-2">
"Logged in as "
<a class="link"
href=profile_link
>
"Logged in as " <a class="link" href=profile_link>
{my_profile.person.username}
</a>
</p>
<button class="btn" on:click=move |_| logout_action.dispatch(())>Logout</button>
<button class="btn" on:click=move |_| logout_action.dispatch(())>
Logout
</button>
}
}

View File

@ -52,7 +52,11 @@ fn user_link(person: &DbPerson) -> impl IntoView {
extract_domain(&person.ap_id)
)
};
view! { <a href=creator_path>{user_title(person)}</a> }
view! {
<a class="link" href=creator_path>
{user_title(person)}
</a>
}
}
fn render_date_time(date_time: DateTime<Utc>) -> String {

View File

@ -3,8 +3,7 @@ use crate::{
frontend::{
app::GlobalState,
article_link,
article_title,
components::article_nav::ArticleNav,
components::article_nav::{ActiveTab, ArticleNav},
pages::article_resource,
DbArticle,
},
@ -52,7 +51,7 @@ pub fn ArticleActions() -> impl IntoView {
}
});
view! {
<ArticleNav article=article />
<ArticleNav article=article active_tab=ActiveTab::Actions />
<Suspense fallback=|| {
view! { "Loading..." }
}>
@ -61,16 +60,14 @@ pub fn ArticleActions() -> impl IntoView {
.get()
.map(|article| {
view! {
<div class="item-view">
<h1>{article_title(&article.article)}</h1>
<div>
{move || {
error
.get()
.map(|err| {
view! { <p style="color:red;">{err}</p> }
view! { <p class="alert">{err}</p> }
})
}}
<Show when=move || {
global_state
.with(|state| {
@ -81,14 +78,19 @@ pub fn ArticleActions() -> impl IntoView {
.unwrap_or_default() && article.article.local
})
}>
<button on:click=move |_| {
protect_action
.dispatch((article.article.id, article.article.protected))
}>Toggle Article Protection</button>
<button
class="btn btn-secondary"
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>
</Show>
<Show when=move || !article.article.local>
</Show> <Show when=move || !article.article.local>
<input
class="input"
placeholder="New Title"
on:keyup=move |ev: ev::KeyboardEvent| {
let val = event_target_value(&ev);
@ -97,6 +99,7 @@ pub fn ArticleActions() -> impl IntoView {
/>
<button
class="btn"
disabled=move || new_title.get().is_empty()
on:click=move |_| {
fork_action.dispatch((article.article.id, new_title.get()))

View File

@ -2,8 +2,7 @@ use crate::{
common::{ApiConflict, ArticleView, EditArticleForm},
frontend::{
app::GlobalState,
article_title,
components::article_nav::ArticleNav,
components::article_nav::{ActiveTab, ArticleNav},
markdown::render_markdown,
pages::article_resource,
},
@ -103,7 +102,7 @@ pub fn EditArticle() -> impl IntoView {
);
view! {
<ArticleNav article=article />
<ArticleNav article=article active_tab=ActiveTab::Edit />
<Show
when=move || edit_response.get() == EditResponse::Success
fallback=move || {
@ -126,7 +125,6 @@ pub fn EditArticle() -> impl IntoView {
view! {
// set initial text, otherwise submit with no changes results in empty text
<div>
<h1>{article_title(&article.article)}</h1>
{move || {
edit_error
.get()
@ -134,7 +132,6 @@ pub fn EditArticle() -> impl IntoView {
view! { <p style="color:red;">{err}</p> }
})
}}
<textarea
id="edit-article-textarea"
class="textarea textarea-bordered textarea-primary min-w-full"
@ -147,19 +144,19 @@ pub fn EditArticle() -> impl IntoView {
>
{article.article.text.clone()}
</textarea>
<button class="btn" on:click=move |_| {
set_show_preview.update(|s| *s = !*s)
}>Preview</button>
<Show when=move || { show_preview.get() }>
<button
class="btn"
on:click=move |_| { set_show_preview.update(|s| *s = !*s) }
>
Preview
</button> <Show when=move || { show_preview.get() }>
<div class="preview" inner_html=move || preview.get()></div>
</Show>
<div>
</Show> <div>
<a href="https://commonmark.org/help/" target="blank_">
Markdown
</a>
" formatting is supported"
</div>
<div class="inputs">
</div> <div class="inputs">
<input
type="text"
class="input input-secondary"
@ -172,7 +169,7 @@ pub fn EditArticle() -> impl IntoView {
/>
<button
class="btn btn-secondary"
class="btn btn-secondary"
prop:disabled=move || button_is_disabled.get()
on:click=move |_| {
submit_action

View File

@ -1,6 +1,5 @@
use crate::frontend::{
article_title,
components::article_nav::ArticleNav,
components::article_nav::{ActiveTab, ArticleNav},
extract_domain,
pages::article_resource,
render_date_time,
@ -13,7 +12,7 @@ pub fn ArticleHistory() -> impl IntoView {
let article = article_resource();
view! {
<ArticleNav article=article />
<ArticleNav article=article active_tab=ActiveTab::History />
<Suspense fallback=|| {
view! { "Loading..." }
}>
@ -22,30 +21,30 @@ pub fn ArticleHistory() -> impl IntoView {
.get()
.map(|article| {
view! {
<div class="item-view">
<h1>{article_title(&article.article)}</h1>
{article
.edits
.into_iter()
.rev()
.map(|edit| {
let path = format!(
"/article/{}@{}/diff/{}",
article.article.title,
extract_domain(&article.article.ap_id),
edit.edit.hash.0,
);
view! {
<li>
{render_date_time(edit.edit.created)}": "
<a href=path>{edit.edit.summary}</a> " by "
{user_link(&edit.creator)}
</li>
}
})
.collect::<Vec<_>>()}
<div>
<ul class="list-disc">
{article
.edits
.into_iter()
.rev()
.map(|edit| {
let path = format!(
"/article/{}@{}/diff/{}",
article.article.title,
extract_domain(&article.article.ap_id),
edit.edit.hash.0,
);
view! {
<li>
{render_date_time(edit.edit.created)}": "
<a class="link link-primary" href=path>
{edit.edit.summary}
</a> " by " {user_link(&edit.creator)}
</li>
}
})
.collect::<Vec<_>>()}
</ul>
</div>
}
})

View File

@ -21,19 +21,19 @@ pub fn ListArticles() -> impl IntoView {
);
view! {
<h1>Most recently edited Articles</h1>
<h1 class="text-4xl font-bold font-serif my-4">Most recently edited Articles</h1>
<Suspense fallback=|| view! { "Loading..." }>
<fieldset on:input=move |ev| {
let val = ev.target().unwrap().unchecked_into::<web_sys::HtmlInputElement>().id();
let is_local_only = val == "only-local";
set_only_local.update(|p| *p = is_local_only);
}>
<input type="radio" name="listing-type" id="only-local" />
<input type="radio" name="listing-type" class="radio radio-primary" />
<label for="only-local">Only Local</label>
<input type="radio" name="listing-type" id="all" checked />
<input type="radio" name="listing-type" class="radio radio-primary" checked />
<label for="all">All</label>
</fieldset>
<ul>
<ul class="list-disc">
{move || {
articles
.get()
@ -42,7 +42,7 @@ pub fn ListArticles() -> impl IntoView {
.map(|a| {
view! {
<li>
<a href=article_link(&a)>{article_title(&a)}</a>
<a class="link link-secondary" href=article_link(&a)>{article_title(&a)}</a>
</li>
}
})

View File

@ -1,6 +1,5 @@
use crate::frontend::{
article_title,
components::article_nav::ArticleNav,
components::article_nav::{ActiveTab, ArticleNav},
markdown::render_markdown,
pages::article_resource,
};
@ -11,7 +10,7 @@ pub fn ReadArticle() -> impl IntoView {
let article = article_resource();
view! {
<ArticleNav article=article />
<ArticleNav article=article active_tab=ActiveTab::Read />
<Suspense fallback=|| {
view! { "Loading..." }
}>
@ -21,10 +20,10 @@ pub fn ReadArticle() -> impl IntoView {
.get()
.map(|article| {
view! {
<div class="prose prose-slate">
<h1 class="slate">{article_title(&article.article)}</h1>
<div inner_html=render_markdown(&article.article.text)></div>
</div>
<div
class="prose prose-slate"
inner_html=render_markdown(&article.article.text)
></div>
}
})
}}

View File

@ -1,5 +1,5 @@
use crate::frontend::{
components::article_nav::ArticleNav,
components::article_nav::{ActiveTab, ArticleNav},
pages::article_resource,
render_date_time,
user_link,
@ -13,7 +13,7 @@ pub fn EditDiff() -> impl IntoView {
let article = article_resource();
view! {
<ArticleNav article=article />
<ArticleNav article=article active_tab=ActiveTab::Edit />
<Suspense fallback=|| {
view! { "Loading..." }
}>