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.

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>

authored by

mhughes9
Matthew Hughes
mhughes9
and committed by
Otto
2024031a 77a1af5a

+21 -95
-2
options/locale/locale_en-US.ini
··· 761 761 website = Website 762 762 location = Location 763 763 pronouns = Pronouns 764 - pronouns_custom = Custom 765 - pronouns_custom_label = Custom pronouns 766 764 pronouns_unspecified = Unspecified 767 765 update_theme = Change theme 768 766 update_profile = Update profile
+3 -5
routers/web/user/setting/profile.go
··· 12 12 "net/http" 13 13 "os" 14 14 "path/filepath" 15 - "slices" 16 15 "strings" 17 16 "time" 18 17 ··· 42 41 tplSettingsRepositories base.TplName = "user/settings/repos" 43 42 ) 44 43 45 - // must be kept in sync with `web_src/js/features/user-settings.js` 46 - var recognisedPronouns = []string{"", "he/him", "she/her", "they/them", "it/its", "any pronouns"} 44 + var commonPronouns = []string{"he/him", "she/her", "they/them", "it/its", "any pronouns"} 47 45 48 46 // Profile render user's profile page 49 47 func Profile(ctx *context.Context) { ··· 51 49 ctx.Data["PageIsSettingsProfile"] = true 52 50 ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice() 53 51 ctx.Data["DisableGravatar"] = setting.Config().Picture.DisableGravatar.Value(ctx) 54 - ctx.Data["PronounsAreCustom"] = !slices.Contains(recognisedPronouns, ctx.Doer.Pronouns) 55 52 ctx.Data["CooldownPeriod"] = setting.Service.UsernameCooldownPeriod 53 + ctx.Data["CommonPronouns"] = commonPronouns 56 54 57 55 ctx.HTML(http.StatusOK, tplSettingsProfile) 58 56 } ··· 63 61 ctx.Data["PageIsSettingsProfile"] = true 64 62 ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice() 65 63 ctx.Data["DisableGravatar"] = setting.Config().Picture.DisableGravatar.Value(ctx) 66 - ctx.Data["PronounsAreCustom"] = !slices.Contains(recognisedPronouns, ctx.Doer.Pronouns) 67 64 ctx.Data["CooldownPeriod"] = setting.Service.UsernameCooldownPeriod 65 + ctx.Data["CommonPronouns"] = commonPronouns 68 66 69 67 if ctx.HasError() { 70 68 ctx.HTML(http.StatusOK, tplSettingsProfile)
+7 -31
templates/user/settings/profile.tmpl
··· 30 30 <input name="full_name" value="{{.SignedUser.FullName}}" maxlength="100"> 31 31 </label> 32 32 33 - <label id="label-pronouns" class="tw-hidden"> 33 + <label id="label-pronouns"> 34 34 {{ctx.Locale.Tr "settings.pronouns"}} 35 - <div id="pronouns-dropdown" class="ui selection dropdown" aria-labelledby="label-pronouns"> 36 - <input type="hidden" value="{{.SignedUser.Pronouns}}"> 37 - <div class="text"> 38 - {{if .PronounsAreCustom}} 39 - {{ctx.Locale.Tr "settings.pronouns_custom"}} 40 - {{else if eq "" .SignedUser.Pronouns}} 41 - {{ctx.Locale.Tr "settings.pronouns_unspecified"}} 42 - {{else}} 43 - {{.SignedUser.Pronouns}} 44 - {{end}} 45 - </div> 46 - {{svg "octicon-triangle-down" 14 "dropdown icon"}} 47 - <div class="menu"> 48 - <div class="item{{if eq "" .SignedUser.Pronouns}} active selected{{end}}" data-value=""><p>{{ctx.Locale.Tr "settings.pronouns_unspecified"}}</p></div> 49 - <div class="item{{if eq "he/him" .SignedUser.Pronouns}} active selected{{end}}" data-value="he/him">he/him</div> 50 - <div class="item{{if eq "she/her" .SignedUser.Pronouns}} active selected{{end}}" data-value="she/her">she/her</div> 51 - <div class="item{{if eq "they/them" .SignedUser.Pronouns}} active selected{{end}}" data-value="they/them">they/them</div> 52 - <div class="item{{if eq "it/its" .SignedUser.Pronouns}} active selected{{end}}" data-value="it/its">it/its</div> 53 - <div class="item{{if eq "any pronouns" .SignedUser.Pronouns}} active selected{{end}}" data-value="any pronouns">any pronouns</div> 54 - {{if .PronounsAreCustom}} 55 - <div class="item active selected" data-value="{{.SignedUser.Pronouns}}">{{ctx.Locale.Tr "settings.pronouns_custom"}}</div> 56 - {{else}} 57 - <div class="item" data-value="!"><i>{{ctx.Locale.Tr "settings.pronouns_custom"}}</i></div> 58 - {{end}} 59 - </div> 60 - </div> 61 - </label> 62 - <label id="label-pronouns-custom"> 63 - {{ctx.Locale.Tr "settings.pronouns_custom_label"}} 64 - <input name="pronouns" value="{{.SignedUser.Pronouns}}" maxlength="50"> 35 + <input name="pronouns" list="pronouns" placeholder="{{ctx.Locale.Tr "settings.pronouns_unspecified"}}" value="{{.SignedUser.Pronouns}}" maxlength="50"> 36 + <datalist id="pronouns"> 37 + {{range .CommonPronouns}} 38 + <option value="{{.}}"></option> 39 + {{end}} 40 + </datalist> 65 41 </label> 66 42 67 43 <label {{if .Err_Biography}}class="field error"{{end}}>
+11 -5
tests/e2e/user-settings.test.e2e.ts
··· 16 16 await page.goto('/user/settings'); 17 17 18 18 await page.getByLabel('Full name').fill('SecondUser'); 19 - await page.locator('#pronouns-dropdown').click(); 20 - await page.getByRole('option', {name: 'she/her'}).click(); 19 + 20 + const pronounsInput = page.locator('input[list="pronouns"]'); 21 + await expect(pronounsInput).toHaveAttribute('placeholder', 'Unspecified'); 22 + await pronounsInput.click(); 23 + const pronounsList = page.locator('datalist#pronouns'); 24 + const pronounsOptions = pronounsList.locator('option'); 25 + const pronounsValues = await pronounsOptions.evaluateAll((opts) => opts.map((opt) => opt.value)); 26 + expect(pronounsValues).toEqual(['he/him', 'she/her', 'they/them', 'it/its', 'any pronouns']); 27 + await pronounsInput.fill('she/her'); 28 + 21 29 await page.getByPlaceholder('Tell others a little bit').fill('I am a playwright test running for several seconds.'); 22 30 await page.getByPlaceholder('Tell others a little bit').press('Tab'); 23 31 await page.getByLabel('Website').fill('https://forgejo.org'); ··· 44 52 await save_visual(page); 45 53 46 54 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'); 55 + await page.locator('input[list="pronouns"]').fill('rob/ot'); 50 56 await page.getByLabel('User visibility').click(); 51 57 await page.getByLabel('Visible to everyone').click(); 52 58 await page.getByLabel('Hide email address Your email').check();
-50
web_src/js/features/user-settings.js
··· 1 - import {hideElem, showElem} from '../utils/dom.js'; 2 - 3 - function onPronounsDropdownUpdate() { 4 - const pronounsCustom = document.getElementById('label-pronouns-custom'); 5 - const pronounsCustomInput = pronounsCustom.querySelector('input'); 6 - const pronounsDropdown = document.getElementById('pronouns-dropdown'); 7 - const pronounsInput = pronounsDropdown.querySelector('input'); 8 - // must be kept in sync with `routers/web/user/setting/profile.go` 9 - const isCustom = !( 10 - pronounsInput.value === '' || 11 - pronounsInput.value === 'he/him' || 12 - pronounsInput.value === 'she/her' || 13 - pronounsInput.value === 'they/them' || 14 - pronounsInput.value === 'it/its' || 15 - pronounsInput.value === 'any pronouns' 16 - ); 17 - if (isCustom) { 18 - if (pronounsInput.value === '!') { 19 - pronounsCustomInput.value = ''; 20 - } else { 21 - pronounsCustomInput.value = pronounsInput.value; 22 - } 23 - showElem(pronounsCustom); 24 - } else { 25 - hideElem(pronounsCustom); 26 - } 27 - } 28 - function onPronounsCustomUpdate() { 29 - const pronounsCustomInput = document.querySelector('#label-pronouns-custom input'); 30 - const pronounsInput = document.querySelector('#pronouns-dropdown input'); 31 - pronounsInput.value = pronounsCustomInput.value; 32 - } 33 - 34 - export function initUserSettings() { 35 - if (!document.querySelectorAll('.user.settings.profile').length) return; 36 - 37 - const pronounsDropdown = document.getElementById('label-pronouns'); 38 - const pronounsCustomInput = document.querySelector('#label-pronouns-custom input'); 39 - const pronounsInput = pronounsDropdown.querySelector('input'); 40 - 41 - // If JS is disabled, the page will show the custom input, as the dropdown requires JS to work. 42 - // JS progressively enhances the input by adding a dropdown, but it works regardless. 43 - pronounsCustomInput.removeAttribute('name'); 44 - pronounsInput.setAttribute('name', 'pronouns'); 45 - showElem(pronounsDropdown); 46 - 47 - onPronounsDropdownUpdate(); 48 - pronounsInput.addEventListener('change', onPronounsDropdownUpdate); 49 - pronounsCustomInput.addEventListener('input', onPronounsCustomUpdate); 50 - }
-2
web_src/js/index.js
··· 51 51 import {initRepoTemplateSearch} from './features/repo-template.js'; 52 52 import {initRepoCodeView} from './features/repo-code.js'; 53 53 import {initSshKeyFormParser} from './features/sshkey-helper.js'; 54 - import {initUserSettings} from './features/user-settings.js'; 55 54 import {initRepoArchiveLinks} from './features/repo-common.js'; 56 55 import {initRepoMigrationStatusChecker} from './features/repo-migrate.js'; 57 56 import { ··· 185 184 initUserAuthOauth2(); 186 185 initUserAuthWebAuthn(); 187 186 initUserAuthWebAuthnRegister(); 188 - initUserSettings(); 189 187 initRepoDiffView(); 190 188 initPdfViewer(); 191 189 initScopedAccessTokenCategories();