mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-21 19:21:27 -03:00
- Replace timestamp comparison (help_last_viewed vs a hardcoded date) with a content-version marker (data-help-content-version) read from the rendered modal markup, so badge state always reflects the content actually served - Only mark content as viewed when the modal is opened while it contains new content; opening a stale pre-upgrade page no longer suppresses the badge after a refresh - Flag the Replay Tutorial button itself with a 'New' chip (hidden by default, one-time glow animation) and scroll it into view when revealed; tab-level dots now mark getting-started and shortcuts instead of documentation - Translate help.newContentBadge into all 9 locales, reusing the established help.documentation.newBadge renderings - Add HelpManager content-version unit tests (12 cases)
228 lines
7.8 KiB
JavaScript
228 lines
7.8 KiB
JavaScript
import { getStorageItem, setStorageItem } from '../utils/storageHelpers.js';
|
|
import { onboardingManager } from './OnboardingManager.js';
|
|
|
|
/**
|
|
* Manages help modal functionality and tutorial update notifications
|
|
*/
|
|
export class HelpManager {
|
|
constructor() {
|
|
// Version of the help content the user has seen. Compared against the
|
|
// data-help-content-version marker rendered into the help modal markup,
|
|
// so badge state is always derived from the content actually served.
|
|
this.viewedContentVersion = getStorageItem('help_viewed_content_version', null);
|
|
this.isInitialized = false;
|
|
}
|
|
|
|
/**
|
|
* Initialize the help manager
|
|
*/
|
|
initialize() {
|
|
if (this.isInitialized) return;
|
|
|
|
console.log('HelpManager: Initializing...');
|
|
|
|
// Set up event handlers
|
|
this.setupEventListeners();
|
|
|
|
// Check if we need to show the badge
|
|
this.updateHelpBadge();
|
|
|
|
this.isInitialized = true;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Set up event listeners for help modal
|
|
*/
|
|
setupEventListeners() {
|
|
// Help toggle button
|
|
const helpToggleBtn = document.getElementById('helpToggleBtn');
|
|
if (helpToggleBtn) {
|
|
helpToggleBtn.addEventListener('click', () => this.openHelpModal());
|
|
}
|
|
|
|
// Help modal tab functionality
|
|
const tabButtons = document.querySelectorAll('.help-tabs .tab-btn');
|
|
tabButtons.forEach(button => {
|
|
button.addEventListener('click', (event) => {
|
|
this.activateHelpTab(event.currentTarget.getAttribute('data-tab'));
|
|
});
|
|
});
|
|
|
|
// Replay tutorial button in the Getting Started tab
|
|
const replayTutorialBtn = document.getElementById('replayTutorialBtn');
|
|
if (replayTutorialBtn) {
|
|
replayTutorialBtn.addEventListener('click', () => {
|
|
// Close the help modal, then restart the onboarding tutorial
|
|
if (window.modalManager) {
|
|
window.modalManager.closeModal('helpModal');
|
|
}
|
|
onboardingManager.reset();
|
|
onboardingManager.startTutorial();
|
|
});
|
|
}
|
|
|
|
// Global "?" shortcut opens the help modal on the Shortcuts tab
|
|
document.addEventListener('keydown', (event) => {
|
|
if (event.key !== '?') return;
|
|
if (this.isTypingContext(event.target)) return;
|
|
if (window.modalManager?.isAnyModalOpen()) return;
|
|
|
|
event.preventDefault();
|
|
this.openHelpModal('shortcuts');
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Check if the event target is a text entry context where "?" is literal input
|
|
*/
|
|
isTypingContext(target) {
|
|
if (!(target instanceof Element)) return false;
|
|
|
|
const tagName = target.tagName?.toLowerCase();
|
|
return target.isContentEditable || tagName === 'input' || tagName === 'textarea' || tagName === 'select';
|
|
}
|
|
|
|
/**
|
|
* Activate a specific help modal tab by its data-tab id
|
|
* @param {string} tabId - The tab id (matches data-tab and pane element id)
|
|
*/
|
|
activateHelpTab(tabId) {
|
|
const tabButton = document.querySelector(`.help-tabs .tab-btn[data-tab="${tabId}"]`);
|
|
const tabPane = document.getElementById(tabId);
|
|
if (!tabButton || !tabPane) return;
|
|
|
|
// Remove active class from all buttons and panes
|
|
document.querySelectorAll('.help-tabs .tab-btn').forEach(btn => {
|
|
btn.classList.remove('active');
|
|
});
|
|
document.querySelectorAll('.help-content .tab-pane').forEach(pane => {
|
|
pane.classList.remove('active');
|
|
});
|
|
|
|
// Activate the requested tab
|
|
tabButton.classList.add('active');
|
|
tabPane.classList.add('active');
|
|
}
|
|
|
|
/**
|
|
* Open the help modal
|
|
* @param {string} [tabId] - Optional tab id to activate after opening
|
|
*/
|
|
openHelpModal(tabId) {
|
|
// Use modalManager to open the help modal
|
|
if (!window.modalManager) return;
|
|
|
|
const hadNewContent = this.hasNewContent();
|
|
|
|
window.modalManager.toggleModal('helpModal');
|
|
|
|
if (tabId) {
|
|
this.activateHelpTab(tabId);
|
|
}
|
|
|
|
// Only acknowledge the content as viewed when the user opened the
|
|
// modal while it actually contained new content. Opening a stale
|
|
// (pre-upgrade) page must not suppress the badge after a refresh.
|
|
if (hadNewContent) {
|
|
this.updateNewContentTabIndicators();
|
|
this.markContentAsViewed();
|
|
}
|
|
|
|
// Hide the badge
|
|
this.hideHelpBadge();
|
|
}
|
|
|
|
/**
|
|
* Add visual indicator to tabs that received new content
|
|
*/
|
|
updateNewContentTabIndicators() {
|
|
if (!this.hasNewContent()) return;
|
|
|
|
// Tabs updated in the 2026-09-03 discoverability release:
|
|
// getting-started (Replay Tutorial button) and shortcuts (new cheat-sheet tab)
|
|
const NEW_CONTENT_TABS = ['getting-started', 'shortcuts'];
|
|
NEW_CONTENT_TABS.forEach(tabId => {
|
|
const tab = document.querySelector(`.help-tabs .tab-btn[data-tab="${tabId}"]`);
|
|
if (tab) {
|
|
tab.classList.add('has-new-content');
|
|
}
|
|
});
|
|
|
|
// Point the indicator at the specific new element inside the
|
|
// Getting Started tab, and scroll it into view so it is not lost
|
|
// below the fold of the modal body.
|
|
const replayBtn = document.getElementById('replayTutorialBtn');
|
|
if (replayBtn) {
|
|
replayBtn.classList.add('has-new-content');
|
|
const gettingStartedActive = document.querySelector('#getting-started.tab-pane.active');
|
|
if (gettingStartedActive && typeof replayBtn.scrollIntoView === 'function') {
|
|
replayBtn.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Mark content as viewed by persisting the version rendered in the DOM.
|
|
* No-op when the served markup carries no version marker (stale assets),
|
|
* so viewing old content never suppresses the badge for new content.
|
|
*/
|
|
markContentAsViewed() {
|
|
const currentVersion = this.getCurrentContentVersion();
|
|
if (!currentVersion) return;
|
|
|
|
this.viewedContentVersion = currentVersion;
|
|
setStorageItem('help_viewed_content_version', this.viewedContentVersion);
|
|
}
|
|
|
|
/**
|
|
* Read the help content version from the rendered modal markup
|
|
* @returns {string|null} Version marker, or null if the served markup has none
|
|
*/
|
|
getCurrentContentVersion() {
|
|
const marker = document.querySelector('[data-help-content-version]');
|
|
return marker ? marker.getAttribute('data-help-content-version') : null;
|
|
}
|
|
|
|
/**
|
|
* Update help badge visibility based on viewed vs. served content version
|
|
*/
|
|
updateHelpBadge() {
|
|
if (this.hasNewContent()) {
|
|
this.showHelpBadge();
|
|
} else {
|
|
this.hideHelpBadge();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the served help content is newer than what the user has viewed
|
|
*/
|
|
hasNewContent() {
|
|
const currentVersion = this.getCurrentContentVersion();
|
|
return Boolean(currentVersion) && currentVersion !== this.viewedContentVersion;
|
|
}
|
|
|
|
/**
|
|
* Show the help badge
|
|
*/
|
|
showHelpBadge() {
|
|
const helpBadge = document.querySelector('#helpToggleBtn .update-badge');
|
|
if (helpBadge) {
|
|
helpBadge.classList.add('visible');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hide the help badge
|
|
*/
|
|
hideHelpBadge() {
|
|
const helpBadge = document.querySelector('#helpToggleBtn .update-badge');
|
|
if (helpBadge) {
|
|
helpBadge.classList.remove('visible');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create singleton instance
|
|
export const helpManager = new HelpManager(); |