Server: Expose NSFW toggle, Dark Mode toggle
Ensure all submission view permission logic is the same
This commit is contained in:
parent
81edfa7123
commit
010dd2952f
|
@ -66,10 +66,16 @@ impl ViewBrowseState {
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let store = state.profiles.clone();
|
let store = state.profiles.clone();
|
||||||
|
|
||||||
|
let can_view_sensitive = if let Some(viewer) = viewer {
|
||||||
|
state.settings.for_profile(viewer).await?.sensitive
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
let state = actix_web::web::block(move || {
|
let state = actix_web::web::block(move || {
|
||||||
let mut cache = Cache::new();
|
let mut cache = Cache::new();
|
||||||
|
|
||||||
let page = browse_page(viewer, &store, &mut cache, source);
|
let page = browse_page(viewer, can_view_sensitive, &store, &mut cache, source);
|
||||||
|
|
||||||
Ok(ViewBrowseState {
|
Ok(ViewBrowseState {
|
||||||
cache,
|
cache,
|
||||||
|
|
|
@ -163,16 +163,16 @@ fn can_view_logged_out(
|
||||||
None => return Ok(false),
|
None => return Ok(false),
|
||||||
};
|
};
|
||||||
|
|
||||||
if submission.is_followers_only() {
|
if crate::pagination::submission::can_view(
|
||||||
return Ok(false);
|
None,
|
||||||
}
|
&submission,
|
||||||
|
&store.store,
|
||||||
let submissioner = match store.store.profiles.by_id(submission.profile_id())? {
|
&mut Default::default(),
|
||||||
Some(s) => s,
|
true,
|
||||||
None => return Ok(false),
|
false,
|
||||||
};
|
)
|
||||||
|
.is_none()
|
||||||
if submissioner.login_required() {
|
{
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,6 +217,7 @@ fn can_view_comment_logged_out_no_recurse(
|
||||||
|
|
||||||
fn can_view(
|
fn can_view(
|
||||||
profile: &Profile,
|
profile: &Profile,
|
||||||
|
can_view_sensitive: bool,
|
||||||
comment: &Comment,
|
comment: &Comment,
|
||||||
store: &hyaenidae_profiles::State,
|
store: &hyaenidae_profiles::State,
|
||||||
) -> Result<bool, Error> {
|
) -> Result<bool, Error> {
|
||||||
|
@ -225,47 +226,19 @@ fn can_view(
|
||||||
None => return Ok(false),
|
None => return Ok(false),
|
||||||
};
|
};
|
||||||
|
|
||||||
let submissioner_id = submission.profile_id();
|
if crate::pagination::submission::can_view(
|
||||||
|
Some(profile.id()),
|
||||||
let blocking_submissioner = store
|
&submission,
|
||||||
.store
|
&store.store,
|
||||||
.view
|
&mut Default::default(),
|
||||||
.blocks
|
true,
|
||||||
.by_forward(submissioner_id, profile.id())?
|
can_view_sensitive,
|
||||||
.is_some();
|
)
|
||||||
|
.is_none()
|
||||||
if blocking_submissioner {
|
{
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
let blocked_by_submissioner = store
|
|
||||||
.store
|
|
||||||
.view
|
|
||||||
.blocks
|
|
||||||
.by_forward(profile.id(), submissioner_id)?
|
|
||||||
.is_some();
|
|
||||||
|
|
||||||
if blocked_by_submissioner {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if submission.is_followers_only() {
|
|
||||||
let is_submissioner = profile.id() == submissioner_id;
|
|
||||||
|
|
||||||
if !is_submissioner {
|
|
||||||
let follows_submissioner = store
|
|
||||||
.store
|
|
||||||
.view
|
|
||||||
.follows
|
|
||||||
.by_forward(submissioner_id, profile.id())?
|
|
||||||
.is_some();
|
|
||||||
|
|
||||||
if !follows_submissioner {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
can_view_comment(profile, comment, store)
|
can_view_comment(profile, comment, store)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -325,8 +298,14 @@ async fn prepare_view(
|
||||||
profile: Option<&Profile>,
|
profile: Option<&Profile>,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<Option<CommentView>, Error> {
|
) -> Result<Option<CommentView>, Error> {
|
||||||
|
let can_view_sensitive = if let Some(profile) = profile {
|
||||||
|
state.settings.for_profile(profile.id()).await?.sensitive
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
match profile {
|
match profile {
|
||||||
Some(profile) if !can_view(&profile, &comment, &state.profiles)? => {
|
Some(profile) if !can_view(&profile, can_view_sensitive, &comment, &state.profiles)? => {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
None if !can_view_logged_out(&comment, &state.profiles)? => {
|
None if !can_view_logged_out(&comment, &state.profiles)? => {
|
||||||
|
@ -550,7 +529,9 @@ async fn reply(
|
||||||
None => return Ok(crate::to_404()),
|
None => return Ok(crate::to_404()),
|
||||||
};
|
};
|
||||||
|
|
||||||
if !can_view(&profile, &comment, &state.profiles)? {
|
let can_view_sensitive = state.settings.for_profile(profile.id()).await?.sensitive;
|
||||||
|
|
||||||
|
if !can_view(&profile, can_view_sensitive, &comment, &state.profiles)? {
|
||||||
return Ok(crate::to_404());
|
return Ok(crate::to_404());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -738,7 +719,9 @@ async fn report_page(
|
||||||
None => return Ok(crate::to_404()),
|
None => return Ok(crate::to_404()),
|
||||||
};
|
};
|
||||||
|
|
||||||
if !can_view(&profile, &comment, &state.profiles)? {
|
let can_view_sensitive = state.settings.for_profile(profile.id()).await?.sensitive;
|
||||||
|
|
||||||
|
if !can_view(&profile, can_view_sensitive, &comment, &state.profiles)? {
|
||||||
return Ok(crate::to_404());
|
return Ok(crate::to_404());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -775,7 +758,9 @@ async fn report(
|
||||||
None => return Ok(crate::to_404()),
|
None => return Ok(crate::to_404()),
|
||||||
};
|
};
|
||||||
|
|
||||||
if !can_view(&profile, &comment, &state.profiles)? {
|
let can_view_sensitive = state.settings.for_profile(profile.id()).await?.sensitive;
|
||||||
|
|
||||||
|
if !can_view(&profile, can_view_sensitive, &comment, &state.profiles)? {
|
||||||
return Ok(crate::to_404());
|
return Ok(crate::to_404());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -828,7 +813,9 @@ async fn report_success_page(
|
||||||
None => return Ok(crate::to_404()),
|
None => return Ok(crate::to_404()),
|
||||||
};
|
};
|
||||||
|
|
||||||
if !can_view(&profile, &comment, &state.profiles)? {
|
let can_view_sensitive = state.settings.for_profile(profile.id()).await?.sensitive;
|
||||||
|
|
||||||
|
if !can_view(&profile, can_view_sensitive, &comment, &state.profiles)? {
|
||||||
return Ok(crate::to_404());
|
return Ok(crate::to_404());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -221,6 +221,7 @@ struct State {
|
||||||
spawn: jobs::Spawn,
|
spawn: jobs::Spawn,
|
||||||
apub: apub::Apub,
|
apub: apub::Apub,
|
||||||
images: images::Images,
|
images: images::Images,
|
||||||
|
settings: profiles::SettingStore,
|
||||||
domain: String,
|
domain: String,
|
||||||
base_url: url::Url,
|
base_url: url::Url,
|
||||||
db: Db,
|
db: Db,
|
||||||
|
@ -240,6 +241,7 @@ impl State {
|
||||||
let domain = base_url.domain().req()?.to_owned();
|
let domain = base_url.domain().req()?.to_owned();
|
||||||
|
|
||||||
let admin = admin::Store::build(db)?;
|
let admin = admin::Store::build(db)?;
|
||||||
|
let settings = profiles::SettingStore::build(db)?;
|
||||||
|
|
||||||
Ok(State {
|
Ok(State {
|
||||||
profiles: hyaenidae_profiles::State::build(
|
profiles: hyaenidae_profiles::State::build(
|
||||||
|
@ -254,6 +256,7 @@ impl State {
|
||||||
spawn,
|
spawn,
|
||||||
apub,
|
apub,
|
||||||
images,
|
images,
|
||||||
|
settings,
|
||||||
base_url,
|
base_url,
|
||||||
domain,
|
domain,
|
||||||
db: db.clone(),
|
db: db.clone(),
|
||||||
|
|
|
@ -63,6 +63,12 @@ impl FromRequest for NavState {
|
||||||
format!("{}?show_nav=true", path)
|
format!("{}?show_nav=true", path)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let dark = if let Some(profile) = &profile {
|
||||||
|
state.settings.for_profile(profile.id()).await?.dark
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
};
|
||||||
|
|
||||||
let notification_count = if let Some(profile) = &profile {
|
let notification_count = if let Some(profile) = &profile {
|
||||||
total_for_profile(profile.id(), &state).await.ok()
|
total_for_profile(profile.id(), &state).await.ok()
|
||||||
} else {
|
} else {
|
||||||
|
@ -99,7 +105,7 @@ impl FromRequest for NavState {
|
||||||
admin,
|
admin,
|
||||||
href,
|
href,
|
||||||
is_open,
|
is_open,
|
||||||
dark: true,
|
dark,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,12 +22,14 @@ impl Cache {
|
||||||
|
|
||||||
pub(crate) fn browse_page(
|
pub(crate) fn browse_page(
|
||||||
viewer: Option<Uuid>,
|
viewer: Option<Uuid>,
|
||||||
|
can_view_sensitive: bool,
|
||||||
store: &hyaenidae_profiles::State,
|
store: &hyaenidae_profiles::State,
|
||||||
cache: &mut Cache,
|
cache: &mut Cache,
|
||||||
source: Option<PageSource>,
|
source: Option<PageSource>,
|
||||||
) -> Page {
|
) -> Page {
|
||||||
Page::from_pagination(
|
Page::from_pagination(
|
||||||
BrowsePager {
|
BrowsePager {
|
||||||
|
can_view_sensitive,
|
||||||
viewer,
|
viewer,
|
||||||
store,
|
store,
|
||||||
cache,
|
cache,
|
||||||
|
@ -39,6 +41,7 @@ pub(crate) fn browse_page(
|
||||||
|
|
||||||
pub(crate) fn draft_page(
|
pub(crate) fn draft_page(
|
||||||
viewer: Option<Uuid>,
|
viewer: Option<Uuid>,
|
||||||
|
can_view_sensitive: bool,
|
||||||
profile_id: Uuid,
|
profile_id: Uuid,
|
||||||
store: &hyaenidae_profiles::State,
|
store: &hyaenidae_profiles::State,
|
||||||
cache: &mut Cache,
|
cache: &mut Cache,
|
||||||
|
@ -46,6 +49,7 @@ pub(crate) fn draft_page(
|
||||||
) -> Page {
|
) -> Page {
|
||||||
Page::from_pagination(
|
Page::from_pagination(
|
||||||
DraftPager {
|
DraftPager {
|
||||||
|
can_view_sensitive,
|
||||||
viewer,
|
viewer,
|
||||||
profile_id,
|
profile_id,
|
||||||
store,
|
store,
|
||||||
|
@ -58,6 +62,7 @@ pub(crate) fn draft_page(
|
||||||
|
|
||||||
pub(crate) fn main_page(
|
pub(crate) fn main_page(
|
||||||
viewer: Option<Uuid>,
|
viewer: Option<Uuid>,
|
||||||
|
can_view_sensitive: bool,
|
||||||
profile_id: Uuid,
|
profile_id: Uuid,
|
||||||
store: &hyaenidae_profiles::State,
|
store: &hyaenidae_profiles::State,
|
||||||
cache: &mut Cache,
|
cache: &mut Cache,
|
||||||
|
@ -65,6 +70,7 @@ pub(crate) fn main_page(
|
||||||
) -> Page {
|
) -> Page {
|
||||||
Page::from_pagination(
|
Page::from_pagination(
|
||||||
SubmissionPager {
|
SubmissionPager {
|
||||||
|
can_view_sensitive,
|
||||||
viewer,
|
viewer,
|
||||||
profile_id,
|
profile_id,
|
||||||
store,
|
store,
|
||||||
|
@ -76,6 +82,7 @@ pub(crate) fn main_page(
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BrowsePager<'b> {
|
struct BrowsePager<'b> {
|
||||||
|
can_view_sensitive: bool,
|
||||||
viewer: Option<Uuid>,
|
viewer: Option<Uuid>,
|
||||||
store: &'b hyaenidae_profiles::State,
|
store: &'b hyaenidae_profiles::State,
|
||||||
cache: &'b mut Cache,
|
cache: &'b mut Cache,
|
||||||
|
@ -93,6 +100,7 @@ impl<'b> Pagination for BrowsePager<'b> {
|
||||||
&self.store.store,
|
&self.store.store,
|
||||||
self.cache,
|
self.cache,
|
||||||
false,
|
false,
|
||||||
|
self.can_view_sensitive,
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -108,6 +116,7 @@ impl<'b> Pagination for BrowsePager<'b> {
|
||||||
&self.store.store,
|
&self.store.store,
|
||||||
self.cache,
|
self.cache,
|
||||||
false,
|
false,
|
||||||
|
self.can_view_sensitive,
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -123,12 +132,14 @@ impl<'b> Pagination for BrowsePager<'b> {
|
||||||
&self.store.store,
|
&self.store.store,
|
||||||
self.cache,
|
self.cache,
|
||||||
false,
|
false,
|
||||||
|
self.can_view_sensitive,
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DraftPager<'b> {
|
struct DraftPager<'b> {
|
||||||
|
can_view_sensitive: bool,
|
||||||
viewer: Option<Uuid>,
|
viewer: Option<Uuid>,
|
||||||
profile_id: Uuid,
|
profile_id: Uuid,
|
||||||
store: &'b hyaenidae_profiles::State,
|
store: &'b hyaenidae_profiles::State,
|
||||||
|
@ -147,6 +158,7 @@ impl<'b> Pagination for DraftPager<'b> {
|
||||||
&self.store.store,
|
&self.store.store,
|
||||||
self.cache,
|
self.cache,
|
||||||
true,
|
true,
|
||||||
|
self.can_view_sensitive,
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -162,6 +174,7 @@ impl<'b> Pagination for DraftPager<'b> {
|
||||||
&self.store.store,
|
&self.store.store,
|
||||||
self.cache,
|
self.cache,
|
||||||
true,
|
true,
|
||||||
|
self.can_view_sensitive,
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -177,12 +190,14 @@ impl<'b> Pagination for DraftPager<'b> {
|
||||||
&self.store.store,
|
&self.store.store,
|
||||||
self.cache,
|
self.cache,
|
||||||
true,
|
true,
|
||||||
|
self.can_view_sensitive,
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SubmissionPager<'b> {
|
struct SubmissionPager<'b> {
|
||||||
|
can_view_sensitive: bool,
|
||||||
viewer: Option<Uuid>,
|
viewer: Option<Uuid>,
|
||||||
profile_id: Uuid,
|
profile_id: Uuid,
|
||||||
store: &'b hyaenidae_profiles::State,
|
store: &'b hyaenidae_profiles::State,
|
||||||
|
@ -201,6 +216,7 @@ impl<'b> Pagination for SubmissionPager<'b> {
|
||||||
&self.store.store,
|
&self.store.store,
|
||||||
self.cache,
|
self.cache,
|
||||||
true,
|
true,
|
||||||
|
self.can_view_sensitive,
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -216,6 +232,7 @@ impl<'b> Pagination for SubmissionPager<'b> {
|
||||||
&self.store.store,
|
&self.store.store,
|
||||||
self.cache,
|
self.cache,
|
||||||
true,
|
true,
|
||||||
|
self.can_view_sensitive,
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -231,6 +248,7 @@ impl<'b> Pagination for SubmissionPager<'b> {
|
||||||
&self.store.store,
|
&self.store.store,
|
||||||
self.cache,
|
self.cache,
|
||||||
true,
|
true,
|
||||||
|
self.can_view_sensitive,
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -241,6 +259,7 @@ fn filter_submissions<'a>(
|
||||||
store: &'a hyaenidae_profiles::store::Store,
|
store: &'a hyaenidae_profiles::store::Store,
|
||||||
cache: &'a mut Cache,
|
cache: &'a mut Cache,
|
||||||
show_unlisted: bool,
|
show_unlisted: bool,
|
||||||
|
can_view_sensitive: bool,
|
||||||
) -> impl FnMut(Uuid) -> Option<Uuid> + 'a {
|
) -> impl FnMut(Uuid) -> Option<Uuid> + 'a {
|
||||||
move |submission_id| {
|
move |submission_id| {
|
||||||
if !cache.submission_map.contains_key(&submission_id) {
|
if !cache.submission_map.contains_key(&submission_id) {
|
||||||
|
@ -251,7 +270,14 @@ fn filter_submissions<'a>(
|
||||||
cache.profile_map.insert(profile.id(), profile);
|
cache.profile_map.insert(profile.id(), profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
let opt = can_view(viewer, &submission, store, cache, show_unlisted);
|
let opt = can_view(
|
||||||
|
viewer,
|
||||||
|
&submission,
|
||||||
|
store,
|
||||||
|
cache,
|
||||||
|
show_unlisted,
|
||||||
|
can_view_sensitive,
|
||||||
|
);
|
||||||
|
|
||||||
if let Some(file_id) = submission.files().get(0) {
|
if let Some(file_id) = submission.files().get(0) {
|
||||||
if !cache.file_map.contains_key(file_id) {
|
if !cache.file_map.contains_key(file_id) {
|
||||||
|
@ -266,7 +292,14 @@ fn filter_submissions<'a>(
|
||||||
} else {
|
} else {
|
||||||
let submission = cache.submission_map.get(&submission_id)?.clone();
|
let submission = cache.submission_map.get(&submission_id)?.clone();
|
||||||
|
|
||||||
can_view(viewer, &submission, store, cache, show_unlisted)?;
|
can_view(
|
||||||
|
viewer,
|
||||||
|
&submission,
|
||||||
|
store,
|
||||||
|
cache,
|
||||||
|
show_unlisted,
|
||||||
|
can_view_sensitive,
|
||||||
|
)?;
|
||||||
|
|
||||||
Some(submission_id)
|
Some(submission_id)
|
||||||
}
|
}
|
||||||
|
@ -279,11 +312,17 @@ pub(crate) fn can_view(
|
||||||
store: &hyaenidae_profiles::store::Store,
|
store: &hyaenidae_profiles::store::Store,
|
||||||
cache: &mut Cache,
|
cache: &mut Cache,
|
||||||
show_unlisted: bool,
|
show_unlisted: bool,
|
||||||
|
can_view_sensitive: bool,
|
||||||
) -> Option<()> {
|
) -> Option<()> {
|
||||||
if let Some(viewer) = viewer {
|
if let Some(viewer) = viewer {
|
||||||
if viewer == submission.profile_id() {
|
if viewer == submission.profile_id() {
|
||||||
return Some(());
|
return Some(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if submission.is_sensitive() && !can_view_sensitive {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(block) = cache.blocks.get(&submission.profile_id()) {
|
if let Some(block) = cache.blocks.get(&submission.profile_id()) {
|
||||||
if *block {
|
if *block {
|
||||||
return None;
|
return None;
|
||||||
|
|
|
@ -15,9 +15,11 @@ use i18n_embed_fl::fl;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
mod settings;
|
||||||
mod state;
|
mod state;
|
||||||
mod update;
|
mod update;
|
||||||
|
|
||||||
|
pub(crate) use settings::{SettingStore, Settings};
|
||||||
pub use state::{EditProfileState, ViewProfileState};
|
pub use state::{EditProfileState, ViewProfileState};
|
||||||
pub use update::HandleState;
|
pub use update::HandleState;
|
||||||
|
|
||||||
|
|
52
src/profiles/settings.rs
Normal file
52
src/profiles/settings.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
use crate::error::Error;
|
||||||
|
use sled::{Db, Tree};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub(crate) struct Settings {
|
||||||
|
pub(crate) dark: bool,
|
||||||
|
pub(crate) sensitive: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct SettingStore {
|
||||||
|
tree: Tree,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SettingStore {
|
||||||
|
pub(crate) fn build(db: &Db) -> Result<Self, sled::Error> {
|
||||||
|
Ok(SettingStore {
|
||||||
|
tree: db.open_tree("/main/settings")?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn for_profile(&self, profile_id: Uuid) -> Result<Settings, Error> {
|
||||||
|
let this = self.clone();
|
||||||
|
|
||||||
|
let opt = actix_web::web::block(move || {
|
||||||
|
Ok(this
|
||||||
|
.tree
|
||||||
|
.get(profile_id.as_bytes())?
|
||||||
|
.and_then(|ivec| serde_json::from_slice(&ivec).ok()))
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(opt.unwrap_or(Settings {
|
||||||
|
dark: true,
|
||||||
|
sensitive: false,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn update(&self, profile_id: Uuid, settings: Settings) -> Result<(), Error> {
|
||||||
|
let this = self.clone();
|
||||||
|
|
||||||
|
actix_web::web::block(move || {
|
||||||
|
let vec = serde_json::to_vec(&settings)?;
|
||||||
|
this.tree.insert(profile_id.as_bytes(), vec)?;
|
||||||
|
Ok(()) as Result<_, Error>
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ use crate::{
|
||||||
submission::{draft_page, main_page, Cache},
|
submission::{draft_page, main_page, Cache},
|
||||||
PageSource,
|
PageSource,
|
||||||
},
|
},
|
||||||
|
profiles::settings::Settings,
|
||||||
views::ProfileView,
|
views::ProfileView,
|
||||||
ActixLoader, State,
|
ActixLoader, State,
|
||||||
};
|
};
|
||||||
|
@ -44,6 +45,8 @@ pub struct EditProfileState {
|
||||||
pub(crate) banner_error: Option<String>,
|
pub(crate) banner_error: Option<String>,
|
||||||
login_required_value: Option<bool>,
|
login_required_value: Option<bool>,
|
||||||
pub(crate) login_required_error: Option<String>,
|
pub(crate) login_required_error: Option<String>,
|
||||||
|
pub(crate) settings_error: Option<String>,
|
||||||
|
pub(crate) settings: Settings,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ViewProfileState {
|
impl ViewProfileState {
|
||||||
|
@ -226,6 +229,12 @@ impl ViewProfileState {
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let store = state.profiles.clone();
|
let store = state.profiles.clone();
|
||||||
|
|
||||||
|
let can_view_sensitive = if let Some(viewer) = viewer {
|
||||||
|
state.settings.for_profile(viewer).await?.sensitive
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
let state = actix_web::web::block(move || {
|
let state = actix_web::web::block(move || {
|
||||||
let mut cache = Cache::new();
|
let mut cache = Cache::new();
|
||||||
|
|
||||||
|
@ -248,9 +257,23 @@ impl ViewProfileState {
|
||||||
cache.profile_map.insert(profile.id(), profile);
|
cache.profile_map.insert(profile.id(), profile);
|
||||||
|
|
||||||
let page = if drafts {
|
let page = if drafts {
|
||||||
draft_page(viewer, profile_id, &store, &mut cache, source)
|
draft_page(
|
||||||
|
viewer,
|
||||||
|
can_view_sensitive,
|
||||||
|
profile_id,
|
||||||
|
&store,
|
||||||
|
&mut cache,
|
||||||
|
source,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
main_page(viewer, profile_id, &store, &mut cache, source)
|
main_page(
|
||||||
|
viewer,
|
||||||
|
can_view_sensitive,
|
||||||
|
profile_id,
|
||||||
|
&store,
|
||||||
|
&mut cache,
|
||||||
|
source,
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let is_self = viewer.map(|id| id == profile_id).unwrap_or(false);
|
let is_self = viewer.map(|id| id == profile_id).unwrap_or(false);
|
||||||
|
@ -308,7 +331,12 @@ impl ViewProfileState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EditProfileState {
|
impl EditProfileState {
|
||||||
pub(super) fn new(profile: Profile, icon: Option<File>, banner: Option<File>) -> Self {
|
pub(super) fn new(
|
||||||
|
profile: Profile,
|
||||||
|
icon: Option<File>,
|
||||||
|
banner: Option<File>,
|
||||||
|
settings: Settings,
|
||||||
|
) -> Self {
|
||||||
EditProfileState {
|
EditProfileState {
|
||||||
profile,
|
profile,
|
||||||
icon,
|
icon,
|
||||||
|
@ -321,6 +349,8 @@ impl EditProfileState {
|
||||||
banner_error: None,
|
banner_error: None,
|
||||||
login_required_value: None,
|
login_required_value: None,
|
||||||
login_required_error: None,
|
login_required_error: None,
|
||||||
|
settings_error: None,
|
||||||
|
settings,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -330,6 +360,8 @@ impl EditProfileState {
|
||||||
let icon_id = profile.icon();
|
let icon_id = profile.icon();
|
||||||
let banner_id = profile.banner();
|
let banner_id = profile.banner();
|
||||||
|
|
||||||
|
let settings = state.settings.for_profile(profile.id()).await?;
|
||||||
|
|
||||||
let (icon, banner) = actix_web::web::block(move || {
|
let (icon, banner) = actix_web::web::block(move || {
|
||||||
let icon = if let Some(id) = icon_id {
|
let icon = if let Some(id) = icon_id {
|
||||||
store.store.files.by_id(id)?
|
store.store.files.by_id(id)?
|
||||||
|
@ -346,7 +378,7 @@ impl EditProfileState {
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(Self::new(profile, icon, banner))
|
Ok(Self::new(profile, icon, banner, settings))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn profile<'a>(&'a self) -> ProfileView<'a> {
|
pub(crate) fn profile<'a>(&'a self) -> ProfileView<'a> {
|
||||||
|
@ -497,4 +529,9 @@ impl EditProfileState {
|
||||||
self.login_required_error = Some(err.to_owned());
|
self.login_required_error = Some(err.to_owned());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn settings_error(&mut self, err: String) -> &mut Self {
|
||||||
|
self.settings_error = Some(err);
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::{
|
||||||
error::{Error, OptionExt},
|
error::{Error, OptionExt},
|
||||||
middleware::{ProfileData, UserProfile},
|
middleware::{ProfileData, UserProfile},
|
||||||
nav::NavState,
|
nav::NavState,
|
||||||
profiles::{state::EditProfileState, to_current_profile},
|
profiles::{settings::Settings, state::EditProfileState, to_current_profile},
|
||||||
ActixLoader, State,
|
ActixLoader, State,
|
||||||
};
|
};
|
||||||
use actix_session::Session;
|
use actix_session::Session;
|
||||||
|
@ -66,6 +66,11 @@ pub(super) fn update_scope() -> Scope {
|
||||||
.route(web::post().to(update_require_login))
|
.route(web::post().to(update_require_login))
|
||||||
.route(web::get().to(to_current_profile)),
|
.route(web::get().to(to_current_profile)),
|
||||||
)
|
)
|
||||||
|
.service(
|
||||||
|
web::resource("/settings")
|
||||||
|
.route(web::post().to(update_settings))
|
||||||
|
.route(web::get().to(to_current_profile)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn to_create() -> HttpResponse {
|
pub(super) fn to_create() -> HttpResponse {
|
||||||
|
@ -328,6 +333,39 @@ async fn update_require_login(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct SettingsForm {
|
||||||
|
sensitive: Option<String>,
|
||||||
|
dark: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_settings(
|
||||||
|
loader: ActixLoader,
|
||||||
|
form: web::Form<SettingsForm>,
|
||||||
|
profile: UserProfile,
|
||||||
|
nav_state: NavState,
|
||||||
|
state: web::Data<State>,
|
||||||
|
) -> Result<HttpResponse, Error> {
|
||||||
|
let profile = profile.0;
|
||||||
|
|
||||||
|
let settings = Settings {
|
||||||
|
sensitive: form.sensitive.is_some(),
|
||||||
|
dark: form.dark.is_some(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let error = match state.settings.update(profile.id(), settings).await {
|
||||||
|
Ok(_) => return Ok(to_current_profile()),
|
||||||
|
Err(e) => e.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut profile_state = EditProfileState::for_profile(profile, &state).await?;
|
||||||
|
profile_state.settings_error(error);
|
||||||
|
|
||||||
|
crate::rendered(HttpResponse::InternalServerError(), |cursor| {
|
||||||
|
crate::templates::profiles::current(cursor, &loader, &profile_state, &nav_state)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize)]
|
#[derive(Clone, Debug, serde::Deserialize)]
|
||||||
pub struct HandleForm {
|
pub struct HandleForm {
|
||||||
pub(crate) handle: String,
|
pub(crate) handle: String,
|
||||||
|
|
|
@ -5,6 +5,7 @@ use crate::{
|
||||||
images::{largest_image, FullImage, ImageType},
|
images::{largest_image, FullImage, ImageType},
|
||||||
middleware::{CurrentSubmission, UserProfile},
|
middleware::{CurrentSubmission, UserProfile},
|
||||||
nav::NavState,
|
nav::NavState,
|
||||||
|
profiles::Settings,
|
||||||
views::{OwnedProfileView, OwnedSubmissionView},
|
views::{OwnedProfileView, OwnedSubmissionView},
|
||||||
ActixLoader, State,
|
ActixLoader, State,
|
||||||
};
|
};
|
||||||
|
@ -195,7 +196,16 @@ async fn report_page(
|
||||||
|
|
||||||
let view = ReportView::build(submission_id.into_inner(), &state).await?;
|
let view = ReportView::build(submission_id.into_inner(), &state).await?;
|
||||||
|
|
||||||
if let Some(res) = can_view(Some(profile.id()), &view.author, &view.submission, &state).await? {
|
let can_view_sensitive = state.settings.for_profile(profile.id()).await?.sensitive;
|
||||||
|
|
||||||
|
if let Some(res) = can_view(
|
||||||
|
Some(profile.id()),
|
||||||
|
can_view_sensitive,
|
||||||
|
&view.submission,
|
||||||
|
&state,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
return Ok(res);
|
return Ok(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,7 +233,16 @@ async fn report(
|
||||||
|
|
||||||
let mut view = ReportView::build(submission_id.into_inner(), &state).await?;
|
let mut view = ReportView::build(submission_id.into_inner(), &state).await?;
|
||||||
|
|
||||||
if let Some(res) = can_view(Some(profile.id()), &view.author, &view.submission, &state).await? {
|
let can_view_sensitive = state.settings.for_profile(profile.id()).await?.sensitive;
|
||||||
|
|
||||||
|
if let Some(res) = can_view(
|
||||||
|
Some(profile.id()),
|
||||||
|
can_view_sensitive,
|
||||||
|
&view.submission,
|
||||||
|
&state,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
return Ok(res);
|
return Ok(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,7 +286,16 @@ async fn report_success_page(
|
||||||
|
|
||||||
let view = ReportView::build(submission_id.into_inner(), &state).await?;
|
let view = ReportView::build(submission_id.into_inner(), &state).await?;
|
||||||
|
|
||||||
if let Some(res) = can_view(Some(profile.id()), &view.author, &view.submission, &state).await? {
|
let can_view_sensitive = state.settings.for_profile(profile.id()).await?.sensitive;
|
||||||
|
|
||||||
|
if let Some(res) = can_view(
|
||||||
|
Some(profile.id()),
|
||||||
|
can_view_sensitive,
|
||||||
|
&view.submission,
|
||||||
|
&state,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
return Ok(res);
|
return Ok(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,12 +321,15 @@ pub struct SubmissionState {
|
||||||
pub(crate) delete_error: Option<String>,
|
pub(crate) delete_error: Option<String>,
|
||||||
pub(crate) visibility_error: Option<String>,
|
pub(crate) visibility_error: Option<String>,
|
||||||
pub(crate) sensitive_error: Option<String>,
|
pub(crate) sensitive_error: Option<String>,
|
||||||
|
pub(crate) settings: Settings,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SubmissionState {
|
impl SubmissionState {
|
||||||
async fn new(submission: Submission, state: &State) -> Result<Self, Error> {
|
async fn new(submission: Submission, state: &State) -> Result<Self, Error> {
|
||||||
let file_ids: Vec<Uuid> = submission.files().iter().copied().collect();
|
let file_ids: Vec<Uuid> = submission.files().iter().copied().collect();
|
||||||
|
|
||||||
|
let settings = state.settings.for_profile(submission.profile_id()).await?;
|
||||||
|
|
||||||
let store = state.profiles.clone();
|
let store = state.profiles.clone();
|
||||||
let files = web::block(move || {
|
let files = web::block(move || {
|
||||||
let mut files = HashMap::new();
|
let mut files = HashMap::new();
|
||||||
|
@ -327,6 +358,7 @@ impl SubmissionState {
|
||||||
delete_error: None,
|
delete_error: None,
|
||||||
visibility_error: None,
|
visibility_error: None,
|
||||||
sensitive_error: None,
|
sensitive_error: None,
|
||||||
|
settings,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -613,52 +645,29 @@ impl ViewSubmissionState {
|
||||||
|
|
||||||
async fn can_view(
|
async fn can_view(
|
||||||
viewer: Option<Uuid>,
|
viewer: Option<Uuid>,
|
||||||
poster: &Profile,
|
can_view_sensitive: bool,
|
||||||
submission: &Submission,
|
submission: &Submission,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<Option<HttpResponse>, Error> {
|
) -> Result<Option<HttpResponse>, Error> {
|
||||||
if submission.is_sensitive() {
|
let store = state.profiles.clone();
|
||||||
if viewer.is_none() {
|
let submission = submission.clone();
|
||||||
return Ok(Some(crate::to_404()));
|
let opt = web::block(move || {
|
||||||
}
|
Ok(crate::pagination::submission::can_view(
|
||||||
}
|
viewer,
|
||||||
|
&submission,
|
||||||
|
&store.store,
|
||||||
|
&mut Default::default(),
|
||||||
|
true,
|
||||||
|
can_view_sensitive,
|
||||||
|
)) as Result<Option<()>, Error>
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
if poster.login_required() && viewer.is_none() {
|
if opt.is_none() {
|
||||||
return Ok(Some(crate::to_404()));
|
return Ok(Some(crate::to_404()));
|
||||||
}
|
|
||||||
|
|
||||||
if poster.local_owner().is_none() && viewer.is_none() {
|
|
||||||
return Ok(Some(crate::to_404()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let is_self = viewer
|
|
||||||
.as_ref()
|
|
||||||
.map(|pid| *pid == poster.id())
|
|
||||||
.unwrap_or(false);
|
|
||||||
|
|
||||||
if submission.published().is_none() && !is_self {
|
|
||||||
return Ok(Some(crate::to_404()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let is_follower_or_self = if let Some(pid) = viewer {
|
|
||||||
let is_follower = state
|
|
||||||
.profiles
|
|
||||||
.store
|
|
||||||
.view
|
|
||||||
.follows
|
|
||||||
.by_forward(poster.id(), pid)?
|
|
||||||
.is_some();
|
|
||||||
|
|
||||||
is_follower || is_self
|
|
||||||
} else {
|
} else {
|
||||||
false
|
Ok(None)
|
||||||
};
|
|
||||||
|
|
||||||
if submission.is_followers_only() && !is_follower_or_self {
|
|
||||||
return Ok(Some(crate::to_404()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(None)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn files_for_submission(
|
async fn files_for_submission(
|
||||||
|
@ -746,6 +755,7 @@ fn submission_nav(
|
||||||
|
|
||||||
async fn adjacent_submissions(
|
async fn adjacent_submissions(
|
||||||
viewer: Option<Uuid>,
|
viewer: Option<Uuid>,
|
||||||
|
can_view_sensitive: bool,
|
||||||
submission: &Submission,
|
submission: &Submission,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> Result<(Option<Uuid>, Option<Uuid>), Error> {
|
) -> Result<(Option<Uuid>, Option<Uuid>), Error> {
|
||||||
|
@ -769,6 +779,7 @@ async fn adjacent_submissions(
|
||||||
&inner_store.store,
|
&inner_store.store,
|
||||||
&mut Default::default(),
|
&mut Default::default(),
|
||||||
true,
|
true,
|
||||||
|
can_view_sensitive,
|
||||||
)
|
)
|
||||||
.map(move |_| id)
|
.map(move |_| id)
|
||||||
});
|
});
|
||||||
|
@ -787,6 +798,7 @@ async fn adjacent_submissions(
|
||||||
&store.store,
|
&store.store,
|
||||||
&mut Default::default(),
|
&mut Default::default(),
|
||||||
true,
|
true,
|
||||||
|
can_view_sensitive,
|
||||||
)
|
)
|
||||||
.map(move |_| id)
|
.map(move |_| id)
|
||||||
});
|
});
|
||||||
|
@ -808,6 +820,7 @@ async fn adjacent_submissions(
|
||||||
&inner_store.store,
|
&inner_store.store,
|
||||||
&mut Default::default(),
|
&mut Default::default(),
|
||||||
true,
|
true,
|
||||||
|
can_view_sensitive,
|
||||||
)
|
)
|
||||||
.map(move |_| id)
|
.map(move |_| id)
|
||||||
});
|
});
|
||||||
|
@ -826,6 +839,7 @@ async fn adjacent_submissions(
|
||||||
&store.store,
|
&store.store,
|
||||||
&mut Default::default(),
|
&mut Default::default(),
|
||||||
true,
|
true,
|
||||||
|
can_view_sensitive,
|
||||||
)
|
)
|
||||||
.map(move |_| id)
|
.map(move |_| id)
|
||||||
});
|
});
|
||||||
|
@ -862,12 +876,18 @@ async fn submission_page(
|
||||||
let cache = files_for_submission(&submission, cache, &state).await?;
|
let cache = files_for_submission(&submission, cache, &state).await?;
|
||||||
let cache = files_for_profile(&poster, cache, &state).await?;
|
let cache = files_for_profile(&poster, cache, &state).await?;
|
||||||
|
|
||||||
if let Some(res) = can_view(viewer, &poster, &submission, &state).await? {
|
let can_view_sensitive = if let Some(viewer) = viewer {
|
||||||
|
state.settings.for_profile(viewer).await?.sensitive
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(res) = can_view(viewer, can_view_sensitive, &submission, &state).await? {
|
||||||
return Ok(res);
|
return Ok(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (next_submission, previous_submission) =
|
let (next_submission, previous_submission) =
|
||||||
adjacent_submissions(viewer, &submission, &state).await?;
|
adjacent_submissions(viewer, can_view_sensitive, &submission, &state).await?;
|
||||||
|
|
||||||
let (nav, current_file) = submission_nav(
|
let (nav, current_file) = submission_nav(
|
||||||
page.map(|p| p.into_inner()),
|
page.map(|p| p.into_inner()),
|
||||||
|
@ -969,7 +989,10 @@ async fn create_comment(
|
||||||
let poster_id = submission.profile_id();
|
let poster_id = submission.profile_id();
|
||||||
let poster = web::block(move || store.store.profiles.by_id(poster_id)?.req()).await?;
|
let poster = web::block(move || store.store.profiles.by_id(poster_id)?.req()).await?;
|
||||||
|
|
||||||
if let Some(res) = can_view(Some(profile.id()), &poster, &submission, &state).await? {
|
let can_view_sensitive = state.settings.for_profile(profile.id()).await?.sensitive;
|
||||||
|
|
||||||
|
if let Some(res) = can_view(Some(profile.id()), can_view_sensitive, &submission, &state).await?
|
||||||
|
{
|
||||||
return Ok(res);
|
return Ok(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -999,7 +1022,7 @@ async fn create_comment(
|
||||||
let cache = files_for_profile(&poster, cache, &state).await?;
|
let cache = files_for_profile(&poster, cache, &state).await?;
|
||||||
|
|
||||||
let (next_submission, previous_submission) =
|
let (next_submission, previous_submission) =
|
||||||
adjacent_submissions(Some(profile.id()), &submission, &state).await?;
|
adjacent_submissions(Some(profile.id()), can_view_sensitive, &submission, &state).await?;
|
||||||
|
|
||||||
let (nav, current_file) = submission_nav(
|
let (nav, current_file) = submission_nav(
|
||||||
page.map(|p| p.into_inner()),
|
page.map(|p| p.into_inner()),
|
||||||
|
|
|
@ -30,6 +30,27 @@
|
||||||
@:button_group(&state.buttons(loader))
|
@:button_group(&state.buttons(loader))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@:card(&Card::full_width().dark(nav_state.dark()), {
|
||||||
|
<form method="POST" action="/profiles/update/settings">
|
||||||
|
@:card_title({
|
||||||
|
@fl!(loader, "update-settings-heading")
|
||||||
|
})
|
||||||
|
@if let Some(error) = &state.settings_error {
|
||||||
|
@:card_body({
|
||||||
|
@error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@:card_body({
|
||||||
|
@:checkbox("sensitive", &fl!(loader, "sensitive-checkbox"), state.settings.sensitive)
|
||||||
|
@:checkbox("dark", &fl!(loader, "dark-checkbox"), state.settings.dark)
|
||||||
|
})
|
||||||
|
@:card_body({
|
||||||
|
@:button_group(&[
|
||||||
|
Button::primary(&fl!(loader, "update-settings-button")),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
</form>
|
||||||
|
})
|
||||||
@:card(&Card::full_width().dark(nav_state.dark()), {
|
@:card(&Card::full_width().dark(nav_state.dark()), {
|
||||||
@:card_title({
|
@:card_title({
|
||||||
@fl!(loader, "update-profile-heading")
|
@fl!(loader, "update-profile-heading")
|
||||||
|
|
|
@ -70,21 +70,23 @@
|
||||||
</form>
|
</form>
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@:card(&Card::full_width().dark(nav_state.dark()), {
|
@if state.settings.sensitive {
|
||||||
<form method="POST" action="@state.sensitive_path()">
|
@:card(&Card::full_width().dark(nav_state.dark()), {
|
||||||
@:card_title({
|
<form method="POST" action="@state.sensitive_path()">
|
||||||
@fl!(loader, "submission-sensitive-heading")
|
@:card_title({
|
||||||
})
|
@fl!(loader, "submission-sensitive-heading")
|
||||||
@:card_body({
|
})
|
||||||
@:checkbox("sensitive", &fl!(loader, "submission-sensitive-checkbox"), state.submission.is_sensitive())
|
@:card_body({
|
||||||
})
|
@:checkbox("sensitive", &fl!(loader, "submission-sensitive-checkbox"), state.submission.is_sensitive())
|
||||||
@:card_body({
|
})
|
||||||
@:button_group(&[
|
@:card_body({
|
||||||
Button::primary(&fl!(loader, "submission-sensitive-button")),
|
@:button_group(&[
|
||||||
])
|
Button::primary(&fl!(loader, "submission-sensitive-button")),
|
||||||
})
|
])
|
||||||
</form>
|
})
|
||||||
})
|
</form>
|
||||||
|
})
|
||||||
|
}
|
||||||
@:card(&Card::full_width().dark(nav_state.dark()), {
|
@:card(&Card::full_width().dark(nav_state.dark()), {
|
||||||
<form method="POST" action="@state.update_path()">
|
<form method="POST" action="@state.update_path()">
|
||||||
@:card_title({
|
@:card_title({
|
||||||
|
|
|
@ -138,6 +138,12 @@ file-num = file {$num}
|
||||||
|
|
||||||
profile-settings-title = Profile Settings
|
profile-settings-title = Profile Settings
|
||||||
profile-settings-subtitle = Edit {$profileName}'s profile on {site-name}
|
profile-settings-subtitle = Edit {$profileName}'s profile on {site-name}
|
||||||
|
|
||||||
|
update-settings-heading = Update Profile Settings
|
||||||
|
sensitive-checkbox = Show me NSFW content
|
||||||
|
dark-checkbox = Enable dark mode
|
||||||
|
update-settings-button = Save
|
||||||
|
|
||||||
update-profile-heading = Update Profile
|
update-profile-heading = Update Profile
|
||||||
update-bio-heading = Update Bio
|
update-bio-heading = Update Bio
|
||||||
update-bio-description =
|
update-bio-description =
|
||||||
|
|
Loading…
Reference in a new issue