Make mobile nav not require page refresh when JS enabled

Make top bar stick to top of screen
Make wide view show rows of 4
Improve notification page styles, text
Add button js to more pages
This commit is contained in:
asonix 2021-01-15 22:50:15 -06:00
parent 95b3b17c61
commit 5f0682ee22
29 changed files with 296 additions and 79 deletions

View file

@ -3,16 +3,31 @@
font-style: italic; font-style: italic;
} }
picture {
width: 100%;
}
.toolkit-card.nav { .toolkit-card.nav {
margin-bottom: 0; margin-bottom: 0;
} }
.desktop-bar,
.mobile-bar {
position: fixed;
z-index: 1;
top: 0;
left: 0;
right: 0;
overflow-x: auto;
}
.mobile-bar { .mobile-bar {
display: none; display: none;
} }
.nav-body { .nav-body {
position: fixed; position: fixed;
z-index: 2;
top: 0; top: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
@ -22,6 +37,13 @@
&.nav-open { &.nav-open {
display: block; display: block;
animation-duration: 0.3s;
animation-name: slideup;
}
&.nav-closing {
display: block;
animation-duration: 0.5s;
animation-name: slidedown;
} }
&.nav-closed { &.nav-closed {
display: none; display: none;
@ -44,7 +66,7 @@
} }
.home-content { .home-content {
padding: 32px 0; padding: 96px 0 32px;
} }
.profile-box { .profile-box {
@ -119,7 +141,7 @@
.submission-tiles { .submission-tiles {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
.submission-icon { .submission-icon {
position: relative; position: relative;
@ -128,7 +150,6 @@
picture { picture {
display: block; display: block;
width: 100%;
height: 100%; height: 100%;
} }
@ -185,10 +206,38 @@
} }
.submission-box { .submission-box {
max-height: 70vh; max-height: 90vh;
display: flex; display: flex;
justify-content: center; justify-content: center;
background-color: #000; background-color: #000;
img {
object-fit: contain;
}
}
@keyframes slideup {
from {
background-color: rgba(0, 0, 0, 0);
bottom: -100vh;
}
to {
background-color: rgba(0, 0, 0, 0.4);
bottom: 0vh;
}
}
@keyframes slidedown {
from {
background-color: rgba(0, 0, 0, 0.4);
bottom: 0vh;
}
to {
background-color: rgba(0, 0, 0, 0);
bottom: -100vh;
}
} }
@media (max-width: 700px) { @media (max-width: 700px) {

View file

@ -1,7 +1,7 @@
use crate::{error::Error, State}; use crate::{error::Error, State};
use activitystreams::base::AnyBase; use activitystreams::base::AnyBase;
use actix_web::{web, HttpResponse, Scope}; use actix_web::{web, HttpResponse, Scope};
use hyaenidae_profiles::{apub::ApubIds, Spawner}; use hyaenidae_profiles::apub::ApubIds;
use sled::Tree; use sled::Tree;
use url::Url; use url::Url;
use uuid::Uuid; use uuid::Uuid;
@ -54,9 +54,11 @@ async fn shared_inbox(
do_inbox(body.into_inner(), &state).await do_inbox(body.into_inner(), &state).await
} }
// TODO: signature validation, Actor check // TODO: signature validation
async fn do_inbox(any_base: AnyBase, state: &State) -> Result<HttpResponse, Error> { async fn do_inbox(any_base: AnyBase, state: &State) -> Result<HttpResponse, Error> {
state.spawn.process(any_base, vec![]); state
.spawn
.ingest(any_base, "http://temporary.url".parse().unwrap());
Ok(HttpResponse::Created().finish()) Ok(HttpResponse::Created().finish())
} }

View file

@ -3,6 +3,7 @@ use ructe::Ructe;
fn main() -> ructe::Result<()> { fn main() -> ructe::Result<()> {
let mut ructe = Ructe::from_env()?; let mut ructe = Ructe::from_env()?;
let mut statics = ructe.statics()?; let mut statics = ructe.statics()?;
statics.add_files("static")?;
statics.add_sass_file("scss/layout.scss")?; statics.add_sass_file("scss/layout.scss")?;
ructe.compile_templates("templates")?; ructe.compile_templates("templates")?;

View file

@ -43,6 +43,18 @@ pub(super) fn build(
#[derive(Clone)] #[derive(Clone)]
pub(super) struct Spawn(QueueHandle); pub(super) struct Spawn(QueueHandle);
impl Spawn {
pub(crate) fn ingest(&self, any_base: AnyBase, key_owner: Url) {
if let Err(e) = self.0.queue(Ingest {
any_base,
key_owner: Some(key_owner),
stack: vec![],
}) {
log::error!("Failed to queue ingest: {}", e);
}
}
}
impl Spawner for Spawn { impl Spawner for Spawn {
fn download_apub(&self, url: Url, stack: Vec<AnyBase>) { fn download_apub(&self, url: Url, stack: Vec<AnyBase>) {
if let Err(e) = self.0.queue(DownloadApub { url, stack }) { if let Err(e) = self.0.queue(DownloadApub { url, stack }) {
@ -63,7 +75,11 @@ impl Spawner for Spawn {
} }
fn process(&self, any_base: AnyBase, stack: Vec<AnyBase>) { fn process(&self, any_base: AnyBase, stack: Vec<AnyBase>) {
if let Err(e) = self.0.queue(Ingest { any_base, stack }) { if let Err(e) = self.0.queue(Ingest {
any_base,
key_owner: None,
stack,
}) {
log::error!("Failed to queue process job: {}", e); log::error!("Failed to queue process job: {}", e);
} }
} }
@ -109,6 +125,7 @@ struct DownloadApub {
#[derive(Clone, serde::Deserialize, serde::Serialize)] #[derive(Clone, serde::Deserialize, serde::Serialize)]
struct Ingest { struct Ingest {
any_base: AnyBase, any_base: AnyBase,
key_owner: Option<Url>,
stack: Vec<AnyBase>, stack: Vec<AnyBase>,
} }
@ -267,7 +284,10 @@ impl ActixJob for Ingest {
if self.stack.len() > MAX_INGEST_DEPTH { if self.stack.len() > MAX_INGEST_DEPTH {
return Err(anyhow::anyhow!("Max recursion depth exceded")); return Err(anyhow::anyhow!("Max recursion depth exceded"));
} }
state.profiles.ingest(self.any_base, self.stack).await?; state
.profiles
.ingest(self.any_base, self.key_owner, self.stack)
.await?;
Ok(()) Ok(())
}) })
} }

View file

@ -58,16 +58,18 @@ async fn main() -> anyhow::Result<()> {
secret_key secret_key
}; };
let domain = config
.base_url
.domain()
.expect("Invalid domain for base url")
.to_owned();
let accounts_config = hyaenidae_accounts::Config { let accounts_config = hyaenidae_accounts::Config {
toolkit_path: format!( toolkit_path: format!(
"/toolkit/{}", "/toolkit/{}",
hyaenidae_toolkit::templates::statics::toolkit_css.name hyaenidae_toolkit::templates::statics::toolkit_css.name
), ),
domain: config domain: domain.clone(),
.base_url
.domain()
.expect("Invalid domain for base url")
.to_owned(),
key: secret_key, key: secret_key,
https: false, https: false,
pages: std::sync::Arc::new(accounts::Pages), pages: std::sync::Arc::new(accounts::Pages),
@ -81,14 +83,17 @@ async fn main() -> anyhow::Result<()> {
)?; )?;
let accounts_state = hyaenidae_accounts::state(&accounts_config, db.clone())?; let accounts_state = hyaenidae_accounts::state(&accounts_config, db.clone())?;
let state = State::new(
spawner.clone(),
config.base_url.clone(),
config.pictrs_upstream.clone(),
&db.clone(),
)?;
state.profiles.create_server_actor(domain).await?;
if let Some(user) = config.make_admin { if let Some(user) = config.make_admin {
let user = accounts_state.by_username(user).await?.req()?; let user = accounts_state.by_username(user).await?.req()?;
let state = State::new(
spawner.clone(),
config.base_url,
config.pictrs_upstream,
&db.clone(),
)?;
state.admin.make_admin(user.id())?; state.admin.make_admin(user.id())?;
return Ok(()); return Ok(());

View file

@ -1,4 +1,7 @@
use crate::{comments::Comment, error::Error, nav::NavState, profiles::Profile, State}; use crate::{
comments::Comment, error::Error, nav::NavState, profiles::Profile, submissions::Submission,
State,
};
use actix_web::{web, HttpResponse, Scope}; use actix_web::{web, HttpResponse, Scope};
use futures::stream::{FuturesUnordered, StreamExt}; use futures::stream::{FuturesUnordered, StreamExt};
use hyaenidae_toolkit::{Button, Link}; use hyaenidae_toolkit::{Button, Link};
@ -206,20 +209,37 @@ async fn remove_comment_notification(
} }
pub(crate) struct CommentView<'a> { pub(crate) struct CommentView<'a> {
submission: &'a Submission,
parent: Option<&'a Comment>,
comment: &'a Comment, comment: &'a Comment,
author: &'a Profile, author: &'a Profile,
id: Uuid, id: Uuid,
self_id: Uuid,
} }
impl<'a> CommentView<'a> { impl<'a> CommentView<'a> {
fn comment_reply(&self) -> Option<Uuid> {
self.parent.and_then(|c| {
if c.profile_id() == self.self_id {
Some(c.id())
} else {
None
}
})
}
fn submission_path(&self) -> Option<String> { fn submission_path(&self) -> Option<String> {
if self.comment.comment_id().is_none() { if self.comment_reply().is_none() {
Some(format!("/submissions/{}", self.comment.submission_id())) Some(format!("/submissions/{}", self.comment.submission_id()))
} else { } else {
None None
} }
} }
pub(crate) fn submission_title(&self) -> String {
self.submission.title()
}
pub(crate) fn submission_link(&self, dark: bool) -> Option<Link> { pub(crate) fn submission_link(&self, dark: bool) -> Option<Link> {
self.submission_path().map(|path| { self.submission_path().map(|path| {
let mut link = Link::new_tab(&path); let mut link = Link::new_tab(&path);
@ -229,7 +249,7 @@ impl<'a> CommentView<'a> {
} }
fn reply_to_path(&self) -> Option<String> { fn reply_to_path(&self) -> Option<String> {
if let Some(reply_to_id) = self.comment.comment_id() { if let Some(reply_to_id) = self.comment_reply() {
Some(format!("/comments/{}", reply_to_id)) Some(format!("/comments/{}", reply_to_id))
} else { } else {
None None
@ -249,7 +269,7 @@ impl<'a> CommentView<'a> {
} }
pub(crate) fn view_button(&self, dark: bool) -> Button { pub(crate) fn view_button(&self, dark: bool) -> Button {
let btn = Button::primary("View"); let btn = Button::secondary("View");
btn.href(&self.comment_path()).new_tab().dark(dark); btn.href(&self.comment_path()).new_tab().dark(dark);
btn btn
} }
@ -259,7 +279,7 @@ impl<'a> CommentView<'a> {
} }
pub(crate) fn remove_button(&self, dark: bool) -> Button { pub(crate) fn remove_button(&self, dark: bool) -> Button {
let btn = Button::primary_outline("Remove"); let btn = Button::secondary("Remove");
btn.form(&self.remove_path()).dark(dark); btn.form(&self.remove_path()).dark(dark);
btn btn
} }
@ -282,7 +302,7 @@ pub(crate) struct FollowRequestView<'a> {
impl<'a> FollowRequestView<'a> { impl<'a> FollowRequestView<'a> {
pub(crate) fn accept_button(&self, dark: bool) -> Button { pub(crate) fn accept_button(&self, dark: bool) -> Button {
let btn = Button::primary("Accept"); let btn = Button::secondary("Accept");
btn.form(&format!( btn.form(&format!(
"/notifications/follow-requests/{}/accept", "/notifications/follow-requests/{}/accept",
self.id self.id
@ -292,7 +312,7 @@ impl<'a> FollowRequestView<'a> {
} }
pub(crate) fn reject_button(&self, dark: bool) -> Button { pub(crate) fn reject_button(&self, dark: bool) -> Button {
let btn = Button::primary_outline("Reject"); let btn = Button::secondary("Reject");
btn.form(&format!( btn.form(&format!(
"/notifications/follow-requests/{}/reject", "/notifications/follow-requests/{}/reject",
self.id self.id
@ -306,10 +326,12 @@ impl<'a> FollowRequestView<'a> {
pub struct NotificationsView { pub struct NotificationsView {
comment_hm: HashMap<Uuid, Comment>, comment_hm: HashMap<Uuid, Comment>,
profile_hm: HashMap<Uuid, Profile>, profile_hm: HashMap<Uuid, Profile>,
submission_hm: HashMap<Uuid, Submission>,
fr_profile_hm: HashMap<Uuid, Uuid>, fr_profile_hm: HashMap<Uuid, Uuid>,
comments: Vec<Uuid>, comments: Vec<Uuid>,
follow_requests: Vec<Uuid>, follow_requests: Vec<Uuid>,
count: u64, count: u64,
self_id: Uuid,
} }
impl NotificationsView { impl NotificationsView {
@ -317,11 +339,16 @@ impl NotificationsView {
self.comments.iter().filter_map(move |comment_id| { self.comments.iter().filter_map(move |comment_id| {
let comment = self.comment_hm.get(comment_id)?; let comment = self.comment_hm.get(comment_id)?;
let author = self.profile_hm.get(&comment.profile_id())?; let author = self.profile_hm.get(&comment.profile_id())?;
let submission = self.submission_hm.get(&comment.submission_id())?;
let parent = comment.comment_id().and_then(|id| self.comment_hm.get(&id));
Some(CommentView { Some(CommentView {
comment, comment,
parent,
author, author,
submission,
id: *comment_id, id: *comment_id,
self_id: self.self_id,
}) })
}) })
} }
@ -397,6 +424,7 @@ impl NotificationsView {
let comment_store = state.profiles.store.comments.clone(); let comment_store = state.profiles.store.comments.clone();
let profile_store = state.profiles.store.profiles.clone(); let profile_store = state.profiles.store.profiles.clone();
let submission_store = state.profiles.store.submissions.clone();
let file_store = state.profiles.store.files.clone(); let file_store = state.profiles.store.files.clone();
let follow_request_store = state.profiles.store.view.follow_requests.clone(); let follow_request_store = state.profiles.store.view.follow_requests.clone();
let comment_notifs = state.profiles.store.view.comments.clone(); let comment_notifs = state.profiles.store.view.comments.clone();
@ -406,10 +434,12 @@ impl NotificationsView {
let mut view = NotificationsView { let mut view = NotificationsView {
comment_hm: HashMap::new(), comment_hm: HashMap::new(),
profile_hm: HashMap::new(), profile_hm: HashMap::new(),
submission_hm: HashMap::new(),
fr_profile_hm: HashMap::new(), fr_profile_hm: HashMap::new(),
comments: vec![], comments: vec![],
follow_requests: vec![], follow_requests: vec![],
count, count,
self_id: profile_id,
}; };
for comment_id in comment_notifs.for_profile(profile_id) { for comment_id in comment_notifs.for_profile(profile_id) {
@ -422,6 +452,21 @@ impl NotificationsView {
)?; )?;
view.profile_hm.insert(profile.id(), profile); view.profile_hm.insert(profile.id(), profile);
} }
if !view.submission_hm.contains_key(&comment.submission_id()) {
let submission = Submission::from_stores(
comment.submission_id(),
&submission_store,
&file_store,
)?;
view.submission_hm.insert(submission.id(), submission);
}
if let Some(id) = comment.comment_id() {
if !view.profile_hm.contains_key(&id) {
if let Some(comment) = comment_store.by_id(id)? {
view.comment_hm.insert(comment.id(), comment);
}
}
}
view.comments.push(comment.id()); view.comments.push(comment.id());
view.comment_hm.insert(comment.id(), comment); view.comment_hm.insert(comment.id(), comment);

47
server/static/nav.js Normal file
View file

@ -0,0 +1,47 @@
(function(fn) {
if (document.readyState === "complete" || document.readyState === "interactive") {
setTimeout(fn, 1);
} else {
document.addEventListener("DOMContentLoaded", fn);
}
})(function() {
function onClick(nav) {
return function _listener(event) {
event.preventDefault();
var containsOpen = false;
for (var i = 0; i < nav.classList.length; i++) {
if (nav.classList[i] == "nav-open") {
containsOpen = true;
}
}
if (containsOpen) {
nav.setAttribute("class", "nav-body nav-closing");
setTimeout(function() {
nav.setAttribute("class", "nav-body nav-closed");
}, 500)
} else {
nav.setAttribute("class", "nav-body nav-open");
}
};
}
var navs = document.getElementsByClassName("nav-body");
var nav = navs[0];
if (!nav) {
return;
}
var links = document.getElementsByClassName("nav-link");
for (var i = 0; i < links.length; i++) {
var link = links[i];
if (!link) {
continue;
}
link.addEventListener("click", onClick(nav));
}
})

View file

@ -0,0 +1,4 @@
@use hyaenidae_toolkit::templates::statics::button_js;
@()
<script src="@crate::toolkit_path(button_js.name)"></script>

View file

@ -1,3 +1,4 @@
@use crate::templates::button_js;
@use crate::templates::layouts::home; @use crate::templates::layouts::home;
@use crate::templates::comments::{nodes, profile_box}; @use crate::templates::comments::{nodes, profile_box};
@use crate::comments::CommentView; @use crate::comments::CommentView;
@ -11,7 +12,9 @@
@(view: &CommentView, nav_state: &NavState) @(view: &CommentView, nav_state: &NavState)
@if let Some((comment, author)) = view.comments.item.comment() { @if let Some((comment, author)) = view.comments.item.comment() {
@:home(&author.name(), comment.body(), nav_state, {}, { @:home(&author.name(), comment.body(), nav_state, {
@:button_js()
}, {
@:card(&Card::full_width().dark(nav_state.dark()), { @:card(&Card::full_width().dark(nav_state.dark()), {
@:card_title({ Comment }) @:card_title({ Comment })
@:card_body({ @:card_body({

View file

@ -1,5 +1,6 @@
@use crate::comments::ReportView; @use crate::comments::ReportView;
@use crate::nav::NavState; @use crate::nav::NavState;
@use crate::templates::button_js;
@use crate::templates::layouts::home; @use crate::templates::layouts::home;
@use crate::templates::comments::profile_box; @use crate::templates::comments::profile_box;
@use hyaenidae_toolkit::{templates::button_group, Button}; @use hyaenidae_toolkit::{templates::button_group, Button};
@ -8,7 +9,9 @@
@(view: &ReportView, nav_state: &NavState) @(view: &ReportView, nav_state: &NavState)
@:home("Report Comment", &format!("Report comment by {}", view.author.name()), nav_state, {}, { @:home("Report Comment", &format!("Report comment by {}", view.author.name()), nav_state, {
@:button_js()
}, {
@:card(Card::full_width().dark(nav_state.dark()), { @:card(Card::full_width().dark(nav_state.dark()), {
@:card_title({ Report Comment }) @:card_title({ Report Comment })
@:card_body({ @:card_body({

View file

@ -0,0 +1,5 @@
@use hyaenidae_toolkit::templates::statics::file_input_js;
@()
<script src="@crate::toolkit_path(file_input_js.name)"></script>

View file

@ -1,4 +1,5 @@
@use crate::{templates::{layouts::root, nav}, nav::NavState}; @use crate::{templates::{layouts::root, nav}, nav::NavState};
@use crate::templates::statics::nav_js;
@use hyaenidae_toolkit::templates::{bar, centered}; @use hyaenidae_toolkit::templates::{bar, centered};
@use hyaenidae_toolkit::{templates::{card, card_body}, Card}; @use hyaenidae_toolkit::{templates::{card, card_body}, Card};
@use hyaenidae_toolkit::{templates::link, Link}; @use hyaenidae_toolkit::{templates::link, Link};
@ -6,7 +7,10 @@
@(title: &str, description: &str, nav_state: &NavState, head: Content, body: Content) @(title: &str, description: &str, nav_state: &NavState, head: Content, body: Content)
@:root(title, description, nav_state.dark(), { @:head() }, { @:root(title, description, nav_state.dark(), {
<script src="@crate::statics_path(nav_js.name)"></script>
@:head()
}, {
@:bar(nav_state.dark(), "desktop-bar", { @:bar(nav_state.dark(), "desktop-bar", {
<div> <div>
@:link(&Link::current_tab("/").plain(true).dark(nav_state.dark()), { @:link(&Link::current_tab("/").plain(true).dark(nav_state.dark()), {
@ -21,7 +25,7 @@
@:link(&Link::current_tab("/").plain(true).dark(nav_state.dark()), { @:link(&Link::current_tab("/").plain(true).dark(nav_state.dark()), {
<h2>Hyaenidae</h2> <h2>Hyaenidae</h2>
}) })
<h3>@:link(&Link::current_tab(nav_state.href()).plain(true).dark(nav_state.dark()), { Nav })</h3> <h3 class="nav-link">@:link(&Link::current_tab(nav_state.href()).plain(true).dark(nav_state.dark()), { Nav })</h3>
}) })
<div class="home-content"> <div class="home-content">
@:centered(false, { @:centered(false, {
@ -29,10 +33,12 @@
}) })
</div> </div>
<div class="nav-body @nav_state.class_string()"> <div class="nav-body @nav_state.class_string()">
@:link(&Link::current_tab(nav_state.href()).plain(true).dark(nav_state.dark()), { <div class="nav-link nav-background">
<div class="nav-background"> @:link(&Link::current_tab(nav_state.href()).plain(true).dark(nav_state.dark()), {
</div> <div class="nav-background">
}) </div>
})
</div>
<nav class="nav-links"> <nav class="nav-links">
@:centered(false, { @:centered(false, {
@:card(&Card::full_width().classes(&["nav"]).dark(nav_state.dark()), { @:card(&Card::full_width().classes(&["nav"]).dark(nav_state.dark()), {
@ -40,9 +46,11 @@
@:nav(nav_state) @:nav(nav_state)
}) })
@:card_body({ @:card_body({
@:button_group(&[ <div class="nav-link">
Button::primary_outline("Close").href(nav_state.href()).dark(nav_state.dark()), @:button_group(&[
]) Button::primary_outline("Close").href(nav_state.href()).dark(nav_state.dark()),
])
</div>
}) })
}) })
}) })

View file

@ -1,5 +1,6 @@
@use crate::nav::NavState; @use crate::nav::NavState;
@use crate::notifications::NotificationsView; @use crate::notifications::NotificationsView;
@use crate::templates::button_js;
@use crate::templates::layouts::home; @use crate::templates::layouts::home;
@use crate::templates::submissions::profile_box; @use crate::templates::submissions::profile_box;
@use hyaenidae_toolkit::templates::button_group; @use hyaenidae_toolkit::templates::button_group;
@ -8,7 +9,9 @@
@(view: &NotificationsView, nav_state: &NavState) @(view: &NotificationsView, nav_state: &NavState)
@:home(&format!("Notifications: {}", view.count()), "Notifications on Hyaenidae", nav_state, {}, { @:home(&format!("Notifications: {}", view.count()), "Notifications on Hyaenidae", nav_state, {
@:button_js()
}, {
@:card(Card::full_width().dark(nav_state.dark()), { @:card(Card::full_width().dark(nav_state.dark()), {
@:card_title({ Clear All }) @:card_title({ Clear All })
@:card_body({ @:card_body({
@ -25,12 +28,13 @@
@:card_title({ Follow Requests }) @:card_title({ Follow Requests })
@for fr in view.follow_requests() { @for fr in view.follow_requests() {
@:card_body({ @:card_body({
@:profile_box(fr.profile, None, nav_state.dark(), { @:profile_box(fr.profile, None, nav_state.dark(), {})
<div class="button-section">
@:button_group(&[ @:button_group(&[
&fr.accept_button(nav_state.dark()), &fr.accept_button(nav_state.dark()),
&fr.reject_button(nav_state.dark()), &fr.reject_button(nav_state.dark()),
]) ])
}) </div>
}) })
} }
@:card_body({ @:card_body({
@ -50,8 +54,8 @@
@c.author_name() @c.author_name()
}) })
@if let Some(l) = c.submission_link(nav_state.dark()) { @if let Some(l) = c.submission_link(nav_state.dark()) {
commented on your commented on your submission:
@:link(&l, { submission }) @:link(&l, { @c.submission_title() })
} }
@if let Some(l) = c.reply_to_link(nav_state.dark()) { @if let Some(l) = c.reply_to_link(nav_state.dark()) {
replied to your replied to your

View file

@ -1,11 +1,12 @@
@use crate::{templates::{layouts::home, profiles::view}, nav::NavState, profiles::Profile}; @use crate::{templates::{layouts::home, profiles::view}, nav::NavState, profiles::Profile};
@use hyaenidae_toolkit::{templates::{button_group, card, card_body, card_title, file_input, statics::{button_js, file_input_js}}, Button, Card, FileInput}; @use crate::templates::{button_js, file_js};
@use hyaenidae_toolkit::{templates::{button_group, card, card_body, card_title, file_input}, Button, Card, FileInput};
@(banner_input: &FileInput, error: Option<String>, profile: &Profile, nav_state: &NavState) @(banner_input: &FileInput, error: Option<String>, profile: &Profile, nav_state: &NavState)
@:home("Create Profile", "Create a new profile on Hyaenidae", nav_state, { @:home("Create Profile", "Create a new profile on Hyaenidae", nav_state, {
<script src="/toolkit/@file_input_js.name"></script> @:button_js()
<script src="/toolkit/@button_js.name"></script> @:file_js()
}, { }, {
@:card(&Card::full_width().dark(nav_state.dark()), { @:card(&Card::full_width().dark(nav_state.dark()), {
<form method="POST" action="/profiles/create/banner" enctype="multipart/form-data"> <form method="POST" action="/profiles/create/banner" enctype="multipart/form-data">

View file

@ -1,10 +1,11 @@
@use crate::{templates::{layouts::home, profiles::view}, nav::NavState, profiles::Profile}; @use crate::{templates::{layouts::home, profiles::view}, nav::NavState, profiles::Profile};
@use hyaenidae_toolkit::{templates::{button_group, card, card_body, card_title, text_input, statics::button_js}, Button, Card, TextInput}; @use crate::templates::button_js;
@use hyaenidae_toolkit::{templates::{button_group, card, card_body, card_title, text_input}, Button, Card, TextInput};
@(display_name_input: &TextInput, description_input: &TextInput, profile: &Profile, nav_state: &NavState) @(display_name_input: &TextInput, description_input: &TextInput, profile: &Profile, nav_state: &NavState)
@:home("Create Profile", "Create a new profile on Hyaenidae", nav_state, { @:home("Create Profile", "Create a new profile on Hyaenidae", nav_state, {
<script src="/toolkit/@button_js.name"></script> @:button_js()
}, { }, {
@:card(&Card::full_width().dark(nav_state.dark()), { @:card(&Card::full_width().dark(nav_state.dark()), {
<form method="POST" action="/profiles/create/bio"> <form method="POST" action="/profiles/create/bio">

View file

@ -1,10 +1,11 @@
@use crate::{templates::layouts::home, nav::NavState}; @use crate::{templates::layouts::home, nav::NavState};
@use hyaenidae_toolkit::{templates::{button_group, card, card_body, card_title, text_input, statics::button_js}, Button, Card, TextInput}; @use crate::templates::button_js;
@use hyaenidae_toolkit::{templates::{button_group, card, card_body, card_title, text_input}, Button, Card, TextInput};
@(handle_input: &TextInput, nav_state: &NavState) @(handle_input: &TextInput, nav_state: &NavState)
@:home("Create Profile", "Create a new profile on Hyaenidae", nav_state, { @:home("Create Profile", "Create a new profile on Hyaenidae", nav_state, {
<script src="/toolkit/@button_js.name"></script> @:button_js()
}, { }, {
@:card(&Card::full_width().dark(nav_state.dark()), { @:card(&Card::full_width().dark(nav_state.dark()), {
<form method="POST" action="/profiles/create/handle"> <form method="POST" action="/profiles/create/handle">

View file

@ -1,11 +1,12 @@
@use crate::templates::{button_js, file_js};
@use crate::{templates::{layouts::home, profiles::view}, nav::NavState, profiles::Profile}; @use crate::{templates::{layouts::home, profiles::view}, nav::NavState, profiles::Profile};
@use hyaenidae_toolkit::{templates::{button_group, card, card_body, card_title, file_input, statics::{button_js, file_input_js}}, Button, Card, FileInput}; @use hyaenidae_toolkit::{templates::{button_group, card, card_body, card_title, file_input}, Button, Card, FileInput};
@(icon_input: &FileInput, error: Option<String>, profile: &Profile, nav_state: &NavState) @(icon_input: &FileInput, error: Option<String>, profile: &Profile, nav_state: &NavState)
@:home("Create Profile", "Create a new profile on Hyaenidae", nav_state, { @:home("Create Profile", "Create a new profile on Hyaenidae", nav_state, {
<script src="/toolkit/@file_input_js.name"></script> @:button_js()
<script src="/toolkit/@button_js.name"></script> @:file_js()
}, { }, {
@:card(&Card::full_width().dark(nav_state.dark()), { @:card(&Card::full_width().dark(nav_state.dark()), {
<form method="POST" action="/profiles/create/icon" enctype="multipart/form-data"> <form method="POST" action="/profiles/create/icon" enctype="multipart/form-data">

View file

@ -1,10 +1,11 @@
@use crate::templates::button_js;
@use crate::{templates::{layouts::home, profiles::view}, nav::NavState, profiles::Profile}; @use crate::{templates::{layouts::home, profiles::view}, nav::NavState, profiles::Profile};
@use hyaenidae_toolkit::{templates::{button_group, card, card_body, card_title, statics::button_js}, Button, Card}; @use hyaenidae_toolkit::{templates::{button_group, card, card_body, card_title}, Button, Card};
@(require_login: bool, error: Option<String>, profile: &Profile, nav_state: &NavState) @(require_login: bool, error: Option<String>, profile: &Profile, nav_state: &NavState)
@:home("Create Profile", "Create a new profile on Hyaenidae", nav_state, { @:home("Create Profile", "Create a new profile on Hyaenidae", nav_state, {
<script src="/toolkit/@button_js.name"></script> @:button_js()
}, { }, {
@:card(&Card::full_width().dark(nav_state.dark()), { @:card(&Card::full_width().dark(nav_state.dark()), {
<form method="POST" action="/profiles/create/require-login"> <form method="POST" action="/profiles/create/require-login">

View file

@ -1,11 +1,12 @@
@use crate::templates::{button_js, file_js};
@use crate::{templates::{layouts::home, profiles::{banner, icon, view}}, nav::NavState, profiles::ProfileState}; @use crate::{templates::{layouts::home, profiles::{banner, icon, view}}, nav::NavState, profiles::ProfileState};
@use hyaenidae_toolkit::{templates::{button, button_group, card, card_body, card_title, file_input, text_input, statics::{button_js, file_input_js}}, Button, Card}; @use hyaenidae_toolkit::{templates::{button, button_group, card, card_body, card_title, file_input, text_input}, Button, Card};
@(state: &ProfileState, nav_state: &NavState) @(state: &ProfileState, nav_state: &NavState)
@:home("Profile Settings", &format!("{}'s profile", state.profile.name()), nav_state, { @:home("Profile Settings", &format!("{}'s profile", state.profile.name()), nav_state, {
<script src="/toolkit/@file_input_js.name"></script> @:button_js()
<script src="/toolkit/@button_js.name"></script> @:file_js()
}, { }, {
@:card(Card::full_width().dark(nav_state.dark()), { @:view(&state.profile, nav_state.dark()) }) @:card(Card::full_width().dark(nav_state.dark()), { @:view(&state.profile, nav_state.dark()) })
@:card(Card::full_width().dark(nav_state.dark()), { @:card(Card::full_width().dark(nav_state.dark()), {

View file

@ -1,10 +1,11 @@
@use crate::templates::button_js;
@use crate::{templates::{layouts::home, profiles::view}, nav::NavState, profiles::Profile}; @use crate::{templates::{layouts::home, profiles::view}, nav::NavState, profiles::Profile};
@use hyaenidae_toolkit::{templates::{button_group, card, card_body, card_title, statics::button_js}, Button, Card}; @use hyaenidae_toolkit::{templates::{button_group, card, card_body, card_title}, Button, Card};
@(profile: &Profile, nav_state: &NavState) @(profile: &Profile, nav_state: &NavState)
@:home("Delete Profile", &format!("Delete {}", profile.name()), nav_state, { @:home("Delete Profile", &format!("Delete {}", profile.name()), nav_state, {
<script src="/toolkit/@button_js.name"></script> @:button_js()
}, { }, {
@:card(Card::full_width().dark(nav_state.dark()), { @:view(profile, nav_state.dark()) }) @:card(Card::full_width().dark(nav_state.dark()), { @:view(profile, nav_state.dark()) })
@:card(Card::full_width().dark(nav_state.dark()), { @:card(Card::full_width().dark(nav_state.dark()), {

View file

@ -1,3 +1,4 @@
@use crate::templates::button_js;
@use crate::{templates::{layouts::home, profiles::view}, nav::NavState, profiles::ReportView}; @use crate::{templates::{layouts::home, profiles::view}, nav::NavState, profiles::ReportView};
@use hyaenidae_toolkit::{templates::button_group, Button}; @use hyaenidae_toolkit::{templates::button_group, Button};
@use hyaenidae_toolkit::{templates::{card, card_body, card_title}, Card}; @use hyaenidae_toolkit::{templates::{card, card_body, card_title}, Card};
@ -5,7 +6,9 @@
@(rview: &ReportView, nav_state: &NavState) @(rview: &ReportView, nav_state: &NavState)
@:home("Report Profile", rview.profile.description().unwrap_or(&format!("Report {}'s profile on Hyaenidae", rview.profile.name())), nav_state, {}, { @:home("Report Profile", rview.profile.description().unwrap_or(&format!("Report {}'s profile on Hyaenidae", rview.profile.name())), nav_state, {
@:button_js()
}, {
@:card(Card::full_width().dark(nav_state.dark()), { @:view(&rview.profile, nav_state.dark()) }) @:card(Card::full_width().dark(nav_state.dark()), { @:view(&rview.profile, nav_state.dark()) })
@:card(Card::full_width().dark(nav_state.dark()), { @:card(Card::full_width().dark(nav_state.dark()), {
<form method="POST" action="@rview.profile.report_path()"> <form method="POST" action="@rview.profile.report_path()">

View file

@ -1,11 +1,12 @@
@use crate::templates::button_js;
@use crate::{templates::layouts::home, nav::NavState}; @use crate::{templates::layouts::home, nav::NavState};
@use hyaenidae_accounts::{templates::{update_password, update_username}, UpdatePasswordState, UpdateUsernameState, User}; @use hyaenidae_accounts::{templates::{update_password, update_username}, UpdatePasswordState, UpdateUsernameState, User};
@use hyaenidae_toolkit::{templates::{button, card, card_body, card_title, statics::button_js}, Button, Card}; @use hyaenidae_toolkit::{templates::{button, card, card_body, card_title}, Button, Card};
@(user: &User, uname_state: &UpdateUsernameState, pass_state: &UpdatePasswordState, nav_state: &NavState) @(user: &User, uname_state: &UpdateUsernameState, pass_state: &UpdatePasswordState, nav_state: &NavState)
@:home("Account Settings", "Update account information", nav_state, { @:home("Account Settings", "Update account information", nav_state, {
<script src="/toolkit/@button_js.name"></script> @:button_js()
}, { }, {
@:card(Card::full_width().dark(nav_state.dark()), { @:card(Card::full_width().dark(nav_state.dark()), {
@:card_title({ Current Username: @user.username() }) @:card_title({ Current Username: @user.username() })

View file

@ -1,9 +1,12 @@
@use crate::templates::button_js;
@use crate::{templates::layouts::home, nav::NavState}; @use crate::{templates::layouts::home, nav::NavState};
@use hyaenidae_accounts::{templates::delete_user, DeleteUserState}; @use hyaenidae_accounts::{templates::delete_user, DeleteUserState};
@use hyaenidae_toolkit::Card; @use hyaenidae_toolkit::Card;
@(state: &DeleteUserState, nav_state: &NavState) @(state: &DeleteUserState, nav_state: &NavState)
@:home("Delete Account", "Are you sure you want to delete your account?", nav_state, {}, { @:home("Delete Account", "Are you sure you want to delete your account?", nav_state, {
@:button_js()
}, {
@:delete_user(&Card::full_width().dark(nav_state.dark()), state) @:delete_user(&Card::full_width().dark(nav_state.dark()), state)
}) })

View file

@ -1,11 +1,12 @@
@use crate::templates::button_js;
@use crate::{templates::layouts::home, nav::NavState}; @use crate::{templates::layouts::home, nav::NavState};
@use hyaenidae_accounts::{templates::login, LoginState}; @use hyaenidae_accounts::{templates::login, LoginState};
@use hyaenidae_toolkit::{templates::statics::button_js, Card}; @use hyaenidae_toolkit::Card;
@(login_state: &LoginState, nav_state: &NavState) @(login_state: &LoginState, nav_state: &NavState)
@:home("Login", "Log into Hyaenidae", nav_state, { @:home("Login", "Log into Hyaenidae", nav_state, {
<script src="/toolkit/@button_js.name"></script> @:button_js()
}, { }, {
@:login(&Card::full_width().dark(nav_state.dark()), login_state) @:login(&Card::full_width().dark(nav_state.dark()), login_state)
}) })

View file

@ -1,11 +1,10 @@
@use crate::templates::button_js;
@use crate::{templates::layouts::home, nav::NavState}; @use crate::{templates::layouts::home, nav::NavState};
@use hyaenidae_accounts::{templates::register, RegisterState}; @use hyaenidae_accounts::{templates::register, RegisterState};
@use hyaenidae_toolkit::{templates::statics::button_js, Card}; @use hyaenidae_toolkit::Card;
@(register_state: &RegisterState, nav_state: &NavState) @(register_state: &RegisterState, nav_state: &NavState)
@:home("Register", "Register for Hyaenidae", nav_state, { @:home("Register", "Register for Hyaenidae", nav_state, { @:button_js() }, {
<script src="/toolkit/@button_js.name"></script>
}, {
@:register(&Card::full_width().dark(nav_state.dark()), register_state) @:register(&Card::full_width().dark(nav_state.dark()), register_state)
}) })

View file

@ -1,11 +1,12 @@
@use crate::templates::{button_js, file_js};
@use crate::{templates::layouts::home, nav::NavState}; @use crate::{templates::layouts::home, nav::NavState};
@use hyaenidae_toolkit::{templates::{button_group, card, card_body, card_title, file_input, statics::{button_js, file_input_js}}, Button, Card, FileInput}; @use hyaenidae_toolkit::{templates::{button_group, card, card_body, card_title, file_input}, Button, Card, FileInput};
@(file: &FileInput, error: Option<String>, nav_state: &NavState) @(file: &FileInput, error: Option<String>, nav_state: &NavState)
@:home("Create Submission", "Upload a file", nav_state, { @:home("Create Submission", "Upload a file", nav_state, {
<script src="/toolkit/@button_js.name"></script> @:button_js()
<script src="/toolkit/@file_input_js.name"></script> @:file_js()
}, { }, {
@:card(&Card::full_width().dark(nav_state.dark()), { @:card(&Card::full_width().dark(nav_state.dark()), {
<form method="POST" action="/submissions/create" enctype="multipart/form-data"> <form method="POST" action="/submissions/create" enctype="multipart/form-data">

View file

@ -1,3 +1,4 @@
@use crate::templates::button_js;
@use crate::templates::layouts::home; @use crate::templates::layouts::home;
@use crate::templates::comments::nodes; @use crate::templates::comments::nodes;
@use crate::templates::submissions::{image, profile_box}; @use crate::templates::submissions::{image, profile_box};
@ -9,7 +10,9 @@
@(view: &SubmissionView, nav_state: &NavState) @(view: &SubmissionView, nav_state: &NavState)
@:home(&view.submission.title(), view.submission.description().unwrap_or(&format!("{} hosted on Hyaenidae", view.submission.title())), nav_state, {}, { @:home(&view.submission.title(), view.submission.description().unwrap_or(&format!("{} hosted on Hyaenidae", view.submission.title())), nav_state, {
@:button_js()
}, {
@:card(&Card::full_width().dark(nav_state.dark()), { @:card(&Card::full_width().dark(nav_state.dark()), {
@:card_title({ @:card_title({
@view.submission.title() @view.submission.title()

View file

@ -1,3 +1,4 @@
@use crate::templates::button_js;
@use crate::templates::admin::submission_box; @use crate::templates::admin::submission_box;
@use crate::templates::layouts::home; @use crate::templates::layouts::home;
@use crate::{nav::NavState, submissions::ReportView}; @use crate::{nav::NavState, submissions::ReportView};
@ -7,8 +8,9 @@
@(view: &ReportView, nav_state: &NavState) @(view: &ReportView, nav_state: &NavState)
@:home("Report Submission", view.submission.description().unwrap_or(&format!("Report {}'s @:home("Report Submission", view.submission.description().unwrap_or(&format!("Report {}'s submission on Hyaenidae", view.profile.name())), nav_state, {
submission on Hyaenidae", view.profile.name())), nav_state, {}, { @:button_js()
}, {
@:card(&Card::full_width().dark(nav_state.dark()), { @:card(&Card::full_width().dark(nav_state.dark()), {
@:card_title({ @:card_title({
Report @view.submission.title() Report @view.submission.title()

View file

@ -1,11 +1,12 @@
@use crate::templates::{button_js, file_js};
@use crate::{templates::{layouts::home, submissions::image}, nav::NavState, submissions::SubmissionState}; @use crate::{templates::{layouts::home, submissions::image}, nav::NavState, submissions::SubmissionState};
@use hyaenidae_toolkit::{templates::{button_group, card, card_body, card_title, file_input, text_input, statics::{button_js, file_input_js}}, Button, Card}; @use hyaenidae_toolkit::{templates::{button_group, card, card_body, card_title, file_input, text_input}, Button, Card};
@(state: &SubmissionState, nav_state: &NavState) @(state: &SubmissionState, nav_state: &NavState)
@:home("Update Submission", "Update information or images", nav_state, { @:home("Update Submission", "Update information or images", nav_state, {
<script src="/toolkit/@button_js.name"></script> @:button_js()
<script src="/toolkit/@file_input_js.name"></script> @:file_js()
}, { }, {
@if !state.is_published() { @if !state.is_published() {
@:card(&Card::full_width().dark(nav_state.dark()), { @:card(&Card::full_width().dark(nav_state.dark()), {