Add chapter checkpoint UI improvements and separate Quests toggle

- Fix checkpoint button display with expandMessageActions setting
- Add body class observer to update buttons when setting toggles
- Add cleanupCheckpointUI function for extension disable
- Separate Quests from Inventory with independent toggle
- Add horizontal scrolling to Info Box dashboard
- Add divider between Inventory and Quests sections
This commit is contained in:
Spicy_Marinara
2025-12-22 01:05:01 +01:00
parent d386752f9c
commit 04bd314da2
9 changed files with 99 additions and 35 deletions
+13 -1
View File
@@ -93,7 +93,8 @@ import {
import { import {
initChapterCheckpointUI, initChapterCheckpointUI,
injectCheckpointButton, injectCheckpointButton,
updateAllCheckpointIndicators updateAllCheckpointIndicators,
cleanupCheckpointUI
} from './src/systems/ui/checkpointUI.js'; } from './src/systems/ui/checkpointUI.js';
import { restoreCheckpointOnLoad } from './src/systems/features/chapterCheckpoint.js'; import { restoreCheckpointOnLoad } from './src/systems/features/chapterCheckpoint.js';
import { import {
@@ -197,6 +198,7 @@ async function addExtensionSettings() {
// Disabling extension - remove UI elements // Disabling extension - remove UI elements
clearExtensionPrompts(); clearExtensionPrompts();
updateChatThoughts(); // Remove thought bubbles updateChatThoughts(); // Remove thought bubbles
cleanupCheckpointUI(); // Remove checkpoint buttons and indicators
// Remove panel and toggle buttons // Remove panel and toggle buttons
$('#rpg-companion-panel').remove(); $('#rpg-companion-panel').remove();
@@ -204,11 +206,14 @@ async function addExtensionSettings() {
$('#rpg-collapse-toggle').remove(); $('#rpg-collapse-toggle').remove();
$('#rpg-debug-toggle').remove(); $('#rpg-debug-toggle').remove();
$('#rpg-debug-panel').remove(); $('#rpg-debug-panel').remove();
$('#rpg-plot-buttons').remove(); // Remove plot buttons
} else if (extensionSettings.enabled && !wasEnabled) { } else if (extensionSettings.enabled && !wasEnabled) {
// Enabling extension - initialize UI // Enabling extension - initialize UI
await initUI(); await initUI();
loadChatData(); // Load chat data for current chat loadChatData(); // Load chat data for current chat
updateChatThoughts(); // Create thought bubbles if data exists updateChatThoughts(); // Create thought bubbles if data exists
injectCheckpointButton(); // Re-add checkpoint buttons
updateAllCheckpointIndicators(); // Update button states
} }
// Update Memory Recollection button visibility // Update Memory Recollection button visibility
@@ -332,6 +337,12 @@ async function initUI() {
updateSectionVisibility(); updateSectionVisibility();
}); });
$('#rpg-toggle-quests').on('change', function() {
extensionSettings.showQuests = $(this).prop('checked');
saveSettings();
updateSectionVisibility();
});
$('#rpg-toggle-thoughts-in-chat').on('change', function() { $('#rpg-toggle-thoughts-in-chat').on('change', function() {
extensionSettings.showThoughtsInChat = $(this).prop('checked'); extensionSettings.showThoughtsInChat = $(this).prop('checked');
// console.log('[RPG Companion] Toggle showThoughtsInChat changed to:', extensionSettings.showThoughtsInChat); // console.log('[RPG Companion] Toggle showThoughtsInChat changed to:', extensionSettings.showThoughtsInChat);
@@ -472,6 +483,7 @@ async function initUI() {
$('#rpg-toggle-info-box').prop('checked', extensionSettings.showInfoBox); $('#rpg-toggle-info-box').prop('checked', extensionSettings.showInfoBox);
$('#rpg-toggle-thoughts').prop('checked', extensionSettings.showCharacterThoughts); $('#rpg-toggle-thoughts').prop('checked', extensionSettings.showCharacterThoughts);
$('#rpg-toggle-inventory').prop('checked', extensionSettings.showInventory); $('#rpg-toggle-inventory').prop('checked', extensionSettings.showInventory);
$('#rpg-toggle-quests').prop('checked', extensionSettings.showQuests);
$('#rpg-toggle-thoughts-in-chat').prop('checked', extensionSettings.showThoughtsInChat); $('#rpg-toggle-thoughts-in-chat').prop('checked', extensionSettings.showThoughtsInChat);
$('#rpg-toggle-always-show-bubble').prop('checked', extensionSettings.alwaysShowThoughtBubble); $('#rpg-toggle-always-show-bubble').prop('checked', extensionSettings.alwaysShowThoughtBubble);
$('#rpg-toggle-html-prompt').prop('checked', extensionSettings.enableHtmlPrompt); $('#rpg-toggle-html-prompt').prop('checked', extensionSettings.enableHtmlPrompt);
+1
View File
@@ -31,6 +31,7 @@ export const defaultSettings = {
showInfoBox: true, showInfoBox: true,
showCharacterThoughts: true, showCharacterThoughts: true,
showInventory: true, // Show inventory section (v2 system) showInventory: true, // Show inventory section (v2 system)
showQuests: true, // Show quests section
showThoughtsInChat: true, // Show thoughts overlay in chat showThoughtsInChat: true, // Show thoughts overlay in chat
alwaysShowThoughtBubble: false, // Auto-expand thought bubble without clicking icon alwaysShowThoughtBubble: false, // Auto-expand thought bubble without clicking icon
enableHtmlPrompt: false, // Enable immersive HTML prompt injection enableHtmlPrompt: false, // Enable immersive HTML prompt injection
+1
View File
@@ -19,6 +19,7 @@ export let extensionSettings = {
showInfoBox: true, showInfoBox: true,
showCharacterThoughts: true, showCharacterThoughts: true,
showInventory: true, // Show inventory section (v2 system) showInventory: true, // Show inventory section (v2 system)
showQuests: true, // Show quests section
showThoughtsInChat: true, // Show thoughts overlay in chat showThoughtsInChat: true, // Show thoughts overlay in chat
enableHtmlPrompt: false, // Enable immersive HTML prompt injection enableHtmlPrompt: false, // Enable immersive HTML prompt injection
customHtmlPrompt: '', // Custom HTML prompt text (empty = use default) customHtmlPrompt: '', // Custom HTML prompt text (empty = use default)
+1
View File
@@ -30,6 +30,7 @@
"template.settingsModal.display.showInfoBox": "Show Info Box", "template.settingsModal.display.showInfoBox": "Show Info Box",
"template.settingsModal.display.showPresentCharacters": "Show Present Characters", "template.settingsModal.display.showPresentCharacters": "Show Present Characters",
"template.settingsModal.display.showInventory": "Show Inventory", "template.settingsModal.display.showInventory": "Show Inventory",
"template.settingsModal.display.showQuests": "Show Quests",
"template.settingsModal.display.showThoughtsInChat": "Show Thoughts in Chat", "template.settingsModal.display.showThoughtsInChat": "Show Thoughts in Chat",
"template.settingsModal.display.showThoughtsInChatNote": "Display character thoughts as overlay bubbles next to their messages", "template.settingsModal.display.showThoughtsInChatNote": "Display character thoughts as overlay bubbles next to their messages",
"template.settingsModal.display.alwaysShowThoughtBubble": "Always Show Thought Bubble", "template.settingsModal.display.alwaysShowThoughtBubble": "Always Show Thought Bubble",
+1 -1
View File
@@ -160,7 +160,7 @@ export function renderOptionalQuestsView(optionalQuests) {
* Main render function for quests * Main render function for quests
*/ */
export function renderQuests() { export function renderQuests() {
if (!extensionSettings.showInventory || !$questsContainer) { if (!extensionSettings.showQuests || !$questsContainer) {
return; return;
} }
+54 -23
View File
@@ -119,6 +119,9 @@ export function updateAllCheckpointIndicators() {
if (!chat) return; if (!chat) return;
// First, remove ALL checkpoint buttons from everywhere
document.querySelectorAll('.rpg-checkpoint-button, .rpg-checkpoint-button-expanded').forEach(btn => btn.remove());
// Update all message blocks // Update all message blocks
const messageBlocks = document.querySelectorAll('.mes'); const messageBlocks = document.querySelectorAll('.mes');
messageBlocks.forEach((block) => { messageBlocks.forEach((block) => {
@@ -129,20 +132,27 @@ export function updateAllCheckpointIndicators() {
addCheckpointIndicator(messageId, block); addCheckpointIndicator(messageId, block);
// Update any existing dropdown menu button for this message // Re-add buttons based on current mode
processExpandedButton(block);
const dropdownMenu = block.querySelector('.extraMesButtons'); const dropdownMenu = block.querySelector('.extraMesButtons');
if (dropdownMenu) { if (dropdownMenu) {
updateCheckpointButtonInMenu(dropdownMenu, messageId); processExtraMesButtons(dropdownMenu);
}
// Update any existing expanded button for this message
const mesButtons = block.querySelector('.mes_buttons');
if (mesButtons) {
updateCheckpointButtonInMenu(mesButtons, messageId);
} }
}); });
} }
/**
* Removes all checkpoint UI elements
*/
export function cleanupCheckpointUI() {
// Remove all checkpoint buttons
document.querySelectorAll('.rpg-checkpoint-button, .rpg-checkpoint-button-expanded').forEach(btn => btn.remove());
// Remove all checkpoint indicators (banner)
document.querySelectorAll('.rpg-checkpoint-indicator').forEach(indicator => indicator.remove());
}
/** /**
* Initializes the chapter checkpoint UI * Initializes the chapter checkpoint UI
*/ */
@@ -152,6 +162,17 @@ export function initChapterCheckpointUI() {
updateAllCheckpointIndicators(); updateAllCheckpointIndicators();
}); });
// Listen for expandMessageActions class changes on body
const bodyObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
// The expandMessageActions class was toggled, refresh all buttons
updateAllCheckpointIndicators();
}
});
});
bodyObserver.observe(document.body, { attributes: true, attributeFilter: ['class'] });
// Listen for chat changes to update indicators // Listen for chat changes to update indicators
const context = getContext(); const context = getContext();
if (context && context.eventSource) { if (context && context.eventSource) {
@@ -205,12 +226,12 @@ export function injectCheckpointButton() {
processExtraMesButtons(node); processExtraMesButtons(node);
} }
// Check if message block was added (for expanded mode) // Check if message block was added (for expanded buttons)
if (node.classList && node.classList.contains('mes')) { if (node.classList && node.classList.contains('mes')) {
processExpandedButton(node); processExpandedButton(node);
} }
// Also check if either exists within added subtree // Also check if any exist within added subtree
if (node.querySelector) { if (node.querySelector) {
const extraButtons = node.querySelectorAll('.extraMesButtons'); const extraButtons = node.querySelectorAll('.extraMesButtons');
extraButtons.forEach(processExtraMesButtons); extraButtons.forEach(processExtraMesButtons);
@@ -237,12 +258,15 @@ export function injectCheckpointButton() {
subtree: true subtree: true
}); });
// Process any existing menus and messages on initialization // Process any existing dropdown menus and messages on initialization
// Use setTimeout to ensure styles are computed
setTimeout(() => {
const existingDropdownMenus = chatContainer.querySelectorAll('.extraMesButtons'); const existingDropdownMenus = chatContainer.querySelectorAll('.extraMesButtons');
existingDropdownMenus.forEach(processExtraMesButtons); existingDropdownMenus.forEach(processExtraMesButtons);
const existingMessages = chatContainer.querySelectorAll('.mes'); const existingMessages = chatContainer.querySelectorAll('.mes');
existingMessages.forEach(processExpandedButton); existingMessages.forEach(processExpandedButton);
}, 100);
} }
} }
@@ -257,21 +281,20 @@ function processExtraMesButtons(menu) {
const messageBlock = menu.closest('.mes'); const messageBlock = menu.closest('.mes');
if (!messageBlock) return; if (!messageBlock) return;
// Check if expanded mode is active - if so, don't add to dropdown
const mesButtons = messageBlock.querySelector('.mes_buttons');
if (mesButtons && window.getComputedStyle(mesButtons).display !== 'none') {
return; // Expanded mode is active, skip dropdown
}
// Get the message ID from the mesid attribute (SillyTavern's standard way) // Get the message ID from the mesid attribute (SillyTavern's standard way)
const messageId = Number(messageBlock.getAttribute('mesid')); const messageId = Number(messageBlock.getAttribute('mesid'));
if (isNaN(messageId)) return; if (isNaN(messageId)) return;
// Check if expanded mode is active - if so, skip dropdown
if (document.body.classList.contains('expandMessageActions')) {
return; // Expanded mode is ON, button will be added to mes_buttons instead
}
// Check if button already exists in this container // Check if button already exists in this container
if (menu.querySelector('.rpg-checkpoint-button')) return; if (menu.querySelector('.rpg-checkpoint-button')) return;
// Add checkpoint button for dropdown menu // Add checkpoint button to dropdown menu
const checkpointBtn = addCheckpointButtonToMessage(messageId, menu, false); const checkpointBtn = addCheckpointButtonToMessage(messageId, menu, false);
if (checkpointBtn) { if (checkpointBtn) {
checkpointBtn.classList.add('rpg-checkpoint-button'); checkpointBtn.classList.add('rpg-checkpoint-button');
@@ -280,7 +303,7 @@ function processExtraMesButtons(menu) {
} }
/** /**
* Process message to add expanded checkpoint button * Process a message block to add expanded checkpoint button
* @param {HTMLElement} messageBlock - The message block element * @param {HTMLElement} messageBlock - The message block element
*/ */
function processExpandedButton(messageBlock) { function processExpandedButton(messageBlock) {
@@ -289,9 +312,9 @@ function processExpandedButton(messageBlock) {
const mesButtons = messageBlock.querySelector('.mes_buttons'); const mesButtons = messageBlock.querySelector('.mes_buttons');
if (!mesButtons) return; if (!mesButtons) return;
// Only add if mes_buttons is visible (expanded mode is active) // Only add if expanded mode is ON (check body class)
if (window.getComputedStyle(mesButtons).display === 'none') { if (!document.body.classList.contains('expandMessageActions')) {
return; return; // Expanded mode is OFF, button will be in dropdown instead
} }
const messageId = Number(messageBlock.getAttribute('mesid')); const messageId = Number(messageBlock.getAttribute('mesid'));
@@ -304,9 +327,17 @@ function processExpandedButton(messageBlock) {
const checkpointBtn = addCheckpointButtonToMessage(messageId, mesButtons, true); const checkpointBtn = addCheckpointButtonToMessage(messageId, mesButtons, true);
if (checkpointBtn) { if (checkpointBtn) {
checkpointBtn.classList.add('rpg-checkpoint-button-expanded'); checkpointBtn.classList.add('rpg-checkpoint-button-expanded');
// Insert before the edit button if it exists, otherwise append
const editButton = mesButtons.querySelector('.mes_edit');
if (editButton) {
mesButtons.insertBefore(checkpointBtn, editButton);
} else {
mesButtons.appendChild(checkpointBtn); mesButtons.appendChild(checkpointBtn);
} }
} }
}
/** /**
* Update the checkpoint button in an existing menu * Update the checkpoint button in an existing menu
@@ -316,7 +347,7 @@ function processExpandedButton(messageBlock) {
function updateCheckpointButtonInMenu(menu, messageId) { function updateCheckpointButtonInMenu(menu, messageId) {
if (!menu) return; if (!menu) return;
// Check for both button classes (dropdown and expanded) // Find the checkpoint button (either dropdown or expanded)
const existingButton = menu.querySelector('.rpg-checkpoint-button, .rpg-checkpoint-button-expanded'); const existingButton = menu.querySelector('.rpg-checkpoint-button, .rpg-checkpoint-button-expanded');
if (!existingButton) return; if (!existingButton) return;
+13 -5
View File
@@ -9,7 +9,8 @@ import {
$userStatsContainer, $userStatsContainer,
$infoBoxContainer, $infoBoxContainer,
$thoughtsContainer, $thoughtsContainer,
$inventoryContainer $inventoryContainer,
$questsContainer
} from '../../core/state.js'; } from '../../core/state.js';
import { i18n } from '../../core/i18n.js'; import { i18n } from '../../core/i18n.js';
@@ -230,22 +231,29 @@ export function updateSectionVisibility() {
if ($inventoryContainer) { if ($inventoryContainer) {
$inventoryContainer.toggle(extensionSettings.showInventory); $inventoryContainer.toggle(extensionSettings.showInventory);
} }
if ($questsContainer) {
$questsContainer.toggle(extensionSettings.showQuests);
}
// Show/hide dividers intelligently // Show/hide dividers intelligently
// Divider after User Stats: shown if User Stats is visible AND at least one section after it is visible // Divider after User Stats: shown if User Stats is visible AND at least one section after it is visible
const showDividerAfterStats = extensionSettings.showUserStats && const showDividerAfterStats = extensionSettings.showUserStats &&
(extensionSettings.showInfoBox || extensionSettings.showCharacterThoughts || extensionSettings.showInventory); (extensionSettings.showInfoBox || extensionSettings.showCharacterThoughts || extensionSettings.showInventory || extensionSettings.showQuests);
$('#rpg-divider-stats').toggle(showDividerAfterStats); $('#rpg-divider-stats').toggle(showDividerAfterStats);
// Divider after Info Box: shown if Info Box is visible AND at least one section after it is visible // Divider after Info Box: shown if Info Box is visible AND at least one section after it is visible
const showDividerAfterInfo = extensionSettings.showInfoBox && const showDividerAfterInfo = extensionSettings.showInfoBox &&
(extensionSettings.showCharacterThoughts || extensionSettings.showInventory); (extensionSettings.showCharacterThoughts || extensionSettings.showInventory || extensionSettings.showQuests);
$('#rpg-divider-info').toggle(showDividerAfterInfo); $('#rpg-divider-info').toggle(showDividerAfterInfo);
// Divider after Thoughts: shown if Thoughts is visible AND Inventory is visible // Divider after Thoughts: shown if Thoughts is visible AND at least one section after it is visible
const showDividerAfterThoughts = extensionSettings.showCharacterThoughts && const showDividerAfterThoughts = extensionSettings.showCharacterThoughts &&
extensionSettings.showInventory; (extensionSettings.showInventory || extensionSettings.showQuests);
$('#rpg-divider-thoughts').toggle(showDividerAfterThoughts); $('#rpg-divider-thoughts').toggle(showDividerAfterThoughts);
// Divider after Inventory: shown if Inventory is visible AND Quests is visible
const showDividerAfterInventory = extensionSettings.showInventory && extensionSettings.showQuests;
$('#rpg-divider-inventory').toggle(showDividerAfterInventory);
} }
/** /**
+2
View File
@@ -1143,6 +1143,8 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
gap: 0.25em; gap: 0.25em;
flex: 1; flex: 1;
min-height: 0; min-height: 0;
overflow-x: auto;
overflow-y: hidden;
} }
/* Row 1: 4 equal-width widgets - more square aspect ratio */ /* Row 1: 4 equal-width widgets - more square aspect ratio */
+8
View File
@@ -53,6 +53,9 @@
<!-- Content will be populated by JavaScript --> <!-- Content will be populated by JavaScript -->
</div> </div>
<!-- Divider after Inventory -->
<div id="rpg-divider-inventory" class="rpg-divider"></div>
<!-- Quests Section --> <!-- Quests Section -->
<div id="rpg-quests" class="rpg-section rpg-quests-section"> <div id="rpg-quests" class="rpg-section rpg-quests-section">
<!-- Content will be populated by JavaScript --> <!-- Content will be populated by JavaScript -->
@@ -182,6 +185,11 @@
<span data-i18n-key="template.settingsModal.display.showInventory">Show Inventory</span> <span data-i18n-key="template.settingsModal.display.showInventory">Show Inventory</span>
</label> </label>
<label class="checkbox_label">
<input type="checkbox" id="rpg-toggle-quests" />
<span data-i18n-key="template.settingsModal.display.showQuests">Show Quests</span>
</label>
<label class="checkbox_label"> <label class="checkbox_label">
<input type="checkbox" id="rpg-toggle-thoughts-in-chat" /> <input type="checkbox" id="rpg-toggle-thoughts-in-chat" />
<span data-i18n-key="template.settingsModal.display.showThoughtsInChat">Show Thoughts in Chat</span> <span data-i18n-key="template.settingsModal.display.showThoughtsInChat">Show Thoughts in Chat</span>