Merge: Combined code block parsing + flexible pattern matching + debug logging
- Combined block parsing: Detects and splits multi-section code blocks - Flexible patterns: Supports variations like 'User Stats', 'Player Stats', etc. - Enhanced debugging: Debug logs with pattern match details - Fallback matching: Uses keyword detection when headers are malformed - Duplicate prevention: Checks prevent overwriting already-found sections
This commit is contained in:
@@ -98,12 +98,17 @@ import {
|
|||||||
setupMobileTabs,
|
setupMobileTabs,
|
||||||
removeMobileTabs,
|
removeMobileTabs,
|
||||||
setupMobileKeyboardHandling,
|
setupMobileKeyboardHandling,
|
||||||
setupContentEditableScrolling
|
setupContentEditableScrolling,
|
||||||
|
setupRefreshButtonDrag,
|
||||||
|
setupDebugButtonDrag
|
||||||
} from './src/systems/ui/mobile.js';
|
} from './src/systems/ui/mobile.js';
|
||||||
import {
|
import {
|
||||||
setupDesktopTabs,
|
setupDesktopTabs,
|
||||||
removeDesktopTabs
|
removeDesktopTabs
|
||||||
} from './src/systems/ui/desktop.js';
|
} from './src/systems/ui/desktop.js';
|
||||||
|
import {
|
||||||
|
updateDebugUIVisibility
|
||||||
|
} from './src/systems/ui/debug.js';
|
||||||
|
|
||||||
// Feature modules
|
// Feature modules
|
||||||
import { setupPlotButtons, sendPlotProgression } from './src/systems/features/plotProgression.js';
|
import { setupPlotButtons, sendPlotProgression } from './src/systems/features/plotProgression.js';
|
||||||
@@ -209,6 +214,22 @@ async function initUI() {
|
|||||||
`;
|
`;
|
||||||
$('body').append(mobileToggleHtml);
|
$('body').append(mobileToggleHtml);
|
||||||
|
|
||||||
|
// Add mobile refresh button (same pattern as toggle button)
|
||||||
|
const mobileRefreshHtml = `
|
||||||
|
<button id="rpg-manual-update-mobile" class="rpg-mobile-refresh" title="Refresh RPG Info">
|
||||||
|
<i class="fa-solid fa-sync"></i>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
$('body').append(mobileRefreshHtml);
|
||||||
|
|
||||||
|
// Add debug toggle FAB button (same pattern as other mobile FABs)
|
||||||
|
const debugToggleHtml = `
|
||||||
|
<button id="rpg-debug-toggle" class="rpg-debug-toggle" title="Toggle Debug Logs">
|
||||||
|
<i class="fa-solid fa-bug"></i>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
$('body').append(debugToggleHtml);
|
||||||
|
|
||||||
// Cache UI elements using state setters
|
// Cache UI elements using state setters
|
||||||
setPanelContainer($('#rpg-companion-panel'));
|
setPanelContainer($('#rpg-companion-panel'));
|
||||||
setUserStatsContainer($('#rpg-user-stats'));
|
setUserStatsContainer($('#rpg-user-stats'));
|
||||||
@@ -263,6 +284,10 @@ async function initUI() {
|
|||||||
extensionSettings.showCharacterThoughts = $(this).prop('checked');
|
extensionSettings.showCharacterThoughts = $(this).prop('checked');
|
||||||
saveSettings();
|
saveSettings();
|
||||||
updateSectionVisibility();
|
updateSectionVisibility();
|
||||||
|
// Refresh the content when toggling on/off
|
||||||
|
if (extensionSettings.showCharacterThoughts) {
|
||||||
|
renderThoughts();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#rpg-toggle-inventory').on('change', function() {
|
$('#rpg-toggle-inventory').on('change', function() {
|
||||||
@@ -291,18 +316,77 @@ async function initUI() {
|
|||||||
togglePlotButtons();
|
togglePlotButtons();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#rpg-toggle-debug-mode').on('change', function() {
|
||||||
|
extensionSettings.debugMode = $(this).prop('checked');
|
||||||
|
saveSettings();
|
||||||
|
updateDebugUIVisibility();
|
||||||
|
});
|
||||||
|
|
||||||
$('#rpg-toggle-animations').on('change', function() {
|
$('#rpg-toggle-animations').on('change', function() {
|
||||||
extensionSettings.enableAnimations = $(this).prop('checked');
|
extensionSettings.enableAnimations = $(this).prop('checked');
|
||||||
saveSettings();
|
saveSettings();
|
||||||
toggleAnimations();
|
toggleAnimations();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#rpg-manual-update').on('click', async function() {
|
// Bind to both desktop and mobile refresh buttons
|
||||||
|
$('#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
|
||||||
|
$('#rpg-reset-fab-positions').on('click', function() {
|
||||||
|
console.log('[RPG Companion] Resetting FAB positions to defaults');
|
||||||
|
|
||||||
|
// Reset to defaults (top-left stacked)
|
||||||
|
extensionSettings.mobileFabPosition = {
|
||||||
|
top: 'calc(var(--topBarBlockSize) + 20px)',
|
||||||
|
left: '12px'
|
||||||
|
};
|
||||||
|
extensionSettings.mobileRefreshPosition = {
|
||||||
|
top: 'calc(var(--topBarBlockSize) + 80px)',
|
||||||
|
left: '12px'
|
||||||
|
};
|
||||||
|
extensionSettings.debugFabPosition = {
|
||||||
|
top: 'calc(var(--topBarBlockSize) + 140px)',
|
||||||
|
left: '12px'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Save settings
|
||||||
|
saveSettings();
|
||||||
|
|
||||||
|
// Apply positions immediately to visible buttons
|
||||||
|
$('#rpg-mobile-toggle').css(extensionSettings.mobileFabPosition);
|
||||||
|
$('#rpg-manual-update-mobile').css(extensionSettings.mobileRefreshPosition);
|
||||||
|
$('#rpg-debug-toggle').css(extensionSettings.debugFabPosition);
|
||||||
|
|
||||||
|
// Show success feedback
|
||||||
|
toastr.success('Button positions reset to defaults', 'RPG Companion');
|
||||||
|
console.log('[RPG Companion] FAB positions reset successfully');
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#rpg-stat-bar-color-low').on('change', function() {
|
$('#rpg-stat-bar-color-low').on('change', function() {
|
||||||
@@ -380,6 +464,7 @@ async function initUI() {
|
|||||||
$('#rpg-toggle-thoughts-in-chat').prop('checked', extensionSettings.showThoughtsInChat);
|
$('#rpg-toggle-thoughts-in-chat').prop('checked', extensionSettings.showThoughtsInChat);
|
||||||
$('#rpg-toggle-html-prompt').prop('checked', extensionSettings.enableHtmlPrompt);
|
$('#rpg-toggle-html-prompt').prop('checked', extensionSettings.enableHtmlPrompt);
|
||||||
$('#rpg-toggle-plot-buttons').prop('checked', extensionSettings.enablePlotButtons);
|
$('#rpg-toggle-plot-buttons').prop('checked', extensionSettings.enablePlotButtons);
|
||||||
|
$('#rpg-toggle-debug-mode').prop('checked', extensionSettings.debugMode);
|
||||||
$('#rpg-toggle-animations').prop('checked', extensionSettings.enableAnimations);
|
$('#rpg-toggle-animations').prop('checked', extensionSettings.enableAnimations);
|
||||||
$('#rpg-stat-bar-color-low').val(extensionSettings.statBarColorLow);
|
$('#rpg-stat-bar-color-low').val(extensionSettings.statBarColorLow);
|
||||||
$('#rpg-stat-bar-color-high').val(extensionSettings.statBarColorHigh);
|
$('#rpg-stat-bar-color-high').val(extensionSettings.statBarColorHigh);
|
||||||
@@ -422,7 +507,12 @@ async function initUI() {
|
|||||||
setupPlotButtons(sendPlotProgression);
|
setupPlotButtons(sendPlotProgression);
|
||||||
setupMobileKeyboardHandling();
|
setupMobileKeyboardHandling();
|
||||||
setupContentEditableScrolling();
|
setupContentEditableScrolling();
|
||||||
|
setupRefreshButtonDrag();
|
||||||
|
setupDebugButtonDrag();
|
||||||
initInventoryEventListeners();
|
initInventoryEventListeners();
|
||||||
|
|
||||||
|
// Initialize debug UI if debug mode is enabled
|
||||||
|
updateDebugUIVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+13
-4
@@ -46,9 +46,17 @@ export const defaultSettings = {
|
|||||||
statBarColorHigh: '#33cc66', // Color for high stat values (green)
|
statBarColorHigh: '#33cc66', // Color for high stat values (green)
|
||||||
enableAnimations: true, // Enable smooth animations for stats and content updates
|
enableAnimations: true, // Enable smooth animations for stats and content updates
|
||||||
mobileFabPosition: {
|
mobileFabPosition: {
|
||||||
top: 'calc(var(--topBarBlockSize) + 60px)',
|
top: 'calc(var(--topBarBlockSize) + 20px)',
|
||||||
right: '12px'
|
left: '12px'
|
||||||
}, // Saved position for mobile FAB button
|
}, // Saved position for mobile FAB button (top-left, stacked vertically)
|
||||||
|
mobileRefreshPosition: {
|
||||||
|
top: 'calc(var(--topBarBlockSize) + 80px)',
|
||||||
|
left: '12px'
|
||||||
|
}, // Saved position for mobile refresh button (below toggle button)
|
||||||
|
debugFabPosition: {
|
||||||
|
top: 'calc(var(--topBarBlockSize) + 140px)',
|
||||||
|
left: '12px'
|
||||||
|
}, // Saved position for debug FAB button (below refresh button)
|
||||||
userStats: {
|
userStats: {
|
||||||
health: 100,
|
health: 100,
|
||||||
satiety: 100,
|
satiety: 100,
|
||||||
@@ -74,5 +82,6 @@ export const defaultSettings = {
|
|||||||
cha: 10
|
cha: 10
|
||||||
},
|
},
|
||||||
lastDiceRoll: null, // Store last dice roll result
|
lastDiceRoll: null, // Store last dice roll result
|
||||||
collapsedInventoryLocations: [] // Array of collapsed storage location names
|
collapsedInventoryLocations: [], // Array of collapsed storage location names
|
||||||
|
debugMode: false // Enable debug logging visible in UI (for mobile debugging)
|
||||||
};
|
};
|
||||||
|
|||||||
+42
-4
@@ -34,9 +34,17 @@ export let extensionSettings = {
|
|||||||
statBarColorHigh: '#33cc66', // Color for high stat values (green)
|
statBarColorHigh: '#33cc66', // Color for high stat values (green)
|
||||||
enableAnimations: true, // Enable smooth animations for stats and content updates
|
enableAnimations: true, // Enable smooth animations for stats and content updates
|
||||||
mobileFabPosition: {
|
mobileFabPosition: {
|
||||||
top: 'calc(var(--topBarBlockSize) + 60px)',
|
top: 'calc(var(--topBarBlockSize) + 20px)',
|
||||||
right: '12px'
|
left: '12px'
|
||||||
}, // Saved position for mobile FAB button
|
}, // Saved position for mobile FAB button (top-left, stacked vertically)
|
||||||
|
mobileRefreshPosition: {
|
||||||
|
top: 'calc(var(--topBarBlockSize) + 80px)',
|
||||||
|
left: '12px'
|
||||||
|
}, // Saved position for mobile refresh button (below toggle button)
|
||||||
|
debugFabPosition: {
|
||||||
|
top: 'calc(var(--topBarBlockSize) + 140px)',
|
||||||
|
left: '12px'
|
||||||
|
}, // Saved position for debug FAB button (below refresh button)
|
||||||
userStats: {
|
userStats: {
|
||||||
health: 100,
|
health: 100,
|
||||||
satiety: 100,
|
satiety: 100,
|
||||||
@@ -68,7 +76,8 @@ export let extensionSettings = {
|
|||||||
onPerson: 'list', // 'list' or 'grid' view mode for On Person section
|
onPerson: 'list', // 'list' or 'grid' view mode for On Person section
|
||||||
stored: 'list', // 'list' or 'grid' view mode for Stored section
|
stored: 'list', // 'list' or 'grid' view mode for Stored section
|
||||||
assets: 'list' // 'list' or 'grid' view mode for Assets section
|
assets: 'list' // 'list' or 'grid' view mode for Assets section
|
||||||
}
|
},
|
||||||
|
debugMode: false // Enable debug logging visible in UI (for mobile debugging)
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -119,6 +128,12 @@ export const FEATURE_FLAGS = {
|
|||||||
useNewInventory: true // Enable v2 inventory system with categorized storage
|
useNewInventory: true // Enable v2 inventory system with categorized storage
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debug logs storage for mobile-friendly debugging
|
||||||
|
* Stores parser logs that can be viewed in UI
|
||||||
|
*/
|
||||||
|
export let debugLogs = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fallback avatar image (base64-encoded SVG with "?" icon)
|
* Fallback avatar image (base64-encoded SVG with "?" icon)
|
||||||
* Using base64 to avoid quote-encoding issues in HTML attributes
|
* Using base64 to avoid quote-encoding issues in HTML attributes
|
||||||
@@ -200,3 +215,26 @@ export function setThoughtsContainer($element) {
|
|||||||
export function setInventoryContainer($element) {
|
export function setInventoryContainer($element) {
|
||||||
$inventoryContainer = $element;
|
$inventoryContainer = $element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function addDebugLog(message, data = null) {
|
||||||
|
const timestamp = new Date().toISOString().split('T')[1].split('.')[0]; // HH:MM:SS
|
||||||
|
const logEntry = {
|
||||||
|
timestamp,
|
||||||
|
message,
|
||||||
|
data: data ? JSON.stringify(data, null, 2) : null
|
||||||
|
};
|
||||||
|
debugLogs.push(logEntry);
|
||||||
|
|
||||||
|
// Keep only last 100 entries to avoid memory issues
|
||||||
|
if (debugLogs.length > 100) {
|
||||||
|
debugLogs.shift();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearDebugLogs() {
|
||||||
|
debugLogs = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDebugLogs() {
|
||||||
|
return debugLogs;
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,10 +3,20 @@
|
|||||||
* Handles parsing of AI responses to extract tracker data
|
* Handles parsing of AI responses to extract tracker data
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { extensionSettings, FEATURE_FLAGS } from '../../core/state.js';
|
import { extensionSettings, FEATURE_FLAGS, addDebugLog } from '../../core/state.js';
|
||||||
import { saveSettings } from '../../core/persistence.js';
|
import { saveSettings } from '../../core/persistence.js';
|
||||||
import { extractInventory } from './inventoryParser.js';
|
import { extractInventory } from './inventoryParser.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to log to both console and debug logs array
|
||||||
|
*/
|
||||||
|
function debugLog(message, data = null) {
|
||||||
|
console.log(message, data || '');
|
||||||
|
if (extensionSettings.debugMode) {
|
||||||
|
addDebugLog(message, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the model response to extract the different data sections.
|
* Parses the model response to extract the different data sections.
|
||||||
* Extracts tracker data from markdown code blocks in the AI response.
|
* Extracts tracker data from markdown code blocks in the AI response.
|
||||||
@@ -22,16 +32,23 @@ export function parseResponse(responseText) {
|
|||||||
characterThoughts: null
|
characterThoughts: null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// DEBUG: Log full response for troubleshooting
|
||||||
|
debugLog('[RPG Parser] ==================== PARSING AI RESPONSE ====================');
|
||||||
|
debugLog('[RPG Parser] Response length:', responseText.length + ' chars');
|
||||||
|
debugLog('[RPG Parser] First 500 chars:', responseText.substring(0, 500));
|
||||||
|
|
||||||
// Extract code blocks
|
// Extract code blocks
|
||||||
const codeBlockRegex = /```([^`]+)```/g;
|
const codeBlockRegex = /```([^`]+)```/g;
|
||||||
const matches = [...responseText.matchAll(codeBlockRegex)];
|
const matches = [...responseText.matchAll(codeBlockRegex)];
|
||||||
|
|
||||||
// console.log('[RPG Companion] Found', matches.length, 'code blocks');
|
debugLog('[RPG Parser] Found', matches.length + ' code blocks');
|
||||||
|
|
||||||
for (const match of matches) {
|
for (let i = 0; i < matches.length; i++) {
|
||||||
|
const match = matches[i];
|
||||||
const content = match[1].trim();
|
const content = match[1].trim();
|
||||||
|
|
||||||
// console.log('[RPG Companion] Checking code block (first 200 chars):', content.substring(0, 200));
|
debugLog(`[RPG Parser] --- Code Block ${i + 1} ---`);
|
||||||
|
debugLog('[RPG Parser] First 300 chars:', content.substring(0, 300));
|
||||||
|
|
||||||
// Check if this is a combined code block with multiple sections
|
// Check if this is a combined code block with multiple sections
|
||||||
const hasMultipleSections = (
|
const hasMultipleSections = (
|
||||||
@@ -41,55 +58,81 @@ export function parseResponse(responseText) {
|
|||||||
|
|
||||||
if (hasMultipleSections) {
|
if (hasMultipleSections) {
|
||||||
// Split the combined code block into individual sections
|
// Split the combined code block into individual sections
|
||||||
// console.log('[RPG Companion] ✓ Found combined code block with multiple sections');
|
debugLog('[RPG Parser] ✓ Found combined code block with multiple sections');
|
||||||
|
|
||||||
// Extract User Stats section
|
// Extract User Stats section
|
||||||
const statsMatch = content.match(/(User )?Stats\s*\n\s*---[\s\S]*?(?=\n\s*\n\s*(Info Box|Present Characters)|$)/i);
|
const statsMatch = content.match(/(User )?Stats\s*\n\s*---[\s\S]*?(?=\n\s*\n\s*(Info Box|Present Characters)|$)/i);
|
||||||
if (statsMatch && !result.userStats) {
|
if (statsMatch && !result.userStats) {
|
||||||
result.userStats = statsMatch[0].trim();
|
result.userStats = statsMatch[0].trim();
|
||||||
// console.log('[RPG Companion] ✓ Extracted Stats from combined block');
|
debugLog('[RPG Parser] ✓ Extracted Stats from combined block');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract Info Box section
|
// Extract Info Box section
|
||||||
const infoBoxMatch = content.match(/Info Box\s*\n\s*---[\s\S]*?(?=\n\s*\n\s*Present Characters|$)/i);
|
const infoBoxMatch = content.match(/Info Box\s*\n\s*---[\s\S]*?(?=\n\s*\n\s*Present Characters|$)/i);
|
||||||
if (infoBoxMatch && !result.infoBox) {
|
if (infoBoxMatch && !result.infoBox) {
|
||||||
result.infoBox = infoBoxMatch[0].trim();
|
result.infoBox = infoBoxMatch[0].trim();
|
||||||
// console.log('[RPG Companion] ✓ Extracted Info Box from combined block');
|
debugLog('[RPG Parser] ✓ Extracted Info Box from combined block');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract Present Characters section
|
// Extract Present Characters section
|
||||||
const charactersMatch = content.match(/Present Characters\s*\n\s*---[\s\S]*$/i);
|
const charactersMatch = content.match(/Present Characters\s*\n\s*---[\s\S]*$/i);
|
||||||
if (charactersMatch && !result.characterThoughts) {
|
if (charactersMatch && !result.characterThoughts) {
|
||||||
result.characterThoughts = charactersMatch[0].trim();
|
result.characterThoughts = charactersMatch[0].trim();
|
||||||
// console.log('[RPG Companion] ✓ Extracted Present Characters from combined block');
|
debugLog('[RPG Parser] ✓ Extracted Present Characters from combined block');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Handle separate code blocks (original behavior)
|
// Handle separate code blocks with flexible pattern matching
|
||||||
// Match Stats section
|
// Match Stats section - flexible patterns
|
||||||
if (content.match(/Stats\s*\n\s*---/i) && !result.userStats) {
|
const isStats =
|
||||||
|
content.match(/Stats\s*\n\s*---/i) ||
|
||||||
|
content.match(/User Stats\s*\n\s*---/i) ||
|
||||||
|
content.match(/Player Stats\s*\n\s*---/i) ||
|
||||||
|
// Fallback: look for stat keywords without strict header
|
||||||
|
(content.match(/Health:\s*\d+%/i) && content.match(/Energy:\s*\d+%/i));
|
||||||
|
|
||||||
|
// Match Info Box section - flexible patterns
|
||||||
|
const isInfoBox =
|
||||||
|
content.match(/Info Box\s*\n\s*---/i) ||
|
||||||
|
content.match(/Scene Info\s*\n\s*---/i) ||
|
||||||
|
content.match(/Information\s*\n\s*---/i) ||
|
||||||
|
// Fallback: look for info box keywords
|
||||||
|
(content.match(/Date:/i) && content.match(/Location:/i) && content.match(/Time:/i));
|
||||||
|
|
||||||
|
// Match Present Characters section - flexible patterns
|
||||||
|
const isCharacters =
|
||||||
|
content.match(/Present Characters\s*\n\s*---/i) ||
|
||||||
|
content.match(/Characters\s*\n\s*---/i) ||
|
||||||
|
content.match(/Character Thoughts\s*\n\s*---/i) ||
|
||||||
|
// Fallback: look for table-like structure with emoji and pipes
|
||||||
|
(content.includes(" | ") && (content.includes("Thoughts") || content.includes("💭")));
|
||||||
|
|
||||||
|
if (isStats && !result.userStats) {
|
||||||
result.userStats = content;
|
result.userStats = content;
|
||||||
// console.log('[RPG Companion] ✓ Found Stats section');
|
debugLog('[RPG Parser] ✓ Matched: Stats section');
|
||||||
}
|
} else if (isInfoBox && !result.infoBox) {
|
||||||
// Match Info Box section
|
|
||||||
else if (content.match(/Info Box\s*\n\s*---/i) && !result.infoBox) {
|
|
||||||
result.infoBox = content;
|
result.infoBox = content;
|
||||||
// console.log('[RPG Companion] ✓ Found Info Box section');
|
debugLog('[RPG Parser] ✓ Matched: Info Box section');
|
||||||
}
|
} else if (isCharacters && !result.characterThoughts) {
|
||||||
// Match Present Characters section - flexible matching
|
|
||||||
else if ((content.match(/Present Characters\s*\n\s*---/i) || content.includes(" | ")) && !result.characterThoughts) {
|
|
||||||
result.characterThoughts = content;
|
result.characterThoughts = content;
|
||||||
// console.log('[RPG Companion] ✓ Found Present Characters section:', content);
|
debugLog('[RPG Parser] ✓ Matched: Present Characters section');
|
||||||
|
debugLog('[RPG Parser] Full content:', content);
|
||||||
} else {
|
} else {
|
||||||
// console.log('[RPG Companion] ✗ Code block did not match any section');
|
debugLog('[RPG Parser] ✗ No match - checking patterns:');
|
||||||
|
debugLog('[RPG Parser] - Has "Stats\\n---"?', !!content.match(/Stats\s*\n\s*---/i));
|
||||||
|
debugLog('[RPG Parser] - Has stat keywords?', !!(content.match(/Health:\s*\d+%/i) && content.match(/Energy:\s*\d+%/i)));
|
||||||
|
debugLog('[RPG Parser] - Has "Info Box\\n---"?', !!content.match(/Info Box\s*\n\s*---/i));
|
||||||
|
debugLog('[RPG Parser] - Has info keywords?', !!(content.match(/Date:/i) && content.match(/Location:/i)));
|
||||||
|
debugLog('[RPG Parser] - Has "Present Characters\\n---"?', !!content.match(/Present Characters\s*\n\s*---/i));
|
||||||
|
debugLog('[RPG Parser] - Has " | " + thoughts?', !!(content.includes(" | ") && (content.includes("Thoughts") || content.includes("💭"))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log('[RPG Companion] Parse results:', {
|
debugLog('[RPG Parser] ==================== PARSE RESULTS ====================');
|
||||||
// hasStats: !!result.userStats,
|
debugLog('[RPG Parser] Found Stats:', !!result.userStats);
|
||||||
// hasInfoBox: !!result.infoBox,
|
debugLog('[RPG Parser] Found Info Box:', !!result.infoBox);
|
||||||
// hasThoughts: !!result.characterThoughts
|
debugLog('[RPG Parser] Found Characters:', !!result.characterThoughts);
|
||||||
// });
|
debugLog('[RPG Parser] =======================================================');
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -101,6 +144,10 @@ export function parseResponse(responseText) {
|
|||||||
* @param {string} statsText - The raw stats text from AI response
|
* @param {string} statsText - The raw stats text from AI response
|
||||||
*/
|
*/
|
||||||
export function parseUserStats(statsText) {
|
export function parseUserStats(statsText) {
|
||||||
|
debugLog('[RPG Parser] ==================== PARSING USER STATS ====================');
|
||||||
|
debugLog('[RPG Parser] Stats text length:', statsText.length + ' chars');
|
||||||
|
debugLog('[RPG Parser] Stats text preview:', statsText.substring(0, 200));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Extract percentages and mood/conditions
|
// Extract percentages and mood/conditions
|
||||||
const healthMatch = statsText.match(/Health:\s*(\d+)%/);
|
const healthMatch = statsText.match(/Health:\s*(\d+)%/);
|
||||||
@@ -109,43 +156,85 @@ export function parseUserStats(statsText) {
|
|||||||
const hygieneMatch = statsText.match(/Hygiene:\s*(\d+)%/);
|
const hygieneMatch = statsText.match(/Hygiene:\s*(\d+)%/);
|
||||||
const arousalMatch = statsText.match(/Arousal:\s*(\d+)%/);
|
const arousalMatch = statsText.match(/Arousal:\s*(\d+)%/);
|
||||||
|
|
||||||
// Match new format: Status: [Emoji, Conditions]
|
debugLog('[RPG Parser] Stat matches:', {
|
||||||
// Also support legacy format: [Emoji]: [Conditions] for backward compatibility
|
health: healthMatch ? healthMatch[1] : 'NOT FOUND',
|
||||||
|
satiety: satietyMatch ? satietyMatch[1] : 'NOT FOUND',
|
||||||
|
energy: energyMatch ? energyMatch[1] : 'NOT FOUND',
|
||||||
|
hygiene: hygieneMatch ? hygieneMatch[1] : 'NOT FOUND',
|
||||||
|
arousal: arousalMatch ? arousalMatch[1] : 'NOT FOUND'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Match mood/status with multiple format variations
|
||||||
|
// Format 1: Status: [Emoji, Conditions]
|
||||||
|
// Format 2: Status: [Emoji], [Conditions]
|
||||||
|
// Format 3: [Emoji]: [Conditions] (legacy)
|
||||||
|
// Format 4: Mood: [Emoji] - [Conditions]
|
||||||
let moodMatch = null;
|
let moodMatch = null;
|
||||||
|
|
||||||
|
// Try new format: Status: emoji, conditions
|
||||||
const statusMatch = statsText.match(/Status:\s*(.+?),\s*(.+)/i);
|
const statusMatch = statsText.match(/Status:\s*(.+?),\s*(.+)/i);
|
||||||
if (statusMatch) {
|
if (statusMatch) {
|
||||||
// New format: Status: [Emoji, Conditions]
|
|
||||||
moodMatch = [null, statusMatch[1].trim(), statusMatch[2].trim()];
|
moodMatch = [null, statusMatch[1].trim(), statusMatch[2].trim()];
|
||||||
} else {
|
}
|
||||||
// Legacy format: [Emoji]: [Conditions]
|
// Try alternative: Mood: emoji, conditions
|
||||||
|
else {
|
||||||
|
const moodAltMatch = statsText.match(/Mood:\s*(.+?)[,\-]\s*(.+)/i);
|
||||||
|
if (moodAltMatch) {
|
||||||
|
moodMatch = [null, moodAltMatch[1].trim(), moodAltMatch[2].trim()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy format fallback: [Emoji]: [Conditions]
|
||||||
|
if (!moodMatch) {
|
||||||
const lines = statsText.split('\n');
|
const lines = statsText.split('\n');
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
const line = lines[i].trim();
|
const line = lines[i].trim();
|
||||||
// Skip lines with percentages or "Inventory:" or "Status:"
|
// Skip lines with percentages or known keywords
|
||||||
if (line.includes('%') || line.toLowerCase().startsWith('inventory:') || line.toLowerCase().startsWith('status:')) continue;
|
if (line.includes('%') ||
|
||||||
// Match emoji followed by colon and conditions
|
line.toLowerCase().startsWith('inventory:') ||
|
||||||
|
line.toLowerCase().startsWith('status:') ||
|
||||||
|
line.toLowerCase().startsWith('health:') ||
|
||||||
|
line.toLowerCase().startsWith('energy:') ||
|
||||||
|
line.toLowerCase().startsWith('satiety:') ||
|
||||||
|
line.toLowerCase().startsWith('hygiene:') ||
|
||||||
|
line.toLowerCase().startsWith('arousal:')) continue;
|
||||||
|
|
||||||
|
// Match emoji/mood followed by colon and conditions
|
||||||
const match = line.match(/^(.+?):\s*(.+)$/);
|
const match = line.match(/^(.+?):\s*(.+)$/);
|
||||||
if (match) {
|
if (match && match[1].length <= 10) { // Emoji/mood should be short
|
||||||
moodMatch = match;
|
moodMatch = match;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debugLog('[RPG Parser] Mood/Status match:', {
|
||||||
|
found: !!moodMatch,
|
||||||
|
emoji: moodMatch ? moodMatch[1] : 'NOT FOUND',
|
||||||
|
conditions: moodMatch ? moodMatch[2] : 'NOT FOUND'
|
||||||
|
});
|
||||||
|
|
||||||
// Extract inventory - use v2 parser if feature flag enabled, otherwise fallback to v1
|
// Extract inventory - use v2 parser if feature flag enabled, otherwise fallback to v1
|
||||||
if (FEATURE_FLAGS.useNewInventory) {
|
if (FEATURE_FLAGS.useNewInventory) {
|
||||||
const inventoryData = extractInventory(statsText);
|
const inventoryData = extractInventory(statsText);
|
||||||
if (inventoryData) {
|
if (inventoryData) {
|
||||||
extensionSettings.userStats.inventory = inventoryData;
|
extensionSettings.userStats.inventory = inventoryData;
|
||||||
|
debugLog('[RPG Parser] Inventory v2 extracted:', inventoryData);
|
||||||
|
} else {
|
||||||
|
debugLog('[RPG Parser] Inventory v2 extraction failed');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Legacy v1 parsing for backward compatibility
|
// Legacy v1 parsing for backward compatibility
|
||||||
const inventoryMatch = statsText.match(/Inventory:\s*(.+)/i);
|
const inventoryMatch = statsText.match(/Inventory:\s*(.+)/i);
|
||||||
if (inventoryMatch) {
|
if (inventoryMatch) {
|
||||||
extensionSettings.userStats.inventory = inventoryMatch[1].trim();
|
extensionSettings.userStats.inventory = inventoryMatch[1].trim();
|
||||||
|
debugLog('[RPG Parser] Inventory v1 extracted:', inventoryMatch[1].trim());
|
||||||
|
} else {
|
||||||
|
debugLog('[RPG Parser] Inventory v1 not found');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update extension settings
|
||||||
if (healthMatch) extensionSettings.userStats.health = parseInt(healthMatch[1]);
|
if (healthMatch) extensionSettings.userStats.health = parseInt(healthMatch[1]);
|
||||||
if (satietyMatch) extensionSettings.userStats.satiety = parseInt(satietyMatch[1]);
|
if (satietyMatch) extensionSettings.userStats.satiety = parseInt(satietyMatch[1]);
|
||||||
if (energyMatch) extensionSettings.userStats.energy = parseInt(energyMatch[1]);
|
if (energyMatch) extensionSettings.userStats.energy = parseInt(energyMatch[1]);
|
||||||
@@ -156,9 +245,25 @@ export function parseUserStats(statsText) {
|
|||||||
extensionSettings.userStats.conditions = moodMatch[2].trim(); // Conditions
|
extensionSettings.userStats.conditions = moodMatch[2].trim(); // Conditions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debugLog('[RPG Parser] Final userStats after parsing:', {
|
||||||
|
health: extensionSettings.userStats.health,
|
||||||
|
satiety: extensionSettings.userStats.satiety,
|
||||||
|
energy: extensionSettings.userStats.energy,
|
||||||
|
hygiene: extensionSettings.userStats.hygiene,
|
||||||
|
arousal: extensionSettings.userStats.arousal,
|
||||||
|
mood: extensionSettings.userStats.mood,
|
||||||
|
conditions: extensionSettings.userStats.conditions,
|
||||||
|
inventory: FEATURE_FLAGS.useNewInventory ? 'v2 object' : extensionSettings.userStats.inventory
|
||||||
|
});
|
||||||
|
|
||||||
saveSettings();
|
saveSettings();
|
||||||
|
debugLog('[RPG Parser] Settings saved successfully');
|
||||||
|
debugLog('[RPG Parser] =======================================================');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[RPG Companion] Error parsing user stats:', error);
|
console.error('[RPG Companion] Error parsing user stats:', error);
|
||||||
|
console.error('[RPG Companion] Stack trace:', error.stack);
|
||||||
|
debugLog('[RPG Parser] ERROR:', error.message);
|
||||||
|
debugLog('[RPG Parser] Stack:', error.stack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,22 @@ import {
|
|||||||
lastGeneratedData,
|
lastGeneratedData,
|
||||||
committedTrackerData,
|
committedTrackerData,
|
||||||
$thoughtsContainer,
|
$thoughtsContainer,
|
||||||
FALLBACK_AVATAR_DATA_URI
|
FALLBACK_AVATAR_DATA_URI,
|
||||||
|
addDebugLog
|
||||||
} from '../../core/state.js';
|
} from '../../core/state.js';
|
||||||
import { saveChatData } from '../../core/persistence.js';
|
import { saveChatData } from '../../core/persistence.js';
|
||||||
import { getSafeThumbnailUrl } from '../../utils/avatars.js';
|
import { getSafeThumbnailUrl } from '../../utils/avatars.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to log to both console and debug logs array
|
||||||
|
*/
|
||||||
|
function debugLog(message, data = null) {
|
||||||
|
console.log(message, data || '');
|
||||||
|
if (extensionSettings.debugMode) {
|
||||||
|
addDebugLog(message, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fuzzy name matching that handles:
|
* Fuzzy name matching that handles:
|
||||||
* - Exact matches: "Sabrina" === "Sabrina"
|
* - Exact matches: "Sabrina" === "Sabrina"
|
||||||
@@ -40,7 +51,9 @@ function namesMatch(cardName, aiName) {
|
|||||||
if (cardCore === aiCore) return true;
|
if (cardCore === aiCore) return true;
|
||||||
|
|
||||||
// 3. Check if card name appears as complete word in AI name
|
// 3. Check if card name appears as complete word in AI name
|
||||||
const wordBoundary = new RegExp(`\\b${cardCore}\\b`);
|
// Escape special regex characters to prevent "Invalid regular expression" errors
|
||||||
|
const escapedCardCore = cardCore.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
|
const wordBoundary = new RegExp(`\\b${escapedCardCore}\\b`);
|
||||||
return wordBoundary.test(aiCore);
|
return wordBoundary.test(aiCore);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +67,10 @@ export function renderThoughts() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debugLog('[RPG Thoughts] ==================== RENDERING PRESENT CHARACTERS ====================');
|
||||||
|
debugLog('[RPG Thoughts] showCharacterThoughts setting:', extensionSettings.showCharacterThoughts);
|
||||||
|
debugLog('[RPG Thoughts] Container exists:', !!$thoughtsContainer);
|
||||||
|
|
||||||
// Add updating class for animation
|
// Add updating class for animation
|
||||||
if (extensionSettings.enableAnimations) {
|
if (extensionSettings.enableAnimations) {
|
||||||
$thoughtsContainer.addClass('rpg-content-updating');
|
$thoughtsContainer.addClass('rpg-content-updating');
|
||||||
@@ -64,25 +81,36 @@ export function renderThoughts() {
|
|||||||
lastGeneratedData.characterThoughts = '';
|
lastGeneratedData.characterThoughts = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debugLog('[RPG Thoughts] Raw characterThoughts data:', lastGeneratedData.characterThoughts);
|
||||||
|
debugLog('[RPG Thoughts] Data length:', lastGeneratedData.characterThoughts.length + ' chars');
|
||||||
|
|
||||||
const lines = lastGeneratedData.characterThoughts.split('\n');
|
const lines = lastGeneratedData.characterThoughts.split('\n');
|
||||||
const presentCharacters = [];
|
const presentCharacters = [];
|
||||||
|
|
||||||
// console.log('[RPG Companion] Raw Present Characters:', lastGeneratedData.characterThoughts);
|
debugLog('[RPG Thoughts] Split into lines count:', lines.length);
|
||||||
// console.log('[RPG Companion] Split into lines:', lines);
|
debugLog('[RPG Thoughts] Lines:', lines);
|
||||||
|
|
||||||
// Parse format: [Emoji]: [Name, Status, Demeanor] | [Relationship] | [Thoughts]
|
// Parse format: [Emoji]: [Name, Status, Demeanor] | [Relationship] | [Thoughts]
|
||||||
// Also supports 4-part format: [Emoji]: [Name, Status] | [Demeanor] | [Relationship] | [Thoughts]
|
// Also supports 4-part format: [Emoji]: [Name, Status] | [Demeanor] | [Relationship] | [Thoughts]
|
||||||
|
let lineNumber = 0;
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
|
lineNumber++;
|
||||||
|
|
||||||
// Skip empty lines, headers, dividers, and code fences
|
// Skip empty lines, headers, dividers, and code fences
|
||||||
if (line.trim() &&
|
if (line.trim() &&
|
||||||
!line.includes('Present Characters') &&
|
!line.includes('Present Characters') &&
|
||||||
!line.includes('---') &&
|
!line.includes('---') &&
|
||||||
!line.trim().startsWith('```')) {
|
!line.trim().startsWith('```')) {
|
||||||
|
|
||||||
|
debugLog(`[RPG Thoughts] Processing line ${lineNumber}:`, line);
|
||||||
|
|
||||||
// Match the new format with pipes
|
// Match the new format with pipes
|
||||||
const parts = line.split('|').map(p => p.trim());
|
const parts = line.split('|').map(p => p.trim());
|
||||||
|
debugLog(`[RPG Thoughts] Split into ${parts.length} parts:`, parts);
|
||||||
|
|
||||||
if (parts.length >= 2) {
|
// Require at least 3 parts (Emoji:Name | Relationship | Thoughts)
|
||||||
|
// This matches updateChatThoughts() and the current prompt format
|
||||||
|
if (parts.length >= 3) {
|
||||||
// First part: [Emoji]: [Name, Status, Demeanor]
|
// First part: [Emoji]: [Name, Status, Demeanor]
|
||||||
const firstPart = parts[0].trim();
|
const firstPart = parts[0].trim();
|
||||||
const emojiMatch = firstPart.match(/^(.+?):\s*(.+)$/);
|
const emojiMatch = firstPart.match(/^(.+?):\s*(.+)$/);
|
||||||
@@ -91,6 +119,8 @@ export function renderThoughts() {
|
|||||||
const emoji = emojiMatch[1].trim();
|
const emoji = emojiMatch[1].trim();
|
||||||
const info = emojiMatch[2].trim();
|
const info = emojiMatch[2].trim();
|
||||||
|
|
||||||
|
debugLog(`[RPG Thoughts] Emoji match found - emoji: "${emoji}", info: "${info}"`);
|
||||||
|
|
||||||
// Handle both 3-part and 4-part formats
|
// Handle both 3-part and 4-part formats
|
||||||
let relationship, thoughts, traits;
|
let relationship, thoughts, traits;
|
||||||
|
|
||||||
@@ -100,7 +130,8 @@ export function renderThoughts() {
|
|||||||
thoughts = parts[2].trim();
|
thoughts = parts[2].trim();
|
||||||
const infoParts = info.split(',').map(p => p.trim());
|
const infoParts = info.split(',').map(p => p.trim());
|
||||||
traits = infoParts.slice(1).join(', ');
|
traits = infoParts.slice(1).join(', ');
|
||||||
} else if (parts.length >= 4) {
|
debugLog('[RPG Thoughts] Parsed as 3-part format');
|
||||||
|
} else {
|
||||||
// 4-part format: Emoji:Name,traits | Demeanor | Relationship | Thoughts
|
// 4-part format: Emoji:Name,traits | Demeanor | Relationship | Thoughts
|
||||||
// Add the demeanor to traits and use last two parts for relationship/thoughts
|
// Add the demeanor to traits and use last two parts for relationship/thoughts
|
||||||
const demeanor = parts[1].trim();
|
const demeanor = parts[1].trim();
|
||||||
@@ -109,23 +140,26 @@ export function renderThoughts() {
|
|||||||
const infoParts = info.split(',').map(p => p.trim());
|
const infoParts = info.split(',').map(p => p.trim());
|
||||||
const baseTraits = infoParts.slice(1).join(', ');
|
const baseTraits = infoParts.slice(1).join(', ');
|
||||||
traits = baseTraits ? `${baseTraits}, ${demeanor}` : demeanor;
|
traits = baseTraits ? `${baseTraits}, ${demeanor}` : demeanor;
|
||||||
} else {
|
debugLog('[RPG Thoughts] Parsed as 4-part format');
|
||||||
// Fallback for 2-part format
|
|
||||||
relationship = parts[1].trim();
|
|
||||||
thoughts = '';
|
|
||||||
const infoParts = info.split(',').map(p => p.trim());
|
|
||||||
traits = infoParts.slice(1).join(', ');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse name from info (first part before comma)
|
// Parse name from info (first part before comma)
|
||||||
const infoParts = info.split(',').map(p => p.trim());
|
const infoParts = info.split(',').map(p => p.trim());
|
||||||
const name = infoParts[0] || '';
|
const name = infoParts[0] || '';
|
||||||
|
|
||||||
|
debugLog(`[RPG Thoughts] Extracted - name: "${name}", traits: "${traits}", relationship: "${relationship}", thoughts: "${thoughts}"`);
|
||||||
|
|
||||||
if (name && name.toLowerCase() !== 'unavailable') {
|
if (name && name.toLowerCase() !== 'unavailable') {
|
||||||
presentCharacters.push({ emoji, name, traits, relationship, thoughts });
|
presentCharacters.push({ emoji, name, traits, relationship, thoughts });
|
||||||
// console.log('[RPG Companion] Parsed character:', { name, relationship, thoughts });
|
debugLog(`[RPG Thoughts] ✓ Added character: ${name}`);
|
||||||
|
} else {
|
||||||
|
debugLog(`[RPG Thoughts] ✗ Rejected character - name: "${name}" (unavailable or empty)`);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
debugLog('[RPG Thoughts] ✗ No emoji match found in first part');
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
debugLog(`[RPG Thoughts] ✗ Not enough parts (${parts.length} < 3, need at least Emoji:Name | Relationship | Thoughts)`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,14 +172,19 @@ export function renderThoughts() {
|
|||||||
'Lover': '❤️'
|
'Lover': '❤️'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
debugLog('[RPG Thoughts] ==================== PARSING COMPLETE ====================');
|
||||||
|
debugLog('[RPG Thoughts] Total characters parsed:', presentCharacters.length);
|
||||||
|
debugLog('[RPG Thoughts] Characters array:', presentCharacters);
|
||||||
|
|
||||||
// Build HTML
|
// Build HTML
|
||||||
let html = '';
|
let html = '';
|
||||||
|
|
||||||
// console.log('[RPG Companion] Total characters parsed:', presentCharacters.length);
|
debugLog('[RPG Thoughts] ==================== BUILDING HTML ====================');
|
||||||
// console.log('[RPG Companion] Characters array:', presentCharacters);
|
debugLog('[RPG Thoughts] Starting HTML generation for', presentCharacters.length + ' characters');
|
||||||
|
|
||||||
// If no characters parsed, show a placeholder editable card
|
// If no characters parsed, show a placeholder editable card
|
||||||
if (presentCharacters.length === 0) {
|
if (presentCharacters.length === 0) {
|
||||||
|
debugLog('[RPG Thoughts] ⚠ No characters parsed - showing placeholder card');
|
||||||
// Get default character portrait (try to use the current character if in 1-on-1 chat)
|
// Get default character portrait (try to use the current character if in 1-on-1 chat)
|
||||||
// Use a base64-encoded SVG placeholder as fallback to avoid 400 errors
|
// Use a base64-encoded SVG placeholder as fallback to avoid 400 errors
|
||||||
let defaultPortrait = FALLBACK_AVATAR_DATA_URI;
|
let defaultPortrait = FALLBACK_AVATAR_DATA_URI;
|
||||||
@@ -180,75 +219,114 @@ export function renderThoughts() {
|
|||||||
html += '</div>';
|
html += '</div>';
|
||||||
} else {
|
} else {
|
||||||
html += '<div class="rpg-thoughts-content">';
|
html += '<div class="rpg-thoughts-content">';
|
||||||
|
|
||||||
|
let characterIndex = 0;
|
||||||
for (const char of presentCharacters) {
|
for (const char of presentCharacters) {
|
||||||
// Find character portrait
|
characterIndex++;
|
||||||
// Use a base64-encoded SVG placeholder as fallback to avoid 400 errors
|
|
||||||
let characterPortrait = FALLBACK_AVATAR_DATA_URI;
|
|
||||||
|
|
||||||
// console.log('[RPG Companion] Looking for avatar for:', char.name);
|
try {
|
||||||
|
debugLog(`[RPG Thoughts] Building HTML for character ${characterIndex}/${presentCharacters.length}:`, char.name);
|
||||||
|
|
||||||
// For group chats, search through group members first
|
// Find character portrait
|
||||||
if (selected_group) {
|
// Use a base64-encoded SVG placeholder as fallback to avoid 400 errors
|
||||||
const groupMembers = getGroupMembers(selected_group);
|
let characterPortrait = FALLBACK_AVATAR_DATA_URI;
|
||||||
const matchingMember = groupMembers.find(member =>
|
|
||||||
member && member.name && namesMatch(member.name, char.name)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (matchingMember && matchingMember.avatar && matchingMember.avatar !== 'none') {
|
debugLog(`[RPG Thoughts] Looking up avatar for: ${char.name}`);
|
||||||
const thumbnailUrl = getSafeThumbnailUrl('avatar', matchingMember.avatar);
|
|
||||||
if (thumbnailUrl) {
|
// For group chats, search through group members first
|
||||||
characterPortrait = thumbnailUrl;
|
if (selected_group) {
|
||||||
|
debugLog('[RPG Thoughts] In group chat, checking group members...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const groupMembers = getGroupMembers(selected_group);
|
||||||
|
debugLog('[RPG Thoughts] Group members count:', groupMembers ? groupMembers.length : 0);
|
||||||
|
|
||||||
|
if (groupMembers && groupMembers.length > 0) {
|
||||||
|
const matchingMember = groupMembers.find(member =>
|
||||||
|
member && member.name && namesMatch(member.name, char.name)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (matchingMember && matchingMember.avatar && matchingMember.avatar !== 'none') {
|
||||||
|
const thumbnailUrl = getSafeThumbnailUrl('avatar', matchingMember.avatar);
|
||||||
|
if (thumbnailUrl) {
|
||||||
|
characterPortrait = thumbnailUrl;
|
||||||
|
debugLog('[RPG Thoughts] Found avatar in group members');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (groupError) {
|
||||||
|
debugLog('[RPG Thoughts] Error checking group members:', groupError.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// For regular chats or if not found in group, search all characters
|
// For regular chats or if not found in group, search all characters
|
||||||
if (characterPortrait === FALLBACK_AVATAR_DATA_URI && characters && characters.length > 0) {
|
if (characterPortrait === FALLBACK_AVATAR_DATA_URI && characters && characters.length > 0) {
|
||||||
const matchingCharacter = characters.find(c =>
|
debugLog('[RPG Thoughts] Searching all characters...');
|
||||||
c && c.name && namesMatch(c.name, char.name)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (matchingCharacter && matchingCharacter.avatar && matchingCharacter.avatar !== 'none') {
|
const matchingCharacter = characters.find(c =>
|
||||||
const thumbnailUrl = getSafeThumbnailUrl('avatar', matchingCharacter.avatar);
|
c && c.name && namesMatch(c.name, char.name)
|
||||||
if (thumbnailUrl) {
|
);
|
||||||
characterPortrait = thumbnailUrl;
|
|
||||||
|
if (matchingCharacter && matchingCharacter.avatar && matchingCharacter.avatar !== 'none') {
|
||||||
|
const thumbnailUrl = getSafeThumbnailUrl('avatar', matchingCharacter.avatar);
|
||||||
|
if (thumbnailUrl) {
|
||||||
|
characterPortrait = thumbnailUrl;
|
||||||
|
debugLog('[RPG Thoughts] Found avatar in all characters');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// If this is the current character in a 1-on-1 chat, use their portrait
|
// If this is the current character in a 1-on-1 chat, use their portrait
|
||||||
if (this_chid !== undefined && characters[this_chid] &&
|
if (this_chid !== undefined && characters[this_chid] &&
|
||||||
characters[this_chid].name && namesMatch(characters[this_chid].name, char.name)) {
|
characters[this_chid].name && namesMatch(characters[this_chid].name, char.name)) {
|
||||||
const thumbnailUrl = getSafeThumbnailUrl('avatar', characters[this_chid].avatar);
|
const thumbnailUrl = getSafeThumbnailUrl('avatar', characters[this_chid].avatar);
|
||||||
if (thumbnailUrl) {
|
if (thumbnailUrl) {
|
||||||
characterPortrait = thumbnailUrl;
|
characterPortrait = thumbnailUrl;
|
||||||
|
debugLog('[RPG Thoughts] Found avatar from current character');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Get relationship emoji
|
debugLog(`[RPG Thoughts] Final avatar for ${char.name}:`, characterPortrait.substring(0, 50) + '...');
|
||||||
const relationshipEmoji = relationshipEmojis[char.relationship] || '⚖️';
|
|
||||||
|
|
||||||
html += `
|
// Get relationship emoji
|
||||||
<div class="rpg-character-card" data-character-name="${char.name}">
|
const relationshipEmoji = relationshipEmojis[char.relationship] || '⚖️';
|
||||||
<div class="rpg-character-avatar">
|
|
||||||
<img src="${characterPortrait}" alt="${char.name}" onerror="this.style.opacity='0.5';this.onerror=null;" />
|
debugLog(`[RPG Thoughts] Building HTML card for ${char.name}...`);
|
||||||
<div class="rpg-relationship-badge rpg-editable" contenteditable="true" data-character="${char.name}" data-field="relationship" title="Click to edit (use emoji: ⚔️ ⚖️ ⭐ ❤️)">${relationshipEmoji}</div>
|
|
||||||
</div>
|
html += `
|
||||||
<div class="rpg-character-info">
|
<div class="rpg-character-card" data-character-name="${char.name}">
|
||||||
<div class="rpg-character-header">
|
<div class="rpg-character-avatar">
|
||||||
<span class="rpg-character-emoji rpg-editable" contenteditable="true" data-character="${char.name}" data-field="emoji" title="Click to edit emoji">${char.emoji}</span>
|
<img src="${characterPortrait}" alt="${char.name}" onerror="this.style.opacity='0.5';this.onerror=null;" />
|
||||||
<span class="rpg-character-name rpg-editable" contenteditable="true" data-character="${char.name}" data-field="name" title="Click to edit name">${char.name}</span>
|
<div class="rpg-relationship-badge rpg-editable" contenteditable="true" data-character="${char.name}" data-field="relationship" title="Click to edit (use emoji: ⚔️ ⚖️ ⭐ ❤️)">${relationshipEmoji}</div>
|
||||||
|
</div>
|
||||||
|
<div class="rpg-character-info">
|
||||||
|
<div class="rpg-character-header">
|
||||||
|
<span class="rpg-character-emoji rpg-editable" contenteditable="true" data-character="${char.name}" data-field="emoji" title="Click to edit emoji">${char.emoji}</span>
|
||||||
|
<span class="rpg-character-name rpg-editable" contenteditable="true" data-character="${char.name}" data-field="name" title="Click to edit name">${char.name}</span>
|
||||||
|
</div>
|
||||||
|
<div class="rpg-character-traits rpg-editable" contenteditable="true" data-character="${char.name}" data-field="traits" title="Click to edit traits">${char.traits}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rpg-character-traits rpg-editable" contenteditable="true" data-character="${char.name}" data-field="traits" title="Click to edit traits">${char.traits}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
`;
|
||||||
`;
|
|
||||||
|
debugLog(`[RPG Thoughts] ✓ Successfully built HTML for ${char.name}`);
|
||||||
|
|
||||||
|
} catch (charError) {
|
||||||
|
debugLog(`[RPG Thoughts] ✗ ERROR building HTML for ${char.name}:`, charError.message);
|
||||||
|
debugLog('[RPG Thoughts] Error stack:', charError.stack);
|
||||||
|
// Continue with next character instead of crashing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debugLog('[RPG Thoughts] Finished building all character cards');
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
$thoughtsContainer.html(html);
|
$thoughtsContainer.html(html);
|
||||||
|
|
||||||
|
debugLog('[RPG Thoughts] ✓ HTML rendered to container');
|
||||||
|
debugLog('[RPG Thoughts] =======================================================');
|
||||||
|
|
||||||
// Add event handlers for editable character fields
|
// Add event handlers for editable character fields
|
||||||
$thoughtsContainer.find('.rpg-editable').on('blur', function() {
|
$thoughtsContainer.find('.rpg-editable').on('blur', function() {
|
||||||
const character = $(this).data('character');
|
const character = $(this).data('character');
|
||||||
|
|||||||
@@ -0,0 +1,220 @@
|
|||||||
|
/**
|
||||||
|
* Debug UI Module
|
||||||
|
* Provides mobile-friendly debug log viewer for troubleshooting parsing issues
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { extensionSettings, getDebugLogs, clearDebugLogs } from '../../core/state.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and injects the debug panel into the page
|
||||||
|
* Note: Debug toggle button is created in index.js, not here
|
||||||
|
*/
|
||||||
|
export function createDebugPanel() {
|
||||||
|
// Remove existing debug panel if any
|
||||||
|
$('#rpg-debug-panel').remove();
|
||||||
|
|
||||||
|
// Create debug panel HTML
|
||||||
|
const debugPanelHtml = `
|
||||||
|
<div id="rpg-debug-panel" class="rpg-debug-panel">
|
||||||
|
<div class="rpg-debug-header">
|
||||||
|
<h3>🔍 Debug Logs</h3>
|
||||||
|
<div class="rpg-debug-actions">
|
||||||
|
<button id="rpg-debug-copy" title="Copy logs to clipboard">
|
||||||
|
<i class="fa-solid fa-copy"></i>
|
||||||
|
</button>
|
||||||
|
<button id="rpg-debug-clear" title="Clear logs">
|
||||||
|
<i class="fa-solid fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
<button id="rpg-debug-close" title="Close debug panel">
|
||||||
|
<i class="fa-solid fa-xmark"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="rpg-debug-logs" class="rpg-debug-logs"></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Append to body
|
||||||
|
$('body').append(debugPanelHtml);
|
||||||
|
|
||||||
|
// Set up event handlers
|
||||||
|
setupDebugEventHandlers();
|
||||||
|
|
||||||
|
// Initial log render
|
||||||
|
renderDebugLogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the debug panel with proper animation (mobile or desktop)
|
||||||
|
*/
|
||||||
|
function closeDebugPanel() {
|
||||||
|
const $panel = $('#rpg-debug-panel');
|
||||||
|
const isMobile = window.innerWidth <= 1000;
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
// Mobile: animate slide-out to right
|
||||||
|
$panel.removeClass('rpg-mobile-open').addClass('rpg-mobile-closing');
|
||||||
|
|
||||||
|
// Wait for animation to complete before hiding
|
||||||
|
$panel.one('animationend', function() {
|
||||||
|
$panel.removeClass('rpg-mobile-closing');
|
||||||
|
$('.rpg-mobile-overlay').remove();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Desktop: simple slide-down
|
||||||
|
$panel.removeClass('rpg-debug-open');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up event handlers for debug panel using event delegation for mobile compatibility
|
||||||
|
*/
|
||||||
|
function setupDebugEventHandlers() {
|
||||||
|
// Use event delegation for better mobile compatibility and reliability with dynamic elements
|
||||||
|
// Remove any existing handlers first to prevent duplicates
|
||||||
|
$(document).off('click.rpgDebug');
|
||||||
|
|
||||||
|
// Toggle button
|
||||||
|
$(document).on('click.rpgDebug', '#rpg-debug-toggle', function() {
|
||||||
|
const $debugToggle = $(this);
|
||||||
|
|
||||||
|
// Skip if we just finished dragging
|
||||||
|
if ($debugToggle.data('just-dragged')) {
|
||||||
|
console.log('[RPG Debug] Click blocked - just finished dragging');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const $panel = $('#rpg-debug-panel');
|
||||||
|
const isMobile = window.innerWidth <= 1000;
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
// Mobile: use rpg-mobile-open class with slide-from-right animation
|
||||||
|
const isOpen = $panel.hasClass('rpg-mobile-open');
|
||||||
|
|
||||||
|
if (isOpen) {
|
||||||
|
// Close with animation
|
||||||
|
closeDebugPanel();
|
||||||
|
} else {
|
||||||
|
// Open with animation
|
||||||
|
$panel.addClass('rpg-mobile-open');
|
||||||
|
renderDebugLogs();
|
||||||
|
|
||||||
|
// Create overlay for mobile
|
||||||
|
const $overlay = $('<div class="rpg-mobile-overlay"></div>');
|
||||||
|
$('body').append($overlay);
|
||||||
|
|
||||||
|
// Close when clicking overlay
|
||||||
|
$overlay.on('click', function() {
|
||||||
|
closeDebugPanel();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Desktop: use rpg-debug-open class with slide-from-bottom animation
|
||||||
|
$panel.toggleClass('rpg-debug-open');
|
||||||
|
renderDebugLogs();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close button
|
||||||
|
$(document).on('click.rpgDebug', '#rpg-debug-close', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
closeDebugPanel();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Copy button
|
||||||
|
$(document).on('click.rpgDebug', '#rpg-debug-copy', function() {
|
||||||
|
const logs = getDebugLogs();
|
||||||
|
const logsText = logs.map(log => {
|
||||||
|
let text = `[${log.timestamp}] ${log.message}`;
|
||||||
|
if (log.data) {
|
||||||
|
text += `\n${log.data}`;
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}).join('\n\n');
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(logsText).then(() => {
|
||||||
|
// Show feedback
|
||||||
|
const $btn = $(this);
|
||||||
|
const $icon = $btn.find('i');
|
||||||
|
$icon.removeClass('fa-copy').addClass('fa-check');
|
||||||
|
setTimeout(() => {
|
||||||
|
$icon.removeClass('fa-check').addClass('fa-copy');
|
||||||
|
}, 1500);
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('Failed to copy logs:', err);
|
||||||
|
alert('Failed to copy logs. Please use browser console instead.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear button
|
||||||
|
$(document).on('click.rpgDebug', '#rpg-debug-clear', function() {
|
||||||
|
if (confirm('Clear all debug logs?')) {
|
||||||
|
clearDebugLogs();
|
||||||
|
renderDebugLogs();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders debug logs to the panel
|
||||||
|
*/
|
||||||
|
function renderDebugLogs() {
|
||||||
|
const logs = getDebugLogs();
|
||||||
|
const $logsContainer = $('#rpg-debug-logs');
|
||||||
|
|
||||||
|
if (logs.length === 0) {
|
||||||
|
$logsContainer.html('<div class="rpg-debug-empty">No logs yet. Logs will appear when parser runs.</div>');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build logs HTML
|
||||||
|
const logsHtml = logs.map(log => {
|
||||||
|
let html = `<div class="rpg-debug-entry">`;
|
||||||
|
html += `<span class="rpg-debug-time">[${log.timestamp}]</span> `;
|
||||||
|
html += `<span class="rpg-debug-message">${escapeHtml(log.message)}</span>`;
|
||||||
|
if (log.data) {
|
||||||
|
html += `<pre class="rpg-debug-data">${escapeHtml(log.data)}</pre>`;
|
||||||
|
}
|
||||||
|
html += `</div>`;
|
||||||
|
return html;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
$logsContainer.html(logsHtml);
|
||||||
|
|
||||||
|
// Auto-scroll to bottom
|
||||||
|
$logsContainer[0].scrollTop = $logsContainer[0].scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes HTML to prevent XSS
|
||||||
|
*/
|
||||||
|
function escapeHtml(text) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.textContent = text;
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows or hides debug UI based on debug mode setting
|
||||||
|
* Note: Debug toggle button always exists in DOM (created in index.js)
|
||||||
|
*/
|
||||||
|
export function updateDebugUIVisibility() {
|
||||||
|
const $debugToggle = $('#rpg-debug-toggle');
|
||||||
|
|
||||||
|
if (extensionSettings.debugMode) {
|
||||||
|
// Show debug toggle button
|
||||||
|
$debugToggle.css('display', 'flex');
|
||||||
|
|
||||||
|
// Create debug panel if it doesn't exist
|
||||||
|
if ($('#rpg-debug-panel').length === 0) {
|
||||||
|
createDebugPanel();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Hide debug toggle button
|
||||||
|
$debugToggle.css('display', 'none');
|
||||||
|
|
||||||
|
// Remove debug panel
|
||||||
|
$('#rpg-debug-panel').remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -266,11 +266,15 @@ export function applyPanelPosition() {
|
|||||||
* Updates the UI based on generation mode selection.
|
* Updates the UI based on generation mode selection.
|
||||||
*/
|
*/
|
||||||
export function updateGenerationModeUI() {
|
export function updateGenerationModeUI() {
|
||||||
|
const $mobileBtn = $('#rpg-manual-update-mobile');
|
||||||
|
|
||||||
if (extensionSettings.generationMode === 'together') {
|
if (extensionSettings.generationMode === 'together') {
|
||||||
// In "together" mode, manual update button is hidden
|
// In "together" mode, hide both desktop and mobile refresh buttons
|
||||||
$('#rpg-manual-update').hide();
|
$('#rpg-manual-update').hide();
|
||||||
|
$mobileBtn.addClass('rpg-hidden-mode');
|
||||||
} else {
|
} else {
|
||||||
// In "separate" mode, manual update button is visible
|
// In "separate" mode, show both desktop and mobile refresh buttons
|
||||||
$('#rpg-manual-update').show();
|
$('#rpg-manual-update').show();
|
||||||
|
$mobileBtn.removeClass('rpg-hidden-mode');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -716,3 +716,434 @@ export function setupContentEditableScrolling() {
|
|||||||
}, 300);
|
}, 300);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the mobile refresh button with drag functionality.
|
||||||
|
* Same pattern as mobile toggle button.
|
||||||
|
* Tap = refresh, drag = reposition
|
||||||
|
*/
|
||||||
|
export function setupRefreshButtonDrag() {
|
||||||
|
const $refreshBtn = $('#rpg-manual-update-mobile');
|
||||||
|
|
||||||
|
if ($refreshBtn.length === 0) {
|
||||||
|
console.warn('[RPG Mobile] Refresh button not found in DOM');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[RPG Mobile] setupRefreshButtonDrag called');
|
||||||
|
|
||||||
|
// Load and apply saved position
|
||||||
|
if (extensionSettings.mobileRefreshPosition) {
|
||||||
|
const pos = extensionSettings.mobileRefreshPosition;
|
||||||
|
console.log('[RPG Mobile] Loading saved refresh button position:', pos);
|
||||||
|
|
||||||
|
// Apply saved position
|
||||||
|
if (pos.top) $refreshBtn.css('top', pos.top);
|
||||||
|
if (pos.right) $refreshBtn.css('right', pos.right);
|
||||||
|
if (pos.bottom) $refreshBtn.css('bottom', pos.bottom);
|
||||||
|
if (pos.left) $refreshBtn.css('left', pos.left);
|
||||||
|
|
||||||
|
// Constrain to viewport after position is applied
|
||||||
|
requestAnimationFrame(() => constrainFabToViewport($refreshBtn));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Touch/drag state
|
||||||
|
let isDragging = false;
|
||||||
|
let touchStartTime = 0;
|
||||||
|
let touchStartX = 0;
|
||||||
|
let touchStartY = 0;
|
||||||
|
let buttonStartX = 0;
|
||||||
|
let buttonStartY = 0;
|
||||||
|
const LONG_PRESS_DURATION = 200;
|
||||||
|
const MOVE_THRESHOLD = 10;
|
||||||
|
let rafId = null;
|
||||||
|
let pendingX = null;
|
||||||
|
let pendingY = null;
|
||||||
|
|
||||||
|
// Update position using requestAnimationFrame
|
||||||
|
function updatePosition() {
|
||||||
|
if (pendingX !== null && pendingY !== null) {
|
||||||
|
$refreshBtn.css({
|
||||||
|
left: pendingX + 'px',
|
||||||
|
top: pendingY + 'px',
|
||||||
|
right: 'auto',
|
||||||
|
bottom: 'auto'
|
||||||
|
});
|
||||||
|
pendingX = null;
|
||||||
|
pendingY = null;
|
||||||
|
}
|
||||||
|
rafId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Touch start
|
||||||
|
$refreshBtn.on('touchstart', function(e) {
|
||||||
|
const touch = e.originalEvent.touches[0];
|
||||||
|
touchStartTime = Date.now();
|
||||||
|
touchStartX = touch.clientX;
|
||||||
|
touchStartY = touch.clientY;
|
||||||
|
|
||||||
|
const offset = $refreshBtn.offset();
|
||||||
|
buttonStartX = offset.left;
|
||||||
|
buttonStartY = offset.top;
|
||||||
|
|
||||||
|
isDragging = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Touch move
|
||||||
|
$refreshBtn.on('touchmove', function(e) {
|
||||||
|
const touch = e.originalEvent.touches[0];
|
||||||
|
const deltaX = touch.clientX - touchStartX;
|
||||||
|
const deltaY = touch.clientY - touchStartY;
|
||||||
|
const timeSinceStart = Date.now() - touchStartTime;
|
||||||
|
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||||
|
|
||||||
|
if (!isDragging && (timeSinceStart > LONG_PRESS_DURATION || distance > MOVE_THRESHOLD)) {
|
||||||
|
isDragging = true;
|
||||||
|
$refreshBtn.addClass('dragging');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDragging) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
let newX = buttonStartX + deltaX;
|
||||||
|
let newY = buttonStartY + deltaY;
|
||||||
|
|
||||||
|
const buttonWidth = $refreshBtn.outerWidth();
|
||||||
|
const buttonHeight = $refreshBtn.outerHeight();
|
||||||
|
|
||||||
|
const minX = 10;
|
||||||
|
const maxX = window.innerWidth - buttonWidth - 10;
|
||||||
|
const minY = 10;
|
||||||
|
const maxY = window.innerHeight - buttonHeight - 10;
|
||||||
|
|
||||||
|
newX = Math.max(minX, Math.min(maxX, newX));
|
||||||
|
newY = Math.max(minY, Math.min(maxY, newY));
|
||||||
|
|
||||||
|
pendingX = newX;
|
||||||
|
pendingY = newY;
|
||||||
|
if (!rafId) {
|
||||||
|
rafId = requestAnimationFrame(updatePosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Touch end
|
||||||
|
$refreshBtn.on('touchend', function(e) {
|
||||||
|
if (isDragging) {
|
||||||
|
// Save new position
|
||||||
|
const offset = $refreshBtn.offset();
|
||||||
|
const newPosition = {
|
||||||
|
left: offset.left + 'px',
|
||||||
|
top: offset.top + 'px'
|
||||||
|
};
|
||||||
|
|
||||||
|
extensionSettings.mobileRefreshPosition = newPosition;
|
||||||
|
saveSettings();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
$refreshBtn.removeClass('dragging');
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
// Set flag to prevent click handler from firing
|
||||||
|
$refreshBtn.data('just-dragged', true);
|
||||||
|
setTimeout(() => {
|
||||||
|
$refreshBtn.data('just-dragged', false);
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
isDragging = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mouse support for desktop
|
||||||
|
let mouseDown = false;
|
||||||
|
|
||||||
|
$refreshBtn.on('mousedown', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
touchStartTime = Date.now();
|
||||||
|
touchStartX = e.clientX;
|
||||||
|
touchStartY = e.clientY;
|
||||||
|
|
||||||
|
const offset = $refreshBtn.offset();
|
||||||
|
buttonStartX = offset.left;
|
||||||
|
buttonStartY = offset.top;
|
||||||
|
|
||||||
|
mouseDown = true;
|
||||||
|
isDragging = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('mousemove', function(e) {
|
||||||
|
if (!mouseDown) return;
|
||||||
|
|
||||||
|
const deltaX = e.clientX - touchStartX;
|
||||||
|
const deltaY = e.clientY - touchStartY;
|
||||||
|
const timeSinceStart = Date.now() - touchStartTime;
|
||||||
|
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||||
|
|
||||||
|
if (!isDragging && (timeSinceStart > LONG_PRESS_DURATION || distance > MOVE_THRESHOLD)) {
|
||||||
|
isDragging = true;
|
||||||
|
$refreshBtn.addClass('dragging');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDragging) {
|
||||||
|
let newX = buttonStartX + deltaX;
|
||||||
|
let newY = buttonStartY + deltaY;
|
||||||
|
|
||||||
|
const buttonWidth = $refreshBtn.outerWidth();
|
||||||
|
const buttonHeight = $refreshBtn.outerHeight();
|
||||||
|
|
||||||
|
const minX = 10;
|
||||||
|
const maxX = window.innerWidth - buttonWidth - 10;
|
||||||
|
const minY = 10;
|
||||||
|
const maxY = window.innerHeight - buttonHeight - 10;
|
||||||
|
|
||||||
|
newX = Math.max(minX, Math.min(maxX, newX));
|
||||||
|
newY = Math.max(minY, Math.min(maxY, newY));
|
||||||
|
|
||||||
|
pendingX = newX;
|
||||||
|
pendingY = newY;
|
||||||
|
if (!rafId) {
|
||||||
|
rafId = requestAnimationFrame(updatePosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('mouseup', function(e) {
|
||||||
|
if (mouseDown && isDragging) {
|
||||||
|
const offset = $refreshBtn.offset();
|
||||||
|
const newPosition = {
|
||||||
|
left: offset.left + 'px',
|
||||||
|
top: offset.top + 'px'
|
||||||
|
};
|
||||||
|
|
||||||
|
extensionSettings.mobileRefreshPosition = newPosition;
|
||||||
|
saveSettings();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
$refreshBtn.removeClass('dragging');
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
$refreshBtn.data('just-dragged', true);
|
||||||
|
setTimeout(() => {
|
||||||
|
$refreshBtn.data('just-dragged', false);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
mouseDown = false;
|
||||||
|
isDragging = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up drag functionality for the debug toggle FAB button
|
||||||
|
* Same pattern as refresh button drag
|
||||||
|
*/
|
||||||
|
export function setupDebugButtonDrag() {
|
||||||
|
const $debugBtn = $('#rpg-debug-toggle');
|
||||||
|
|
||||||
|
if ($debugBtn.length === 0) {
|
||||||
|
console.warn('[RPG Mobile] Debug button not found in DOM');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[RPG Mobile] setupDebugButtonDrag called');
|
||||||
|
|
||||||
|
// Load and apply saved position
|
||||||
|
if (extensionSettings.debugFabPosition) {
|
||||||
|
const pos = extensionSettings.debugFabPosition;
|
||||||
|
console.log('[RPG Mobile] Loading saved debug button position:', pos);
|
||||||
|
|
||||||
|
// Apply saved position
|
||||||
|
if (pos.top) $debugBtn.css('top', pos.top);
|
||||||
|
if (pos.right) $debugBtn.css('right', pos.right);
|
||||||
|
if (pos.bottom) $debugBtn.css('bottom', pos.bottom);
|
||||||
|
if (pos.left) $debugBtn.css('left', pos.left);
|
||||||
|
|
||||||
|
// Constrain to viewport after position is applied
|
||||||
|
requestAnimationFrame(() => constrainFabToViewport($debugBtn));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Touch/drag state
|
||||||
|
let isDragging = false;
|
||||||
|
let touchStartTime = 0;
|
||||||
|
let touchStartX = 0;
|
||||||
|
let touchStartY = 0;
|
||||||
|
let buttonStartX = 0;
|
||||||
|
let buttonStartY = 0;
|
||||||
|
const LONG_PRESS_DURATION = 200;
|
||||||
|
const MOVE_THRESHOLD = 10;
|
||||||
|
let rafId = null;
|
||||||
|
let pendingX = null;
|
||||||
|
let pendingY = null;
|
||||||
|
|
||||||
|
// Update position using requestAnimationFrame
|
||||||
|
function updatePosition() {
|
||||||
|
if (pendingX !== null && pendingY !== null) {
|
||||||
|
$debugBtn.css({
|
||||||
|
left: pendingX + 'px',
|
||||||
|
top: pendingY + 'px',
|
||||||
|
right: 'auto',
|
||||||
|
bottom: 'auto'
|
||||||
|
});
|
||||||
|
pendingX = null;
|
||||||
|
pendingY = null;
|
||||||
|
}
|
||||||
|
rafId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Touch start
|
||||||
|
$debugBtn.on('touchstart', function(e) {
|
||||||
|
const touch = e.originalEvent.touches[0];
|
||||||
|
touchStartTime = Date.now();
|
||||||
|
touchStartX = touch.clientX;
|
||||||
|
touchStartY = touch.clientY;
|
||||||
|
|
||||||
|
const offset = $debugBtn.offset();
|
||||||
|
buttonStartX = offset.left;
|
||||||
|
buttonStartY = offset.top;
|
||||||
|
|
||||||
|
isDragging = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Touch move
|
||||||
|
$debugBtn.on('touchmove', function(e) {
|
||||||
|
const touch = e.originalEvent.touches[0];
|
||||||
|
const deltaX = touch.clientX - touchStartX;
|
||||||
|
const deltaY = touch.clientY - touchStartY;
|
||||||
|
const timeSinceStart = Date.now() - touchStartTime;
|
||||||
|
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||||
|
|
||||||
|
if (!isDragging && (timeSinceStart > LONG_PRESS_DURATION || distance > MOVE_THRESHOLD)) {
|
||||||
|
isDragging = true;
|
||||||
|
$debugBtn.addClass('dragging');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDragging) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
let newX = buttonStartX + deltaX;
|
||||||
|
let newY = buttonStartY + deltaY;
|
||||||
|
|
||||||
|
const buttonWidth = $debugBtn.outerWidth();
|
||||||
|
const buttonHeight = $debugBtn.outerHeight();
|
||||||
|
|
||||||
|
const minX = 10;
|
||||||
|
const maxX = window.innerWidth - buttonWidth - 10;
|
||||||
|
const minY = 10;
|
||||||
|
const maxY = window.innerHeight - buttonHeight - 10;
|
||||||
|
|
||||||
|
newX = Math.max(minX, Math.min(maxX, newX));
|
||||||
|
newY = Math.max(minY, Math.min(maxY, newY));
|
||||||
|
|
||||||
|
pendingX = newX;
|
||||||
|
pendingY = newY;
|
||||||
|
if (!rafId) {
|
||||||
|
rafId = requestAnimationFrame(updatePosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Touch end
|
||||||
|
$debugBtn.on('touchend', function(e) {
|
||||||
|
if (isDragging) {
|
||||||
|
// Save new position
|
||||||
|
const offset = $debugBtn.offset();
|
||||||
|
const newPosition = {
|
||||||
|
left: offset.left + 'px',
|
||||||
|
top: offset.top + 'px'
|
||||||
|
};
|
||||||
|
|
||||||
|
extensionSettings.debugFabPosition = newPosition;
|
||||||
|
saveSettings();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
$debugBtn.removeClass('dragging');
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
// Set flag to prevent click handler from firing
|
||||||
|
$debugBtn.data('just-dragged', true);
|
||||||
|
setTimeout(() => {
|
||||||
|
$debugBtn.data('just-dragged', false);
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
isDragging = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mouse support for desktop
|
||||||
|
let mouseDown = false;
|
||||||
|
|
||||||
|
$debugBtn.on('mousedown', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
touchStartTime = Date.now();
|
||||||
|
touchStartX = e.clientX;
|
||||||
|
touchStartY = e.clientY;
|
||||||
|
|
||||||
|
const offset = $debugBtn.offset();
|
||||||
|
buttonStartX = offset.left;
|
||||||
|
buttonStartY = offset.top;
|
||||||
|
|
||||||
|
mouseDown = true;
|
||||||
|
isDragging = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('mousemove.rpgDebugDrag', function(e) {
|
||||||
|
if (!mouseDown) return;
|
||||||
|
|
||||||
|
const deltaX = e.clientX - touchStartX;
|
||||||
|
const deltaY = e.clientY - touchStartY;
|
||||||
|
const timeSinceStart = Date.now() - touchStartTime;
|
||||||
|
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||||
|
|
||||||
|
if (!isDragging && (timeSinceStart > LONG_PRESS_DURATION || distance > MOVE_THRESHOLD)) {
|
||||||
|
isDragging = true;
|
||||||
|
$debugBtn.addClass('dragging');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDragging) {
|
||||||
|
let newX = buttonStartX + deltaX;
|
||||||
|
let newY = buttonStartY + deltaY;
|
||||||
|
|
||||||
|
const buttonWidth = $debugBtn.outerWidth();
|
||||||
|
const buttonHeight = $debugBtn.outerHeight();
|
||||||
|
|
||||||
|
const minX = 10;
|
||||||
|
const maxX = window.innerWidth - buttonWidth - 10;
|
||||||
|
const minY = 10;
|
||||||
|
const maxY = window.innerHeight - buttonHeight - 10;
|
||||||
|
|
||||||
|
newX = Math.max(minX, Math.min(maxX, newX));
|
||||||
|
newY = Math.max(minY, Math.min(maxY, newY));
|
||||||
|
|
||||||
|
pendingX = newX;
|
||||||
|
pendingY = newY;
|
||||||
|
if (!rafId) {
|
||||||
|
rafId = requestAnimationFrame(updatePosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('mouseup.rpgDebugDrag', function(e) {
|
||||||
|
if (mouseDown && isDragging) {
|
||||||
|
const offset = $debugBtn.offset();
|
||||||
|
const newPosition = {
|
||||||
|
left: offset.left + 'px',
|
||||||
|
top: offset.top + 'px'
|
||||||
|
};
|
||||||
|
|
||||||
|
extensionSettings.debugFabPosition = newPosition;
|
||||||
|
saveSettings();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
$debugBtn.removeClass('dragging');
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
$debugBtn.data('just-dragged', true);
|
||||||
|
setTimeout(() => {
|
||||||
|
$debugBtn.data('just-dragged', false);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
mouseDown = false;
|
||||||
|
isDragging = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -2054,6 +2054,34 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rpg-btn-reset-fab {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.625em;
|
||||||
|
background: rgba(52, 152, 219, 0.2);
|
||||||
|
border: 2px solid rgba(52, 152, 219, 0.5);
|
||||||
|
border-radius: 0.5em;
|
||||||
|
color: #5dade2;
|
||||||
|
font-size: 1.2vw;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5em;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-btn-reset-fab:hover {
|
||||||
|
background: rgba(52, 152, 219, 0.3);
|
||||||
|
border-color: rgba(52, 152, 219, 0.8);
|
||||||
|
color: #85c1e9;
|
||||||
|
transform: translateY(-0.062rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-btn-reset-fab:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
/* ============================================
|
/* ============================================
|
||||||
THEME VARIATIONS
|
THEME VARIATIONS
|
||||||
============================================ */
|
============================================ */
|
||||||
@@ -3277,6 +3305,66 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
MOBILE REFRESH FAB BUTTON (Same pattern as mobile 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(--SmartThemeBodyColor);
|
||||||
|
font-size: 1.125rem;
|
||||||
|
cursor: grab;
|
||||||
|
z-index: 1001; /* Below mobile toggle (10002), above debug (1000) */
|
||||||
|
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;
|
||||||
|
/* Hidden by default - shown when panel open AND separate mode */
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Mobile overlay backdrop */
|
/* Mobile overlay backdrop */
|
||||||
.rpg-mobile-overlay {
|
.rpg-mobile-overlay {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -3302,7 +3390,23 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide FAB when panel is open */
|
/* Show the mobile FAB refresh button (but hidden by opacity) */
|
||||||
|
.rpg-mobile-refresh {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show refresh button when panel is open AND not hidden by generation mode */
|
||||||
|
body:has(.rpg-panel.rpg-mobile-open) .rpg-mobile-refresh:not(.rpg-hidden-mode) {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide desktop refresh button on mobile */
|
||||||
|
#rpg-manual-update {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide toggle FAB when panel is open */
|
||||||
body:has(.rpg-panel.rpg-mobile-open) .rpg-mobile-toggle {
|
body:has(.rpg-panel.rpg-mobile-open) .rpg-mobile-toggle {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
@@ -3960,6 +4064,11 @@ 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 */
|
||||||
|
.rpg-mobile-refresh {
|
||||||
|
font-size: clamp(20px, 5.1vw, 26px) !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* ========================================
|
/* ========================================
|
||||||
MOBILE SETTINGS POPUP
|
MOBILE SETTINGS POPUP
|
||||||
======================================== */
|
======================================== */
|
||||||
@@ -3994,6 +4103,10 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
.rpg-btn-clear-cache {
|
.rpg-btn-clear-cache {
|
||||||
font-size: clamp(13px, 3.3vw, 17px) !important;
|
font-size: clamp(13px, 3.3vw, 17px) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rpg-btn-reset-fab {
|
||||||
|
font-size: clamp(13px, 3.3vw, 17px) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Very narrow screens - single column layout for all stats */
|
/* Very narrow screens - single column layout for all stats */
|
||||||
@@ -4675,3 +4788,239 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
min-height: 2rem;
|
min-height: 2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===================================================================
|
||||||
|
Debug Panel Styles - Mobile-Friendly Debug Log Viewer
|
||||||
|
=================================================================== */
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
DEBUG TOGGLE FAB BUTTON (Same pattern as mobile FABs)
|
||||||
|
============================================ */
|
||||||
|
.rpg-debug-toggle {
|
||||||
|
display: none; /* Hidden by default, shown when debugMode is enabled */
|
||||||
|
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: 1000; /* Below refresh (1001) and 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-debug-toggle.dragging {
|
||||||
|
transition: none;
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-debug-toggle:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-debug-toggle:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Debug panel */
|
||||||
|
.rpg-debug-panel {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 60vh;
|
||||||
|
background: var(--SmartThemeBlurTintColor, #1a1a1a);
|
||||||
|
border-top: 2px solid var(--SmartThemeBorderColor, #333);
|
||||||
|
z-index: 10002;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
transform: translateY(100%);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-debug-panel.rpg-debug-open {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-debug-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1rem;
|
||||||
|
border-bottom: 1px solid var(--SmartThemeBorderColor, #333);
|
||||||
|
background: var(--SmartThemeBodyColor, #0d0d0d);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-debug-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--rpg-text, #ecf0f1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-debug-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-debug-actions button {
|
||||||
|
background: var(--SmartThemeBlurTintColor, #2a2a2a);
|
||||||
|
border: 1px solid var(--SmartThemeBorderColor, #444);
|
||||||
|
color: var(--rpg-text, #ecf0f1);
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-debug-actions button:hover {
|
||||||
|
background: var(--SmartThemeBorderColor, #333);
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-debug-actions button:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-debug-actions button i {
|
||||||
|
pointer-events: none; /* Prevent icon from blocking clicks */
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-debug-logs {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 1rem;
|
||||||
|
font-family: 'Courier New', Courier, monospace;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: var(--rpg-text, #ecf0f1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-debug-empty {
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem;
|
||||||
|
color: #888;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-debug-entry {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-debug-entry:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-debug-time {
|
||||||
|
color: #888;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-debug-message {
|
||||||
|
color: #4fc3f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-debug-data {
|
||||||
|
margin: 0.5rem 0 0 0;
|
||||||
|
padding: 0.75rem;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow-x: auto;
|
||||||
|
color: #9ccc65;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile view - slide from right like main panel */
|
||||||
|
@media (max-width: 1000px) {
|
||||||
|
.rpg-debug-panel {
|
||||||
|
/* Reset bottom slide positioning */
|
||||||
|
transform: none;
|
||||||
|
transition: none;
|
||||||
|
bottom: auto;
|
||||||
|
|
||||||
|
/* Mobile panel - slide from right */
|
||||||
|
position: fixed !important;
|
||||||
|
top: var(--topBarBlockSize) !important;
|
||||||
|
right: 0 !important;
|
||||||
|
left: auto !important;
|
||||||
|
|
||||||
|
/* Mobile sizing using dynamic viewport units */
|
||||||
|
width: 85dvw !important;
|
||||||
|
max-width: 400px !important;
|
||||||
|
height: calc(100dvh - var(--topBarBlockSize)) !important;
|
||||||
|
|
||||||
|
/* Hidden by default */
|
||||||
|
display: none !important;
|
||||||
|
|
||||||
|
/* Mobile scrolling */
|
||||||
|
overflow-y: auto !important;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
|
||||||
|
/* Styling */
|
||||||
|
border-radius: 20px 0 0 0;
|
||||||
|
border-left: 1px solid var(--SmartThemeBorderColor);
|
||||||
|
border-top: 1px solid var(--SmartThemeBorderColor);
|
||||||
|
border-bottom: none;
|
||||||
|
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
|
||||||
|
box-shadow: -5px 0 20px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show panel when opened with slide-in animation */
|
||||||
|
.rpg-debug-panel.rpg-mobile-open {
|
||||||
|
display: flex !important;
|
||||||
|
z-index: 10002;
|
||||||
|
animation: rpgSlideInFromRight 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Closing animation - slide out to right */
|
||||||
|
.rpg-debug-panel.rpg-mobile-closing {
|
||||||
|
display: flex !important;
|
||||||
|
z-index: 10002;
|
||||||
|
animation: rpgSlideOutToRight 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Debug logs container needs to stay scrollable */
|
||||||
|
.rpg-debug-logs {
|
||||||
|
overflow-y: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Debug toggle button on mobile */
|
||||||
|
.rpg-debug-toggle {
|
||||||
|
font-size: clamp(20px, 5.1vw, 26px) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Desktop view - smaller panel in bottom right */
|
||||||
|
@media (min-width: 1001px) {
|
||||||
|
.rpg-debug-panel {
|
||||||
|
bottom: 20px;
|
||||||
|
left: auto;
|
||||||
|
right: 20px;
|
||||||
|
width: 600px;
|
||||||
|
max-width: 90vw;
|
||||||
|
height: 400px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 2px solid var(--SmartThemeBorderColor, #333);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -195,6 +195,14 @@
|
|||||||
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;">
|
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;">
|
||||||
Display buttons above chat input for plot progression prompts
|
Display buttons above chat input for plot progression prompts
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
<label class="checkbox_label">
|
||||||
|
<input type="checkbox" id="rpg-toggle-debug-mode" />
|
||||||
|
<span>Enable Debug Mode</span>
|
||||||
|
</label>
|
||||||
|
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;">
|
||||||
|
Shows parser logs in a mobile-friendly UI panel. Useful for troubleshooting. Look for the red bug button.
|
||||||
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rpg-settings-group">
|
<div class="rpg-settings-group">
|
||||||
@@ -229,6 +237,16 @@
|
|||||||
<i class="fa-solid fa-trash" aria-hidden="true"></i> Clear Extension Cache
|
<i class="fa-solid fa-trash" aria-hidden="true"></i> Clear Extension Cache
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Reset FAB Positions Button -->
|
||||||
|
<div style="margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--rpg-border);">
|
||||||
|
<button id="rpg-reset-fab-positions" class="rpg-btn-reset-fab">
|
||||||
|
<i class="fa-solid fa-arrows-rotate" aria-hidden="true"></i> Reset Button Positions
|
||||||
|
</button>
|
||||||
|
<small style="display: block; margin-top: 8px; color: #888; font-size: 11px;">
|
||||||
|
Resets all floating action buttons (toggle, refresh, debug) to default top-left positions. Useful if buttons are off-screen.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user