mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-01-10 23:52:16 +01:00
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)
This commit is contained in:
parent
de4053db83
commit
2885ea8da2
11 changed files with 195 additions and 174 deletions
|
@ -748,13 +748,14 @@ blocked_users = Blocked users
|
||||||
public_profile = Public profile
|
public_profile = Public profile
|
||||||
biography_placeholder = Tell others a little bit about yourself! (Markdown is supported)
|
biography_placeholder = Tell others a little bit about yourself! (Markdown is supported)
|
||||||
location_placeholder = Share your approximate location with others
|
location_placeholder = Share your approximate location with others
|
||||||
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.
|
profile_desc = About you
|
||||||
password_username_disabled = Non-local users are not allowed to change their username. Please contact your site administrator for more details.
|
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
|
full_name = Full name
|
||||||
website = Website
|
website = Website
|
||||||
location = Location
|
location = Location
|
||||||
pronouns = Pronouns
|
pronouns = Pronouns
|
||||||
pronouns_custom = Custom
|
pronouns_custom = Custom
|
||||||
|
pronouns_custom_label = Custom pronouns
|
||||||
pronouns_unspecified = Unspecified
|
pronouns_unspecified = Unspecified
|
||||||
update_theme = Change theme
|
update_theme = Change theme
|
||||||
update_profile = Update profile
|
update_profile = Update profile
|
||||||
|
@ -849,7 +850,7 @@ add_email_success = The new email address has been added.
|
||||||
email_preference_set_success = Email preference has been set successfully.
|
email_preference_set_success = Email preference has been set successfully.
|
||||||
add_openid_success = The new OpenID address has been added.
|
add_openid_success = The new OpenID address has been added.
|
||||||
keep_email_private = Hide email address
|
keep_email_private = Hide email address
|
||||||
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.
|
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.
|
||||||
openid_desc = OpenID lets you delegate authentication to an external provider.
|
openid_desc = OpenID lets you delegate authentication to an external provider.
|
||||||
|
|
||||||
manage_ssh_keys = Manage SSH keys
|
manage_ssh_keys = Manage SSH keys
|
||||||
|
|
|
@ -40,9 +40,9 @@
|
||||||
</h4>
|
</h4>
|
||||||
<div class="ui attached segment">
|
<div class="ui attached segment">
|
||||||
<div class="ui list">
|
<div class="ui list">
|
||||||
|
<div class="tw-mb-2">{{ctx.Locale.Tr "settings.email_desc"}}</div>
|
||||||
{{if $.EnableNotifyMail}}
|
{{if $.EnableNotifyMail}}
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<div class="tw-mb-2">{{ctx.Locale.Tr "settings.email_desc"}}</div>
|
|
||||||
<form action="{{AppSubUrl}}/user/settings/account/email" class="ui form" method="post">
|
<form action="{{AppSubUrl}}/user/settings/account/email" class="ui form" method="post">
|
||||||
{{$.CsrfTokenHtml}}
|
{{$.CsrfTokenHtml}}
|
||||||
<input name="_method" type="hidden" value="NOTIFICATION">
|
<input name="_method" type="hidden" value="NOTIFICATION">
|
||||||
|
|
|
@ -4,126 +4,121 @@
|
||||||
{{ctx.Locale.Tr "settings.public_profile"}}
|
{{ctx.Locale.Tr "settings.public_profile"}}
|
||||||
</h4>
|
</h4>
|
||||||
<div class="ui attached segment">
|
<div class="ui attached segment">
|
||||||
<p>{{ctx.Locale.Tr "settings.profile_desc"}}</p>
|
|
||||||
<form class="ui form" action="{{.Link}}" method="post">
|
<form class="ui form" action="{{.Link}}" method="post">
|
||||||
{{.CsrfTokenHtml}}
|
{{.CsrfTokenHtml}}
|
||||||
<div class="required field {{if .Err_Name}}error{{end}}">
|
<fieldset>
|
||||||
<label for="username">{{ctx.Locale.Tr "username"}}
|
<legend>{{ctx.Locale.Tr "settings.profile_desc"}}</legend>
|
||||||
<span class="text red tw-hidden" id="name-change-prompt"> {{ctx.Locale.Tr "settings.change_username_prompt"}}</span>
|
<label {{if .Err_Name}}class="field error"{{end}}>
|
||||||
<span class="text red tw-hidden" id="name-change-redirect-prompt"> {{ctx.Locale.Tr "settings.change_username_redirect_prompt"}}</span>
|
{{ctx.Locale.Tr "username"}}
|
||||||
|
<input name="name" value="{{.SignedUser.Name}}" data-name="{{.SignedUser.Name}}" autofocus required {{if or (not .SignedUser.IsLocal) .IsReverseProxy}}disabled{{end}} maxlength="40">
|
||||||
|
{{if or (not .SignedUser.IsLocal) .IsReverseProxy}}
|
||||||
|
<span class="help">{{ctx.Locale.Tr "settings.password_username_disabled"}}</span>
|
||||||
|
{{else}}
|
||||||
|
<span class="help">
|
||||||
|
{{ctx.Locale.Tr "settings.change_username_prompt"}}
|
||||||
|
{{ctx.Locale.Tr "settings.change_username_redirect_prompt"}}
|
||||||
|
</span>
|
||||||
|
{{end}}
|
||||||
</label>
|
</label>
|
||||||
<input id="username" name="name" value="{{.SignedUser.Name}}" data-name="{{.SignedUser.Name}}" autofocus required {{if or (not .SignedUser.IsLocal) .IsReverseProxy}}disabled{{end}} maxlength="40">
|
|
||||||
{{if or (not .SignedUser.IsLocal) .IsReverseProxy}}
|
|
||||||
<p class="help text blue">{{ctx.Locale.Tr "settings.password_username_disabled"}}</p>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
<div class="field {{if .Err_FullName}}error{{end}}">
|
|
||||||
<label for="full_name">{{ctx.Locale.Tr "settings.full_name"}}</label>
|
|
||||||
<input id="full_name" name="full_name" value="{{.SignedUser.FullName}}" maxlength="100">
|
|
||||||
</div>
|
|
||||||
<div class="inline field">
|
|
||||||
<span class="inline field"><label for="pronouns">{{ctx.Locale.Tr "settings.pronouns"}}</label></span>
|
|
||||||
<div id="pronouns-dropdown" style="display: none" class="ui selection dropdown">
|
|
||||||
<input type="hidden" value="{{.SignedUser.Pronouns}}">
|
|
||||||
<div class="text">
|
|
||||||
{{if .PronounsAreCustom}}
|
|
||||||
{{ctx.Locale.Tr "settings.pronouns_custom"}}
|
|
||||||
{{else if eq "" .SignedUser.Pronouns}}
|
|
||||||
{{ctx.Locale.Tr "settings.pronouns_unspecified"}}
|
|
||||||
{{else}}
|
|
||||||
{{.SignedUser.Pronouns}}
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
|
||||||
<div class="menu">
|
|
||||||
<div class="item{{if eq "" .SignedUser.Pronouns}} active selected{{end}}" data-value=""><p>{{ctx.Locale.Tr "settings.pronouns_unspecified"}}</p></div>
|
|
||||||
<div class="item{{if eq "he/him" .SignedUser.Pronouns}} active selected{{end}}" data-value="he/him">he/him</div>
|
|
||||||
<div class="item{{if eq "she/her" .SignedUser.Pronouns}} active selected{{end}}" data-value="she/her">she/her</div>
|
|
||||||
<div class="item{{if eq "they/them" .SignedUser.Pronouns}} active selected{{end}}" data-value="they/them">they/them</div>
|
|
||||||
<div class="item{{if eq "it/its" .SignedUser.Pronouns}} active selected{{end}}" data-value="it/its">it/its</div>
|
|
||||||
<div class="item{{if eq "any pronouns" .SignedUser.Pronouns}} active selected{{end}}" data-value="any pronouns">any pronouns</div>
|
|
||||||
{{if .PronounsAreCustom}}
|
|
||||||
<div class="item active selected" data-value="{{.SignedUser.Pronouns}}"><p>{{ctx.Locale.Tr "settings.pronouns_custom"}}</p></div>
|
|
||||||
{{else}}
|
|
||||||
<div class="item" data-value="!"><i>{{ctx.Locale.Tr "settings.pronouns_custom"}}</i></div>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input id="pronouns-custom" name="pronouns" value="{{.SignedUser.Pronouns}}" maxlength="50">
|
|
||||||
</div>
|
|
||||||
{{if not .SignedUser.KeepEmailPrivate}}
|
|
||||||
<div class="field">
|
|
||||||
<label>{{ctx.Locale.Tr "email"}}</label>
|
|
||||||
<p id="signed-user-email">{{.SignedUser.Email}}</p>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
<div class="field {{if .Err_Biography}}error{{end}}">
|
|
||||||
<label for="biography">{{ctx.Locale.Tr "user.user_bio"}}</label>
|
|
||||||
<textarea id="biography" name="biography" rows="2" placeholder="{{ctx.Locale.Tr "settings.biography_placeholder"}}" maxlength="255">{{.SignedUser.Description}}</textarea>
|
|
||||||
</div>
|
|
||||||
<div class="field {{if .Err_Website}}error{{end}}">
|
|
||||||
<label for="website">{{ctx.Locale.Tr "settings.website"}}</label>
|
|
||||||
<input id="website" name="website" type="url" value="{{.SignedUser.Website}}" maxlength="255">
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label for="location">{{ctx.Locale.Tr "settings.location"}}</label>
|
|
||||||
<input id="location" name="location" placeholder="{{ctx.Locale.Tr "settings.location_placeholder"}}" value="{{.SignedUser.Location}}" maxlength="50">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="divider"></div>
|
<label {{if .Err_FullName}}class="field error"{{end}}>
|
||||||
<!-- private block -->
|
{{ctx.Locale.Tr "settings.full_name"}}
|
||||||
|
<input name="full_name" value="{{.SignedUser.FullName}}" maxlength="100">
|
||||||
|
</label>
|
||||||
|
|
||||||
<div class="field" id="privacy-user-settings">
|
<label id="label-pronouns" class="tw-hidden">
|
||||||
<label><strong>{{ctx.Locale.Tr "settings.privacy"}}</strong></label>
|
{{ctx.Locale.Tr "settings.pronouns"}}
|
||||||
</div>
|
<div id="pronouns-dropdown" class="ui selection dropdown" aria-labelledby="label-pronouns">
|
||||||
|
<input type="hidden" value="{{.SignedUser.Pronouns}}">
|
||||||
<div class="inline field {{if .Err_Visibility}}error{{end}}">
|
<div class="text">
|
||||||
<span class="inline required field" id="visibility-setting"><label>{{ctx.Locale.Tr "settings.visibility"}}</label></span>
|
{{if .PronounsAreCustom}}
|
||||||
<div class="ui selection type dropdown">
|
{{ctx.Locale.Tr "settings.pronouns_custom"}}
|
||||||
{{if .SignedUser.Visibility.IsPublic}}<input type="hidden" id="visibility" name="visibility" value="0">{{end}}
|
{{else if eq "" .SignedUser.Pronouns}}
|
||||||
{{if .SignedUser.Visibility.IsLimited}}<input type="hidden" id="visibility" name="visibility" value="1">{{end}}
|
{{ctx.Locale.Tr "settings.pronouns_unspecified"}}
|
||||||
{{if .SignedUser.Visibility.IsPrivate}}<input type="hidden" id="visibility" name="visibility" value="2">{{end}}
|
{{else}}
|
||||||
<div class="text">
|
{{.SignedUser.Pronouns}}
|
||||||
{{if .SignedUser.Visibility.IsPublic}}{{ctx.Locale.Tr "settings.visibility.public"}}{{end}}
|
|
||||||
{{if .SignedUser.Visibility.IsLimited}}{{ctx.Locale.Tr "settings.visibility.limited"}}{{end}}
|
|
||||||
{{if .SignedUser.Visibility.IsPrivate}}{{ctx.Locale.Tr "settings.visibility.private"}}{{end}}
|
|
||||||
</div>
|
|
||||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
|
||||||
<div class="menu">
|
|
||||||
{{range $mode := .AllowedUserVisibilityModes}}
|
|
||||||
{{if $mode.IsPublic}}
|
|
||||||
<div class="item" data-tooltip-content="{{ctx.Locale.Tr "settings.visibility.public_tooltip"}}" data-value="0">{{ctx.Locale.Tr "settings.visibility.public"}}</div>
|
|
||||||
{{else if $mode.IsLimited}}
|
|
||||||
<div class="item" data-tooltip-content="{{ctx.Locale.Tr "settings.visibility.limited_tooltip"}}" data-value="1">{{ctx.Locale.Tr "settings.visibility.limited"}}</div>
|
|
||||||
{{else if $mode.IsPrivate}}
|
|
||||||
<div class="item" data-tooltip-content="{{ctx.Locale.Tr "settings.visibility.private_tooltip"}}" data-value="2">{{ctx.Locale.Tr "settings.visibility.private"}}</div>
|
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
</div>
|
||||||
|
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||||
|
<div class="menu">
|
||||||
|
<div class="item{{if eq "" .SignedUser.Pronouns}} active selected{{end}}" data-value=""><p>{{ctx.Locale.Tr "settings.pronouns_unspecified"}}</p></div>
|
||||||
|
<div class="item{{if eq "he/him" .SignedUser.Pronouns}} active selected{{end}}" data-value="he/him">he/him</div>
|
||||||
|
<div class="item{{if eq "she/her" .SignedUser.Pronouns}} active selected{{end}}" data-value="she/her">she/her</div>
|
||||||
|
<div class="item{{if eq "they/them" .SignedUser.Pronouns}} active selected{{end}}" data-value="they/them">they/them</div>
|
||||||
|
<div class="item{{if eq "it/its" .SignedUser.Pronouns}} active selected{{end}}" data-value="it/its">it/its</div>
|
||||||
|
<div class="item{{if eq "any pronouns" .SignedUser.Pronouns}} active selected{{end}}" data-value="any pronouns">any pronouns</div>
|
||||||
|
{{if .PronounsAreCustom}}
|
||||||
|
<div class="item active selected" data-value="{{.SignedUser.Pronouns}}">{{ctx.Locale.Tr "settings.pronouns_custom"}}</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="item" data-value="!"><i>{{ctx.Locale.Tr "settings.pronouns_custom"}}</i></div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</label>
|
||||||
</div>
|
<label id="label-pronouns-custom">
|
||||||
|
{{ctx.Locale.Tr "settings.pronouns_custom_label"}}
|
||||||
|
<input name="pronouns" value="{{.SignedUser.Pronouns}}" maxlength="50">
|
||||||
|
</label>
|
||||||
|
|
||||||
<div class="field">
|
<label {{if .Err_Biography}}class="field error"{{end}}>
|
||||||
<div class="ui checkbox">
|
{{ctx.Locale.Tr "user.user_bio"}}
|
||||||
<label>{{ctx.Locale.Tr "settings.keep_email_private"}}</label>
|
<textarea name="biography" rows="2" placeholder="{{ctx.Locale.Tr "settings.biography_placeholder"}}" maxlength="255">{{.SignedUser.Description}}</textarea>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label {{if .Err_Website}}class="field error"{{end}}>
|
||||||
|
{{ctx.Locale.Tr "settings.website"}}
|
||||||
|
<input name="website" type="url" value="{{.SignedUser.Website}}" maxlength="255">
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
{{ctx.Locale.Tr "settings.location"}}
|
||||||
|
<input name="location" placeholder="{{ctx.Locale.Tr "settings.location_placeholder"}}" value="{{.SignedUser.Location}}" maxlength="50">
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend id="privacy-user-settings">{{ctx.Locale.Tr "settings.privacy"}}</legend>
|
||||||
|
|
||||||
|
<label id="visibility-setting" {{if .Err_Visibility}}class="field error"{{end}}>
|
||||||
|
{{ctx.Locale.Tr "settings.visibility"}}
|
||||||
|
<div class="ui selection type dropdown" aria-labelledby="visibility-setting">
|
||||||
|
{{if .SignedUser.Visibility.IsPublic}}<input type="hidden" id="visibility" name="visibility" value="0">{{end}}
|
||||||
|
{{if .SignedUser.Visibility.IsLimited}}<input type="hidden" id="visibility" name="visibility" value="1">{{end}}
|
||||||
|
{{if .SignedUser.Visibility.IsPrivate}}<input type="hidden" id="visibility" name="visibility" value="2">{{end}}
|
||||||
|
<div class="text">
|
||||||
|
{{if .SignedUser.Visibility.IsPublic}}{{ctx.Locale.Tr "settings.visibility.public"}}{{end}}
|
||||||
|
{{if .SignedUser.Visibility.IsLimited}}{{ctx.Locale.Tr "settings.visibility.limited"}}{{end}}
|
||||||
|
{{if .SignedUser.Visibility.IsPrivate}}{{ctx.Locale.Tr "settings.visibility.private"}}{{end}}
|
||||||
|
</div>
|
||||||
|
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||||
|
<div class="menu">
|
||||||
|
{{range $mode := .AllowedUserVisibilityModes}}
|
||||||
|
{{if $mode.IsPublic}}
|
||||||
|
<div class="item" data-tooltip-content="{{ctx.Locale.Tr "settings.visibility.public_tooltip"}}" data-value="0">{{ctx.Locale.Tr "settings.visibility.public"}}</div>
|
||||||
|
{{else if $mode.IsLimited}}
|
||||||
|
<div class="item" data-tooltip-content="{{ctx.Locale.Tr "settings.visibility.limited_tooltip"}}" data-value="1">{{ctx.Locale.Tr "settings.visibility.limited"}}</div>
|
||||||
|
{{else if $mode.IsPrivate}}
|
||||||
|
<div class="item" data-tooltip-content="{{ctx.Locale.Tr "settings.visibility.private_tooltip"}}" data-value="2">{{ctx.Locale.Tr "settings.visibility.private"}}</div>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
<input name="keep_email_private" type="checkbox" {{if .SignedUser.KeepEmailPrivate}}checked{{end}}>
|
<input name="keep_email_private" type="checkbox" {{if .SignedUser.KeepEmailPrivate}}checked{{end}}>
|
||||||
</div>
|
{{ctx.Locale.Tr "settings.keep_email_private"}}
|
||||||
<span class="help tw-block">{{ctx.Locale.Tr "settings.keep_email_private_popup" .SignedUser.GetPlaceholderEmail}}</span>
|
<span class="help">{{ctx.Locale.Tr "settings.keep_email_private_popup" .SignedUser.GetPlaceholderEmail}}</span>
|
||||||
</div>
|
</label>
|
||||||
|
|
||||||
<div class="field">
|
<label id="keep-activity-private">
|
||||||
<div class="ui checkbox" id="keep-activity-private">
|
|
||||||
<label>{{ctx.Locale.Tr "settings.keep_activity_private"}}</label>
|
|
||||||
<input name="keep_activity_private" type="checkbox" {{if .SignedUser.KeepActivityPrivate}}checked{{end}}>
|
<input name="keep_activity_private" type="checkbox" {{if .SignedUser.KeepActivityPrivate}}checked{{end}}>
|
||||||
</div>
|
{{ctx.Locale.Tr "settings.keep_activity_private"}}
|
||||||
<span class="help tw-block">{{ctx.Locale.Tr "settings.keep_activity_private.description" (printf "/%s?tab=activity" .SignedUser.Name)}}</span>
|
<span class="help">{{ctx.Locale.Tr "settings.keep_activity_private.description" (printf "/%s?tab=activity" .SignedUser.Name)}}</span>
|
||||||
</div>
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
<div class="divider"></div>
|
<button class="ui primary button">{{ctx.Locale.Tr "settings.update_profile"}}</button>
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<button class="ui primary button">{{ctx.Locale.Tr "settings.update_profile"}}</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
65
tests/e2e/user-settings.test.e2e.ts
Normal file
65
tests/e2e/user-settings.test.e2e.ts
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// @watch start
|
||||||
|
// templates/user/settings/**.tmpl
|
||||||
|
// web_src/css/{form,user}.css
|
||||||
|
// @watch end
|
||||||
|
|
||||||
|
import {expect} from '@playwright/test';
|
||||||
|
import {test, save_visual, login_user, login} from './utils_e2e.ts';
|
||||||
|
import {validate_form} from './shared/forms.ts';
|
||||||
|
|
||||||
|
test.beforeAll(async ({browser}, workerInfo) => {
|
||||||
|
await login_user(browser, workerInfo, 'user2');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('User: Profile settings', async ({browser}, workerInfo) => {
|
||||||
|
const page = await login({browser}, workerInfo);
|
||||||
|
await page.goto('/user/settings');
|
||||||
|
|
||||||
|
await page.getByLabel('Full name').fill('SecondUser');
|
||||||
|
await page.locator('#pronouns-dropdown').click();
|
||||||
|
await page.getByRole('option', {name: 'she/her'}).click();
|
||||||
|
await page.getByPlaceholder('Tell others a little bit').fill('I am a playwright test running for several seconds.');
|
||||||
|
await page.getByPlaceholder('Tell others a little bit').press('Tab');
|
||||||
|
await page.getByLabel('Website').fill('https://forgejo.org');
|
||||||
|
await page.getByPlaceholder('Share your approximate').fill('on a computer chip');
|
||||||
|
await page.getByLabel('User visibility').click();
|
||||||
|
await page.getByLabel('Visible only to signed-in').click();
|
||||||
|
await page.getByLabel('Hide email address Your email').uncheck();
|
||||||
|
await page.getByLabel('Hide activity from profile').check();
|
||||||
|
|
||||||
|
await validate_form({page}, 'fieldset');
|
||||||
|
await save_visual(page);
|
||||||
|
await page.getByRole('button', {name: 'Update profile'}).click();
|
||||||
|
await expect(page.getByText('Your profile has been updated.')).toBeVisible();
|
||||||
|
await page.getByRole('link', {name: 'public activity'}).click();
|
||||||
|
await expect(page.getByText('Your activity is only visible')).toBeVisible();
|
||||||
|
await save_visual(page);
|
||||||
|
|
||||||
|
await page.goto('/user2');
|
||||||
|
await expect(page.getByText('SecondUser')).toBeVisible();
|
||||||
|
await expect(page.getByText('on a computer chip')).toBeVisible();
|
||||||
|
await expect(page.locator('li').filter({hasText: 'user2@example.com'})).toBeVisible();
|
||||||
|
await expect(page.locator('li').filter({hasText: 'https://forgejo.org'})).toBeVisible();
|
||||||
|
await expect(page.getByText('I am a playwright test')).toBeVisible();
|
||||||
|
await save_visual(page);
|
||||||
|
|
||||||
|
await page.goto('/user/settings');
|
||||||
|
await page.locator('#pronouns-dropdown').click();
|
||||||
|
await page.getByRole('option', {name: 'Custom'}).click();
|
||||||
|
await page.getByLabel('Custom pronouns').fill('rob/ot');
|
||||||
|
await page.getByLabel('User visibility').click();
|
||||||
|
await page.getByLabel('Visible to everyone').click();
|
||||||
|
await page.getByLabel('Hide email address Your email').check();
|
||||||
|
await page.getByLabel('Hide activity from profile').uncheck();
|
||||||
|
await expect(page.getByText('Your profile has been updated.')).toBeHidden();
|
||||||
|
await validate_form({page}, 'fieldset');
|
||||||
|
await save_visual(page);
|
||||||
|
await page.getByRole('button', {name: 'Update profile'}).click();
|
||||||
|
await expect(page.getByText('Your profile has been updated.')).toBeVisible();
|
||||||
|
|
||||||
|
await page.goto('/user2');
|
||||||
|
await expect(page.getByText('SecondUser')).toBeVisible();
|
||||||
|
await expect(page.locator('li').filter({hasText: 'user2@example.com'})).toBeHidden();
|
||||||
|
await page.goto('/user2?tab=activity');
|
||||||
|
await expect(page.getByText('Your activity is visible to everyone')).toBeVisible();
|
||||||
|
});
|
|
@ -105,6 +105,7 @@ export async function save_visual(page: Page) {
|
||||||
page.locator('#repo_migrating'),
|
page.locator('#repo_migrating'),
|
||||||
// update order of recently created repos is not fully deterministic
|
// update order of recently created repos is not fully deterministic
|
||||||
page.locator('.flex-item-main').filter({hasText: 'relative time in repo'}),
|
page.locator('.flex-item-main').filter({hasText: 'relative time in repo'}),
|
||||||
|
page.locator('#activity-feed'),
|
||||||
// dynamic IDs in fixed-size inputs
|
// dynamic IDs in fixed-size inputs
|
||||||
page.locator('input[value*="dyn-id-"]'),
|
page.locator('input[value*="dyn-id-"]'),
|
||||||
],
|
],
|
||||||
|
|
|
@ -185,7 +185,6 @@ func TestLDAPUserSignin(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, u.UserName, htmlDoc.GetInputValueByName("name"))
|
assert.Equal(t, u.UserName, htmlDoc.GetInputValueByName("name"))
|
||||||
assert.Equal(t, u.FullName, htmlDoc.GetInputValueByName("full_name"))
|
assert.Equal(t, u.FullName, htmlDoc.GetInputValueByName("full_name"))
|
||||||
assert.Equal(t, u.Email, htmlDoc.Find("#signed-user-email").Text())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLDAPAuthChange(t *testing.T) {
|
func TestLDAPAuthChange(t *testing.T) {
|
||||||
|
|
|
@ -157,23 +157,3 @@ func TestSettingSecurityAuthSource(t *testing.T) {
|
||||||
assert.Contains(t, resp.Body.String(), `gitlab-active`)
|
assert.Contains(t, resp.Body.String(), `gitlab-active`)
|
||||||
assert.Contains(t, resp.Body.String(), `gitlab-inactive`)
|
assert.Contains(t, resp.Body.String(), `gitlab-inactive`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSettingShowUserEmailSettings(t *testing.T) {
|
|
||||||
defer tests.PrepareTestEnv(t)()
|
|
||||||
|
|
||||||
// user1: keep_email_private = false, user2: keep_email_private = true
|
|
||||||
|
|
||||||
// user1 can see own visible email
|
|
||||||
session := loginUser(t, "user1")
|
|
||||||
req := NewRequest(t, "GET", "/user/settings")
|
|
||||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
||||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
|
||||||
assert.Contains(t, htmlDoc.doc.Find("#signed-user-email").Text(), "user1@example.com")
|
|
||||||
|
|
||||||
// user2 cannot see own hidden email
|
|
||||||
session = loginUser(t, "user2")
|
|
||||||
req = NewRequest(t, "GET", "/user/settings")
|
|
||||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
|
||||||
htmlDoc = NewHTMLParser(t, resp.Body)
|
|
||||||
assert.NotContains(t, htmlDoc.doc.Find("#signed-user-email").Text(), "user2@example.com")
|
|
||||||
}
|
|
||||||
|
|
|
@ -42,25 +42,16 @@ func TestUserProfileActivity(t *testing.T) {
|
||||||
|
|
||||||
// Verify the hint for all types of users: admin, self, guest
|
// Verify the hint for all types of users: admin, self, guest
|
||||||
testUser2ActivityVisibility(t, userAdmin, "This activity is visible to everyone, but as an administrator you can also see interactions in private spaces.", true)
|
testUser2ActivityVisibility(t, userAdmin, "This activity is visible to everyone, but as an administrator you can also see interactions in private spaces.", true)
|
||||||
hintLink := testUser2ActivityVisibility(t, userRegular, "Your activity is visible to everyone, except for interactions in private spaces. Configure.", true)
|
testUser2ActivityVisibility(t, userRegular, "Your activity is visible to everyone, except for interactions in private spaces. Configure.", true)
|
||||||
testUser2ActivityVisibility(t, userGuest, "", true)
|
testUser2ActivityVisibility(t, userGuest, "", true)
|
||||||
|
|
||||||
// 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.
|
|
||||||
linkCorrect := assert.EqualValues(t, "/user/settings#keep-activity-private", hintLink)
|
|
||||||
if linkCorrect {
|
|
||||||
page := NewHTMLParser(t, userRegular.MakeRequest(t, NewRequest(t, "GET", hintLink), http.StatusOK).Body)
|
|
||||||
activityLink, exists := page.Find(".field:has(.checkbox#keep-activity-private) .help a").Attr("href")
|
|
||||||
assert.True(t, exists)
|
|
||||||
assert.EqualValues(t, "/user2?tab=activity", activityLink)
|
|
||||||
}
|
|
||||||
|
|
||||||
// = Private profile, but public activity =
|
// = Private profile, but public activity =
|
||||||
|
|
||||||
// Set profile visibility of user2 to private
|
// Set profile visibility of user2 to private
|
||||||
testChangeUserProfileVisibility(t, userRegular, structs.VisibleTypePrivate)
|
testChangeUserProfileVisibility(t, userRegular, structs.VisibleTypePrivate)
|
||||||
|
|
||||||
// When profile activity is configured as public, but the profile is private, tell the user about this and link to visibility settings.
|
// When profile activity is configured as public, but the profile is private, tell the user about this and link to visibility settings.
|
||||||
hintLink = testUser2ActivityVisibility(t, userRegular, "Your activity is only visible to you and the instance administrators because your profile is private. Configure.", true)
|
hintLink := testUser2ActivityVisibility(t, userRegular, "Your activity is only visible to you and the instance administrators because your profile is private. Configure.", true)
|
||||||
assert.EqualValues(t, "/user/settings#visibility-setting", hintLink)
|
assert.EqualValues(t, "/user/settings#visibility-setting", hintLink)
|
||||||
|
|
||||||
// When the profile is private, tell the admin about this.
|
// When the profile is private, tell the admin about this.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
fieldset {
|
fieldset {
|
||||||
margin: 0.5em 0 1em;
|
margin: 0.2em 0 0.3em;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,12 @@ fieldset legend {
|
||||||
margin-bottom: 0.75em;
|
margin-bottom: 0.75em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fieldset + fieldset > legend {
|
||||||
|
width: 100%;
|
||||||
|
padding-top: 1em;
|
||||||
|
border-top: 1px solid var(--color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
fieldset label {
|
fieldset label {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 0.6em;
|
margin-bottom: 0.6em;
|
||||||
|
|
|
@ -143,7 +143,3 @@
|
||||||
.notifications-item:hover .notifications-updated {
|
.notifications-item:hover .notifications-updated {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pronouns-dropdown, #pronouns-custom {
|
|
||||||
width: 140px;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import {hideElem, showElem} from '../utils/dom.js';
|
import {hideElem, showElem} from '../utils/dom.js';
|
||||||
|
|
||||||
function onPronounsDropdownUpdate() {
|
function onPronounsDropdownUpdate() {
|
||||||
const pronounsCustom = document.getElementById('pronouns-custom');
|
const pronounsCustom = document.getElementById('label-pronouns-custom');
|
||||||
|
const pronounsCustomInput = pronounsCustom.querySelector('input');
|
||||||
const pronounsDropdown = document.getElementById('pronouns-dropdown');
|
const pronounsDropdown = document.getElementById('pronouns-dropdown');
|
||||||
const pronounsInput = pronounsDropdown.querySelector('input');
|
const pronounsInput = pronounsDropdown.querySelector('input');
|
||||||
// must be kept in sync with `routers/web/user/setting/profile.go`
|
// must be kept in sync with `routers/web/user/setting/profile.go`
|
||||||
|
@ -15,49 +16,35 @@ function onPronounsDropdownUpdate() {
|
||||||
);
|
);
|
||||||
if (isCustom) {
|
if (isCustom) {
|
||||||
if (pronounsInput.value === '!') {
|
if (pronounsInput.value === '!') {
|
||||||
pronounsCustom.value = '';
|
pronounsCustomInput.value = '';
|
||||||
} else {
|
} else {
|
||||||
pronounsCustom.value = pronounsInput.value;
|
pronounsCustomInput.value = pronounsInput.value;
|
||||||
}
|
}
|
||||||
pronounsCustom.style.display = '';
|
showElem(pronounsCustom);
|
||||||
} else {
|
} else {
|
||||||
pronounsCustom.style.display = 'none';
|
hideElem(pronounsCustom);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function onPronounsCustomUpdate() {
|
function onPronounsCustomUpdate() {
|
||||||
const pronounsCustom = document.getElementById('pronouns-custom');
|
const pronounsCustomInput = document.querySelector('#label-pronouns-custom input');
|
||||||
const pronounsInput = document.querySelector('#pronouns-dropdown input');
|
const pronounsInput = document.querySelector('#pronouns-dropdown input');
|
||||||
pronounsInput.value = pronounsCustom.value;
|
pronounsInput.value = pronounsCustomInput.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initUserSettings() {
|
export function initUserSettings() {
|
||||||
if (!document.querySelectorAll('.user.settings.profile').length) return;
|
if (!document.querySelectorAll('.user.settings.profile').length) return;
|
||||||
|
|
||||||
const usernameInput = document.getElementById('username');
|
const pronounsDropdown = document.getElementById('label-pronouns');
|
||||||
if (!usernameInput) return;
|
const pronounsCustomInput = document.querySelector('#label-pronouns-custom input');
|
||||||
usernameInput.addEventListener('input', function () {
|
|
||||||
const prompt = document.getElementById('name-change-prompt');
|
|
||||||
const promptRedirect = document.getElementById('name-change-redirect-prompt');
|
|
||||||
if (this.value.toLowerCase() !== this.getAttribute('data-name').toLowerCase()) {
|
|
||||||
showElem(prompt);
|
|
||||||
showElem(promptRedirect);
|
|
||||||
} else {
|
|
||||||
hideElem(prompt);
|
|
||||||
hideElem(promptRedirect);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const pronounsDropdown = document.getElementById('pronouns-dropdown');
|
|
||||||
const pronounsCustom = document.getElementById('pronouns-custom');
|
|
||||||
const pronounsInput = pronounsDropdown.querySelector('input');
|
const pronounsInput = pronounsDropdown.querySelector('input');
|
||||||
|
|
||||||
// If JS is disabled, the page will show the custom input, as the dropdown requires JS to work.
|
// If JS is disabled, the page will show the custom input, as the dropdown requires JS to work.
|
||||||
// JS progressively enhances the input by adding a dropdown, but it works regardless.
|
// JS progressively enhances the input by adding a dropdown, but it works regardless.
|
||||||
pronounsCustom.removeAttribute('name');
|
pronounsCustomInput.removeAttribute('name');
|
||||||
pronounsInput.setAttribute('name', 'pronouns');
|
pronounsInput.setAttribute('name', 'pronouns');
|
||||||
pronounsDropdown.style.display = '';
|
showElem(pronounsDropdown);
|
||||||
|
|
||||||
onPronounsDropdownUpdate();
|
onPronounsDropdownUpdate();
|
||||||
pronounsInput.addEventListener('change', onPronounsDropdownUpdate);
|
pronounsInput.addEventListener('change', onPronounsDropdownUpdate);
|
||||||
pronounsCustom.addEventListener('input', onPronounsCustomUpdate);
|
pronounsCustomInput.addEventListener('input', onPronounsCustomUpdate);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue