Fix extension loading, enhance theming, add horizontal scrolling, improve emoji parsing, rename to Main Quests

This commit is contained in:
Spicy_Marinara
2025-10-26 22:31:21 +01:00
parent d68ddd601e
commit 141a3f4bec
17 changed files with 2888 additions and 437 deletions
+207 -6
View File
@@ -12,6 +12,44 @@ import {
} from '../../core/state.js';
import { saveChatData } from '../../core/persistence.js';
/**
* Helper to separate emoji from text in a string
* Handles cases where there's no comma or space after emoji
* @param {string} str - String potentially containing emoji followed by text
* @returns {{emoji: string, text: string}} Separated emoji and text
*/
function separateEmojiFromText(str) {
if (!str) return { emoji: '', text: '' };
str = str.trim();
// Regex to match emoji at the start (handles most emoji including compound ones)
const emojiRegex = /^[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{1F000}-\u{1F02F}\u{1F0A0}-\u{1F0FF}\u{1F100}-\u{1F64F}\u{1F680}-\u{1F6FF}\u{1F910}-\u{1F96B}\u{1F980}-\u{1F9E0}\u{FE00}-\u{FE0F}\u{200D}\u{20E3}]+/u;
const emojiMatch = str.match(emojiRegex);
if (emojiMatch) {
const emoji = emojiMatch[0];
let text = str.substring(emoji.length).trim();
// Remove leading comma or space if present
text = text.replace(/^[,\s]+/, '');
return { emoji, text };
}
// No emoji found - check if there's a comma separator anyway
const commaParts = str.split(',');
if (commaParts.length >= 2) {
return {
emoji: commaParts[0].trim(),
text: commaParts.slice(1).join(',').trim()
};
}
// No clear separation - return original as text
return { emoji: '', text: str };
}
/**
* Renders the info box as a visual dashboard with calendar, weather, temperature, clock, and map widgets.
* Includes event listeners for editable fields.
@@ -155,11 +193,24 @@ export function renderInfoBox() {
}
} else if (line.startsWith('Weather:')) {
if (!parsedFields.weather) {
// New text format: Weather: [Emoji], [Forecast]
// New text format: Weather: [Emoji], [Forecast] OR Weather: [Emoji][Forecast] (no separator - FIXED)
const weatherStr = line.replace('Weather:', '').trim();
const weatherParts = weatherStr.split(',').map(p => p.trim());
data.weatherEmoji = weatherParts[0] || '';
data.weatherForecast = weatherParts[1] || '';
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
const weatherParts = weatherStr.split(',').map(p => p.trim());
data.weatherEmoji = weatherParts[0] || '';
data.weatherForecast = weatherParts[1] || '';
} else {
// No clear separation - assume it's all forecast text
data.weatherEmoji = '🌤️'; // Default emoji
data.weatherForecast = weatherStr;
}
parsedFields.weather = true;
}
} else {
@@ -217,8 +268,11 @@ export function renderInfoBox() {
// });
// Build visual dashboard HTML
// Wrap all content in a scrollable container
let html = '<div class="rpg-info-content">';
// Row 1: Date, Weather, Temperature, Time widgets
let html = '<div class="rpg-dashboard rpg-dashboard-row-1">';
html += '<div class="rpg-dashboard rpg-dashboard-row-1">';
// Calendar widget - always show (editable even if empty)
// Display abbreviated version but allow editing full value
@@ -301,6 +355,67 @@ export function renderInfoBox() {
</div>
`;
// Row 3: Recent Events widget (notebook style) - dynamically show 1-3 events
// Parse Recent Events from infoBox string
let recentEvents = [];
if (committedTrackerData.infoBox) {
const recentEventsLine = committedTrackerData.infoBox.split('\n').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);
}
}
}
const validEvents = recentEvents.filter(e => e && e.trim() && e !== 'Event 1' && e !== 'Event 2' && e !== 'Event 3');
// If no valid events, show at least one placeholder
if (validEvents.length === 0) {
validEvents.push('Click to add event');
}
html += `
<div class="rpg-dashboard rpg-dashboard-row-3">
<div class="rpg-dashboard-widget rpg-events-widget">
<div class="rpg-notebook-header">
<div class="rpg-notebook-ring"></div>
<div class="rpg-notebook-ring"></div>
<div class="rpg-notebook-ring"></div>
</div>
<div class="rpg-notebook-title">Recent Events</div>
<div class="rpg-notebook-lines">
`;
// Dynamically generate event lines (max 3)
for (let i = 0; i < Math.min(validEvents.length, 3); i++) {
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>
</div>
`;
}
// If we have less than 3 events, add empty placeholders with + icon
for (let i = validEvents.length; i < 3; i++) {
html += `
<div class="rpg-notebook-line rpg-event-add">
<span class="rpg-bullet">+</span>
<span class="rpg-event-text rpg-editable rpg-event-placeholder" contenteditable="true" data-field="event${i + 1}" title="Click to add event">Add event...</span>
</div>
`;
}
html += `
</div>
</div>
</div>
`;
// Close the scrollable content wrapper
html += '</div>';
$infoBoxContainer.html(html);
// Add event handlers for editable Info Box fields
@@ -320,7 +435,12 @@ export function renderInfoBox() {
}
}
updateInfoBoxField(field, value);
// Handle recent events separately
if (field === 'event1' || field === 'event2' || field === 'event3') {
updateRecentEvent(field, value);
} else {
updateInfoBoxField(field, value);
}
});
// For date fields, show full value on focus
@@ -610,3 +730,84 @@ export function updateInfoBoxField(field, value) {
renderInfoBox();
}
}
/**
* Update a recent event in the committed tracker data
* @param {string} field - event1, event2, or event3
* @param {string} value - New event text
*/
function updateRecentEvent(field, value) {
// Map field to index
const eventIndex = {
'event1': 0,
'event2': 1,
'event3': 2
}[field];
if (eventIndex !== undefined) {
// Parse current infoBox to get existing events
const lines = (committedTrackerData.infoBox || '').split('\n');
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);
}
}
// Ensure array has enough slots
while (recentEvents.length <= eventIndex) {
recentEvents.push('');
}
// Update the specific event
recentEvents[eventIndex] = value;
// Filter out empty events and rebuild the line
const validEvents = recentEvents.filter(e => e && e.trim());
const newRecentEventsLine = validEvents.length > 0
? `Recent Events: ${validEvents.join(', ')}`
: '';
// Update infoBox with new Recent Events line
const updatedLines = lines.filter(line => !line.startsWith('Recent Events:'));
if (newRecentEventsLine) {
// Add Recent Events line at the end (before any empty lines)
let insertIndex = updatedLines.length;
for (let i = updatedLines.length - 1; i >= 0; i--) {
if (updatedLines[i].trim() !== '') {
insertIndex = i + 1;
break;
}
}
updatedLines.splice(insertIndex, 0, newRecentEventsLine);
}
committedTrackerData.infoBox = updatedLines.join('\n');
lastGeneratedData.infoBox = updatedLines.join('\n');
// Update the message's swipe data
const chat = getContext().chat;
if (chat && chat.length > 0) {
for (let i = chat.length - 1; i >= 0; i--) {
const message = chat[i];
if (!message.is_user) {
if (message.extra && message.extra.rpg_companion_swipes) {
const swipeId = message.swipe_id || 0;
if (message.extra.rpg_companion_swipes[swipeId]) {
message.extra.rpg_companion_swipes[swipeId].infoBox = updatedLines.join('\n');
}
}
break;
}
}
}
saveChatData();
renderInfoBox();
console.log(`[RPG Companion] Updated recent event ${field}:`, value);
}
}