Simplify pronouns in user settings (#6835)
The main change here is to use `datalist` for pronouns This supports (see also docs[1]): * Displaying the value already set by the user (if any), otherwise * Presenting a list of common options to the user, and * Allowing them to freely enter any value This setup requires no additional JS and resolves[2]. This is different from the previous flow which used, if JS was available: * A menu for a default 'recognised' set of pronouns, and if the user wanted another value: * An extra text div if the user wanted to enter custom pronouns Without JS enabled both the menu and the custom text div would always be displayed. This change means there's no longer a distinction between 'custom' and 'recognised' pronouns (this difference looks to have only been made in code, and not in any data models). Link: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist [1] Link: https://codeberg.org/forgejo/forgejo/issues/6774 [2] Co-authored-by: Matthew Hughes <matthewhughes934@gmail.com> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6835 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Reviewed-by: Otto <otto@codeberg.org> Co-authored-by: mhughes9 <mhughes9@noreply.codeberg.org> Co-committed-by: mhughes9 <mhughes9@noreply.codeberg.org>
This commit is contained in:
parent
77a1af5ab8
commit
2024031a7a
|
@ -761,8 +761,6 @@ full_name = Full name
|
|||
website = Website
|
||||
location = Location
|
||||
pronouns = Pronouns
|
||||
pronouns_custom = Custom
|
||||
pronouns_custom_label = Custom pronouns
|
||||
pronouns_unspecified = Unspecified
|
||||
update_theme = Change theme
|
||||
update_profile = Update profile
|
||||
|
|
|
@ -12,7 +12,6 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -42,8 +41,7 @@ const (
|
|||
tplSettingsRepositories base.TplName = "user/settings/repos"
|
||||
)
|
||||
|
||||
// must be kept in sync with `web_src/js/features/user-settings.js`
|
||||
var recognisedPronouns = []string{"", "he/him", "she/her", "they/them", "it/its", "any pronouns"}
|
||||
var commonPronouns = []string{"he/him", "she/her", "they/them", "it/its", "any pronouns"}
|
||||
|
||||
// Profile render user's profile page
|
||||
func Profile(ctx *context.Context) {
|
||||
|
@ -51,8 +49,8 @@ func Profile(ctx *context.Context) {
|
|||
ctx.Data["PageIsSettingsProfile"] = true
|
||||
ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()
|
||||
ctx.Data["DisableGravatar"] = setting.Config().Picture.DisableGravatar.Value(ctx)
|
||||
ctx.Data["PronounsAreCustom"] = !slices.Contains(recognisedPronouns, ctx.Doer.Pronouns)
|
||||
ctx.Data["CooldownPeriod"] = setting.Service.UsernameCooldownPeriod
|
||||
ctx.Data["CommonPronouns"] = commonPronouns
|
||||
|
||||
ctx.HTML(http.StatusOK, tplSettingsProfile)
|
||||
}
|
||||
|
@ -63,8 +61,8 @@ func ProfilePost(ctx *context.Context) {
|
|||
ctx.Data["PageIsSettingsProfile"] = true
|
||||
ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()
|
||||
ctx.Data["DisableGravatar"] = setting.Config().Picture.DisableGravatar.Value(ctx)
|
||||
ctx.Data["PronounsAreCustom"] = !slices.Contains(recognisedPronouns, ctx.Doer.Pronouns)
|
||||
ctx.Data["CooldownPeriod"] = setting.Service.UsernameCooldownPeriod
|
||||
ctx.Data["CommonPronouns"] = commonPronouns
|
||||
|
||||
if ctx.HasError() {
|
||||
ctx.HTML(http.StatusOK, tplSettingsProfile)
|
||||
|
|
|
@ -30,38 +30,14 @@
|
|||
<input name="full_name" value="{{.SignedUser.FullName}}" maxlength="100">
|
||||
</label>
|
||||
|
||||
<label id="label-pronouns" class="tw-hidden">
|
||||
<label id="label-pronouns">
|
||||
{{ctx.Locale.Tr "settings.pronouns"}}
|
||||
<div id="pronouns-dropdown" class="ui selection dropdown" aria-labelledby="label-pronouns">
|
||||
<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}}">{{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>
|
||||
</label>
|
||||
<label id="label-pronouns-custom">
|
||||
{{ctx.Locale.Tr "settings.pronouns_custom_label"}}
|
||||
<input name="pronouns" value="{{.SignedUser.Pronouns}}" maxlength="50">
|
||||
<input name="pronouns" list="pronouns" placeholder="{{ctx.Locale.Tr "settings.pronouns_unspecified"}}" value="{{.SignedUser.Pronouns}}" maxlength="50">
|
||||
<datalist id="pronouns">
|
||||
{{range .CommonPronouns}}
|
||||
<option value="{{.}}"></option>
|
||||
{{end}}
|
||||
</datalist>
|
||||
</label>
|
||||
|
||||
<label {{if .Err_Biography}}class="field error"{{end}}>
|
||||
|
|
|
@ -16,8 +16,16 @@ test('User: Profile settings', async ({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();
|
||||
|
||||
const pronounsInput = page.locator('input[list="pronouns"]');
|
||||
await expect(pronounsInput).toHaveAttribute('placeholder', 'Unspecified');
|
||||
await pronounsInput.click();
|
||||
const pronounsList = page.locator('datalist#pronouns');
|
||||
const pronounsOptions = pronounsList.locator('option');
|
||||
const pronounsValues = await pronounsOptions.evaluateAll((opts) => opts.map((opt) => opt.value));
|
||||
expect(pronounsValues).toEqual(['he/him', 'she/her', 'they/them', 'it/its', 'any pronouns']);
|
||||
await pronounsInput.fill('she/her');
|
||||
|
||||
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');
|
||||
|
@ -44,9 +52,7 @@ test('User: Profile settings', async ({browser}, workerInfo) => {
|
|||
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.locator('input[list="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();
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
import {hideElem, showElem} from '../utils/dom.js';
|
||||
|
||||
function onPronounsDropdownUpdate() {
|
||||
const pronounsCustom = document.getElementById('label-pronouns-custom');
|
||||
const pronounsCustomInput = pronounsCustom.querySelector('input');
|
||||
const pronounsDropdown = document.getElementById('pronouns-dropdown');
|
||||
const pronounsInput = pronounsDropdown.querySelector('input');
|
||||
// must be kept in sync with `routers/web/user/setting/profile.go`
|
||||
const isCustom = !(
|
||||
pronounsInput.value === '' ||
|
||||
pronounsInput.value === 'he/him' ||
|
||||
pronounsInput.value === 'she/her' ||
|
||||
pronounsInput.value === 'they/them' ||
|
||||
pronounsInput.value === 'it/its' ||
|
||||
pronounsInput.value === 'any pronouns'
|
||||
);
|
||||
if (isCustom) {
|
||||
if (pronounsInput.value === '!') {
|
||||
pronounsCustomInput.value = '';
|
||||
} else {
|
||||
pronounsCustomInput.value = pronounsInput.value;
|
||||
}
|
||||
showElem(pronounsCustom);
|
||||
} else {
|
||||
hideElem(pronounsCustom);
|
||||
}
|
||||
}
|
||||
function onPronounsCustomUpdate() {
|
||||
const pronounsCustomInput = document.querySelector('#label-pronouns-custom input');
|
||||
const pronounsInput = document.querySelector('#pronouns-dropdown input');
|
||||
pronounsInput.value = pronounsCustomInput.value;
|
||||
}
|
||||
|
||||
export function initUserSettings() {
|
||||
if (!document.querySelectorAll('.user.settings.profile').length) return;
|
||||
|
||||
const pronounsDropdown = document.getElementById('label-pronouns');
|
||||
const pronounsCustomInput = document.querySelector('#label-pronouns-custom input');
|
||||
const pronounsInput = pronounsDropdown.querySelector('input');
|
||||
|
||||
// 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.
|
||||
pronounsCustomInput.removeAttribute('name');
|
||||
pronounsInput.setAttribute('name', 'pronouns');
|
||||
showElem(pronounsDropdown);
|
||||
|
||||
onPronounsDropdownUpdate();
|
||||
pronounsInput.addEventListener('change', onPronounsDropdownUpdate);
|
||||
pronounsCustomInput.addEventListener('input', onPronounsCustomUpdate);
|
||||
}
|
|
@ -51,7 +51,6 @@ import {initAdminCommon} from './features/admin/common.js';
|
|||
import {initRepoTemplateSearch} from './features/repo-template.js';
|
||||
import {initRepoCodeView} from './features/repo-code.js';
|
||||
import {initSshKeyFormParser} from './features/sshkey-helper.js';
|
||||
import {initUserSettings} from './features/user-settings.js';
|
||||
import {initRepoArchiveLinks} from './features/repo-common.js';
|
||||
import {initRepoMigrationStatusChecker} from './features/repo-migrate.js';
|
||||
import {
|
||||
|
@ -185,7 +184,6 @@ onDomReady(() => {
|
|||
initUserAuthOauth2();
|
||||
initUserAuthWebAuthn();
|
||||
initUserAuthWebAuthnRegister();
|
||||
initUserSettings();
|
||||
initRepoDiffView();
|
||||
initPdfViewer();
|
||||
initScopedAccessTokenCategories();
|
||||
|
|
Loading…
Reference in New Issue