chore: final cleanup

This commit is contained in:
Subarashimo
2025-12-05 18:10:21 +01:00
parent 38328de1bf
commit 7e47dbfd7c
29 changed files with 338 additions and 2168 deletions
+45 -54
View File
@@ -128,11 +128,7 @@ export function renderInfoBox() {
return;
}
// console.log('[RPG Companion] renderInfoBox called with data:', infoBoxData);
// Parse the info box data
const lines = infoBoxData.split('\n');
// console.log('[RPG Companion] Info Box split into lines:', lines);
const data = {
date: '',
weekday: '',
@@ -158,8 +154,6 @@ export function renderInfoBox() {
};
for (const line of lines) {
// console.log('[RPG Companion] Processing line:', line);
// Helper to check if a value is valid (not null/empty)
const isValidParsedValue = (val) => val && val !== 'null' && val !== 'undefined' && val.toLowerCase() !== 'none';
@@ -167,7 +161,6 @@ export function renderInfoBox() {
// 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();
if (isValidParsedValue(dateStr)) {
const dateParts = dateStr.split(',').map(p => p.trim());
@@ -180,7 +173,6 @@ export function renderInfoBox() {
}
} else if (line.includes('🗓️:')) {
if (!parsedFields.date) {
// console.log('[RPG Companion] → Matched DATE (emoji format)');
const dateStr = line.replace('🗓️:', '').trim();
if (isValidParsedValue(dateStr)) {
const dateParts = dateStr.split(',').map(p => p.trim());
@@ -193,7 +185,6 @@ export function renderInfoBox() {
}
} else if (line.startsWith('Temperature:')) {
if (!parsedFields.temperature) {
// console.log('[RPG Companion] → Matched TEMPERATURE (text format)');
const tempStr = line.replace('Temperature:', '').trim();
if (isValidParsedValue(tempStr)) {
data.temperature = tempStr;
@@ -206,7 +197,6 @@ export function renderInfoBox() {
}
} else if (line.includes('🌡️:')) {
if (!parsedFields.temperature) {
// console.log('[RPG Companion] → Matched TEMPERATURE (emoji format)');
const tempStr = line.replace('🌡️:', '').trim();
if (isValidParsedValue(tempStr)) {
data.temperature = tempStr;
@@ -219,7 +209,6 @@ export function renderInfoBox() {
}
} else if (line.startsWith('Time:')) {
if (!parsedFields.time) {
// console.log('[RPG Companion] → Matched TIME (text format)');
const timeStr = line.replace('Time:', '').trim();
if (isValidParsedValue(timeStr)) {
data.time = timeStr;
@@ -231,7 +220,6 @@ export function renderInfoBox() {
}
} else if (line.includes('🕒:')) {
if (!parsedFields.time) {
// console.log('[RPG Companion] → Matched TIME (emoji format)');
const timeStr = line.replace('🕒:', '').trim();
if (isValidParsedValue(timeStr)) {
data.time = timeStr;
@@ -243,7 +231,6 @@ export function renderInfoBox() {
}
} else if (line.startsWith('Location:')) {
if (!parsedFields.location) {
// console.log('[RPG Companion] → Matched LOCATION (text format)');
const locStr = line.replace('Location:', '').trim();
if (isValidParsedValue(locStr)) {
data.location = locStr;
@@ -252,7 +239,6 @@ export function renderInfoBox() {
}
} else if (line.includes('🗺️:')) {
if (!parsedFields.location) {
// console.log('[RPG Companion] → Matched LOCATION (emoji format)');
const locStr = line.replace('🗺️:', '').trim();
if (isValidParsedValue(locStr)) {
data.location = locStr;
@@ -297,45 +283,22 @@ export function renderInfoBox() {
const notDivider = !line.includes('---');
const notCodeFence = !line.trim().startsWith('```');
// console.log('[RPG Companion] → Checking weather conditions:', {
// line: line,
// hasColon: hasColon,
// notInfoBox: notInfoBox,
// notDivider: notDivider
// });
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] ✗ Weather regex did not match');
}
} 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
@@ -635,14 +598,12 @@ export function updateInfoBoxField(field, value) {
// Reconstruct the Info Box text with updated field
const lines = lastGeneratedData.infoBox.split('\n');
let dateLineFound = false;
let dateLineIndex = -1;
let weatherLineIndex = -1;
// Find the date line
for (let i = 0; i < lines.length; i++) {
if (lines[i].includes('🗓️:') || lines[i].startsWith('Date:')) {
dateLineFound = true;
dateLineIndex = i;
break;
}
}
@@ -876,7 +837,6 @@ export function updateInfoBoxField(field, value) {
const swipeId = message.swipe_id || 0;
if (message.extra.rpg_companion_swipes[swipeId]) {
message.extra.rpg_companion_swipes[swipeId].infoBox = updatedLines.join('\n');
// console.log('[RPG Companion] Updated infoBox in message swipe data');
}
}
break;
@@ -907,34 +867,57 @@ function updateRecentEvent(field, value) {
}[field];
if (eventIndex !== undefined) {
// Parse current infoBox to get existing events
const lines = (committedTrackerData.infoBox || '').split('\n');
// Get existing events - prioritize structured data (same logic as renderInfoBox)
let recentEvents = [];
// Find existing Recent Events line
const recentEventsLine = lines.find(line => line.startsWith('Recent Events:'));
if (recentEventsLine) {
const eventsString = recentEventsLine.replace('Recent Events:', '').trim();
if (eventsString) {
recentEvents = eventsString.split(',').map(e => e.trim()).filter(e => e);
// First check structured infoBoxData (from JSON parsing)
if (extensionSettings.infoBoxData?.recentEvents) {
const events = extensionSettings.infoBoxData.recentEvents;
if (Array.isArray(events)) {
// Get all valid events, preserving order (max 3)
recentEvents = events.filter(e => e && e !== 'null').slice(0, 3);
} else if (typeof events === 'string' && events !== 'null') {
recentEvents = [events];
}
}
// Fallback to text format from committedTrackerData
if (recentEvents.length === 0 && committedTrackerData.infoBox) {
const lines = (committedTrackerData.infoBox || '').split('\n');
const recentEventsLine = lines.find(line => line.startsWith('Recent Events:'));
if (recentEventsLine) {
const eventsString = recentEventsLine.replace('Recent Events:', '').trim();
if (eventsString) {
recentEvents = eventsString.split(',').map(e => e.trim()).filter(e => e).slice(0, 3);
}
}
}
// Ensure array has enough slots
// Filter out placeholder text - treat it as empty
const placeholderText = i18n.getTranslation('infobox.recentEvents.addEventPlaceholder');
const cleanedValue = (value === placeholderText || value === 'Add event...' || value === 'Click to add event') ? '' : value.trim();
// Update the specific event in the array
// Ensure array has enough slots for the index we're updating
while (recentEvents.length <= eventIndex) {
recentEvents.push('');
}
// Update the specific event
recentEvents[eventIndex] = value;
// Filter out empty events and rebuild the line
recentEvents[eventIndex] = cleanedValue;
// Filter out empty events for final storage (but preserve order of non-empty ones)
const validEvents = recentEvents.filter(e => e && e.trim());
const newRecentEventsLine = validEvents.length > 0
? `Recent Events: ${validEvents.join(', ')}`
: '';
// Update infoBox with new Recent Events line
// Need to get lines from committedTrackerData if we haven't already
let lines = [];
if (committedTrackerData.infoBox) {
lines = committedTrackerData.infoBox.split('\n');
}
const updatedLines = lines.filter(line => !line.startsWith('Recent Events:'));
if (newRecentEventsLine) {
// Add Recent Events line at the end (before any empty lines)
@@ -951,6 +934,14 @@ function updateRecentEvent(field, value) {
committedTrackerData.infoBox = updatedLines.join('\n');
lastGeneratedData.infoBox = updatedLines.join('\n');
// Also update the structured data to keep it in sync
// Store only valid events (renderInfoBox will handle showing placeholders for empty slots)
// This prevents renderInfoBox() from using stale structured data
if (!extensionSettings.infoBoxData) {
extensionSettings.infoBoxData = {};
}
extensionSettings.infoBoxData.recentEvents = validEvents;
// Update the message's swipe data
const chat = getContext().chat;
if (chat && chat.length > 0) {
+3 -13
View File
@@ -8,7 +8,7 @@ import { getInventoryRenderOptions, restoreFormStates } from '../interaction/inv
import { updateInventoryItem } from '../interaction/inventoryEdit.js';
import { parseItems } from '../../utils/itemParser.js';
import { i18n } from '../../core/i18n.js';
import { itemHasLinkedSkills, navigateToLinkedSkills } from './skills.js';
import { itemHasLinkedSkills } from './skills.js';
// Type imports
/** @typedef {import('../../types/inventory.js').InventoryV2} InventoryV2 */
@@ -443,18 +443,9 @@ function generateInventoryHTML(inventory, options = {}) {
collapsedLocations = []
} = options;
// Handle legacy v1 format - convert to v2 for display
let v2Inventory = inventory;
if (typeof inventory === 'string') {
v2Inventory = {
version: 2,
onPerson: inventory,
stored: {},
assets: 'None'
};
}
// Ensure v2 structure has all required fields
// Note: Migration functions handle v1→v2 conversion on load, so inventory should always be v2 here
let v2Inventory = inventory;
if (!v2Inventory || typeof v2Inventory !== 'object') {
v2Inventory = {
version: 2,
@@ -686,7 +677,6 @@ export function renderInventory() {
$inventoryContainer.html(html);
// Restore form states after re-rendering (fixes Bug #1)
restoreFormStates();
// Event listener for editing item names (mobile-friendly contenteditable)
+111 -180
View File
@@ -1,10 +1,11 @@
/**
* Quests Rendering Module
* Handles UI rendering for quests system (main and optional quests)
* Uses the same structure and styling as items/skills
*/
import { extensionSettings, $questsContainer } from '../../core/state.js';
import { saveSettings } from '../../core/persistence.js';
import { saveSettings, saveChatData } from '../../core/persistence.js';
import { i18n } from '../../core/i18n.js';
/**
@@ -19,45 +20,22 @@ function escapeHtml(text) {
}
/**
* Checks if we have structured quests data (v2 format with name + description)
* @returns {boolean}
*/
function hasStructuredQuests() {
const q = extensionSettings.questsV2;
return q && (q.main !== undefined || q.optional !== undefined);
}
/**
* Gets the main quest (supports both legacy and structured format)
* Gets the main quest (migration handles legacy format conversion)
* @returns {{name: string, description: string}|null}
*/
function getMainQuest() {
if (hasStructuredQuests() && extensionSettings.questsV2.main) {
if (extensionSettings.questsV2?.main) {
return extensionSettings.questsV2.main;
}
// Legacy format
const title = extensionSettings.quests?.main;
if (title && title !== 'None') {
return { name: title, description: extensionSettings.quests?.mainDescription || '' };
}
return null;
}
/**
* Gets optional quests (supports both legacy and structured format)
* Gets optional quests (migration handles legacy format conversion)
* @returns {Array<{name: string, description: string}>}
*/
function getOptionalQuests() {
if (hasStructuredQuests() && extensionSettings.questsV2.optional) {
return extensionSettings.questsV2.optional;
}
// Legacy format
const titles = extensionSettings.quests?.optional || [];
const descriptions = extensionSettings.quests?.optionalDescriptions || [];
return titles.map((title, i) => ({
name: title,
description: descriptions[i] || ''
}));
return extensionSettings.questsV2?.optional || [];
}
/**
@@ -79,94 +57,90 @@ export function renderQuestsSubTabs(activeTab = 'main') {
}
/**
* Renders the main quest view
* @param {string} mainQuest - Current main quest title (legacy param, ignored if structured)
* Renders the main quest view (matches items/skills structure)
* @returns {string} HTML for main quest view
*/
export function renderMainQuestView(mainQuest) {
// Use structured data helpers
export function renderMainQuestView() {
const quest = getMainQuest();
const hasQuest = quest !== null;
const questName = quest?.name || '';
const questDesc = quest?.description || '';
// Track if add form is open
const isFormOpen = openAddForms?.main || false;
let itemsHtml = '';
if (hasQuest) {
// Render quest as item (list view style, matching items/skills)
itemsHtml = `
<div class="rpg-item-row" data-field="main">
<div class="rpg-item-main-row">
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="main" data-prop="name" title="Click to edit">${escapeHtml(questName)}</span>
<button class="rpg-item-remove" data-action="remove-quest" data-field="main" title="Complete/Remove quest">
<i class="fa-solid fa-times"></i>
</button>
</div>
<div class="rpg-item-desc-row">
<span class="rpg-item-description rpg-editable" contenteditable="true" data-field="main" data-prop="description" title="Click to edit description">${escapeHtml(questDesc)}</span>
</div>
</div>
`;
}
return `
<div class="rpg-quest-section">
<div class="rpg-quest-header">
<h3 class="rpg-quest-section-title" data-i18n-key="quests.main.title">${i18n.getTranslation('quests.main.title')}</h3>
${!hasQuest ? `<button class="rpg-add-quest-btn" data-action="add-quest" data-field="main" title="${i18n.getTranslation('quests.main.addQuestTitle')}">
<button class="rpg-inventory-add-btn" data-action="add-quest" data-field="main" title="${i18n.getTranslation('quests.main.addQuestTitle')}">
<i class="fa-solid fa-plus"></i> <span data-i18n-key="global.add">${i18n.getTranslation('global.add')}</span>
</button>` : ''}
</button>
</div>
<div class="rpg-quest-content">
${hasQuest ? `
<div class="rpg-inline-form" id="rpg-edit-quest-form-main" style="display: none;">
<input type="text" class="rpg-inline-input" id="rpg-edit-quest-main" value="${escapeHtml(questName)}" placeholder="Quest name" />
<input type="text" class="rpg-inline-input" id="rpg-edit-quest-desc-main" value="${escapeHtml(questDesc)}" placeholder="Description (optional)" />
<div class="rpg-inline-buttons">
<button class="rpg-inline-btn rpg-inline-cancel" data-action="cancel-edit-quest" data-field="main">
<i class="fa-solid fa-times"></i> <span data-i18n-key="global.cancel">${i18n.getTranslation('global.cancel')}</span>
</button>
<button class="rpg-inline-btn rpg-inline-save" data-action="save-edit-quest" data-field="main">
<i class="fa-solid fa-check"></i> <span data-i18n-key="global.save">${i18n.getTranslation('global.save')}</span>
</button>
</div>
<div class="rpg-inline-form" id="rpg-add-quest-form-main" style="display: ${isFormOpen ? 'flex' : 'none'};">
<input type="text" class="rpg-inline-input" id="rpg-new-quest-main" placeholder="${i18n.getTranslation('quests.main.addQuestPlaceholder')}" data-i18n-placeholder-key="quests.main.addQuestPlaceholder" />
<div class="rpg-inline-buttons">
<button class="rpg-inline-btn rpg-inline-cancel" data-action="cancel-add-quest" data-field="main">
<i class="fa-solid fa-times"></i> <span data-i18n-key="global.cancel">${i18n.getTranslation('global.cancel')}</span>
</button>
<button class="rpg-inline-btn rpg-inline-save" data-action="save-add-quest" data-field="main">
<i class="fa-solid fa-check"></i> <span data-i18n-key="global.add">${i18n.getTranslation('global.add')}</span>
</button>
</div>
<div class="rpg-quest-item" data-field="main">
<div class="rpg-quest-title">${escapeHtml(questName)}</div>
<div class="rpg-quest-actions">
<button class="rpg-quest-edit" data-action="edit-quest" data-field="main" title="Edit quest">
<i class="fa-solid fa-edit"></i>
</button>
<button class="rpg-quest-remove" data-action="remove-quest" data-field="main" title="Complete/Remove quest">
<i class="fa-solid fa-check"></i>
</button>
</div>
</div>
` : `
<div class="rpg-inline-form" id="rpg-add-quest-form-main" style="display: none;">
<input type="text" class="rpg-inline-input" id="rpg-new-quest-main" placeholder="${i18n.getTranslation('quests.main.addQuestPlaceholder')}" data-i18n-placeholder-key="quests.main.addQuestPlaceholder" />
<div class="rpg-inline-actions">
<button class="rpg-inline-btn rpg-inline-cancel" data-action="cancel-add-quest" data-field="main">
<i class="fa-solid fa-times"></i> <span data-i18n-key="global.cancel">${i18n.getTranslation('global.cancel')}</span>
</button>
<button class="rpg-inline-btn rpg-inline-save" data-action="save-add-quest" data-field="main">
<i class="fa-solid fa-check"></i> <span data-i18n-key="global.add">${i18n.getTranslation('global.add')}</span>
</button>
</div>
</div>
<div class="rpg-quest-empty" data-i18n-key="quests.main.empty">${i18n.getTranslation('quests.main.empty')}</div>
`}
</div>
<div class="rpg-quest-hint">
<i class="fa-solid fa-lightbulb"></i>
<span data-i18n-key="quests.main.hint">${i18n.getTranslation('quests.main.hint')}</span>
</div>
<div class="rpg-item-list rpg-item-list-view">
${itemsHtml || `<div class="rpg-inventory-empty" data-i18n-key="quests.main.empty">${i18n.getTranslation('quests.main.empty')}</div>`}
</div>
</div>
</div>
`;
}
/**
* Renders the optional quests view
* @param {string[]} optionalQuests - Array of optional quest titles
* Renders the optional quests view (matches items/skills structure)
* @returns {string} HTML for optional quests view
*/
export function renderOptionalQuestsView(optionalQuests) {
// Use structured data helpers
export function renderOptionalQuestsView() {
const quests = getOptionalQuests().filter(q => q && q.name && q.name !== 'None');
// Track if add form is open
const isFormOpen = openAddForms?.optional || false;
let questsHtml = '';
let itemsHtml = '';
if (quests.length === 0) {
questsHtml = `<div class="rpg-quest-empty" data-i18n-key="quests.optional.empty">${i18n.getTranslation('quests.optional.empty')}</div>`;
itemsHtml = `<div class="rpg-inventory-empty" data-i18n-key="quests.optional.empty">${i18n.getTranslation('quests.optional.empty')}</div>`;
} else {
questsHtml = quests.map((quest, index) => `
<div class="rpg-quest-item" data-field="optional" data-index="${index}">
<div class="rpg-quest-title rpg-editable" contenteditable="true" data-field="optional" data-index="${index}" title="Click to edit">${escapeHtml(quest.name)}</div>
<div class="rpg-quest-actions">
<button class="rpg-quest-remove" data-action="remove-quest" data-field="optional" data-index="${index}" title="Complete/Remove quest">
<i class="fa-solid fa-check"></i>
// Render quests as items (list view style, matching items/skills)
itemsHtml = quests.map((quest, index) => `
<div class="rpg-item-row" data-field="optional" data-index="${index}">
<div class="rpg-item-main-row">
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="optional" data-index="${index}" data-prop="name" title="Click to edit">${escapeHtml(quest.name)}</span>
<button class="rpg-item-remove" data-action="remove-quest" data-field="optional" data-index="${index}" title="Complete/Remove quest">
<i class="fa-solid fa-times"></i>
</button>
</div>
<div class="rpg-item-desc-row">
<span class="rpg-item-description rpg-editable" contenteditable="true" data-field="optional" data-index="${index}" data-prop="description" title="Click to edit description">${escapeHtml(quest.description || '')}</span>
</div>
</div>
`).join('');
}
@@ -175,12 +149,12 @@ export function renderOptionalQuestsView(optionalQuests) {
<div class="rpg-quest-section">
<div class="rpg-quest-header">
<h3 class="rpg-quest-section-title" data-i18n-key="quests.optional.title">${i18n.getTranslation('quests.optional.title')}</h3>
<button class="rpg-add-quest-btn" data-action="add-quest" data-field="optional" title="${i18n.getTranslation('quests.optional.addQuestTitle')}">
<button class="rpg-inventory-add-btn" data-action="add-quest" data-field="optional" title="${i18n.getTranslation('quests.optional.addQuestTitle')}">
<i class="fa-solid fa-plus"></i> <span data-i18n-key="global.add">${i18n.getTranslation('global.add')}</span>
</button>
</div>
<div class="rpg-quest-content">
<div class="rpg-inline-form" id="rpg-add-quest-form-optional" style="display: none;">
<div class="rpg-inline-form" id="rpg-add-quest-form-optional" style="display: ${isFormOpen ? 'flex' : 'none'};">
<input type="text" class="rpg-inline-input" id="rpg-new-quest-optional" placeholder="${i18n.getTranslation('quests.optional.addQuestPlaceholder')}" data-i18n-placeholder-key="quests.optional.addQuestPlaceholder" />
<div class="rpg-inline-buttons">
<button class="rpg-inline-btn rpg-inline-cancel" data-action="cancel-add-quest" data-field="optional">
@@ -191,18 +165,17 @@ export function renderOptionalQuestsView(optionalQuests) {
</button>
</div>
</div>
<div class="rpg-quest-list">
${questsHtml}
</div>
<div class="rpg-quest-hint">
<i class="fa-solid fa-info-circle"></i>
<span data-i18n-key="quests.optional.hint">${i18n.getTranslation('quests.optional.hint')}</span>
<div class="rpg-item-list rpg-item-list-view">
${itemsHtml}
</div>
</div>
</div>
`;
}
// Track open add forms (matching items/skills pattern)
let openAddForms = {};
/**
* Main render function for quests
*/
@@ -214,10 +187,6 @@ export function renderQuests() {
// Get current sub-tab from container or default to 'main'
const activeSubTab = $questsContainer.data('active-subtab') || 'main';
// Get quests data
const mainQuest = extensionSettings.quests.main || 'None';
const optionalQuests = extensionSettings.quests.optional || [];
// Build HTML
let html = '<div class="rpg-quests-wrapper">';
html += renderQuestsSubTabs(activeSubTab);
@@ -225,9 +194,9 @@ export function renderQuests() {
// Render active sub-tab
html += '<div class="rpg-quests-panels">';
if (activeSubTab === 'main') {
html += renderMainQuestView(mainQuest);
html += renderMainQuestView();
} else {
html += renderOptionalQuestsView(optionalQuests);
html += renderOptionalQuestsView();
}
html += '</div></div>';
@@ -238,37 +207,39 @@ export function renderQuests() {
}
/**
* Attach event handlers for quest interactions
* Attach event handlers for quest interactions (matching items/skills pattern)
*/
function attachQuestEventHandlers() {
// Sub-tab switching
$questsContainer.find('.rpg-quests-subtab').on('click', function() {
$questsContainer.find('.rpg-quests-subtab').off('click').on('click', function() {
const tab = $(this).data('tab');
$questsContainer.data('active-subtab', tab);
renderQuests();
});
// Add quest button
$questsContainer.find('[data-action="add-quest"]').on('click', function() {
$questsContainer.find('[data-action="add-quest"]').off('click').on('click', function() {
const field = $(this).data('field');
$(`#rpg-add-quest-form-${field}`).show();
$(`#rpg-new-quest-${field}`).focus();
openAddForms[field] = true;
renderQuests();
setTimeout(() => {
$(`#rpg-new-quest-${field}`).focus();
}, 50);
});
// Cancel add quest
$questsContainer.find('[data-action="cancel-add-quest"]').on('click', function() {
$questsContainer.find('[data-action="cancel-add-quest"]').off('click').on('click', function() {
const field = $(this).data('field');
$(`#rpg-add-quest-form-${field}`).hide();
openAddForms[field] = false;
$(`#rpg-new-quest-${field}`).val('');
renderQuests();
});
// Save add quest
$questsContainer.find('[data-action="save-add-quest"]').on('click', function() {
$questsContainer.find('[data-action="save-add-quest"]').off('click').on('click', function() {
const field = $(this).data('field');
const nameInput = $(`#rpg-new-quest-${field}`);
const descInput = $(`#rpg-new-quest-desc-${field}`);
const questTitle = nameInput.val().trim();
const questDesc = descInput?.val()?.trim() || '';
if (questTitle) {
// Ensure structured format exists
@@ -277,117 +248,77 @@ function attachQuestEventHandlers() {
}
if (field === 'main') {
extensionSettings.quests.main = questTitle;
extensionSettings.questsV2.main = { name: questTitle, description: questDesc };
extensionSettings.questsV2.main = { name: questTitle, description: '' };
} else {
if (!extensionSettings.quests.optional) {
extensionSettings.quests.optional = [];
}
if (!extensionSettings.questsV2.optional) {
extensionSettings.questsV2.optional = [];
}
extensionSettings.quests.optional.push(questTitle);
extensionSettings.questsV2.optional.push({ name: questTitle, description: questDesc });
extensionSettings.questsV2.optional.push({ name: questTitle, description: '' });
}
openAddForms[field] = false;
saveSettings();
renderQuests();
}
});
// Edit quest (main only)
$questsContainer.find('[data-action="edit-quest"]').on('click', function() {
const field = $(this).data('field');
$(`#rpg-edit-quest-form-${field}`).show();
$('.rpg-quest-item[data-field="main"]').hide();
$(`#rpg-edit-quest-${field}`).focus();
});
// Cancel edit quest
$questsContainer.find('[data-action="cancel-edit-quest"]').on('click', function() {
const field = $(this).data('field');
$(`#rpg-edit-quest-form-${field}`).hide();
$('.rpg-quest-item[data-field="main"]').show();
});
// Save edit quest (main)
$questsContainer.find('[data-action="save-edit-quest"]').on('click', function() {
const field = $(this).data('field');
const nameInput = $(`#rpg-edit-quest-${field}`);
const descInput = $(`#rpg-edit-quest-desc-${field}`);
const questTitle = nameInput.val().trim();
const questDesc = descInput.val()?.trim() || '';
if (questTitle) {
// Use structured format
if (!extensionSettings.questsV2) {
extensionSettings.questsV2 = { main: null, optional: [] };
}
extensionSettings.questsV2.main = { name: questTitle, description: questDesc };
// Also update legacy for backwards compatibility
extensionSettings.quests.main = questTitle;
saveSettings();
saveChatData();
renderQuests();
}
});
// Remove quest
$questsContainer.find('[data-action="remove-quest"]').on('click', function() {
$questsContainer.find('[data-action="remove-quest"]').off('click').on('click', function() {
const field = $(this).data('field');
const index = $(this).data('index');
if (field === 'main') {
extensionSettings.quests.main = 'None';
if (extensionSettings.questsV2) {
extensionSettings.questsV2.main = null;
}
} else {
extensionSettings.quests.optional.splice(index, 1);
if (extensionSettings.questsV2?.optional) {
extensionSettings.questsV2.optional.splice(index, 1);
}
}
saveSettings();
saveChatData();
renderQuests();
});
// Inline editing for optional quests (name and description)
$questsContainer.find('.rpg-quest-title.rpg-editable, .rpg-quest-description.rpg-editable').on('blur', function() {
// Inline editing for quests (name and description) - matching items/skills pattern
$questsContainer.off('blur', '.rpg-item-name.rpg-editable, .rpg-item-description.rpg-editable')
.on('blur', '.rpg-item-name.rpg-editable, .rpg-item-description.rpg-editable', function() {
const $this = $(this);
const field = $this.data('field');
const index = $this.data('index');
const prop = $this.data('prop') || 'name';
const newValue = $this.text().trim();
if (field === 'optional' && index !== undefined) {
// Ensure structured format exists
if (!extensionSettings.questsV2) {
extensionSettings.questsV2 = { main: null, optional: [] };
// Ensure structured format exists
if (!extensionSettings.questsV2) {
extensionSettings.questsV2 = { main: null, optional: [] };
}
if (field === 'main') {
// Update main quest
if (!extensionSettings.questsV2.main) {
extensionSettings.questsV2.main = { name: '', description: '' };
}
extensionSettings.questsV2.main[prop] = newValue;
} else if (field === 'optional' && index !== undefined) {
// Update optional quest
if (!extensionSettings.questsV2.optional[index]) {
extensionSettings.questsV2.optional[index] = { name: '', description: '' };
}
extensionSettings.questsV2.optional[index][prop] = newValue;
// Also update legacy for backwards compatibility
if (prop === 'name') {
extensionSettings.quests.optional[index] = newValue;
}
saveSettings();
}
saveSettings();
saveChatData();
});
// Enter key to save in forms
$questsContainer.find('.rpg-inline-input').on('keypress', function(e) {
// Enter key to save in forms (matching items/skills pattern)
$questsContainer.find('.rpg-inline-input').off('keypress').on('keypress', function(e) {
if (e.which === 13) {
const field = $(this).attr('id').includes('edit') ?
$(this).attr('id').replace('rpg-edit-quest-', '') :
$(this).attr('id').replace('rpg-new-quest-', '');
if ($(this).attr('id').includes('edit')) {
$(`[data-action="save-edit-quest"][data-field="${field}"]`).click();
} else {
$(`[data-action="save-add-quest"][data-field="${field}"]`).click();
}
const field = $(this).attr('id').replace('rpg-new-quest-', '');
$(`[data-action="save-add-quest"][data-field="${field}"]`).click();
}
});
}
+3 -3
View File
@@ -37,10 +37,10 @@ function serializeItems(items) {
*/
export function getSkillCategories() {
const categories = extensionSettings.trackerConfig?.userStats?.skillsSection?.customFields || [];
// Handle both old format (string array) and new format (object array)
// Migration function handles string array → object array conversion on load
return categories
.filter(cat => typeof cat === 'string' || cat.enabled !== false)
.map(cat => typeof cat === 'string' ? cat : cat.name);
.filter(cat => cat.enabled !== false)
.map(cat => cat.name);
}
/**
-63
View File
@@ -101,45 +101,6 @@ function namesMatch(cardName, aiName) {
return wordBoundary.test(aiCore);
}
/**
* Gets relationship emoji from relationship string
* Returns a default emoji (⚖️) if relationship is not in the predefined map
*/
function getRelationshipEmoji(relationship) {
if (!relationship) return null;
const map = {
'Enemy': '⚔️',
'Neutral': '⚖️',
'Friend': '⭐',
'Lover': '❤️',
'Ally': '🤝',
'Rival': '🎯',
'Family': '👨‍👩‍👧',
'Stranger': '❓'
};
// Return mapped emoji or default '⚖️' for unknown relationships
return map[relationship] || '⚖️';
}
/**
* Gets character avatar URL
*/
function getCharacterAvatarUrl(characterName) {
// Try to find matching character from SillyTavern
try {
const context = getContext();
if (context && characters) {
const char = characters.find(c => namesMatch(c.name, characterName));
if (char && char.avatar) {
return getSafeThumbnailUrl('avatar', char.avatar);
}
}
} catch (e) {
debugLog('[RPG Thoughts] Error getting avatar:', e);
}
return FALLBACK_AVATAR_DATA_URI;
}
export function renderThoughts() {
if (!extensionSettings.showCharacterThoughts || !$thoughtsContainer) {
return;
@@ -818,11 +779,9 @@ export function removeCharacter(characterName) {
}
const lines = lastGeneratedData.characterThoughts.split('\n');
const presentCharsConfig = extensionSettings.trackerConfig?.presentCharacters;
let characterFound = false;
let inTargetCharacter = false;
let characterStartIndex = -1;
let characterEndIndex = -1;
const linesToRemove = [];
@@ -835,7 +794,6 @@ export function removeCharacter(characterName) {
if (name.toLowerCase() === characterName.toLowerCase()) {
characterFound = true;
inTargetCharacter = true;
characterStartIndex = i;
linesToRemove.push(i);
} else if (inTargetCharacter) {
characterEndIndex = i;
@@ -902,12 +860,6 @@ export function removeCharacter(characterName) {
* Creates floating thought bubbles positioned near character avatars.
*/
export function updateChatThoughts() {
// console.log('[RPG Companion] ======== updateChatThoughts called ========');
// console.log('[RPG Companion] Extension enabled:', extensionSettings.enabled);
// console.log('[RPG Companion] showThoughtsInChat setting:', extensionSettings.showThoughtsInChat);
// console.log('[RPG Companion] Toggle element checked:', $('#rpg-toggle-thoughts-in-chat').prop('checked'));
// console.log('[RPG Companion] lastGeneratedData.characterThoughts:', lastGeneratedData.characterThoughts);
// Remove existing thought panel and icon
$('#rpg-thought-panel').remove();
$('#rpg-thought-icon').remove();
@@ -915,9 +867,7 @@ export function updateChatThoughts() {
$(window).off('resize.thoughtPanel');
$(document).off('click.thoughtPanel');
// If extension is disabled, thoughts in chat are disabled, or no thoughts, just return
if (!extensionSettings.enabled || !extensionSettings.showThoughtsInChat || !lastGeneratedData.characterThoughts) {
// console.log('[RPG Companion] Thoughts in chat disabled or no data');
return;
}
@@ -927,8 +877,6 @@ export function updateChatThoughts() {
const thoughtsConfig = extensionSettings.trackerConfig?.presentCharacters?.thoughts;
const thoughtsLabel = thoughtsConfig?.name || 'Thoughts';
// console.log('[RPG Companion] Parsing thoughts from lines:', lines);
// Parse new format to build character map and thoughts
let currentCharName = null;
let currentCharEmoji = null;
@@ -985,13 +933,9 @@ export function updateChatThoughts() {
// If no thoughts parsed, return
if (thoughtsArray.length === 0) {
// console.log('[RPG Companion] No thoughts parsed, returning');
return;
}
// console.log('[RPG Companion] Total thoughts:', thoughtsArray.length);
// console.log('[RPG Companion] Thoughts array:', thoughtsArray);
// Find the last message to position near
const $messages = $('#chat .mes');
let $targetMessage = null;
@@ -1006,7 +950,6 @@ export function updateChatThoughts() {
}
if (!$targetMessage) {
// console.log('[RPG Companion] No target message found');
return;
}
@@ -1026,10 +969,8 @@ export function createThoughtPanel($message, thoughtsArray) {
$('#rpg-thought-panel').remove();
$('#rpg-thought-icon').remove();
// Get the avatar position from the message
const $avatar = $message.find('.avatar img');
if (!$avatar.length) {
// console.log('[RPG Companion] No avatar found in message');
return;
}
@@ -1226,14 +1167,10 @@ export function createThoughtPanel($message, thoughtsArray) {
});
}
// console.log('[RPG Companion] Thought panel created at:', { top, left });
// Add event handlers for editable thoughts in the bubble
$thoughtPanel.find('.rpg-editable').on('blur', function() {
const character = $(this).data('character');
const field = $(this).data('field');
const value = $(this).text().trim();
// console.log('[RPG Companion] 💭 Thought bubble blur event - character:', character, 'field:', field, 'value:', value);
updateCharacterField(character, field, value);
});
+12 -16
View File
@@ -317,23 +317,19 @@ export function renderUserStats() {
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'
};
// Update the stat name in customStats array (new format)
const config = extensionSettings.trackerConfig?.userStats;
if (config && config.customStats) {
const stat = config.customStats.find(s => s.id === field);
if (stat && value) {
stat.name = value;
saveSettings();
saveChatData();
// Re-render to update the display
renderUserStats();
}
}
extensionSettings.statNames[field] = value || extensionSettings.statNames[field];
saveSettings();
saveChatData();
// Re-render to update the display
renderUserStats();
});
// Add event listener for level editing