mirror of
https://github.com/elder-plinius/P4RS3LT0NGV3.git
synced 2026-06-06 06:53:56 +02:00
Enhanced universal decoder to support braille, base64, and other encodings; Fixed emoji library to always show all emojis when typing
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// Initialize Vue app
|
||||
new Vue({
|
||||
window.app = new Vue({
|
||||
el: '#app',
|
||||
data: {
|
||||
// Theme
|
||||
@@ -24,13 +24,22 @@ new Vue({
|
||||
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
|
||||
emojiSearch: '',
|
||||
filteredEmojis: [...window.emojiLibrary.EMOJI_LIST],
|
||||
selectedEmoji: null
|
||||
selectedEmoji: null,
|
||||
|
||||
// History of copied content
|
||||
copyHistory: [],
|
||||
maxHistoryItems: 10,
|
||||
showCopyHistory: false
|
||||
},
|
||||
methods: {
|
||||
// Theme Toggle
|
||||
@@ -38,19 +47,49 @@ new Vue({
|
||||
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);
|
||||
this.copyToClipboard(this.transformOutput);
|
||||
|
||||
// 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(`<i class="fas fa-check"></i> ${transform.name} applied and copied!`, 'success');
|
||||
}
|
||||
},
|
||||
autoTransform() {
|
||||
if (this.transformInput && this.activeTransform) {
|
||||
// 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);
|
||||
this.copyToClipboard(this.transformOutput);
|
||||
|
||||
// Use forceCopyToClipboard for auto-copy
|
||||
this.forceCopyToClipboard(this.transformOutput);
|
||||
|
||||
// Add to copy history
|
||||
this.addToCopyHistory(`Transform: ${this.activeTransform.name}`, this.transformOutput);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -67,34 +106,50 @@ new Vue({
|
||||
}
|
||||
},
|
||||
setStegMode(mode) {
|
||||
// Toggle mode selection if clicking the same one again
|
||||
if (this.activeSteg === mode) {
|
||||
this.activeSteg = null;
|
||||
this.encodedMessage = '';
|
||||
} else {
|
||||
// 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;
|
||||
// When switching to invisible mode, clear the carrier selection
|
||||
if (mode === 'invisible') {
|
||||
this.selectedCarrier = null;
|
||||
}
|
||||
// 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('<i class="fas fa-check"></i> 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() {
|
||||
if (!this.emojiMessage) {
|
||||
// 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);
|
||||
// Don't auto-copy to avoid clipboard permission errors
|
||||
// Auto-copy will be handled in setStegMode method
|
||||
} else if (this.selectedCarrier) {
|
||||
this.encodedMessage = window.steganography.encodeEmoji(
|
||||
this.selectedCarrier.emoji,
|
||||
this.emojiMessage
|
||||
);
|
||||
// Don't auto-copy to avoid clipboard permission errors
|
||||
// Auto-copy for emoji carrier is handled in selectEmoji method
|
||||
}
|
||||
},
|
||||
autoDecode() {
|
||||
@@ -108,7 +163,21 @@ new Vue({
|
||||
|
||||
if (result) {
|
||||
this.decodedMessage = `Decoded (${result.method}): ${result.text}`;
|
||||
// Don't auto-copy to avoid clipboard permission errors
|
||||
|
||||
// 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(`<i class="fas fa-check"></i> Decoded message copied!`, 'success');
|
||||
|
||||
// Add to copy history
|
||||
this.addToCopyHistory(`Decoded (${result.method})`, decodedText);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.decodedMessage = 'No encoded message detected';
|
||||
}
|
||||
@@ -117,27 +186,47 @@ new Vue({
|
||||
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;
|
||||
|
||||
// Don't auto-copy in preview/iframe environments to avoid permission errors
|
||||
// Only copy when explicitly triggered by a button click
|
||||
const isExplicitUserAction = event && event.isTrusted;
|
||||
|
||||
// Only try to copy if we're not in a restricted environment or it's a direct user action
|
||||
if (isExplicitUserAction) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
|
||||
// Show a success notification
|
||||
this.showNotification('<i class="fas fa-check"></i> Copied!', 'success');
|
||||
} catch (err) {
|
||||
console.warn('Clipboard access not available:', err);
|
||||
|
||||
// Try fallback method for copying (textarea method)
|
||||
this.fallbackCopy(text);
|
||||
}
|
||||
// Always try to copy, regardless of event source
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
|
||||
// Show a success notification
|
||||
this.showNotification('<i class="fas fa-check"></i> 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);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -156,6 +245,23 @@ new Vue({
|
||||
// Show appropriate notification
|
||||
if (successful) {
|
||||
this.showNotification('<i class="fas fa-check"></i> 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('<i class="fas fa-exclamation-triangle"></i> Copy not supported', 'error');
|
||||
}
|
||||
@@ -168,6 +274,73 @@ new Vue({
|
||||
}
|
||||
},
|
||||
|
||||
// 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');
|
||||
@@ -186,6 +359,76 @@ new Vue({
|
||||
}, 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 = '<i class="fas fa-clipboard-check"></i> 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 '';
|
||||
@@ -196,9 +439,13 @@ new Vue({
|
||||
// - 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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,15 +498,127 @@ new Vue({
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// - Try reverse each transform
|
||||
// - 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]/.test(result)) {
|
||||
if (result !== input && /[a-zA-Z0-9\s]{3,}/.test(result)) {
|
||||
return { text: result, method: transform.name };
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -273,86 +632,171 @@ new Vue({
|
||||
|
||||
// Emoji Library Methods
|
||||
filterEmojis() {
|
||||
if (!this.emojiSearch) {
|
||||
this.filteredEmojis = [...window.emojiLibrary.EMOJI_LIST];
|
||||
this.renderEmojiGrid();
|
||||
return;
|
||||
}
|
||||
|
||||
const searchTerm = this.emojiSearch.toLowerCase();
|
||||
this.filteredEmojis = window.emojiLibrary.EMOJI_LIST.filter(emoji => {
|
||||
// Simple search - we could enhance this with emoji names/descriptions later
|
||||
return emoji.toLowerCase().includes(searchTerm);
|
||||
});
|
||||
|
||||
// Always show all emojis - search functionality removed
|
||||
this.filteredEmojis = [...window.emojiLibrary.EMOJI_LIST];
|
||||
this.renderEmojiGrid();
|
||||
},
|
||||
|
||||
selectEmoji(emoji) {
|
||||
this.selectedEmoji = emoji;
|
||||
|
||||
// Insert the emoji at cursor position in the textarea
|
||||
const textarea = document.getElementById('steg-input');
|
||||
if (textarea) {
|
||||
const startPos = textarea.selectionStart;
|
||||
const endPos = textarea.selectionEnd;
|
||||
const currentValue = this.emojiMessage;
|
||||
// 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();
|
||||
|
||||
// Insert emoji at cursor position
|
||||
this.emojiMessage = currentValue.substring(0, startPos) + emoji + currentValue.substring(endPos);
|
||||
|
||||
// Set cursor position after the inserted emoji
|
||||
// Wait for encoding to complete, then copy to clipboard
|
||||
this.$nextTick(() => {
|
||||
textarea.focus();
|
||||
textarea.selectionStart = startPos + emoji.length;
|
||||
textarea.selectionEnd = startPos + emoji.length;
|
||||
|
||||
// Trigger encoding
|
||||
this.autoEncode();
|
||||
if (this.encodedMessage) {
|
||||
// Force clipboard copy regardless of event source
|
||||
this.forceCopyToClipboard(this.encodedMessage);
|
||||
this.showNotification(`<i class="fas fa-check"></i> Copied hidden message with ${emoji}`, 'success');
|
||||
|
||||
// Add to copy history
|
||||
this.addToCopyHistory(`Emoji: ${emoji}`, this.encodedMessage);
|
||||
}
|
||||
});
|
||||
|
||||
this.showNotification(`Emoji ${emoji} inserted`);
|
||||
} 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 = '<i class="fas fa-info-circle"></i> 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
|
||||
// Initialize emoji grid with all emojis shown by default
|
||||
this.$nextTick(() => {
|
||||
this.renderEmojiGrid();
|
||||
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();
|
||||
});
|
||||
},
|
||||
// Keyboard shortcuts
|
||||
created() {
|
||||
window.addEventListener('keydown', (e) => {
|
||||
// Theme toggle
|
||||
if (e.key === 'd' || e.key === 'D') {
|
||||
this.toggleTheme();
|
||||
}
|
||||
// Tab switching
|
||||
else if (e.key === 't' || e.key === 'T') {
|
||||
this.activeTab = 'transforms';
|
||||
}
|
||||
else if (e.key === 'h' || e.key === 'H') {
|
||||
this.activeTab = 'steganography';
|
||||
}
|
||||
// Transform shortcuts (1-9)
|
||||
else if (this.activeTab === 'transforms' && e.key >= '1' && e.key <= '9') {
|
||||
const index = parseInt(e.key) - 1;
|
||||
if (index < this.transforms.length) {
|
||||
this.applyTransform(this.transforms[index]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 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();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
+174
-23
@@ -7,45 +7,137 @@ window.emojiLibrary = {};
|
||||
window.emojiLibrary.EMOJI_LIST = [
|
||||
// Faces and People
|
||||
"😀", // Grinning Face
|
||||
"😁", // Beaming Face with Smiling Eyes
|
||||
"😂", // Face with Tears of Joy
|
||||
"🥰", // Smiling Face with Hearts
|
||||
"😎", // Smiling Face with Sunglasses
|
||||
"🤔", // Thinking Face
|
||||
"😅", // Smiling Face with Sweat
|
||||
"😊", // Smiling Face with Smiling Eyes
|
||||
"😇", // Smiling Face with Halo
|
||||
"🙃", // Upside-Down Face
|
||||
"🤣", // Rolling on the Floor Laughing
|
||||
"😃", // Grinning Face with Big Eyes
|
||||
"😄", // Grinning Face with Smiling Eyes
|
||||
"😅", // Grinning Face with Sweat
|
||||
"😆", // Grinning Squinting Face
|
||||
"😉", // Winking Face
|
||||
"🥳", // Partying Face
|
||||
"😊", // Smiling Face with Smiling Eyes
|
||||
"😋", // Face Savoring Food
|
||||
"😎", // Smiling Face with Sunglasses
|
||||
"😍", // Smiling Face with Heart-Eyes
|
||||
"😘", // Face Blowing a Kiss
|
||||
"🥰", // Smiling Face with Hearts
|
||||
"😗", // Kissing Face
|
||||
"😙", // Kissing Face with Smiling Eyes
|
||||
"😚", // Kissing Face with Closed Eyes
|
||||
"🙂", // Slightly Smiling Face
|
||||
"🤗", // Hugging Face
|
||||
"🤩", // Star-Struck
|
||||
"🤔", // Thinking Face
|
||||
"🤨", // Face with Raised Eyebrow
|
||||
"😐", // Neutral Face
|
||||
"😑", // Expressionless Face
|
||||
"😶", // Face Without Mouth
|
||||
"🙄", // Face with Rolling Eyes
|
||||
"😏", // Smirking Face
|
||||
"😣", // Persevering Face
|
||||
"😥", // Sad but Relieved Face
|
||||
"😮", // Face with Open Mouth
|
||||
"🤐", // Zipper-Mouth Face
|
||||
"😯", // Hushed Face
|
||||
"😪", // Sleepy Face
|
||||
"😫", // Tired Face
|
||||
"😴", // Sleeping Face
|
||||
"🥱", // Yawning Face
|
||||
"😌", // Relieved Face
|
||||
"😛", // Face with Tongue
|
||||
"😜", // Winking Face with Tongue
|
||||
"😝", // Squinting Face with Tongue
|
||||
"🤤", // Drooling Face
|
||||
"😒", // Unamused Face
|
||||
"😓", // Downcast Face with Sweat
|
||||
"😔", // Pensive Face
|
||||
"😕", // Confused Face
|
||||
"🙃", // Upside-Down Face
|
||||
"🤑", // Money-Mouth Face
|
||||
"😲", // Astonished Face
|
||||
"🙁", // Slightly Frowning Face
|
||||
"😖", // Confounded Face
|
||||
"😞", // Disappointed Face
|
||||
"😟", // Worried Face
|
||||
"😤", // Face with Steam From Nose
|
||||
"😢", // Crying Face
|
||||
"😭", // Loudly Crying Face
|
||||
"😧", // Anguished Face
|
||||
"😨", // Fearful Face
|
||||
"😩", // Weary Face
|
||||
"🤯", // Exploding Head
|
||||
"😱", // Face Screaming in Fear
|
||||
"😳", // Flushed Face
|
||||
"🥵", // Hot Face
|
||||
"🥶", // Cold Face
|
||||
"😡", // Pouting Face
|
||||
"😠", // Angry Face
|
||||
"🤬", // Face with Symbols on Mouth
|
||||
"😷", // Face with Medical Mask
|
||||
"🤒", // Face with Thermometer
|
||||
"🤕", // Face with Head-Bandage
|
||||
"🤢", // Nauseated Face
|
||||
"🤮", // Face Vomiting
|
||||
"🤧", // Sneezing Face
|
||||
"😇", // Smiling Face with Halo
|
||||
"🥳", // Partying Face
|
||||
"🥴", // Woozy Face
|
||||
"🥺", // Pleading Face
|
||||
"🧐", // Face with Monocle
|
||||
"🥱", // Yawning Face
|
||||
"🧠", // Brain
|
||||
|
||||
// Gestures and Body Parts
|
||||
"👍", // Thumbs Up
|
||||
"👎", // Thumbs Down
|
||||
"👏", // Clapping Hands
|
||||
"🙌", // Raising Hands
|
||||
"🤝", // Handshake
|
||||
"👋", // Waving Hand
|
||||
"✌️", // Victory Hand
|
||||
"🤟", // Love-You Gesture
|
||||
"🤘", // Sign of the Horns
|
||||
"👊", // Oncoming Fist
|
||||
"✊", // Raised Fist
|
||||
"👆", // Backhand Index Pointing Up
|
||||
"👇", // Backhand Index Pointing Down
|
||||
"👈", // Backhand Index Pointing Left
|
||||
"👉", // Backhand Index Pointing Right
|
||||
"👌", // OK Hand
|
||||
"🤌", // Pinched Fingers
|
||||
"🤏", // Pinching Hand
|
||||
"✋", // Raised Hand
|
||||
"🤚", // Raised Back of Hand
|
||||
"🖐️", // Hand with Fingers Splayed
|
||||
"🖖", // Vulcan Salute
|
||||
"👀", // Eyes
|
||||
"👁️", // Eye
|
||||
"👄", // Mouth
|
||||
"🧿", // Nazar Amulet
|
||||
|
||||
// Celebration & Objects
|
||||
"🎉", // Party Popper
|
||||
"🎊", // Confetti Ball
|
||||
"🎂", // Birthday Cake
|
||||
"🎁", // Wrapped Gift
|
||||
"🎈", // Balloon
|
||||
"🎄", // Christmas Tree
|
||||
"🎃", // Jack-O-Lantern
|
||||
"🏆", // Trophy
|
||||
"🏅", // Sports Medal
|
||||
"🥇", // 1st Place Medal
|
||||
"🥈", // 2nd Place Medal
|
||||
"🥉", // 3rd Place Medal
|
||||
"💰", // Money Bag
|
||||
"💸", // Money with Wings
|
||||
"💵", // Dollar Banknote
|
||||
"💴", // Yen Banknote
|
||||
"💶", // Euro Banknote
|
||||
"💷", // Pound Banknote
|
||||
"💯", // Hundred Points
|
||||
"📱", // Mobile Phone
|
||||
"💻", // Laptop
|
||||
"⌨️", // Keyboard
|
||||
"🖥️", // Desktop Computer
|
||||
"🔒", // Locked
|
||||
"🔓", // Unlocked
|
||||
|
||||
@@ -186,36 +278,84 @@ window.emojiLibrary.EMOJI_LIST = [
|
||||
|
||||
// Function to render emoji grid
|
||||
window.emojiLibrary.renderEmojiGrid = function(containerId, onEmojiSelect, filteredList) {
|
||||
console.log('Rendering emoji grid to:', containerId);
|
||||
|
||||
// Get container by ID
|
||||
const container = document.getElementById(containerId);
|
||||
if (!container) return;
|
||||
if (!container) {
|
||||
console.error('Container not found:', containerId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear container
|
||||
container.innerHTML = '';
|
||||
|
||||
// Create emoji grid
|
||||
// Create emoji grid with enforced styling
|
||||
const gridContainer = document.createElement('div');
|
||||
gridContainer.className = 'emoji-grid';
|
||||
|
||||
// Set a heading explaining the full library is shown
|
||||
if (!filteredList || filteredList.length === window.emojiLibrary.EMOJI_LIST.length) {
|
||||
const fullLibraryNote = document.createElement('div');
|
||||
fullLibraryNote.className = 'emoji-grid-note';
|
||||
fullLibraryNote.innerHTML = '<i class="fas fa-info-circle"></i> Showing all emojis. Use search to filter.';
|
||||
container.appendChild(fullLibraryNote);
|
||||
}
|
||||
// Force grid styling
|
||||
gridContainer.style.display = 'grid';
|
||||
gridContainer.style.gridTemplateColumns = 'repeat(auto-fill, minmax(50px, 1fr))';
|
||||
gridContainer.style.gap = '8px';
|
||||
gridContainer.style.padding = '15px';
|
||||
gridContainer.style.maxHeight = '300px';
|
||||
gridContainer.style.overflowY = 'auto';
|
||||
gridContainer.style.border = '1px solid #ccc';
|
||||
gridContainer.style.borderRadius = '4px';
|
||||
gridContainer.style.margin = '10px 0';
|
||||
|
||||
// Determine which list to use (filtered or full)
|
||||
const emojisToShow = filteredList || window.emojiLibrary.EMOJI_LIST;
|
||||
// Add a message showing we're displaying all emojis
|
||||
const fullLibraryNote = document.createElement('div');
|
||||
fullLibraryNote.className = 'emoji-grid-note';
|
||||
fullLibraryNote.innerHTML = '<i class="fas fa-magic"></i> Click an emoji to automatically copy your hidden message';
|
||||
fullLibraryNote.style.padding = '10px';
|
||||
fullLibraryNote.style.marginBottom = '10px';
|
||||
fullLibraryNote.style.backgroundColor = 'rgba(0,0,0,0.05)';
|
||||
fullLibraryNote.style.borderRadius = '4px';
|
||||
fullLibraryNote.style.textAlign = 'center';
|
||||
container.appendChild(fullLibraryNote);
|
||||
|
||||
// Add emojis to grid
|
||||
// Always use full emoji list - search removed
|
||||
// Use the provided filtered list if available, otherwise default to full list
|
||||
// This ensures we always show ALL emojis regardless of input state
|
||||
const emojisToShow = filteredList && filteredList.length > 0 ? filteredList : window.emojiLibrary.EMOJI_LIST;
|
||||
console.log(`Adding ${emojisToShow.length} emojis to grid`);
|
||||
|
||||
// Add emojis to grid with enforced styling
|
||||
emojisToShow.forEach(emoji => {
|
||||
const emojiButton = document.createElement('button');
|
||||
emojiButton.className = 'emoji-button';
|
||||
emojiButton.innerHTML = emoji;
|
||||
emojiButton.title = 'Click to select this emoji';
|
||||
emojiButton.textContent = emoji; // Use textContent for better emoji handling
|
||||
emojiButton.title = 'Click to encode with this emoji';
|
||||
|
||||
// Force button styling
|
||||
emojiButton.style.fontSize = '24px';
|
||||
emojiButton.style.padding = '8px';
|
||||
emojiButton.style.border = '1px solid #ddd';
|
||||
emojiButton.style.borderRadius = '8px';
|
||||
emojiButton.style.cursor = 'pointer';
|
||||
emojiButton.style.backgroundColor = '#fff';
|
||||
emojiButton.style.transition = 'transform 0.1s';
|
||||
|
||||
// Add hover effect
|
||||
emojiButton.onmouseover = function() {
|
||||
this.style.transform = 'scale(1.1)';
|
||||
this.style.boxShadow = '0 0 5px rgba(0,0,0,0.2)';
|
||||
};
|
||||
emojiButton.onmouseout = function() {
|
||||
this.style.transform = 'scale(1)';
|
||||
this.style.boxShadow = 'none';
|
||||
};
|
||||
|
||||
emojiButton.addEventListener('click', () => {
|
||||
if (typeof onEmojiSelect === 'function') {
|
||||
onEmojiSelect(emoji);
|
||||
// Add visual feedback when clicked
|
||||
emojiButton.style.backgroundColor = '#e6f7ff';
|
||||
setTimeout(() => {
|
||||
emojiButton.style.backgroundColor = '#fff';
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -223,4 +363,15 @@ window.emojiLibrary.renderEmojiGrid = function(containerId, onEmojiSelect, filte
|
||||
});
|
||||
|
||||
container.appendChild(gridContainer);
|
||||
console.log('Emoji grid rendering complete');
|
||||
|
||||
// Force container to be visible
|
||||
container.style.display = 'block !important';
|
||||
container.style.visibility = 'visible !important';
|
||||
|
||||
// Debug info - add count display
|
||||
const countDisplay = document.createElement('div');
|
||||
countDisplay.className = 'emoji-count';
|
||||
countDisplay.textContent = `${emojisToShow.length} emojis available`;
|
||||
container.appendChild(countDisplay);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user