mirror of
https://github.com/Nutomic/ibis.git
synced 2024-11-22 08:11:08 +00:00
wip2
This commit is contained in:
parent
8033a00b4e
commit
68f7f31b8e
10 changed files with 152 additions and 94 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
>
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
})
|
||||
}}
|
||||
|
|
|
@ -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..." }
|
||||
}>
|
||||
|
|
Loading…
Reference in a new issue