/** * Text Processor Module * Handles text formatting and typography enhancements like smart quotes and hyphenation */ import { BaseModule } from './base-module.js'; import { moduleRegistry } from './module-registry.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.hyphenatorReady = false; this.locale = 'en-us'; } /** * Load module dependencies * @returns {Promise} - Resolves with success status */ async loadDependencies() { try { this.reportProgress(10, "Loading dependencies"); // Load SmartyPants script dynamically await this.loadSmartyPantsScript(); this.reportProgress(50, "SmartyPants loaded"); // Initialize hyphenation in the background, but don't wait for it this.initializeHyphenation(); this.reportProgress(90, "Dependencies loaded"); return true; } catch (error) { console.error("Error loading Text Processor dependencies:", error); return false; } } /** * Load the SmartyPants script dynamically and wait for it to be ready * @returns {Promise} */ loadSmartyPantsScript() { return new Promise((resolve, reject) => { // Check if already loaded globally if (typeof window.SmartyPants === 'object' && typeof window.SmartyPants.smartypants === 'function') { this.smartyPants = window.SmartyPants.smartypants; this.smartypantsu = window.SmartyPants.smartypantsu; console.log("SmartyPants already loaded globally"); resolve(); return; } // Load the script using a script tag const script = document.createElement('script'); script.src = '/js/smartypants.js'; script.async = false; // Load synchronously relative to other scripts 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')); } }); }; script.onerror = () => { console.error('Failed to load smartypants.js script'); reject(new Error('Failed to load smartypants.js script')); }; document.head.appendChild(script); }); } /** * Initialize the module * @returns {Promise} - 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 */ 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) { try { processed = this.hyphenator(processed); } catch (error) { console.error("Error applying hyphenation:", error); } } return processed; } /** * Check if hyphenation is available * @returns {boolean} - Whether hyphenation is available */ isHyphenationAvailable() { return this.hyphenatorReady && this.hyphenator !== null; } /** * Apply only hyphenation to text * @param {string} text - The text to hyphenate * @returns {string} - The hyphenated text */ hyphenate(text) { if (!text || !this.hyphenatorReady || !this.hyphenator) { return text; } try { return this.hyphenator(text); } catch (error) { console.error("Error hyphenating text:", error); return text; } } /** * Set the locale for text processing * @param {string} locale - The locale code (e.g., 'en-us', 'de') */ 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}`); } } } // Create the singleton instance const TextProcessor = new TextProcessorModule(); // Register with the module registry moduleRegistry.register(TextProcessor); // Export the module export { TextProcessor }; // Keep a reference in window for loader system window.TextProcessor = TextProcessor;