Not too important, but I think that it'd be a pretty neat touch. Also fixes some layout bugs introduced by a previous PR. --------- Co-authored-by: Gusted <postmaster@gusted.xyz> Co-authored-by: Caesar Schinas <caesar@caesarschinas.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>tags/v1.21.0-rc0
@@ -827,6 +827,15 @@ LEVEL = Info | |||
;; Dependencies can be added from any repository where the user is granted access or only from the current repository depending on this setting. | |||
;ALLOW_CROSS_REPOSITORY_DEPENDENCIES = true | |||
;; | |||
;; Default map service. No external API support has been included. A service has to allow | |||
;; searching using URL parameters, the location will be appended to the URL as escaped query parameter. | |||
;; Disabled by default, some example values are: | |||
;; - OpenStreetMap: https://www.openstreetmap.org/search?query= | |||
;; - Google Maps: https://www.google.com/maps/place/ | |||
;; - MapQuest: https://www.mapquest.com/search/ | |||
;; - Bing Maps: https://www.bing.com/maps?where1= | |||
; USER_LOCATION_MAP_URL = | |||
;; | |||
;; Enable heatmap on users profiles. | |||
;ENABLE_USER_HEATMAP = true | |||
;; |
@@ -648,6 +648,7 @@ And the following unique queues: | |||
- `DEFAULT_USER_IS_RESTRICTED`: **false**: Give new users restricted permissions by default | |||
- `DEFAULT_ENABLE_DEPENDENCIES`: **true**: Enable this to have dependencies enabled by default. | |||
- `ALLOW_CROSS_REPOSITORY_DEPENDENCIES` : **true** Enable this to allow dependencies on issues from any repository where the user is granted access. | |||
- `USER_LOCATION_MAP_URL`: **""**: A map service URL to show user's location on a map. The location will be appended to the URL as escaped query parameter. | |||
- `ENABLE_USER_HEATMAP`: **true**: Enable this to display the heatmap on users profiles. | |||
- `ENABLE_TIMETRACKING`: **true**: Enable Timetracking feature. | |||
- `DEFAULT_ENABLE_TIMETRACKING`: **true**: Allow repositories to use timetracking by default. |
@@ -73,6 +73,7 @@ var Service = struct { | |||
AllowCrossRepositoryDependencies bool | |||
DefaultAllowOnlyContributorsToTrackTime bool | |||
NoReplyAddress string | |||
UserLocationMapURL string | |||
EnableUserHeatmap bool | |||
AutoWatchNewRepos bool | |||
AutoWatchOnChanges bool | |||
@@ -185,6 +186,7 @@ func loadServiceFrom(rootCfg ConfigProvider) { | |||
Service.AllowCrossRepositoryDependencies = sec.Key("ALLOW_CROSS_REPOSITORY_DEPENDENCIES").MustBool(true) | |||
Service.DefaultAllowOnlyContributorsToTrackTime = sec.Key("DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME").MustBool(true) | |||
Service.NoReplyAddress = sec.Key("NO_REPLY_ADDRESS").MustString("noreply." + Domain) | |||
Service.UserLocationMapURL = sec.Key("USER_LOCATION_MAP_URL").String() | |||
Service.EnableUserHeatmap = sec.Key("ENABLE_USER_HEATMAP").MustBool(true) | |||
Service.AutoWatchNewRepos = sec.Key("AUTO_WATCH_NEW_REPOS").MustBool(true) | |||
Service.AutoWatchOnChanges = sec.Key("AUTO_WATCH_ON_CHANGES").MustBool(false) |
@@ -601,6 +601,7 @@ user_bio = Biography | |||
disabled_public_activity = This user has disabled the public visibility of the activity. | |||
email_visibility.limited = Your email address is visible to all authenticated users | |||
email_visibility.private = Your email address is only visible to you and administrators | |||
show_on_map = Show this place on a map | |||
form.name_reserved = The username "%s" is reserved. | |||
form.name_pattern_not_allowed = The pattern "%s" is not allowed in a username. | |||
@@ -627,6 +628,7 @@ webauthn = Security Keys | |||
public_profile = Public Profile | |||
biography_placeholder = Tell us a little bit about yourself | |||
location_placeholder = Share your approximate location with others | |||
profile_desc = Your email address will be used for notifications and other operations. | |||
password_username_disabled = Non-local users are not allowed to change their username. Please contact your site administrator for more details. | |||
full_name = Full Name |
@@ -52,6 +52,7 @@ func userProfile(ctx *context.Context) { | |||
ctx.Data["Title"] = ctx.ContextUser.DisplayName() | |||
ctx.Data["PageIsUserProfile"] = true | |||
ctx.Data["UserLocationMapURL"] = setting.Service.UserLocationMapURL | |||
// prepare heatmap data | |||
if setting.Service.EnableUserHeatmap { |
@@ -24,19 +24,28 @@ | |||
<div class="extra content gt-word-break"> | |||
<ul> | |||
{{if .ContextUser.Location}} | |||
<li>{{svg "octicon-location"}} {{.ContextUser.Location}}</li> | |||
<li> | |||
{{svg "octicon-location"}} | |||
<span class="gt-f1">{{.ContextUser.Location}}</span> | |||
{{if .UserLocationMapURL}} | |||
{{/* We presume that the UserLocationMapURL is safe, as it is provided by the site administrator. */}} | |||
<a href="{{.UserLocationMapURL | Safe}}{{.ContextUser.Location | QueryEscape}}" rel="nofollow noreferrer" data-tooltip-content="{{.locale.Tr "user.show_on_map"}}"> | |||
{{svg "octicon-link-external"}} | |||
</a> | |||
{{end}} | |||
</li> | |||
{{end}} | |||
{{if (eq .SignedUserID .ContextUser.ID)}} | |||
<li> | |||
{{svg "octicon-mail"}} | |||
<a href="mailto:{{.ContextUser.Email}}" rel="nofollow">{{.ContextUser.Email}}</a> | |||
<a class="gt-f1" href="mailto:{{.ContextUser.Email}}" rel="nofollow">{{.ContextUser.Email}}</a> | |||
<a href="{{AppSubUrl}}/user/settings#keep-email-private"> | |||
{{if .ShowUserEmail}} | |||
<i class="ui right" data-tooltip-content="{{.locale.Tr "user.email_visibility.limited"}}"> | |||
<i data-tooltip-content="{{.locale.Tr "user.email_visibility.limited"}}"> | |||
{{svg "octicon-unlock"}} | |||
</i> | |||
{{else}} | |||
<i class="ui right" data-tooltip-content="{{.locale.Tr "user.email_visibility.private"}}"> | |||
<i data-tooltip-content="{{.locale.Tr "user.email_visibility.private"}}"> | |||
{{svg "octicon-lock"}} | |||
</i> | |||
{{end}} | |||
@@ -69,7 +78,7 @@ | |||
</li> | |||
{{end}} | |||
{{end}} | |||
<li>{{svg "octicon-calendar"}} {{.locale.Tr "user.joined_on" (DateTime "short" .ContextUser.CreatedUnix) | Safe}}</li> | |||
<li>{{svg "octicon-calendar"}} <span>{{.locale.Tr "user.joined_on" (DateTime "short" .ContextUser.CreatedUnix) | Safe}}</span></li> | |||
{{if and .Orgs .HasOrgsVisible}} | |||
<li> | |||
<ul class="user-orgs"> |
@@ -35,7 +35,7 @@ | |||
</div> | |||
<div class="field"> | |||
<label for="location">{{.locale.Tr "settings.location"}}</label> | |||
<input id="location" name="location" value="{{.SignedUser.Location}}" maxlength="50"> | |||
<input id="location" name="location" placeholder="{{.locale.Tr "settings.location_placeholder"}}" value="{{.SignedUser.Location}}" maxlength="50"> | |||
</div> | |||
<div class="divider"></div> |
@@ -12,6 +12,7 @@ import ( | |||
repo_model "code.gitea.io/gitea/models/repo" | |||
"code.gitea.io/gitea/models/unittest" | |||
user_model "code.gitea.io/gitea/models/user" | |||
"code.gitea.io/gitea/modules/setting" | |||
api "code.gitea.io/gitea/modules/structs" | |||
"code.gitea.io/gitea/modules/test" | |||
"code.gitea.io/gitea/modules/translation" | |||
@@ -276,3 +277,23 @@ func TestListStopWatches(t *testing.T) { | |||
assert.Greater(t, apiWatches[0].Seconds, int64(0)) | |||
} | |||
} | |||
func TestUserLocationMapLink(t *testing.T) { | |||
setting.Service.UserLocationMapURL = "https://example/foo/" | |||
defer tests.PrepareTestEnv(t)() | |||
session := loginUser(t, "user2") | |||
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{ | |||
"_csrf": GetCSRF(t, session, "/user/settings"), | |||
"name": "user2", | |||
"email": "user@example.com", | |||
"language": "en-US", | |||
"location": "A/b", | |||
}) | |||
session.MakeRequest(t, req, http.StatusSeeOther) | |||
req = NewRequest(t, "GET", "/user2/") | |||
resp := session.MakeRequest(t, req, http.StatusOK) | |||
htmlDoc := NewHTMLParser(t, resp.Body) | |||
htmlDoc.AssertElement(t, `a[href="https://example/foo/A%2Fb"]`, true) | |||
} |
@@ -22,18 +22,16 @@ | |||
.user.profile .ui.card .extra.content > ul > li { | |||
padding: 10px; | |||
display: flex; | |||
list-style: none; | |||
align-items: center; | |||
gap: 0.25em; | |||
} | |||
.user.profile .ui.card .extra.content > ul > li:not(:last-child) { | |||
border-bottom: 1px solid var(--color-secondary); | |||
} | |||
.user.profile .ui.card .extra.content > ul > li .svg { | |||
margin-left: 1px; | |||
margin-right: 5px; | |||
} | |||
.user.profile .ui.card .extra.content > ul > li.follow .ui.button { | |||
width: 100%; | |||
} |