Expose sensitive on submission page

- Don't show sensitive content to logged-out users
- Add sensitive indicator on submission tiles, thumbnails
- Improve mobile nav UI
This commit is contained in:
asonix 2021-02-02 21:21:31 -06:00
parent 2f9decc07f
commit 7ed5264bef
13 changed files with 252 additions and 145 deletions

View file

@ -23,12 +23,20 @@ picture {
.mobile-bar {
display: none;
.toolkit-button {
min-width: auto;
}
}
.profile-nav {
display: flex;
align-items: center;
.mobile-bar-icon {
margin-right: 0;
}
.bar-icon {
margin-right: 0;
margin-left: 16px;
@ -45,13 +53,6 @@ picture {
}
}
.narrow-nav {
display: none;
}
.wide-nav {
display: block;
}
.nav-body {
position: fixed;
z-index: 2;
@ -307,15 +308,6 @@ picture {
}
}
@media (max-width: 975px) {
.narrow-nav {
display: block;
}
.wide-nav {
display: none;
}
}
@media (max-width: 700px) {
.desktop-bar {
display: none;

View file

@ -117,13 +117,23 @@ impl ViewBrowseState {
.title(&submission.title_text())
.author(&author.name(), &author.view_path());
let sensitive_color = if submission.is_sensitive() {
Some(IndicatorColor::Red)
} else {
None
};
if submission.files().len() > 1 {
Some(thumb.indicator(
&format!("+{}", submission.files().len() - 1),
IndicatorColor::White,
sensitive_color.unwrap_or(IndicatorColor::White),
))
} else {
Some(thumb)
if let Some(sensitive_color) = sensitive_color {
Some(thumb.indicator("", sensitive_color))
} else {
Some(thumb)
}
}
})
.collect()

View file

@ -1,7 +1,6 @@
use crate::{
admin::Admin,
error::Error,
extensions::ProfileExt,
middleware::UserProfile,
notifications::total_for_profile,
views::{OwnedProfileView, ProfileView},
@ -133,52 +132,54 @@ impl NavState {
Button::primary_link(&fl!(loader, "nav-submission-button")).href("/submissions/create")
}
fn nav_button(&self, loader: &ActixLoader) -> Button {
Button::link(&fl!(loader, "nav-text"))
.href(&self.href)
.class("nav-link")
fn mobile_submission_button(&self, loader: &ActixLoader) -> Button {
Button::primary(&fl!(loader, "nav-submission-button")).href("/submissions/create")
}
pub(crate) fn browse_button(&self, loader: &ActixLoader) -> Button {
Button::link(&fl!(loader, "nav-browse-button")).href("/browse")
}
fn profile_button(&self, loader: &ActixLoader) -> Button {
if let Some(view) = self.profile.as_ref() {
Button::link(&fl!(loader, "nav-profile-button")).href(&view.profile.view_path())
} else {
Button::link(&fl!(loader, "nav-switch-profile-button")).href("/profiles/change")
}
fn mobile_browse_button(&self, loader: &ActixLoader) -> Button {
Button::secondary(&fl!(loader, "nav-browse-button")).href("/browse")
}
pub(crate) fn notifications_path(&self) -> &'static str {
"/notifications"
}
fn notifications_button(&self, loader: &ActixLoader) -> Button {
Button::link(&fl!(loader, "nav-notifications-button")).href(self.notifications_path())
}
fn admin_button(&self, loader: &ActixLoader) -> Button {
pub(crate) fn admin_button(&self, loader: &ActixLoader) -> Button {
Button::link(&fl!(loader, "nav-admin-button")).href("/admin")
}
fn account_button(&self, loader: &ActixLoader) -> Button {
Button::link(&fl!(loader, "nav-account-button")).href("/session/account")
fn mobile_admin_button(&self, loader: &ActixLoader) -> Button {
Button::secondary(&fl!(loader, "nav-admin-button")).href("/admin")
}
fn login_button(&self, loader: &ActixLoader) -> Button {
Button::primary_link(&fl!(loader, "nav-login-button")).href("/session/auth/login")
}
fn mobile_login_button(&self, loader: &ActixLoader) -> Button {
Button::primary_outline(&fl!(loader, "nav-login-button")).href("/session/auth/login")
}
fn register_button(&self, loader: &ActixLoader) -> Button {
Button::primary_link(&fl!(loader, "nav-register-button")).href("/session/auth/register")
}
fn mobile_register_button(&self, loader: &ActixLoader) -> Button {
Button::primary_outline(&fl!(loader, "nav-register-button")).href("/session/auth/register")
}
fn logout_button(&self, logout_state: &LogoutState, loader: &ActixLoader) -> Button {
Button::primary_link(&fl!(loader, "nav-logout-button")).form(&logout_state.logout_path())
}
fn mobile_logout_button(&self, logout_state: &LogoutState, loader: &ActixLoader) -> Button {
Button::primary_outline(&fl!(loader, "nav-logout-button")).form(&logout_state.logout_path())
}
pub(crate) fn has_notifications(&self) -> bool {
if let Some(count) = self.notification_count {
count > 0
@ -191,16 +192,11 @@ impl NavState {
let mut nav = vec![];
if let Some(logout_state) = &self.logout_state {
nav.push(self.submission_button(loader));
nav.push(self.browse_button(loader));
nav.push(self.profile_button(loader));
if self.has_notifications() {
nav.push(self.notifications_button(loader));
if self.profile.is_some() {
nav.push(self.submission_button(loader));
nav.push(self.browse_button(loader));
}
nav.push(self.account_button(loader));
if self.admin.is_some() {
nav.push(self.admin_button(loader));
}
@ -213,50 +209,25 @@ impl NavState {
nav
}
pub(crate) fn narrow_buttons(&self, loader: &ActixLoader) -> Vec<Button> {
let mut narrow_nav = vec![];
pub(crate) fn mobile_buttons(&self, loader: &ActixLoader) -> Vec<Button> {
let mut nav = vec![];
if self.logout_state.is_some() {
narrow_nav.push(self.submission_button(loader));
narrow_nav.push(self.nav_button(loader));
} else {
narrow_nav.push(self.nav_button(loader));
}
narrow_nav
}
pub(crate) fn site_buttons(&self, loader: &ActixLoader) -> Vec<Button> {
let mut site_nav = vec![];
if self.logout_state.is_some() {
site_nav.push(self.submission_button(loader));
site_nav.push(self.browse_button(loader));
site_nav.push(self.profile_button(loader));
if self.has_notifications() {
site_nav.push(self.notifications_button(loader));
if let Some(logout_state) = &self.logout_state {
if self.profile.is_some() {
nav.push(self.mobile_submission_button(loader));
nav.push(self.mobile_browse_button(loader));
}
if self.admin.is_some() {
site_nav.push(self.admin_button(loader));
nav.push(self.mobile_admin_button(loader));
}
}
site_nav
}
pub(crate) fn account_buttons(&self, loader: &ActixLoader) -> Vec<Button> {
let mut account_nav = vec![];
if let Some(logout_state) = &self.logout_state {
account_nav.push(self.logout_button(logout_state, loader));
nav.push(self.mobile_logout_button(logout_state, loader));
} else {
account_nav.push(self.login_button(loader));
account_nav.push(self.register_button(loader));
nav.push(self.mobile_login_button(loader));
nav.push(self.mobile_register_button(loader));
}
account_nav
nav
}
pub(crate) fn class_string(&self) -> &str {

View file

@ -337,6 +337,10 @@ pub(crate) fn can_view(
}
}
} else {
if submission.is_sensitive() {
return None;
}
if submission.is_unlisted() && !show_unlisted {
return None;
}

View file

@ -89,13 +89,23 @@ impl ViewProfileState {
.description(&author.name())
.link(&submission.view_path());
let sensitive_color = if submission.is_sensitive() {
Some(IndicatorColor::Red)
} else {
None
};
if submission.files().len() > 1 {
Some(tile.indicator(
&format!("+{}", submission.files().len() - 1),
IndicatorColor::White,
sensitive_color.unwrap_or(IndicatorColor::White),
))
} else {
Some(tile)
if let Some(sensitive_color) = sensitive_color {
Some(tile.indicator("", sensitive_color))
} else {
Some(tile)
}
}
})
.collect()
@ -106,6 +116,10 @@ impl ViewProfileState {
.href(&self.unwrap_profile().view_path())
}
fn account_settings_button(&self, loader: &ActixLoader) -> Button {
Button::secondary(&fl!(loader, "account-settings-button")).href("/session/account")
}
fn edit_profile_button(&self, loader: &ActixLoader) -> Button {
Button::secondary(&fl!(loader, "edit-profile-button")).href("/profiles/current")
}
@ -152,10 +166,12 @@ impl ViewProfileState {
btns.push(self.view_profile_button(loader));
btns.push(self.edit_profile_button(loader));
btns.push(self.switch_profile_button(loader));
btns.push(self.account_settings_button(loader));
} else if self.is_self {
btns.push(self.view_drafts_button(loader));
btns.push(self.edit_profile_button(loader));
btns.push(self.switch_profile_button(loader));
btns.push(self.account_settings_button(loader));
} else if self.is_follow_requested {
btns.push(self.cancel_request_button(loader));
btns.push(self.block_button(loader));
@ -341,6 +357,31 @@ impl EditProfileState {
}
}
fn view_profile_button(&self, loader: &ActixLoader) -> Button {
Button::secondary(&fl!(loader, "view-profile-button")).href(&self.profile.view_path())
}
fn view_drafts_button(&self, loader: &ActixLoader) -> Button {
Button::secondary(&fl!(loader, "drafts-button")).href("/profiles/drafts")
}
fn switch_profile_button(&self, loader: &ActixLoader) -> Button {
Button::secondary(&fl!(loader, "switch-profile-button")).href("/profiles/change")
}
fn account_settings_button(&self, loader: &ActixLoader) -> Button {
Button::secondary(&fl!(loader, "account-settings-button")).href("/session/account")
}
pub(crate) fn buttons(&self, loader: &ActixLoader) -> Vec<Button> {
vec![
self.view_profile_button(loader),
self.view_drafts_button(loader),
self.switch_profile_button(loader),
self.account_settings_button(loader),
]
}
pub(crate) fn display_name(&self, loader: &ActixLoader) -> TextInput {
let input = TextInput::new("display_name")
.title(&fl!(loader, "create-bio-display-name"))

View file

@ -51,6 +51,11 @@ pub(super) fn scope() -> Scope {
.route(web::get().to(route_to_update_page))
.route(web::post().to(update_visibility)),
)
.service(
web::resource("/sensitive")
.route(web::get().to(route_to_update_page))
.route(web::post().to(update_sensitive)),
)
.service(
web::resource("/add-file")
.route(web::get().to(route_to_update_page))
@ -287,6 +292,7 @@ pub struct SubmissionState {
pub(crate) publish_error: Option<String>,
pub(crate) delete_error: Option<String>,
pub(crate) visibility_error: Option<String>,
pub(crate) sensitive_error: Option<String>,
}
impl SubmissionState {
@ -320,6 +326,7 @@ impl SubmissionState {
publish_error: None,
delete_error: None,
visibility_error: None,
sensitive_error: None,
})
}
@ -382,6 +389,10 @@ impl SubmissionState {
format!("/submissions/{}/visibility", self.submission.id())
}
pub(crate) fn sensitive_path(&self) -> String {
format!("/submissions/{}/sensitive", self.submission.id())
}
pub(crate) fn update_path(&self) -> String {
format!("/submissions/{}/update", self.submission.id())
}
@ -460,6 +471,11 @@ impl SubmissionState {
self.visibility_error = Some(error);
self
}
fn sensitive_error(mut self, error: String) -> Self {
self.sensitive_error = Some(error);
self
}
}
async fn files_page(
@ -601,6 +617,12 @@ async fn can_view(
submission: &Submission,
state: &State,
) -> Result<Option<HttpResponse>, Error> {
if submission.is_sensitive() {
if viewer.is_none() {
return Ok(Some(crate::to_404()));
}
}
if poster.login_required() && viewer.is_none() {
return Ok(Some(crate::to_404()));
}
@ -1093,8 +1115,6 @@ async fn update_visibility(
return Ok(crate::to_404());
}
log::debug!("form: {:?}", form);
let res = state
.profiles
.run(UpdateSubmission::from_visibility(
@ -1119,6 +1139,50 @@ async fn update_visibility(
})
}
#[derive(Clone, Debug, serde::Deserialize)]
struct UpdateSensitiveForm {
sensitive: Option<String>,
}
async fn update_sensitive(
loader: ActixLoader,
form: web::Form<UpdateSensitiveForm>,
submission: CurrentSubmission,
profile: UserProfile,
nav_state: NavState,
state: web::Data<State>,
) -> Result<HttpResponse, Error> {
let submission = submission.0;
let profile = profile.0;
let form = form.into_inner();
if profile.id() != submission.profile_id() {
return Ok(crate::to_404());
}
let res = state
.profiles
.run(UpdateSubmission::from_sensitive(
submission.id(),
form.sensitive.is_some(),
))
.await;
let error = match res {
Ok(_) => return Ok(to_update_page(submission.id())),
Err(e) => e.to_string(),
};
let state = SubmissionState::new(submission, &state)
.await?
.sensitive_error(error);
crate::rendered(HttpResponse::BadRequest(), |cursor| {
crate::templates::submissions::update(cursor, &loader, &state, &nav_state)
})
}
async fn add_file(
loader: ActixLoader,
req: HttpRequest,

View file

@ -44,10 +44,23 @@ impl<'a> SubmissionView<'a> {
.title(&self.submission.title_text())
.author(&self.author.name(), &self.author.view_path());
if self.files.len() > 1 {
Some(thumb.indicator(&format!("+{}", self.files.len() - 1), IndicatorColor::White))
let sensitive_color = if self.submission.is_sensitive() {
Some(IndicatorColor::Red)
} else {
Some(thumb)
None
};
if self.files.len() > 1 {
Some(thumb.indicator(
&format!("+{}", self.files.len() - 1),
sensitive_color.unwrap_or(IndicatorColor::White),
))
} else {
if let Some(sensitive_color) = sensitive_color {
Some(thumb.indicator("", sensitive_color))
} else {
Some(thumb)
}
}
}
}

View file

@ -3,6 +3,7 @@
@use hyaenidae_toolkit::templates::bar;
@use hyaenidae_toolkit::templates::{button, button_group};
@use hyaenidae_toolkit::{templates::icon, Size};
@use hyaenidae_toolkit::templates::icon_button;
@use hyaenidae_toolkit::{templates::link, Link};
@use i18n_embed_fl::fl;
@ -21,33 +22,38 @@
@:button(&nav_state.submission_button(loader))
@:button(&nav_state.browse_button(loader))
@if nav_state.has_notifications() {
<div class="toolkit-button toolkit-button__link">
<span><i class="fa fa-bell"></i></span>
@:link(&Link::current_tab(nav_state.notifications_path()).plain(true).class("toolkit-button--action").title("Notifications"), {
<i class="fa fa-bell"></i>
})
</div>
@:icon_button("bell", &fl!(loader, "nav-notifications-button"), nav_state.notifications_path())
}
</div>
@:icon(&profile.icon(loader).size(Size::Tiny).class("bar-icon").dark(true))
</div>
} else {
<div class="narrow-nav">
@:button_group(&nav_state.narrow_buttons(loader))
</div>
<div class="wide-nav">
@:button_group(&nav_state.buttons(loader))
</div>
@:button_group(&nav_state.buttons(loader))
}
</nav>
})
@:bar(nav_state.dark(), "mobile-bar", {
@:link(&Link::current_tab("/").plain(true), {
<h2>@fl!(loader, "site-name")</h2>
})
<h3 class="nav-link">
@:link(&Link::current_tab(nav_state.href()).plain(true), {
@fl!(loader, "nav-text")
})
</h3>
<div class="profile-nav">
@if let Some(profile) = nav_state.profile() {
@:icon(&profile.icon(loader).size(Size::Tiny).class("mobile-bar-icon").dark(true))
} else {
@:link(&Link::current_tab("/").plain(true), {
<h2>@fl!(loader, "site-name")</h2>
})
}
</div>
<div class="toolkit-button-group">
@if nav_state.profile().is_some() {
@:icon_button("upload", &fl!(loader, "nav-submission-button"), "/submissions/create")
@:icon_button("home", &fl!(loader, "feed-title"), "/")
}
@if nav_state.has_notifications() {
<div class="toolkit-button-group">
@:icon_button("bell", &fl!(loader, "nav-notifications-button"), nav_state.notifications_path())
</div>
}
<div class="nav-link">
@:icon_button("ellipsis-v", &fl!(loader, "nav-text"), nav_state.href())
</div>
</div>
})

View file

@ -29,19 +29,11 @@
<nav class="nav-links">
@:centered(false, {
@:card(&Card::full_width().classes(&["nav"]).dark(nav_state.dark()), {
@if !nav_state.site_buttons(loader).is_empty() {
@:card_body({
<div class="nav-heading">
<h3>@fl!(loader, "site-navigation-heading")</h3>
</div>
@:button_group(&nav_state.site_buttons(loader))
})
}
@:card_body({
<div class="nav-heading">
<h3>@fl!(loader, "account-navigation-heading")</h3>
<h3>@fl!(loader, "site-name")</h3>
</div>
@:button_group(&nav_state.account_buttons(loader))
@:button_group(&nav_state.mobile_buttons(loader))
})
@:card_body({
@:button_group(&[

View file

@ -27,10 +27,7 @@
@fl!(loader, "profile-actions-heading")
})
@:card_body({
@:button_group(&[
Button::secondary(&fl!(loader, "view-profile-button")).href(&state.profile.view_path()),
Button::secondary(&fl!(loader, "switch-profile-button")).href("/profiles/change"),
])
@:button_group(&state.buttons(loader))
})
})
@:card(&Card::full_width().dark(nav_state.dark()), {

View file

@ -20,6 +20,16 @@
@:home(loader, &view.submission.title_text(), view.submission.description_text().unwrap_or(&fl!(loader, "view-submission-subtitle", title = view.submission.title_text())), nav_state, {
@:button_js()
}, {
@if view.is_self {
@:card(&Card::full_width().dark(nav_state.dark()), {
@:card_title({ Edit Submission })
@:card_body({
@:button_group(&[
Button::primary(&fl!(loader, "view-submission-edit-button")).href(&view.submission.update_path()),
])
})
})
}
@:card(&Card::full_width().dark(nav_state.dark()), {
@:card_title({
@Html(view.submission.title_text())
@ -58,16 +68,6 @@
})
})
})
@if view.is_self {
@:card(&Card::full_width().dark(nav_state.dark()), {
@:card_title({ Edit Submission })
@:card_body({
@:button_group(&[
Button::primary(&fl!(loader, "view-submission-edit-button")).href(&view.submission.update_path()),
])
})
})
}
@if view.profile.is_some() && view.submission.published().is_some() {
@:card(&Card::full_width().dark(nav_state.dark()), {
<form method="POST" action="@view.submission.comment_path()">

View file

@ -36,6 +36,16 @@
</form>
})
}
@:card(&Card::full_width().dark(nav_state.dark()), {
@:card_title({
@fl!(loader, "update-submission-view-heading")
})
@:card_body({
@:button_group(&[
Button::primary(&fl!(loader, "update-submission-view-button")).href(&state.view_path()),
])
})
})
@if !state.is_published() {
@:card(&Card::full_width().dark(nav_state.dark()), {
<form method="POST" action="@state.visibility_path()">
@ -60,6 +70,21 @@
</form>
})
}
@:card(&Card::full_width().dark(nav_state.dark()), {
<form method="POST" action="@state.sensitive_path()">
@:card_title({
@fl!(loader, "submission-sensitive-heading")
})
@:card_body({
@:checkbox("sensitive", &fl!(loader, "submission-sensitive-checkbox"), state.submission.is_sensitive())
})
@:card_body({
@:button_group(&[
Button::primary(&fl!(loader, "submission-sensitive-button")),
])
})
</form>
})
@:card(&Card::full_width().dark(nav_state.dark()), {
<form method="POST" action="@state.update_path()">
@:card_title({
@ -119,16 +144,6 @@
})
</form>
})
@:card(&Card::full_width().dark(nav_state.dark()), {
@:card_title({
@fl!(loader, "update-submission-view-heading")
})
@:card_body({
@:button_group(&[
Button::primary(&fl!(loader, "update-submission-view-button")).href(&state.view_path()),
])
})
})
@:card(&Card::full_width().dark(nav_state.dark()), {
@:card_title({
@fl!(loader, "danger-heading")

View file

@ -1,9 +1,7 @@
site-name = Hyaenidae
site-description = A simple website
nav-text = Nav
site-navigation-heading = Site Navigation
account-navigation-heading = Account Navigation
nav-text = Navigation
nav-cancel = Close
browse-logged-out = Log into {site-name} to see what's happening
@ -119,6 +117,7 @@ drafts-button = View Drafts
edit-profile-button = Edit Profile
switch-profile-button = Switch Profile
view-profile-button = View Profile
account-settings-button = Account Settings
profile-submissions-heading = Submissions
profile-drafts-heading = Drafts
@ -129,7 +128,6 @@ nav-profile-button = Profile
nav-switch-profile-button = Switch Profile
nav-notifications-button = Notifications
nav-admin-button = Admin
nav-account-button = Account
nav-login-button = Login
nav-register-button = Register
nav-logout-button = Logout
@ -178,6 +176,10 @@ submission-visibility-heading = Audience
submission-login-required = Require login to view this submission
submission-local-only = Restrict this submission to your {site-name} server
submission-sensitive-heading = Content Rating
submission-sensitive-checkbox = Mark this submission has NSFW
submission-sensitive-button = Save
update-submission-title = Edit Submission
update-submission-subtitle = Edit information or update images
update-submission-publish-title = Publish Submission