Split everything up into dynamically loaded modules.
This commit is contained in:
+298
-55
@@ -1,71 +1,314 @@
|
||||
/**
|
||||
* TextProcessor Module
|
||||
* Encapsulates text pre-processing steps required before layout calculation.
|
||||
* Text Processor Module
|
||||
* Handles text formatting and typography enhancements like smart quotes and hyphenation
|
||||
*/
|
||||
export class TextProcessor {
|
||||
/**
|
||||
* Create a new TextProcessor
|
||||
* @param {Object} smartyPants - The SmartyPants library
|
||||
* @param {Function} [hyphenator] - Optional: The hyphenation function (can be set later)
|
||||
*/
|
||||
constructor(smartyPants, hyphenator = null) { // Make hyphenator optional
|
||||
this.smartyPants = smartyPants;
|
||||
this.hyphenator = hyphenator;
|
||||
this.hyphenationClass = '.hyphenatePipe'; // Default hyphenation class for Knuth-Plass with pipe character
|
||||
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';
|
||||
}
|
||||
|
||||
/**
|
||||
* Process text with typographic enhancements and hyphenation
|
||||
* Load module dependencies
|
||||
* @returns {Promise<boolean>} - 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<void>}
|
||||
*/
|
||||
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<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
|
||||
*/
|
||||
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
|
||||
* @returns {string} The processed text
|
||||
* @param {boolean} useHyphenation - Whether to apply hyphenation
|
||||
* @returns {string} - The processed text
|
||||
*/
|
||||
process(text) {
|
||||
// First apply SmartyPants for typographic enhancement
|
||||
const smartyPantsText = this.smartyPants.smartypantsu(text, 1)
|
||||
// Remove these replacements that were causing the spacing issues
|
||||
// .replace(/\.\s*$/g, '.')
|
||||
// .replace(/\?\s*$/g, '?')
|
||||
// .replace(/!\s*$/g, '!')
|
||||
|
||||
// Instead, ensure proper spacing between sentences
|
||||
.replace(/\.\s+/g, '. ') // Normalize spaces after periods
|
||||
.replace(/\?\s+/g, '? ') // Normalize spaces after question marks
|
||||
.replace(/!\s+/g, '! '); // Normalize spaces after exclamation marks
|
||||
process(text, useHyphenation = false) {
|
||||
if (!text) return '';
|
||||
|
||||
// Then apply hyphenation if available
|
||||
if (typeof this.hyphenator === 'function') {
|
||||
return this.hyphenator(smartyPantsText, this.hyphenationClass);
|
||||
} else {
|
||||
console.warn('TextProcessor: Hyphenator not set, skipping hyphenation.');
|
||||
return smartyPantsText; // Return text without hyphenation if not set
|
||||
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 hyphenator function after initialization.
|
||||
* @param {Function} hyphenatorFunction - The hyphenation function provided by Hyphenopoly.
|
||||
* Set the locale for text processing
|
||||
* @param {string} locale - The locale code (e.g., 'en-us', 'de')
|
||||
*/
|
||||
setHyphenator(hyphenatorFunction) {
|
||||
if (typeof hyphenatorFunction === 'function') {
|
||||
this.hyphenator = hyphenatorFunction;
|
||||
} else {
|
||||
console.error('TextProcessor: Invalid hyphenator function provided.');
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the hyphenation class
|
||||
* @param {string} className - The CSS class for hyphenation
|
||||
*/
|
||||
setHyphenationClass(className) {
|
||||
this.hyphenationClass = className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current hyphenation class
|
||||
* @returns {string} The current hyphenation class
|
||||
*/
|
||||
getHyphenationClass() {
|
||||
return this.hyphenationClass;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
Reference in New Issue
Block a user