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:
+111
-143
@@ -446,161 +446,129 @@ export function renderUserStats() {
|
||||
|
||||
// Only re-bind event listeners if DOM was actually updated
|
||||
if (domUpdated) {
|
||||
$('.rpg-editable-stat').on('blur', function () {
|
||||
const field = $(this).data('field');
|
||||
const mode = $(this).data('mode');
|
||||
const maxValue = parseInt($(this).data('max')) || 100;
|
||||
const textValue = $(this).text().trim();
|
||||
let value;
|
||||
$userStatsContainer.find('.rpg-editable-stat').on('blur', function () {
|
||||
const field = $(this).data('field');
|
||||
const mode = $(this).data('mode');
|
||||
const maxValue = parseInt($(this).data('max')) || 100;
|
||||
const textValue = $(this).text().trim();
|
||||
let value;
|
||||
|
||||
if (mode === 'number') {
|
||||
// In number mode, parse "X/MAX" or just "X"
|
||||
const parts = textValue.split('/');
|
||||
value = parseInt(parts[0]);
|
||||
|
||||
// Validate and clamp value between 0 and maxValue
|
||||
if (isNaN(value)) {
|
||||
value = 0;
|
||||
if (mode === 'number') {
|
||||
const parts = textValue.split('/');
|
||||
value = parseInt(parts[0]);
|
||||
if (isNaN(value)) {
|
||||
value = 0;
|
||||
}
|
||||
value = Math.max(0, Math.min(maxValue, value));
|
||||
} else {
|
||||
value = parseInt(textValue.replace('%', ''));
|
||||
if (isNaN(value)) {
|
||||
value = 0;
|
||||
}
|
||||
value = Math.max(0, Math.min(100, value));
|
||||
}
|
||||
value = Math.max(0, Math.min(maxValue, value));
|
||||
} else {
|
||||
// In percentage mode, parse "X%" or just "X"
|
||||
value = parseInt(textValue.replace('%', ''));
|
||||
|
||||
// Validate and clamp value between 0 and 100
|
||||
if (isNaN(value)) {
|
||||
value = 0;
|
||||
extensionSettings.userStats[field] = value;
|
||||
updateUserStatsData();
|
||||
saveSettings();
|
||||
saveChatData();
|
||||
updateMessageSwipeData();
|
||||
renderUserStats();
|
||||
updateFabWidgets();
|
||||
});
|
||||
|
||||
// Add event listeners for mood/conditions editing
|
||||
$userStatsContainer.find('.rpg-mood-emoji.rpg-editable').on('blur', function () {
|
||||
const value = $(this).text().trim();
|
||||
extensionSettings.userStats.mood = value || '😐';
|
||||
updateUserStatsData();
|
||||
saveSettings();
|
||||
saveChatData();
|
||||
updateMessageSwipeData();
|
||||
});
|
||||
|
||||
$userStatsContainer.find('.rpg-mood-conditions.rpg-editable').on('blur', function () {
|
||||
const value = $(this).text().trim();
|
||||
const fieldKey = $(this).data('field');
|
||||
extensionSettings.userStats[fieldKey] = value || 'None';
|
||||
updateUserStatsData();
|
||||
saveSettings();
|
||||
saveChatData();
|
||||
updateMessageSwipeData();
|
||||
});
|
||||
|
||||
// Add event listener for skills editing
|
||||
$userStatsContainer.find('.rpg-skills-value.rpg-editable').on('blur', function () {
|
||||
const value = $(this).text().trim();
|
||||
extensionSettings.userStats.skills = value || 'None';
|
||||
updateUserStatsData();
|
||||
saveSettings();
|
||||
saveChatData();
|
||||
updateMessageSwipeData();
|
||||
});
|
||||
|
||||
// Add event listeners for stat name editing
|
||||
$userStatsContainer.find('.rpg-editable-stat-name').on('blur', function () {
|
||||
const field = $(this).data('field');
|
||||
const value = $(this).text().trim().replace(':', '');
|
||||
|
||||
if (!extensionSettings.statNames) {
|
||||
extensionSettings.statNames = {
|
||||
health: 'Health',
|
||||
satiety: 'Satiety',
|
||||
energy: 'Energy',
|
||||
hygiene: 'Hygiene',
|
||||
arousal: 'Arousal'
|
||||
};
|
||||
}
|
||||
value = Math.max(0, Math.min(100, value));
|
||||
}
|
||||
|
||||
// Update the setting
|
||||
extensionSettings.userStats[field] = value;
|
||||
extensionSettings.statNames[field] = value || extensionSettings.statNames[field];
|
||||
saveSettings();
|
||||
saveChatData();
|
||||
renderUserStats();
|
||||
});
|
||||
|
||||
// Update userStats data (maintains JSON or text format)
|
||||
updateUserStatsData();
|
||||
// Add event listener for level editing
|
||||
$userStatsContainer.find('.rpg-level-value.rpg-editable').on('blur', function () {
|
||||
let value = parseInt($(this).text().trim());
|
||||
if (isNaN(value) || value < 1) {
|
||||
value = 1;
|
||||
}
|
||||
value = Math.min(100, value);
|
||||
|
||||
saveSettings();
|
||||
saveChatData();
|
||||
updateMessageSwipeData();
|
||||
extensionSettings.level = value;
|
||||
saveSettings();
|
||||
saveChatData();
|
||||
updateMessageSwipeData();
|
||||
renderUserStats();
|
||||
});
|
||||
|
||||
// Re-render to update the bar and FAB widgets
|
||||
renderUserStats();
|
||||
updateFabWidgets();
|
||||
});
|
||||
// Prevent line breaks in level field
|
||||
$userStatsContainer.find('.rpg-level-value.rpg-editable').on('keydown', function (e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
$(this).blur();
|
||||
}
|
||||
});
|
||||
|
||||
// Add event listeners for mood/conditions editing
|
||||
$('.rpg-mood-emoji.rpg-editable').on('blur', function () {
|
||||
const value = $(this).text().trim();
|
||||
extensionSettings.userStats.mood = value || '😐';
|
||||
|
||||
// Update userStats data (maintains JSON or text format)
|
||||
updateUserStatsData();
|
||||
|
||||
saveSettings();
|
||||
saveChatData();
|
||||
updateMessageSwipeData();
|
||||
});
|
||||
|
||||
$('.rpg-mood-conditions.rpg-editable').on('blur', function () {
|
||||
const value = $(this).text().trim();
|
||||
const fieldKey = $(this).data('field');
|
||||
extensionSettings.userStats[fieldKey] = value || 'None';
|
||||
|
||||
// Update userStats data (maintains JSON or text format)
|
||||
updateUserStatsData();
|
||||
|
||||
saveSettings();
|
||||
saveChatData();
|
||||
updateMessageSwipeData();
|
||||
});
|
||||
|
||||
// Add event listener for skills editing
|
||||
$('.rpg-skills-value.rpg-editable').on('blur', function () {
|
||||
const value = $(this).text().trim();
|
||||
extensionSettings.userStats.skills = value || 'None';
|
||||
|
||||
// Update userStats data (maintains JSON or text format)
|
||||
updateUserStatsData();
|
||||
|
||||
saveSettings();
|
||||
saveChatData();
|
||||
updateMessageSwipeData();
|
||||
});
|
||||
|
||||
// Add event listeners for stat name editing
|
||||
$('.rpg-editable-stat-name').on('blur', function () {
|
||||
const field = $(this).data('field');
|
||||
const value = $(this).text().trim().replace(':', '');
|
||||
|
||||
if (!extensionSettings.statNames) {
|
||||
extensionSettings.statNames = {
|
||||
health: 'Health',
|
||||
satiety: 'Satiety',
|
||||
energy: 'Energy',
|
||||
hygiene: 'Hygiene',
|
||||
arousal: 'Arousal'
|
||||
};
|
||||
}
|
||||
|
||||
extensionSettings.statNames[field] = value || extensionSettings.statNames[field];
|
||||
|
||||
saveSettings();
|
||||
saveChatData();
|
||||
|
||||
// Re-render to update the display
|
||||
renderUserStats();
|
||||
});
|
||||
|
||||
// Add event listener for level editing
|
||||
$('.rpg-level-value.rpg-editable').on('blur', function () {
|
||||
let value = parseInt($(this).text().trim());
|
||||
if (isNaN(value) || value < 1) {
|
||||
value = 1;
|
||||
}
|
||||
// Set reasonable max level
|
||||
value = Math.min(100, value);
|
||||
|
||||
extensionSettings.level = value;
|
||||
saveSettings();
|
||||
saveChatData();
|
||||
updateMessageSwipeData();
|
||||
|
||||
// Re-render to update the display
|
||||
renderUserStats();
|
||||
});
|
||||
|
||||
// Prevent line breaks in level field
|
||||
$('.rpg-level-value.rpg-editable').on('keydown', function (e) {
|
||||
if (e.key === 'Enter') {
|
||||
// Add event listener for section lock icon clicks (support both click and touch)
|
||||
$userStatsContainer.find('.rpg-section-lock-icon').on('click touchend', function (e) {
|
||||
e.preventDefault();
|
||||
$(this).blur();
|
||||
}
|
||||
});
|
||||
e.stopPropagation();
|
||||
const $icon = $(this);
|
||||
const trackerType = $icon.data('tracker');
|
||||
const itemPath = $icon.data('path');
|
||||
const currentlyLocked = isItemLocked(trackerType, itemPath);
|
||||
|
||||
// Add event listener for section lock icon clicks (support both click and touch)
|
||||
$('.rpg-section-lock-icon').on('click touchend', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const $icon = $(this);
|
||||
const trackerType = $icon.data('tracker');
|
||||
const itemPath = $icon.data('path');
|
||||
const currentlyLocked = isItemLocked(trackerType, itemPath);
|
||||
setItemLock(trackerType, itemPath, !currentlyLocked);
|
||||
|
||||
// Toggle lock state
|
||||
setItemLock(trackerType, itemPath, !currentlyLocked);
|
||||
const newIcon = !currentlyLocked ? '🔒' : '🔓';
|
||||
const newTitle = !currentlyLocked ? (i18n.getTranslation('infoBox.locked') || 'Locked') : (i18n.getTranslation('infoBox.unlocked') || 'Unlocked');
|
||||
$icon.text(newIcon);
|
||||
$icon.attr('title', newTitle);
|
||||
$icon.toggleClass('locked', !currentlyLocked);
|
||||
|
||||
// Update icon
|
||||
const newIcon = !currentlyLocked ? '🔒' : '🔓';
|
||||
const newTitle = !currentlyLocked ? (i18n.getTranslation('infoBox.locked') || 'Locked') : (i18n.getTranslation('infoBox.unlocked') || 'Unlocked');
|
||||
$icon.text(newIcon);
|
||||
$icon.attr('title', newTitle);
|
||||
|
||||
// Toggle 'locked' class for persistent visibility
|
||||
$icon.toggleClass('locked', !currentlyLocked);
|
||||
|
||||
// Save settings
|
||||
saveSettings();
|
||||
});
|
||||
saveSettings();
|
||||
});
|
||||
} // end if (domUpdated)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user