refactor: migrate to modular tool-based architecture

- Implement tool registry system with individual tool modules
- Reorganize transformers into categorized source modules
- Remove emojiLibrary.js, consolidate into EmojiUtils and emojiData
- Fix mobile close button and tooltip functionality
- Add build system for transforms and emoji data
- Migrate from Python backend to pure JavaScript
- Add comprehensive documentation and testing
- Improve code organization and maintainability
- Ignore generated files (transforms-bundle.js, emojiData.js)
This commit is contained in:
Dustin Farley
2025-12-02 20:26:32 -08:00
parent 105084437a
commit dc10a90851
146 changed files with 12712 additions and 8171 deletions
+51
View File
@@ -0,0 +1,51 @@
/**
* Clipboard Utility
* Provides unified clipboard copy functionality using Clipboard API
*/
window.ClipboardUtils = {
/**
* Copy text to clipboard using Clipboard API
* @param {string} text - Text to copy
* @param {Object} options - Options object
* @param {Function} options.onSuccess - Callback on success
* @param {Function} options.onError - Callback on error
* @param {boolean} options.suppressNotification - Don't show notification
* @returns {Promise<boolean>} - Success status
*/
async copy(text, options = {}) {
if (!text) return false;
const {
onSuccess,
onError,
suppressNotification = false
} = options;
if (!navigator.clipboard || !navigator.clipboard.writeText) {
const errorMsg = 'Clipboard API not available';
console.error(errorMsg);
if (!suppressNotification && window.NotificationUtils) {
window.NotificationUtils.showNotification('Clipboard not supported', 'error', 'fas fa-exclamation-triangle');
}
if (onError) onError(new Error(errorMsg));
return false;
}
try {
await navigator.clipboard.writeText(text);
if (!suppressNotification && window.NotificationUtils) {
window.NotificationUtils.showNotification('Copied!', 'success', 'fas fa-check');
}
if (onSuccess) onSuccess();
return true;
} catch (err) {
console.error('Clipboard copy failed:', err);
if (!suppressNotification && window.NotificationUtils) {
window.NotificationUtils.showNotification('Copy failed', 'error', 'fas fa-exclamation-triangle');
}
if (onError) onError(err);
return false;
}
}
};
+33
View File
@@ -0,0 +1,33 @@
window.EmojiUtils = {
splitEmojis(text) {
if (Intl.Segmenter) {
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
return Array.from(segmenter.segment(text), ({ segment }) => segment);
}
return Array.from(text);
},
joinEmojis(emojis) {
return emojis.join('');
},
getAllEmojis() {
if (!window.emojiData || typeof window.emojiData !== 'object') {
return [];
}
return Object.keys(window.emojiData).filter(key => {
const value = window.emojiData[key];
return typeof value === 'object' && value !== null && 'official' in value;
});
},
async getCompatibleEmojis(progressCallback) {
const allEmojis = this.getAllEmojis();
if (window.emojiCompatibility && typeof window.emojiCompatibility.getCompatibleEmojis === 'function') {
return await window.emojiCompatibility.getCompatibleEmojis(allEmojis, progressCallback);
}
return allEmojis;
}
};
+40
View File
@@ -0,0 +1,40 @@
window.EscapeParser = {
parseEscapeSequence(str) {
if (!str || typeof str !== 'string') {
return str;
}
const escapeMap = {
'\\u200B': '\u200B', // Zero Width Space
'\\u200C': '\u200C', // Zero Width Non-Joiner
'\\u200D': '\u200D', // Zero Width Joiner
'\\u2060': '\u2060', // Word Joiner
'\\uFE0E': '\uFE0E', // Variation Selector-15
'\\uFE0F': '\uFE0F', // Variation Selector-16
'\\n': '\n',
'\\r': '\r',
'\\t': '\t',
'\\0': '\0',
'\\\'': '\'',
'\\"': '"',
'\\\\': '\\'
};
if (escapeMap[str] !== undefined) {
return escapeMap[str];
}
const unicodeMatch = str.match(/^\\u([0-9A-Fa-f]{4})$/);
if (unicodeMatch) {
return String.fromCharCode(parseInt(unicodeMatch[1], 16));
}
const hexMatch = str.match(/^\\x([0-9A-Fa-f]{2})$/);
if (hexMatch) {
return String.fromCharCode(parseInt(hexMatch[1], 16));
}
return str;
}
};
+29
View File
@@ -0,0 +1,29 @@
window.FocusUtils = {
focusWithoutScroll(element) {
if (!element) return;
try {
const scrollX = window.pageXOffset || window.scrollX || 0;
const scrollY = window.pageYOffset || window.scrollY || 0;
element.focus();
window.scrollTo(scrollX, scrollY);
} catch (e) {
try {
element.focus();
} catch (err) {
console.warn('Failed to focus element:', err);
}
}
},
clearFocusAndSelection() {
if (document.activeElement && document.activeElement.blur) {
document.activeElement.blur();
}
if (window.getSelection) {
window.getSelection().removeAllRanges();
}
document.body.focus();
}
};
+60
View File
@@ -0,0 +1,60 @@
window.HistoryUtils = {
addToHistory(historyArray, maxItems, source, content) {
if (!historyArray || !Array.isArray(historyArray)) {
console.warn('HistoryUtils.addToHistory: historyArray is not an array');
return;
}
if (!content) {
return;
}
const entry = {
source: source || 'Unknown',
content: content,
timestamp: new Date().toISOString(),
id: Date.now() + Math.random()
};
historyArray.unshift(entry);
if (historyArray.length > maxItems) {
historyArray.splice(maxItems);
}
},
clearHistory(historyArray) {
if (historyArray && Array.isArray(historyArray)) {
// Use splice to remove all items (same approach as removeFromHistory)
historyArray.splice(0, historyArray.length);
}
},
removeFromHistory(historyArray, id) {
if (!historyArray || !Array.isArray(historyArray)) {
return;
}
const index = historyArray.findIndex(item => item.id === id);
if (index !== -1) {
historyArray.splice(index, 1);
}
},
getHistorySource(activeTab, context = {}) {
if (activeTab === 'transforms' && context.activeTransform) {
return `Transform: ${context.activeTransform.name}`;
} else if (activeTab === 'steganography') {
if (context.activeSteg === 'invisible') {
return 'Invisible Text';
} else if (context.selectedEmoji) {
return `Emoji: ${context.selectedEmoji}`;
}
return 'Steganography';
} else if (activeTab === 'transforms') {
return 'Transform';
}
return 'Unknown';
}
};
+37
View File
@@ -0,0 +1,37 @@
window.NotificationUtils = {
showNotification(message, type = 'success', iconClass = null) {
const existing = document.querySelector('.copy-notification');
if (existing) {
existing.remove();
}
const notification = document.createElement('div');
notification.className = `copy-notification ${type || 'success'}`;
if (iconClass) {
const icon = document.createElement('i');
icon.className = iconClass;
notification.appendChild(icon);
}
const text = document.createElement('span');
text.textContent = message;
notification.appendChild(text);
document.body.appendChild(notification);
setTimeout(() => {
notification.classList.add('fade-out');
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 300);
}, 3000);
},
showCopiedPopup() {
this.showNotification('Copied!', 'success', 'fas fa-check');
}
};
+37
View File
@@ -0,0 +1,37 @@
window.ThemeUtils = {
toggleTheme(currentTheme) {
const newTheme = !currentTheme;
if (newTheme) {
document.body.classList.add('dark-theme');
document.body.classList.remove('light-theme');
} else {
document.body.classList.add('light-theme');
document.body.classList.remove('dark-theme');
}
try {
localStorage.setItem('theme', newTheme ? 'dark' : 'light');
} catch (e) {
console.warn('Failed to save theme preference:', e);
}
return newTheme;
},
initializeTheme() {
try {
const saved = localStorage.getItem('theme');
if (saved === 'light') {
return false;
} else if (saved === 'dark') {
return true;
}
} catch (e) {
console.warn('Failed to load theme preference:', e);
}
return true;
}
};