loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Rework user profile settings

Accessibility:

- improved semantic layout
- Fixes unlabelled input for custom pronouns. CC @hazy
- Adds labels to dropdowns.
- Shortens certain texts for less verbose screen reader outputs and
people with slow reading speed.
- Turned optional username rename helper text with low contrast into
"normal" help text.

UI/UX:

- Removes section about primary email which is no longer managed in the
profile section.
- Fixes section about primary email not displaying in user settings when notifications are
not available.
- Removes primary email display, because it is not actually a form
element here. (Alternatively, we could display it and link to the
account settings for managing the email)

+193 -172
+3 -2
options/locale/locale_en-US.ini
··· 748 748 public_profile = Public profile 749 749 biography_placeholder = Tell others a little bit about yourself! (Markdown is supported) 750 750 location_placeholder = Share your approximate location with others 751 - profile_desc = Control how your profile is shown to other users. Your primary email address will be used for notifications, password recovery and web-based Git operations. 751 + profile_desc = About you 752 752 password_username_disabled = Non-local users are not allowed to change their username. Please contact your site administrator for more details. 753 753 full_name = Full name 754 754 website = Website 755 755 location = Location 756 756 pronouns = Pronouns 757 757 pronouns_custom = Custom 758 + pronouns_custom_label = Custom pronouns 758 759 pronouns_unspecified = Unspecified 759 760 update_theme = Change theme 760 761 update_profile = Update profile ··· 849 850 email_preference_set_success = Email preference has been set successfully. 850 851 add_openid_success = The new OpenID address has been added. 851 852 keep_email_private = Hide email address 852 - keep_email_private_popup = This will hide your email address from your profile. It will no longer be the default for commits made via the web interface, like file uploads and edits, and will not be used for merge commits. Instead a special address %s can be used to associate commits with your account. Note that changing this option will not affect existing commits. 853 + keep_email_private_popup = Your email address will not be shown on your profile and will not be the default for commits made via the web interface, like file uploads, edits, and merge commits. Instead, a special address %s can be used to link commits to your account. This option will not affect existing commits. 853 854 openid_desc = OpenID lets you delegate authentication to an external provider. 854 855 855 856 manage_ssh_keys = Manage SSH keys
+1 -1
templates/user/settings/account.tmpl
··· 40 40 </h4> 41 41 <div class="ui attached segment"> 42 42 <div class="ui list"> 43 + <div class="tw-mb-2">{{ctx.Locale.Tr "settings.email_desc"}}</div> 43 44 {{if $.EnableNotifyMail}} 44 45 <div class="item"> 45 - <div class="tw-mb-2">{{ctx.Locale.Tr "settings.email_desc"}}</div> 46 46 <form action="{{AppSubUrl}}/user/settings/account/email" class="ui form" method="post"> 47 47 {{$.CsrfTokenHtml}} 48 48 <input name="_method" type="hidden" value="NOTIFICATION">
+101 -106
templates/user/settings/profile.tmpl
··· 4 4 {{ctx.Locale.Tr "settings.public_profile"}} 5 5 </h4> 6 6 <div class="ui attached segment"> 7 - <p>{{ctx.Locale.Tr "settings.profile_desc"}}</p> 8 7 <form class="ui form" action="{{.Link}}" method="post"> 9 8 {{.CsrfTokenHtml}} 10 - <div class="required field {{if .Err_Name}}error{{end}}"> 11 - <label for="username">{{ctx.Locale.Tr "username"}} 12 - <span class="text red tw-hidden" id="name-change-prompt"> {{ctx.Locale.Tr "settings.change_username_prompt"}}</span> 13 - <span class="text red tw-hidden" id="name-change-redirect-prompt"> {{ctx.Locale.Tr "settings.change_username_redirect_prompt"}}</span> 9 + <fieldset> 10 + <legend>{{ctx.Locale.Tr "settings.profile_desc"}}</legend> 11 + <label {{if .Err_Name}}class="field error"{{end}}> 12 + {{ctx.Locale.Tr "username"}} 13 + <input name="name" value="{{.SignedUser.Name}}" data-name="{{.SignedUser.Name}}" autofocus required {{if or (not .SignedUser.IsLocal) .IsReverseProxy}}disabled{{end}} maxlength="40"> 14 + {{if or (not .SignedUser.IsLocal) .IsReverseProxy}} 15 + <span class="help">{{ctx.Locale.Tr "settings.password_username_disabled"}}</span> 16 + {{else}} 17 + <span class="help"> 18 + {{ctx.Locale.Tr "settings.change_username_prompt"}} 19 + {{ctx.Locale.Tr "settings.change_username_redirect_prompt"}} 20 + </span> 21 + {{end}} 14 22 </label> 15 - <input id="username" name="name" value="{{.SignedUser.Name}}" data-name="{{.SignedUser.Name}}" autofocus required {{if or (not .SignedUser.IsLocal) .IsReverseProxy}}disabled{{end}} maxlength="40"> 16 - {{if or (not .SignedUser.IsLocal) .IsReverseProxy}} 17 - <p class="help text blue">{{ctx.Locale.Tr "settings.password_username_disabled"}}</p> 18 - {{end}} 19 - </div> 20 - <div class="field {{if .Err_FullName}}error{{end}}"> 21 - <label for="full_name">{{ctx.Locale.Tr "settings.full_name"}}</label> 22 - <input id="full_name" name="full_name" value="{{.SignedUser.FullName}}" maxlength="100"> 23 - </div> 24 - <div class="inline field"> 25 - <span class="inline field"><label for="pronouns">{{ctx.Locale.Tr "settings.pronouns"}}</label></span> 26 - <div id="pronouns-dropdown" style="display: none" class="ui selection dropdown"> 27 - <input type="hidden" value="{{.SignedUser.Pronouns}}"> 28 - <div class="text"> 29 - {{if .PronounsAreCustom}} 30 - {{ctx.Locale.Tr "settings.pronouns_custom"}} 31 - {{else if eq "" .SignedUser.Pronouns}} 32 - {{ctx.Locale.Tr "settings.pronouns_unspecified"}} 33 - {{else}} 34 - {{.SignedUser.Pronouns}} 35 - {{end}} 36 - </div> 37 - {{svg "octicon-triangle-down" 14 "dropdown icon"}} 38 - <div class="menu"> 39 - <div class="item{{if eq "" .SignedUser.Pronouns}} active selected{{end}}" data-value=""><p>{{ctx.Locale.Tr "settings.pronouns_unspecified"}}</p></div> 40 - <div class="item{{if eq "he/him" .SignedUser.Pronouns}} active selected{{end}}" data-value="he/him">he/him</div> 41 - <div class="item{{if eq "she/her" .SignedUser.Pronouns}} active selected{{end}}" data-value="she/her">she/her</div> 42 - <div class="item{{if eq "they/them" .SignedUser.Pronouns}} active selected{{end}}" data-value="they/them">they/them</div> 43 - <div class="item{{if eq "it/its" .SignedUser.Pronouns}} active selected{{end}}" data-value="it/its">it/its</div> 44 - <div class="item{{if eq "any pronouns" .SignedUser.Pronouns}} active selected{{end}}" data-value="any pronouns">any pronouns</div> 45 - {{if .PronounsAreCustom}} 46 - <div class="item active selected" data-value="{{.SignedUser.Pronouns}}"><p>{{ctx.Locale.Tr "settings.pronouns_custom"}}</p></div> 47 - {{else}} 48 - <div class="item" data-value="!"><i>{{ctx.Locale.Tr "settings.pronouns_custom"}}</i></div> 49 - {{end}} 23 + 24 + <label {{if .Err_FullName}}class="field error"{{end}}> 25 + {{ctx.Locale.Tr "settings.full_name"}} 26 + <input name="full_name" value="{{.SignedUser.FullName}}" maxlength="100"> 27 + </label> 28 + 29 + <label id="label-pronouns" class="tw-hidden"> 30 + {{ctx.Locale.Tr "settings.pronouns"}} 31 + <div id="pronouns-dropdown" class="ui selection dropdown" aria-labelledby="label-pronouns"> 32 + <input type="hidden" value="{{.SignedUser.Pronouns}}"> 33 + <div class="text"> 34 + {{if .PronounsAreCustom}} 35 + {{ctx.Locale.Tr "settings.pronouns_custom"}} 36 + {{else if eq "" .SignedUser.Pronouns}} 37 + {{ctx.Locale.Tr "settings.pronouns_unspecified"}} 38 + {{else}} 39 + {{.SignedUser.Pronouns}} 40 + {{end}} 41 + </div> 42 + {{svg "octicon-triangle-down" 14 "dropdown icon"}} 43 + <div class="menu"> 44 + <div class="item{{if eq "" .SignedUser.Pronouns}} active selected{{end}}" data-value=""><p>{{ctx.Locale.Tr "settings.pronouns_unspecified"}}</p></div> 45 + <div class="item{{if eq "he/him" .SignedUser.Pronouns}} active selected{{end}}" data-value="he/him">he/him</div> 46 + <div class="item{{if eq "she/her" .SignedUser.Pronouns}} active selected{{end}}" data-value="she/her">she/her</div> 47 + <div class="item{{if eq "they/them" .SignedUser.Pronouns}} active selected{{end}}" data-value="they/them">they/them</div> 48 + <div class="item{{if eq "it/its" .SignedUser.Pronouns}} active selected{{end}}" data-value="it/its">it/its</div> 49 + <div class="item{{if eq "any pronouns" .SignedUser.Pronouns}} active selected{{end}}" data-value="any pronouns">any pronouns</div> 50 + {{if .PronounsAreCustom}} 51 + <div class="item active selected" data-value="{{.SignedUser.Pronouns}}">{{ctx.Locale.Tr "settings.pronouns_custom"}}</div> 52 + {{else}} 53 + <div class="item" data-value="!"><i>{{ctx.Locale.Tr "settings.pronouns_custom"}}</i></div> 54 + {{end}} 55 + </div> 50 56 </div> 51 - </div> 52 - <input id="pronouns-custom" name="pronouns" value="{{.SignedUser.Pronouns}}" maxlength="50"> 53 - </div> 54 - {{if not .SignedUser.KeepEmailPrivate}} 55 - <div class="field"> 56 - <label>{{ctx.Locale.Tr "email"}}</label> 57 - <p id="signed-user-email">{{.SignedUser.Email}}</p> 58 - </div> 59 - {{end}} 60 - <div class="field {{if .Err_Biography}}error{{end}}"> 61 - <label for="biography">{{ctx.Locale.Tr "user.user_bio"}}</label> 62 - <textarea id="biography" name="biography" rows="2" placeholder="{{ctx.Locale.Tr "settings.biography_placeholder"}}" maxlength="255">{{.SignedUser.Description}}</textarea> 63 - </div> 64 - <div class="field {{if .Err_Website}}error{{end}}"> 65 - <label for="website">{{ctx.Locale.Tr "settings.website"}}</label> 66 - <input id="website" name="website" type="url" value="{{.SignedUser.Website}}" maxlength="255"> 67 - </div> 68 - <div class="field"> 69 - <label for="location">{{ctx.Locale.Tr "settings.location"}}</label> 70 - <input id="location" name="location" placeholder="{{ctx.Locale.Tr "settings.location_placeholder"}}" value="{{.SignedUser.Location}}" maxlength="50"> 71 - </div> 57 + </label> 58 + <label id="label-pronouns-custom"> 59 + {{ctx.Locale.Tr "settings.pronouns_custom_label"}} 60 + <input name="pronouns" value="{{.SignedUser.Pronouns}}" maxlength="50"> 61 + </label> 62 + 63 + <label {{if .Err_Biography}}class="field error"{{end}}> 64 + {{ctx.Locale.Tr "user.user_bio"}} 65 + <textarea name="biography" rows="2" placeholder="{{ctx.Locale.Tr "settings.biography_placeholder"}}" maxlength="255">{{.SignedUser.Description}}</textarea> 66 + </label> 67 + 68 + <label {{if .Err_Website}}class="field error"{{end}}> 69 + {{ctx.Locale.Tr "settings.website"}} 70 + <input name="website" type="url" value="{{.SignedUser.Website}}" maxlength="255"> 71 + </label> 72 72 73 - <div class="divider"></div> 74 - <!-- private block --> 73 + <label> 74 + {{ctx.Locale.Tr "settings.location"}} 75 + <input name="location" placeholder="{{ctx.Locale.Tr "settings.location_placeholder"}}" value="{{.SignedUser.Location}}" maxlength="50"> 76 + </label> 77 + </fieldset> 75 78 76 - <div class="field" id="privacy-user-settings"> 77 - <label><strong>{{ctx.Locale.Tr "settings.privacy"}}</strong></label> 78 - </div> 79 + <fieldset> 80 + <legend id="privacy-user-settings">{{ctx.Locale.Tr "settings.privacy"}}</legend> 79 81 80 - <div class="inline field {{if .Err_Visibility}}error{{end}}"> 81 - <span class="inline required field" id="visibility-setting"><label>{{ctx.Locale.Tr "settings.visibility"}}</label></span> 82 - <div class="ui selection type dropdown"> 83 - {{if .SignedUser.Visibility.IsPublic}}<input type="hidden" id="visibility" name="visibility" value="0">{{end}} 84 - {{if .SignedUser.Visibility.IsLimited}}<input type="hidden" id="visibility" name="visibility" value="1">{{end}} 85 - {{if .SignedUser.Visibility.IsPrivate}}<input type="hidden" id="visibility" name="visibility" value="2">{{end}} 86 - <div class="text"> 87 - {{if .SignedUser.Visibility.IsPublic}}{{ctx.Locale.Tr "settings.visibility.public"}}{{end}} 88 - {{if .SignedUser.Visibility.IsLimited}}{{ctx.Locale.Tr "settings.visibility.limited"}}{{end}} 89 - {{if .SignedUser.Visibility.IsPrivate}}{{ctx.Locale.Tr "settings.visibility.private"}}{{end}} 90 - </div> 91 - {{svg "octicon-triangle-down" 14 "dropdown icon"}} 92 - <div class="menu"> 93 - {{range $mode := .AllowedUserVisibilityModes}} 94 - {{if $mode.IsPublic}} 95 - <div class="item" data-tooltip-content="{{ctx.Locale.Tr "settings.visibility.public_tooltip"}}" data-value="0">{{ctx.Locale.Tr "settings.visibility.public"}}</div> 96 - {{else if $mode.IsLimited}} 97 - <div class="item" data-tooltip-content="{{ctx.Locale.Tr "settings.visibility.limited_tooltip"}}" data-value="1">{{ctx.Locale.Tr "settings.visibility.limited"}}</div> 98 - {{else if $mode.IsPrivate}} 99 - <div class="item" data-tooltip-content="{{ctx.Locale.Tr "settings.visibility.private_tooltip"}}" data-value="2">{{ctx.Locale.Tr "settings.visibility.private"}}</div> 82 + <label id="visibility-setting" {{if .Err_Visibility}}class="field error"{{end}}> 83 + {{ctx.Locale.Tr "settings.visibility"}} 84 + <div class="ui selection type dropdown" aria-labelledby="visibility-setting"> 85 + {{if .SignedUser.Visibility.IsPublic}}<input type="hidden" id="visibility" name="visibility" value="0">{{end}} 86 + {{if .SignedUser.Visibility.IsLimited}}<input type="hidden" id="visibility" name="visibility" value="1">{{end}} 87 + {{if .SignedUser.Visibility.IsPrivate}}<input type="hidden" id="visibility" name="visibility" value="2">{{end}} 88 + <div class="text"> 89 + {{if .SignedUser.Visibility.IsPublic}}{{ctx.Locale.Tr "settings.visibility.public"}}{{end}} 90 + {{if .SignedUser.Visibility.IsLimited}}{{ctx.Locale.Tr "settings.visibility.limited"}}{{end}} 91 + {{if .SignedUser.Visibility.IsPrivate}}{{ctx.Locale.Tr "settings.visibility.private"}}{{end}} 92 + </div> 93 + {{svg "octicon-triangle-down" 14 "dropdown icon"}} 94 + <div class="menu"> 95 + {{range $mode := .AllowedUserVisibilityModes}} 96 + {{if $mode.IsPublic}} 97 + <div class="item" data-tooltip-content="{{ctx.Locale.Tr "settings.visibility.public_tooltip"}}" data-value="0">{{ctx.Locale.Tr "settings.visibility.public"}}</div> 98 + {{else if $mode.IsLimited}} 99 + <div class="item" data-tooltip-content="{{ctx.Locale.Tr "settings.visibility.limited_tooltip"}}" data-value="1">{{ctx.Locale.Tr "settings.visibility.limited"}}</div> 100 + {{else if $mode.IsPrivate}} 101 + <div class="item" data-tooltip-content="{{ctx.Locale.Tr "settings.visibility.private_tooltip"}}" data-value="2">{{ctx.Locale.Tr "settings.visibility.private"}}</div> 102 + {{end}} 100 103 {{end}} 101 - {{end}} 104 + </div> 102 105 </div> 103 - </div> 104 - </div> 106 + </label> 105 107 106 - <div class="field"> 107 - <div class="ui checkbox"> 108 - <label>{{ctx.Locale.Tr "settings.keep_email_private"}}</label> 108 + <label> 109 109 <input name="keep_email_private" type="checkbox" {{if .SignedUser.KeepEmailPrivate}}checked{{end}}> 110 - </div> 111 - <span class="help tw-block">{{ctx.Locale.Tr "settings.keep_email_private_popup" .SignedUser.GetPlaceholderEmail}}</span> 112 - </div> 110 + {{ctx.Locale.Tr "settings.keep_email_private"}} 111 + <span class="help">{{ctx.Locale.Tr "settings.keep_email_private_popup" .SignedUser.GetPlaceholderEmail}}</span> 112 + </label> 113 113 114 - <div class="field"> 115 - <div class="ui checkbox" id="keep-activity-private"> 116 - <label>{{ctx.Locale.Tr "settings.keep_activity_private"}}</label> 114 + <label id="keep-activity-private"> 117 115 <input name="keep_activity_private" type="checkbox" {{if .SignedUser.KeepActivityPrivate}}checked{{end}}> 118 - </div> 119 - <span class="help tw-block">{{ctx.Locale.Tr "settings.keep_activity_private.description" (printf "/%s?tab=activity" .SignedUser.Name)}}</span> 120 - </div> 121 - 122 - <div class="divider"></div> 116 + {{ctx.Locale.Tr "settings.keep_activity_private"}} 117 + <span class="help">{{ctx.Locale.Tr "settings.keep_activity_private.description" (printf "/%s?tab=activity" .SignedUser.Name)}}</span> 118 + </label> 119 + </fieldset> 123 120 124 - <div class="field"> 125 - <button class="ui primary button">{{ctx.Locale.Tr "settings.update_profile"}}</button> 126 - </div> 121 + <button class="ui primary button">{{ctx.Locale.Tr "settings.update_profile"}}</button> 127 122 </form> 128 123 </div> 129 124
+65
tests/e2e/user-settings.test.e2e.ts
··· 1 + // @watch start 2 + // templates/user/settings/**.tmpl 3 + // web_src/css/{form,user}.css 4 + // @watch end 5 + 6 + import {expect} from '@playwright/test'; 7 + import {test, save_visual, login_user, login} from './utils_e2e.ts'; 8 + import {validate_form} from './shared/forms.ts'; 9 + 10 + test.beforeAll(async ({browser}, workerInfo) => { 11 + await login_user(browser, workerInfo, 'user2'); 12 + }); 13 + 14 + test('User: Profile settings', async ({browser}, workerInfo) => { 15 + const page = await login({browser}, workerInfo); 16 + await page.goto('/user/settings'); 17 + 18 + await page.getByLabel('Full name').fill('SecondUser'); 19 + await page.locator('#pronouns-dropdown').click(); 20 + await page.getByRole('option', {name: 'she/her'}).click(); 21 + await page.getByPlaceholder('Tell others a little bit').fill('I am a playwright test running for several seconds.'); 22 + await page.getByPlaceholder('Tell others a little bit').press('Tab'); 23 + await page.getByLabel('Website').fill('https://forgejo.org'); 24 + await page.getByPlaceholder('Share your approximate').fill('on a computer chip'); 25 + await page.getByLabel('User visibility').click(); 26 + await page.getByLabel('Visible only to signed-in').click(); 27 + await page.getByLabel('Hide email address Your email').uncheck(); 28 + await page.getByLabel('Hide activity from profile').check(); 29 + 30 + await validate_form({page}, 'fieldset'); 31 + await save_visual(page); 32 + await page.getByRole('button', {name: 'Update profile'}).click(); 33 + await expect(page.getByText('Your profile has been updated.')).toBeVisible(); 34 + await page.getByRole('link', {name: 'public activity'}).click(); 35 + await expect(page.getByText('Your activity is only visible')).toBeVisible(); 36 + await save_visual(page); 37 + 38 + await page.goto('/user2'); 39 + await expect(page.getByText('SecondUser')).toBeVisible(); 40 + await expect(page.getByText('on a computer chip')).toBeVisible(); 41 + await expect(page.locator('li').filter({hasText: 'user2@example.com'})).toBeVisible(); 42 + await expect(page.locator('li').filter({hasText: 'https://forgejo.org'})).toBeVisible(); 43 + await expect(page.getByText('I am a playwright test')).toBeVisible(); 44 + await save_visual(page); 45 + 46 + await page.goto('/user/settings'); 47 + await page.locator('#pronouns-dropdown').click(); 48 + await page.getByRole('option', {name: 'Custom'}).click(); 49 + await page.getByLabel('Custom pronouns').fill('rob/ot'); 50 + await page.getByLabel('User visibility').click(); 51 + await page.getByLabel('Visible to everyone').click(); 52 + await page.getByLabel('Hide email address Your email').check(); 53 + await page.getByLabel('Hide activity from profile').uncheck(); 54 + await expect(page.getByText('Your profile has been updated.')).toBeHidden(); 55 + await validate_form({page}, 'fieldset'); 56 + await save_visual(page); 57 + await page.getByRole('button', {name: 'Update profile'}).click(); 58 + await expect(page.getByText('Your profile has been updated.')).toBeVisible(); 59 + 60 + await page.goto('/user2'); 61 + await expect(page.getByText('SecondUser')).toBeVisible(); 62 + await expect(page.locator('li').filter({hasText: 'user2@example.com'})).toBeHidden(); 63 + await page.goto('/user2?tab=activity'); 64 + await expect(page.getByText('Your activity is visible to everyone')).toBeVisible(); 65 + });
+1
tests/e2e/utils_e2e.ts
··· 105 105 page.locator('#repo_migrating'), 106 106 // update order of recently created repos is not fully deterministic 107 107 page.locator('.flex-item-main').filter({hasText: 'relative time in repo'}), 108 + page.locator('#activity-feed'), 108 109 // dynamic IDs in fixed-size inputs 109 110 page.locator('input[value*="dyn-id-"]'), 110 111 ],
-1
tests/integration/auth_ldap_test.go
··· 185 185 186 186 assert.Equal(t, u.UserName, htmlDoc.GetInputValueByName("name")) 187 187 assert.Equal(t, u.FullName, htmlDoc.GetInputValueByName("full_name")) 188 - assert.Equal(t, u.Email, htmlDoc.Find("#signed-user-email").Text()) 189 188 } 190 189 191 190 func TestLDAPAuthChange(t *testing.T) {
-20
tests/integration/setting_test.go
··· 157 157 assert.Contains(t, resp.Body.String(), `gitlab-active`) 158 158 assert.Contains(t, resp.Body.String(), `gitlab-inactive`) 159 159 } 160 - 161 - func TestSettingShowUserEmailSettings(t *testing.T) { 162 - defer tests.PrepareTestEnv(t)() 163 - 164 - // user1: keep_email_private = false, user2: keep_email_private = true 165 - 166 - // user1 can see own visible email 167 - session := loginUser(t, "user1") 168 - req := NewRequest(t, "GET", "/user/settings") 169 - resp := session.MakeRequest(t, req, http.StatusOK) 170 - htmlDoc := NewHTMLParser(t, resp.Body) 171 - assert.Contains(t, htmlDoc.doc.Find("#signed-user-email").Text(), "user1@example.com") 172 - 173 - // user2 cannot see own hidden email 174 - session = loginUser(t, "user2") 175 - req = NewRequest(t, "GET", "/user/settings") 176 - resp = session.MakeRequest(t, req, http.StatusOK) 177 - htmlDoc = NewHTMLParser(t, resp.Body) 178 - assert.NotContains(t, htmlDoc.doc.Find("#signed-user-email").Text(), "user2@example.com") 179 - }
+2 -11
tests/integration/user_profile_activity_test.go
··· 42 42 43 43 // Verify the hint for all types of users: admin, self, guest 44 44 testUser2ActivityVisibility(t, userAdmin, "This activity is visible to everyone, but as an administrator you can also see interactions in private spaces.", true) 45 - hintLink := testUser2ActivityVisibility(t, userRegular, "Your activity is visible to everyone, except for interactions in private spaces. Configure.", true) 45 + testUser2ActivityVisibility(t, userRegular, "Your activity is visible to everyone, except for interactions in private spaces. Configure.", true) 46 46 testUser2ActivityVisibility(t, userGuest, "", true) 47 47 48 - // When viewing own profile, the user is offered to configure activity visibility. Verify that the link is correct and works, also check that it links back to the activity tab. 49 - linkCorrect := assert.EqualValues(t, "/user/settings#keep-activity-private", hintLink) 50 - if linkCorrect { 51 - page := NewHTMLParser(t, userRegular.MakeRequest(t, NewRequest(t, "GET", hintLink), http.StatusOK).Body) 52 - activityLink, exists := page.Find(".field:has(.checkbox#keep-activity-private) .help a").Attr("href") 53 - assert.True(t, exists) 54 - assert.EqualValues(t, "/user2?tab=activity", activityLink) 55 - } 56 - 57 48 // = Private profile, but public activity = 58 49 59 50 // Set profile visibility of user2 to private 60 51 testChangeUserProfileVisibility(t, userRegular, structs.VisibleTypePrivate) 61 52 62 53 // When profile activity is configured as public, but the profile is private, tell the user about this and link to visibility settings. 63 - hintLink = testUser2ActivityVisibility(t, userRegular, "Your activity is only visible to you and the instance administrators because your profile is private. Configure.", true) 54 + hintLink := testUser2ActivityVisibility(t, userRegular, "Your activity is only visible to you and the instance administrators because your profile is private. Configure.", true) 64 55 assert.EqualValues(t, "/user/settings#visibility-setting", hintLink) 65 56 66 57 // When the profile is private, tell the admin about this.
+7 -1
web_src/css/form.css
··· 1 1 fieldset { 2 - margin: 0.5em 0 1em; 2 + margin: 0.2em 0 0.3em; 3 3 padding: 0; 4 4 } 5 5 6 6 fieldset legend { 7 7 font-weight: var(--font-weight-medium); 8 8 margin-bottom: 0.75em; 9 + } 10 + 11 + fieldset + fieldset > legend { 12 + width: 100%; 13 + padding-top: 1em; 14 + border-top: 1px solid var(--color-secondary); 9 15 } 10 16 11 17 fieldset label {
-4
web_src/css/user.css
··· 143 143 .notifications-item:hover .notifications-updated { 144 144 display: none; 145 145 } 146 - 147 - #pronouns-dropdown, #pronouns-custom { 148 - width: 140px; 149 - }
+13 -26
web_src/js/features/user-settings.js
··· 1 1 import {hideElem, showElem} from '../utils/dom.js'; 2 2 3 3 function onPronounsDropdownUpdate() { 4 - const pronounsCustom = document.getElementById('pronouns-custom'); 4 + const pronounsCustom = document.getElementById('label-pronouns-custom'); 5 + const pronounsCustomInput = pronounsCustom.querySelector('input'); 5 6 const pronounsDropdown = document.getElementById('pronouns-dropdown'); 6 7 const pronounsInput = pronounsDropdown.querySelector('input'); 7 8 // must be kept in sync with `routers/web/user/setting/profile.go` ··· 15 16 ); 16 17 if (isCustom) { 17 18 if (pronounsInput.value === '!') { 18 - pronounsCustom.value = ''; 19 + pronounsCustomInput.value = ''; 19 20 } else { 20 - pronounsCustom.value = pronounsInput.value; 21 + pronounsCustomInput.value = pronounsInput.value; 21 22 } 22 - pronounsCustom.style.display = ''; 23 + showElem(pronounsCustom); 23 24 } else { 24 - pronounsCustom.style.display = 'none'; 25 + hideElem(pronounsCustom); 25 26 } 26 27 } 27 28 function onPronounsCustomUpdate() { 28 - const pronounsCustom = document.getElementById('pronouns-custom'); 29 + const pronounsCustomInput = document.querySelector('#label-pronouns-custom input'); 29 30 const pronounsInput = document.querySelector('#pronouns-dropdown input'); 30 - pronounsInput.value = pronounsCustom.value; 31 + pronounsInput.value = pronounsCustomInput.value; 31 32 } 32 33 33 34 export function initUserSettings() { 34 35 if (!document.querySelectorAll('.user.settings.profile').length) return; 35 36 36 - const usernameInput = document.getElementById('username'); 37 - if (!usernameInput) return; 38 - usernameInput.addEventListener('input', function () { 39 - const prompt = document.getElementById('name-change-prompt'); 40 - const promptRedirect = document.getElementById('name-change-redirect-prompt'); 41 - if (this.value.toLowerCase() !== this.getAttribute('data-name').toLowerCase()) { 42 - showElem(prompt); 43 - showElem(promptRedirect); 44 - } else { 45 - hideElem(prompt); 46 - hideElem(promptRedirect); 47 - } 48 - }); 49 - 50 - const pronounsDropdown = document.getElementById('pronouns-dropdown'); 51 - const pronounsCustom = document.getElementById('pronouns-custom'); 37 + const pronounsDropdown = document.getElementById('label-pronouns'); 38 + const pronounsCustomInput = document.querySelector('#label-pronouns-custom input'); 52 39 const pronounsInput = pronounsDropdown.querySelector('input'); 53 40 54 41 // If JS is disabled, the page will show the custom input, as the dropdown requires JS to work. 55 42 // JS progressively enhances the input by adding a dropdown, but it works regardless. 56 - pronounsCustom.removeAttribute('name'); 43 + pronounsCustomInput.removeAttribute('name'); 57 44 pronounsInput.setAttribute('name', 'pronouns'); 58 - pronounsDropdown.style.display = ''; 45 + showElem(pronounsDropdown); 59 46 60 47 onPronounsDropdownUpdate(); 61 48 pronounsInput.addEventListener('change', onPronounsDropdownUpdate); 62 - pronounsCustom.addEventListener('input', onPronounsCustomUpdate); 49 + pronounsCustomInput.addEventListener('input', onPronounsCustomUpdate); 63 50 }