Fix: Scope event handlers to container in userStats.js
Fixes critical bug identified by Zephyre_Review: event handlers used global jQuery selectors instead of container-scoped selectors, causing potential event handler leaks and scope mismatches.
This commit is contained in:
@@ -446,7 +446,7 @@ export function renderUserStats() {
|
|||||||
|
|
||||||
// Only re-bind event listeners if DOM was actually updated
|
// Only re-bind event listeners if DOM was actually updated
|
||||||
if (domUpdated) {
|
if (domUpdated) {
|
||||||
$('.rpg-editable-stat').on('blur', function () {
|
$userStatsContainer.find('.rpg-editable-stat').on('blur', function () {
|
||||||
const field = $(this).data('field');
|
const field = $(this).data('field');
|
||||||
const mode = $(this).data('mode');
|
const mode = $(this).data('mode');
|
||||||
const maxValue = parseInt($(this).data('max')) || 100;
|
const maxValue = parseInt($(this).data('max')) || 100;
|
||||||
@@ -454,82 +454,61 @@ export function renderUserStats() {
|
|||||||
let value;
|
let value;
|
||||||
|
|
||||||
if (mode === 'number') {
|
if (mode === 'number') {
|
||||||
// In number mode, parse "X/MAX" or just "X"
|
|
||||||
const parts = textValue.split('/');
|
const parts = textValue.split('/');
|
||||||
value = parseInt(parts[0]);
|
value = parseInt(parts[0]);
|
||||||
|
|
||||||
// Validate and clamp value between 0 and maxValue
|
|
||||||
if (isNaN(value)) {
|
if (isNaN(value)) {
|
||||||
value = 0;
|
value = 0;
|
||||||
}
|
}
|
||||||
value = Math.max(0, Math.min(maxValue, value));
|
value = Math.max(0, Math.min(maxValue, value));
|
||||||
} else {
|
} else {
|
||||||
// In percentage mode, parse "X%" or just "X"
|
|
||||||
value = parseInt(textValue.replace('%', ''));
|
value = parseInt(textValue.replace('%', ''));
|
||||||
|
|
||||||
// Validate and clamp value between 0 and 100
|
|
||||||
if (isNaN(value)) {
|
if (isNaN(value)) {
|
||||||
value = 0;
|
value = 0;
|
||||||
}
|
}
|
||||||
value = Math.max(0, Math.min(100, value));
|
value = Math.max(0, Math.min(100, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the setting
|
|
||||||
extensionSettings.userStats[field] = value;
|
extensionSettings.userStats[field] = value;
|
||||||
|
|
||||||
// Update userStats data (maintains JSON or text format)
|
|
||||||
updateUserStatsData();
|
updateUserStatsData();
|
||||||
|
|
||||||
saveSettings();
|
saveSettings();
|
||||||
saveChatData();
|
saveChatData();
|
||||||
updateMessageSwipeData();
|
updateMessageSwipeData();
|
||||||
|
|
||||||
// Re-render to update the bar and FAB widgets
|
|
||||||
renderUserStats();
|
renderUserStats();
|
||||||
updateFabWidgets();
|
updateFabWidgets();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add event listeners for mood/conditions editing
|
// Add event listeners for mood/conditions editing
|
||||||
$('.rpg-mood-emoji.rpg-editable').on('blur', function () {
|
$userStatsContainer.find('.rpg-mood-emoji.rpg-editable').on('blur', function () {
|
||||||
const value = $(this).text().trim();
|
const value = $(this).text().trim();
|
||||||
extensionSettings.userStats.mood = value || '😐';
|
extensionSettings.userStats.mood = value || '😐';
|
||||||
|
|
||||||
// Update userStats data (maintains JSON or text format)
|
|
||||||
updateUserStatsData();
|
updateUserStatsData();
|
||||||
|
|
||||||
saveSettings();
|
saveSettings();
|
||||||
saveChatData();
|
saveChatData();
|
||||||
updateMessageSwipeData();
|
updateMessageSwipeData();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.rpg-mood-conditions.rpg-editable').on('blur', function () {
|
$userStatsContainer.find('.rpg-mood-conditions.rpg-editable').on('blur', function () {
|
||||||
const value = $(this).text().trim();
|
const value = $(this).text().trim();
|
||||||
const fieldKey = $(this).data('field');
|
const fieldKey = $(this).data('field');
|
||||||
extensionSettings.userStats[fieldKey] = value || 'None';
|
extensionSettings.userStats[fieldKey] = value || 'None';
|
||||||
|
|
||||||
// Update userStats data (maintains JSON or text format)
|
|
||||||
updateUserStatsData();
|
updateUserStatsData();
|
||||||
|
|
||||||
saveSettings();
|
saveSettings();
|
||||||
saveChatData();
|
saveChatData();
|
||||||
updateMessageSwipeData();
|
updateMessageSwipeData();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add event listener for skills editing
|
// Add event listener for skills editing
|
||||||
$('.rpg-skills-value.rpg-editable').on('blur', function () {
|
$userStatsContainer.find('.rpg-skills-value.rpg-editable').on('blur', function () {
|
||||||
const value = $(this).text().trim();
|
const value = $(this).text().trim();
|
||||||
extensionSettings.userStats.skills = value || 'None';
|
extensionSettings.userStats.skills = value || 'None';
|
||||||
|
|
||||||
// Update userStats data (maintains JSON or text format)
|
|
||||||
updateUserStatsData();
|
updateUserStatsData();
|
||||||
|
|
||||||
saveSettings();
|
saveSettings();
|
||||||
saveChatData();
|
saveChatData();
|
||||||
updateMessageSwipeData();
|
updateMessageSwipeData();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add event listeners for stat name editing
|
// Add event listeners for stat name editing
|
||||||
$('.rpg-editable-stat-name').on('blur', function () {
|
$userStatsContainer.find('.rpg-editable-stat-name').on('blur', function () {
|
||||||
const field = $(this).data('field');
|
const field = $(this).data('field');
|
||||||
const value = $(this).text().trim().replace(':', '');
|
const value = $(this).text().trim().replace(':', '');
|
||||||
|
|
||||||
@@ -544,34 +523,28 @@ export function renderUserStats() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extensionSettings.statNames[field] = value || extensionSettings.statNames[field];
|
extensionSettings.statNames[field] = value || extensionSettings.statNames[field];
|
||||||
|
|
||||||
saveSettings();
|
saveSettings();
|
||||||
saveChatData();
|
saveChatData();
|
||||||
|
|
||||||
// Re-render to update the display
|
|
||||||
renderUserStats();
|
renderUserStats();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add event listener for level editing
|
// Add event listener for level editing
|
||||||
$('.rpg-level-value.rpg-editable').on('blur', function () {
|
$userStatsContainer.find('.rpg-level-value.rpg-editable').on('blur', function () {
|
||||||
let value = parseInt($(this).text().trim());
|
let value = parseInt($(this).text().trim());
|
||||||
if (isNaN(value) || value < 1) {
|
if (isNaN(value) || value < 1) {
|
||||||
value = 1;
|
value = 1;
|
||||||
}
|
}
|
||||||
// Set reasonable max level
|
|
||||||
value = Math.min(100, value);
|
value = Math.min(100, value);
|
||||||
|
|
||||||
extensionSettings.level = value;
|
extensionSettings.level = value;
|
||||||
saveSettings();
|
saveSettings();
|
||||||
saveChatData();
|
saveChatData();
|
||||||
updateMessageSwipeData();
|
updateMessageSwipeData();
|
||||||
|
|
||||||
// Re-render to update the display
|
|
||||||
renderUserStats();
|
renderUserStats();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Prevent line breaks in level field
|
// Prevent line breaks in level field
|
||||||
$('.rpg-level-value.rpg-editable').on('keydown', function (e) {
|
$userStatsContainer.find('.rpg-level-value.rpg-editable').on('keydown', function (e) {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
$(this).blur();
|
$(this).blur();
|
||||||
@@ -579,7 +552,7 @@ export function renderUserStats() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Add event listener for section lock icon clicks (support both click and touch)
|
// Add event listener for section lock icon clicks (support both click and touch)
|
||||||
$('.rpg-section-lock-icon').on('click touchend', function (e) {
|
$userStatsContainer.find('.rpg-section-lock-icon').on('click touchend', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const $icon = $(this);
|
const $icon = $(this);
|
||||||
@@ -587,19 +560,14 @@ export function renderUserStats() {
|
|||||||
const itemPath = $icon.data('path');
|
const itemPath = $icon.data('path');
|
||||||
const currentlyLocked = isItemLocked(trackerType, itemPath);
|
const currentlyLocked = isItemLocked(trackerType, itemPath);
|
||||||
|
|
||||||
// Toggle lock state
|
|
||||||
setItemLock(trackerType, itemPath, !currentlyLocked);
|
setItemLock(trackerType, itemPath, !currentlyLocked);
|
||||||
|
|
||||||
// Update icon
|
|
||||||
const newIcon = !currentlyLocked ? '🔒' : '🔓';
|
const newIcon = !currentlyLocked ? '🔒' : '🔓';
|
||||||
const newTitle = !currentlyLocked ? (i18n.getTranslation('infoBox.locked') || 'Locked') : (i18n.getTranslation('infoBox.unlocked') || 'Unlocked');
|
const newTitle = !currentlyLocked ? (i18n.getTranslation('infoBox.locked') || 'Locked') : (i18n.getTranslation('infoBox.unlocked') || 'Unlocked');
|
||||||
$icon.text(newIcon);
|
$icon.text(newIcon);
|
||||||
$icon.attr('title', newTitle);
|
$icon.attr('title', newTitle);
|
||||||
|
|
||||||
// Toggle 'locked' class for persistent visibility
|
|
||||||
$icon.toggleClass('locked', !currentlyLocked);
|
$icon.toggleClass('locked', !currentlyLocked);
|
||||||
|
|
||||||
// Save settings
|
|
||||||
saveSettings();
|
saveSettings();
|
||||||
});
|
});
|
||||||
} // end if (domUpdated)
|
} // end if (domUpdated)
|
||||||
|
|||||||
Reference in New Issue
Block a user