From 0ccc06def3fec7700ef871a40fbaaed4c68083a6 Mon Sep 17 00:00:00 2001 From: albertlast Date: Sun, 9 Aug 2026 12:46:34 +0200 Subject: [PATCH] Gives the avatar picker one gallery list and no generated script The gallery was two selects: pick a directory in the first, and a second appeared holding its files, filled in from a flat list of every file the forum has written into the page. Everything that drove it - the file list, the directory, the currently selected file, the avatar directory url, the preview element - was generated as globals, and a second block below wrote a swap_avatar() with one case per choice, each of them generated again from which choices the forum allows. There is one select now, with the directories as optgroups, so picking an avatar is one action instead of two and no list of filenames has to be written into the page. Each panel says which choice it belongs to with data-avatar-choice, and profile.js reads that rather than being told which panels exist. Two things the old script did are kept: focusing a field inside a panel selects that panel's radio, and switching to Gravatar clears an address that came from one of the other choices. The condition for the second one travels as a data attribute instead of being compiled into a case. getAvatars() marks a file as checked by comparing its name against the member's avatar, which for a file inside a directory is the path and not the name, so a nested avatar could never come back selected. It compares against the path now, which is what the single list needs. Utils::$context['avatar_list'] goes with the script that was its only reader. Signed-off-by: Mathias Alberts Signed-off-by: albertlast --- Sources/Profile.php | 9 +-- Themes/default/Profile.template.php | 107 ++++++++-------------------- Themes/default/scripts/profile.js | 104 ++++++++++++++++----------- 3 files changed, 93 insertions(+), 127 deletions(-) diff --git a/Sources/Profile.php b/Sources/Profile.php index 53bf4dcf328..ec3208340c7 100644 --- a/Sources/Profile.php +++ b/Sources/Profile.php @@ -1218,7 +1218,6 @@ public function loadAvatarData(): bool // Get a list of all the server stored avatars. if ($this->avatar->allow_server_stored) { - Utils::$context['avatar_list'] = []; Utils::$context['avatars'] = is_dir(Config::$modSettings['avatar_directory']) ? $this->getAvatars('', 0) : []; } else { Utils::$context['avatars'] = []; @@ -2607,14 +2606,12 @@ protected function getAvatars(string $directory, int $level = 0): array $result[] = [ 'filename' => Utils::htmlspecialchars($line), - 'checked' => $line == Utils::$context['member']['avatar']['server_pic'], + // server_pic names the directory too, so a file one level down + // has to be compared against the path, not just its own name. + 'checked' => ($directory === '' ? $line : $directory . '/' . $line) == Utils::$context['member']['avatar']['server_pic'], 'name' => Utils::htmlspecialchars(str_replace('_', ' ', $filename)), 'is_dir' => false, ]; - - if ($level == 1) { - Utils::$context['avatar_list'][] = $directory . '/' . $line; - } } return $result; diff --git a/Themes/default/Profile.template.php b/Themes/default/Profile.template.php index ef257af4853..d7e97bfe573 100644 --- a/Themes/default/Profile.template.php +++ b/Themes/default/Profile.template.php @@ -2978,7 +2978,7 @@ function template_profile_avatar_select() if (empty(Config::$modSettings['gravatarEnabled']) || empty(Config::$modSettings['gravatarOverride'])) { echo ' - +
'; @@ -2986,7 +2986,7 @@ function template_profile_avatar_select() if (!empty(Utils::$context['member']['avatar']['allow_server_stored'])) { echo ' - +
'; @@ -2994,7 +2994,7 @@ function template_profile_avatar_select() if (!empty(Utils::$context['member']['avatar']['allow_external'])) { echo ' - +
'; @@ -3002,7 +3002,7 @@ function template_profile_avatar_select() if (!empty(Utils::$context['member']['avatar']['allow_upload'])) { echo ' - +
'; @@ -3010,7 +3010,7 @@ function template_profile_avatar_select() if (!empty(Utils::$context['member']['avatar']['allow_gravatar'])) { echo ' - + '; } @@ -3022,59 +3022,55 @@ function template_profile_avatar_select() // If users are allowed to choose avatars stored on the server show selection boxes to choice them from. if (!empty(Utils::$context['member']['avatar']['allow_server_stored'])) { echo ' -
+
- '; - // This lists all the file categories. + // One entry per avatar, with the directories as groups. Nothing here is + // nested more than one deep, because that is all the picker can show. foreach (Utils::$context['avatars'] as $avatar) { - echo ' - '; + if (!empty($avatar['is_dir'])) { + echo ' + '; + + foreach ($avatar['files'] as $file) { + echo ' + '; + } + + echo ' + '; + } else { + echo ' + '; + } } echo '
-
- -
-
'; } // If the user can link to an off server avatar, show them a box to input the address. if (!empty(Utils::$context['member']['avatar']['allow_external'])) { echo ' -
+
', Utils::$context['member']['avatar']['choice'] == 'external' ? '
' : '', '
', Lang::getTxt('avatar_by_url', file: 'Profile'), '
', !empty(Config::$modSettings['avatar_action_too_large']) && Config::$modSettings['avatar_action_too_large'] == 'option_download_and_resize' ? template_max_size('external') : '', ' -
+
'; } // If the user is able to upload avatars to the server show them an upload box. if (!empty(Utils::$context['member']['avatar']['allow_upload'])) { echo ' -
+
', Utils::$context['member']['avatar']['choice'] == 'upload' ? '
' : '', ' - ', template_max_size('upload'), ' + ', template_max_size('upload'), ' ', (!empty(Utils::$context['member']['avatar']['id_attach']) ? '
' : ''), '
'; } @@ -3082,7 +3078,7 @@ function template_profile_avatar_select() // if the user is able to use Gravatar avatars show then the image preview if (!empty(Utils::$context['member']['avatar']['allow_gravatar'])) { echo ' -
+
', Utils::$context['member']['avatar']['choice'] == 'gravatar' ? '
' : ''; if (empty(Config::$modSettings['gravatarAllowExtraEmail'])) { @@ -3105,51 +3101,6 @@ function template_profile_avatar_select() } echo ' - '; } diff --git a/Themes/default/scripts/profile.js b/Themes/default/scripts/profile.js index e72465db71d..9e43d782781 100644 --- a/Themes/default/scripts/profile.js +++ b/Themes/default/scripts/profile.js @@ -162,67 +162,85 @@ function ajax_getSignaturePreview (showPreview) return false; } -function changeSel(selected) +/* + * The avatar picker. Which panel is on show follows the radio buttons, and + * every panel says which choice it belongs to with data-avatar-choice, so + * nothing here has to be told which ones the forum allows. + */ +document.addEventListener('DOMContentLoaded', function () { - if (cat.selectedIndex == -1) + var panels = document.querySelectorAll('[data-avatar-choice]'); + + if (!panels.length) return; - if (cat.options[cat.selectedIndex].value.indexOf("/") > 0) - { - var i; - var count = 0; + var form = panels[0].closest('form'), + choices = form.avatar_choice; - file.style.display = "inline"; - file.disabled = false; + var showPanel = function () { + var chosen = form.avatar_choice.value; - for (i = file.length; i >= 0; i = i - 1) - file.options[i] = null; + for (var i = 0; i < panels.length; i++) + panels[i].style.display = panels[i].dataset.avatarChoice == chosen ? '' : 'none'; - for (i = 0; i < files.length; i++) - if (files[i].indexOf(cat.options[cat.selectedIndex].value) == 0) - { - var filename = files[i].substr(files[i].indexOf("/") + 1); - var showFilename = filename.substr(0, filename.lastIndexOf(".")); - showFilename = showFilename.replace(/[_]/g, " "); + // Switching to Gravatar throws away an address that came from one of + // the other choices, since it would not be an address at all. + var gravatar = document.getElementById('avatar_gravatar'); - file.options[count] = new Option(showFilename, files[i]); + if (chosen == 'gravatar' && gravatar && 'clearEmail' in gravatar.dataset && document.getElementById('gravatarEmail')) + document.getElementById('gravatarEmail').value = ''; + }; - if (filename == selected) - { - if (file.options.defaultSelected) - file.options[count].defaultSelected = true; - else - file.options[count].selected = true; - } + for (var i = 0; i < choices.length; i++) + choices[i].addEventListener('change', showPanel); - count++; - } + showPanel(); - if (file.selectedIndex == -1 && file.options[0]) - file.options[0].selected = true; + // Touching anything inside a panel picks that panel's radio, which is what + // the onfocus attributes on each field used to do. + for (var i = 0; i < panels.length; i++) + panels[i].addEventListener('focusin', function () { + selectRadioByName(form.avatar_choice, this.dataset.avatarChoice); + showPanel(); + }); - showAvatar(); - } - else + var gallery = document.getElementById('cat'); + + if (gallery) { - file.style.display = "none"; - file.disabled = true; - document.getElementById("avatar").src = avatardir + cat.options[cat.selectedIndex].value; - document.getElementById("avatar").style.width = ""; - document.getElementById("avatar").style.height = ""; + gallery.addEventListener('change', showAvatar); + showAvatar.call(gallery); } -} + var external = form.userpicpersonal; + + if (external) + external.addEventListener('change', function () { + previewExternalAvatar(this.value); + }); + + var upload = document.getElementById('avatar_upload_box'); + + if (upload) + upload.addEventListener('change', function () { + readfromUpload(this); + }); +}); + +// Shows whichever avatar the gallery is pointing at. function showAvatar() { - if (file.selectedIndex == -1) + var chosen = this.options[this.selectedIndex]; + + if (!chosen || chosen.value == '') return; - document.getElementById("avatar").src = avatardir + file.options[file.selectedIndex].value; - document.getElementById("avatar").alt = file.options[file.selectedIndex].text; - document.getElementById("avatar").alt += file.options[file.selectedIndex].text == size ? "!" : ""; - document.getElementById("avatar").style.width = ""; - document.getElementById("avatar").style.height = ""; + var preview = document.getElementById('avatar'); + + preview.src = this.dataset.avatardir + chosen.value; + preview.alt = chosen.text; + preview.style.width = ''; + preview.style.height = ''; } function previewExternalAvatar(src)