Fix TTS module initialization and dependency issues. Update module IDs for consistency, improve circular dependency detection, and fix UI Controller event handling.

This commit is contained in:
2025-04-04 19:15:28 +00:00
parent 02c7b9ef28
commit 49a5af252c
33 changed files with 7227 additions and 4060 deletions
+228 -220
View File
@@ -4,40 +4,118 @@
*/
import { BaseModule } from './base-module.js';
import { moduleRegistry } from './module-registry.js';
import Hyphenopoly from './hyphenopoly.module.js';
class TextProcessorModule extends BaseModule {
constructor() {
super('text-processor', 'Text Processor');
this.smartyPants = null; // Store the function reference here
this.smartypantsu = null; // Store the function reference here
this.hyphenator = null; // For hyphenation function
this.smartyPants = null;
this.smartypantsu = null;
this.hyphenator = null;
this.hyphenatorReady = false;
this.locale = 'en-us';
// Bind methods using parent's bindMethods utility
this.bindMethods([
'loadSmartyPantsScript',
'initializeHyphenation',
'process',
'isHyphenationAvailable',
'hyphenate',
'setLocale',
'handleLocaleChanged'
]);
// Add localization as a dependency
this.dependencies = ['localization'];
}
/**
* Load module dependencies
* Initialize the module
* @returns {Promise<boolean>} - Resolves with success status
*/
async loadDependencies() {
async initialize() {
try {
this.reportProgress(10, "Loading dependencies");
this.reportProgress(10, "Initializing text processor");
// Load SmartyPants script dynamically
await this.loadSmartyPantsScript();
this.reportProgress(50, "SmartyPants loaded");
// Get locale from Localization module if available
const localizationModule = this.getModule('localization');
if (!localizationModule) {
console.error("Localization module not found, required dependency missing");
this.reportProgress(100, "Text processor initialization failed - missing localization");
return false;
}
this.locale = localizationModule.getLocale();
// Register for locale changes using the proper event pattern
this.addEventListener(document, 'locale-changed', this.handleLocaleChanged);
this.reportProgress(30, `Locale set to ${this.locale}`);
// Initialize hyphenation in the background, but don't wait for it
this.initializeHyphenation();
this.reportProgress(90, "Dependencies loaded");
return true;
// Ensure global locale is set for SmartyPants
window.locale = this.locale;
// Load SmartyPants - critical dependency
this.reportProgress(40, "Loading SmartyPants");
try {
await this.loadSmartyPantsScript();
// Verify SmartyPants is properly loaded
if (!this.smartyPants || typeof this.smartyPants !== 'function') {
throw new Error('SmartyPants not properly loaded');
}
this.reportProgress(70, "SmartyPants loaded successfully");
} catch (error) {
console.error("Failed to load SmartyPants:", error);
this.reportProgress(100, "Text processor initialization failed - SmartyPants not available");
return false;
}
// Initialize hyphenation (non-critical)
this.reportProgress(80, "Initializing hyphenation");
try {
await this.initializeHyphenation();
this.reportProgress(100, "Text processor ready");
return true;
} catch (error) {
console.warn("Failed to initialize hyphenation:", error);
// Continue without hyphenation, still mark as successful
this.reportProgress(100, "Text processor ready (without hyphenation)");
return true;
}
} catch (error) {
console.error("Error loading Text Processor dependencies:", error);
console.error("Error initializing text processor:", error);
this.reportProgress(100, "Text processor initialization failed");
return false;
}
}
/**
* Handle locale changed event
* @param {CustomEvent} event - The locale-changed event
*/
handleLocaleChanged(event) {
if (event && event.detail && event.detail.locale) {
this.setLocale(event.detail.locale);
}
}
/**
* Set the locale for the text processor
* @param {string} locale - The locale to set
*/
setLocale(locale) {
this.locale = locale;
console.log(`Text processor locale set to ${locale}`);
// Reinitialize hyphenation with new locale if needed
if (this.hyphenatorReady) {
this.initializeHyphenation();
}
}
/**
* Load the SmartyPants script dynamically and wait for it to be ready
* @returns {Promise<void>}
@@ -52,233 +130,146 @@ class TextProcessorModule extends BaseModule {
resolve();
return;
}
// Load the script using a script tag
// Create script element
const script = document.createElement('script');
script.src = '/js/smartypants.js';
script.async = false; // Load synchronously relative to other scripts
script.type = 'text/javascript';
script.src = '/js/smartypants.js'; // Use relative URL
script.async = true;
// Set up load and error handlers
script.onload = () => {
// Use a microtask to ensure the script has executed
Promise.resolve().then(() => {
if (typeof window.SmartyPants === 'object' && typeof window.SmartyPants.smartypants === 'function') {
this.smartyPants = window.SmartyPants.smartypants;
this.smartypantsu = window.SmartyPants.smartypantsu;
console.log("SmartyPants loaded successfully via script tag");
resolve();
} else {
console.error("SmartyPants script loaded but functions not found on window.SmartyPants");
reject(new Error('SmartyPants functions not found after loading'));
}
});
if (typeof window.SmartyPants === 'object' && typeof window.SmartyPants.smartypants === 'function') {
this.smartyPants = window.SmartyPants.smartypants;
this.smartypantsu = window.SmartyPants.smartypantsu;
console.log("SmartyPants loaded successfully");
resolve();
} else {
const error = new Error('SmartyPants loaded but functions not found');
console.error(error);
reject(error);
}
};
script.onerror = () => {
console.error('Failed to load smartypants.js script');
reject(new Error('Failed to load smartypants.js script'));
script.onerror = (error) => {
console.error("Error loading SmartyPants script:", error);
reject(new Error('Failed to load SmartyPants script'));
};
// Add script to document
document.head.appendChild(script);
});
}
/**
* Initialize the module
* @returns {Promise<boolean>} - Resolves with success status
*/
async initialize() {
try {
this.reportProgress(70, "Initializing text processor");
// Get locale from Localization module if available
const localizationModule = moduleRegistry.getModule('localization');
if (localizationModule) {
this.locale = localizationModule.getLocale();
// Register as an observer for locale changes
localizationModule.registerObserver(this, (newLocale) => {
this.setLocale(newLocale);
});
}
// Ensure global locale is set for SmartyPants
window.locale = this.locale;
// Verify SmartyPants is available via the stored references
if (typeof this.smartyPants !== 'function') {
console.error("SmartyPants function not available for initialization");
return false;
}
// Final initialization steps
this.reportProgress(100, "Text processor ready");
return true;
} catch (error) {
console.error("Error initializing Text Processor:", error);
return false;
}
}
/**
* Initialize hyphenation using Hyphenopoly
* Initialize hyphenation using Hyphenopoly module
* @returns {Promise<boolean>} - Resolves when hyphenation is initialized
*/
initializeHyphenation() {
// Create custom events for hyphenation loading status
const hyphenationLoadedEvent = new CustomEvent('hyphenation-loaded');
// Add listener for hyphenation loaded event
document.addEventListener('hyphenation-loaded', () => {
console.log('Hyphenation module loaded');
this.hyphenatorReady = true;
}, { once: true });
// Check if Hyphenopoly is loaded
if (window.Hyphenopoly) {
this.setupHyphenopoly();
} else {
// Set up listener for when Hyphenopoly might be loaded later
window.addEventListener('hyphenopoly-loaded', () => {
this.setupHyphenopoly();
});
// Try loading Hyphenopoly if not already loading
if (!document.querySelector('script[src*="Hyphenopoly_Loader.js"]')) {
this.loadHyphenopolyScript();
}
}
}
/**
* Load the Hyphenopoly script
*/
loadHyphenopolyScript() {
// Create script element for loader
const script = document.createElement('script');
script.src = '/js/Hyphenopoly_Loader.js';
script.async = true;
script.onload = () => {
document.dispatchEvent(new CustomEvent('hyphenopoly-script-loaded'));
};
script.onerror = (error) => {
console.error('Failed to load Hyphenopoly:', error);
document.dispatchEvent(new CustomEvent('hyphenation-error', {
detail: { error: 'Failed to load Hyphenopoly script' }
}));
};
document.head.appendChild(script);
// Set up configuration for Hyphenopoly
window.Hyphenopoly = {
require: {
'en-us': 'FORCEHYPHENATION'
},
paths: {
maindir: '/js/',
patterndir: '/js/patterns/'
},
setup: {
selectors: {
'.hyphenate': {}
}
}
};
}
/**
* Set up Hyphenopoly when it's available
*/
setupHyphenopoly() {
// Wait for hyphenator to be available
if (window.Hyphenopoly && window.Hyphenopoly.hyphenators) {
// Get hyphenator for English
window.Hyphenopoly.hyphenators['en-us'].then((hyphenator) => {
console.log('Hyphenator ready');
this.hyphenator = hyphenator;
this.hyphenatorReady = true;
// Dispatch event that hyphenation is ready
document.dispatchEvent(new CustomEvent('hyphenation-loaded'));
}).catch(err => {
console.error('Error loading hyphenator:', err);
});
}
}
/**
* Set the hyphenator function
* @param {Function} hyphenatorFunc - The hyphenator function
*/
setHyphenator(hyphenatorFunc) {
if (typeof hyphenatorFunc === 'function') {
this.hyphenator = hyphenatorFunc;
this.hyphenatorReady = true;
console.log("Hyphenator function set explicitly");
} else {
console.warn("Invalid hyphenator provided");
}
}
/**
* Process text with SmartyPants and optional hyphenation
* @param {string} text - The text to process
* @param {boolean} useHyphenation - Whether to apply hyphenation
* @returns {string} - The processed text
*/
process(text, useHyphenation = false) {
if (!text) return '';
let processed = text;
// Apply SmartyPants for typographic punctuation using stored references
try {
if (typeof this.smartyPants === 'function') {
processed = this.smartyPants(processed);
} else {
console.warn("SmartyPants function not available for processing");
}
// Convert HTML entities to UTF-8 characters
if (typeof this.smartypantsu === 'function') {
processed = this.smartypantsu(processed);
} else {
console.warn("smartypantsu function not available for processing");
}
} catch (error) {
console.error("Error applying SmartyPants:", error);
}
// Apply hyphenation if enabled and available
if (useHyphenation && this.hyphenatorReady && this.hyphenator) {
return new Promise((resolve, reject) => {
try {
processed = this.hyphenator(processed);
console.log("Initializing hyphenation with Hyphenopoly module");
// Configure Hyphenopoly with our requirements
const hyphenatorPromise = Hyphenopoly.config({
require: [this.locale],
hyphen: '\u00AD', // Soft hyphen character
minWordLength: 5,
leftmin: 2,
rightmin: 2,
compound: "hyphen",
// Define a custom loader for the patterns
loader: (file) => {
return new Promise((resolve, reject) => {
const patternPath = `/js/patterns/${file}`;
console.log(`Loading hyphenation pattern: ${patternPath}`);
fetch(patternPath)
.then(response => {
if (!response.ok) {
throw new Error(`Failed to load ${file}: ${response.status} ${response.statusText}`);
}
return response.arrayBuffer();
})
.then(arrayBuffer => {
resolve(arrayBuffer);
})
.catch(error => {
console.error(`Error loading hyphenation pattern ${file}:`, error);
reject(error);
});
});
},
handleEvent: {
error: (e) => {
console.warn(`Hyphenopoly error: ${e.msg}`);
},
engineReady: (e) => {
console.log(`Hyphenopoly engine ready for ${e.msg}`);
}
}
});
// Get the hyphenator for our locale
hyphenatorPromise.get(this.locale)
.then(hyphenator => {
this.hyphenator = hyphenator;
this.hyphenatorReady = true;
console.log(`Hyphenator ready for ${this.locale}`);
// Dispatch event that hyphenation is ready
document.dispatchEvent(new CustomEvent('hyphenation-loaded'));
resolve(true); // Successfully initialized
})
.catch(error => {
console.error(`Failed to initialize hyphenator for ${this.locale}:`, error);
// Try to fall back to en-us if the current locale failed
if (this.locale !== 'en-us') {
console.log("Falling back to en-us hyphenation");
return hyphenatorPromise.get('en-us');
}
throw error;
})
.then(fallbackHyphenator => {
if (fallbackHyphenator) {
this.hyphenator = fallbackHyphenator;
this.hyphenatorReady = true;
console.log("Using fallback en-us hyphenator");
// Dispatch event that hyphenation is ready
document.dispatchEvent(new CustomEvent('hyphenation-loaded'));
resolve(true); // Successfully initialized with fallback
}
})
.catch(error => {
console.error("Failed to initialize hyphenation even with fallback:", error);
reject(error); // Failed to initialize
});
} catch (error) {
console.error("Error applying hyphenation:", error);
console.error("Error setting up hyphenation:", error);
reject(error); // Failed to initialize
}
}
return processed;
});
}
/**
* Check if hyphenation is available
* @returns {boolean} - Whether hyphenation is available
* @returns {boolean} - True if hyphenation is available
*/
isHyphenationAvailable() {
return this.hyphenatorReady && this.hyphenator !== null;
}
/**
* Apply only hyphenation to text
* Hyphenate a text using the Hyphenopoly module
* @param {string} text - The text to hyphenate
* @returns {string} - The hyphenated text
*/
hyphenate(text) {
if (!text || !this.hyphenatorReady || !this.hyphenator) {
if (!this.isHyphenationAvailable()) {
return text;
}
try {
return this.hyphenator(text);
} catch (error) {
@@ -288,16 +279,33 @@ class TextProcessorModule extends BaseModule {
}
/**
* Set the locale for text processing
* @param {string} locale - The locale code (e.g., 'en-us', 'de')
* Process text with typography enhancements
* @param {string} text - The text to process
* @param {Object} options - Processing options
* @param {boolean} [options.smartypants=true] - Whether to apply SmartyPants processing
* @param {boolean} [options.hyphenate=true] - Whether to apply hyphenation
* @returns {string} - The processed text
*/
setLocale(locale) {
if (locale && typeof locale === 'string') {
this.locale = locale.toLowerCase();
// Update global locale for SmartyPants
window.locale = this.locale;
console.log(`TextProcessor: Locale set to ${locale}`);
process(text, options = {}) {
const opts = {
smartypants: true,
hyphenate: true,
...options
};
let result = text;
// Apply SmartyPants if available and requested
if (opts.smartypants && this.smartyPants) {
result = this.smartyPants(result);
}
// Apply hyphenation if available and requested
if (opts.hyphenate && this.isHyphenationAvailable()) {
result = this.hyphenate(result);
}
return result;
}
}