// Initialize Vue app window.app = new Vue({ el: '#app', data: { // Theme isDarkTheme: true, // Tab Management activeTab: 'transforms', // Transform Tab transformInput: '', transformOutput: '', activeTransform: null, transforms: Object.entries(window.transforms).map(([key, transform]) => ({ name: transform.name, func: transform.func.bind(transform), preview: transform.preview.bind(transform) })), // Steganography Tab emojiMessage: '', encodedMessage: '', decodeInput: '', decodedMessage: '', selectedCarrier: null, // Universal Decoder - works on both tabs universalDecodeInput: '', universalDecodeResult: null, isPasteOperation: false, // Flag to track paste operations activeSteg: null, carriers: window.steganography.carriers, showDecoder: true, // Emoji Library filteredEmojis: [...window.emojiLibrary.EMOJI_LIST], selectedEmoji: null, // History of copied content copyHistory: [], maxHistoryItems: 10, showCopyHistory: false }, methods: { // Theme Toggle toggleTheme() { this.isDarkTheme = !this.isDarkTheme; document.body.classList.toggle('light-theme'); }, // Copy History Toggle toggleCopyHistory() { this.showCopyHistory = !this.showCopyHistory; console.log('Copy history toggled:', this.showCopyHistory); // If showing history panel, focus the first copy-again button if available if (this.showCopyHistory && this.copyHistory.length > 0) { this.$nextTick(() => { const firstCopyButton = document.querySelector('.copy-again-button'); if (firstCopyButton) { firstCopyButton.focus(); } }); } }, // Transform Methods applyTransform(transform) { if (this.transformInput) { this.activeTransform = transform; this.transformOutput = transform.func(this.transformInput); // Force copy and log to history this.forceCopyToClipboard(this.transformOutput); // Add to copy history this.addToCopyHistory(`Transform: ${transform.name}`, this.transformOutput); // Enhanced notification for transform and copy this.showNotification(` ${transform.name} applied and copied!`, 'success'); } }, autoTransform() { // Only proceed if we're in the transforms tab and have an active transform if (this.transformInput && this.activeTransform && this.activeTab === 'transforms') { this.transformOutput = this.activeTransform.func(this.transformInput); // Use forceCopyToClipboard for auto-copy this.forceCopyToClipboard(this.transformOutput); // Add to copy history this.addToCopyHistory(`Transform: ${this.activeTransform.name}`, this.transformOutput); } }, // Steganography Methods selectCarrier(carrier) { // Toggle carrier selection if clicking the same one again if (this.selectedCarrier === carrier) { this.selectedCarrier = null; this.encodedMessage = ''; } else { this.selectedCarrier = carrier; this.activeSteg = 'emoji'; this.autoEncode(); } }, setStegMode(mode) { // For invisible text, make it a direct action (not a toggle) if (mode === 'invisible') { // Set the mode temporarily to generate the encoded message this.activeSteg = mode; // Clear any carrier selection this.selectedCarrier = null; // Generate the encoded message this.autoEncode(); // Auto-copy the encoded message if (this.encodedMessage) { this.$nextTick(() => { this.forceCopyToClipboard(this.encodedMessage); this.showNotification(' Invisible text created and copied!', 'success'); this.addToCopyHistory('Invisible Text', this.encodedMessage); }); } } else { // For other modes (like emoji), keep the toggle behavior if (this.activeSteg === mode) { this.activeSteg = null; this.encodedMessage = ''; } else { this.activeSteg = mode; this.autoEncode(); } } }, autoEncode() { // Only proceed if we're in the steganography tab if (!this.emojiMessage || this.activeTab !== 'steganography') { this.encodedMessage = ''; return; } if (this.activeSteg === 'invisible') { this.encodedMessage = window.steganography.encodeInvisible(this.emojiMessage); // Auto-copy will be handled in setStegMode method } else if (this.selectedCarrier) { this.encodedMessage = window.steganography.encodeEmoji( this.selectedCarrier.emoji, this.emojiMessage ); // Auto-copy for emoji carrier is handled in selectEmoji method } }, autoDecode() { if (!this.decodeInput) { this.decodedMessage = ''; return; } // Use the universal decoder const result = this.universalDecode(this.decodeInput); if (result) { this.decodedMessage = `Decoded (${result.method}): ${result.text}`; // Auto-copy decoded message to clipboard this.$nextTick(() => { // Only copy the actual decoded text, not the formatted message const decodedText = result.text; if (decodedText) { // Force clipboard copy regardless of event source this.forceCopyToClipboard(decodedText); this.showNotification(` Decoded message copied!`, 'success'); // Add to copy history this.addToCopyHistory(`Decoded (${result.method})`, decodedText); } }); } else { this.decodedMessage = 'No encoded message detected'; } }, previewInvisible(text) { return '[invisible]'; }, // Add to copy history functionality addToCopyHistory(source, content) { // Create history item with timestamp const historyItem = { source: source, content: content, timestamp: new Date().toLocaleTimeString(), date: new Date().toLocaleDateString() }; // Add to beginning of array (most recent first) this.copyHistory.unshift(historyItem); // Limit history to maxHistoryItems if (this.copyHistory.length > this.maxHistoryItems) { this.copyHistory.pop(); } // Log history item for debugging console.log('Added to copy history:', historyItem); }, // Utility Methods async copyToClipboard(text) { if (!text) return; // Always try to copy, regardless of event source try { await navigator.clipboard.writeText(text); // Show a success notification this.showNotification(' Copied!', 'success'); // Add to history - determine source from active tab or context const source = this.activeTab === 'transforms' ? 'Transform' : 'Steganography'; this.addToCopyHistory(source, text); } catch (err) { console.warn('Clipboard access not available:', err); // Try fallback method for copying (textarea method) this.fallbackCopy(text); } }, fallbackCopy(text) { try { // Create temporary textarea const textarea = document.createElement('textarea'); textarea.value = text; textarea.style.position = 'fixed'; // Avoid scrolling to bottom document.body.appendChild(textarea); textarea.select(); // Try the copy command const successful = document.execCommand('copy'); // Show appropriate notification if (successful) { this.showNotification(' Copied!', 'success'); // Add to history when successful // Try to determine a more specific source based on the context let source = this.activeTab === 'transforms' ? 'Transform' : 'Steganography'; // Add more context if available if (this.activeTab === 'transforms' && this.activeTransform) { source = `Transform: ${this.activeTransform.name}`; } else if (this.activeTab === 'steganography') { if (this.activeSteg === 'invisible') { source = 'Invisible Text'; } else if (this.selectedEmoji) { source = `Emoji: ${this.selectedEmoji}`; } } this.addToCopyHistory(source, text); } else { this.showNotification(' Copy not supported', 'error'); } // Clean up document.body.removeChild(textarea); } catch (err) { console.warn('Fallback copy method failed:', err); this.showNotification(' Copy not supported', 'error'); } }, // Force copy to clipboard regardless of event context forceCopyToClipboard(text) { if (!text) return; // Skip notifications if this was triggered by a paste operation if (this.isPasteOperation) { console.log('Skipping clipboard notification for paste operation'); return; } console.log('Force copying to clipboard:', text); try { // Try to use the Clipboard API first if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(text) .then(() => { console.log('Force copy successful using Clipboard API'); // Show clear notification on success this.showCopiedPopup(); }) .catch(err => { console.warn('Force Clipboard API failed:', err); this.forceFallbackCopy(text); // Still show notification, as fallback might work this.showCopiedPopup(); }); } else { // Fall back to execCommand method this.forceFallbackCopy(text); // Show notification for fallback method too this.showCopiedPopup(); } } catch (error) { console.error('Force copy failed:', error); // Try one last fallback and still show notification this.forceFallbackCopy(text); this.showCopiedPopup(); } }, // Fallback copy method that doesn't rely on user-initiated events forceFallbackCopy(text) { try { const textarea = document.createElement('textarea'); textarea.value = text; textarea.style.position = 'fixed'; textarea.style.left = '-9999px'; textarea.style.top = '0'; document.body.appendChild(textarea); textarea.focus(); textarea.select(); try { document.execCommand('copy'); console.log('Force fallback copy successful'); } catch (err) { console.error('Force fallback copy command failed:', err); } document.body.removeChild(textarea); } catch (err) { console.error('Force fallback copy method failed:', err); } }, // Notification system showNotification(message, type = 'success') { // Create notification element const notification = document.createElement('div'); notification.className = `copy-notification ${type}`; notification.innerHTML = message; document.body.appendChild(notification); // Remove after animation setTimeout(() => { notification.classList.add('fade-out'); setTimeout(() => { if (notification.parentNode) { document.body.removeChild(notification); } }, 300); }, 1000); }, // Special prominent copy notification showCopiedPopup() { // Create a more visible popup just for copy operations const popup = document.createElement('div'); popup.className = 'copy-popup'; popup.innerHTML = ' Copied to clipboard!'; // Add to body document.body.appendChild(popup); // Force it to be visible and centered popup.style.position = 'fixed'; popup.style.top = '50%'; popup.style.left = '50%'; popup.style.transform = 'translate(-50%, -50%)'; popup.style.backgroundColor = 'rgba(0, 0, 0, 0.8)'; popup.style.color = 'white'; popup.style.padding = '15px 25px'; popup.style.borderRadius = '5px'; popup.style.fontSize = '18px'; popup.style.fontWeight = 'bold'; popup.style.zIndex = '10000'; popup.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.5)'; popup.style.textAlign = 'center'; // Add fade-in animation popup.style.opacity = '0'; popup.style.transition = 'opacity 0.3s ease-in-out'; // Force reflow to make animation work void popup.offsetWidth; // Fade in popup.style.opacity = '1'; // Remove after a short delay setTimeout(() => { popup.style.opacity = '0'; setTimeout(() => { if (popup.parentNode) { document.body.removeChild(popup); } }, 300); }, 1500); }, // Run the universal decoder when input changes runUniversalDecode() { console.log('Running universal decoder with input:', this.universalDecodeInput); // Clear result if input is empty if (!this.universalDecodeInput) { this.universalDecodeResult = null; return; } // Try to decode using all available methods const result = this.universalDecode(this.universalDecodeInput); // Update the result this.universalDecodeResult = result; // Log the result if (result) { console.log(`Universal decoder found a match: ${result.method}`); } else { console.log('Universal decoder could not decode the input'); } }, // Universal Decoder - tries all decoding methods universalDecode(input) { if (!input) return ''; // Try all decoders in order // 1. Try steganography decoders // - Check for emoji steganography first // The emoji encoding uses variation selectors which are hard to see if (/[\u{1F300}-\u{1F6FF}\u{2600}-\u{26FF}]/u.test(input)) { console.log('Detected emoji, attempting to decode...'); const decoded = window.steganography.decodeEmoji(input); if (decoded) { console.log('Successfully decoded emoji:', decoded); return { text: decoded, method: 'Emoji Steganography' }; } else { console.log('Emoji detected but no hidden message found'); } } // - Invisible text let decoded = window.steganography.decodeInvisible(input); if (decoded) { return { text: decoded, method: 'Invisible Text' }; } // 2. Try transform reversals // - Binary if (/^[01\s]+$/.test(input.trim())) { try { // Use binary transform's reverse function if available if (window.transforms.binary && window.transforms.binary.reverse) { const result = window.transforms.binary.reverse(input); if (result && /[\x20-\x7E]/.test(result)) { // Make sure it's readable ASCII return { text: result, method: 'Binary' }; } } else { // Fallback implementation const binText = input.replace(/\s+/g, ''); let result = ''; for (let i = 0; i < binText.length; i += 8) { const byte = binText.substr(i, 8); if (byte.length === 8) { result += String.fromCharCode(parseInt(byte, 2)); } } if (result && /[\x20-\x7E]/.test(result)) { // Make sure it's readable ASCII return { text: result, method: 'Binary' }; } } } catch (e) { console.error('Binary decode error:', e); } } // - Morse code if (/^[.\-\s\/]+$/.test(input.trim())) { try { // Use morse transform's reverse function if available if (window.transforms.morse && window.transforms.morse.reverse) { const result = window.transforms.morse.reverse(input); if (result !== input && /[a-zA-Z0-9]/.test(result)) { return { text: result, method: 'Morse Code' }; } } } catch (e) { console.error('Morse decode error:', e); } } // - Braille const braillePattern = /[⠀-⣿]/; if (braillePattern.test(input)) { try { // Create a reverse mapping for braille const brailleReverseMap = {}; if (window.transforms.braille && window.transforms.braille.map) { for (const [key, value] of Object.entries(window.transforms.braille.map)) { brailleReverseMap[value] = key; } // Decode the braille let result = ''; for (const char of input) { result += brailleReverseMap[char] || char; } if (result !== input && /[a-zA-Z0-9]/.test(result)) { return { text: result, method: 'Braille' }; } } } catch (e) { console.error('Braille decode error:', e); } } // - Base64 if (/^[A-Za-z0-9+/=]+$/.test(input.trim())) { try { // Attempt to decode as base64 const result = atob(input.trim()); // Check if result is readable text if (/[\x20-\x7E]{3,}/.test(result)) { // At least 3 readable ASCII chars return { text: result, method: 'Base64' }; } } catch (e) { // Not valid base64, continue to next decoder console.error('Base64 decode error:', e); } } // - Upside Down text if (window.transforms.upside_down && window.transforms.upside_down.reverse) { try { const result = window.transforms.upside_down.reverse(input); // Check if the result is significantly different if (result !== input && result.length > 3 && /[a-zA-Z0-9\s]{3,}/.test(result)) { return { text: result, method: 'Upside Down' }; } } catch (e) { console.error('Upside Down decode error:', e); } } // - Small Caps (create reverse mapping since there's no built-in decoder) if (window.transforms.small_caps && window.transforms.small_caps.map) { try { // Create reverse mapping const smallCapsReverseMap = {}; for (const [key, value] of Object.entries(window.transforms.small_caps.map)) { smallCapsReverseMap[value] = key; } // Check if input contains small caps characters const smallCapsChars = Object.values(window.transforms.small_caps.map); const hasSmallCaps = smallCapsChars.some(char => input.includes(char)); if (hasSmallCaps) { // Decode text let result = ''; for (const char of input) { result += smallCapsReverseMap[char] || char; } if (result !== input && /[a-zA-Z]/.test(result)) { return { text: result, method: 'Small Caps' }; } } } catch (e) { console.error('Small Caps decode error:', e); } } // - Bubble text (create reverse mapping) if (window.transforms.bubble && window.transforms.bubble.map) { try { // Create reverse mapping const bubbleReverseMap = {}; for (const [key, value] of Object.entries(window.transforms.bubble.map)) { bubbleReverseMap[value] = key; } // Check if input contains bubble characters const bubbleChars = Object.values(window.transforms.bubble.map); const hasBubbleChars = bubbleChars.some(char => input.includes(char)); if (hasBubbleChars) { // Decode text let result = ''; for (const char of input) { result += bubbleReverseMap[char] || char; } if (result !== input && /[a-zA-Z]/.test(result)) { return { text: result, method: 'Bubble' }; } } } catch (e) { console.error('Bubble decode error:', e); } } // - Try reverse each transform that has a built-in reverse function for (const name in window.transforms) { const transform = window.transforms[name]; if (transform.reverse) { try { const result = transform.reverse(input); // Only return if the result is different and contains readable characters if (result !== input && /[a-zA-Z0-9\s]{3,}/.test(result)) { return { text: result, method: transform.name }; } } catch (e) { console.error(`Error decoding with ${name}:`, e); } } } return null; }, // Emoji Library Methods filterEmojis() { // Always show all emojis - search functionality removed this.filteredEmojis = [...window.emojiLibrary.EMOJI_LIST]; this.renderEmojiGrid(); }, selectEmoji(emoji) { this.selectedEmoji = emoji; // Create a temporary carrier for this emoji const tempCarrier = { name: `${emoji} Carrier`, emoji: emoji, encode: (text) => this.steganography.encode(text, emoji), decode: (text) => this.steganography.decode(text), preview: (text) => `${emoji}${text}${emoji}` }; // Use this emoji as carrier this.selectedCarrier = tempCarrier; this.activeSteg = 'emoji'; // Encode the message with this emoji if (this.emojiMessage) { this.autoEncode(); // Wait for encoding to complete, then copy to clipboard this.$nextTick(() => { if (this.encodedMessage) { // Force clipboard copy regardless of event source this.forceCopyToClipboard(this.encodedMessage); this.showNotification(` Copied hidden message with ${emoji}`, 'success'); // Add to copy history this.addToCopyHistory(`Emoji: ${emoji}`, this.encodedMessage); } }); } else { this.showNotification(`Select an emoji and enter a message first`, 'info'); } }, renderEmojiGrid() { console.log('renderEmojiGrid called with', this.filteredEmojis.length, 'emojis'); // Make sure container exists const container = document.getElementById('emoji-grid-container'); if (!container) { console.error('emoji-grid-container not found!'); return; } // Force container to be completely visible container.style.cssText = 'display: block !important; visibility: visible !important; min-height: 300px;'; // Make sure parent containers are visible too const emojiLibrary = document.querySelector('.emoji-library'); if (emojiLibrary) { emojiLibrary.style.cssText = 'display: block !important; visibility: visible !important;'; } // Clear any existing content to avoid duplication container.innerHTML = ''; // Render the emoji grid window.emojiLibrary.renderEmojiGrid('emoji-grid-container', this.selectEmoji.bind(this), this.filteredEmojis); // Add a bold message about copying const copyNote = document.createElement('div'); copyNote.style.cssText = 'text-align: center; margin-top: 10px; font-weight: bold; padding: 5px; background-color: #f0f0f0; border-radius: 4px;'; copyNote.innerHTML = ' Clicking an emoji will automatically copy your hidden message'; container.appendChild(copyNote); // Log success console.log('Emoji grid rendered successfully'); } }, // Initialize theme and components mounted() { console.log('Vue app mounted'); // Apply theme if (this.isDarkTheme) { document.body.classList.add('dark-theme'); } // Initialize emoji grid with all emojis shown by default this.$nextTick(() => { console.log('nextTick: Initializing emoji grid'); // Make sure filtered emojis is populated this.filteredEmojis = [...window.emojiLibrary.EMOJI_LIST]; // Define a function to properly initialize the emoji grid const initializeEmojiGrid = () => { const emojiGridContainer = document.getElementById('emoji-grid-container'); if (emojiGridContainer) { console.log('Found emoji-grid-container, rendering grid'); // Set inline styles to ensure visibility emojiGridContainer.setAttribute('style', 'display: block !important; visibility: visible !important; min-height: 300px; padding: 10px; border: 1px solid #ccc;'); // Also make sure the parent container is visible const emojiLibrary = document.querySelector('.emoji-library'); if (emojiLibrary) { emojiLibrary.setAttribute('style', 'display: block !important; visibility: visible !important; margin-top: 30px; border: 3px solid var(--accent-color); overflow: visible;'); } // Now render the grid this.renderEmojiGrid(); console.log('Emoji grid rendering complete in mounted()'); } else { console.error('emoji-grid-container not found, will retry'); // Try again after a longer delay if not found setTimeout(initializeEmojiGrid, 500); } }; // Start with a small delay to ensure DOM is ready setTimeout(initializeEmojiGrid, 100); // Set up paste event handlers for all textareas to prevent unwanted clipboard notifications this.setupPasteHandlers(); }); }, // Set up paste event handlers for all textareas setupPasteHandlers() { // Get all textareas in the app const textareas = document.querySelectorAll('textarea'); // Add paste event listener to each textarea textareas.forEach(textarea => { textarea.addEventListener('paste', (e) => { // Mark this as an explicit paste event this.isPasteOperation = true; // Reset the flag after a short delay setTimeout(() => { this.isPasteOperation = false; }, 100); }); }); }, // No keyboard shortcuts - they were removed as requested created() { // Initialize any required functionality // But no keyboard shortcuts/hotkeys for now }, // Watch for input events and ensure emojis stay visible watch: { // Make sure emoji list stays loaded when user types in any input emojiMessage() { // Reset the filtered emojis to the full list whenever typing occurs this.filteredEmojis = [...window.emojiLibrary.EMOJI_LIST]; this.$nextTick(() => { this.renderEmojiGrid(); }); }, // Also watch the decode input field for typing activity decodeInput() { // Always show full emoji list this.filteredEmojis = [...window.emojiLibrary.EMOJI_LIST]; this.$nextTick(() => { this.renderEmojiGrid(); }); } } });