Merge branch 'main' into fix/user-parsing-issues
This commit is contained in:
@@ -328,34 +328,12 @@ async function initUI() {
|
|||||||
toggleAnimations();
|
toggleAnimations();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Bind to both desktop and mobile refresh buttons
|
$('#rpg-manual-update').on('click', async function() {
|
||||||
$('#rpg-manual-update, #rpg-manual-update-mobile').on('click', async function() {
|
|
||||||
// Get mobile button reference
|
|
||||||
const $mobileBtn = $('#rpg-manual-update-mobile');
|
|
||||||
|
|
||||||
// Skip if we just finished dragging the mobile button
|
|
||||||
if ($mobileBtn.data('just-dragged')) {
|
|
||||||
console.log('[RPG Companion] Click blocked - just finished dragging refresh button');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!extensionSettings.enabled) {
|
if (!extensionSettings.enabled) {
|
||||||
// console.log('[RPG Companion] Extension is disabled. Please enable it in the Extensions tab.');
|
// console.log('[RPG Companion] Extension is disabled. Please enable it in the Extensions tab.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
await updateRPGData(renderUserStats, renderInfoBox, renderThoughts, renderInventory);
|
||||||
// Remove focus to prevent sticky black state on mobile
|
|
||||||
$(this).blur();
|
|
||||||
|
|
||||||
// Add spinning animation to mobile button
|
|
||||||
$mobileBtn.addClass('spinning');
|
|
||||||
|
|
||||||
try {
|
|
||||||
await updateRPGData(renderUserStats, renderInfoBox, renderThoughts, renderInventory);
|
|
||||||
} finally {
|
|
||||||
// Remove spinning animation when done
|
|
||||||
$mobileBtn.removeClass('spinning');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reset FAB positions button
|
// Reset FAB positions button
|
||||||
|
|||||||
+1
-1
@@ -6,6 +6,6 @@
|
|||||||
"js": "index.js",
|
"js": "index.js",
|
||||||
"css": "style.css",
|
"css": "style.css",
|
||||||
"author": "Marysia",
|
"author": "Marysia",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"homePage": "https://github.com/SpicyMarinara/rpg-companion-sillytavern"
|
"homePage": "https://github.com/SpicyMarinara/rpg-companion-sillytavern"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,82 +63,146 @@ export function renderInfoBox() {
|
|||||||
characters: []
|
characters: []
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Track which fields we've already parsed to avoid duplicates from mixed formats
|
||||||
|
const parsedFields = {
|
||||||
|
date: false,
|
||||||
|
temperature: false,
|
||||||
|
time: false,
|
||||||
|
location: false,
|
||||||
|
weather: false
|
||||||
|
};
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
// console.log('[RPG Companion] Processing line:', line);
|
// console.log('[RPG Companion] Processing line:', line);
|
||||||
|
|
||||||
// Support both new text format (Date:) and legacy emoji format (🗓️:)
|
// Support both new text format (Date:) and legacy emoji format (🗓️:)
|
||||||
if (line.startsWith('Date:') || line.includes('🗓️:')) {
|
// Prioritize text format over emoji format
|
||||||
// console.log('[RPG Companion] → Matched DATE');
|
if (line.startsWith('Date:')) {
|
||||||
const dateStr = line.replace('Date:', '').replace('🗓️:', '').trim();
|
if (!parsedFields.date) {
|
||||||
// Parse format: "Weekday, Month Day, Year" or "Weekday, Month, Year"
|
// console.log('[RPG Companion] → Matched DATE (text format)');
|
||||||
const dateParts = dateStr.split(',').map(p => p.trim());
|
const dateStr = line.replace('Date:', '').trim();
|
||||||
data.weekday = dateParts[0] || '';
|
const dateParts = dateStr.split(',').map(p => p.trim());
|
||||||
data.month = dateParts[1] || '';
|
data.weekday = dateParts[0] || '';
|
||||||
data.year = dateParts[2] || '';
|
data.month = dateParts[1] || '';
|
||||||
data.date = dateStr;
|
data.year = dateParts[2] || '';
|
||||||
} else if (line.startsWith('Temperature:') || line.includes('🌡️:')) {
|
data.date = dateStr;
|
||||||
// console.log('[RPG Companion] → Matched TEMPERATURE');
|
parsedFields.date = true;
|
||||||
const tempStr = line.replace('Temperature:', '').replace('🌡️:', '').trim();
|
}
|
||||||
data.temperature = tempStr;
|
} else if (line.includes('🗓️:')) {
|
||||||
// Extract numeric value
|
if (!parsedFields.date) {
|
||||||
const tempMatch = tempStr.match(/(-?\d+)/);
|
// console.log('[RPG Companion] → Matched DATE (emoji format)');
|
||||||
if (tempMatch) {
|
const dateStr = line.replace('🗓️:', '').trim();
|
||||||
data.tempValue = parseInt(tempMatch[1]);
|
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]);
|
||||||
|
}
|
||||||
|
parsedFields.temperature = true;
|
||||||
|
}
|
||||||
|
} else if (line.includes('🌡️:')) {
|
||||||
|
if (!parsedFields.temperature) {
|
||||||
|
// console.log('[RPG Companion] → Matched TEMPERATURE (emoji format)');
|
||||||
|
const tempStr = line.replace('🌡️:', '').trim();
|
||||||
|
data.temperature = tempStr;
|
||||||
|
const tempMatch = tempStr.match(/(-?\d+)/);
|
||||||
|
if (tempMatch) {
|
||||||
|
data.tempValue = parseInt(tempMatch[1]);
|
||||||
|
}
|
||||||
|
parsedFields.temperature = true;
|
||||||
|
}
|
||||||
|
} else if (line.startsWith('Time:')) {
|
||||||
|
if (!parsedFields.time) {
|
||||||
|
// console.log('[RPG Companion] → Matched TIME (text format)');
|
||||||
|
const timeStr = line.replace('Time:', '').trim();
|
||||||
|
data.time = timeStr;
|
||||||
|
const timeParts = timeStr.split('→').map(t => t.trim());
|
||||||
|
data.timeStart = timeParts[0] || '';
|
||||||
|
data.timeEnd = timeParts[1] || '';
|
||||||
|
parsedFields.time = true;
|
||||||
|
}
|
||||||
|
} else if (line.includes('🕒:')) {
|
||||||
|
if (!parsedFields.time) {
|
||||||
|
// console.log('[RPG Companion] → Matched TIME (emoji format)');
|
||||||
|
const timeStr = line.replace('🕒:', '').trim();
|
||||||
|
data.time = timeStr;
|
||||||
|
const timeParts = timeStr.split('→').map(t => t.trim());
|
||||||
|
data.timeStart = timeParts[0] || '';
|
||||||
|
data.timeEnd = timeParts[1] || '';
|
||||||
|
parsedFields.time = true;
|
||||||
|
}
|
||||||
|
} else if (line.startsWith('Location:')) {
|
||||||
|
if (!parsedFields.location) {
|
||||||
|
// console.log('[RPG Companion] → Matched LOCATION (text format)');
|
||||||
|
data.location = line.replace('Location:', '').trim();
|
||||||
|
parsedFields.location = true;
|
||||||
|
}
|
||||||
|
} else if (line.includes('🗺️:')) {
|
||||||
|
if (!parsedFields.location) {
|
||||||
|
// console.log('[RPG Companion] → Matched LOCATION (emoji format)');
|
||||||
|
data.location = line.replace('🗺️:', '').trim();
|
||||||
|
parsedFields.location = true;
|
||||||
}
|
}
|
||||||
} else if (line.startsWith('Time:') || line.includes('🕒:')) {
|
|
||||||
// console.log('[RPG Companion] → Matched TIME');
|
|
||||||
const timeStr = line.replace('Time:', '').replace('🕒:', '').trim();
|
|
||||||
data.time = timeStr;
|
|
||||||
// Parse "HH:MM → HH:MM" format
|
|
||||||
const timeParts = timeStr.split('→').map(t => t.trim());
|
|
||||||
data.timeStart = timeParts[0] || '';
|
|
||||||
data.timeEnd = timeParts[1] || '';
|
|
||||||
} else if (line.startsWith('Location:') || line.includes('🗺️:')) {
|
|
||||||
// console.log('[RPG Companion] → Matched LOCATION');
|
|
||||||
data.location = line.replace('Location:', '').replace('🗺️:', '').trim();
|
|
||||||
} else if (line.startsWith('Weather:')) {
|
} else if (line.startsWith('Weather:')) {
|
||||||
// New text format: Weather: [Emoji], [Forecast]
|
if (!parsedFields.weather) {
|
||||||
const weatherStr = line.replace('Weather:', '').trim();
|
// New text format: Weather: [Emoji], [Forecast]
|
||||||
const weatherParts = weatherStr.split(',').map(p => p.trim());
|
const weatherStr = line.replace('Weather:', '').trim();
|
||||||
data.weatherEmoji = weatherParts[0] || '';
|
const weatherParts = weatherStr.split(',').map(p => p.trim());
|
||||||
data.weatherForecast = weatherParts[1] || '';
|
data.weatherEmoji = weatherParts[0] || '';
|
||||||
|
data.weatherForecast = weatherParts[1] || '';
|
||||||
|
parsedFields.weather = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Check if it's a weather line
|
// Check if it's a legacy weather line (emoji format)
|
||||||
// Since \p{Emoji} doesn't work reliably, use a simpler approach
|
// Only parse if we haven't already found weather in text format
|
||||||
const hasColon = line.includes(':');
|
if (!parsedFields.weather) {
|
||||||
const notInfoBox = !line.includes('Info Box');
|
// Since \p{Emoji} doesn't work reliably, use a simpler approach
|
||||||
const notDivider = !line.includes('---');
|
const hasColon = line.includes(':');
|
||||||
const notCodeFence = !line.trim().startsWith('```');
|
const notInfoBox = !line.includes('Info Box');
|
||||||
|
const notDivider = !line.includes('---');
|
||||||
|
const notCodeFence = !line.trim().startsWith('```');
|
||||||
|
|
||||||
// console.log('[RPG Companion] → Checking weather conditions:', {
|
// console.log('[RPG Companion] → Checking weather conditions:', {
|
||||||
// line: line,
|
// line: line,
|
||||||
// hasColon: hasColon,
|
// hasColon: hasColon,
|
||||||
// notInfoBox: notInfoBox,
|
// notInfoBox: notInfoBox,
|
||||||
// notDivider: notDivider
|
// notDivider: notDivider
|
||||||
// });
|
// });
|
||||||
|
|
||||||
if (hasColon && notInfoBox && notDivider && notCodeFence && line.trim().length > 0) {
|
if (hasColon && notInfoBox && notDivider && notCodeFence && line.trim().length > 0) {
|
||||||
// Match format: [Weather Emoji]: [Forecast]
|
// Match format: [Weather Emoji]: [Forecast]
|
||||||
// Capture everything before colon as emoji, everything after as forecast
|
// Capture everything before colon as emoji, everything after as forecast
|
||||||
// console.log('[RPG Companion] → Testing WEATHER match for:', line);
|
// console.log('[RPG Companion] → Testing WEATHER match for:', line);
|
||||||
const weatherMatch = line.match(/^\s*([^:]+):\s*(.+)$/);
|
const weatherMatch = line.match(/^\s*([^:]+):\s*(.+)$/);
|
||||||
if (weatherMatch) {
|
if (weatherMatch) {
|
||||||
const potentialEmoji = weatherMatch[1].trim();
|
const potentialEmoji = weatherMatch[1].trim();
|
||||||
const forecast = weatherMatch[2].trim();
|
const forecast = weatherMatch[2].trim();
|
||||||
|
|
||||||
// If the first part is short (likely emoji), treat as weather
|
// If the first part is short (likely emoji), treat as weather
|
||||||
if (potentialEmoji.length <= 5) {
|
if (potentialEmoji.length <= 5) {
|
||||||
data.weatherEmoji = potentialEmoji;
|
data.weatherEmoji = potentialEmoji;
|
||||||
data.weatherForecast = forecast;
|
data.weatherForecast = forecast;
|
||||||
// console.log('[RPG Companion] ✓ Weather parsed:', data.weatherEmoji, data.weatherForecast);
|
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 {
|
} else {
|
||||||
// console.log('[RPG Companion] ✗ First part too long for emoji:', potentialEmoji);
|
// console.log('[RPG Companion] ✗ Weather regex did not match');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// console.log('[RPG Companion] ✗ Weather regex did not match');
|
// console.log('[RPG Companion] → No match for this line');
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// console.log('[RPG Companion] → No match for this line');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,14 +221,15 @@ export function renderInfoBox() {
|
|||||||
let html = '<div class="rpg-dashboard rpg-dashboard-row-1">';
|
let html = '<div class="rpg-dashboard rpg-dashboard-row-1">';
|
||||||
|
|
||||||
// Calendar widget - always show (editable even if empty)
|
// Calendar widget - always show (editable even if empty)
|
||||||
|
// Display abbreviated version but allow editing full value
|
||||||
const monthShort = data.month ? data.month.substring(0, 3).toUpperCase() : 'MON';
|
const monthShort = data.month ? data.month.substring(0, 3).toUpperCase() : 'MON';
|
||||||
const weekdayShort = data.weekday ? data.weekday.substring(0, 3).toUpperCase() : 'DAY';
|
const weekdayShort = data.weekday ? data.weekday.substring(0, 3).toUpperCase() : 'DAY';
|
||||||
const yearDisplay = data.year || 'YEAR';
|
const yearDisplay = data.year || 'YEAR';
|
||||||
html += `
|
html += `
|
||||||
<div class="rpg-dashboard-widget rpg-calendar-widget">
|
<div class="rpg-dashboard-widget rpg-calendar-widget">
|
||||||
<div class="rpg-calendar-top rpg-editable" contenteditable="true" data-field="month" title="Click to edit">${monthShort}</div>
|
<div class="rpg-calendar-top rpg-editable" contenteditable="true" data-field="month" data-full-value="${data.month || ''}" title="Click to edit">${monthShort}</div>
|
||||||
<div class="rpg-calendar-day rpg-editable" contenteditable="true" data-field="weekday" title="Click to edit">${weekdayShort}</div>
|
<div class="rpg-calendar-day rpg-editable" contenteditable="true" data-field="weekday" data-full-value="${data.weekday || ''}" title="Click to edit">${weekdayShort}</div>
|
||||||
<div class="rpg-calendar-year rpg-editable" contenteditable="true" data-field="year" title="Click to edit">${yearDisplay}</div>
|
<div class="rpg-calendar-year rpg-editable" contenteditable="true" data-field="year" data-full-value="${data.year || ''}" title="Click to edit">${yearDisplay}</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -196,7 +261,8 @@ export function renderInfoBox() {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
// Time widget - always show (editable even if empty)
|
// Time widget - always show (editable even if empty)
|
||||||
const timeDisplay = data.timeStart || '12:00';
|
// Display the end time (second time in range) if available, otherwise start time
|
||||||
|
const timeDisplay = data.timeEnd || data.timeStart || '12:00';
|
||||||
// Parse time for clock hands
|
// Parse time for clock hands
|
||||||
const timeMatch = timeDisplay.match(/(\d+):(\d+)/);
|
const timeMatch = timeDisplay.match(/(\d+):(\d+)/);
|
||||||
let hourAngle = 0;
|
let hourAngle = 0;
|
||||||
@@ -239,11 +305,32 @@ export function renderInfoBox() {
|
|||||||
|
|
||||||
// Add event handlers for editable Info Box fields
|
// Add event handlers for editable Info Box fields
|
||||||
$infoBoxContainer.find('.rpg-editable').on('blur', function() {
|
$infoBoxContainer.find('.rpg-editable').on('blur', function() {
|
||||||
const field = $(this).data('field');
|
const $this = $(this);
|
||||||
const value = $(this).text().trim();
|
const field = $this.data('field');
|
||||||
|
const value = $this.text().trim();
|
||||||
|
|
||||||
|
// For date fields, update the data-full-value immediately
|
||||||
|
if (field === 'month' || field === 'weekday' || field === 'year') {
|
||||||
|
$this.data('full-value', value);
|
||||||
|
// Update the display to show abbreviated version
|
||||||
|
if (field === 'month' || field === 'weekday') {
|
||||||
|
$this.text(value.substring(0, 3).toUpperCase());
|
||||||
|
} else {
|
||||||
|
$this.text(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updateInfoBoxField(field, value);
|
updateInfoBoxField(field, value);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// For date fields, show full value on focus
|
||||||
|
$infoBoxContainer.find('[data-field="month"], [data-field="weekday"], [data-field="year"]').on('focus', function() {
|
||||||
|
const fullValue = $(this).data('full-value');
|
||||||
|
if (fullValue) {
|
||||||
|
$(this).text(fullValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Remove updating class after animation
|
// Remove updating class after animation
|
||||||
if (extensionSettings.enableAnimations) {
|
if (extensionSettings.enableAnimations) {
|
||||||
setTimeout(() => $infoBoxContainer.removeClass('rpg-content-updating'), 500);
|
setTimeout(() => $infoBoxContainer.removeClass('rpg-content-updating'), 500);
|
||||||
@@ -512,5 +599,10 @@ export function updateInfoBoxField(field, value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
saveChatData();
|
saveChatData();
|
||||||
renderInfoBox();
|
|
||||||
|
// Only re-render if NOT editing date fields
|
||||||
|
// Date fields will update on next tracker generation to avoid losing user input
|
||||||
|
if (field !== 'month' && field !== 'weekday' && field !== 'year') {
|
||||||
|
renderInfoBox();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,9 +34,6 @@ export function closeMobilePanelWithAnimation() {
|
|||||||
$panel.removeClass('rpg-mobile-open').addClass('rpg-mobile-closing');
|
$panel.removeClass('rpg-mobile-open').addClass('rpg-mobile-closing');
|
||||||
$mobileToggle.removeClass('active');
|
$mobileToggle.removeClass('active');
|
||||||
|
|
||||||
// Trigger event for other components (like refresh button)
|
|
||||||
$(document).trigger('rpg-panel-toggled', { isOpen: false });
|
|
||||||
|
|
||||||
// Wait for animation to complete before hiding
|
// Wait for animation to complete before hiding
|
||||||
$panel.one('animationend', function() {
|
$panel.one('animationend', function() {
|
||||||
$panel.removeClass('rpg-mobile-closing');
|
$panel.removeClass('rpg-mobile-closing');
|
||||||
@@ -130,9 +127,6 @@ export function setupCollapseToggle() {
|
|||||||
const $overlay = $('<div class="rpg-mobile-overlay"></div>');
|
const $overlay = $('<div class="rpg-mobile-overlay"></div>');
|
||||||
$('body').append($overlay);
|
$('body').append($overlay);
|
||||||
|
|
||||||
// Trigger event for other components (like refresh button)
|
|
||||||
$(document).trigger('rpg-panel-toggled', { isOpen: true });
|
|
||||||
|
|
||||||
// Debug: Check state after animation should complete
|
// Debug: Check state after animation should complete
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log('[RPG Mobile] 500ms after opening:', {
|
console.log('[RPG Mobile] 500ms after opening:', {
|
||||||
@@ -273,13 +267,10 @@ export function applyPanelPosition() {
|
|||||||
*/
|
*/
|
||||||
export function updateGenerationModeUI() {
|
export function updateGenerationModeUI() {
|
||||||
if (extensionSettings.generationMode === 'together') {
|
if (extensionSettings.generationMode === 'together') {
|
||||||
// In "together" mode, hide both update buttons
|
// In "together" mode, manual update button is hidden
|
||||||
$('#rpg-manual-update').hide();
|
$('#rpg-manual-update').hide();
|
||||||
$('#rpg-manual-update-mobile').hide();
|
|
||||||
} else {
|
} else {
|
||||||
// In "separate" mode, show both buttons
|
// In "separate" mode, manual update button is visible
|
||||||
// (CSS media queries control which one is visible based on viewport)
|
|
||||||
$('#rpg-manual-update').show();
|
$('#rpg-manual-update').show();
|
||||||
$('#rpg-manual-update-mobile').show();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-27
@@ -278,9 +278,6 @@ export function setupMobileToggle() {
|
|||||||
$('body').append($overlay);
|
$('body').append($overlay);
|
||||||
$mobileToggle.addClass('active');
|
$mobileToggle.addClass('active');
|
||||||
|
|
||||||
// Trigger event for other components (like refresh button)
|
|
||||||
$(document).trigger('rpg-panel-toggled', { isOpen: true });
|
|
||||||
|
|
||||||
// Close when clicking overlay
|
// Close when clicking overlay
|
||||||
$overlay.on('click', function() {
|
$overlay.on('click', function() {
|
||||||
closeMobilePanelWithAnimation();
|
closeMobilePanelWithAnimation();
|
||||||
@@ -313,9 +310,6 @@ export function setupMobileToggle() {
|
|||||||
$('body').append($overlay);
|
$('body').append($overlay);
|
||||||
$mobileToggle.addClass('active');
|
$mobileToggle.addClass('active');
|
||||||
|
|
||||||
// Trigger event for other components (like refresh button)
|
|
||||||
$(document).trigger('rpg-panel-toggled', { isOpen: true });
|
|
||||||
|
|
||||||
$overlay.on('click', function() {
|
$overlay.on('click', function() {
|
||||||
console.log('[RPG Mobile] Overlay clicked - closing panel');
|
console.log('[RPG Mobile] Overlay clicked - closing panel');
|
||||||
closeMobilePanelWithAnimation();
|
closeMobilePanelWithAnimation();
|
||||||
@@ -440,41 +434,32 @@ export function setupMobileToggle() {
|
|||||||
* Constrains the mobile FAB button to viewport bounds with top-bar awareness.
|
* Constrains the mobile FAB button to viewport bounds with top-bar awareness.
|
||||||
* Only runs when button is in user-controlled state (mobileFabPosition exists).
|
* Only runs when button is in user-controlled state (mobileFabPosition exists).
|
||||||
* Ensures button never goes behind the top bar or outside viewport edges.
|
* Ensures button never goes behind the top bar or outside viewport edges.
|
||||||
* @param {jQuery} $button - Optional button element (defaults to mobile toggle)
|
|
||||||
*/
|
*/
|
||||||
export function constrainFabToViewport($button = null) {
|
export function constrainFabToViewport() {
|
||||||
// Default to mobile toggle if no button specified
|
|
||||||
if (!$button) {
|
|
||||||
$button = $('#rpg-mobile-toggle');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($button.length === 0) return;
|
|
||||||
|
|
||||||
// Determine which position setting to check based on button ID
|
|
||||||
const isRefreshButton = $button.attr('id') === 'rpg-manual-update-mobile';
|
|
||||||
const positionSetting = isRefreshButton ? 'mobileRefreshPosition' : 'mobileFabPosition';
|
|
||||||
|
|
||||||
// Only constrain if user has set a custom position
|
// Only constrain if user has set a custom position
|
||||||
if (!extensionSettings[positionSetting]) {
|
if (!extensionSettings.mobileFabPosition) {
|
||||||
console.log('[RPG Mobile] Skipping viewport constraint - using CSS defaults');
|
console.log('[RPG Mobile] Skipping viewport constraint - using CSS defaults');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const $mobileToggle = $('#rpg-mobile-toggle');
|
||||||
|
if ($mobileToggle.length === 0) return;
|
||||||
|
|
||||||
// Skip if button is not visible
|
// Skip if button is not visible
|
||||||
if (!$button.is(':visible')) {
|
if (!$mobileToggle.is(':visible')) {
|
||||||
console.log('[RPG Mobile] Skipping viewport constraint - button not visible');
|
console.log('[RPG Mobile] Skipping viewport constraint - button not visible');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current position
|
// Get current position
|
||||||
const offset = $button.offset();
|
const offset = $mobileToggle.offset();
|
||||||
if (!offset) return;
|
if (!offset) return;
|
||||||
|
|
||||||
let currentX = offset.left;
|
let currentX = offset.left;
|
||||||
let currentY = offset.top;
|
let currentY = offset.top;
|
||||||
|
|
||||||
const buttonWidth = $button.outerWidth();
|
const buttonWidth = $mobileToggle.outerWidth();
|
||||||
const buttonHeight = $button.outerHeight();
|
const buttonHeight = $mobileToggle.outerHeight();
|
||||||
|
|
||||||
// Get top bar height from CSS variable (fallback to 50px if not set)
|
// Get top bar height from CSS variable (fallback to 50px if not set)
|
||||||
const topBarHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--topBarBlockSize')) || 50;
|
const topBarHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--topBarBlockSize')) || 50;
|
||||||
@@ -500,15 +485,15 @@ export function constrainFabToViewport($button = null) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Apply new position
|
// Apply new position
|
||||||
$button.css({
|
$mobileToggle.css({
|
||||||
left: newX + 'px',
|
left: newX + 'px',
|
||||||
top: newY + 'px',
|
top: newY + 'px',
|
||||||
right: 'auto',
|
right: 'auto',
|
||||||
bottom: 'auto'
|
bottom: 'auto'
|
||||||
});
|
});
|
||||||
|
|
||||||
// Save corrected position to appropriate setting
|
// Save corrected position
|
||||||
extensionSettings[positionSetting] = {
|
extensionSettings.mobileFabPosition = {
|
||||||
left: newX + 'px',
|
left: newX + 'px',
|
||||||
top: newY + 'px'
|
top: newY + 'px'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -576,8 +576,8 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.rpg-user-portrait {
|
.rpg-user-portrait {
|
||||||
width: clamp(24px, 4vh, 32px);
|
width: clamp(1.7vw, 1.8vw, 1.9vw);
|
||||||
height: clamp(24px, 4vh, 32px);
|
height: clamp(1.7vw, 1.8vw, 1.9vw);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
border: 2px solid var(--rpg-highlight);
|
border: 2px solid var(--rpg-highlight);
|
||||||
box-shadow: 0 0 8px var(--rpg-highlight);
|
box-shadow: 0 0 8px var(--rpg-highlight);
|
||||||
@@ -726,7 +726,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
/* User name and level - inline with portrait */
|
/* User name and level - inline with portrait */
|
||||||
.rpg-user-name {
|
.rpg-user-name {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 1em;
|
font-size: 0.7vw;
|
||||||
color: var(--rpg-text-color);
|
color: var(--rpg-text-color);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -734,17 +734,17 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.rpg-level-label {
|
.rpg-level-label {
|
||||||
font-size: 1em;
|
font-size: 0.7vw;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--rpg-text-color);
|
color: var(--rpg-text-color);
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rpg-level-value {
|
.rpg-level-value {
|
||||||
font-size: 1em;
|
font-size: 0.7vw;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--rpg-highlight-color);
|
color: var(--rpg-highlight-color);
|
||||||
padding: 0 0.375em;
|
padding: clamp(1px, 0.2vh, 2px) 0.375em;
|
||||||
background: var(--rpg-accent-color);
|
background: var(--rpg-accent-color);
|
||||||
border-radius: clamp(2px, 0.3vh, 3px);
|
border-radius: clamp(2px, 0.3vh, 3px);
|
||||||
border: 1px solid var(--rpg-highlight-color);
|
border: 1px solid var(--rpg-highlight-color);
|
||||||
@@ -2698,7 +2698,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================
|
/* ============================================
|
||||||
MANUAL UPDATE BUTTON (Desktop)
|
MANUAL UPDATE BUTTON
|
||||||
============================================ */
|
============================================ */
|
||||||
.rpg-manual-update-btn {
|
.rpg-manual-update-btn {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -2735,64 +2735,6 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================
|
|
||||||
MOBILE REFRESH BUTTON (FAB - Same pattern as toggle)
|
|
||||||
============================================ */
|
|
||||||
.rpg-mobile-refresh {
|
|
||||||
display: none;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
position: fixed;
|
|
||||||
/* Position set by JavaScript based on saved settings */
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: var(--SmartThemeBlurTintColor);
|
|
||||||
border: 2px solid var(--SmartThemeBorderColor);
|
|
||||||
color: var(--rpg-text, #ecf0f1);
|
|
||||||
font-size: 1.85vw;
|
|
||||||
cursor: grab;
|
|
||||||
z-index: 1001; /* Above panel (1000) but below mobile toggle (10002) */
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
||||||
transition: opacity 0.3s ease, transform 0.2s ease, top 0.3s ease, left 0.3s ease, right 0.3s ease, bottom 0.3s ease;
|
|
||||||
user-select: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
will-change: top, left;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Disable transitions while actively dragging */
|
|
||||||
.rpg-mobile-refresh.dragging {
|
|
||||||
transition: none;
|
|
||||||
cursor: grabbing;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-mobile-refresh:hover {
|
|
||||||
transform: scale(1.1);
|
|
||||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-mobile-refresh:active {
|
|
||||||
transform: scale(0.95);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Spinning animation when refreshing */
|
|
||||||
.rpg-mobile-refresh.spinning i {
|
|
||||||
animation: rpg-spin 0.8s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-mobile-refresh i {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes rpg-spin {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================
|
/* ============================================
|
||||||
SETTINGS BUTTON
|
SETTINGS BUTTON
|
||||||
============================================ */
|
============================================ */
|
||||||
@@ -3378,31 +3320,6 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
|
|
||||||
/* Mobile-specific panel behavior - matches SillyTavern's 1000px breakpoint */
|
/* Mobile-specific panel behavior - matches SillyTavern's 1000px breakpoint */
|
||||||
/* CACHE BUST v2025-01-16 */
|
/* CACHE BUST v2025-01-16 */
|
||||||
/* Mobile refresh button visibility - opposite of toggle */
|
|
||||||
@media (max-width: 1000px) {
|
|
||||||
/* Show mobile refresh button */
|
|
||||||
.rpg-mobile-refresh {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hide desktop refresh button */
|
|
||||||
.rpg-manual-update-btn {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Show refresh button when panel is open (opposite of toggle) */
|
|
||||||
body:has(.rpg-panel.rpg-mobile-open) .rpg-mobile-refresh {
|
|
||||||
opacity: 1;
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hide refresh button when panel is closed */
|
|
||||||
.rpg-mobile-refresh {
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1000px) {
|
@media (max-width: 1000px) {
|
||||||
/* ========================================
|
/* ========================================
|
||||||
MOBILE PANEL FOUNDATION
|
MOBILE PANEL FOUNDATION
|
||||||
@@ -3727,9 +3644,6 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
|
|
||||||
.rpg-calendar-day {
|
.rpg-calendar-day {
|
||||||
font-size: clamp(11px, 2.9vw, 14px) !important;
|
font-size: clamp(11px, 2.9vw, 14px) !important;
|
||||||
min-height: 3em !important; /* Ensure enough height for content to center */
|
|
||||||
padding: 0.75em 0.5em !important; /* More vertical padding on mobile */
|
|
||||||
line-height: 1.2 !important; /* Tighter line height */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.rpg-calendar-year {
|
.rpg-calendar-year {
|
||||||
@@ -3867,20 +3781,14 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
grid-row: 3;
|
grid-row: 3;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 3px !important; /* Reduced from 6px for more compact display */
|
gap: 6px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
padding: 4px 0.375em !important; /* Reduced vertical padding */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make mood text readable on mobile */
|
/* Make mood text readable on mobile */
|
||||||
.rpg-mood-conditions {
|
.rpg-mood-conditions {
|
||||||
font-size: clamp(11px, 2.8vw, 14px);
|
font-size: clamp(11px, 2.8vw, 14px);
|
||||||
line-height: 1.2 !important; /* Tighter line height */
|
line-height: 1.3;
|
||||||
}
|
|
||||||
|
|
||||||
/* Smaller emoji on mobile */
|
|
||||||
.rpg-mood-emoji {
|
|
||||||
font-size: clamp(14px, 3.5vw, 18px) !important; /* Slightly smaller */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Attributes - right side, rows 4-6 aligned with mood */
|
/* Attributes - right side, rows 4-6 aligned with mood */
|
||||||
@@ -4080,11 +3988,6 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
font-size: clamp(20px, 5.1vw, 26px) !important;
|
font-size: clamp(20px, 5.1vw, 26px) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Larger mobile refresh icon (same as toggle) */
|
|
||||||
.rpg-mobile-refresh {
|
|
||||||
font-size: clamp(20px, 5.1vw, 26px) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ========================================
|
/* ========================================
|
||||||
MOBILE SETTINGS POPUP
|
MOBILE SETTINGS POPUP
|
||||||
======================================== */
|
======================================== */
|
||||||
@@ -4188,13 +4091,6 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
min-height: 2.75rem;
|
min-height: 2.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Exception: Level value should stay compact */
|
|
||||||
.rpg-level-value.rpg-editable {
|
|
||||||
padding: 0 0.375em;
|
|
||||||
min-height: auto;
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Larger close buttons */
|
/* Larger close buttons */
|
||||||
.rpg-thought-close {
|
.rpg-thought-close {
|
||||||
min-width: 2.75rem;
|
min-width: 2.75rem;
|
||||||
|
|||||||
Reference in New Issue
Block a user