refactor(slash-commands): 优化 /goto-floor 命令并添加高亮功能

- 重新组织消息加载和滚动逻辑,提高命令成功率
- 添加消息元素高亮功能,使用 flashHighlight 或临时 CSS 类
This commit is contained in:
awaae001
2025-04-23 01:04:52 +08:00
parent ceeaeea123
commit ee11f021eb

View File

@ -1,4 +1,5 @@
import { Fuse, DOMPurify } from '../lib.js'; import { Fuse, DOMPurify } from '../lib.js';
import { flashHighlight } from './utils.js';
import { import {
Generate, Generate,
@ -2134,64 +2135,67 @@ export function initDefaultSlashCommands() {
name: 'goto-floor', name: 'goto-floor',
aliases: ['floor', 'jump', 'scrollto'], aliases: ['floor', 'jump', 'scrollto'],
callback: async (_, index) => { callback: async (_, index) => {
// --- Load all messages first to ensure the target element exists ---
console.log(`INFO: Loading all messages before attempting to goto-floor ${index}.`);
await showMoreMessages(Number.MAX_SAFE_INTEGER);
console.log(`INFO: All messages loaded (or loading initiated).`);
// --- End of loading step ---
const floorIndex = Number(index); const floorIndex = Number(index);
// Validate input const chatLength = typeof chat !== 'undefined' ? chat.length : -1; // Use -1 if chat is undefined to avoid errors
if (isNaN(floorIndex) || floorIndex < 0 || (typeof chat !== 'undefined' && floorIndex >= chat.length)) { if (isNaN(floorIndex) || floorIndex < 0 || (chatLength !== -1 && floorIndex >= chatLength)) {
const maxIndex = (typeof chat !== 'undefined' ? chat.length - 1 : 'unknown'); const maxIndex = (chatLength !== -1 ? chatLength - 1 : 'unknown');
toastr.warning(`Invalid message index: ${index}. Please enter a number between 0 and ${maxIndex}.`); toastr.warning(`Invalid message index: ${index}. Please enter a number between 0 and ${maxIndex}.`);
console.warn(`WARN: Invalid message index provided for /goto-floor: ${index}. Max index: ${maxIndex}`); console.warn(`WARN: Invalid message index provided for /goto-floor: ${index}. Max index: ${maxIndex}`);
return ''; return '';
} }
// Give the rendering a moment to potentially catch up after showMoreMessages // --- Load all messages first to ensure the target element exists ---
// This might be necessary depending on how showMoreMessages works internally console.log(`INFO: Attempting to load all messages before attempting to goto-floor ${index}.`);
await new Promise(resolve => setTimeout(resolve, 100)); // Adjust delay if needed try {
// Assuming showMoreMessages is available globally or within scope
await showMoreMessages(Number.MAX_SAFE_INTEGER);
console.log(`INFO: All messages loaded (or loading initiated).`);
// Give the rendering a moment to potentially catch up after showMoreMessages
await new Promise(resolve => setTimeout(resolve, 100)); // Adjust delay if needed
} catch (error) {
console.error('Error loading messages:', error);
toastr.error('An error occurred while trying to load messages.');
return ''; // Exit if loading fails
}
// --- End of loading step ---
const messageElement = document.querySelector(`[mesid="${floorIndex}"]`); const messageElement = document.querySelector(`[mesid="${floorIndex}"]`);
if (messageElement) { if (messageElement) {
const headerElement = messageElement.querySelector('.mes_header') || // --- Corrected: Use the actual class from the template ---
messageElement.querySelector('.mes_meta') || const headerElement = messageElement.querySelector('.ch_name');
messageElement.querySelector('.mes_name_area') || const elementToScroll = headerElement || messageElement; // Fallback to the entire message div
messageElement.querySelector('.mes_name'); const blockPosition = headerElement ? 'center' : 'start';
const elementToScroll = headerElement || messageElement; // Prefer header, fallback to whole message
const blockPosition = headerElement ? 'center' : 'start'; // Center header, else start of message
elementToScroll.scrollIntoView({ behavior: 'smooth', block: blockPosition }); elementToScroll.scrollIntoView({ behavior: 'smooth', block: blockPosition });
console.log(`INFO: Scrolled ${headerElement ? 'header of' : ''} message ${floorIndex} into view (block: ${blockPosition}).`);
// --- Highlight with smooth animation --- // Highlight the message element
messageElement.classList.add('highlight-scroll'); if (messageElement instanceof HTMLElement) {
setTimeout(() => { if (typeof $ !== 'undefined') { // Check if jQuery is available
// Add a class to fade out, then remove the highlight class after fade flashHighlight($(messageElement), 1500);
messageElement.classList.add('highlight-scroll-fadeout'); } else {
// Wait for fadeout transition to complete before removing the base class console.warn('jQuery not available, cannot use flashHighlight.');
// Match this duration to the transition duration in CSS // Optional: Add a temporary CSS class highlight if jQuery/flashHighlight is missing
setTimeout(() => { messageElement.style.transition = 'background-color 0.5s ease';
messageElement.classList.remove('highlight-scroll', 'highlight-scroll-fadeout'); messageElement.style.backgroundColor = 'yellow'; // Or some highlight color
}, 500); // Matches the 0.5s transition duration setTimeout(() => {
}, 1500); // Start fade out after 1.5 seconds messageElement.style.backgroundColor = ''; // Remove highlight
}, 1500); // Match flash duration
}
} else {
console.warn('Message element is not an HTMLElement, cannot flash highlight.');
}
} else { } else {
// This case is less likely now after showMoreMessages, but still possible // Only warn if element is not found *after* attempting to load all messages
// if the element hasn't been added to the DOM yet for some reason after loading. toastr.warning(`Could not find element for message ${floorIndex} (using [mesid="${floorIndex}"]) even after attempting to load all messages. It might not be rendered yet or the index is invalid.`);
toastr.warning(`Could not find element for message ${floorIndex} (using [mesid="${floorIndex}"]) even after attempting to load all messages. It might not be rendered yet. Try scrolling up or use /chat-render all again if issues persist.`);
console.warn(`WARN: Element not found for message index ${floorIndex} using querySelector [mesid="${floorIndex}"] in /goto-floor, even after attempting to load all messages.`); console.warn(`WARN: Element not found for message index ${floorIndex} using querySelector [mesid="${floorIndex}"] in /goto-floor, even after attempting to load all messages.`);
const chatContainer = document.getElementById('chat'); // Do NOT scroll the chat container in this case
if (chatContainer) {
chatContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
} }
return ''; return ''; // Return empty string as expected by some slash command parsers
}, },
unnamedArgumentList: [ unnamedArgumentList: [
SlashCommandArgument.fromProps({ SlashCommandArgument.fromProps({
@ -2204,8 +2208,9 @@ export function initDefaultSlashCommands() {
helpString: ` helpString: `
<div> <div>
Scrolls the chat view to the specified message index. Uses the <code>[mesid]</code> attribute for locating the message element. Index starts at 0. Scrolls the chat view to the specified message index. Uses the <code>[mesid]</code> attribute for locating the message element. Index starts at 0.
It attempts to center the character's name/header area within the message block. Highlights the message using the theme's 'matchedText' color with a smooth animation. It attempts to center the character's name/header area within the message block by targeting the <code>.ch_name</code> element. Highlights the message using a flash animation.
Automatically attempts to load all messages before scrolling to improve success rate, addressing issues with lazy loading. Automatically attempts to load all messages before scrolling to improve success rate, addressing issues with lazy loading.
A warning is displayed if the message element cannot be located even after attempting to load all messages.
</div> </div>
<div> <div>
<strong>Example:</strong> <pre><code>/goto-floor 10</code></pre> Scrolls to the 11th message (mesid=10). <strong>Example:</strong> <pre><code>/goto-floor 10</code></pre> Scrolls to the 11th message (mesid=10).
@ -2213,55 +2218,11 @@ export function initDefaultSlashCommands() {
`, `,
})); }));
// --- Improved CSS for highlight ---
const styleId = 'goto-floor-highlight-style'; const styleId = 'goto-floor-highlight-style';
if (!document.getElementById(styleId)) { if (document.getElementById(styleId)) {
const style = document.createElement('style'); document.getElementById(styleId).remove();
style.id = styleId;
style.textContent = `
/* Base state for elements that *can* be highlighted */
/* Ensures transition applies smoothly in both directions */
.mes, .mes_block {
transition: background-color 0.5s ease-in-out, box-shadow 0.5s ease-in-out;
/* Add position relative if not already present, needed for potential pseudo-elements */
position: relative;
}
/* --- Highlighting Style --- */
.mes.highlight-scroll,
.mes_block.highlight-scroll {
/* Use theme color for shadow, fallback to gold */
box-shadow: 0 0 10px var(--ac-style-color-matchedText, #FFD700) !important;
z-index: 5; /* Ensure highlight is visually prominent */
}
/* Modern browsers: Use color-mix for transparent background */
@supports (background-color: color-mix(in srgb, white 50%, black)) {
.mes.highlight-scroll,
.mes_block.highlight-scroll {
/* Mix theme color with transparent for background */
background-color: color-mix(in srgb, var(--ac-style-color-matchedText, #FFD700) 35%, transparent) !important;
}
}
/* Fallback for older browsers: Use a fixed semi-transparent background */
@supports not (background-color: color-mix(in srgb, white 50%, black)) {
.mes.highlight-scroll,
.mes_block.highlight-scroll {
background-color: rgba(255, 215, 0, 0.35) !important; /* Fallback semi-transparent gold */
}
}
/* --- Fade-out Control --- */
/* When fading out, explicitly transition back to transparent/none */
.mes.highlight-scroll-fadeout,
.mes_block.highlight-scroll-fadeout {
background-color: transparent !important;
box-shadow: none !important;
}
`;
document.head.appendChild(style);
} }
registerVariableCommands(); registerVariableCommands();
} }