chore: final cleanup
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user