feat: json format, et al.

This commit is contained in:
Subarashimo
2025-12-03 14:55:30 +01:00
parent 56349f30e6
commit 0f7fdfcef1
28 changed files with 5692 additions and 237 deletions
+283 -53
View File
@@ -51,6 +51,175 @@ function separateEmojiFromText(str) {
return { emoji: '', text: str };
}
/**
* Checks if a value is valid (not null, undefined, or the string "null")
*/
function isValidValue(val) {
return val !== null && val !== undefined && val !== 'null' && val !== '';
}
/**
* Checks if we have valid structured infoBox data
* @param {Object} data - The infoBoxData object
* @returns {boolean}
*/
function hasStructuredInfoBoxData(data) {
if (!data) return false;
// Handle recentEvents as either string or array
const hasEvents = data.recentEvents && (
(Array.isArray(data.recentEvents) && data.recentEvents.length > 0) ||
(typeof data.recentEvents === 'string' && data.recentEvents.length > 0 && data.recentEvents !== 'null')
);
return isValidValue(data.date) || isValidValue(data.weather) || isValidValue(data.temperature) ||
isValidValue(data.time) || isValidValue(data.location) || hasEvents;
}
/**
* Renders the info box using structured JSON data
* @param {Object} data - Structured infoBox data
*/
function renderStructuredInfoBox(data) {
const config = extensionSettings.trackerConfig?.infoBox;
const widgets = config?.widgets || {};
// Build widgets HTML
let widgetsHtml = '';
let widgetCount = 0;
// Date widget - skip null values
if (widgets.date?.enabled && isValidValue(data.date)) {
widgetCount++;
widgetsHtml += `
<div class="rpg-dashboard-widget rpg-calendar-widget">
<i class="fa-solid fa-calendar"></i>
<div class="rpg-widget-content">
<span class="rpg-widget-label" data-i18n-key="infobox.date">${i18n.getTranslation('infobox.date')}</span>
<span class="rpg-widget-value rpg-editable" contenteditable="true" data-field="date">${data.date}</span>
</div>
</div>`;
}
// Weather widget - skip null values
if (widgets.weather?.enabled && isValidValue(data.weather)) {
widgetCount++;
const { emoji, text } = separateEmojiFromText(data.weather);
widgetsHtml += `
<div class="rpg-dashboard-widget rpg-weather-widget">
<span class="rpg-weather-emoji rpg-editable" contenteditable="true" data-field="weatherEmoji">${emoji || '🌤️'}</span>
<div class="rpg-widget-content">
<span class="rpg-widget-label" data-i18n-key="infobox.weather">${i18n.getTranslation('infobox.weather')}</span>
<span class="rpg-widget-value rpg-editable" contenteditable="true" data-field="weather">${text}</span>
</div>
</div>`;
}
// Temperature widget - skip null values
if (widgets.temperature?.enabled && isValidValue(data.temperature)) {
widgetCount++;
widgetsHtml += `
<div class="rpg-dashboard-widget rpg-temperature-widget">
<i class="fa-solid fa-temperature-half"></i>
<div class="rpg-widget-content">
<span class="rpg-widget-label" data-i18n-key="infobox.temperature">${i18n.getTranslation('infobox.temperature')}</span>
<span class="rpg-widget-value rpg-editable" contenteditable="true" data-field="temperature">${data.temperature}</span>
</div>
</div>`;
}
// Time widget - skip null values
if (widgets.time?.enabled && isValidValue(data.time)) {
widgetCount++;
widgetsHtml += `
<div class="rpg-dashboard-widget rpg-time-widget">
<i class="fa-solid fa-clock"></i>
<div class="rpg-widget-content">
<span class="rpg-widget-label" data-i18n-key="infobox.time">${i18n.getTranslation('infobox.time')}</span>
<span class="rpg-widget-value rpg-editable" contenteditable="true" data-field="time">${data.time}</span>
</div>
</div>`;
}
// Location widget - skip null values
if (widgets.location?.enabled && isValidValue(data.location)) {
widgetCount++;
widgetsHtml += `
<div class="rpg-dashboard-widget rpg-location-widget">
<i class="fa-solid fa-map-marker-alt"></i>
<div class="rpg-widget-content">
<span class="rpg-widget-label" data-i18n-key="infobox.location">${i18n.getTranslation('infobox.location')}</span>
<span class="rpg-widget-value rpg-editable" contenteditable="true" data-field="location">${data.location}</span>
</div>
</div>`;
}
// Recent events widget - handle both string and array formats
const recentEvents = Array.isArray(data.recentEvents)
? data.recentEvents
: (data.recentEvents ? [data.recentEvents] : []);
if (widgets.recentEvents?.enabled && recentEvents.length > 0) {
widgetCount++;
const eventsHtml = recentEvents.map((event, i) =>
`<li class="rpg-event-item rpg-editable" contenteditable="true" data-field="recentEvents" data-index="${i}">${event}</li>`
).join('');
widgetsHtml += `
<div class="rpg-dashboard-widget rpg-events-widget rpg-widget-wide">
<i class="fa-solid fa-scroll"></i>
<div class="rpg-widget-content">
<span class="rpg-widget-label" data-i18n-key="infobox.recentEvents">${i18n.getTranslation('infobox.recentEvents')}</span>
<ul class="rpg-events-list">${eventsHtml}</ul>
</div>
</div>`;
}
// Determine layout class based on widget count
const layoutClass = widgetCount <= 2 ? 'rpg-dashboard-row-1' :
widgetCount <= 4 ? 'rpg-dashboard-row-2' : 'rpg-dashboard-row-3';
const html = `<div class="rpg-dashboard ${layoutClass}">${widgetsHtml}</div>`;
$infoBoxContainer.html(html);
// Remove updating animation
if (extensionSettings.enableAnimations) {
setTimeout(() => $infoBoxContainer.removeClass('rpg-content-updating'), 300);
}
// Setup event listeners for editable fields
setupStructuredInfoBoxEventListeners();
}
/**
* Setup event listeners for structured info box editing
*/
function setupStructuredInfoBoxEventListeners() {
$infoBoxContainer.off('blur', '.rpg-editable').on('blur', '.rpg-editable', function() {
const $this = $(this);
const field = $this.data('field');
const index = $this.data('index');
const newValue = $this.text().trim();
if (!extensionSettings.infoBoxData) {
extensionSettings.infoBoxData = {};
}
if (field === 'recentEvents' && index !== undefined) {
if (!extensionSettings.infoBoxData.recentEvents) {
extensionSettings.infoBoxData.recentEvents = [];
}
extensionSettings.infoBoxData.recentEvents[index] = newValue;
} else if (field === 'weatherEmoji') {
// Combine emoji with existing weather text
const currentWeather = extensionSettings.infoBoxData.weather || '';
const { text } = separateEmojiFromText(currentWeather);
extensionSettings.infoBoxData.weather = newValue + ' ' + text;
} else {
extensionSettings.infoBoxData[field] = newValue;
}
saveChatData();
});
}
/**
* Renders the info box as a visual dashboard with calendar, weather, temperature, clock, and map widgets.
* Includes event listeners for editable fields.
@@ -65,8 +234,28 @@ export function renderInfoBox() {
$infoBoxContainer.addClass('rpg-content-updating');
}
// Use committedTrackerData as fallback if lastGeneratedData is empty (e.g., after page refresh)
const infoBoxData = lastGeneratedData.infoBox || committedTrackerData.infoBox;
// Convert structured JSON data to text format for the original fancy renderer
const structuredData = extensionSettings.infoBoxData;
let infoBoxData = lastGeneratedData.infoBox || committedTrackerData.infoBox;
// If we have structured data, convert it to text format
if (structuredData && hasStructuredInfoBoxData(structuredData)) {
const lines = [];
if (isValidValue(structuredData.date)) lines.push(`Date: ${structuredData.date}`);
if (isValidValue(structuredData.time)) lines.push(`Time: ${structuredData.time}`);
if (isValidValue(structuredData.weather)) lines.push(`Weather: ${structuredData.weather}`);
if (isValidValue(structuredData.temperature)) lines.push(`Temperature: ${structuredData.temperature}`);
if (isValidValue(structuredData.location)) lines.push(`Location: ${structuredData.location}`);
if (structuredData.recentEvents) {
const events = Array.isArray(structuredData.recentEvents)
? structuredData.recentEvents
: [structuredData.recentEvents];
events.filter(e => e && e !== 'null').forEach(e => lines.push(`Recent Events: ${e}`));
}
if (lines.length > 0) {
infoBoxData = lines.join('\n');
}
}
// If no data yet, show placeholder
if (!infoBoxData) {
@@ -117,105 +306,132 @@ 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';
// 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;
if (isValidParsedValue(dateStr)) {
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;
if (isValidParsedValue(dateStr)) {
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]);
if (isValidParsedValue(tempStr)) {
data.temperature = tempStr;
const tempMatch = tempStr.match(/(-?\d+)/);
if (tempMatch) {
data.tempValue = parseInt(tempMatch[1]);
}
parsedFields.temperature = 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]);
if (isValidParsedValue(tempStr)) {
data.temperature = tempStr;
const tempMatch = tempStr.match(/(-?\d+)/);
if (tempMatch) {
data.tempValue = parseInt(tempMatch[1]);
}
parsedFields.temperature = 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;
if (isValidParsedValue(timeStr)) {
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;
if (isValidParsedValue(timeStr)) {
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;
const locStr = line.replace('Location:', '').trim();
if (isValidParsedValue(locStr)) {
data.location = locStr;
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;
const locStr = line.replace('🗺️:', '').trim();
if (isValidParsedValue(locStr)) {
data.location = locStr;
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
const weatherParts = weatherStr.split(',').map(p => p.trim());
data.weatherEmoji = weatherParts[0] || '';
data.weatherForecast = weatherParts[1] || '';
// Skip null/invalid values
if (!isValidParsedValue(weatherStr)) {
parsedFields.weather = true; // Mark as parsed so we don't try again
} else {
// No clear separation - assume it's all forecast text
data.weatherEmoji = '🌤️'; // Default emoji
data.weatherForecast = weatherStr;
}
const { emoji, text } = separateEmojiFromText(weatherStr);
parsedFields.weather = true;
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 {
// Check if it's a legacy weather line (emoji format)
@@ -271,6 +487,20 @@ export function renderInfoBox() {
// location: data.location
// });
// Sanitize parsed values - filter out "null" strings and invalid values
const sanitize = (val) => (val && val !== 'null' && val !== 'undefined' && val.toLowerCase() !== 'none') ? val : '';
data.date = sanitize(data.date);
data.weekday = sanitize(data.weekday);
data.month = sanitize(data.month);
data.year = sanitize(data.year);
data.weatherEmoji = sanitize(data.weatherEmoji);
data.weatherForecast = sanitize(data.weatherForecast);
data.temperature = sanitize(data.temperature);
data.time = sanitize(data.time);
data.timeStart = sanitize(data.timeStart);
data.timeEnd = sanitize(data.timeEnd);
data.location = sanitize(data.location);
// Get tracker configuration
const config = extensionSettings.trackerConfig?.infoBox;