diff --git a/Sources/Actions/QuickModerationInTopic.php b/Sources/Actions/QuickModerationInTopic.php index 9d7edfbddf..920134cda4 100644 --- a/Sources/Actions/QuickModerationInTopic.php +++ b/Sources/Actions/QuickModerationInTopic.php @@ -62,6 +62,14 @@ public function execute(): void // Check the session = get or post. User::$me->checkSession('request'); + // Nothing ticked, nothing to do. Every branch below assumes at least + // one message: splitting takes the min() of them, and the other two + // hand the list to an {array_int:...}, which will not accept an empty + // one. + if (empty($this->messages)) { + Utils::redirectexit('topic=' . Topic::$topic_id . '.' . ($_REQUEST['start'] ?? 0)); + } + if (isset($_REQUEST['restore_selected'])) { $this->restore(); } elseif (isset($_REQUEST['split_selection'])) { @@ -80,7 +88,7 @@ public function execute(): void */ protected function __construct() { - $this->messages = array_map('intval', $_REQUEST['msgs']); + $this->messages = array_map('intval', (array) ($_REQUEST['msgs'] ?? [])); } /** diff --git a/Themes/default/scripts/topic.js b/Themes/default/scripts/topic.js index 6b2cd75c57..99d00937c3 100755 --- a/Themes/default/scripts/topic.js +++ b/Themes/default/scripts/topic.js @@ -872,20 +872,41 @@ function InTopicModeration(oOptions) InTopicModeration.prototype.init = function() { + /* + * The topic draws two of these strips - one for the page and one for the + * mobile menu - and they share the containers, so the second instance + * finds the checkboxes the first one already made. Collect those once, + * under the message id each one carries, and listen to them as well + * rather than putting a second checkbox beside every post. This file is + * the only thing that emits a msgs[] input, so there is nothing else on + * the page for this to pick up. + */ + var oExisting = {}; + var aCheckboxes = document.querySelectorAll('input[name="msgs[]"]'); + + for (var j = 0, m = aCheckboxes.length; j < m; j++) + oExisting[aCheckboxes[j].value] = aCheckboxes[j]; + // Add checkboxes to all the messages. for (var i = 0, n = this.opt.aMessageIds.length; i < n; i++) { - // Create the checkbox. - var oCheckbox = document.createElement('input'); - oCheckbox.type = 'checkbox'; - oCheckbox.className = this.opt.sButtonStrip + '_check'; - oCheckbox.name = 'msgs[]'; - oCheckbox.value = this.opt.aMessageIds[i]; - oCheckbox.onclick = this.handleClick.bind(this, oCheckbox); - // Append it to the container var oCheckboxContainer = document.getElementById(this.opt.sCheckboxContainerMask + this.opt.aMessageIds[i]); - oCheckboxContainer.appendChild(oCheckbox); + var oCheckbox = oExisting[this.opt.aMessageIds[i]]; + + if (!oCheckbox) + { + // Create the checkbox. + oCheckbox = document.createElement('input'); + oCheckbox.type = 'checkbox'; + oCheckbox.className = this.opt.sButtonStrip + '_check'; + oCheckbox.name = 'msgs[]'; + oCheckbox.value = this.opt.aMessageIds[i]; + + oCheckboxContainer.appendChild(oCheckbox); + } + + oCheckbox.addEventListener('click', this.handleClick.bind(this, oCheckbox)); oCheckboxContainer.style.display = ''; } @@ -898,8 +919,8 @@ InTopicModeration.prototype.init = function() else { oButtonStripDisplay = document.createElement('div'); - oNewDiv.id = this.opt.sButtonStripDisplay; - oNewDiv.className = this.opt.sButtonStripClass || 'buttonlist floatbottom'; + oButtonStripDisplay.id = this.opt.sButtonStripDisplay; + oButtonStripDisplay.className = this.opt.sButtonStripClass || 'buttonlist floatbottom'; oButtonStrip.appendChild(oButtonStripDisplay); } @@ -936,6 +957,14 @@ InTopicModeration.prototype.init = function() ['click', this.handleSubmit.bind(this, 'split')] ] }); + + /* + * Nothing is selected yet, so put the buttons in the state that says so. + * They used to be built on the first click instead of here, which is why + * nothing hid them to begin with - and pressing one with an empty + * selection submits the form with no msgs[] at all. + */ + this.updateButtons(); } InTopicModeration.prototype.handleClick = function(oCheckbox) @@ -944,23 +973,28 @@ InTopicModeration.prototype.handleClick = function(oCheckbox) // Keep stats on how many items were selected. this.iNumSelected += oCheckbox.checked ? 1 : -1; - // Show the number of messages selected in each of the buttons. - if (this.opt.bCanRemove && !this.opt.bUseImageButton) - { - this.oRemoveButton.innerHTML = this.opt.sRemoveButtonLabel + ' [' + this.iNumSelected + ']'; - this.oRemoveButton.style.display = this.iNumSelected < 1 ? "none" : ""; - } + this.updateButtons(); +} - if (this.opt.bCanRestore && !this.opt.bUseImageButton) - { - this.oRestoreButton.innerHTML = this.opt.sRestoreButtonLabel + ' [' + this.iNumSelected + ']'; - this.oRestoreButton.style.display = this.iNumSelected < 1 ? "none" : ""; - } +// Show the number of messages selected in each of the buttons, and hide them +// while that number is zero. +InTopicModeration.prototype.updateButtons = function() +{ + var aButtons = [ + [this.opt.bCanRemove, this.oRemoveButton, this.opt.sRemoveButtonLabel], + [this.opt.bCanRestore, this.oRestoreButton, this.opt.sRestoreButtonLabel], + [this.opt.bCanSplit, this.oSplitButton, this.opt.sSplitButtonLabel] + ]; - if (this.opt.bCanSplit && !this.opt.bUseImageButton) + for (var i = 0; i < aButtons.length; i++) { - this.oSplitButton.innerHTML = this.opt.sSplitButtonLabel + ' [' + this.iNumSelected + ']'; - this.oSplitButton.style.display = this.iNumSelected < 1 ? "none" : ""; + if (!aButtons[i][0] || !aButtons[i][1]) + continue; + + if (!this.opt.bUseImageButton) + aButtons[i][1].innerHTML = aButtons[i][2] + ' [' + this.iNumSelected + ']'; + + aButtons[i][1].style.display = this.iNumSelected < 1 ? "none" : ""; } }