Add French translate

Add French translation and localize hardcoded strings :
Changes
1. Translation Files
Created
src/i18n/fr.json
: Contains all French translations for the extension.
Updated
src/i18n/en.json
: Added new keys for terms that were previously hardcoded (e.g., "Force", "Volonté", "Météo", "Locked", "Unlocked").
2. UI Updates
settings.html
: Added "Français" to the language selection dropdown.
3. Code Refactoring
src/systems/rendering/thoughts.js
: Replaced hardcoded strings ("Add Character", "Locked", "Click to edit") with i18n calls.
src/systems/rendering/userStats.js
: Replaced hardcoded tooltips and titles with i18n calls.
src/systems/rendering/infoBox.js
: Localized weather, location, and date widget texts.
src/systems/ui/trackerEditor.js
: Updated the "Reset to Defaults" logic to use localized names for stats (e.g., "Santé", "Force") based on the active language.
This commit is contained in:
rei
2026-02-18 03:56:18 +01:00
parent 105e20e97a
commit 25aedce786
7 changed files with 770 additions and 449 deletions
+168 -168
View File
@@ -159,171 +159,171 @@ export function renderInfoBox() {
const lines = infoBoxData.split('\n');
// console.log('[RPG Companion] Info Box split into lines:', lines);
// Track which fields we've already parsed to avoid duplicates from mixed formats
const parsedFields = {
date: false,
temperature: false,
time: false,
location: false,
weather: false
};
// Track which fields we've already parsed to avoid duplicates from mixed formats
const parsedFields = {
date: false,
temperature: false,
time: false,
location: false,
weather: false
};
for (const line of lines) {
// console.log('[RPG Companion] Processing line:', line);
for (const line of lines) {
// console.log('[RPG Companion] Processing line:', line);
// Support both new text format (Date:) and legacy emoji format (🗓️:)
// Prioritize text format over emoji format
if (line.startsWith('Date:')) {
if (!parsedFields.date) {
// console.log('[RPG Companion] → Matched DATE (text format)');
const dateStr = line.replace('Date:', '').trim();
const dateParts = dateStr.split(',').map(p => p.trim());
data.weekday = dateParts[0] || '';
data.month = dateParts[1] || '';
data.year = dateParts[2] || '';
data.date = dateStr;
parsedFields.date = true;
}
} else if (line.includes('🗓️:')) {
if (!parsedFields.date) {
// console.log('[RPG Companion] → Matched DATE (emoji format)');
const dateStr = line.replace('🗓️:', '').trim();
const dateParts = dateStr.split(',').map(p => p.trim());
data.weekday = dateParts[0] || '';
data.month = dateParts[1] || '';
data.year = dateParts[2] || '';
data.date = dateStr;
parsedFields.date = true;
}
} else if (line.startsWith('Temperature:')) {
if (!parsedFields.temperature) {
// console.log('[RPG Companion] → Matched TEMPERATURE (text format)');
const tempStr = line.replace('Temperature:', '').trim();
data.temperature = tempStr;
const tempMatch = tempStr.match(/(-?\d+)/);
if (tempMatch) {
data.tempValue = parseInt(tempMatch[1]);
// Support both new text format (Date:) and legacy emoji format (🗓️:)
// Prioritize text format over emoji format
if (line.startsWith('Date:')) {
if (!parsedFields.date) {
// console.log('[RPG Companion] → Matched DATE (text format)');
const dateStr = line.replace('Date:', '').trim();
const dateParts = dateStr.split(',').map(p => p.trim());
data.weekday = dateParts[0] || '';
data.month = dateParts[1] || '';
data.year = dateParts[2] || '';
data.date = dateStr;
parsedFields.date = true;
}
parsedFields.temperature = true;
}
} else if (line.includes('🌡️:')) {
if (!parsedFields.temperature) {
// console.log('[RPG Companion] → Matched TEMPERATURE (emoji format)');
const tempStr = line.replace('🌡️:', '').trim();
data.temperature = tempStr;
const tempMatch = tempStr.match(/(-?\d+)/);
if (tempMatch) {
data.tempValue = parseInt(tempMatch[1]);
} else if (line.includes('🗓️:')) {
if (!parsedFields.date) {
// console.log('[RPG Companion] → Matched DATE (emoji format)');
const dateStr = line.replace('🗓️:', '').trim();
const dateParts = dateStr.split(',').map(p => p.trim());
data.weekday = dateParts[0] || '';
data.month = dateParts[1] || '';
data.year = dateParts[2] || '';
data.date = dateStr;
parsedFields.date = true;
}
parsedFields.temperature = true;
}
} else if (line.startsWith('Time:')) {
if (!parsedFields.time) {
// console.log('[RPG Companion] → Matched TIME (text format)');
const timeStr = line.replace('Time:', '').trim();
data.time = timeStr;
const timeParts = timeStr.split('→').map(t => t.trim());
data.timeStart = timeParts[0] || '';
data.timeEnd = timeParts[1] || '';
parsedFields.time = true;
}
} else if (line.includes('🕒:')) {
if (!parsedFields.time) {
// console.log('[RPG Companion] → Matched TIME (emoji format)');
const timeStr = line.replace('🕒:', '').trim();
data.time = timeStr;
const timeParts = timeStr.split('→').map(t => t.trim());
data.timeStart = timeParts[0] || '';
data.timeEnd = timeParts[1] || '';
parsedFields.time = true;
}
} else if (line.startsWith('Location:')) {
if (!parsedFields.location) {
// console.log('[RPG Companion] → Matched LOCATION (text format)');
data.location = line.replace('Location:', '').trim();
parsedFields.location = true;
}
} else if (line.includes('🗺️:')) {
if (!parsedFields.location) {
// console.log('[RPG Companion] → Matched LOCATION (emoji format)');
data.location = line.replace('🗺️:', '').trim();
parsedFields.location = true;
}
} else if (line.startsWith('Weather:')) {
if (!parsedFields.weather) {
// New text format: Weather: [Emoji], [Forecast] OR Weather: [Emoji][Forecast] (no separator - FIXED)
const weatherStr = line.replace('Weather:', '').trim();
const { emoji, text } = separateEmojiFromText(weatherStr);
if (emoji && text) {
data.weatherEmoji = emoji;
data.weatherForecast = text;
} else if (weatherStr.includes(',')) {
// Fallback to comma split if emoji detection failed - split only on FIRST comma
const firstCommaIndex = weatherStr.indexOf(',');
data.weatherEmoji = weatherStr.substring(0, firstCommaIndex).trim();
data.weatherForecast = weatherStr.substring(firstCommaIndex + 1).trim();
} else {
// No clear separation - assume it's all forecast text
data.weatherEmoji = '🌤️'; // Default emoji
data.weatherForecast = weatherStr;
} else if (line.startsWith('Temperature:')) {
if (!parsedFields.temperature) {
// console.log('[RPG Companion] → Matched TEMPERATURE (text format)');
const tempStr = line.replace('Temperature:', '').trim();
data.temperature = tempStr;
const tempMatch = tempStr.match(/(-?\d+)/);
if (tempMatch) {
data.tempValue = parseInt(tempMatch[1]);
}
parsedFields.temperature = true;
}
} else if (line.includes('🌡️:')) {
if (!parsedFields.temperature) {
// console.log('[RPG Companion] → Matched TEMPERATURE (emoji format)');
const tempStr = line.replace('🌡️:', '').trim();
data.temperature = tempStr;
const tempMatch = tempStr.match(/(-?\d+)/);
if (tempMatch) {
data.tempValue = parseInt(tempMatch[1]);
}
parsedFields.temperature = true;
}
} else if (line.startsWith('Time:')) {
if (!parsedFields.time) {
// console.log('[RPG Companion] → Matched TIME (text format)');
const timeStr = line.replace('Time:', '').trim();
data.time = timeStr;
const timeParts = timeStr.split('→').map(t => t.trim());
data.timeStart = timeParts[0] || '';
data.timeEnd = timeParts[1] || '';
parsedFields.time = true;
}
} else if (line.includes('🕒:')) {
if (!parsedFields.time) {
// console.log('[RPG Companion] → Matched TIME (emoji format)');
const timeStr = line.replace('🕒:', '').trim();
data.time = timeStr;
const timeParts = timeStr.split('→').map(t => t.trim());
data.timeStart = timeParts[0] || '';
data.timeEnd = timeParts[1] || '';
parsedFields.time = true;
}
} else if (line.startsWith('Location:')) {
if (!parsedFields.location) {
// console.log('[RPG Companion] → Matched LOCATION (text format)');
data.location = line.replace('Location:', '').trim();
parsedFields.location = true;
}
} else if (line.includes('🗺️:')) {
if (!parsedFields.location) {
// console.log('[RPG Companion] → Matched LOCATION (emoji format)');
data.location = line.replace('🗺️:', '').trim();
parsedFields.location = true;
}
} else if (line.startsWith('Weather:')) {
if (!parsedFields.weather) {
// New text format: Weather: [Emoji], [Forecast] OR Weather: [Emoji][Forecast] (no separator - FIXED)
const weatherStr = line.replace('Weather:', '').trim();
const { emoji, text } = separateEmojiFromText(weatherStr);
parsedFields.weather = true;
}
} else {
// Check if it's a legacy weather line (emoji format)
// Only parse if we haven't already found weather in text format
if (!parsedFields.weather) {
// Since \p{Emoji} doesn't work reliably, use a simpler approach
const hasColon = line.includes(':');
const notInfoBox = !line.includes('Info Box');
const notDivider = !line.includes('---');
const notCodeFence = !line.trim().startsWith('```');
if (emoji && text) {
data.weatherEmoji = emoji;
data.weatherForecast = text;
} else if (weatherStr.includes(',')) {
// Fallback to comma split if emoji detection failed - split only on FIRST comma
const firstCommaIndex = weatherStr.indexOf(',');
data.weatherEmoji = weatherStr.substring(0, firstCommaIndex).trim();
data.weatherForecast = weatherStr.substring(firstCommaIndex + 1).trim();
} else {
// No clear separation - assume it's all forecast text
data.weatherEmoji = '🌤️'; // Default emoji
data.weatherForecast = weatherStr;
}
// console.log('[RPG Companion] → Checking weather conditions:', {
// line: line,
// hasColon: hasColon,
// notInfoBox: notInfoBox,
// notDivider: notDivider
// });
parsedFields.weather = true;
}
} else {
// Check if it's a legacy weather line (emoji format)
// Only parse if we haven't already found weather in text format
if (!parsedFields.weather) {
// Since \p{Emoji} doesn't work reliably, use a simpler approach
const hasColon = line.includes(':');
const notInfoBox = !line.includes('Info Box');
const notDivider = !line.includes('---');
const notCodeFence = !line.trim().startsWith('```');
if (hasColon && notInfoBox && notDivider && notCodeFence && line.trim().length > 0) {
// Match format: [Weather Emoji]: [Forecast]
// Capture everything before colon as emoji, everything after as forecast
// console.log('[RPG Companion] → Testing WEATHER match for:', line);
const weatherMatch = line.match(/^\s*([^:]+):\s*(.+)$/);
if (weatherMatch) {
const potentialEmoji = weatherMatch[1].trim();
const forecast = weatherMatch[2].trim();
// console.log('[RPG Companion] → Checking weather conditions:', {
// line: line,
// hasColon: hasColon,
// notInfoBox: notInfoBox,
// notDivider: notDivider
// });
// If the first part is short (likely emoji), treat as weather
if (potentialEmoji.length <= 5) {
data.weatherEmoji = potentialEmoji;
data.weatherForecast = forecast;
parsedFields.weather = true;
// console.log('[RPG Companion] ✓ Weather parsed:', data.weatherEmoji, data.weatherForecast);
if (hasColon && notInfoBox && notDivider && notCodeFence && line.trim().length > 0) {
// Match format: [Weather Emoji]: [Forecast]
// Capture everything before colon as emoji, everything after as forecast
// console.log('[RPG Companion] → Testing WEATHER match for:', line);
const weatherMatch = line.match(/^\s*([^:]+):\s*(.+)$/);
if (weatherMatch) {
const potentialEmoji = weatherMatch[1].trim();
const forecast = weatherMatch[2].trim();
// If the first part is short (likely emoji), treat as weather
if (potentialEmoji.length <= 5) {
data.weatherEmoji = potentialEmoji;
data.weatherForecast = forecast;
parsedFields.weather = true;
// console.log('[RPG Companion] ✓ Weather parsed:', data.weatherEmoji, data.weatherForecast);
} else {
// console.log('[RPG Companion] ✗ First part too long for emoji:', potentialEmoji);
}
} else {
// console.log('[RPG Companion] ✗ First part too long for emoji:', potentialEmoji);
// console.log('[RPG Companion] ✗ Weather regex did not match');
}
} else {
// console.log('[RPG Companion] ✗ Weather regex did not match');
// console.log('[RPG Companion] → No match for this line');
}
} else {
// console.log('[RPG Companion] → No match for this line');
}
}
}
}
// console.log('[RPG Companion] Parsed Info Box data:', {
// date: data.date,
// weatherEmoji: data.weatherEmoji,
// weatherForecast: data.weatherForecast,
// temperature: data.temperature,
// timeStart: data.timeStart,
// location: data.location
// });
// console.log('[RPG Companion] Parsed Info Box data:', {
// date: data.date,
// weatherEmoji: data.weatherEmoji,
// weatherForecast: data.weatherForecast,
// temperature: data.temperature,
// timeStart: data.timeStart,
// location: data.location
// });
}
// Get tracker configuration
@@ -363,9 +363,9 @@ export function renderInfoBox() {
row1Widgets.push(`
<div class="rpg-dashboard-widget rpg-calendar-widget">
${dateLockIconHtml}
<div class="rpg-calendar-top rpg-editable" contenteditable="true" data-field="month" data-full-value="${data.month || ''}" title="Click to edit">${monthDisplay}</div>
<div class="rpg-calendar-day" title="Click to edit"><span class="rpg-calendar-day-text rpg-editable" contenteditable="true" data-field="weekday" data-full-value="${data.weekday || ''}">${weekdayDisplay}</span></div>
<div class="rpg-calendar-year rpg-editable" contenteditable="true" data-field="year" data-full-value="${data.year || ''}" title="Click to edit">${yearDisplay}</div>
<div class="rpg-calendar-top rpg-editable" contenteditable="true" data-field="month" data-full-value="${data.month || ''}" title="${i18n.getTranslation('infoBox.clickToEdit')}">${monthDisplay}</div>
<div class="rpg-calendar-day" title="${i18n.getTranslation('infoBox.clickToEdit')}"><span class="rpg-calendar-day-text rpg-editable" contenteditable="true" data-field="weekday" data-full-value="${data.weekday || ''}">${weekdayDisplay}</span></div>
<div class="rpg-calendar-year rpg-editable" contenteditable="true" data-field="year" data-full-value="${data.year || ''}" title="${i18n.getTranslation('infoBox.clickToEdit')}">${yearDisplay}</div>
</div>
`);
}
@@ -373,14 +373,14 @@ export function renderInfoBox() {
// Weather widget - show if enabled
if (config?.widgets?.weather?.enabled) {
const weatherEmoji = data.weatherEmoji || '🌤️';
const weatherForecast = data.weatherForecast || 'Weather';
const weatherForecast = data.weatherForecast || i18n.getTranslation('infoBox.weatherFallback');
const weatherLockIconHtml = getLockIconHtml('infoBox', 'weather');
row1Widgets.push(`
<div class="rpg-dashboard-widget rpg-weather-widget">
${weatherLockIconHtml}
<div class="rpg-weather-icon rpg-editable" contenteditable="true" data-field="weatherEmoji" title="Click to edit emoji">${weatherEmoji}</div>
<div class="rpg-weather-forecast rpg-editable" contenteditable="true" data-field="weatherForecast" title="Click to edit">${weatherForecast}</div>
<div class="rpg-weather-icon rpg-editable" contenteditable="true" data-field="weatherEmoji" title="${i18n.getTranslation('userStats.clickToEditEmoji')}">${weatherEmoji}</div>
<div class="rpg-weather-forecast rpg-editable" contenteditable="true" data-field="weatherForecast" title="${i18n.getTranslation('infoBox.clickToEdit')}">${weatherForecast}</div>
</div>
`);
}
@@ -399,12 +399,12 @@ export function renderInfoBox() {
if (preferredUnit === 'F' && isCelsius) {
// Convert C to F
const fahrenheit = Math.round((tempValue * 9/5) + 32);
const fahrenheit = Math.round((tempValue * 9 / 5) + 32);
tempDisplay = `${fahrenheit}°F`;
tempValue = fahrenheit;
} else if (preferredUnit === 'C' && isFahrenheit) {
// Convert F to C
const celsius = Math.round((tempValue - 32) * 5/9);
const celsius = Math.round((tempValue - 32) * 5 / 9);
tempDisplay = `${celsius}°C`;
tempValue = celsius;
}
@@ -415,7 +415,7 @@ export function renderInfoBox() {
}
// Calculate thermometer display (convert to Celsius for consistent thresholds)
const tempInCelsius = preferredUnit === 'F' ? Math.round((tempValue - 32) * 5/9) : tempValue;
const tempInCelsius = preferredUnit === 'F' ? Math.round((tempValue - 32) * 5 / 9) : tempValue;
const tempPercent = Math.min(100, Math.max(0, ((tempInCelsius + 20) / 60) * 100));
const tempColor = tempInCelsius < 10 ? '#4a90e2' : tempInCelsius < 25 ? '#67c23a' : '#e94560';
const tempLockIconHtml = getLockIconHtml('infoBox', 'temperature');
@@ -429,7 +429,7 @@ export function renderInfoBox() {
<div class="rpg-thermometer-fill" style="height: ${tempPercent}%; background: ${tempColor}"></div>
</div>
</div>
<div class="rpg-temp-value rpg-editable" contenteditable="true" data-field="temperature" title="Click to edit">${tempDisplay}</div>
<div class="rpg-temp-value rpg-editable" contenteditable="true" data-field="temperature" title="${i18n.getTranslation('infoBox.clickToEdit')}">${tempDisplay}</div>
</div>
`);
}
@@ -464,9 +464,9 @@ export function renderInfoBox() {
</div>
</div>
<div class="rpg-time-range">
<div class="rpg-time-value rpg-editable" contenteditable="true" data-field="timeStart" title="Click to edit start time">${timeStartDisplay}</div>
<div class="rpg-time-value rpg-editable" contenteditable="true" data-field="timeStart" title="${i18n.getTranslation('infoBox.clickToEdit')}">${timeStartDisplay}</div>
<span class="rpg-time-separator">→</span>
<div class="rpg-time-value rpg-editable" contenteditable="true" data-field="timeEnd" title="Click to edit end time">${timeEndDisplay}</div>
<div class="rpg-time-value rpg-editable" contenteditable="true" data-field="timeEnd" title="${i18n.getTranslation('infoBox.clickToEdit')}">${timeEndDisplay}</div>
</div>
</div>
`);
@@ -481,7 +481,7 @@ export function renderInfoBox() {
// Row 2: Location widget (full width) - show if enabled
if (config?.widgets?.location?.enabled) {
const locationDisplay = data.location || 'Location';
const locationDisplay = data.location || i18n.getTranslation('infoBox.locationFallback');
const locationLockIconHtml = getLockIconHtml('infoBox', 'location');
html += `
@@ -491,7 +491,7 @@ export function renderInfoBox() {
<div class="rpg-map-bg">
<div class="rpg-map-marker">📍</div>
</div>
<div class="rpg-location-text rpg-editable" contenteditable="true" data-field="location" title="Click to edit">${locationDisplay}</div>
<div class="rpg-location-text rpg-editable" contenteditable="true" data-field="location" title="${i18n.getTranslation('infoBox.clickToEdit')}">${locationDisplay}</div>
</div>
</div>
`;
@@ -550,7 +550,7 @@ export function renderInfoBox() {
html += `
<div class="rpg-notebook-line">
<span class="rpg-bullet">•</span>
<span class="rpg-event-text rpg-editable" contenteditable="true" data-field="event${i + 1}" title="Click to edit">${validEvents[i]}</span>
<span class="rpg-event-text rpg-editable" contenteditable="true" data-field="event${i + 1}" title="${i18n.getTranslation('infoBox.clickToEdit')}">${validEvents[i]}</span>
</div>
`;
}
@@ -591,7 +591,7 @@ export function renderInfoBox() {
}
// Add event handlers for editable Info Box fields
$infoBoxContainer.find('.rpg-editable').on('blur', function() {
$infoBoxContainer.find('.rpg-editable').on('blur', function () {
const $this = $(this);
const field = $this.data('field');
const value = $this.text().trim();
@@ -624,12 +624,12 @@ export function renderInfoBox() {
});
// Update location size on input as well (real-time)
$infoBoxContainer.find('[data-field="location"]').on('input', function() {
$infoBoxContainer.find('[data-field="location"]').on('input', function () {
updateLocationTextSize($(this));
});
// For date fields, show full value on focus
$infoBoxContainer.find('[data-field="month"], [data-field="weekday"], [data-field="year"]').on('focus', function() {
$infoBoxContainer.find('[data-field="month"], [data-field="weekday"], [data-field="year"]').on('focus', function () {
const fullValue = $(this).data('full-value');
if (fullValue) {
$(this).text(fullValue);
@@ -637,7 +637,7 @@ export function renderInfoBox() {
});
// Add event handler for lock icons (support both click and touch)
$infoBoxContainer.find('.rpg-section-lock-icon').on('click touchend', function(e) {
$infoBoxContainer.find('.rpg-section-lock-icon').on('click touchend', function (e) {
e.preventDefault();
e.stopPropagation();
const $lockIcon = $(this);
@@ -652,7 +652,7 @@ export function renderInfoBox() {
// Update icon
$lockIcon.text(newLockState ? '🔒' : '🔓');
$lockIcon.attr('title', newLockState ? 'Locked - AI cannot change this' : 'Unlocked - AI can change this');
$lockIcon.attr('title', newLockState ? i18n.getTranslation('infoBox.locked') : i18n.getTranslation('infoBox.unlocked'));
$lockIcon.toggleClass('locked', newLockState);
// Save settings to persist lock state
+157 -156
View File
@@ -14,6 +14,7 @@ import {
FALLBACK_AVATAR_DATA_URI,
addDebugLog
} from '../../core/state.js';
import { i18n } from '../../core/i18n.js';
import { saveChatData, saveSettings } from '../../core/persistence.js';
import { getSafeThumbnailUrl } from '../../utils/avatars.js';
import { isItemLocked, setItemLock } from '../generation/lockManager.js';
@@ -30,7 +31,7 @@ function getLockIconHtml(tracker, path) {
const isLocked = isItemLocked(tracker, path);
const lockIcon = isLocked ? '🔒' : '🔓';
const lockTitle = isLocked ? 'Locked' : 'Unlocked';
const lockTitle = isLocked ? i18n.getTranslation('thoughts.locked') : i18n.getTranslation('thoughts.unlocked');
const lockedClass = isLocked ? ' locked' : '';
return `<span class="rpg-section-lock-icon${lockedClass}" data-tracker="${tracker}" data-path="${path}" title="${lockTitle}">${lockIcon}</span>`;
}
@@ -300,88 +301,88 @@ export function renderThoughts({ preserveScroll = false } = {}) {
debugLog('[RPG Thoughts] Split into lines count:', lines.length);
debugLog('[RPG Thoughts] Lines:', lines);
// Parse new multi-line format:
// - [Name]
// Details: [Emoji] | [Field1] | [Field2] | ...
// Relationship: [Relationship]
// Stats: Stat1: X% | Stat2: X% | ...
// Thoughts: [Description]
let lineNumber = 0;
let currentCharacter = null;
// Parse new multi-line format:
// - [Name]
// Details: [Emoji] | [Field1] | [Field2] | ...
// Relationship: [Relationship]
// Stats: Stat1: X% | Stat2: X% | ...
// Thoughts: [Description]
let lineNumber = 0;
let currentCharacter = null;
for (const line of lines) {
lineNumber++;
for (const line of lines) {
lineNumber++;
// Skip empty lines, headers, dividers, and code fences
if (!line.trim() ||
line.includes('Present Characters') ||
line.includes('---') ||
line.trim().startsWith('```') ||
line.trim() === '- …' ||
line.includes('(Repeat the format')) {
continue;
}
debugLog(`[RPG Thoughts] Processing line ${lineNumber}:`, line);
// Check if this is a character name line (starts with "- ")
if (line.trim().startsWith('- ')) {
const name = line.trim().substring(2).trim();
if (name && name.toLowerCase() !== 'unavailable') {
currentCharacter = { name };
presentCharacters.push(currentCharacter);
debugLog(`[RPG Thoughts] ✓ Started new character: ${name}`);
} else {
currentCharacter = null;
debugLog(`[RPG Thoughts] ✗ Rejected character - name: "${name}" (unavailable or empty)`);
}
}
// Check if this is a Details line
else if (line.trim().startsWith('Details:') && currentCharacter) {
const detailsContent = line.substring(line.indexOf(':') + 1).trim();
const parts = detailsContent.split('|').map(p => p.trim());
// First part is the emoji
if (parts.length > 0) {
currentCharacter.emoji = parts[0];
debugLog(`[RPG Thoughts] Parsed emoji: ${parts[0]}`);
// Skip empty lines, headers, dividers, and code fences
if (!line.trim() ||
line.includes('Present Characters') ||
line.includes('---') ||
line.trim().startsWith('```') ||
line.trim() === '- …' ||
line.includes('(Repeat the format')) {
continue;
}
// Remaining parts are custom fields
for (let i = 0; i < enabledFields.length && i + 1 < parts.length; i++) {
const fieldName = enabledFields[i].name;
currentCharacter[fieldName] = parts[i + 1];
debugLog(`[RPG Thoughts] Parsed field ${fieldName}: ${parts[i + 1]}`);
}
}
// Check if this is a Relationship line
else if (line.trim().startsWith('Relationship:') && currentCharacter) {
const relationship = line.substring(line.indexOf(':') + 1).trim();
currentCharacter.Relationship = relationship;
debugLog(`[RPG Thoughts] Parsed relationship: ${relationship}`);
}
// Check if this is a Stats line
else if (line.trim().startsWith('Stats:') && currentCharacter && enabledCharStats.length > 0) {
const statsContent = line.substring(line.indexOf(':') + 1).trim();
const statParts = statsContent.split('|').map(p => p.trim());
debugLog(`[RPG Thoughts] Processing line ${lineNumber}:`, line);
for (const statPart of statParts) {
const statMatch = statPart.match(/^(.+?):\s*(\d+)%$/);
if (statMatch) {
const statName = statMatch[1].trim();
const statValue = parseInt(statMatch[2]);
currentCharacter[statName] = statValue;
debugLog(`[RPG Thoughts] Parsed stat: ${statName} = ${statValue}%`);
// Check if this is a character name line (starts with "- ")
if (line.trim().startsWith('- ')) {
const name = line.trim().substring(2).trim();
if (name && name.toLowerCase() !== 'unavailable') {
currentCharacter = { name };
presentCharacters.push(currentCharacter);
debugLog(`[RPG Thoughts] ✓ Started new character: ${name}`);
} else {
currentCharacter = null;
debugLog(`[RPG Thoughts] ✗ Rejected character - name: "${name}" (unavailable or empty)`);
}
}
// Check if this is a Details line
else if (line.trim().startsWith('Details:') && currentCharacter) {
const detailsContent = line.substring(line.indexOf(':') + 1).trim();
const parts = detailsContent.split('|').map(p => p.trim());
// First part is the emoji
if (parts.length > 0) {
currentCharacter.emoji = parts[0];
debugLog(`[RPG Thoughts] Parsed emoji: ${parts[0]}`);
}
// Remaining parts are custom fields
for (let i = 0; i < enabledFields.length && i + 1 < parts.length; i++) {
const fieldName = enabledFields[i].name;
currentCharacter[fieldName] = parts[i + 1];
debugLog(`[RPG Thoughts] Parsed field ${fieldName}: ${parts[i + 1]}`);
}
}
// Check if this is a Relationship line
else if (line.trim().startsWith('Relationship:') && currentCharacter) {
const relationship = line.substring(line.indexOf(':') + 1).trim();
currentCharacter.Relationship = relationship;
debugLog(`[RPG Thoughts] Parsed relationship: ${relationship}`);
}
// Check if this is a Stats line
else if (line.trim().startsWith('Stats:') && currentCharacter && enabledCharStats.length > 0) {
const statsContent = line.substring(line.indexOf(':') + 1).trim();
const statParts = statsContent.split('|').map(p => p.trim());
for (const statPart of statParts) {
const statMatch = statPart.match(/^(.+?):\s*(\d+)%$/);
if (statMatch) {
const statName = statMatch[1].trim();
const statValue = parseInt(statMatch[2]);
currentCharacter[statName] = statValue;
debugLog(`[RPG Thoughts] Parsed stat: ${statName} = ${statValue}%`);
}
}
}
// Check if this is a Thoughts line (handled separately for thought bubbles)
else if (line.trim().match(/^[A-Z][a-z]+:/) && currentCharacter) {
// This could be Thoughts, Feelings, etc. - skip for now, handled in thought bubble rendering
debugLog(`[RPG Thoughts] Skipping thoughts/feelings line (handled in bubble rendering)`);
}
}
// Check if this is a Thoughts line (handled separately for thought bubbles)
else if (line.trim().match(/^[A-Z][a-z]+:/) && currentCharacter) {
// This could be Thoughts, Feelings, etc. - skip for now, handled in thought bubble rendering
debugLog(`[RPG Thoughts] Skipping thoughts/feelings line (handled in bubble rendering)`);
}
}
} // End of text format parsing
// Get relationship emojis from config (with fallback defaults)
@@ -502,14 +503,14 @@ export function renderThoughts({ preserveScroll = false } = {}) {
html += `
<div class="rpg-character-card" data-character-name="${char.name}">
<div class="rpg-character-header-row">
<div class="rpg-character-avatar rpg-avatar-upload" data-character="${char.name}" title="Click to upload avatar">
<div class="rpg-character-avatar rpg-avatar-upload" data-character="${char.name}" title="${i18n.getTranslation('thoughts.clickToUpload')}">
<img src="${characterPortrait}" alt="${char.name}" onerror="this.style.opacity='0.5';this.onerror=null;" />
${hasRelationshipEnabled ? `<div class="rpg-relationship-badge rpg-editable" contenteditable="true" data-character="${char.name}" data-field="${relationshipFieldName}" title="Click to edit (use emoji: ⚔️ ⚖️ ⭐ ❤️)">${relationshipBadge}</div>` : ''}
${hasRelationshipEnabled ? `<div class="rpg-relationship-badge rpg-editable" contenteditable="true" data-character="${char.name}" data-field="${relationshipFieldName}" title="${i18n.getTranslation('thoughts.clickToEdit')} (emoji: ⚔️ ⚖️ ⭐ ❤️)">${relationshipBadge}</div>` : ''}
</div>
<div class="rpg-character-header">
<span class="rpg-character-emoji rpg-editable" contenteditable="true" data-character="${char.name}" data-field="emoji" title="Click to edit emoji">${char.emoji}</span>
<span class="rpg-character-name rpg-editable" contenteditable="true" data-character="${char.name}" data-field="name" title="Click to edit name">${char.name}</span>
<button class="rpg-character-remove" data-character="${char.name}" title="Remove character">×</button>
<span class="rpg-character-emoji rpg-editable" contenteditable="true" data-character="${char.name}" data-field="emoji" title="${i18n.getTranslation('thoughts.clickToEdit')}">${char.emoji}</span>
<span class="rpg-character-name rpg-editable" contenteditable="true" data-character="${char.name}" data-field="name" title="${i18n.getTranslation('thoughts.clickToEdit')}">${char.name}</span>
<button class="rpg-character-remove" data-character="${char.name}" title="${i18n.getTranslation('thoughts.removeCharacter')}">×</button>
</div>
</div>
<div class="rpg-character-content">
@@ -532,12 +533,12 @@ export function renderThoughts({ preserveScroll = false } = {}) {
html += `
<div class="rpg-character-field rpg-character-${fieldId}" style="position: relative;">
${lockIconHtml}
<span class="rpg-editable${emptyClass}" contenteditable="true" data-character="${char.name}" data-field="${field.name}" title="Click to edit ${field.name}" ${placeholder}>${fieldValue}</span>
<span class="rpg-editable${emptyClass}" contenteditable="true" data-character="${char.name}" data-field="${field.name}" title="${i18n.getTranslation('thoughts.clickToEdit')}" ${placeholder}>${fieldValue}</span>
</div>
`;
} else {
html += `
<div class="rpg-character-field rpg-character-${fieldId} rpg-editable${emptyClass}" contenteditable="true" data-character="${char.name}" data-field="${field.name}" title="Click to edit ${field.name}" ${placeholder}>${fieldValue}</div>
<div class="rpg-character-field rpg-character-${fieldId} rpg-editable${emptyClass}" contenteditable="true" data-character="${char.name}" data-field="${field.name}" title="${i18n.getTranslation('thoughts.clickToEdit')}" ${placeholder}>${fieldValue}</div>
`;
}
}
@@ -563,7 +564,7 @@ export function renderThoughts({ preserveScroll = false } = {}) {
);
html += `
<div class="rpg-character-stat">
<span class="rpg-stat-name">${stat.name}: </span><span class="rpg-editable" contenteditable="true" data-character="${char.name}" data-field="${stat.name}" style="color: ${statColor}" title="Click to edit ${stat.name}">${statValue}%</span>
<span class="rpg-stat-name">${stat.name}: </span><span class="rpg-editable" contenteditable="true" data-character="${char.name}" data-field="${stat.name}" style="color: ${statColor}" title="${i18n.getTranslation('thoughts.clickToEdit')}">${statValue}%</span>
</div>
`;
}
@@ -589,8 +590,8 @@ export function renderThoughts({ preserveScroll = false } = {}) {
// Add "Add Character" button if data exists (inside rpg-thoughts-content)
if (presentCharacters.length > 0) {
html += `
<button class="rpg-add-character-btn" title="Add a new character">
<i class="fa-solid fa-plus"></i> Add Character
<button class="rpg-add-character-btn" title="${i18n.getTranslation('thoughts.addCharacter')}">
<i class="fa-solid fa-plus"></i> ${i18n.getTranslation('thoughts.addCharacter')}
</button>
`;
}
@@ -604,7 +605,7 @@ export function renderThoughts({ preserveScroll = false } = {}) {
debugLog('[RPG Thoughts] =======================================================');
// Add event handlers for editable character fields
$thoughtsContainer.find('.rpg-editable').on('blur', function() {
$thoughtsContainer.find('.rpg-editable').on('blur', function () {
const character = $(this).data('character');
const field = $(this).data('field');
const value = $(this).text().trim();
@@ -613,12 +614,12 @@ export function renderThoughts({ preserveScroll = false } = {}) {
});
// Prevent click events on editable elements from bubbling to avatar upload handler
$thoughtsContainer.find('.rpg-editable').on('click mousedown', function(e) {
$thoughtsContainer.find('.rpg-editable').on('click mousedown', function (e) {
e.stopPropagation();
});
// Add event listener for section lock icon clicks (support both click and touch)
$thoughtsContainer.find('.rpg-section-lock-icon').on('click touchend', function(e) {
$thoughtsContainer.find('.rpg-section-lock-icon').on('click touchend', function (e) {
e.preventDefault();
e.stopPropagation();
const $icon = $(this);
@@ -643,7 +644,7 @@ export function renderThoughts({ preserveScroll = false } = {}) {
});
// Add event listener for character remove button
$thoughtsContainer.find('.rpg-character-remove').on('click', function(e) {
$thoughtsContainer.find('.rpg-character-remove').on('click', function (e) {
e.preventDefault();
e.stopPropagation();
@@ -652,7 +653,7 @@ export function renderThoughts({ preserveScroll = false } = {}) {
});
// Add event listener for avatar upload clicks
$thoughtsContainer.find('.rpg-avatar-upload').on('click', function(e) {
$thoughtsContainer.find('.rpg-avatar-upload').on('click', function (e) {
e.preventDefault();
e.stopPropagation();
@@ -661,13 +662,13 @@ export function renderThoughts({ preserveScroll = false } = {}) {
// Create hidden file input
const fileInput = $('<input type="file" accept="image/*" style="display: none;">');
fileInput.on('change', function() {
fileInput.on('change', function () {
const file = this.files[0];
if (!file) return;
// Read file as data URL
const reader = new FileReader();
reader.onload = function(e) {
reader.onload = function (e) {
const imageUrl = e.target.result;
// Store in npcAvatars
@@ -694,20 +695,20 @@ export function renderThoughts({ preserveScroll = false } = {}) {
});
// Add event listener for "Add Character" button (support both click and touch for mobile)
$thoughtsContainer.find('.rpg-add-character-btn').on('click touchend', function(e) {
$thoughtsContainer.find('.rpg-add-character-btn').on('click touchend', function (e) {
e.preventDefault();
e.stopPropagation();
addNewCharacter();
});
// Handle empty field focus - remove placeholder styling on focus
$thoughtsContainer.find('.rpg-editable.rpg-empty-field').on('focus', function() {
$thoughtsContainer.find('.rpg-editable.rpg-empty-field').on('focus', function () {
$(this).removeClass('rpg-empty-field');
$(this).removeAttr('data-placeholder');
});
// Restore placeholder if field becomes empty on blur (after the main blur handler)
$thoughtsContainer.find('.rpg-editable').on('blur', function() {
$thoughtsContainer.find('.rpg-editable').on('blur', function () {
const $this = $(this);
if (!$this.text().trim()) {
const field = $this.data('field');
@@ -1494,59 +1495,59 @@ export function updateChatThoughts() {
if (thoughtsArray.length === 0) {
const lines = lastGeneratedData.characterThoughts.split('\n');
// console.log('[RPG Companion] Parsing thoughts from lines:', lines);
// console.log('[RPG Companion] Parsing thoughts from lines:', lines);
// Parse new format to build character map and thoughts
let currentCharName = null;
let currentCharEmoji = null;
// Parse new format to build character map and thoughts
let currentCharName = null;
let currentCharEmoji = null;
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (!line ||
line.includes('Present Characters') ||
line.includes('---') ||
line.startsWith('```') ||
line.trim() === '- …' ||
line.includes('(Repeat the format')) {
continue;
}
if (!line ||
line.includes('Present Characters') ||
line.includes('---') ||
line.startsWith('```') ||
line.trim() === '- …' ||
line.includes('(Repeat the format')) {
continue;
}
// Check if this is a character name line (starts with "- ")
if (line.startsWith('- ')) {
const name = line.substring(2).trim();
if (name && name.toLowerCase() !== 'unavailable') {
currentCharName = name;
currentCharEmoji = null; // Reset emoji for new character
} else {
currentCharName = null;
currentCharEmoji = null;
// Check if this is a character name line (starts with "- ")
if (line.startsWith('- ')) {
const name = line.substring(2).trim();
if (name && name.toLowerCase() !== 'unavailable') {
currentCharName = name;
currentCharEmoji = null; // Reset emoji for new character
} else {
currentCharName = null;
currentCharEmoji = null;
}
}
// Check if this is a Details line (contains the emoji)
else if (line.startsWith('Details:') && currentCharName) {
const detailsContent = line.substring(line.indexOf(':') + 1).trim();
const parts = detailsContent.split('|').map(p => p.trim());
// First part is the emoji
if (parts.length > 0) {
currentCharEmoji = parts[0];
}
}
// Check if this is a Thoughts line
else if (line.startsWith(thoughtsLabel + ':') && currentCharName && currentCharEmoji) {
const thoughtContent = line.substring(thoughtsLabel.length + 1).trim();
// The thought content is just the text (no emoji prefix in new format)
if (thoughtContent) {
thoughtsArray.push({
name: currentCharName.toLowerCase(),
emoji: currentCharEmoji,
thought: thoughtContent
});
}
}
}
// Check if this is a Details line (contains the emoji)
else if (line.startsWith('Details:') && currentCharName) {
const detailsContent = line.substring(line.indexOf(':') + 1).trim();
const parts = detailsContent.split('|').map(p => p.trim());
// First part is the emoji
if (parts.length > 0) {
currentCharEmoji = parts[0];
}
}
// Check if this is a Thoughts line
else if (line.startsWith(thoughtsLabel + ':') && currentCharName && currentCharEmoji) {
const thoughtContent = line.substring(thoughtsLabel.length + 1).trim();
// The thought content is just the text (no emoji prefix in new format)
if (thoughtContent) {
thoughtsArray.push({
name: currentCharName.toLowerCase(),
emoji: currentCharEmoji,
thought: thoughtContent
});
}
}
}
} // End of text format parsing for thoughts bubbles
debugLog('[RPG Thoughts] Parsed thoughts:', thoughtsArray);
@@ -1628,7 +1629,7 @@ function attachDragHandlersToIcon($icon) {
$icon.off('.thoughtIconDrag');
// Test: add a simple click handler to verify events work
$icon.on('click.thoughtIconDrag', function(e) {
$icon.on('click.thoughtIconDrag', function (e) {
// Check global flag set immediately after drag completes
if (justFinishedDragging) {
// console.log('[Thought Icon] CLICK blocked - just finished dragging');
@@ -1641,7 +1642,7 @@ function attachDragHandlersToIcon($icon) {
});
// Touch drag support - mobile only
$icon.on('touchstart.thoughtIconDrag', function(e) {
$icon.on('touchstart.thoughtIconDrag', function (e) {
if (window.innerWidth > 1000) return;
// console.log('[Thought Icon] touchstart');
@@ -1658,7 +1659,7 @@ function attachDragHandlersToIcon($icon) {
isDragging = false;
});
$icon.on('touchmove.thoughtIconDrag', function(e) {
$icon.on('touchmove.thoughtIconDrag', function (e) {
if (window.innerWidth > 1000) return;
if (!touchMoved) {
@@ -1701,7 +1702,7 @@ function attachDragHandlersToIcon($icon) {
}
});
$icon.on('touchend.thoughtIconDrag', function(e) {
$icon.on('touchend.thoughtIconDrag', function (e) {
// console.log('[Thought Icon] touchend - isDragging:', isDragging, 'touchMoved:', touchMoved);
if (isDragging) {
@@ -1756,7 +1757,7 @@ function attachDragHandlersToIcon($icon) {
// Mouse drag support - mobile only
let mouseDown = false;
$icon.on('mousedown.thoughtIconDrag', function(e) {
$icon.on('mousedown.thoughtIconDrag', function (e) {
if (window.innerWidth > 1000) return;
// console.log('[Thought Icon] mousedown');
@@ -1775,7 +1776,7 @@ function attachDragHandlersToIcon($icon) {
isDragging = false;
});
$(document).on('mousemove.thoughtIconDrag', function(e) {
$(document).on('mousemove.thoughtIconDrag', function (e) {
if (!mouseDown || window.innerWidth > 1000) return;
if (!touchMoved) {
@@ -1819,7 +1820,7 @@ function attachDragHandlersToIcon($icon) {
}
});
$(document).on('mouseup.thoughtIconDrag', function(e) {
$(document).on('mouseup.thoughtIconDrag', function (e) {
if (!mouseDown) return;
// console.log('[Thought Icon] mouseup - isDragging:', isDragging, 'touchMoved:', touchMoved);
@@ -2222,7 +2223,7 @@ export function createThoughtPanel($message, thoughtsArray) {
});
// Close button functionality - support both click and touch
$thoughtPanel.find('.rpg-thought-close').on('click touchend', function(e) {
$thoughtPanel.find('.rpg-thought-close').on('click touchend', function (e) {
e.preventDefault();
e.stopPropagation();
@@ -2230,7 +2231,7 @@ export function createThoughtPanel($message, thoughtsArray) {
if (isMobileView) {
// Mobile: hide panel and show icon
$thoughtPanel.fadeOut(200, function() {
$thoughtPanel.fadeOut(200, function () {
// Make sure icon is visible and clean state when panel closes (use selector, not variable)
const $icon = $('#rpg-thought-icon');
$icon.removeClass('rpg-hidden dragging');
@@ -2252,14 +2253,14 @@ export function createThoughtPanel($message, thoughtsArray) {
$icon.addClass('rpg-collapsed-desktop');
// Hide panel and show icon
$thoughtPanel.fadeOut(200, function() {
$thoughtPanel.fadeOut(200, function () {
$icon.removeClass('rpg-hidden rpg-force-hide');
});
}
});
// Icon click/tap to show panel
const handleThoughtIconTap = function(e) {
const handleThoughtIconTap = function (e) {
const isMobileView = window.innerWidth <= 1000;
const $icon = $('#rpg-thought-icon');
@@ -2303,7 +2304,7 @@ export function createThoughtPanel($message, thoughtsArray) {
$thoughtIcon.on('click touchend', handleThoughtIconTap);
// Add event handlers for editable thoughts in the bubble
$thoughtPanel.find('.rpg-editable').on('blur', function() {
$thoughtPanel.find('.rpg-editable').on('blur', function () {
const character = $(this).data('character');
const field = $(this).data('field');
const value = $(this).text().trim();
@@ -2312,7 +2313,7 @@ export function createThoughtPanel($message, thoughtsArray) {
});
// Add event listener for section lock icon clicks (support both click and touch)
$thoughtPanel.find('.rpg-section-lock-icon').on('click touchend', function(e) {
$thoughtPanel.find('.rpg-section-lock-icon').on('click touchend', function (e) {
e.preventDefault();
e.stopPropagation();
const $icon = $(this);
@@ -2363,7 +2364,7 @@ export function createThoughtPanel($message, thoughtsArray) {
// Position stays fixed at top-left
// Remove panel when clicking outside (mobile only)
$(document).on('click.thoughtPanel', function(e) {
$(document).on('click.thoughtPanel', function (e) {
// Only hide on click outside in mobile view
if (window.innerWidth <= 1000) {
if (!$(e.target).closest('#rpg-thought-panel, #rpg-thought-icon').length) {
+26 -25
View File
@@ -12,6 +12,7 @@ import {
$userStatsContainer,
FALLBACK_AVATAR_DATA_URI
} from '../../core/state.js';
import { i18n } from '../../core/i18n.js';
import {
saveSettings,
saveChatData,
@@ -273,7 +274,7 @@ export function renderUserStats() {
// Check if stats bars section is locked
const isStatsLocked = isItemLocked('userStats', 'stats');
const lockIcon = isStatsLocked ? '🔒' : '🔓';
const lockTitle = isStatsLocked ? 'Locked - AI cannot change stats' : 'Unlocked - AI can change stats';
const lockTitle = isStatsLocked ? i18n.getTranslation('userStats.statsLocked') : i18n.getTranslation('userStats.statsUnlocked');
const lockedClass = isStatsLocked ? ' locked' : '';
let html = '<div class="rpg-stats-content">';
@@ -286,8 +287,8 @@ export function renderUserStats() {
<img src="${userPortrait}" alt="${userName}" class="rpg-user-portrait" onerror="this.style.opacity='0.5';this.onerror=null;" />
<span class="rpg-user-name">${userName}</span>
${showLevel ? `<span style="opacity: 0.5;">|</span>
<span class="rpg-level-label">LVL</span>
<span class="rpg-level-value rpg-editable" contenteditable="true" data-field="level" title="Click to edit level">${extensionSettings.level}</span>` : ''}
<span class="rpg-level-label">${i18n.getTranslation('userStats.level')}</span>
<span class="rpg-level-value rpg-editable" contenteditable="true" data-field="level" title="${i18n.getTranslation('userStats.clickToEditLevel')}">${extensionSettings.level}</span>` : ''}
</div>
`;
@@ -320,11 +321,11 @@ export function renderUserStats() {
html += `
<div class="rpg-stat-row">
<span class="rpg-stat-label rpg-editable-stat-name" contenteditable="true" data-field="${stat.id}" title="Click to edit stat name">${stat.name}:</span>
<span class="rpg-stat-label rpg-editable-stat-name" contenteditable="true" data-field="${stat.id}" title="${i18n.getTranslation('userStats.clickToEditStatName')}">${stat.name}:</span>
<div class="rpg-stat-bar" style="background: ${gradient}">
<div class="rpg-stat-fill" style="width: ${100 - percentage}%"></div>
</div>
<span class="rpg-stat-value rpg-editable-stat" contenteditable="true" data-field="${stat.id}" data-max="${maxValue}" data-mode="${displayMode}" title="Click to edit">${displayValue}</span>
<span class="rpg-stat-value rpg-editable-stat" contenteditable="true" data-field="${stat.id}" data-max="${maxValue}" data-mode="${displayMode}" title="${i18n.getTranslation('userStats.clickToEditStatValue')}">${displayValue}</span>
</div>
`;
}
@@ -334,7 +335,7 @@ export function renderUserStats() {
if (config.statusSection.enabled) {
const isMoodLocked = isItemLocked('userStats', 'status');
const moodLockIcon = isMoodLocked ? '🔒' : '🔓';
const moodLockTitle = isMoodLocked ? 'Locked - AI cannot change mood' : 'Unlocked - AI can change mood';
const moodLockTitle = isMoodLocked ? i18n.getTranslation('userStats.moodLocked') : i18n.getTranslation('userStats.moodUnlocked');
const moodLockedClass = isMoodLocked ? ' locked' : '';
html += '<div class="rpg-mood">';
if (showLockIcons) {
@@ -342,7 +343,7 @@ export function renderUserStats() {
}
if (config.statusSection.showMoodEmoji) {
html += `<div class="rpg-mood-emoji rpg-editable" contenteditable="true" data-field="mood" title="Click to edit emoji">${stats.mood}</div>`;
html += `<div class="rpg-mood-emoji rpg-editable" contenteditable="true" data-field="mood" title="${i18n.getTranslation('userStats.clickToEditEmoji')}">${stats.mood}</div>`;
}
// Render custom status fields
@@ -368,7 +369,7 @@ export function renderUserStats() {
if (config.skillsSection.enabled) {
const isSkillsLocked = isItemLocked('userStats', 'skills');
const skillsLockIcon = isSkillsLocked ? '🔒' : '🔓';
const skillsLockTitle = isSkillsLocked ? 'Locked - AI cannot change skills' : 'Unlocked - AI can change skills';
const skillsLockTitle = isSkillsLocked ? i18n.getTranslation('userStats.skillsLocked') : i18n.getTranslation('userStats.skillsUnlocked');
const skillsLockedClass = isSkillsLocked ? ' locked' : '';
let skillsValue = 'None';
// Handle JSON array format: [{name: "Art"}, {name: "Coding"}]
@@ -385,7 +386,7 @@ export function renderUserStats() {
}
html += `
<span class="rpg-skills-label">${config.skillsSection.label}:</span>
<div class="rpg-skills-value rpg-editable" contenteditable="true" data-field="skills" title="Click to edit skills">${skillsValue}</div>
<div class="rpg-skills-value rpg-editable" contenteditable="true" data-field="skills" title="${i18n.getTranslation('userStats.clickToEditSkills')}">${skillsValue}</div>
</div>
`;
}
@@ -409,15 +410,15 @@ export function renderUserStats() {
const enabledAttributes = rpgAttributes.filter(attr => attr && attr.enabled && attr.name && attr.id);
if (enabledAttributes.length > 0) {
html += `
html += `
<div class="rpg-stats-right">
<div class="rpg-classic-stats">
<div class="rpg-classic-stats-grid">
`;
enabledAttributes.forEach(attr => {
const value = extensionSettings.classicStats[attr.id] !== undefined ? extensionSettings.classicStats[attr.id] : 10;
html += `
enabledAttributes.forEach(attr => {
const value = extensionSettings.classicStats[attr.id] !== undefined ? extensionSettings.classicStats[attr.id] : 10;
html += `
<div class="rpg-classic-stat" data-stat="${attr.id}">
<span class="rpg-classic-stat-label">${attr.name}</span>
<div class="rpg-classic-stat-buttons">
@@ -427,9 +428,9 @@ export function renderUserStats() {
</div>
</div>
`;
});
});
html += `
html += `
</div>
</div>
</div>
@@ -448,7 +449,7 @@ export function renderUserStats() {
// console.log('[RPG UserStats Render] ✓ HTML rendered to #rpg-user-stats container');
// Add event listeners for editable stat values
$('.rpg-editable-stat').on('blur', function() {
$('.rpg-editable-stat').on('blur', function () {
const field = $(this).data('field');
const mode = $(this).data('mode');
const maxValue = parseInt($(this).data('max')) || 100;
@@ -492,7 +493,7 @@ export function renderUserStats() {
});
// Add event listeners for mood/conditions editing
$('.rpg-mood-emoji.rpg-editable').on('blur', function() {
$('.rpg-mood-emoji.rpg-editable').on('blur', function () {
const value = $(this).text().trim();
extensionSettings.userStats.mood = value || '😐';
@@ -504,7 +505,7 @@ export function renderUserStats() {
updateMessageSwipeData();
});
$('.rpg-mood-conditions.rpg-editable').on('blur', function() {
$('.rpg-mood-conditions.rpg-editable').on('blur', function () {
const value = $(this).text().trim();
const fieldKey = $(this).data('field');
extensionSettings.userStats[fieldKey] = value || 'None';
@@ -518,7 +519,7 @@ export function renderUserStats() {
});
// Add event listener for skills editing
$('.rpg-skills-value.rpg-editable').on('blur', function() {
$('.rpg-skills-value.rpg-editable').on('blur', function () {
const value = $(this).text().trim();
extensionSettings.userStats.skills = value || 'None';
@@ -531,7 +532,7 @@ export function renderUserStats() {
});
// Add event listeners for stat name editing
$('.rpg-editable-stat-name').on('blur', function() {
$('.rpg-editable-stat-name').on('blur', function () {
const field = $(this).data('field');
const value = $(this).text().trim().replace(':', '');
@@ -555,7 +556,7 @@ export function renderUserStats() {
});
// Add event listener for level editing
$('.rpg-level-value.rpg-editable').on('blur', function() {
$('.rpg-level-value.rpg-editable').on('blur', function () {
let value = parseInt($(this).text().trim());
if (isNaN(value) || value < 1) {
value = 1;
@@ -573,15 +574,15 @@ export function renderUserStats() {
});
// Prevent line breaks in level field
$('.rpg-level-value.rpg-editable').on('keydown', function(e) {
$('.rpg-level-value.rpg-editable').on('keydown', function (e) {
if (e.key === 'Enter') {
e.preventDefault();
$(this).blur();
}
});
// Add event listener for section lock icon clicks (support both click and touch)
$('.rpg-section-lock-icon').on('click touchend', function(e) {
// 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);
@@ -594,7 +595,7 @@ export function renderUserStats() {
// Update icon
const newIcon = !currentlyLocked ? '🔒' : '🔓';
const newTitle = !currentlyLocked ? 'Locked - AI cannot change this section' : 'Unlocked - AI can change this section';
const newTitle = !currentlyLocked ? i18n.getTranslation('infoBox.locked') : i18n.getTranslation('infoBox.unlocked');
$icon.text(newIcon);
$icon.attr('title', newTitle);
+87 -87
View File
@@ -51,7 +51,7 @@ export function initTrackerEditor() {
}
// Tab switching
$(document).on('click', '.rpg-editor-tab', function() {
$(document).on('click', '.rpg-editor-tab', function () {
$('.rpg-editor-tab').removeClass('active');
$(this).addClass('active');
@@ -61,51 +61,51 @@ export function initTrackerEditor() {
});
// Save button
$(document).on('click', '#rpg-editor-save', function() {
$(document).on('click', '#rpg-editor-save', function () {
applyTrackerConfig();
closeTrackerEditor();
});
// Cancel button
$(document).on('click', '#rpg-editor-cancel', function() {
$(document).on('click', '#rpg-editor-cancel', function () {
closeTrackerEditor();
});
// Close X button
$(document).on('click', '#rpg-close-tracker-editor', function() {
$(document).on('click', '#rpg-close-tracker-editor', function () {
closeTrackerEditor();
});
// Reset button
$(document).on('click', '#rpg-editor-reset', function() {
$(document).on('click', '#rpg-editor-reset', function () {
resetToDefaults();
renderEditorUI();
});
// Close on background click
$(document).on('click', '#rpg-tracker-editor-popup', function(e) {
$(document).on('click', '#rpg-tracker-editor-popup', function (e) {
if (e.target.id === 'rpg-tracker-editor-popup') {
closeTrackerEditor();
}
});
// Open button
$(document).on('click', '#rpg-open-tracker-editor', function() {
$(document).on('click', '#rpg-open-tracker-editor', function () {
openTrackerEditor();
});
// Export button
$(document).on('click', '#rpg-editor-export', function() {
$(document).on('click', '#rpg-editor-export', function () {
exportTrackerPreset();
});
// Import button
$(document).on('click', '#rpg-editor-import', function() {
$(document).on('click', '#rpg-editor-import', function () {
importTrackerPreset();
});
// Preset select change
$(document).on('change', '#rpg-preset-select', function() {
$(document).on('change', '#rpg-preset-select', function () {
const presetId = $(this).val();
if (presetId && presetId !== getActivePresetId()) {
// Check if the current character had an association (either original or pending)
@@ -139,7 +139,7 @@ export function initTrackerEditor() {
});
// New preset button
$(document).on('click', '#rpg-preset-new', function() {
$(document).on('click', '#rpg-preset-new', function () {
const name = prompt('Enter a name for the new preset:');
if (name && name.trim()) {
const newId = createPreset(name.trim());
@@ -150,7 +150,7 @@ export function initTrackerEditor() {
});
// Set as default preset button
$(document).on('click', '#rpg-preset-default', function() {
$(document).on('click', '#rpg-preset-default', function () {
const currentPresetId = getActivePresetId();
if (currentPresetId) {
setDefaultPreset(currentPresetId);
@@ -161,7 +161,7 @@ export function initTrackerEditor() {
});
// Delete preset button
$(document).on('click', '#rpg-preset-delete', function() {
$(document).on('click', '#rpg-preset-delete', function () {
const currentPresetId = getActivePresetId();
const presets = getPresets();
if (Object.keys(presets).length <= 1) {
@@ -180,7 +180,7 @@ export function initTrackerEditor() {
});
// Associate preset checkbox
$(document).on('change', '#rpg-preset-associate', function() {
$(document).on('change', '#rpg-preset-associate', function () {
const activePresetId = getActivePresetId();
const preset = getPreset(activePresetId);
const entityName = getCurrentEntityName();
@@ -334,20 +334,20 @@ function resetToDefaults() {
extensionSettings.trackerConfig = {
userStats: {
customStats: [
{ id: 'health', name: 'Health', enabled: true, persistInHistory: false },
{ id: 'satiety', name: 'Satiety', enabled: true, persistInHistory: false },
{ id: 'energy', name: 'Energy', enabled: true, persistInHistory: false },
{ id: 'hygiene', name: 'Hygiene', enabled: true, persistInHistory: false },
{ id: 'arousal', name: 'Arousal', enabled: true, persistInHistory: false }
{ id: 'health', name: i18n.getTranslation('stats.health'), enabled: true, persistInHistory: false },
{ id: 'satiety', name: i18n.getTranslation('stats.satiety'), enabled: true, persistInHistory: false },
{ id: 'energy', name: i18n.getTranslation('stats.energy'), enabled: true, persistInHistory: false },
{ id: 'hygiene', name: i18n.getTranslation('stats.hygiene'), enabled: true, persistInHistory: false },
{ id: 'arousal', name: i18n.getTranslation('stats.arousal'), enabled: true, persistInHistory: false }
],
showRPGAttributes: true,
rpgAttributes: [
{ id: 'str', name: 'STR', enabled: true, persistInHistory: false },
{ id: 'dex', name: 'DEX', enabled: true, persistInHistory: false },
{ id: 'con', name: 'CON', enabled: true, persistInHistory: false },
{ id: 'int', name: 'INT', enabled: true, persistInHistory: false },
{ id: 'wis', name: 'WIS', enabled: true, persistInHistory: false },
{ id: 'cha', name: 'CHA', enabled: true, persistInHistory: false }
{ id: 'str', name: i18n.getTranslation('stats.str'), enabled: true, persistInHistory: false },
{ id: 'dex', name: i18n.getTranslation('stats.dex'), enabled: true, persistInHistory: false },
{ id: 'con', name: i18n.getTranslation('stats.con'), enabled: true, persistInHistory: false },
{ id: 'int', name: i18n.getTranslation('stats.int'), enabled: true, persistInHistory: false },
{ id: 'wis', name: i18n.getTranslation('stats.wis'), enabled: true, persistInHistory: false },
{ id: 'cha', name: i18n.getTranslation('stats.cha'), enabled: true, persistInHistory: false }
],
statusSection: {
enabled: true,
@@ -408,8 +408,8 @@ function resetToDefaults() {
characterStats: {
enabled: false,
customStats: [
{ id: 'health', name: 'Health', enabled: true, colorLow: '#ff4444', colorHigh: '#44ff44' },
{ id: 'energy', name: 'Energy', enabled: true, colorLow: '#ffaa00', colorHigh: '#44ffff' }
{ id: 'health', name: i18n.getTranslation('stats.health'), enabled: true, colorLow: '#ff4444', colorHigh: '#44ff44' },
{ id: 'energy', name: i18n.getTranslation('stats.energy'), enabled: true, colorLow: '#ffaa00', colorHigh: '#44ffff' }
]
}
}
@@ -854,7 +854,7 @@ function renderUserStatsTab() {
*/
function setupUserStatsListeners() {
// Add stat
$('#rpg-add-stat').off('click').on('click', function() {
$('#rpg-add-stat').off('click').on('click', function () {
const newId = 'custom_' + Date.now();
extensionSettings.trackerConfig.userStats.customStats.push({
id: newId,
@@ -870,39 +870,39 @@ function setupUserStatsListeners() {
});
// Remove stat
$('.rpg-stat-remove').off('click').on('click', function() {
$('.rpg-stat-remove').off('click').on('click', function () {
const index = $(this).data('index');
extensionSettings.trackerConfig.userStats.customStats.splice(index, 1);
renderUserStatsTab();
});
// Toggle stat
$('.rpg-stat-toggle').off('change').on('change', function() {
$('.rpg-stat-toggle').off('change').on('change', function () {
const index = $(this).data('index');
extensionSettings.trackerConfig.userStats.customStats[index].enabled = $(this).is(':checked');
});
// Rename stat
$('.rpg-stat-name').off('blur').on('blur', function() {
$('.rpg-stat-name').off('blur').on('blur', function () {
const index = $(this).data('index');
extensionSettings.trackerConfig.userStats.customStats[index].name = $(this).val();
});
// Change stat max value
$('.rpg-stat-max').off('blur').on('blur', function() {
$('.rpg-stat-max').off('blur').on('blur', function () {
const index = $(this).data('index');
const value = parseInt($(this).val()) || 100;
extensionSettings.trackerConfig.userStats.customStats[index].maxValue = Math.max(1, value);
});
// Stats display mode toggle
$('input[name="stats-display-mode"]').off('change').on('change', function() {
$('input[name="stats-display-mode"]').off('change').on('change', function () {
extensionSettings.trackerConfig.userStats.statsDisplayMode = $(this).val();
renderUserStatsTab(); // Re-render to show/hide max value fields
});
// Add attribute
$('#rpg-add-attr').off('click').on('click', function() {
$('#rpg-add-attr').off('click').on('click', function () {
// Ensure rpgAttributes array exists with defaults if needed
if (!extensionSettings.trackerConfig.userStats.rpgAttributes || extensionSettings.trackerConfig.userStats.rpgAttributes.length === 0) {
extensionSettings.trackerConfig.userStats.rpgAttributes = [
@@ -928,64 +928,64 @@ function setupUserStatsListeners() {
});
// Remove attribute
$('.rpg-attr-remove').off('click').on('click', function() {
$('.rpg-attr-remove').off('click').on('click', function () {
const index = $(this).data('index');
extensionSettings.trackerConfig.userStats.rpgAttributes.splice(index, 1);
renderUserStatsTab();
});
// Toggle attribute
$('.rpg-attr-toggle').off('change').on('change', function() {
$('.rpg-attr-toggle').off('change').on('change', function () {
const index = $(this).data('index');
extensionSettings.trackerConfig.userStats.rpgAttributes[index].enabled = $(this).is(':checked');
});
// Rename attribute
$('.rpg-attr-name').off('blur').on('blur', function() {
$('.rpg-attr-name').off('blur').on('blur', function () {
const index = $(this).data('index');
extensionSettings.trackerConfig.userStats.rpgAttributes[index].name = $(this).val();
});
// Enable/disable RPG Attributes section toggle
$('#rpg-show-rpg-attrs').off('change').on('change', function() {
$('#rpg-show-rpg-attrs').off('change').on('change', function () {
extensionSettings.trackerConfig.userStats.showRPGAttributes = $(this).is(':checked');
});
// Show/hide level toggle
$('#rpg-show-level').off('change').on('change', function() {
$('#rpg-show-level').off('change').on('change', function () {
extensionSettings.trackerConfig.userStats.showLevel = $(this).is(':checked');
});
// Always send attributes toggle
$('#rpg-always-send-attrs').off('change').on('change', function() {
$('#rpg-always-send-attrs').off('change').on('change', function () {
extensionSettings.trackerConfig.userStats.alwaysSendAttributes = $(this).is(':checked');
});
// Status section toggles
$('#rpg-status-enabled').off('change').on('change', function() {
$('#rpg-status-enabled').off('change').on('change', function () {
extensionSettings.trackerConfig.userStats.statusSection.enabled = $(this).is(':checked');
});
$('#rpg-mood-emoji').off('change').on('change', function() {
$('#rpg-mood-emoji').off('change').on('change', function () {
extensionSettings.trackerConfig.userStats.statusSection.showMoodEmoji = $(this).is(':checked');
});
$('#rpg-status-fields').off('blur').on('blur', function() {
$('#rpg-status-fields').off('blur').on('blur', function () {
const fields = $(this).val().split(',').map(f => f.trim()).filter(f => f);
extensionSettings.trackerConfig.userStats.statusSection.customFields = fields;
});
// Skills section toggles
$('#rpg-skills-enabled').off('change').on('change', function() {
$('#rpg-skills-enabled').off('change').on('change', function () {
extensionSettings.trackerConfig.userStats.skillsSection.enabled = $(this).is(':checked');
});
$('#rpg-skills-label').off('blur').on('blur', function() {
$('#rpg-skills-label').off('blur').on('blur', function () {
extensionSettings.trackerConfig.userStats.skillsSection.label = $(this).val();
saveSettings();
});
$('#rpg-skills-fields').off('blur').on('blur', function() {
$('#rpg-skills-fields').off('blur').on('blur', function () {
const fields = $(this).val().split(',').map(f => f.trim()).filter(f => f);
extensionSettings.trackerConfig.userStats.skillsSection.customFields = fields;
saveSettings();
@@ -1057,35 +1057,35 @@ function renderInfoBoxTab() {
function setupInfoBoxListeners() {
const widgets = extensionSettings.trackerConfig.infoBox.widgets;
$('#rpg-widget-date').off('change').on('change', function() {
$('#rpg-widget-date').off('change').on('change', function () {
widgets.date.enabled = $(this).is(':checked');
});
$('#rpg-date-format').off('change').on('change', function() {
$('#rpg-date-format').off('change').on('change', function () {
widgets.date.format = $(this).val();
});
$('#rpg-widget-weather').off('change').on('change', function() {
$('#rpg-widget-weather').off('change').on('change', function () {
widgets.weather.enabled = $(this).is(':checked');
});
$('#rpg-widget-temperature').off('change').on('change', function() {
$('#rpg-widget-temperature').off('change').on('change', function () {
widgets.temperature.enabled = $(this).is(':checked');
});
$('input[name="temp-unit"]').off('change').on('change', function() {
$('input[name="temp-unit"]').off('change').on('change', function () {
widgets.temperature.unit = $(this).val();
});
$('#rpg-widget-time').off('change').on('change', function() {
$('#rpg-widget-time').off('change').on('change', function () {
widgets.time.enabled = $(this).is(':checked');
});
$('#rpg-widget-location').off('change').on('change', function() {
$('#rpg-widget-location').off('change').on('change', function () {
widgets.location.enabled = $(this).is(':checked');
});
$('#rpg-widget-events').off('change').on('change', function() {
$('#rpg-widget-events').off('change').on('change', function () {
widgets.recentEvents.enabled = $(this).is(':checked');
});
}
@@ -1209,7 +1209,7 @@ function renderPresentCharactersTab() {
*/
function setupPresentCharactersListeners() {
// Relationships enabled toggle
$('#rpg-relationships-enabled').off('change').on('change', function() {
$('#rpg-relationships-enabled').off('change').on('change', function () {
if (!extensionSettings.trackerConfig.presentCharacters.relationships) {
extensionSettings.trackerConfig.presentCharacters.relationships = { enabled: true, relationshipEmojis: {} };
}
@@ -1217,7 +1217,7 @@ function setupPresentCharactersListeners() {
});
// Add new relationship
$('#rpg-add-relationship').off('click').on('click', function() {
$('#rpg-add-relationship').off('click').on('click', function () {
// Ensure relationships object exists
if (!extensionSettings.trackerConfig.presentCharacters.relationships) {
extensionSettings.trackerConfig.presentCharacters.relationships = { enabled: true, relationshipEmojis: {} };
@@ -1254,7 +1254,7 @@ function setupPresentCharactersListeners() {
});
// Remove relationship
$('.rpg-remove-relationship').off('click').on('click', function() {
$('.rpg-remove-relationship').off('click').on('click', function () {
const relationship = $(this).data('relationship');
// Remove from new structure
@@ -1275,7 +1275,7 @@ function setupPresentCharactersListeners() {
});
// Update relationship name
$('.rpg-relationship-name').off('blur').on('blur', function() {
$('.rpg-relationship-name').off('blur').on('blur', function () {
const newName = $(this).val();
const $item = $(this).closest('.rpg-relationship-item');
const emoji = $item.find('.rpg-relationship-emoji').val();
@@ -1309,7 +1309,7 @@ function setupPresentCharactersListeners() {
});
// Update relationship emoji
$('.rpg-relationship-emoji').off('blur').on('blur', function() {
$('.rpg-relationship-emoji').off('blur').on('blur', function () {
const name = $(this).closest('.rpg-relationship-item').find('.rpg-relationship-name').val();
// Ensure structures exist
@@ -1326,21 +1326,21 @@ function setupPresentCharactersListeners() {
});
// Thoughts configuration
$('#rpg-thoughts-enabled').off('change').on('change', function() {
$('#rpg-thoughts-enabled').off('change').on('change', function () {
if (!extensionSettings.trackerConfig.presentCharacters.thoughts) {
extensionSettings.trackerConfig.presentCharacters.thoughts = {};
}
extensionSettings.trackerConfig.presentCharacters.thoughts.enabled = $(this).is(':checked');
});
$('#rpg-thoughts-name').off('blur').on('blur', function() {
$('#rpg-thoughts-name').off('blur').on('blur', function () {
if (!extensionSettings.trackerConfig.presentCharacters.thoughts) {
extensionSettings.trackerConfig.presentCharacters.thoughts = {};
}
extensionSettings.trackerConfig.presentCharacters.thoughts.name = $(this).val();
});
$('#rpg-thoughts-description').off('blur').on('blur', function() {
$('#rpg-thoughts-description').off('blur').on('blur', function () {
if (!extensionSettings.trackerConfig.presentCharacters.thoughts) {
extensionSettings.trackerConfig.presentCharacters.thoughts = {};
}
@@ -1348,7 +1348,7 @@ function setupPresentCharactersListeners() {
});
// Add field
$('#rpg-add-field').off('click').on('click', function() {
$('#rpg-add-field').off('click').on('click', function () {
extensionSettings.trackerConfig.presentCharacters.customFields.push({
id: 'custom_' + Date.now(),
name: 'New Field',
@@ -1359,14 +1359,14 @@ function setupPresentCharactersListeners() {
});
// Remove field
$('.rpg-field-remove').off('click').on('click', function() {
$('.rpg-field-remove').off('click').on('click', function () {
const index = $(this).data('index');
extensionSettings.trackerConfig.presentCharacters.customFields.splice(index, 1);
renderPresentCharactersTab();
});
// Move field up
$('.rpg-field-move-up').off('click').on('click', function() {
$('.rpg-field-move-up').off('click').on('click', function () {
const index = $(this).data('index');
if (index > 0) {
const fields = extensionSettings.trackerConfig.presentCharacters.customFields;
@@ -1376,7 +1376,7 @@ function setupPresentCharactersListeners() {
});
// Move field down
$('.rpg-field-move-down').off('click').on('click', function() {
$('.rpg-field-move-down').off('click').on('click', function () {
const index = $(this).data('index');
const fields = extensionSettings.trackerConfig.presentCharacters.customFields;
if (index < fields.length - 1) {
@@ -1386,25 +1386,25 @@ function setupPresentCharactersListeners() {
});
// Toggle field
$('.rpg-field-toggle').off('change').on('change', function() {
$('.rpg-field-toggle').off('change').on('change', function () {
const index = $(this).data('index');
extensionSettings.trackerConfig.presentCharacters.customFields[index].enabled = $(this).is(':checked');
});
// Rename field
$('.rpg-field-label').off('blur').on('blur', function() {
$('.rpg-field-label').off('blur').on('blur', function () {
const index = $(this).data('index');
extensionSettings.trackerConfig.presentCharacters.customFields[index].name = $(this).val();
});
// Update description
$('.rpg-field-placeholder').off('blur').on('blur', function() {
$('.rpg-field-placeholder').off('blur').on('blur', function () {
const index = $(this).data('index');
extensionSettings.trackerConfig.presentCharacters.customFields[index].description = $(this).val();
});
// Character stats toggle
$('#rpg-char-stats-enabled').off('change').on('change', function() {
$('#rpg-char-stats-enabled').off('change').on('change', function () {
if (!extensionSettings.trackerConfig.presentCharacters.characterStats) {
extensionSettings.trackerConfig.presentCharacters.characterStats = { enabled: false, customStats: [] };
}
@@ -1412,7 +1412,7 @@ function setupPresentCharactersListeners() {
});
// Add character stat
$('#rpg-add-char-stat').off('click').on('click', function() {
$('#rpg-add-char-stat').off('click').on('click', function () {
if (!extensionSettings.trackerConfig.presentCharacters.characterStats) {
extensionSettings.trackerConfig.presentCharacters.characterStats = { enabled: false, customStats: [] };
}
@@ -1428,20 +1428,20 @@ function setupPresentCharactersListeners() {
});
// Remove character stat
$('.rpg-char-stat-remove').off('click').on('click', function() {
$('.rpg-char-stat-remove').off('click').on('click', function () {
const index = $(this).data('index');
extensionSettings.trackerConfig.presentCharacters.characterStats.customStats.splice(index, 1);
renderPresentCharactersTab();
});
// Toggle character stat
$('.rpg-char-stat-toggle').off('change').on('change', function() {
$('.rpg-char-stat-toggle').off('change').on('change', function () {
const index = $(this).data('index');
extensionSettings.trackerConfig.presentCharacters.characterStats.customStats[index].enabled = $(this).is(':checked');
});
// Rename character stat
$('.rpg-char-stat-label').off('blur').on('blur', function() {
$('.rpg-char-stat-label').off('blur').on('blur', function () {
const index = $(this).data('index');
extensionSettings.trackerConfig.presentCharacters.characterStats.customStats[index].name = $(this).val();
});
@@ -1637,70 +1637,70 @@ function setupHistoryPersistenceListeners() {
}
// Main toggle
$('#rpg-history-persistence-enabled').off('change').on('change', function() {
$('#rpg-history-persistence-enabled').off('change').on('change', function () {
extensionSettings.historyPersistence.enabled = $(this).is(':checked');
});
// Send All Enabled on Refresh toggle
$('#rpg-history-send-all-enabled').off('change').on('change', function() {
$('#rpg-history-send-all-enabled').off('change').on('change', function () {
extensionSettings.historyPersistence.sendAllEnabledOnRefresh = $(this).is(':checked');
});
// Message count
$('#rpg-history-message-count').off('change').on('change', function() {
$('#rpg-history-message-count').off('change').on('change', function () {
extensionSettings.historyPersistence.messageCount = parseInt($(this).val()) || 0;
});
// Injection position
$('#rpg-history-injection-position').off('change').on('change', function() {
$('#rpg-history-injection-position').off('change').on('change', function () {
extensionSettings.historyPersistence.injectionPosition = $(this).val();
});
// Context preamble
$('#rpg-history-context-preamble').off('blur').on('blur', function() {
$('#rpg-history-context-preamble').off('blur').on('blur', function () {
extensionSettings.historyPersistence.contextPreamble = $(this).val();
});
// User Stats toggles
$('.rpg-history-stat-toggle').off('change').on('change', function() {
$('.rpg-history-stat-toggle').off('change').on('change', function () {
const index = $(this).data('index');
extensionSettings.trackerConfig.userStats.customStats[index].persistInHistory = $(this).is(':checked');
});
// Status section
$('#rpg-history-status').off('change').on('change', function() {
$('#rpg-history-status').off('change').on('change', function () {
extensionSettings.trackerConfig.userStats.statusSection.persistInHistory = $(this).is(':checked');
});
// Skills section
$('#rpg-history-skills').off('change').on('change', function() {
$('#rpg-history-skills').off('change').on('change', function () {
extensionSettings.trackerConfig.userStats.skillsSection.persistInHistory = $(this).is(':checked');
});
// Inventory
$('#rpg-history-inventory').off('change').on('change', function() {
$('#rpg-history-inventory').off('change').on('change', function () {
extensionSettings.trackerConfig.userStats.inventoryPersistInHistory = $(this).is(':checked');
});
// Quests
$('#rpg-history-quests').off('change').on('change', function() {
$('#rpg-history-quests').off('change').on('change', function () {
extensionSettings.trackerConfig.userStats.questsPersistInHistory = $(this).is(':checked');
});
// Info Box widget toggles
$('.rpg-history-widget-toggle').off('change').on('change', function() {
$('.rpg-history-widget-toggle').off('change').on('change', function () {
const widgetId = $(this).data('widget');
extensionSettings.trackerConfig.infoBox.widgets[widgetId].persistInHistory = $(this).is(':checked');
});
// Present Characters field toggles
$('.rpg-history-charfield-toggle').off('change').on('change', function() {
$('.rpg-history-charfield-toggle').off('change').on('change', function () {
const index = $(this).data('index');
extensionSettings.trackerConfig.presentCharacters.customFields[index].persistInHistory = $(this).is(':checked');
});
// Thoughts
$('#rpg-history-thoughts').off('change').on('change', function() {
$('#rpg-history-thoughts').off('change').on('change', function () {
extensionSettings.trackerConfig.presentCharacters.thoughts.persistInHistory = $(this).is(':checked');
});
}