1b3e8cb250
* Add a user privacy setting so they can gate who is allowed to share private photos with them (for people who dislike unsolicited shares): * Anybody (default) * Friends only * Friends + people whom they have sent a DM to (on the main website) * Nobody * Add gating around whether to display the prompt to unlock your private photos while you are viewing somebody's gallery: * The current user needs at least one private photo to share. * The target user's new privacy preference is taken into consideration. * The "should show private photo share prompt" logic is also used on the actual share page, e.g. for people who manually paste in a username to share with. You can not grant access to private photos which don't exist. * Improve the UI on the private photo shares page. * Profile cards to add elements from the Member Directory page, such as a Friends and Liked indicator. * A count of the user's Private photos is shown, which links directly to their private gallery. * Add "Decline" buttons to the Shared With Me page: so the target of a private photo share is able to remove/cancel shares with them.
1898 lines
94 KiB
HTML
1898 lines
94 KiB
HTML
{{define "title"}}User Settings{{end}}
|
|
{{define "content"}}
|
|
<div class="container">
|
|
<section class="hero is-link is-bold">
|
|
<div class="hero-body">
|
|
<div class="container">
|
|
<h1 class="title">User Settings</h1>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{{ $User := .CurrentUser }}
|
|
|
|
<div class="columns mt-4">
|
|
<div class="column is-one-quarter">
|
|
<div class="card">
|
|
<div class="card-header has-background-info">
|
|
<p class="card-header-title has-text-light">
|
|
<i class="fa fa-gear mr-2"></i>
|
|
Settings Menu
|
|
</p>
|
|
</div>
|
|
|
|
<div class="card-content p-4">
|
|
<aside class="menu">
|
|
<p class="menu-label">
|
|
Settings
|
|
</p>
|
|
|
|
<ul class="menu-list">
|
|
<li>
|
|
<a href="/settings#profile" class="nonshy-tab-button">
|
|
<strong><i class="fa fa-address-card mr-1"></i> My Profile</strong>
|
|
<p class="help">Manage your profile page.</p>
|
|
</a>
|
|
</li>
|
|
|
|
<li>
|
|
<a href="/settings#look" class="nonshy-tab-button">
|
|
<strong><i class="fa fa-palette mr-1"></i> Look & Feel</strong>
|
|
<p class="help">
|
|
Customize your profile with a color scheme.
|
|
</p>
|
|
</a>
|
|
</li>
|
|
|
|
<li>
|
|
<a href="/settings#prefs" class="nonshy-tab-button">
|
|
<strong><i class="fa fa-square-check mr-1"></i> Website Preferences</strong>
|
|
<p class="help">
|
|
Explicit content filter <i class="fa fa-fire"></i>; photo blur.
|
|
</p>
|
|
</a>
|
|
</li>
|
|
|
|
<li>
|
|
<a href="/settings#location" class="nonshy-tab-button">
|
|
<strong><i class="fa fa-globe mr-1"></i> Location Settings</strong>
|
|
<p class="help">
|
|
For the "Who's Nearby?" feature.
|
|
</p>
|
|
</a>
|
|
</li>
|
|
|
|
<li>
|
|
<a href="/settings#privacy" class="nonshy-tab-button">
|
|
<strong><i class="fa fa-eye mr-1"></i> Privacy</strong>
|
|
<p class="help">
|
|
Profile visibility; who can slide into your DMs.
|
|
</p>
|
|
</a>
|
|
</li>
|
|
|
|
<li>
|
|
<a href="/settings#notifications" class="nonshy-tab-button">
|
|
<strong><i class="fa fa-bell mr-1"></i> Notifications</strong>
|
|
<p class="help">
|
|
Control your (on-site) notification preferences.
|
|
</p>
|
|
</a>
|
|
</li>
|
|
|
|
<li>
|
|
<a href="/settings#account" class="nonshy-tab-button">
|
|
<strong><i class="fa fa-user mr-1"></i> Account Settings</strong>
|
|
<p class="help">
|
|
Change password or e-mail; set up Two-Factor Authentication (2FA).
|
|
</p>
|
|
</a>
|
|
</li>
|
|
|
|
<li>
|
|
<a href="/settings#deactivate" class="nonshy-tab-button">
|
|
<strong><i class="fa fa-exclamation-triangle mr-1"></i> Deactivate Account</strong>
|
|
<p class="help">
|
|
Temporarily deactivate or permanently delete my account.
|
|
</p>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
|
|
<p class="menu-label">
|
|
See Also
|
|
</p>
|
|
|
|
<ul class="menu-list">
|
|
<li>
|
|
<a href="/photo/certification">
|
|
<strong><i class="fa fa-certificate mr-1"></i> Certification Photo</strong>
|
|
<p class="help">
|
|
View and manage your Certification status.
|
|
</p>
|
|
</a>
|
|
</li>
|
|
|
|
<li>
|
|
<a href="/photo/private">
|
|
<strong><i class="fa fa-eye mr-1"></i> Private Photos</strong>
|
|
<p class="help">
|
|
Manage who can see your private photos.
|
|
</p>
|
|
</a>
|
|
</li>
|
|
|
|
<li>
|
|
<a href="/users/blocked">
|
|
<strong><i class="fa fa-hand mr-1"></i> Blocked Users</strong>
|
|
<p class="help">
|
|
View and manage your block list.
|
|
</p>
|
|
</a>
|
|
</li>
|
|
|
|
<li>
|
|
<a href="/notes/me">
|
|
<strong><i class="fa fa-pen-to-square mr-1"></i> My User Notes</strong>
|
|
<p class="help">
|
|
Browse and search private notes you have written about others.
|
|
</p>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</aside>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="column">
|
|
|
|
<!-- Profile -->
|
|
<div class="card" id="profile">
|
|
<header class="card-header has-background-link">
|
|
<p class="card-header-title has-text-light">
|
|
<i class="fa fa-address-card pr-2"></i>
|
|
My Profile
|
|
</p>
|
|
</header>
|
|
|
|
<form method="POST" action="/settings">
|
|
<input type="hidden" name="intent" value="profile">
|
|
{{InputCSRF}}
|
|
|
|
<div class="card-content">
|
|
<p class="block">
|
|
The fields here are shown on your <a href="/u/{{.CurrentUser.Username}}">profile page</a>
|
|
and are all optional. Fields with a <i class="fa fa-lock"></i> icon are not shown on
|
|
your page but may drive some data that is (e.g., your current age derived from your birthdate).
|
|
</p>
|
|
|
|
<div class="columns">
|
|
<div class="column field is-half">
|
|
<label class="label" for="display_name">Display Name</label>
|
|
<input type="text" class="input"
|
|
id="display_name"
|
|
name="display_name"
|
|
placeholder="John Doe"
|
|
value="{{or $User.Name ""}}">
|
|
</div>
|
|
|
|
<div class="column field is-half">
|
|
<label class="label" for="dob">Birthdate <i class="fa fa-lock"></i></label>
|
|
<input type="date" class="input{{if not $User.Birthdate.IsZero}} cursor-not-allowed{{end}}"
|
|
id="dob"
|
|
name="dob"
|
|
value="{{if not $User.Birthdate.IsZero}}{{$User.Birthdate.Format "2006-01-02"}}{{end}}"
|
|
required>
|
|
<p class="help">
|
|
Used to show your age on your profile.
|
|
{{if not $User.Birthdate.IsZero}}
|
|
If you entered a wrong birthdate, you can change it here. Note that all birthdate
|
|
changes will notify the admin, so don't mess around or behave dishonestly.
|
|
{{end}}
|
|
</p>
|
|
|
|
<!-- Don't show age checkbox -->
|
|
<label class="checkbox">
|
|
<input type="checkbox"
|
|
name="hide_age"
|
|
value="true"
|
|
{{if eq ($User.GetProfileField "hide_age") "true"}}checked{{end}}>
|
|
Don't show my age on my profile
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="columns">
|
|
<div class="column field is-half">
|
|
<label class="label" for="gender">Gender</label>
|
|
<div class="select is-fullwidth">
|
|
<select id="gender" name="gender">
|
|
<option value="">No answer</option>
|
|
{{range .Enum.Gender}}
|
|
<option value="{{.}}"{{if eq ($User.GetProfileField "gender") .}} selected{{end}}>{{.}}</option>
|
|
{{end}}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="column field is-half">
|
|
<label class="label" for="pronouns">Pronouns</label>
|
|
<input type="text" class="input"
|
|
id="pronouns"
|
|
name="pronouns"
|
|
maxlength="30"
|
|
value="{{$User.GetProfileField "pronouns"}}">
|
|
<p class="help">e.g.
|
|
<a href="#" onclick="document.querySelector('#pronouns').value='he/him'; return false">he/him</a>;
|
|
<a href="#" onclick="document.querySelector('#pronouns').value='she/her'; return false">she/her</a>;
|
|
<a href="#" onclick="document.querySelector('#pronouns').value='they/them'; return false">they/them</a>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="columns">
|
|
<div class="column field is-half">
|
|
<label class="label" for="city">City/Location</label>
|
|
<input type="text" class="input"
|
|
id="city"
|
|
name="city"
|
|
value="{{$User.GetProfileField "city"}}">
|
|
</div>
|
|
|
|
|
|
<div class="column field is-half">
|
|
<label class="label" for="job">Job/Occupation</label>
|
|
<input type="text" class="input"
|
|
id="job"
|
|
name="job"
|
|
value="{{$User.GetProfileField "job"}}">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="columns">
|
|
<div class="column field is-half">
|
|
<label class="label" for="marital_status">Marital Status</label>
|
|
<div class="select is-fullwidth">
|
|
<select id="marital_status" name="marital_status">
|
|
<option value="">No answer</option>
|
|
{{range .Enum.MaritalStatus}}
|
|
<option value="{{.}}"{{if eq ($User.GetProfileField "marital_status") .}} selected{{end}}>{{.}}</option>
|
|
{{end}}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="column field is-half">
|
|
<label class="label" for="relationship_type">Relationship Type</label>
|
|
<div class="select is-fullwidth">
|
|
<select id="relationship_type" name="relationship_type">
|
|
<option value="">No answer</option>
|
|
{{range .Enum.RelationshipType}}
|
|
<option value="{{.}}"{{if eq ($User.GetProfileField "relationship_type") .}} selected{{end}}>{{.}}</option>
|
|
{{end}}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="columns">
|
|
<div class="column field is-half">
|
|
<label class="label" for="orientation">Orientation</label>
|
|
<div class="select is-fullwidth">
|
|
<select id="orientation" name="orientation">
|
|
<option value="">No answer</option>
|
|
{{range .Enum.Orientation}}
|
|
<option value="{{.}}"{{if eq ($User.GetProfileField "orientation") .}} selected{{end}}>{{.}}</option>
|
|
{{end}}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label class="label">Here For</label>
|
|
<div class="columns is-multiline pb-5">
|
|
{{range .Enum.HereFor}}
|
|
<div class="column is-one-third pb-0">
|
|
<label class="checkbox">
|
|
<input type="checkbox"
|
|
name="here_for"
|
|
value="{{.}}"
|
|
{{if $User.ProfileFieldIn "here_for" .}}checked{{end}}>
|
|
{{.}}
|
|
</label>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field" id="profile/about_me">
|
|
<label class="label" for="about_me">About Me</label>
|
|
<textarea class="textarea" cols="60" rows="4"
|
|
id="about_me"
|
|
name="about_me"
|
|
placeholder="A little blurb about myself">{{$User.GetProfileField "about_me"}}</textarea>
|
|
<p class="help">
|
|
Write a bit about yourself. <a href="/markdown" target="_blank">Markdown formatting</a> is supported here.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="field" id="profile/interests">
|
|
<label class="label" for="interests">My Interests</label>
|
|
<textarea class="textarea" cols="60" rows="4"
|
|
id="interests"
|
|
name="interests"
|
|
placeholder="What kinds of things make you curious?">{{$User.GetProfileField "interests"}}</textarea>
|
|
</div>
|
|
|
|
<div class="field" id="profile/music_movies">
|
|
<label class="label" for="music_movies">Music/Bands/Movies</label>
|
|
<textarea class="textarea" cols="60" rows="4"
|
|
id="music_movies"
|
|
name="music_movies"
|
|
placeholder="What is your style of music or movie?">{{$User.GetProfileField "music_movies"}}</textarea>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<button type="submit" class="button is-primary">
|
|
<i class="fa fa-save mr-2"></i> Save Profile Settings
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Look & Feel -->
|
|
<div class="card mb-5" id="look">
|
|
<header class="card-header has-background-link">
|
|
<p class="card-header-title has-text-light">
|
|
<i class="fa fa-palette pr-2"></i>
|
|
Look & Feel
|
|
</p>
|
|
</header>
|
|
|
|
<div class="card-content">
|
|
<form method="POST" action="/settings">
|
|
<input type="hidden" name="intent" value="look">
|
|
{{InputCSRF}}
|
|
|
|
<p class="block">
|
|
On this screen, you may customize the visual appearance of your profile
|
|
page. You may set a custom color gradient for your header bar and personalize
|
|
the colors of your profile cards.
|
|
</p>
|
|
|
|
<!--
|
|
-- Website Theme Section
|
|
-->
|
|
<h2 class="subtitle">Website Theme <span class="tag is-success">New!</span></h2>
|
|
|
|
<div class="field">
|
|
<label class="checkbox">
|
|
<input type="radio"
|
|
name="website-theme"
|
|
value=""
|
|
{{if eq (.CurrentUser.GetProfileField "website-theme") ""}}checked{{end}}>
|
|
Automatically match my device's theme
|
|
</label>
|
|
</div>
|
|
<div class="field">
|
|
<label class="checkbox">
|
|
<input type="radio"
|
|
name="website-theme"
|
|
value="light"
|
|
{{if eq (.CurrentUser.GetProfileField "website-theme") "light"}}checked{{end}}>
|
|
Always use the light theme
|
|
</label>
|
|
</div>
|
|
<div class="field">
|
|
<label class="checkbox">
|
|
<input type="radio"
|
|
name="website-theme"
|
|
value="dark"
|
|
{{if eq (.CurrentUser.GetProfileField "website-theme") "dark"}}checked{{end}}>
|
|
Always use the dark theme
|
|
</label>
|
|
<p class="help">
|
|
This setting controls how the website theme will appear to you while logged in.
|
|
By default, the dark theme is used if your device prefers dark, but you can
|
|
manually override it to the Light or Dark themes above.
|
|
Remember to click "Save Look & Feel" below to see the change!
|
|
</p>
|
|
</div>
|
|
|
|
<hr>
|
|
|
|
<!--
|
|
-- Profile Header Section
|
|
-->
|
|
<h2 class="subtitle">Profile Header</h2>
|
|
|
|
<div class="field">
|
|
<label class="label">Preview</label>
|
|
</div>
|
|
|
|
<!-- Header preview hero, should be similar to profile page. -->
|
|
<div class="hero is-link is-bold mb-4" id="header-hero-preview">
|
|
<div class="hero-body p-4">
|
|
<div class="container">
|
|
<div class="columns is-mobile is-gapless mt-1 mb-6">
|
|
<div class="column is-narrow has-text-centered">
|
|
<figure class="profile-photo is-inline-block" style="width: auto; height: auto">
|
|
{{template "avatar-48x48" .CurrentUser}}
|
|
</figure>
|
|
</div>
|
|
|
|
<div class="column mx-2">
|
|
<strong>{{.CurrentUser.NameOrUsername}}</strong>
|
|
</div>
|
|
|
|
{{if and .LoggedIn (not .IsPrivate)}}
|
|
<div class="column is-narrow">
|
|
<div class="box">
|
|
<div style="width: 16px;"></div>
|
|
</div>
|
|
</div>
|
|
{{end}}<!-- if .LoggedIn -->
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label class="label">Header Gradient</label>
|
|
<div class="columns is-mobile">
|
|
<div class="column is-narrow">
|
|
<input type="color" class="color"
|
|
id="hero-color-start"
|
|
name="hero-color-start"
|
|
{{if ($User.GetProfileField "hero-color-start")}}
|
|
value="{{$User.GetProfileField "hero-color-start"}}"
|
|
{{else}}
|
|
value="{{template "--prof-colorA"}}"
|
|
{{end}}
|
|
>
|
|
</div>
|
|
<div class="column is-narrow">
|
|
<input type="color" class="color"
|
|
id="hero-color-end"
|
|
name="hero-color-end"
|
|
{{if ($User.GetProfileField "hero-color-end")}}
|
|
value="{{$User.GetProfileField "hero-color-end"}}"
|
|
{{else}}
|
|
value="{{template "--prof-colorB"}}"
|
|
{{end}}
|
|
>
|
|
</div>
|
|
<div class="column">
|
|
<label class="checkbox">
|
|
<input type="checkbox"
|
|
id="hero-text-dark"
|
|
name="hero-text-dark"
|
|
value="true"
|
|
{{if eq (.CurrentUser.GetProfileField "hero-text-dark") "true"}}checked{{end}}
|
|
>
|
|
Dark text
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<hr>
|
|
|
|
<!--
|
|
-- Profile Page Section
|
|
-->
|
|
<h2 class="subtitle">Profile Page</h2>
|
|
|
|
<div class="card block" id="profile-card-preview">
|
|
<div class="card-header">
|
|
<p class="card-header-title">Card Colors</p>
|
|
</div>
|
|
<div class="card-body">
|
|
<table class="table is-fullwidth">
|
|
<tr>
|
|
<td>
|
|
<strong>Lightness</strong>
|
|
</td>
|
|
<td>
|
|
<div class="select is-fullwidth">
|
|
<select id="card-lightness" name="card-lightness">
|
|
<option value="">Automatic</option>
|
|
<option value="light"{{if eq ($User.GetProfileField "card-lightness") "light"}} selected{{end}}>Light theme (black on white)</option>
|
|
<option value="dark"{{if eq ($User.GetProfileField "card-lightness") "dark"}} selected{{end}}>Dark theme (white on black)</option>
|
|
</select>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="is-narrow">
|
|
<strong>Card Title BG</strong>
|
|
</td>
|
|
<td>
|
|
<input type="color"
|
|
id="card-title-bg"
|
|
name="card-title-bg"
|
|
{{if ($User.GetProfileField "card-title-bg")}}
|
|
value="{{$User.GetProfileField "card-title-bg"}}"
|
|
{{else}}
|
|
value="{{template "--prof-card-bg"}}"
|
|
{{end}}
|
|
>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>
|
|
<strong>Card Title FG</strong>
|
|
</td>
|
|
<td>
|
|
<input type="color"
|
|
id="card-title-fg"
|
|
name="card-title-fg"
|
|
{{if ($User.GetProfileField "card-title-fg")}}
|
|
value="{{$User.GetProfileField "card-title-fg"}}"
|
|
{{else}}
|
|
value="{{template "--prof-card-fg"}}"
|
|
{{end}}
|
|
>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>
|
|
<strong>Link Color</strong>
|
|
</td>
|
|
<td>
|
|
<input type="color"
|
|
id="card-link-color"
|
|
name="card-link-color"
|
|
{{if ($User.GetProfileField "card-link-color")}}
|
|
value="{{$User.GetProfileField "card-link-color"}}"
|
|
{{else}}
|
|
value="{{template "--prof-link-fg"}}"
|
|
{{end}}
|
|
>
|
|
<a href="#" onclick="return false" class="ml-2">
|
|
Example
|
|
<i class="fa fa-link"></i>
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label class="label">
|
|
Reset Styles
|
|
</label>
|
|
<label class="checkbox">
|
|
<input type="checkbox"
|
|
name="reset"
|
|
value="true">
|
|
Reset to default style
|
|
</label>
|
|
<p class="help">
|
|
If you'd like to reset all your page styles to their default, check
|
|
this box and click Save below.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<button type="submit" class="button is-primary">
|
|
<i class="fa fa-save mr-2"></i> Save Look & Feel
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Website Preferences -->
|
|
<div class="card mb-5" id="prefs">
|
|
<header class="card-header has-background-link">
|
|
<p class="card-header-title has-text-light">
|
|
<i class="fa fa-square-check pr-2"></i>
|
|
Website Preferences
|
|
</p>
|
|
</header>
|
|
|
|
<div class="card-content">
|
|
<form method="POST" action="/settings">
|
|
<input type="hidden" name="intent" value="preferences">
|
|
{{InputCSRF}}
|
|
|
|
<div class="field">
|
|
<label class="label">Explicit Content Filter</label>
|
|
<label class="checkbox">
|
|
<input type="checkbox"
|
|
name="explicit"
|
|
value="true"
|
|
{{if .CurrentUser.Explicit}}checked{{end}}>
|
|
Show explicit content <i class="fa fa-fire has-text-danger ml-1"></i>
|
|
</label>
|
|
<p class="help">
|
|
Check this box if you are OK seeing explicit content on this site, which may
|
|
include erections or sexually charged content. These may appear on the Site
|
|
Gallery as well as user profile pages.
|
|
</p>
|
|
|
|
<label class="checkbox">
|
|
<input type="checkbox"
|
|
name="blur_explicit"
|
|
value="true"
|
|
{{if eq (.CurrentUser.GetProfileField "blur_explicit") "true"}}checked{{end}}>
|
|
Blur explicit photos by default
|
|
</label>
|
|
<p class="help">
|
|
Explicit photos on Gallery pages will be blurred by default until clicked. Photos
|
|
on explicit Forum threads will also be blurred until clicked.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label class="label">Display Settings</label>
|
|
<label class="checkbox">
|
|
<input type="checkbox"
|
|
name="autoplay_gif"
|
|
value="true"
|
|
{{if not (eq (.CurrentUser.GetProfileField "autoplay_gif") "false")}}checked{{end}}>
|
|
Automatically play animated GIFs
|
|
</label>
|
|
<p class="help">
|
|
Uncheck this box to disable auto-play on GIFs.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<button type="submit" class="button is-primary">
|
|
<i class="fa fa-save mr-2"></i> Save Website Preferences
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Location Settings -->
|
|
<div id="location">
|
|
<form method="POST" action="/settings">
|
|
<input type="hidden" name="intent" value="location">
|
|
{{InputCSRF}}
|
|
|
|
<div class="card mb-5">
|
|
<header class="card-header has-background-link">
|
|
<p class="card-header-title has-text-light">
|
|
<i class="fa fa-globe mr-2"></i>
|
|
Location Settings
|
|
</p>
|
|
</header>
|
|
|
|
<div class="card-content">
|
|
<p class="block">
|
|
The settings on this page control your location for the <a href="/members?sort=distance"><strong>Who's Nearby</strong></a>
|
|
feature of {{PrettyTitle}}. Being discoverable by your location is an <strong>opt-in</strong>
|
|
feature and you have your choice of options how you want your location to be found.
|
|
</p>
|
|
|
|
<div class="notification is-info is-light py-2 px-3">
|
|
<i class="fa fa-exclamation-triangle"></i>
|
|
<strong>Notice:</strong>
|
|
Remember to click "Save" after setting your location preference!
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label class="label">How do you want your location to be determined?</label>
|
|
<label class="checkbox">
|
|
<input type="radio"
|
|
name="source"
|
|
value=""
|
|
id="location-option-none"
|
|
{{if eq .UserLocation.Source ""}}checked{{end}}>
|
|
<i class="fa fa-ban ml-2 mr-1 has-text-danger"></i>
|
|
Do not share my location with {{PrettyTitle}}.
|
|
</label>
|
|
<p class="help mb-4">
|
|
This option will opt-out of the Who's Nearby feature and your profile will not be
|
|
discoverable by distance to other members. Any location data already stored by
|
|
the website will be erased if you choose this option.
|
|
</p>
|
|
|
|
<label class="checkbox">
|
|
<input type="radio"
|
|
name="source"
|
|
value="geoip"
|
|
id="location-option-geoip"
|
|
{{if eq .UserLocation.Source "geoip"}}checked{{end}}>
|
|
<i class="fa fa-network-wired ml-2 mr-1 has-text-info"></i>
|
|
Automatically detect my location by my IP address
|
|
</label>
|
|
<p class="help mb-4">
|
|
Coarse location data based on your IP address. Might be accurate to your
|
|
city level.
|
|
|
|
<!-- Do we have GeoIP insights? -->
|
|
{{if not .GeoIPInsights.IsZero}}
|
|
<br>
|
|
<strong>Currently:</strong>
|
|
{{.GeoIPInsights}}
|
|
{{end}}
|
|
</p>
|
|
|
|
<label class="checkbox">
|
|
<input type="radio"
|
|
name="source"
|
|
value="gps"
|
|
id="location-option-gps"
|
|
{{if eq .UserLocation.Source "gps"}}checked{{end}}>
|
|
<i class="fa fa-location-dot ml-2 mr-1 has-text-success"></i>
|
|
Automatically detect my current location by GPS (with your permission)
|
|
</label>
|
|
<p class="help mb-4">
|
|
Your web browser will prompt you to share your current location with {{PrettyTitle}}.
|
|
<br>
|
|
<strong>Currently:</strong>
|
|
<span id="location-status-gps">You have not granted permission.</span>
|
|
<a href="#" class="fa fa-arrows-rotate" id="gps-refresh" title="Refresh my location"></a>
|
|
</p>
|
|
|
|
<label class="checkbox">
|
|
<input type="radio"
|
|
name="source"
|
|
value="pin"
|
|
id="location-option-pin"
|
|
{{if eq .UserLocation.Source "pin"}}checked{{end}}>
|
|
<i class="fa fa-map-pin ml-2 mr-1 has-text-info"></i>
|
|
Drop a pin on the map myself
|
|
</label>
|
|
<p class="help mb-4">
|
|
Use the map below and drop a pin anywhere you like to set your location.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label class="label">
|
|
Your Current Location (click "Save" when you are satisfied)
|
|
</label>
|
|
<p class="block">
|
|
Your location will be saved as the following in the database:
|
|
</p>
|
|
<div class="columns is-mobile" style="max-width: 500px">
|
|
<div class="column is-half pr-1">
|
|
<label class="label mb-0">Latitude:</label>
|
|
<input type="text" class="input is-fullwidth"
|
|
name="latitude"
|
|
id="saveLatitude"
|
|
value="{{.UserLocation.Latitude}}"
|
|
placeholder="None"
|
|
readonly>
|
|
</div>
|
|
<div class="column is-half pl-1">
|
|
<label class="label mb-0">Longitude:</label>
|
|
<input type="text" class="input is-fullwidth"
|
|
name="longitude"
|
|
id="saveLongitude"
|
|
value="{{.UserLocation.Longitude}}"
|
|
placeholder="None"
|
|
readonly>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<button type="submit" class="button is-success mr-2"
|
|
name="intent" value="location">
|
|
<i class="fa fa-save mr-2"></i>
|
|
Save My Location Settings
|
|
</button>
|
|
</div>
|
|
|
|
<h3 class="is-size-3">Map</h3>
|
|
|
|
<p class="block">
|
|
This map shows your current location pin. To click and drop a pin manually,
|
|
select the "Drop a pin on the map myself" option above. Otherwise, the map will
|
|
center on your GPS location (if available) or your IP address location, depending
|
|
on your selection above.
|
|
</p>
|
|
|
|
<div id="map" class="block" style="width: 100%; height: 450px"></div>
|
|
|
|
<p class="block">
|
|
Map tiles provided by <a href="https://www.openstreetmap.org" target="_blank">OpenStreetMap.</a>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Privacy Settings -->
|
|
<div class="card mb-5" id="privacy">
|
|
<header class="card-header has-background-success">
|
|
<p class="card-header-title has-text-dark-dark">
|
|
<i class="fa fa-square-check pr-2"></i>
|
|
Privacy Settings
|
|
</p>
|
|
</header>
|
|
|
|
<div class="card-content">
|
|
<form method="POST" action="/settings">
|
|
{{InputCSRF}}
|
|
<input type="hidden" name="intent" value="privacy">
|
|
|
|
<div class="field">
|
|
<label class="label">Profile Visibility</label>
|
|
<label class="checkbox">
|
|
<input type="radio"
|
|
name="visibility"
|
|
value="public"
|
|
{{if eq .CurrentUser.Visibility "public"}}checked{{end}}>
|
|
Public + Login Required
|
|
<i class="fa fa-eye ml-2 has-text-info"></i>
|
|
</label>
|
|
<p class="help">
|
|
The default is that users must be logged-in to even look at your profile
|
|
page. If your profile URL (/u/{{.CurrentUser.Username}}) is visited by a
|
|
logged-out browser, they are prompted to log in.
|
|
</p>
|
|
|
|
<label class="checkbox mt-3">
|
|
<input type="radio"
|
|
name="visibility"
|
|
value="external"
|
|
{{if eq .CurrentUser.Visibility "external"}}checked{{end}}>
|
|
Public + Limited Logged-out View
|
|
<i class="fa fa-eye ml-2 has-text-danger"></i>
|
|
</label>
|
|
<p class="help">
|
|
Your profile is fully visible to logged-in users, but if a logged-out browser
|
|
visits your page they will see a very minimal view: only your profile picture
|
|
and display name are shown.
|
|
<a href="/u/{{.CurrentUser.Username}}?view=external" target="_blank">Preview <i class="fa fa-external-link"></i></a>
|
|
</p>
|
|
|
|
<label class="checkbox mt-3">
|
|
<input type="radio"
|
|
name="visibility"
|
|
value="private"
|
|
{{if eq .CurrentUser.Visibility "private"}}checked{{end}}>
|
|
Mark my profile page as "private"
|
|
<i class="fa fa-lock ml-2 has-text-private"></i>
|
|
</label>
|
|
<p class="help">
|
|
If you check this box then only friends who you have approved are able to
|
|
see your profile page and gallery. Your gallery photos also will NOT appear
|
|
on the Site Gallery page. If your profile page is visited by a logged-out
|
|
viewer, they are prompted to log in.
|
|
</p>
|
|
</div>
|
|
|
|
<hr>
|
|
|
|
<div class="field">
|
|
<label class="label mb-0">Who can send me the first <i class="fa fa-envelope"></i> Message?</label>
|
|
|
|
<div class="has-text-info">
|
|
<small><em>
|
|
Note: this refers to Direct Messages on the main website
|
|
(not inside the chat room).
|
|
</em></small>
|
|
</div>
|
|
|
|
<label class="checkbox mt-3">
|
|
<input type="radio"
|
|
name="dm_privacy"
|
|
value=""
|
|
{{if eq (.CurrentUser.GetProfileField "dm_privacy") ""}}checked{{end}}>
|
|
Anybody on the site
|
|
</label>
|
|
<p class="help">
|
|
Almost any member of the site may send you a Direct Message from your profile
|
|
page (except for maybe <a href="/faq#shy-faqs" target="_blank">Shy Accounts</a>).
|
|
</p>
|
|
|
|
<label class="checkbox mt-3">
|
|
<input type="radio"
|
|
name="dm_privacy"
|
|
value="friends"
|
|
{{if eq (.CurrentUser.GetProfileField "dm_privacy") "friends"}}checked{{end}}>
|
|
Only people on my Friends list
|
|
<i class="fa fa-user-group has-text-warning ml-2"></i>
|
|
</label>
|
|
<p class="help">
|
|
Nobody can slide into your DMs except for friends (and admins if needed). Anybody
|
|
may <em>reply</em> to messages that you send to them.
|
|
</p>
|
|
|
|
<label class="checkbox mt-3">
|
|
<input type="radio"
|
|
name="dm_privacy"
|
|
value="nobody"
|
|
{{if eq (.CurrentUser.GetProfileField "dm_privacy") "nobody"}}checked{{end}}>
|
|
Nobody (close my DMs)
|
|
<i class="fa fa-hand has-text-danger ml-2"></i>
|
|
</label>
|
|
<p class="help">
|
|
Nobody can start a Direct Message conversation with you on the main website
|
|
(except an admin if necessary). Anybody may <em>reply</em> to messages that you
|
|
sent to them first.
|
|
</p>
|
|
</div>
|
|
|
|
<hr>
|
|
|
|
<div class="field">
|
|
<label class="label mb-0">
|
|
Who can share their
|
|
<span class="has-text-private">
|
|
<i class="fa fa-eye"></i> Private Photos
|
|
</span>
|
|
with me?
|
|
<span class="tag is-success">New!</span>
|
|
</label>
|
|
|
|
<p class="help">
|
|
This setting can help you to be in control of who else on {{PrettyTitle}} is allowed
|
|
to unlock their private photo gallery for you.
|
|
</p>
|
|
|
|
<label class="checkbox mt-3">
|
|
<input type="radio"
|
|
name="private_photo_gate"
|
|
value=""
|
|
{{if eq (.CurrentUser.GetProfileField "private_photo_gate") ""}}checked{{end}}>
|
|
Anybody on the site
|
|
</label>
|
|
<p class="help">
|
|
Any member of the website is able to share their private photo gallery with you.
|
|
</p>
|
|
|
|
<label class="checkbox mt-3">
|
|
<input type="radio"
|
|
name="private_photo_gate"
|
|
value="friends"
|
|
{{if eq (.CurrentUser.GetProfileField "private_photo_gate") "friends"}}checked{{end}}>
|
|
Only people on my Friends list
|
|
<i class="fa fa-user-group has-text-warning ml-2"></i>
|
|
</label>
|
|
<p class="help">
|
|
Only people who you have accepted as a friend will have the ability to share their private
|
|
photo gallery with you.
|
|
</p>
|
|
|
|
<label class="checkbox mt-3">
|
|
<input type="radio"
|
|
name="private_photo_gate"
|
|
value="messaged"
|
|
{{if eq (.CurrentUser.GetProfileField "private_photo_gate") "messaged"}}checked{{end}}>
|
|
Only my friends and people I have sent a DM to
|
|
<i class="fa fa-user-group has-text-warning ml-2"></i>
|
|
<i class="fa fa-envelope has-text-link"></i>
|
|
</label>
|
|
<p class="help">
|
|
People on your friend list and people who <strong>you</strong> have sent a Direct Message to
|
|
(on the main website - not the chat room) will be able to share their private photos with you.
|
|
Note: for example, if somebody sends <em>you</em> an unsolicited DM and you did not respond,
|
|
that person can not share their private photos with you.
|
|
</p>
|
|
|
|
<label class="checkbox mt-3">
|
|
<input type="radio"
|
|
name="private_photo_gate"
|
|
value="nobody"
|
|
{{if eq (.CurrentUser.GetProfileField "private_photo_gate") "nobody"}}checked{{end}}>
|
|
Nobody <i class="fa fa-hand has-text-danger ml-2"></i>
|
|
</label>
|
|
<p class="help">
|
|
Nobody on the website will be allowed to share their private gallery with you. Note: this
|
|
will mean that you will have no method to see private photos on the site except those which
|
|
had already been shared with you in the past.
|
|
</p>
|
|
</div>
|
|
|
|
<hr>
|
|
|
|
<div class="field">
|
|
<button type="submit" class="button is-primary">
|
|
<i class="fa fa-save mr-2"></i> Save Privacy Settings
|
|
</button>
|
|
</div>
|
|
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Notification Settings -->
|
|
<div id="notifications">
|
|
<div class="card mb-5">
|
|
<header class="card-header has-background-link">
|
|
<p class="card-header-title has-text-light">
|
|
<i class="fa fa-bell pr-2"></i>
|
|
Web Push Notifications <span class="tag is-success ml-2">New!</span>
|
|
</p>
|
|
</header>
|
|
|
|
<div class="card-content">
|
|
<p class="block">
|
|
You may opt-in to receive Web Push Notifications for some of your important updates
|
|
from {{PrettyTitle}}, such as when you receive a new Direct Message on the main website,
|
|
even when you have closed your browser.
|
|
</p>
|
|
|
|
<p class="block">
|
|
<strong>Push Notification Permission:</strong>
|
|
<strong class="has-text-success" id="push-status-enabled" style="display: none">
|
|
<i class="fa fa-check mr-1"></i> Granted
|
|
</strong>
|
|
<strong class="has-text-danger" id="push-status-disabled" style="display: none">
|
|
<i class="fa fa-xmark mr-1"></i> Denied
|
|
</strong>
|
|
<strong class="has-text-warning" id="push-status-default" style="display: none">
|
|
<i class="fa fa-xmark mr-1"></i> Not Granted
|
|
</strong>
|
|
</p>
|
|
|
|
<p class="block">
|
|
<!-- Button to Grant Permission or Test Notifications -->
|
|
<a href="#" id="grant-push-permission"
|
|
class="button is-small is-success">
|
|
Test Notifications
|
|
</a>
|
|
|
|
<!-- Help error if the permission is denied -->
|
|
<span id="push-denied-help" style="display: none">
|
|
<i class="fa fa-info-circle has-text-warning mr-1"></i>
|
|
You had denied notification permission to this site. Please check in your web browser's
|
|
settings (or click in your address bar to the left of the website URL) to reset your
|
|
permission setting. Please <a href="/faq#troubleshoot-web-push">see this page</a> for
|
|
help in case you want to resolve this.
|
|
</span>
|
|
</p>
|
|
|
|
<!-- Have existing subscriptions? -->
|
|
{{if .PushNotificationsCount}}
|
|
<p class="block">
|
|
<strong>Sessions:</strong> you have enabled push notifications on {{.PushNotificationsCount}} web browser{{Pluralize64 .PushNotificationsCount}}. You may
|
|
<a href="/v1/web-push/unregister">click here</a> to reset your subscriptions. Devices that you actively use
|
|
(and had granted permission on before) may re-subscribe on your next visit.
|
|
</p>
|
|
{{end}}
|
|
|
|
<!-- Specific Push Notification Opt-out Form -->
|
|
<form method="POST" action="/settings">
|
|
{{InputCSRF}}
|
|
<input type="hidden" name="intent" value="push_notifications">
|
|
|
|
<div class="field">
|
|
<label class="label">Send a Web Push Notification when...</label>
|
|
<label class="checkbox">
|
|
<input type="checkbox"
|
|
name="notif_optout_push_messages"
|
|
value="true"
|
|
{{if ne (.CurrentUser.GetProfileField "notif_optout_push_messages") "true"}}checked{{end}}>
|
|
I receive a Direct Message on the main website
|
|
</label>
|
|
</div>
|
|
<div class="field">
|
|
<label class="checkbox">
|
|
<input type="checkbox"
|
|
name="notif_optout_push_friends"
|
|
value="true"
|
|
{{if ne (.CurrentUser.GetProfileField "notif_optout_push_friends") "true"}}checked{{end}}>
|
|
I receive a new Friend Request
|
|
</label>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<button type="submit" class="button is-primary">
|
|
<i class="fa fa-save mr-2"></i> Save Push Notification Settings
|
|
</button>
|
|
</div>
|
|
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card mb-5">
|
|
<header class="card-header has-background-link">
|
|
<p class="card-header-title has-text-light">
|
|
<i class="fa fa-bell pr-2"></i>
|
|
Notification Settings
|
|
</p>
|
|
</header>
|
|
|
|
<div class="card-content">
|
|
<p class="block">
|
|
On this page you may opt-out of certain kinds of (on-site) notification messages.
|
|
{{PrettyTitle}} does not send you any e-mails or push notification -- these on-site
|
|
notifications only appear while you are visiting the website (on your
|
|
<a href="/me">home/user dashboard page</a>).
|
|
</p>
|
|
|
|
<form method="POST" action="/settings">
|
|
{{InputCSRF}}
|
|
<input type="hidden" name="intent" value="notifications">
|
|
|
|
<h2 class="subtitle">New Photo Uploads</h2>
|
|
|
|
<p class="block">
|
|
By default you will be notified when your friends upload a new picture to the site.
|
|
Below, you may opt-out of new photo upload notifications.
|
|
</p>
|
|
|
|
<div class="field">
|
|
<label class="label">Notify me when...</label>
|
|
<label class="checkbox">
|
|
<input type="checkbox"
|
|
name="notif_optout_friends_photos"
|
|
value="true"
|
|
{{if ne (.CurrentUser.GetProfileField "notif_optout_friends_photos") "true"}}checked{{end}}>
|
|
My friends upload a new photo
|
|
</label>
|
|
<p class="help">
|
|
If unchecked, the following two notifications will not be sent either.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label class="checkbox">
|
|
<input type="checkbox"
|
|
name="notif_optout_private_photos"
|
|
value="true"
|
|
{{if ne (.CurrentUser.GetProfileField "notif_optout_private_photos") "true"}}checked{{end}}>
|
|
A friend who shared their private photos with me uploads a new private photo
|
|
</label>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label class="checkbox">
|
|
<input type="checkbox"
|
|
name="notif_optout_explicit_photos"
|
|
value="true"
|
|
{{if ne (.CurrentUser.GetProfileField "notif_optout_explicit_photos") "true"}}checked{{end}}>
|
|
Allow notifications for 'explicit' photo uploads by my friends
|
|
</label>
|
|
<p class="help">
|
|
This will also depend on your <a href="/settings#prefs" target="_blank">opt-in to see explicit content</a> -- otherwise
|
|
notifications about explicit photo uploads will not be sent to you.
|
|
</p>
|
|
</div>
|
|
|
|
<h2 class="subtitle mt-5">Likes & Comments</h2>
|
|
|
|
<p class="block">
|
|
By default you will be notified when somebody 'likes' or comments on your profile page
|
|
or photos. You may turn off those notifications with the options below.
|
|
</p>
|
|
|
|
<div class="field">
|
|
<label class="label">Notify me when...</label>
|
|
<label class="checkbox">
|
|
<input type="checkbox"
|
|
name="notif_optout_likes"
|
|
value="true"
|
|
{{if ne (.CurrentUser.GetProfileField "notif_optout_likes") "true"}}checked{{end}}>
|
|
Somebody 'likes' my profile page, photos, or comments
|
|
</label>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label class="checkbox">
|
|
<input type="checkbox"
|
|
name="notif_optout_comments"
|
|
value="true"
|
|
{{if ne (.CurrentUser.GetProfileField "notif_optout_comments") "true"}}checked{{end}}>
|
|
Somebody leaves a comment on one of my photos
|
|
</label>
|
|
</div>
|
|
|
|
<h2 class="subtitle mt-5">Comment Thread Subscriptions</h2>
|
|
|
|
<p class="block">
|
|
Comment threads and forum posts may be 'subscribed' to so that you can be notified about
|
|
comments left by other people after you. By default, you will subscribe to comment threads
|
|
after you leave your first comment.
|
|
</p>
|
|
|
|
<p class="block">
|
|
<strong>Note:</strong> you may unsubscribe from comment threads by using the link at the
|
|
top of its page (for example: at the top of a forum thread page or the top of the list of
|
|
comments on a photo page). You may also opt <em>in</em> to get notifications on a thread
|
|
that you didn't comment on by using the same link at the top of their pages.
|
|
</p>
|
|
|
|
<p class="block">
|
|
The options below can control the automatic opt-in for subscriptions when you leave a
|
|
comment on a new comment thread.
|
|
</p>
|
|
|
|
<div class="field">
|
|
<label class="checkbox">
|
|
<input type="checkbox"
|
|
name="notif_optout_subscriptions"
|
|
value="true"
|
|
{{if ne (.CurrentUser.GetProfileField "notif_optout_subscriptions") "true"}}checked{{end}}>
|
|
Subscribe to notifications for future comments when I leave a comment on something
|
|
</label>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label class="label">Unsubscribe from Comment Threads</label>
|
|
<label class="checkbox">
|
|
<input type="checkbox"
|
|
name="unsubscribe_all_threads"
|
|
value="true">
|
|
Unsubscribe NOW from <strong>all ({{.SubscriptionCount}}) comment threads</strong> that I am currently following.
|
|
</label>
|
|
<p class="help">
|
|
You are currently subscribed to <strong>{{.SubscriptionCount}}</strong> comment thread{{Pluralize64 .SubscriptionCount}}.
|
|
You may immediately unsubscribe from all of these threads by checking this box and clicking "Save" below.
|
|
</p>
|
|
</div>
|
|
|
|
<h2 class="subtitle mt-5">Miscellaneous</h2>
|
|
|
|
<div class="field">
|
|
<label class="label">Notify me when...</label>
|
|
<label class="checkbox">
|
|
<input type="checkbox"
|
|
name="notif_optout_friend_request_accepted"
|
|
value="true"
|
|
{{if ne (.CurrentUser.GetProfileField "notif_optout_friend_request_accepted") "true"}}checked{{end}}>
|
|
Somebody approves my friendship request
|
|
</label>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label class="checkbox">
|
|
<input type="checkbox"
|
|
name="notif_optout_private_grant"
|
|
value="true"
|
|
{{if ne (.CurrentUser.GetProfileField "notif_optout_private_grant") "true"}}checked{{end}}>
|
|
Somebody unlocks their private photos for me to see
|
|
</label>
|
|
</div>
|
|
|
|
<!-- Read-only box for certification photo response -->
|
|
<div class="field">
|
|
<label class="checkbox">
|
|
<input type="checkbox"
|
|
value="true"
|
|
checked
|
|
disabled>
|
|
My certification photo is approved or rejected
|
|
</label>
|
|
<p class="help">
|
|
This notification is important for your account status, is rarely sent out, and can
|
|
not be opted-out from.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<button type="submit" class="button is-primary">
|
|
<i class="fa fa-save mr-2"></i> Save Settings
|
|
</button>
|
|
</div>
|
|
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Account Settings -->
|
|
<div id="account">
|
|
<div class="card mb-5">
|
|
<header class="card-header has-background-link">
|
|
<p class="card-header-title has-text-light">
|
|
<i class="fa fa-lock pr-2"></i>
|
|
Two-Factor Authentication
|
|
</p>
|
|
</header>
|
|
|
|
<div class="card-content content">
|
|
<p>
|
|
To help protect your {{PrettyTitle}} account, you may opt-in to add a
|
|
second factor to your login ("Two-Factor Authentication", or 2FA). This
|
|
means that in addition to needing "something you know" (your password) to
|
|
log in to your account, you can also require "something you have" (an
|
|
authenticator device which generates random time-dependent codes).
|
|
</p>
|
|
|
|
<p>
|
|
{{PrettyTitle}} offers Two-Factor Authentication using the industry
|
|
standard "Time-based One-Time Password" (TOTP) system that is compatible
|
|
with Google Authenticator and Authy.
|
|
</p>
|
|
|
|
<p>
|
|
Your Two-Factor is currently:
|
|
{{if .TwoFactorEnabled}}
|
|
<i class="fa fa-check mr-1 has-text-success"></i>
|
|
<strong class="has-text-success">Enabled</strong>
|
|
{{else}}
|
|
<i class="fa fa-xmark mr-1 has-text-danger"></i>
|
|
<strong class="has-text-danger">Not Enabled</strong>
|
|
{{end}}
|
|
</p>
|
|
|
|
<p>
|
|
<a href="/account/two-factor/setup" class="button is-primary">
|
|
Manage Two-Factor Authentication
|
|
</a>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card mb-5">
|
|
<header class="card-header has-background-link">
|
|
<p class="card-header-title has-text-light">
|
|
<i class="fa fa-lock pr-2"></i>
|
|
Update E-mail, Username or Password
|
|
</p>
|
|
</header>
|
|
|
|
<div class="card-content">
|
|
<form method="POST" action="/settings">
|
|
<input type="hidden" name="intent" value="settings">
|
|
{{InputCSRF}}
|
|
|
|
<div class="block">
|
|
You may use the fields below to change the e-mail address you log in with,
|
|
change your username on the site, or set a new password. You will need to
|
|
confirm your current password first before making these changes to your
|
|
account.
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label class="label" for="old_password">
|
|
Current Password
|
|
</label>
|
|
<input type="password" class="input"
|
|
name="old_password"
|
|
id="old_password"
|
|
placeholder="Current password"
|
|
required>
|
|
<p class="help">
|
|
Enter your current password before making any changes to your
|
|
email address or setting a new password.
|
|
</p>
|
|
</div>
|
|
<div class="field">
|
|
<label class="label" for="change_email">Change Email</label>
|
|
<input type="email" class="input"
|
|
id="change_email"
|
|
name="change_email"
|
|
placeholder="name@domain.com"
|
|
value="{{.CurrentUser.Email}}">
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label class="label" for="change_username">Change Username</label>
|
|
<input type="text" class="input"
|
|
id="change_username"
|
|
name="change_username"
|
|
placeholder="{{.CurrentUser.Username}}"
|
|
value="{{.CurrentUser.Username}}"
|
|
{{if .OnChat}}readonly{{end}}>
|
|
<p class="help">
|
|
{{if .OnChat}}
|
|
<span class="has-text-danger">
|
|
<i class="fa fa-exclamation-triangle mr-1"></i>
|
|
You are currently logged into the chat room, so your username may not be
|
|
updated at this time. To change your username, please log out of the chat
|
|
room and wait a minute before reloading this page.
|
|
</span>
|
|
{{else}}
|
|
Usernames are 3 to 32 characters a-z 0-9 . -
|
|
{{end}}
|
|
</p>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label class="label">Change Password</label>
|
|
<input type="password" class="input mb-2"
|
|
name="new_password"
|
|
placeholder="New password">
|
|
<input type="password" class="input mb-2"
|
|
name="new_password2"
|
|
placeholder="Confirm new password">
|
|
</div>
|
|
|
|
<div class="field">
|
|
<button type="submit" class="button is-primary">
|
|
<i class="fa fa-save mr-2"></i> Save Account Settings
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Deactivate or Delete Account -->
|
|
<div id="deactivate">
|
|
<div class="card mb-5">
|
|
<header class="card-header has-background-warning">
|
|
<p class="card-header-title has-text-dark-dark">
|
|
<i class="fa fa-lock pr-2"></i>
|
|
Deactivate My Account
|
|
</p>
|
|
</header>
|
|
|
|
<div class="card-content content">
|
|
<p>
|
|
If you'd like to take a break from {{PrettyTitle}} but think you may want to
|
|
come back later, you may <strong>temporarily deactivate your account</strong>
|
|
which will mark your profile as hidden from everywhere on the website (as if
|
|
it were deleted), but in a way that you can recover your account and reactivate
|
|
it again in the future.
|
|
</p>
|
|
|
|
<p>
|
|
<a href="/account/deactivate" class="button is-primary">
|
|
Temporarily Deactivate My Account
|
|
</a>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Delete Account -->
|
|
<div class="card mb-5">
|
|
<header class="card-header has-background-danger">
|
|
<p class="card-header-title has-text-light">
|
|
<i class="fa fa-exclamation-triangle pr-2"></i>
|
|
Permanently Delete My Account
|
|
</p>
|
|
</header>
|
|
|
|
<div class="card-content">
|
|
<p class="block">
|
|
If you would like to delete your account, please click
|
|
on the button below.
|
|
</p>
|
|
|
|
<p class="block">
|
|
<a href="/account/delete" class="button is-danger">
|
|
Permanently Delete My Account
|
|
</a>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
{{define "scripts"}}
|
|
<link rel="stylesheet" href="/static/js/openlayers-7.5.1/en/latest/ol/ol.css">
|
|
<script src="/static/js/openlayers-7.5.1/en/latest/ol/dist/ol.js"></script>
|
|
<script>
|
|
window.addEventListener("DOMContentLoaded", (event) => {
|
|
// The tabs
|
|
const $profile = document.querySelector("#profile"),
|
|
$look = document.querySelector("#look"),
|
|
$prefs = document.querySelector("#prefs"),
|
|
$location = document.querySelector("#location"),
|
|
$privacy = document.querySelector("#privacy"),
|
|
$notifications = document.querySelector("#notifications"),
|
|
$account = document.querySelector("#account")
|
|
$deactivate = document.querySelector("#deactivate"),
|
|
buttons = Array.from(document.getElementsByClassName("nonshy-tab-button"));
|
|
|
|
// Hide all by default.
|
|
$profile.style.display = 'none';
|
|
$look.style.display = 'none';
|
|
$prefs.style.display = 'none';
|
|
$location.style.display = 'none';
|
|
$privacy.style.display = 'none';
|
|
$notifications.style.display = 'none';
|
|
$account.style.display = 'none';
|
|
$deactivate.style.display = 'none';
|
|
|
|
// Current tab to select by default.
|
|
let $activeTab = $profile;
|
|
|
|
// Global function to toggle the active tab.
|
|
const showTab = (name) => {
|
|
name = name.replace(/\.$/, '');
|
|
let tabName = name.split('/')[0]; // "#profile/about_me"
|
|
if (!tabName) name = "profile";
|
|
$activeTab.style.display = 'none';
|
|
switch (tabName) {
|
|
case "look":
|
|
$activeTab = $look;
|
|
break;
|
|
case "prefs":
|
|
$activeTab = $prefs;
|
|
break;
|
|
case "location":
|
|
$activeTab = $location;
|
|
break;
|
|
case "privacy":
|
|
$activeTab = $privacy;
|
|
break;
|
|
case "notifications":
|
|
$activeTab = $notifications;
|
|
break;
|
|
case "account":
|
|
$activeTab = $account;
|
|
break;
|
|
case "deactivate":
|
|
$activeTab = $deactivate;
|
|
break;
|
|
default:
|
|
$activeTab = $profile;
|
|
}
|
|
|
|
// Update the is-active classes on all the tabs.
|
|
buttons.forEach(tab_ => {
|
|
let name_ = tab_.href.split("#").pop();
|
|
|
|
if (name !== name_) {
|
|
tab_.classList.remove("is-active");
|
|
} else {
|
|
tab_.classList.add("is-active");
|
|
}
|
|
|
|
tab_.addEventListener("click", (e) => {
|
|
document.querySelector("#"+name_).scrollIntoView();
|
|
});
|
|
});
|
|
|
|
$activeTab.style.display = 'block';
|
|
history.replaceState(undefined, undefined, '#'+name);
|
|
};
|
|
|
|
// Wire the tab buttons up.
|
|
buttons.forEach(el => {
|
|
let name = el.href.split("#").pop();
|
|
el.addEventListener("click", (e) => {
|
|
showTab(name);
|
|
|
|
if (screen.width >= 1024) {
|
|
e.preventDefault();
|
|
}
|
|
|
|
window.requestAnimationFrame(() => {
|
|
window.scrollTo(0, 0);
|
|
});
|
|
});
|
|
})
|
|
|
|
// Show the requested tab on first page load.
|
|
showTab(window.location.hash.replace(/^#/, ''));
|
|
// window.requestAnimationFrame(() => {
|
|
// window.scrollTo(0, 0);
|
|
// });
|
|
});
|
|
|
|
// Look & Feel tab scripts.
|
|
window.addEventListener("DOMContentLoaded", (event) => {
|
|
let $headerPreview = document.querySelector("#header-hero-preview"),
|
|
$colorA = document.querySelector("#hero-color-start"),
|
|
$colorB = document.querySelector("#hero-color-end"),
|
|
$darkText = document.querySelector("#hero-text-dark"),
|
|
$cardPreview = document.querySelector("#profile-card-preview"),
|
|
$cardLightness = document.querySelector("#card-lightness"),
|
|
$cardTitleBG = document.querySelector("#card-title-bg"),
|
|
$cardTitleFG = document.querySelector("#card-title-fg"),
|
|
$cardLinkColor = document.querySelector("#card-link-color");
|
|
|
|
function updatePreview() {
|
|
/* Hero banner preview */
|
|
let css = `linear-gradient(141deg, ${$colorA.value}, ${$colorB.value})`;
|
|
$headerPreview.style.backgroundImage = css;
|
|
|
|
if ($darkText.checked) {
|
|
$headerPreview.classList.remove("has-text-light");
|
|
$headerPreview.classList.add("has-text-dark-dark");
|
|
} else {
|
|
$headerPreview.classList.remove("has-text-dark-dark");
|
|
$headerPreview.classList.add("has-text-light");
|
|
}
|
|
|
|
/* Card style preview */
|
|
let $table = $cardPreview.querySelector("table"),
|
|
$header = $cardPreview.querySelector("div.card-header"),
|
|
$title = $cardPreview.querySelector("p.card-header-title"),
|
|
$link = $cardPreview.querySelector("a");
|
|
$header.style.backgroundColor = $cardTitleBG.value;
|
|
$title.style.color = $cardTitleFG.value;
|
|
$table.style.backgroundColor = "transparent";
|
|
$link.style.color = $cardLinkColor.value;
|
|
if ($cardLightness.value === "light") {
|
|
$table.style.backgroundColor = "#fff";
|
|
$table.style.color = "#4a4a4a";
|
|
} else if ($cardLightness.value === "dark") {
|
|
$table.style.backgroundColor = "#4a4a4a";
|
|
$table.style.color = "#f5f5f5";
|
|
}
|
|
|
|
/* Update <strong> tags in card style table */
|
|
($table.querySelectorAll("strong") || []).forEach(node => {
|
|
node.style.color = "";
|
|
if ($cardLightness.value === "light") {
|
|
node.style.color = "#4a4a4a";
|
|
} else if ($cardLightness.value === "dark") {
|
|
node.style.color = "#f5f5f5";
|
|
}
|
|
});
|
|
}
|
|
|
|
updatePreview();
|
|
|
|
([$colorA, $colorB, $darkText, $cardLightness, $cardTitleBG, $cardTitleFG, $cardLinkColor]).forEach(node => {
|
|
node.addEventListener("change", (e) => {
|
|
updatePreview();
|
|
});
|
|
});
|
|
});
|
|
|
|
// Notifications tab scripts.
|
|
window.addEventListener("DOMContentLoaded", (event) => {
|
|
// Get useful controls from the tab.
|
|
const $pushStatusGranted = document.querySelector("#push-status-enabled"),
|
|
$pushStatusDenied = document.querySelector("#push-status-disabled"),
|
|
$pushStatusDefault = document.querySelector("#push-status-default"),
|
|
$pushEnableButton = document.querySelector("#grant-push-permission"),
|
|
$pushDeniedHelp = document.querySelector("#push-denied-help");
|
|
|
|
// Is the Notification API unavailable?
|
|
if (typeof(window.Notification) === "undefined" || typeof(PushNotificationSubscribe) === "undefined") {
|
|
$pushStatusDefault.innerHTML = `<i class="fa fa-xmark mr-1"></i> Notification API unavailable`;
|
|
$pushStatusDefault.style.display = "";
|
|
return;
|
|
}
|
|
|
|
// And testing widely for errors...
|
|
try {
|
|
|
|
// Get the current permission status: default, granted, denied.
|
|
const showPermission = (permission) => {
|
|
$pushStatusGranted.style.display = "none";
|
|
$pushStatusDenied.style.display = "none";
|
|
$pushStatusDefault.style.display = "none";
|
|
switch (permission) {
|
|
case "granted":
|
|
$pushStatusGranted.style.display = "";
|
|
$pushEnableButton.innerHTML = "Test Push Notification";
|
|
break;
|
|
case "denied":
|
|
$pushEnableButton.style.display = "none";
|
|
$pushDeniedHelp.style.display = "";
|
|
$pushStatusDenied.style.display = "";
|
|
break;
|
|
default:
|
|
$pushStatusDefault.style.display = "";
|
|
$pushEnableButton.innerHTML = "Grant Push Notification Permission";
|
|
break;
|
|
}
|
|
};
|
|
showPermission(Notification.permission);
|
|
|
|
$pushEnableButton.addEventListener("click", (e) => {
|
|
e.preventDefault();
|
|
Notification.requestPermission().then(permission => {
|
|
// Update the displayed permission status.
|
|
showPermission(Notification.permission);
|
|
|
|
// If granted, subscribe to push notifications now.
|
|
if (permission === "granted") {
|
|
// In static/js/web-push.js
|
|
PushNotificationSubscribe();
|
|
|
|
// Test the notification now.
|
|
const notification = new Notification(`Hello from ${document.location.hostname}!`, {
|
|
body: "This is an example notification from this site.",
|
|
icon: "/static/img/favicon-192.png",
|
|
});
|
|
}
|
|
});
|
|
});
|
|
} catch(err) {
|
|
$pushStatusDefault.innerHTML = `<i class="fa fa-xmark mr-1"></i> Error: ${err}`;
|
|
$pushStatusDefault.style.display = "block";
|
|
}
|
|
});
|
|
|
|
// Location tab scripts.
|
|
window.addEventListener("DOMContentLoaded", (event) => {
|
|
// Get useful controls from the tab.
|
|
const $optionGPS = document.querySelector("#location-option-gps"),
|
|
$optionGeoIP = document.querySelector("#location-option-geoip"),
|
|
$optionPin = document.querySelector("#location-option-pin"),
|
|
$optionNone = document.querySelector("#location-option-none"),
|
|
$gpsCurrentStatus = document.querySelector("#location-status-gps"),
|
|
$gpsRefreshButton = document.querySelector("#gps-refresh"),
|
|
$saveLatitude = document.querySelector("#saveLatitude"),
|
|
$saveLongitude = document.querySelector("#saveLongitude");
|
|
|
|
// Function to massage coordinates for saving in the DB.
|
|
const saveCoords = (latitude, longitude) => {
|
|
$saveLatitude.value = latitude === null ? "" : latitude.toFixed(2);
|
|
$saveLongitude.value = longitude === null ? "" : longitude.toFixed(2);
|
|
};
|
|
|
|
// A lazy-initialized function to handle setting a new pin on the OpenStreetMap widget.
|
|
let setMapPin = null; // function(lonLat)
|
|
|
|
// The user's GeoIP coordinates, if available.
|
|
let geoIPInsights = {{ToJSON .GeoIPInsights}},
|
|
savedLocation = {{.UserLocation}};
|
|
|
|
// Default GPS coords to select on the map on page load:
|
|
// whatever we get from the backend DB.
|
|
let defaultCoords = [12.5, 41.9];
|
|
if (savedLocation.Latitude != 0 || savedLocation.Longitude != 0) {
|
|
// Prefer the DB saved coords.
|
|
defaultCoords = [
|
|
savedLocation.Longitude,
|
|
savedLocation.Latitude,
|
|
];
|
|
} else if (geoIPInsights.Latitude != 0 || geoIPInsights.Longitude != 0) {
|
|
// Then fall back on GeoIP coords.
|
|
defaultCoords = [
|
|
geoIPInsights.Longitude,
|
|
geoIPInsights.Latitude,
|
|
];
|
|
}
|
|
|
|
// Is geolocation not supported?
|
|
if (navigator.geolocation == undefined) {
|
|
$gpsCurrentStatus.className = "";
|
|
$gpsCurrentStatus.classList.add("has-text-danger");
|
|
$gpsCurrentStatus.innerHTML = "Geolocation is not supported by your browser.";
|
|
}
|
|
|
|
// Geolocation callback funcs.
|
|
const onSuccess = (pos) => {
|
|
const crd = pos.coords;
|
|
$gpsCurrentStatus.className = "";
|
|
$gpsCurrentStatus.classList.add("has-text-success-dark");
|
|
$gpsCurrentStatus.innerHTML = `Lat: ${crd.latitude}; Long: ${crd.longitude}; Accuracy: ${crd.accuracy}`;
|
|
$gpsRefreshButton.style.display = "";
|
|
|
|
// Set the form fields.
|
|
saveCoords(crd.latitude, crd.longitude);
|
|
|
|
// Can we drop a pin?
|
|
if (setMapPin !== null) {
|
|
setMapPin([crd.longitude, crd.latitude]);
|
|
}
|
|
};
|
|
const onError = (err) => {
|
|
$gpsCurrentStatus.className = "";
|
|
$gpsCurrentStatus.classList.add("has-text-danger");
|
|
$gpsCurrentStatus.innerHTML = `${err.message} (code ${err.code})`;
|
|
$gpsRefreshButton.style.display = "";
|
|
};
|
|
|
|
// Get or refresh their location.
|
|
const getLocation = () => {
|
|
if (navigator.geolocation != undefined) {
|
|
$gpsRefreshButton.style.display = "none";
|
|
$gpsCurrentStatus.className = "";
|
|
$gpsCurrentStatus.classList.add("has-text-info");
|
|
$gpsCurrentStatus.innerHTML = "Requesting your current location...";
|
|
navigator.geolocation.getCurrentPosition(onSuccess, onError, {
|
|
enableHighAccuracy: true,
|
|
timeout: 10000,
|
|
maximumAge: 0,
|
|
});
|
|
}
|
|
};
|
|
|
|
// Set an onClick handler for the GPS location tab, to request
|
|
// permission from the user's browser.
|
|
$optionGPS.addEventListener("click", (e) => {
|
|
getLocation();
|
|
});
|
|
|
|
// If the page loaded w/ the GPS option on, ask right away.
|
|
if ($optionGPS.checked) {
|
|
$optionGPS.click();
|
|
}
|
|
|
|
// Wire the refresh button.
|
|
$gpsRefreshButton.addEventListener("click", (e) => {
|
|
e.preventDefault();
|
|
getLocation();
|
|
});
|
|
|
|
// If the user goes back to GeoIP coords, reinitialize the defaults.
|
|
$optionGeoIP.addEventListener("click", (e) => {
|
|
// Set the form fields.
|
|
// NOTE: defaultCoords are [lon, lat] we want [lat, lon]
|
|
if (geoIPInsights.Latitude != 0 || geoIPInsights.Longitude != 0) {
|
|
defaultCoords = [
|
|
geoIPInsights.Longitude,
|
|
geoIPInsights.Latitude,
|
|
];
|
|
}
|
|
saveCoords(defaultCoords[1], defaultCoords[0]);
|
|
|
|
if (setMapPin !== null) {
|
|
setMapPin(defaultCoords);
|
|
}
|
|
});
|
|
|
|
// If the user disables their geolocation.
|
|
$optionNone.addEventListener("click", (e) => {
|
|
saveCoords(null, null);
|
|
});
|
|
|
|
/***
|
|
* OpenLayers Map Widget
|
|
***/
|
|
|
|
let vectorSource = new ol.source.Vector(),
|
|
vectorLayer = new ol.layer.Vector({
|
|
source: vectorSource,
|
|
});
|
|
|
|
let map = new ol.Map({
|
|
target: 'map', // the <div id="map"> target
|
|
layers: [
|
|
new ol.layer.Tile({
|
|
source: new ol.source.OSM()
|
|
}),
|
|
vectorLayer
|
|
],
|
|
|
|
// The view allows to specify the center, resolution, and rotation of the map.
|
|
view: new ol.View({
|
|
center: ol.proj.fromLonLat(defaultCoords),
|
|
zoom: 10
|
|
})
|
|
});
|
|
|
|
// Define the function to drop a pin.
|
|
let onMapClick = (coordinate, { center=true }) => {
|
|
vectorSource.clear();
|
|
let feature = new ol.Feature(new ol.geom.Point(coordinate));
|
|
vectorSource.addFeatures([feature]);
|
|
|
|
let prettyCoord = ol.coordinate.toStringHDMS(
|
|
ol.proj.transform(coordinate, 'EPSG:3857', 'EPSG:4326'),
|
|
2,
|
|
);
|
|
|
|
// Center the map on the pin.
|
|
if (center) {
|
|
map.setView(new ol.View({
|
|
center: coordinate,
|
|
zoom: 10
|
|
}));
|
|
}
|
|
};
|
|
|
|
setMapPin = (lonLat) => {
|
|
let center = ol.proj.fromLonLat(lonLat);
|
|
onMapClick(ol.proj.fromLonLat(lonLat), {center: true});
|
|
};
|
|
|
|
map.on('click', (e) => {
|
|
// Do not drop a pin if the option isn't set.
|
|
if (!$optionPin.checked) return;
|
|
|
|
// Save the selected coords to the form.
|
|
let center = ol.proj.toLonLat(e.coordinate);
|
|
saveCoords(center[1], center[0]);
|
|
|
|
onMapClick(e.coordinate, {center: false});
|
|
});
|
|
|
|
// Drop the initial pin at the default coords.
|
|
setMapPin(defaultCoords);
|
|
});
|
|
</script>
|
|
|
|
<style type="text/css">
|
|
/* Ugly hack */
|
|
div.hero-body figure.profile-photo {
|
|
padding: 2px !important;
|
|
}
|
|
div.hero-body figure.is-inline-block {
|
|
margin-bottom: -6px !important;
|
|
}
|
|
</style>
|
|
{{end}}
|