1
0
Fork 0
mirror of https://github.com/Nutomic/ibis.git synced 2025-02-03 21:41:35 +00:00

Move frontend stuff into subfolders

This commit is contained in:
Felix Ableitner 2025-01-23 22:34:18 +01:00
parent ae44c169ba
commit 5229ec34a7
37 changed files with 251 additions and 229 deletions

View file

@ -1,4 +1,7 @@
use crate::backend::{config::IbisConfig, database::schema::jwt_secret, utils::error::MyResult}; use crate::backend::{
database::schema::jwt_secret,
utils::{config::IbisConfig, error::MyResult},
};
use diesel::{ use diesel::{
r2d2::{ConnectionManager, Pool}, r2d2::{ConnectionManager, Pool},
PgConnection, PgConnection,

View file

@ -1,6 +1,6 @@
use super::utils::error::MyResult; use super::utils::error::MyResult;
use crate::{ use crate::{
backend::{config::IbisConfig, database::IbisContext}, backend::{database::IbisContext, utils::config::IbisConfig},
common::{instance::DbInstance, user::DbPerson}, common::{instance::DbInstance, user::DbPerson},
}; };
use activities::announce::AnnounceActivity; use activities::announce::AnnounceActivity;

View file

@ -1,9 +1,9 @@
use crate::{ use crate::{
backend::{ backend::{
config::IbisConfig,
database::{article::DbArticleForm, instance::DbInstanceForm, IbisContext}, database::{article::DbArticleForm, instance::DbInstanceForm, IbisContext},
federation::{activities::submit_article_update, VerifyUrlData}, federation::{activities::submit_article_update, VerifyUrlData},
utils::{ utils::{
config::IbisConfig,
error::{Error, MyResult}, error::{Error, MyResult},
generate_activity_id, generate_activity_id,
}, },
@ -37,7 +37,6 @@ use tokio::sync::oneshot;
use utils::{generate_keypair, scheduled_tasks}; use utils::{generate_keypair, scheduled_tasks};
pub mod api; pub mod api;
pub mod config;
pub mod database; pub mod database;
pub mod federation; pub mod federation;
mod server; mod server;

View file

@ -15,6 +15,7 @@ use rand::{distributions::Alphanumeric, thread_rng, Rng};
use std::sync::LazyLock; use std::sync::LazyLock;
use url::{ParseError, Url}; use url::{ParseError, Url};
pub mod config;
pub mod error; pub mod error;
pub(super) mod scheduled_tasks; pub(super) mod scheduled_tasks;
pub(super) mod validate; pub(super) mod validate;

View file

@ -1,30 +1,32 @@
use crate::{ use crate::frontend::{
common::instance::SiteView, api::CLIENT,
frontend::{ components::{nav::Nav, protected_route::IbisProtectedRoute},
api::CLIENT, pages::{
components::{nav::Nav, protected_route::IbisProtectedRoute}, article::{
dark_mode::DarkMode, actions::ArticleActions,
instance_title, create::CreateArticle,
pages::{
article::{
actions::ArticleActions,
create::CreateArticle,
discussion::ArticleDiscussion,
edit::EditArticle,
history::ArticleHistory,
list::ListArticles,
read::ReadArticle,
},
diff::EditDiff, diff::EditDiff,
instance::{details::InstanceDetails, list::ListInstances, settings::InstanceSettings}, discussion::ArticleDiscussion,
edit::EditArticle,
history::ArticleHistory,
list::ListArticles,
read::ReadArticle,
},
instance::{
details::InstanceDetails,
list::ListInstances,
search::Search,
settings::InstanceSettings,
},
user::{
edit_profile::UserEditProfile,
login::Login, login::Login,
notifications::Notifications, notifications::Notifications,
profile::UserProfile,
register::Register, register::Register,
search::Search,
user_edit_profile::UserEditProfile,
user_profile::UserProfile,
}, },
}, },
utils::{dark_mode::DarkMode, formatting::instance_title},
}; };
use leptos::prelude::*; use leptos::prelude::*;
use leptos_meta::{provide_meta_context, *}; use leptos_meta::{provide_meta_context, *};
@ -33,40 +35,6 @@ use leptos_router::{
path, path,
}; };
pub fn site() -> Resource<SiteView> {
use_context::<Resource<SiteView>>().unwrap()
}
pub fn is_logged_in() -> bool {
site().with_default(|site| site.my_profile.is_some())
}
pub fn is_admin() -> bool {
site().with_default(|site| {
site.my_profile
.as_ref()
.map(|p| p.local_user.admin)
.unwrap_or(false)
})
}
pub trait DefaultResource<T> {
fn with_default<O>(&self, f: impl FnOnce(&T) -> O) -> O;
fn get_default(&self) -> T;
}
impl<T: Default + Send + Sync + Clone> DefaultResource<T> for Resource<T> {
fn with_default<O>(&self, f: impl FnOnce(&T) -> O) -> O {
self.with(|x| match x {
Some(x) => f(x),
None => f(&T::default()),
})
}
fn get_default(&self) -> T {
match self.get() {
Some(x) => x.clone(),
None => T::default(),
}
}
}
pub fn shell(options: LeptosOptions) -> impl IntoView { pub fn shell(options: LeptosOptions) -> impl IntoView {
view! { view! {
<!DOCTYPE html> <!DOCTYPE html>

View file

@ -1,4 +1,4 @@
use crate::frontend::{markdown::render_article_markdown, use_cookie}; use crate::frontend::{markdown::render_article_markdown, utils::use_cookie};
use leptos::{ev::beforeunload, html::Textarea, prelude::*}; use leptos::{ev::beforeunload, html::Textarea, prelude::*};
use leptos_use::{use_event_listener, use_window}; use leptos_use::{use_event_listener, use_window};

View file

@ -1,9 +1,8 @@
use crate::{ use crate::{
common::{article::DbArticleView, validation::can_edit_article}, common::{article::DbArticleView, validation::can_edit_article},
frontend::{ frontend::utils::{
app::{is_admin, is_logged_in}, formatting::{article_path, article_title},
article_path, resources::{is_admin, is_logged_in},
article_title,
}, },
}; };
use leptos::prelude::*; use leptos::prelude::*;

View file

@ -6,11 +6,12 @@ use crate::{
}, },
frontend::{ frontend::{
api::CLIENT, api::CLIENT,
app::{site, DefaultResource},
components::comment_editor::{CommentEditorView, EditParams}, components::comment_editor::{CommentEditorView, EditParams},
markdown::render_comment_markdown, markdown::render_comment_markdown,
time_ago, utils::{
user_link, formatting::{time_ago, user_link},
resources::{site, DefaultResource},
},
}, },
}; };
use leptos::prelude::*; use leptos::prelude::*;

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
common::{article::EditView, utils::extract_domain}, common::{article::EditView, utils::extract_domain},
frontend::{article_link, render_date_time, user_link}, frontend::utils::formatting::{article_link, render_date_time, user_link},
}; };
use leptos::{either::Either, prelude::*}; use leptos::{either::Either, prelude::*};

View file

@ -5,7 +5,7 @@ use crate::{
}, },
frontend::{ frontend::{
api::CLIENT, api::CLIENT,
app::{site, DefaultResource}, utils::resources::{site, DefaultResource},
}, },
}; };
use leptos::prelude::*; use leptos::prelude::*;

View file

@ -1,8 +1,10 @@
use crate::frontend::{ use crate::frontend::{
api::CLIENT, api::CLIENT,
app::{is_admin, is_logged_in, site, DefaultResource}, utils::{
dark_mode::DarkMode, dark_mode::DarkMode,
instance_title, formatting::instance_title,
resources::{is_admin, is_logged_in, site, DefaultResource},
},
}; };
use leptos::{component, prelude::*, view, IntoView, *}; use leptos::{component, prelude::*, view, IntoView, *};
use leptos_router::hooks::use_navigate; use leptos_router::hooks::use_navigate;

View file

@ -1,4 +1,4 @@
use crate::frontend::app::is_logged_in; use crate::frontend::utils::resources::is_logged_in;
use leptos::prelude::*; use leptos::prelude::*;
use leptos_router::{ use leptos_router::{
components::{ProtectedRoute, ProtectedRouteProps}, components::{ProtectedRoute, ProtectedRouteProps},

View file

@ -1,22 +1,11 @@
use crate::common::{ use crate::common::article::DbArticle;
article::DbArticle,
instance::DbInstance,
user::DbPerson,
utils::extract_domain,
};
use chrono::{DateTime, Duration, Local, Utc};
use codee::string::FromToStringCodec;
use leptos::prelude::*;
use leptos_use::{use_cookie_with_options, SameSite, UseCookieOptions};
use std::sync::OnceLock;
use timeago::Formatter;
pub mod api; pub mod api;
pub mod app; pub mod app;
mod components; mod components;
pub mod dark_mode; mod markdown;
pub mod markdown; mod pages;
pub mod pages; mod utils;
#[cfg(feature = "hydrate")] #[cfg(feature = "hydrate")]
#[wasm_bindgen::prelude::wasm_bindgen] #[wasm_bindgen::prelude::wasm_bindgen]
@ -26,107 +15,3 @@ pub fn hydrate() {
console_error_panic_hook::set_once(); console_error_panic_hook::set_once();
leptos::mount::hydrate_body(App); leptos::mount::hydrate_body(App);
} }
fn article_path(article: &DbArticle) -> String {
if article.local {
format!("/article/{}", article.title)
} else {
format!(
"/article/{}@{}",
article.title,
extract_domain(&article.ap_id)
)
}
}
fn article_link(article: &DbArticle) -> impl IntoView {
let article_path = article_path(article);
view! {
<a class="link" href=article_path>
{article.title.clone()}
</a>
}
}
fn article_title(article: &DbArticle) -> String {
let title = article.title.replace('_', " ");
if article.local {
title
} else {
format!("{}@{}", title, extract_domain(&article.ap_id))
}
}
fn user_title(person: &DbPerson) -> String {
let name = person
.display_name
.clone()
.unwrap_or(person.username.clone());
if person.local {
format!("@{name}")
} else {
format!("@{}@{}", name, extract_domain(&person.ap_id))
}
}
fn user_link(person: &DbPerson) -> impl IntoView {
let creator_path = if person.local {
format!("/user/{}", person.username)
} else {
format!(
"/user/{}@{}",
person.username,
extract_domain(&person.ap_id)
)
};
view! {
<a class="link" href=creator_path>
{user_title(person)}
</a>
}
}
fn render_date_time(date_time: DateTime<Utc>) -> String {
date_time
.with_timezone(&Local)
.format("%Y-%m-%d %H:%M:%S")
.to_string()
}
fn use_cookie(name: &str) -> (Signal<Option<bool>>, WriteSignal<Option<bool>>) {
let expires = (Local::now() + Duration::days(356)).timestamp();
let cookie_options = UseCookieOptions::default()
.path("/")
.expires(expires)
.same_site(SameSite::Strict);
use_cookie_with_options::<bool, FromToStringCodec>(name, cookie_options)
}
fn time_ago(time: DateTime<Utc>) -> String {
static INSTANCE: OnceLock<Formatter> = OnceLock::new();
let secs = Utc::now().signed_duration_since(time).num_seconds();
let duration = std::time::Duration::from_secs(secs.try_into().unwrap_or_default());
INSTANCE.get_or_init(Formatter::new).convert(duration)
}
fn instance_title_with_domain(instance: &DbInstance) -> String {
let name = instance.name.clone();
let domain = instance.domain.clone();
if let Some(name) = name {
format!("{name} ({domain})")
} else {
domain
}
}
fn instance_title(instance: &DbInstance) -> String {
instance.name.clone().unwrap_or(instance.domain.clone())
}
fn instance_updated(instance: &DbInstance) -> String {
if instance.local {
"Local".to_string()
} else {
format!("Updated {}", time_ago(instance.last_refreshed_at))
}
}

View file

@ -5,10 +5,9 @@ use crate::{
}, },
frontend::{ frontend::{
api::CLIENT, api::CLIENT,
app::is_admin,
article_path,
components::article_nav::{ActiveTab, ArticleNav}, components::article_nav::{ActiveTab, ArticleNav},
pages::article_resource, pages::article_resource,
utils::{formatting::article_path, resources::is_admin},
DbArticle, DbArticle,
}, },
}; };

View file

@ -2,8 +2,8 @@ use crate::{
common::article::CreateArticleParams, common::article::CreateArticleParams,
frontend::{ frontend::{
api::CLIENT, api::CLIENT,
app::{is_admin, site, DefaultResource},
components::article_editor::EditorView, components::article_editor::EditorView,
utils::resources::{is_admin, site, DefaultResource},
}, },
}; };
use leptos::{html::Textarea, prelude::*}; use leptos::{html::Textarea, prelude::*};

View file

@ -1,9 +1,7 @@
use crate::frontend::{ use crate::frontend::{
article_title,
components::article_nav::{ActiveTab, ArticleNav}, components::article_nav::{ActiveTab, ArticleNav},
pages::{article_edits_resource, article_resource}, pages::{article_edits_resource, article_resource},
render_date_time, utils::formatting::{article_title, render_date_time, user_link},
user_link,
}; };
use leptos::prelude::*; use leptos::prelude::*;
use leptos_meta::Title; use leptos_meta::Title;

View file

@ -2,10 +2,11 @@ use crate::{
common::article::ListArticlesParams, common::article::ListArticlesParams,
frontend::{ frontend::{
api::CLIENT, api::CLIENT,
app::DefaultResource,
article_path,
article_title,
components::connect::ConnectView, components::connect::ConnectView,
utils::{
formatting::{article_path, article_title},
resources::DefaultResource,
},
}, },
}; };
use leptos::prelude::*; use leptos::prelude::*;

View file

@ -1,5 +1,6 @@
pub mod actions; pub mod actions;
pub mod create; pub mod create;
pub mod diff;
pub mod discussion; pub mod discussion;
pub mod edit; pub mod edit;
pub mod history; pub mod history;

View file

@ -2,11 +2,13 @@ use crate::{
common::{article::ListArticlesParams, instance::DbInstance, utils::http_protocol_str}, common::{article::ListArticlesParams, instance::DbInstance, utils::http_protocol_str},
frontend::{ frontend::{
api::CLIENT, api::CLIENT,
article_path,
article_title,
components::instance_follow_button::InstanceFollowButton, components::instance_follow_button::InstanceFollowButton,
instance_title_with_domain, utils::formatting::{
instance_updated, article_path,
article_title,
instance_title_with_domain,
instance_updated,
},
}, },
}; };
use leptos::prelude::*; use leptos::prelude::*;

View file

@ -1,8 +1,7 @@
use crate::frontend::{ use crate::frontend::{
api::CLIENT, api::CLIENT,
components::connect::ConnectView, components::connect::ConnectView,
instance_title_with_domain, utils::formatting::{instance_title_with_domain, instance_updated},
instance_updated,
}; };
use leptos::prelude::*; use leptos::prelude::*;
use leptos_meta::Title; use leptos_meta::Title;

View file

@ -1,3 +1,4 @@
pub(crate) mod details; pub mod details;
pub(crate) mod list; pub mod list;
pub(crate) mod settings; pub mod search;
pub mod settings;

View file

@ -3,7 +3,10 @@ use crate::{
article::{DbArticle, SearchArticleParams}, article::{DbArticle, SearchArticleParams},
instance::DbInstance, instance::DbInstance,
}, },
frontend::{api::CLIENT, article_path, article_title}, frontend::{
api::CLIENT,
utils::formatting::{article_path, article_title},
},
}; };
use leptos::prelude::*; use leptos::prelude::*;
use leptos_meta::Title; use leptos_meta::Title;

View file

@ -8,15 +8,9 @@ use crate::{
use leptos::prelude::*; use leptos::prelude::*;
use leptos_router::hooks::use_params_map; use leptos_router::hooks::use_params_map;
pub(crate) mod article; pub mod article;
pub(crate) mod diff; pub mod instance;
pub(crate) mod instance; pub mod user;
pub(crate) mod login;
pub(crate) mod notifications;
pub(crate) mod register;
pub(crate) mod search;
pub(crate) mod user_edit_profile;
pub(crate) mod user_profile;
fn article_resource() -> Resource<DbArticleView> { fn article_resource() -> Resource<DbArticleView> {
let params = use_params_map(); let params = use_params_map();

View file

@ -2,7 +2,7 @@ use crate::{
common::user::UpdateUserParams, common::user::UpdateUserParams,
frontend::{ frontend::{
api::CLIENT, api::CLIENT,
app::{site, DefaultResource}, utils::resources::{site, DefaultResource},
}, },
}; };
use leptos::prelude::*; use leptos::prelude::*;

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
common::user::LoginUserParams, common::user::LoginUserParams,
frontend::{api::CLIENT, app::site, components::credentials::*}, frontend::{api::CLIENT, components::credentials::*, utils::resources::site},
}; };
use leptos::prelude::*; use leptos::prelude::*;
use leptos_meta::Title; use leptos_meta::Title;

View file

@ -0,0 +1,5 @@
pub mod edit_profile;
pub mod login;
pub mod notifications;
pub mod profile;
pub mod register;

View file

@ -1,6 +1,9 @@
use crate::{ use crate::{
common::Notification, common::Notification,
frontend::{api::CLIENT, article_path, article_title}, frontend::{
api::CLIENT,
utils::formatting::{article_path, article_title},
},
}; };
use leptos::prelude::*; use leptos::prelude::*;
use leptos_meta::Title; use leptos_meta::Title;

View file

@ -4,7 +4,7 @@ use crate::{
api::CLIENT, api::CLIENT,
components::edit_list::EditList, components::edit_list::EditList,
markdown::render_article_markdown, markdown::render_article_markdown,
user_title, utils::formatting::user_title,
}, },
}; };
use leptos::prelude::*; use leptos::prelude::*;

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
common::user::RegisterUserParams, common::user::RegisterUserParams,
frontend::{api::CLIENT, app::site, components::credentials::*}, frontend::{api::CLIENT, components::credentials::*, utils::resources::site},
}; };
use leptos::prelude::*; use leptos::prelude::*;
use leptos_meta::Title; use leptos_meta::Title;

View file

@ -1,4 +1,4 @@
use super::use_cookie; use crate::frontend::utils::use_cookie;
use leptos::prelude::*; use leptos::prelude::*;
use leptos_use::use_preferred_dark; use leptos_use::use_preferred_dark;

View file

@ -0,0 +1,105 @@
use crate::common::{
article::DbArticle,
instance::DbInstance,
user::DbPerson,
utils::extract_domain,
};
use chrono::{DateTime, Local, Utc};
use leptos::prelude::*;
use std::sync::OnceLock;
use timeago::Formatter;
pub fn article_path(article: &DbArticle) -> String {
if article.local {
format!("/article/{}", article.title)
} else {
format!(
"/article/{}@{}",
article.title,
extract_domain(&article.ap_id)
)
}
}
pub fn article_link(article: &DbArticle) -> impl IntoView {
let article_path = article_path(article);
view! {
<a class="link" href=article_path>
{article.title.clone()}
</a>
}
}
pub fn article_title(article: &DbArticle) -> String {
let title = article.title.replace('_', " ");
if article.local {
title
} else {
format!("{}@{}", title, extract_domain(&article.ap_id))
}
}
pub fn user_title(person: &DbPerson) -> String {
let name = person
.display_name
.clone()
.unwrap_or(person.username.clone());
if person.local {
format!("@{name}")
} else {
format!("@{}@{}", name, extract_domain(&person.ap_id))
}
}
pub fn user_link(person: &DbPerson) -> impl IntoView {
let creator_path = if person.local {
format!("/user/{}", person.username)
} else {
format!(
"/user/{}@{}",
person.username,
extract_domain(&person.ap_id)
)
};
view! {
<a class="link" href=creator_path>
{user_title(person)}
</a>
}
}
pub fn render_date_time(date_time: DateTime<Utc>) -> String {
date_time
.with_timezone(&Local)
.format("%Y-%m-%d %H:%M:%S")
.to_string()
}
pub fn time_ago(time: DateTime<Utc>) -> String {
static INSTANCE: OnceLock<Formatter> = OnceLock::new();
let secs = Utc::now().signed_duration_since(time).num_seconds();
let duration = std::time::Duration::from_secs(secs.try_into().unwrap_or_default());
INSTANCE.get_or_init(Formatter::new).convert(duration)
}
pub fn instance_title_with_domain(instance: &DbInstance) -> String {
let name = instance.name.clone();
let domain = instance.domain.clone();
if let Some(name) = name {
format!("{name} ({domain})")
} else {
domain
}
}
pub fn instance_title(instance: &DbInstance) -> String {
instance.name.clone().unwrap_or(instance.domain.clone())
}
pub fn instance_updated(instance: &DbInstance) -> String {
if instance.local {
"Local".to_string()
} else {
format!("Updated {}", time_ago(instance.last_refreshed_at))
}
}

17
src/frontend/utils/mod.rs Normal file
View file

@ -0,0 +1,17 @@
use chrono::{Duration, Local};
use codee::string::FromToStringCodec;
use leptos::prelude::*;
use leptos_use::{use_cookie_with_options, SameSite, UseCookieOptions};
pub mod dark_mode;
pub mod formatting;
pub mod resources;
pub fn use_cookie(name: &str) -> (Signal<Option<bool>>, WriteSignal<Option<bool>>) {
let expires = (Local::now() + Duration::days(356)).timestamp();
let cookie_options = UseCookieOptions::default()
.path("/")
.expires(expires)
.same_site(SameSite::Strict);
use_cookie_with_options::<bool, FromToStringCodec>(name, cookie_options)
}

View file

@ -0,0 +1,37 @@
use crate::common::instance::SiteView;
use leptos::prelude::*;
pub fn site() -> Resource<SiteView> {
use_context::<Resource<SiteView>>().unwrap()
}
pub fn is_logged_in() -> bool {
site().with_default(|site| site.my_profile.is_some())
}
pub fn is_admin() -> bool {
site().with_default(|site| {
site.my_profile
.as_ref()
.map(|p| p.local_user.admin)
.unwrap_or(false)
})
}
pub trait DefaultResource<T> {
fn with_default<O>(&self, f: impl FnOnce(&T) -> O) -> O;
fn get_default(&self) -> T;
}
impl<T: Default + Send + Sync + Clone> DefaultResource<T> for Resource<T> {
fn with_default<O>(&self, f: impl FnOnce(&T) -> O) -> O {
self.with(|x| match x {
Some(x) => f(x),
None => f(&T::default()),
})
}
fn get_default(&self) -> T {
match self.get() {
Some(x) => x.clone(),
None => T::default(),
}
}
}

View file

@ -1,7 +1,7 @@
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
#[tokio::main] #[tokio::main]
pub async fn main() -> ibis::backend::utils::error::MyResult<()> { pub async fn main() -> ibis::backend::utils::error::MyResult<()> {
use ibis::backend::config::IbisConfig; use ibis::backend::utils::config::IbisConfig;
use log::LevelFilter; use log::LevelFilter;
if std::env::args().collect::<Vec<_>>().get(1) == Some(&"--print-config".to_string()) { if std::env::args().collect::<Vec<_>>().get(1) == Some(&"--print-config".to_string()) {

View file

@ -11,7 +11,6 @@ module.exports = {
require('@tailwindcss/typography') require('@tailwindcss/typography')
], ],
daisyui: { daisyui: {
//themes: ["emerald", "dim"]
themes: [ themes: [
{ {
emerald: { emerald: {

View file

@ -3,8 +3,8 @@
use anyhow::Result; use anyhow::Result;
use ibis::{ use ibis::{
backend::{ backend::{
config::{IbisConfig, IbisConfigDatabase, IbisConfigFederation},
start, start,
utils::config::{IbisConfig, IbisConfigDatabase, IbisConfigFederation},
}, },
common::{instance::Options, user::RegisterUserParams}, common::{instance::Options, user::RegisterUserParams},
frontend::api::ApiClient, frontend::api::ApiClient,