Fix chapter checkpoint button duplication issue

This commit is contained in:
Spicy_Marinara
2025-12-22 00:27:25 +01:00
parent 9d8b758317
commit d386752f9c
+83 -30
View File
@@ -15,15 +15,23 @@ import {
* Adds the chapter checkpoint button to a message's extra menu * Adds the chapter checkpoint button to a message's extra menu
* @param {number} messageId - The message index * @param {number} messageId - The message index
* @param {HTMLElement} menu - The message menu element * @param {HTMLElement} menu - The message menu element
* @param {boolean} isExpanded - Whether this is for expanded message actions
*/ */
export function addCheckpointButtonToMessage(messageId, menu) { export function addCheckpointButtonToMessage(messageId, menu, isExpanded = false) {
if (!menu) return; if (!menu) return;
const isCheckpoint = isCheckpointMessage(messageId); const isCheckpoint = isCheckpointMessage(messageId);
// Create the menu item // Create the menu item
const menuItem = document.createElement('div'); const menuItem = document.createElement('div');
menuItem.className = 'extraMesButtonsHint list-group-item flex-container flexGap5'; // Use different classes for expanded vs dropdown menu
if (isExpanded) {
menuItem.className = 'mes_button';
menuItem.setAttribute('tabindex', '0');
} else {
menuItem.className = 'extraMesButtonsHint list-group-item flex-container flexGap5';
}
const translationKey = isCheckpoint ? 'checkpoint.clearChapterStart' : 'checkpoint.setChapterStart'; const translationKey = isCheckpoint ? 'checkpoint.clearChapterStart' : 'checkpoint.setChapterStart';
menuItem.setAttribute('data-i18n', translationKey); menuItem.setAttribute('data-i18n', translationKey);
menuItem.title = isCheckpoint menuItem.title = isCheckpoint
@@ -111,11 +119,6 @@ export function updateAllCheckpointIndicators() {
if (!chat) return; if (!chat) return;
// Clear all processed flags so buttons can be updated
document.querySelectorAll('.extraMesButtons[data-checkpoint-processed]').forEach(menu => {
delete menu.dataset.checkpointProcessed;
});
// Update all message blocks // Update all message blocks
const messageBlocks = document.querySelectorAll('.mes'); const messageBlocks = document.querySelectorAll('.mes');
messageBlocks.forEach((block) => { messageBlocks.forEach((block) => {
@@ -126,10 +129,16 @@ export function updateAllCheckpointIndicators() {
addCheckpointIndicator(messageId, block); addCheckpointIndicator(messageId, block);
// Also update any open menus for this message // Update any existing dropdown menu button for this message
const menu = block.querySelector('.extraMesButtons'); const dropdownMenu = block.querySelector('.extraMesButtons');
if (menu) { if (dropdownMenu) {
updateCheckpointButtonInMenu(menu, messageId); updateCheckpointButtonInMenu(dropdownMenu, messageId);
}
// Update any existing expanded button for this message
const mesButtons = block.querySelector('.mes_buttons');
if (mesButtons) {
updateCheckpointButtonInMenu(mesButtons, messageId);
} }
}); });
} }
@@ -185,21 +194,29 @@ export function initChapterCheckpointUI() {
* This should be called when SillyTavern renders message menus * This should be called when SillyTavern renders message menus
*/ */
export function injectCheckpointButton() { export function injectCheckpointButton() {
// Direct approach: Hook into when extraMesButtons elements appear or are populated // Observer for dropdown menus and message blocks
const observer = new MutationObserver((mutations) => { const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => { mutations.forEach((mutation) => {
// Check for added nodes // Check for added nodes
mutation.addedNodes.forEach((node) => { mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) { if (node.nodeType === Node.ELEMENT_NODE) {
// Check if extraMesButtons container was added // Check if extraMesButtons container was added (dropdown menu)
if (node.classList && node.classList.contains('extraMesButtons')) { if (node.classList && node.classList.contains('extraMesButtons')) {
processExtraMesButtons(node); processExtraMesButtons(node);
} }
// Also check if extraMesButtons exists within added subtree // Check if message block was added (for expanded mode)
if (node.classList && node.classList.contains('mes')) {
processExpandedButton(node);
}
// Also check if either exists 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);
const messageBlocks = node.querySelectorAll('.mes');
messageBlocks.forEach(processExpandedButton);
} }
} }
}); });
@@ -220,14 +237,17 @@ export function injectCheckpointButton() {
subtree: true subtree: true
}); });
// Process any existing menus on initialization // Process any existing menus and messages on initialization
const existingMenus = chatContainer.querySelectorAll('.extraMesButtons'); const existingDropdownMenus = chatContainer.querySelectorAll('.extraMesButtons');
existingMenus.forEach(processExtraMesButtons); existingDropdownMenus.forEach(processExtraMesButtons);
const existingMessages = chatContainer.querySelectorAll('.mes');
existingMessages.forEach(processExpandedButton);
} }
} }
/** /**
* Process an extraMesButtons container to add checkpoint button * Process an extraMesButtons container to add checkpoint button (dropdown menu)
* @param {HTMLElement} menu - The extraMesButtons container * @param {HTMLElement} menu - The extraMesButtons container
*/ */
function processExtraMesButtons(menu) { function processExtraMesButtons(menu) {
@@ -237,34 +257,67 @@ 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 button already exists // Check if button already exists in this container
if (!menu.dataset.checkpointProcessed) { if (menu.querySelector('.rpg-checkpoint-button')) return;
// Mark as processed
menu.dataset.checkpointProcessed = 'true';
// Add checkpoint button // Add checkpoint button for dropdown menu
const checkpointBtn = addCheckpointButtonToMessage(messageId, menu); const checkpointBtn = addCheckpointButtonToMessage(messageId, menu, false);
if (checkpointBtn) { if (checkpointBtn) {
checkpointBtn.classList.add('rpg-checkpoint-button'); checkpointBtn.classList.add('rpg-checkpoint-button');
menu.appendChild(checkpointBtn); menu.appendChild(checkpointBtn);
} }
}
/**
* Process message to add expanded checkpoint button
* @param {HTMLElement} messageBlock - The message block element
*/
function processExpandedButton(messageBlock) {
if (!messageBlock) return;
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;
}
const messageId = Number(messageBlock.getAttribute('mesid'));
if (isNaN(messageId)) return;
// Check if button already exists in this container
if (mesButtons.querySelector('.rpg-checkpoint-button-expanded')) return;
// Add checkpoint button as separate mes_button
const checkpointBtn = addCheckpointButtonToMessage(messageId, mesButtons, true);
if (checkpointBtn) {
checkpointBtn.classList.add('rpg-checkpoint-button-expanded');
mesButtons.appendChild(checkpointBtn);
} }
} }
/** /**
* Update the checkpoint button in an existing menu * Update the checkpoint button in an existing menu
* @param {HTMLElement} menu - The extraMesButtons container * @param {HTMLElement} menu - The extraMesButtons or mes_buttons container
* @param {number} messageId - The message index * @param {number} messageId - The message index
*/ */
function updateCheckpointButtonInMenu(menu, messageId) { function updateCheckpointButtonInMenu(menu, messageId) {
if (!menu) return; if (!menu) return;
const existingButton = menu.querySelector('.rpg-checkpoint-button'); // Check for both button classes (dropdown and expanded)
const existingButton = menu.querySelector('.rpg-checkpoint-button, .rpg-checkpoint-button-expanded');
if (!existingButton) return; if (!existingButton) return;
const isCheckpoint = isCheckpointMessage(messageId); const isCheckpoint = isCheckpointMessage(messageId);