diff --git a/index.js b/index.js index 80ac29e..ce7e8b2 100644 --- a/index.js +++ b/index.js @@ -93,7 +93,8 @@ import { import { initChapterCheckpointUI, injectCheckpointButton, - updateAllCheckpointIndicators + updateAllCheckpointIndicators, + cleanupCheckpointUI } from './src/systems/ui/checkpointUI.js'; import { restoreCheckpointOnLoad } from './src/systems/features/chapterCheckpoint.js'; import { @@ -197,6 +198,7 @@ async function addExtensionSettings() { // Disabling extension - remove UI elements clearExtensionPrompts(); updateChatThoughts(); // Remove thought bubbles + cleanupCheckpointUI(); // Remove checkpoint buttons and indicators // Remove panel and toggle buttons $('#rpg-companion-panel').remove(); @@ -204,11 +206,14 @@ async function addExtensionSettings() { $('#rpg-collapse-toggle').remove(); $('#rpg-debug-toggle').remove(); $('#rpg-debug-panel').remove(); + $('#rpg-plot-buttons').remove(); // Remove plot buttons } else if (extensionSettings.enabled && !wasEnabled) { // Enabling extension - initialize UI await initUI(); loadChatData(); // Load chat data for current chat updateChatThoughts(); // Create thought bubbles if data exists + injectCheckpointButton(); // Re-add checkpoint buttons + updateAllCheckpointIndicators(); // Update button states } // Update Memory Recollection button visibility @@ -332,6 +337,12 @@ async function initUI() { updateSectionVisibility(); }); + $('#rpg-toggle-quests').on('change', function() { + extensionSettings.showQuests = $(this).prop('checked'); + saveSettings(); + updateSectionVisibility(); + }); + $('#rpg-toggle-thoughts-in-chat').on('change', function() { extensionSettings.showThoughtsInChat = $(this).prop('checked'); // 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-thoughts').prop('checked', extensionSettings.showCharacterThoughts); $('#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-always-show-bubble').prop('checked', extensionSettings.alwaysShowThoughtBubble); $('#rpg-toggle-html-prompt').prop('checked', extensionSettings.enableHtmlPrompt); diff --git a/src/core/config.js b/src/core/config.js index 2377186..da6d396 100644 --- a/src/core/config.js +++ b/src/core/config.js @@ -31,6 +31,7 @@ export const defaultSettings = { showInfoBox: true, showCharacterThoughts: true, showInventory: true, // Show inventory section (v2 system) + showQuests: true, // Show quests section showThoughtsInChat: true, // Show thoughts overlay in chat alwaysShowThoughtBubble: false, // Auto-expand thought bubble without clicking icon enableHtmlPrompt: false, // Enable immersive HTML prompt injection diff --git a/src/core/state.js b/src/core/state.js index 3ec579e..bc7e59f 100644 --- a/src/core/state.js +++ b/src/core/state.js @@ -19,6 +19,7 @@ export let extensionSettings = { showInfoBox: true, showCharacterThoughts: true, showInventory: true, // Show inventory section (v2 system) + showQuests: true, // Show quests section showThoughtsInChat: true, // Show thoughts overlay in chat enableHtmlPrompt: false, // Enable immersive HTML prompt injection customHtmlPrompt: '', // Custom HTML prompt text (empty = use default) diff --git a/src/i18n/en.json b/src/i18n/en.json index 846199f..47a4f22 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -30,6 +30,7 @@ "template.settingsModal.display.showInfoBox": "Show Info Box", "template.settingsModal.display.showPresentCharacters": "Show Present Characters", "template.settingsModal.display.showInventory": "Show Inventory", + "template.settingsModal.display.showQuests": "Show Quests", "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.alwaysShowThoughtBubble": "Always Show Thought Bubble", diff --git a/src/systems/rendering/quests.js b/src/systems/rendering/quests.js index d122835..4b0dc26 100644 --- a/src/systems/rendering/quests.js +++ b/src/systems/rendering/quests.js @@ -160,7 +160,7 @@ export function renderOptionalQuestsView(optionalQuests) { * Main render function for quests */ export function renderQuests() { - if (!extensionSettings.showInventory || !$questsContainer) { + if (!extensionSettings.showQuests || !$questsContainer) { return; } diff --git a/src/systems/ui/checkpointUI.js b/src/systems/ui/checkpointUI.js index 0a25a76..35872da 100644 --- a/src/systems/ui/checkpointUI.js +++ b/src/systems/ui/checkpointUI.js @@ -119,6 +119,9 @@ export function updateAllCheckpointIndicators() { 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 const messageBlocks = document.querySelectorAll('.mes'); messageBlocks.forEach((block) => { @@ -129,20 +132,27 @@ export function updateAllCheckpointIndicators() { 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'); if (dropdownMenu) { - updateCheckpointButtonInMenu(dropdownMenu, messageId); - } - - // Update any existing expanded button for this message - const mesButtons = block.querySelector('.mes_buttons'); - if (mesButtons) { - updateCheckpointButtonInMenu(mesButtons, messageId); + processExtraMesButtons(dropdownMenu); } }); } +/** + * 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 */ @@ -152,6 +162,17 @@ export function initChapterCheckpointUI() { 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 const context = getContext(); if (context && context.eventSource) { @@ -205,12 +226,12 @@ export function injectCheckpointButton() { 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')) { processExpandedButton(node); } - // Also check if either exists within added subtree + // Also check if any exist within added subtree if (node.querySelector) { const extraButtons = node.querySelectorAll('.extraMesButtons'); extraButtons.forEach(processExtraMesButtons); @@ -237,12 +258,15 @@ export function injectCheckpointButton() { subtree: true }); - // Process any existing menus and messages on initialization - const existingDropdownMenus = chatContainer.querySelectorAll('.extraMesButtons'); - existingDropdownMenus.forEach(processExtraMesButtons); + // Process any existing dropdown menus and messages on initialization + // Use setTimeout to ensure styles are computed + setTimeout(() => { + const existingDropdownMenus = chatContainer.querySelectorAll('.extraMesButtons'); + existingDropdownMenus.forEach(processExtraMesButtons); - const existingMessages = chatContainer.querySelectorAll('.mes'); - existingMessages.forEach(processExpandedButton); + const existingMessages = chatContainer.querySelectorAll('.mes'); + existingMessages.forEach(processExpandedButton); + }, 100); } } @@ -257,21 +281,20 @@ function processExtraMesButtons(menu) { const messageBlock = menu.closest('.mes'); 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) const messageId = Number(messageBlock.getAttribute('mesid')); 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 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); if (checkpointBtn) { 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 */ function processExpandedButton(messageBlock) { @@ -289,9 +312,9 @@ function processExpandedButton(messageBlock) { const mesButtons = messageBlock.querySelector('.mes_buttons'); if (!mesButtons) return; - // Only add if mes_buttons is visible (expanded mode is active) - if (window.getComputedStyle(mesButtons).display === 'none') { - return; + // Only add if expanded mode is ON (check body class) + if (!document.body.classList.contains('expandMessageActions')) { + return; // Expanded mode is OFF, button will be in dropdown instead } const messageId = Number(messageBlock.getAttribute('mesid')); @@ -304,10 +327,18 @@ function processExpandedButton(messageBlock) { const checkpointBtn = addCheckpointButtonToMessage(messageId, mesButtons, true); if (checkpointBtn) { checkpointBtn.classList.add('rpg-checkpoint-button-expanded'); - mesButtons.appendChild(checkpointBtn); + + // 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); + } } } + /** * Update the checkpoint button in an existing menu * @param {HTMLElement} menu - The extraMesButtons or mes_buttons container @@ -316,7 +347,7 @@ function processExpandedButton(messageBlock) { function updateCheckpointButtonInMenu(menu, messageId) { 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'); if (!existingButton) return; diff --git a/src/systems/ui/layout.js b/src/systems/ui/layout.js index 089cd71..6916169 100644 --- a/src/systems/ui/layout.js +++ b/src/systems/ui/layout.js @@ -9,7 +9,8 @@ import { $userStatsContainer, $infoBoxContainer, $thoughtsContainer, - $inventoryContainer + $inventoryContainer, + $questsContainer } from '../../core/state.js'; import { i18n } from '../../core/i18n.js'; @@ -230,22 +231,29 @@ export function updateSectionVisibility() { if ($inventoryContainer) { $inventoryContainer.toggle(extensionSettings.showInventory); } + if ($questsContainer) { + $questsContainer.toggle(extensionSettings.showQuests); + } // Show/hide dividers intelligently // Divider after User Stats: shown if User Stats is visible AND at least one section after it is visible const showDividerAfterStats = extensionSettings.showUserStats && - (extensionSettings.showInfoBox || extensionSettings.showCharacterThoughts || extensionSettings.showInventory); + (extensionSettings.showInfoBox || extensionSettings.showCharacterThoughts || extensionSettings.showInventory || extensionSettings.showQuests); $('#rpg-divider-stats').toggle(showDividerAfterStats); // Divider after Info Box: shown if Info Box is visible AND at least one section after it is visible const showDividerAfterInfo = extensionSettings.showInfoBox && - (extensionSettings.showCharacterThoughts || extensionSettings.showInventory); + (extensionSettings.showCharacterThoughts || extensionSettings.showInventory || extensionSettings.showQuests); $('#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 && - extensionSettings.showInventory; + (extensionSettings.showInventory || extensionSettings.showQuests); $('#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); } /** diff --git a/style.css b/style.css index b57b4e5..a534144 100644 --- a/style.css +++ b/style.css @@ -1143,6 +1143,8 @@ body:has(.rpg-panel.rpg-position-left) #sheld { gap: 0.25em; flex: 1; min-height: 0; + overflow-x: auto; + overflow-y: hidden; } /* Row 1: 4 equal-width widgets - more square aspect ratio */ diff --git a/template.html b/template.html index 47f3021..9fb899d 100644 --- a/template.html +++ b/template.html @@ -53,6 +53,9 @@ + +
+
@@ -182,6 +185,11 @@ Show Inventory + +