481bd0ae61
* Add a way for users to temporarily deactivate their accounts, in a recoverable way should they decide to return later. * A deactivated account may log in but have limited options: to reactivate their account, permanently delete it, or log out. * Fix several bugs around the display of comments, messages and forum threads for disabled, banned, or blocked users: * Messages (inbox and sentbox) will be hidden and the unread indicator will not count unread messages the user can't access. * Comments on photos and forum posts are hidden, and top-level threads on the "Newest" tab will show "[unavailable]" for their text and username. * Your historical notifications will hide users who are blocked, banned or disabled. * Add a "Friends" tab to user profile pages, to see other users' friends. * The page is Certification Required so non-cert users can't easily discover any members on the site.
1090 lines
51 KiB
HTML
1090 lines
51 KiB
HTML
{{define "title"}}User Settings{{end}}
|
|
{{define "content"}}
|
|
<div class="container">
|
|
<section class="hero is-info 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">
|
|
<p class="menu-label">
|
|
Settings
|
|
</p>
|
|
|
|
<ul class="menu-list block">
|
|
<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#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#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 block">
|
|
<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>
|
|
</ul>
|
|
</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">
|
|
<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">
|
|
<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">
|
|
<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>
|
|
|
|
<!-- 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-2">
|
|
<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-2">
|
|
<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 ml-4">
|
|
<small><em>
|
|
Note: this refers to Direct Messages on the main website
|
|
(not inside the chat room).
|
|
</em></small>
|
|
{{.CurrentUser.GetProfileField "dm_privacy"}}
|
|
</div>
|
|
|
|
<label class="checkbox">
|
|
<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">
|
|
<input type="radio"
|
|
name="dm_privacy"
|
|
value="friends"
|
|
{{if eq (.CurrentUser.GetProfileField "dm_privacy") "friends"}}checked{{end}}>
|
|
Only people on my Friends list
|
|
</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">
|
|
<input type="radio"
|
|
name="dm_privacy"
|
|
value="nobody"
|
|
{{if eq (.CurrentUser.GetProfileField "dm_privacy") "nobody"}}checked{{end}}>
|
|
Nobody (close my DMs)
|
|
</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">
|
|
<button type="submit" class="button is-primary">
|
|
<i class="fa fa-save mr-2"></i> Save Privacy Settings
|
|
</button>
|
|
</div>
|
|
|
|
</form>
|
|
</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-dark"></i>
|
|
<strong class="has-text-success-dark">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 or Password
|
|
</p>
|
|
</header>
|
|
|
|
<div class="card-content">
|
|
<form method="POST" action="/settings">
|
|
<input type="hidden" name="intent" value="settings">
|
|
{{InputCSRF}}
|
|
|
|
<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">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"),
|
|
$prefs = document.querySelector("#prefs"),
|
|
$location = document.querySelector("#location"),
|
|
$privacy = document.querySelector("#privacy"),
|
|
$account = document.querySelector("#account")
|
|
$deactivate = document.querySelector("#deactivate"),
|
|
buttons = Array.from(document.getElementsByClassName("nonshy-tab-button"));
|
|
|
|
// Hide all by default.
|
|
$profile.style.display = 'none';
|
|
$prefs.style.display = 'none';
|
|
$location.style.display = 'none';
|
|
$privacy.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(/\.$/, '');
|
|
if (!name) name = "profile";
|
|
$activeTab.style.display = 'none';
|
|
switch (name) {
|
|
case "prefs":
|
|
$activeTab = $prefs;
|
|
break;
|
|
case "location":
|
|
$activeTab = $location;
|
|
break;
|
|
case "privacy":
|
|
$activeTab = $privacy;
|
|
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_) {
|
|
console.log("button: remove is-active", tab_);
|
|
tab_.classList.remove("is-active");
|
|
} else {
|
|
console.log("button %s: ADD is-active", tab_);
|
|
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);
|
|
|
|
e.preventDefault();
|
|
});
|
|
})
|
|
|
|
// Show the requested tab on first page load.
|
|
showTab(window.location.hash.replace(/^#/, ''));
|
|
});
|
|
|
|
// 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,
|
|
);
|
|
console.log("COORDINATE: " + prettyCoord);
|
|
|
|
// 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>
|
|
{{end}}
|