mirror of
https://github.com/elder-plinius/P4RS3LT0NGV3.git
synced 2026-08-28 13:50:49 +02:00
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:
@@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user