Files
P4RS3LT0NGV3/js/app.js
Dustin Farley dc10a90851 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)
2025-12-02 20:26:32 -08:00

423 lines
17 KiB
JavaScript

const baseData = {
isDarkTheme: true,
activeTab: 'transforms',
registeredTools: [],
universalDecodeInput: '',
universalDecodeResult: null,
isPasteOperation: false,
lastCopyTime: 0,
ignoreKeyboardEvents: false,
isTransformCopy: false,
keyboardEventsTimeout: null,
showDecoder: true,
tbCarrierManual: '',
copyHistory: [],
maxHistoryItems: window.CONFIG.MAX_HISTORY_ITEMS,
showCopyHistory: false,
showUnicodePanel: false,
unicodeApplyBusy: false,
unicodeApplyFlash: false,
unicodePanelToggleLock: false,
unicodeApplyFlashTimeout: null,
showDangerModal: false,
dangerThresholdTokens: window.CONFIG.DANGER_THRESHOLD_TOKENS
};
const toolData = (window.toolRegistry && typeof window.toolRegistry.mergeVueData === 'function')
? window.toolRegistry.mergeVueData()
: {};
const mergedData = Object.assign({}, baseData, toolData);
const toolMethods = (window.toolRegistry && typeof window.toolRegistry.mergeVueMethods === 'function')
? window.toolRegistry.mergeVueMethods()
: {};
window.app = new Vue({
el: '#app',
data: mergedData,
methods: Object.assign({}, toolMethods || {}, {
toggleUnicodePanel(event) {
if (this.unicodePanelToggleLock) return;
this.unicodePanelToggleLock = true;
this.showUnicodePanel = !this.showUnicodePanel;
const panel = document.getElementById('unicode-options-panel');
if (panel) {
if (this.showUnicodePanel) panel.classList.add('active');
else panel.classList.remove('active');
}
setTimeout(() => {
this.unicodePanelToggleLock = false;
}, 300);
},
applyUnicodeOptions() {
if (this.unicodeApplyBusy) return;
if (this.unicodeApplyFlashTimeout) {
clearTimeout(this.unicodeApplyFlashTimeout);
this.unicodeApplyFlashTimeout = null;
}
this.unicodeApplyFlash = false;
this.unicodeApplyBusy = true;
try {
const initSel = document.querySelector('.steg-initial-presentation');
const vs0Sel = document.querySelector('.steg-vs-zero');
const vs1Sel = document.querySelector('.steg-vs-one');
const zwSel = document.querySelector('.steg-inter-zw');
const everyInput = document.querySelector('.steg-inter-every');
const orderSel = document.querySelector('.steg-bit-order');
const trailSel = document.querySelector('.steg-trailing-zw');
const parseEsc = (s) => window.EscapeParser.parseEscapeSequence(s);
if (window.steganography && window.steganography.setStegOptions) {
window.steganography.setStegOptions({
initialPresentation: (initSel && initSel.value) || 'none',
bitZeroVS: parseEsc(vs0Sel && vs0Sel.value) || '\ufe0e',
bitOneVS: parseEsc(vs1Sel && vs1Sel.value) || '\ufe0f',
interBitZW: parseEsc(zwSel && zwSel.value) || null,
interBitEvery: Math.max(1, Math.min(8, Number((everyInput && everyInput.value) || 1))),
bitOrder: (orderSel && orderSel.value) || 'msb',
trailingZW: parseEsc(trailSel && trailSel.value) || ''
});
this.unicodeApplyFlash = true;
this.showNotification('Advanced settings applied', 'success', 'fas fa-sliders-h');
this.unicodeApplyFlashTimeout = setTimeout(() => {
this.unicodeApplyFlash = false;
this.unicodeApplyFlashTimeout = null;
}, 1200);
} else {
this.showNotification('Engine missing setStegOptions()', 'warning', 'fas fa-exclamation-triangle');
}
} catch (e) {
console.error('Apply Unicode options error', e);
this.showNotification('Failed to apply settings', 'error', 'fas fa-exclamation-triangle');
this.unicodeApplyFlash = false;
} finally {
this.unicodeApplyBusy = false;
}
},
focusWithoutScroll(el) {
window.FocusUtils.focusWithoutScroll(el);
},
triggerRandomizerChaos() {
try {
const section = document.getElementById('category-randomizer');
const overlay = section && section.querySelector('.chaos-overlay');
if (!overlay) return;
const emojis = ['✨','🌀','💥','⚡','🔥','🌈','🎲','🔮','💫','🌪️'];
for (let i=0;i<10;i++) {
const el = document.createElement('div');
el.className = 'chaos-particle';
el.textContent = emojis[Math.floor(Math.random()*emojis.length)];
el.style.left = (10 + Math.random()*80) + '%';
el.style.fontSize = (14 + Math.random()*10) + 'px';
el.style.animationDelay = (Math.random()*0.2) + 's';
overlay.appendChild(el);
setTimeout(()=>{ if (el.parentNode) el.parentNode.removeChild(el); }, 1300);
}
section.classList.add('shake-once','randomizer-glow');
setTimeout(()=>section && section.classList.remove('shake-once','randomizer-glow'), 600);
} catch(_) {}
},
switchToTab(tabName) {
if (this.activeTab && window.toolRegistry) {
window.toolRegistry.deactivateTool(this.activeTab, this);
}
this.activeTab = tabName;
this.universalDecodeInput = '';
this.universalDecodeResult = null;
if (window.toolRegistry) {
window.toolRegistry.activateTool(tabName, this);
}
},
toggleTheme() {
this.isDarkTheme = window.ThemeUtils.toggleTheme(this.isDarkTheme);
},
toggleCopyHistory() {
this.showCopyHistory = !this.showCopyHistory;
if (this.showCopyHistory && this.copyHistory.length > 0) {
this.$nextTick(() => {
const firstCopyButton = document.querySelector('.copy-again-button');
if (firstCopyButton) {
firstCopyButton.focus();
}
});
}
},
addToCopyHistory(source, content) {
window.HistoryUtils.addToHistory(
this.copyHistory,
this.maxHistoryItems,
source,
content
);
},
clearCopyHistory() {
window.HistoryUtils.clearHistory(this.copyHistory);
this.showNotification('History cleared', 'success', 'fas fa-check');
},
removeFromCopyHistory(id) {
window.HistoryUtils.removeFromHistory(this.copyHistory, id);
this.showNotification('Removed from history', 'success', 'fas fa-check');
},
formatHistoryTime(timestamp) {
if (!timestamp) return '';
const date = new Date(timestamp);
return date.toLocaleString();
},
async copyToClipboard(text, skipHistory = false) {
if (!text || !window.ClipboardUtils) return;
// Check if content already exists in history
const alreadyInHistory = this.copyHistory.some(item => item.content === text);
const source = window.HistoryUtils.getHistorySource(this.activeTab, {
activeTransform: this.activeTransform,
activeSteg: this.activeSteg,
selectedEmoji: this.selectedEmoji
});
const success = await window.ClipboardUtils.copy(text, {
onSuccess: () => {
// Only add to history if not skipping and not already in history
if (!skipHistory && !alreadyInHistory) {
this.addToCopyHistory(source, text);
}
window.FocusUtils.clearFocusAndSelection();
}
});
return success;
},
forceCopyToClipboard(text) {
if (!text || !window.ClipboardUtils) return;
if (this.isPasteOperation) {
this.isPasteOperation = false;
return;
}
if (!this.isTransformCopy && this.ignoreKeyboardEvents) {
return;
}
const source = window.HistoryUtils.getHistorySource(this.activeTab, {
activeTransform: this.activeTransform,
activeSteg: this.activeSteg,
selectedEmoji: this.selectedEmoji
});
window.ClipboardUtils.copy(text, {
onSuccess: () => {
this.addToCopyHistory(source, text);
if (this.isTransformCopy) {
this.showCopiedPopup();
this.ignoreKeyboardEvents = true;
clearTimeout(this.keyboardEventsTimeout);
this.keyboardEventsTimeout = setTimeout(() => {
this.ignoreKeyboardEvents = false;
}, window.CONFIG.KEYBOARD_EVENTS_TIMEOUT_MS);
}
this.isTransformCopy = false;
const inputBox = document.querySelector('#transform-input');
if (inputBox) {
window.FocusUtils.focusWithoutScroll(inputBox);
const len = inputBox.value.length;
try { inputBox.setSelectionRange(len, len); } catch (_) {}
}
}
});
},
showNotification(message, type = 'success', iconClass = null) {
window.NotificationUtils.showNotification(message, type, iconClass);
},
showCopiedPopup() {
window.NotificationUtils.showCopiedPopup();
},
setupPasteHandlers() {
const textareas = document.querySelectorAll('textarea');
textareas.forEach(textarea => {
textarea.addEventListener('paste', (e) => {
this.isPasteOperation = true;
setTimeout(() => {
this.isPasteOperation = false;
}, window.CONFIG.PASTE_FLAG_RESET_DELAY_MS);
});
});
}
}),
mounted() {
if (window.ThemeUtils && window.ThemeUtils.initializeTheme) {
this.isDarkTheme = window.ThemeUtils.initializeTheme();
if (this.isDarkTheme) {
document.body.classList.add('dark-theme');
} else {
document.body.classList.add('light-theme');
}
} else if (this.isDarkTheme) {
document.body.classList.add('dark-theme');
}
if (window.toolRegistry && typeof window.toolRegistry.mergeVueLifecycle === 'function') {
const lifecycleHooks = window.toolRegistry.mergeVueLifecycle();
if (lifecycleHooks && lifecycleHooks.mounted) {
lifecycleHooks.mounted.call(this);
}
}
if (window.toolRegistry && typeof window.toolRegistry.getAll === 'function') {
this.registeredTools = window.toolRegistry.getAll();
}
this.$nextTick(() => {
const closeButton = document.querySelector('#unicode-options-panel .close-button');
if (closeButton) {
const handleClose = (e) => {
e.preventDefault();
e.stopPropagation();
this.toggleUnicodePanel(e);
};
closeButton.addEventListener('click', handleClose, { passive: false });
closeButton.addEventListener('touchend', handleClose, { passive: false });
}
});
document.addEventListener('click', (e) => {
if (e.target.closest('.custom-tooltip')) {
return;
}
const tooltipIcon = e.target.closest('.tooltip-icon');
if (tooltipIcon) {
e.preventDefault();
e.stopPropagation();
const tooltipText = tooltipIcon.getAttribute('data-tooltip');
if (!tooltipText) return;
const existingTooltip = document.querySelector('.custom-tooltip.active');
if (existingTooltip && existingTooltip.textContent === tooltipText) {
existingTooltip.classList.remove('active');
setTimeout(() => {
if (!existingTooltip.classList.contains('active')) {
existingTooltip.remove();
}
}, 200);
return;
}
document.querySelectorAll('.custom-tooltip.active').forEach(tooltip => {
tooltip.classList.remove('active');
setTimeout(() => {
if (!tooltip.classList.contains('active')) {
tooltip.remove();
}
}, 200);
});
setTimeout(() => {
const tooltip = document.createElement('div');
tooltip.className = 'custom-tooltip active';
tooltip.textContent = tooltipText;
document.body.appendChild(tooltip);
const rect = tooltipIcon.getBoundingClientRect();
tooltip.style.left = (rect.left + rect.width / 2) + 'px';
tooltip.style.top = (rect.top - tooltip.offsetHeight - 8) + 'px';
tooltip.style.transform = 'translateX(-50%)';
}, 10);
return;
}
if (e.target.closest('#unicode-options-panel')) {
return;
}
document.querySelectorAll('.custom-tooltip.active').forEach(tooltip => {
tooltip.classList.remove('active');
setTimeout(() => {
if (!tooltip.classList.contains('active')) {
tooltip.remove();
}
}, 200);
});
});
this.$nextTick(() => {
const initializeEmojiGrid = () => {
if (this.activeTab !== 'steganography') {
return;
}
const emojiGridContainer = document.getElementById('emoji-grid-container');
if (emojiGridContainer) {
emojiGridContainer.setAttribute('style', 'display: block !important; visibility: visible !important; min-height: 300px; padding: 10px;');
const emojiLibrary = document.querySelector('.emoji-library');
if (emojiLibrary) {
emojiLibrary.setAttribute('style', 'display: block !important; visibility: visible !important; margin-top: 20px; overflow: visible;');
}
this.renderEmojiGrid();
clearInterval(emojiGridInitializer);
}
};
const emojiGridInitializer = setInterval(initializeEmojiGrid, window.CONFIG.EMOJI_GRID_INIT_INTERVAL_MS);
this._emojiGridInitializer = emojiGridInitializer;
this.setupPasteHandlers();
});
},
created() {
if (window.toolRegistry && typeof window.toolRegistry.mergeVueLifecycle === 'function') {
const lifecycleHooks = window.toolRegistry.mergeVueLifecycle();
if (lifecycleHooks && lifecycleHooks.created) {
lifecycleHooks.created.call(this);
}
}
},
beforeDestroy() {
if (this._emojiGridInitializer) {
clearInterval(this._emojiGridInitializer);
this._emojiGridInitializer = null;
}
if (window.toolRegistry && typeof window.toolRegistry.mergeVueLifecycle === 'function') {
const lifecycleHooks = window.toolRegistry.mergeVueLifecycle();
if (lifecycleHooks && lifecycleHooks.beforeDestroy) {
lifecycleHooks.beforeDestroy.call(this);
}
}
},
watch: (window.toolRegistry && typeof window.toolRegistry.mergeVueWatchers === 'function')
? window.toolRegistry.mergeVueWatchers()
: {}
});