Files
ComfyUI-Lora-Manager/static/js/managers/HelpManager.js
T
Will Miao 03569c62df feat(ui): point help new-content indicator at the updated tabs and elements
- 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)
2026-09-03 21:40:18 +08:00

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();