mirror of
https://github.com/elder-plinius/P4RS3LT0NGV3.git
synced 2026-06-06 06:53:56 +02:00
Fix input focus issues and improve clipboard handling
- Fixed input box focus after clicking transform buttons - Improved clipboard handling to prevent unwanted notifications - Removed aggressive focus management in transformInput watcher - Added event object to transform button clicks - Fixed syntax errors in fallback copy method
This commit is contained in:
+220
-22
@@ -3,7 +3,9 @@
|
||||
--main-bg-color: #1a1a1a;
|
||||
--secondary-bg: #242424;
|
||||
--text-color: #e0e0e0;
|
||||
--text-muted: #a0a0a0;
|
||||
--accent-color: #64b5f6;
|
||||
--accent-color-rgb: 100, 181, 246;
|
||||
--accent-hover: #90caf9;
|
||||
--success-color: #81c784;
|
||||
--button-bg: #2d2d2d;
|
||||
@@ -59,6 +61,77 @@ header {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.logo h1 {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-weight: bold;
|
||||
letter-spacing: 2px;
|
||||
text-transform: uppercase;
|
||||
color: #00ff00;
|
||||
text-shadow: 0 0 5px #00ff00, 0 0 10px rgba(0, 255, 0, 0.8);
|
||||
background: linear-gradient(90deg, transparent 0%, rgba(0, 255, 0, 0.2) 50%, transparent 100%);
|
||||
padding: 8px;
|
||||
border: 1px solid rgba(0, 255, 0, 0.3);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
animation: glitch 3s infinite;
|
||||
}
|
||||
|
||||
/* Glitch animation */
|
||||
@keyframes glitch {
|
||||
0% {
|
||||
text-shadow: 0 0 5px #00ff00, 0 0 10px rgba(0, 255, 0, 0.8);
|
||||
transform: translateX(0);
|
||||
}
|
||||
5% {
|
||||
text-shadow: -2px 0 #ff00ff, 2px 0 #00ffff;
|
||||
transform: translateX(2px);
|
||||
}
|
||||
10% {
|
||||
text-shadow: 2px 0 #ff00ff, -2px 0 #00ffff;
|
||||
transform: translateX(-2px);
|
||||
}
|
||||
15% {
|
||||
text-shadow: 0 0 5px #00ff00, 0 0 10px rgba(0, 255, 0, 0.8);
|
||||
transform: translateX(0);
|
||||
}
|
||||
85% {
|
||||
text-shadow: 0 0 5px #00ff00, 0 0 10px rgba(0, 255, 0, 0.8);
|
||||
transform: translateX(0);
|
||||
}
|
||||
90% {
|
||||
text-shadow: 1px 0 #ff00ff, -1px 0 #00ffff;
|
||||
transform: translateX(1px);
|
||||
}
|
||||
95% {
|
||||
text-shadow: -1px 0 #ff00ff, 1px 0 #00ffff;
|
||||
transform: translateX(-1px);
|
||||
}
|
||||
100% {
|
||||
text-shadow: 0 0 5px #00ff00, 0 0 10px rgba(0, 255, 0, 0.8);
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Adding scanline effect */
|
||||
.logo h1::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(
|
||||
transparent 0%,
|
||||
transparent 50%,
|
||||
rgba(0, 255, 0, 0.2) 50%,
|
||||
rgba(0, 255, 0, 0.2) 100%
|
||||
);
|
||||
background-size: 100% 4px;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.theme-toggle {
|
||||
margin-left: 20px;
|
||||
}
|
||||
@@ -163,15 +236,93 @@ h1, h2, h3, h4, h5 {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.section-header h3 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.section-header h3 small {
|
||||
font-size: 0.7em;
|
||||
font-weight: normal;
|
||||
color: var(--text-muted);
|
||||
background-color: rgba(100, 181, 246, 0.1);
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.section-header p {
|
||||
margin: 0;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.decoded-message {
|
||||
margin-top: 8px;
|
||||
padding: 8px;
|
||||
padding: 12px;
|
||||
background: var(--button-bg);
|
||||
border-radius: 4px;
|
||||
font-family: 'Fira Code', monospace;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.decode-method {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.decode-method strong {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.decode-priority {
|
||||
font-size: 0.8em;
|
||||
background-color: rgba(var(--accent-color-rgb), 0.1);
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.decode-result {
|
||||
margin-bottom: 12px;
|
||||
word-break: break-all;
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.decode-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.use-as-input-button {
|
||||
background-color: rgba(var(--accent-color-rgb), 0.1);
|
||||
color: var(--accent-color);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 5px 10px;
|
||||
font-size: 0.8em;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.use-as-input-button:hover {
|
||||
background-color: rgba(var(--accent-color-rgb), 0.2);
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
@@ -345,26 +496,65 @@ textarea {
|
||||
|
||||
.emoji-grid-note {
|
||||
background: linear-gradient(to right, rgba(102, 187, 106, 0.05), rgba(126, 87, 194, 0.05));
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
font-size: 0.85rem;
|
||||
padding: 10px 15px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 12px;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
border-left: 2px solid var(--special-color);
|
||||
box-shadow: none !important;
|
||||
max-width: 450px;
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
border-bottom: none !important;
|
||||
border-top: none !important;
|
||||
border-right: none !important;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.emoji-grid-note i {
|
||||
.emoji-header {
|
||||
padding: 15px 20px;
|
||||
margin-bottom: 15px;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid var(--input-border);
|
||||
background: linear-gradient(to right, rgba(66, 165, 245, 0.02), rgba(126, 87, 194, 0.02));
|
||||
}
|
||||
|
||||
.emoji-header h3 {
|
||||
font-size: 1.3rem;
|
||||
margin: 0 0 8px 0;
|
||||
color: var(--text-color);
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.emoji-header h3 i {
|
||||
color: var(--special-color);
|
||||
font-size: 1rem;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.emoji-subtitle {
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-color);
|
||||
opacity: 0.85;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.emoji-grid-note i, .emoji-subtitle i {
|
||||
color: var(--special-color);
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
@@ -387,16 +577,16 @@ textarea {
|
||||
|
||||
.emoji-grid {
|
||||
display: grid !important;
|
||||
grid-template-columns: repeat(auto-fill, minmax(40px, 1fr)) !important;
|
||||
grid-auto-rows: 40px !important;
|
||||
gap: 4px !important;
|
||||
padding: 10px !important;
|
||||
border-radius: 4px !important;
|
||||
grid-template-columns: repeat(auto-fill, minmax(42px, 1fr)) !important;
|
||||
grid-auto-rows: 42px !important;
|
||||
gap: 6px !important;
|
||||
padding: 12px !important;
|
||||
border-radius: 6px !important;
|
||||
border: 1px solid var(--input-border) !important;
|
||||
background-color: var(--secondary-bg) !important;
|
||||
box-shadow: none !important;
|
||||
transition: all 0.2s ease !important;
|
||||
margin-bottom: 8px !important;
|
||||
margin-bottom: 10px !important;
|
||||
width: 100% !important;
|
||||
max-height: none !important;
|
||||
overflow: visible !important;
|
||||
@@ -406,19 +596,26 @@ textarea {
|
||||
.emoji-category-tabs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
margin-bottom: 8px;
|
||||
gap: 6px;
|
||||
margin-bottom: 10px;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.emoji-category-tab {
|
||||
padding: 5px 10px;
|
||||
padding: 6px 12px;
|
||||
background: var(--button-bg);
|
||||
border: 1px solid var(--input-border);
|
||||
border-radius: 4px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
font-size: 0.9rem;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
min-width: 120px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.emoji-category-tab.active,
|
||||
@@ -451,15 +648,16 @@ textarea {
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 1.3rem;
|
||||
font-size: 1.5rem;
|
||||
background-color: var(--button-bg);
|
||||
border: 1px solid var(--input-border);
|
||||
border-radius: 3px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.emoji-button:before {
|
||||
|
||||
+50
-26
@@ -51,14 +51,14 @@
|
||||
<div class="tab-buttons">
|
||||
<button
|
||||
:class="{ active: activeTab === 'transforms' }"
|
||||
@click="activeTab = 'transforms'"
|
||||
@click="switchToTab('transforms')"
|
||||
title="Transform text (T)"
|
||||
>
|
||||
<i class="fas fa-font"></i> Transform
|
||||
</button>
|
||||
<button
|
||||
:class="{ active: activeTab === 'steganography' }"
|
||||
@click="activeTab = 'steganography'"
|
||||
@click="switchToTab('steganography')"
|
||||
title="Hide text in emojis (H)"
|
||||
>
|
||||
<i class="fas fa-smile"></i> Emoji
|
||||
@@ -77,16 +77,12 @@
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="transform-section">
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Emoji Library Section -->
|
||||
<div class="emoji-library">
|
||||
<div class="emoji-library-header">
|
||||
<div class="emoji-library-title">
|
||||
<h3><i class="fas fa-icons"></i> Choose an Emoji</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Emoji grid container - this MUST have id="emoji-grid-container" -->
|
||||
<div id="emoji-grid-container" class="emoji-grid-container">
|
||||
<!-- Dynamic content will be inserted here by JavaScript -->
|
||||
@@ -136,8 +132,14 @@
|
||||
<!-- Universal Decoder Section for Steganography Tab -->
|
||||
<div class="decode-section">
|
||||
<div class="section-header">
|
||||
<h3><i class="fas fa-magic"></i> Universal Decoder</h3>
|
||||
<p>Paste any encoded text to try all decoding methods at once</p>
|
||||
<h3>
|
||||
<i class="fas fa-magic"></i> Universal Decoder
|
||||
<small v-if="activeTransform">(Prioritizing {{ activeTransform.name }})</small>
|
||||
</h3>
|
||||
<p v-if="activeTransform && transformHasReverse(activeTransform)">
|
||||
Paste encoded text to decode with {{ activeTransform.name }} or try other methods
|
||||
</p>
|
||||
<p v-else>Paste any encoded text to try all decoding methods at once</p>
|
||||
</div>
|
||||
<div class="input-container">
|
||||
<textarea
|
||||
@@ -147,11 +149,19 @@
|
||||
@input="runUniversalDecode"
|
||||
></textarea>
|
||||
<div class="decoded-message" v-if="universalDecodeResult">
|
||||
<div class="decode-method">Decoded using: <strong>{{ universalDecodeResult.method }}</strong></div>
|
||||
<div class="decode-method">
|
||||
<span>Decoded using: <strong>{{ universalDecodeResult.method }}</strong></span>
|
||||
<span v-if="activeTransform && universalDecodeResult.method === activeTransform.name" class="decode-priority">(Priority Match)</span>
|
||||
</div>
|
||||
<div class="decode-result">{{ universalDecodeResult.text }}</div>
|
||||
<button class="copy-button" @click="copyToClipboard(universalDecodeResult.text)" title="Copy decoded text">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
<div class="decode-actions">
|
||||
<button class="copy-button" @click="copyToClipboard(universalDecodeResult.text)" title="Copy decoded text">
|
||||
<i class="fas fa-copy"></i> Copy
|
||||
</button>
|
||||
<button class="use-as-input-button" v-if="universalDecodeResult.text" @click="transformInput = universalDecodeResult.text" title="Use this as input for transforms">
|
||||
<i class="fas fa-arrow-up"></i> Use as Input
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -187,7 +197,7 @@
|
||||
<div class="transform-buttons">
|
||||
<div v-for="transform in getTransformsByCategory('encoding')" :key="transform.name" class="transform-button-group">
|
||||
<button
|
||||
@click="applyTransform(transform)"
|
||||
@click="applyTransform(transform, $event)"
|
||||
class="transform-button transform-category-encoding"
|
||||
:class="{ active: activeTransform === transform }"
|
||||
:title="'Click to transform and copy: ' + transform.name"
|
||||
@@ -216,7 +226,7 @@
|
||||
<div class="transform-buttons">
|
||||
<div v-for="transform in getTransformsByCategory('cipher')" :key="transform.name" class="transform-button-group">
|
||||
<button
|
||||
@click="applyTransform(transform)"
|
||||
@click="applyTransform(transform, $event)"
|
||||
class="transform-button transform-category-cipher"
|
||||
:class="{ active: activeTransform === transform }"
|
||||
:title="'Click to transform and copy: ' + transform.name"
|
||||
@@ -245,7 +255,7 @@
|
||||
<div class="transform-buttons">
|
||||
<div v-for="transform in getTransformsByCategory('visual')" :key="transform.name" class="transform-button-group">
|
||||
<button
|
||||
@click="applyTransform(transform)"
|
||||
@click="applyTransform(transform, $event)"
|
||||
class="transform-button transform-category-visual"
|
||||
:class="{ active: activeTransform === transform }"
|
||||
:title="'Click to transform and copy: ' + transform.name"
|
||||
@@ -274,7 +284,7 @@
|
||||
<div class="transform-buttons">
|
||||
<div v-for="transform in getTransformsByCategory('format')" :key="transform.name" class="transform-button-group">
|
||||
<button
|
||||
@click="applyTransform(transform)"
|
||||
@click="applyTransform(transform, $event)"
|
||||
class="transform-button transform-category-format"
|
||||
:class="{ active: activeTransform === transform }"
|
||||
:title="'Click to transform and copy: ' + transform.name"
|
||||
@@ -303,7 +313,7 @@
|
||||
<div class="transform-buttons">
|
||||
<div v-for="transform in getTransformsByCategory('unicode')" :key="transform.name" class="transform-button-group">
|
||||
<button
|
||||
@click="applyTransform(transform)"
|
||||
@click="applyTransform(transform, $event)"
|
||||
class="transform-button transform-category-unicode"
|
||||
:class="{ active: activeTransform === transform }"
|
||||
:title="'Click to transform and copy: ' + transform.name"
|
||||
@@ -332,7 +342,7 @@
|
||||
<div class="transform-buttons">
|
||||
<div v-for="transform in getTransformsByCategory('special')" :key="transform.name" class="transform-button-group">
|
||||
<button
|
||||
@click="applyTransform(transform)"
|
||||
@click="applyTransform(transform, $event)"
|
||||
class="transform-button transform-category-special"
|
||||
:class="{ active: activeTransform === transform }"
|
||||
:title="'Click to transform and copy: ' + transform.name"
|
||||
@@ -373,8 +383,14 @@
|
||||
<!-- Universal Decoder Section for Transforms Tab -->
|
||||
<div class="decode-section">
|
||||
<div class="section-header">
|
||||
<h3><i class="fas fa-magic"></i> Universal Decoder</h3>
|
||||
<p>Paste any encoded text to try all decoding methods at once</p>
|
||||
<h3>
|
||||
<i class="fas fa-magic"></i> Universal Decoder
|
||||
<small v-if="activeTransform">(Prioritizing {{ activeTransform.name }})</small>
|
||||
</h3>
|
||||
<p v-if="activeTransform && transformHasReverse(activeTransform)">
|
||||
Paste encoded text to decode with {{ activeTransform.name }} or try other methods
|
||||
</p>
|
||||
<p v-else>Paste any encoded text to try all decoding methods at once</p>
|
||||
</div>
|
||||
<div class="input-container">
|
||||
<textarea
|
||||
@@ -384,11 +400,19 @@
|
||||
@input="runUniversalDecode"
|
||||
></textarea>
|
||||
<div class="decoded-message" v-if="universalDecodeResult">
|
||||
<div class="decode-method">Decoded using: <strong>{{ universalDecodeResult.method }}</strong></div>
|
||||
<div class="decode-method">
|
||||
<span>Decoded using: <strong>{{ universalDecodeResult.method }}</strong></span>
|
||||
<span v-if="activeTransform && universalDecodeResult.method === activeTransform.name" class="decode-priority">(Priority Match)</span>
|
||||
</div>
|
||||
<div class="decode-result">{{ universalDecodeResult.text }}</div>
|
||||
<button class="copy-button" @click="copyToClipboard(universalDecodeResult.text)" title="Copy decoded text">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
<div class="decode-actions">
|
||||
<button class="copy-button" @click="copyToClipboard(universalDecodeResult.text)" title="Copy decoded text">
|
||||
<i class="fas fa-copy"></i> Copy
|
||||
</button>
|
||||
<button class="use-as-input-button" v-if="universalDecodeResult.text" @click="transformInput = universalDecodeResult.text" title="Use this as input for transforms">
|
||||
<i class="fas fa-arrow-up"></i> Use as Input
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -38,6 +38,10 @@ window.app = new Vue({
|
||||
universalDecodeInput: '',
|
||||
universalDecodeResult: null,
|
||||
isPasteOperation: false, // Flag to track paste operations
|
||||
lastCopyTime: 0, // Timestamp of last copy operation for debounce
|
||||
ignoreKeyboardEvents: false, // Flag to prevent keyboard events from triggering copies
|
||||
isTransformCopy: false, // Flag to mark transform-initiated copy operations
|
||||
keyboardEventsTimeout: null, // Timeout for resetting keyboard event flag
|
||||
activeSteg: null,
|
||||
carriers: window.steganography.carriers,
|
||||
showDecoder: true,
|
||||
@@ -51,6 +55,33 @@ window.app = new Vue({
|
||||
showCopyHistory: false
|
||||
},
|
||||
methods: {
|
||||
// Switch between tabs with proper initialization
|
||||
switchToTab(tabName) {
|
||||
this.activeTab = tabName;
|
||||
console.log('Switched to tab:', tabName);
|
||||
|
||||
// Reset universal decoder input when switching tabs
|
||||
this.universalDecodeInput = '';
|
||||
this.universalDecodeResult = null;
|
||||
|
||||
// Initialize emoji grid when switching to steganography tab
|
||||
if (tabName === 'steganography') {
|
||||
this.$nextTick(() => {
|
||||
console.log('Tab switch: Initializing emoji grid');
|
||||
const emojiGridContainer = document.getElementById('emoji-grid-container');
|
||||
if (emojiGridContainer) {
|
||||
console.log('Found emoji grid container after tab switch');
|
||||
// Make sure the container is visible
|
||||
emojiGridContainer.setAttribute('style', 'display: block !important; visibility: visible !important; min-height: 300px; padding: 10px;');
|
||||
// Render the emoji grid
|
||||
this.renderEmojiGrid();
|
||||
} else {
|
||||
console.log('Emoji grid container not found after tab switch');
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Get transforms grouped by category
|
||||
getTransformsByCategory(category) {
|
||||
return this.transforms.filter(transform =>
|
||||
@@ -81,12 +112,19 @@ window.app = new Vue({
|
||||
},
|
||||
|
||||
// Transform Methods
|
||||
applyTransform(transform) {
|
||||
applyTransform(transform, event) {
|
||||
if (this.transformInput) {
|
||||
// Prevent default button behavior
|
||||
event && event.preventDefault();
|
||||
|
||||
// Update active transform and apply it
|
||||
this.activeTransform = transform;
|
||||
this.transformOutput = transform.func(this.transformInput);
|
||||
|
||||
// Force copy and log to history
|
||||
// Set flag to mark this as a transform-initiated copy
|
||||
this.isTransformCopy = true;
|
||||
|
||||
// Force copy the transform output to clipboard
|
||||
this.forceCopyToClipboard(this.transformOutput);
|
||||
|
||||
// Add to copy history
|
||||
@@ -94,18 +132,30 @@ window.app = new Vue({
|
||||
|
||||
// Enhanced notification for transform and copy
|
||||
this.showNotification(`<i class="fas fa-check"></i> ${transform.name} applied and copied!`, 'success');
|
||||
|
||||
// Remove active state from transform buttons
|
||||
document.querySelectorAll('.transform-button').forEach(button => {
|
||||
button.classList.remove('active');
|
||||
});
|
||||
|
||||
// Keep focus on input and move cursor to end
|
||||
const inputBox = document.querySelector('#transform-input');
|
||||
if (inputBox) {
|
||||
inputBox.focus();
|
||||
const len = inputBox.value.length;
|
||||
inputBox.setSelectionRange(len, len);
|
||||
}
|
||||
|
||||
// Reset flags immediately
|
||||
this.isTransformCopy = false;
|
||||
this.ignoreKeyboardEvents = false;
|
||||
}
|
||||
},
|
||||
autoTransform() {
|
||||
// Only proceed if we're in the transforms tab and have an active transform
|
||||
if (this.transformInput && this.activeTransform && this.activeTab === 'transforms') {
|
||||
// Update the output without copying
|
||||
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);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -258,9 +308,30 @@ window.app = new Vue({
|
||||
},
|
||||
|
||||
// Utility Methods
|
||||
// Track last copy operation to prevent rapid repeated copies
|
||||
lastCopyTime: 0,
|
||||
|
||||
async copyToClipboard(text) {
|
||||
if (!text) return;
|
||||
|
||||
// Check clipboard lock - don't proceed if locked
|
||||
if (this.clipboardLocked) {
|
||||
console.log('Copy operation prevented by clipboard lock');
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent rapid successive copy operations (debounce)
|
||||
const now = Date.now();
|
||||
if (now - this.lastCopyTime < 500) {
|
||||
console.log('Copy operation debounced');
|
||||
return;
|
||||
}
|
||||
this.lastCopyTime = now;
|
||||
|
||||
// Set clipboard lock immediately
|
||||
this.clipboardLocked = true;
|
||||
console.log('Setting clipboard lock during regular copy');
|
||||
|
||||
// Always try to copy, regardless of event source
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
@@ -271,6 +342,25 @@ window.app = new Vue({
|
||||
// Add to history - determine source from active tab or context
|
||||
const source = this.activeTab === 'transforms' ? 'Transform' : 'Steganography';
|
||||
this.addToCopyHistory(source, text);
|
||||
|
||||
// Aggressively clear focus and selections
|
||||
if (document.activeElement && document.activeElement.blur) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
|
||||
// Clear any text selection
|
||||
if (window.getSelection) {
|
||||
window.getSelection().removeAllRanges();
|
||||
}
|
||||
|
||||
// Focus body to avoid any specific interactive elements
|
||||
document.body.focus();
|
||||
|
||||
// Release clipboard lock after a longer delay
|
||||
setTimeout(() => {
|
||||
this.clipboardLocked = false;
|
||||
console.log('Clipboard lock released after regular copy');
|
||||
}, 500);
|
||||
} catch (err) {
|
||||
console.warn('Clipboard access not available:', err);
|
||||
|
||||
@@ -281,10 +371,31 @@ window.app = new Vue({
|
||||
|
||||
fallbackCopy(text) {
|
||||
try {
|
||||
// Check if keyboard events should be ignored
|
||||
if (this.ignoreKeyboardEvents && !this.isTransformCopy) {
|
||||
console.log('Ignoring fallback copy due to keyboard event flag');
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset the transform flag if it was set
|
||||
if (this.isTransformCopy) {
|
||||
this.isTransformCopy = false;
|
||||
}
|
||||
|
||||
// Debounce check
|
||||
const now = Date.now();
|
||||
if (now - this.lastCopyTime < 300) {
|
||||
console.log('Fallback copy operation debounced');
|
||||
return;
|
||||
}
|
||||
this.lastCopyTime = now;
|
||||
|
||||
// Create temporary textarea
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = text;
|
||||
textarea.style.position = 'fixed'; // Avoid scrolling to bottom
|
||||
textarea.style.left = '-9999px'; // Move offscreen
|
||||
textarea.style.top = '0';
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
|
||||
@@ -295,11 +406,8 @@ window.app = new Vue({
|
||||
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
|
||||
// Add to history with 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') {
|
||||
@@ -309,7 +417,6 @@ window.app = new Vue({
|
||||
source = `Emoji: ${this.selectedEmoji}`;
|
||||
}
|
||||
}
|
||||
|
||||
this.addToCopyHistory(source, text);
|
||||
} else {
|
||||
this.showNotification('<i class="fas fa-exclamation-triangle"></i> Copy not supported', 'error');
|
||||
@@ -317,6 +424,19 @@ window.app = new Vue({
|
||||
|
||||
// Clean up
|
||||
document.body.removeChild(textarea);
|
||||
|
||||
// Aggressively clear focus and selection
|
||||
if (document.activeElement && document.activeElement.blur) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
|
||||
// Clear any text selection
|
||||
if (window.getSelection) {
|
||||
window.getSelection().removeAllRanges();
|
||||
}
|
||||
|
||||
// Focus on body element
|
||||
document.body.focus();
|
||||
} catch (err) {
|
||||
console.warn('Fallback copy method failed:', err);
|
||||
this.showNotification('<i class="fas fa-exclamation-triangle"></i> Copy not supported', 'error');
|
||||
@@ -327,52 +447,79 @@ window.app = new Vue({
|
||||
forceCopyToClipboard(text) {
|
||||
if (!text) return;
|
||||
|
||||
// Skip notifications if this was triggered by a paste operation
|
||||
// Skip copy operations during paste
|
||||
if (this.isPasteOperation) {
|
||||
console.log('Skipping clipboard notification for paste operation');
|
||||
this.isPasteOperation = false;
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Force copying to clipboard:', text);
|
||||
// Block keyboard-triggered copies unless it's a transform
|
||||
if (!this.isTransformCopy && this.ignoreKeyboardEvents) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Try to use the Clipboard API first
|
||||
// Use Clipboard API
|
||||
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();
|
||||
// Only show notification for transform copies
|
||||
if (this.isTransformCopy) {
|
||||
this.showCopiedPopup();
|
||||
|
||||
// Temporarily disable keyboard events
|
||||
this.ignoreKeyboardEvents = true;
|
||||
clearTimeout(this.keyboardEventsTimeout);
|
||||
this.keyboardEventsTimeout = setTimeout(() => {
|
||||
this.ignoreKeyboardEvents = false;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// Reset transform flag
|
||||
this.isTransformCopy = false;
|
||||
|
||||
// Focus back on input
|
||||
const inputBox = document.querySelector('#transform-input');
|
||||
if (inputBox) {
|
||||
inputBox.focus();
|
||||
const len = inputBox.value.length;
|
||||
inputBox.setSelectionRange(len, len);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.warn('Force Clipboard API failed:', err);
|
||||
console.warn('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 {
|
||||
// If clipboard is locked, don't proceed
|
||||
if (this.clipboardLocked) {
|
||||
console.log('Fallback copy prevented by clipboard lock');
|
||||
return;
|
||||
}
|
||||
|
||||
// Set clipboard lock immediately
|
||||
this.clipboardLocked = true;
|
||||
|
||||
// Create temporary textarea for copying
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = text;
|
||||
textarea.style.position = 'fixed';
|
||||
textarea.style.left = '-9999px';
|
||||
textarea.style.top = '0';
|
||||
document.body.appendChild(textarea);
|
||||
|
||||
// Focus and select the text
|
||||
textarea.focus();
|
||||
textarea.select();
|
||||
|
||||
@@ -383,9 +530,25 @@ window.app = new Vue({
|
||||
console.error('Force fallback copy command failed:', err);
|
||||
}
|
||||
|
||||
// Remove the temporary element
|
||||
document.body.removeChild(textarea);
|
||||
|
||||
// Keep focus on input
|
||||
const inputBox = document.querySelector('#transform-input');
|
||||
if (inputBox) {
|
||||
inputBox.focus();
|
||||
const len = inputBox.value.length;
|
||||
inputBox.setSelectionRange(len, len);
|
||||
}
|
||||
|
||||
// Reset flags immediately
|
||||
this.clipboardLocked = false;
|
||||
this.isTransformCopy = false;
|
||||
this.ignoreKeyboardEvents = false;
|
||||
console.log('Clipboard lock released after fallback copy');
|
||||
} catch (err) {
|
||||
console.error('Force fallback copy method failed:', err);
|
||||
this.clipboardLocked = false; // Make sure we don't leave it locked in case of error
|
||||
}
|
||||
},
|
||||
|
||||
@@ -464,7 +627,28 @@ window.app = new Vue({
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to decode using all available methods
|
||||
// Try to decode using the currently selected transform first, if any
|
||||
if (this.activeTransform && this.transformHasReverse(this.activeTransform)) {
|
||||
try {
|
||||
console.log(`Trying to decode with currently selected transform: ${this.activeTransform.name}`);
|
||||
const decodedText = this.activeTransform.reverse(this.universalDecodeInput);
|
||||
|
||||
// If the decoded text is different from the input and looks like readable text
|
||||
if (decodedText !== this.universalDecodeInput && /[a-zA-Z0-9\s]{3,}/.test(decodedText)) {
|
||||
this.universalDecodeResult = {
|
||||
text: decodedText,
|
||||
method: this.activeTransform.name
|
||||
};
|
||||
console.log(`Successfully decoded with ${this.activeTransform.name}`);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Error decoding with selected transform ${this.activeTransform.name}:`, e);
|
||||
}
|
||||
}
|
||||
|
||||
// If the selected transform didn't work or there isn't one selected,
|
||||
// fall back to trying all available methods
|
||||
const result = this.universalDecode(this.universalDecodeInput);
|
||||
|
||||
// Update the result
|
||||
@@ -507,26 +691,60 @@ window.app = new Vue({
|
||||
}
|
||||
|
||||
// 2. Try transform reversals
|
||||
// - Binary
|
||||
// Try to decode using active transform first
|
||||
if (this.activeTab === 'transforms' && this.activeTransform) {
|
||||
try {
|
||||
const transformKey = Object.keys(window.transforms).find(
|
||||
key => window.transforms[key].name === this.activeTransform.name
|
||||
);
|
||||
|
||||
if (transformKey && window.transforms[transformKey].reverse) {
|
||||
const result = window.transforms[transformKey].reverse(input);
|
||||
if (result && result !== input) {
|
||||
return {
|
||||
text: result,
|
||||
method: this.activeTransform.name,
|
||||
priorityMatch: true
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error decoding with active transform:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// - Binary (improved with more patterns)
|
||||
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
|
||||
if (result && /[\x20-\x7E]{3,}/.test(result)) { // Make sure it's readable ASCII
|
||||
return { text: result, method: 'Binary' };
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
// Try different binary formats (with and without spaces)
|
||||
const variations = [
|
||||
input.trim(), // Original input
|
||||
input.replace(/\s+/g, ''), // No spaces
|
||||
input.replace(/([01]{8})/g, '$1 ') // Force 8-bit spacing
|
||||
];
|
||||
|
||||
for (const binVariation of variations) {
|
||||
// Fallback implementation
|
||||
const binText = input.replace(/\s+/g, '');
|
||||
const binText = binVariation.replace(/\s+/g, '');
|
||||
let result = '';
|
||||
|
||||
// Try standard 8-bit ASCII
|
||||
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
|
||||
|
||||
if (result && /[\x20-\x7E]{3,}/.test(result)) { // Make sure it's readable ASCII
|
||||
return { text: result, method: 'Binary' };
|
||||
}
|
||||
}
|
||||
@@ -962,32 +1180,39 @@ window.app = new Vue({
|
||||
|
||||
// Define a function to properly initialize the emoji grid
|
||||
const initializeEmojiGrid = () => {
|
||||
// Only try to initialize when steganography tab is active
|
||||
if (this.activeTab !== 'steganography') {
|
||||
return;
|
||||
}
|
||||
|
||||
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;');
|
||||
emojiGridContainer.setAttribute('style', 'display: block !important; visibility: visible !important; min-height: 300px; padding: 10px;');
|
||||
|
||||
// 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;');
|
||||
emojiLibrary.setAttribute('style', 'display: block !important; visibility: visible !important; margin-top: 20px; overflow: visible;');
|
||||
}
|
||||
|
||||
// Now render the grid
|
||||
this.renderEmojiGrid();
|
||||
console.log('Emoji grid rendering complete in mounted()');
|
||||
|
||||
// Stop retrying once we've successfully found and rendered the grid
|
||||
clearInterval(emojiGridInitializer);
|
||||
} else {
|
||||
console.error('emoji-grid-container not found, will retry');
|
||||
// Try again after a longer delay if not found
|
||||
setTimeout(initializeEmojiGrid, 500);
|
||||
console.log('emoji-grid-container not found, will retry when steganography tab is active');
|
||||
}
|
||||
};
|
||||
|
||||
// Start with a small delay to ensure DOM is ready
|
||||
setTimeout(initializeEmojiGrid, 100);
|
||||
// Use an interval instead of recursive setTimeout for more reliable initialization
|
||||
// This will try every 500ms until it succeeds or the page is navigated away from
|
||||
const emojiGridInitializer = setInterval(initializeEmojiGrid, 500);
|
||||
|
||||
// Set up paste event handlers for all textareas to prevent unwanted clipboard notifications
|
||||
this.setupPasteHandlers();
|
||||
@@ -1018,11 +1243,17 @@ window.app = new Vue({
|
||||
// But no keyboard shortcuts/hotkeys for now
|
||||
},
|
||||
|
||||
// Watch for input events and ensure emojis stay visible
|
||||
// Watch for input events and ensure proper focus handling
|
||||
watch: {
|
||||
// Watch transform input to update transforms
|
||||
transformInput() {
|
||||
// Only auto-transform if we have an active transform
|
||||
if (this.activeTransform && this.activeTab === 'transforms') {
|
||||
this.transformOutput = this.activeTransform.func(this.transformInput);
|
||||
}
|
||||
},
|
||||
// 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();
|
||||
@@ -1030,7 +1261,6 @@ window.app = new Vue({
|
||||
},
|
||||
// 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();
|
||||
|
||||
+71
-208
@@ -427,17 +427,38 @@ window.emojiLibrary.EMOJI_LIST = [
|
||||
"🇮🇪", // Ireland
|
||||
];
|
||||
|
||||
// Define standard emoji categories using the Unicode CLDR categorization
|
||||
// Define emoji categories with specific emojis for each category
|
||||
window.emojiLibrary.EMOJIS = {
|
||||
faces_people: ["😀", "😁", "😂", "🤣", "😃", "😄", "😅", "😆", "😉", "😊", "😋", "😎", "😍", "😘", "🥰", "😗", "😙", "😚", "🙂", "🤗", "🤩", "🤔", "🤨", "😐", "😑", "😶", "🙄", "😏", "😣", "😥", "😮", "🤐", "😯", "😪", "😫", "😴", "😌", "😛", "😜", "😝", "🤤", "😒", "😓", "😔", "😕", "🙃", "🤑", "😲", "🙁", "😖", "😞", "😟", "😤", "😢", "😭", "😧", "😨", "😩", "🤯", "😱", "😳", "🥵", "🥶", "😡", "😠", "🤬", "😷", "🤒", "🤕", "🤢", "🤮", "🤧", "😇", "🥳", "🥴", "🥺", "🧐", "🥱", "🧠"],
|
||||
|
||||
gestures: ["👍", "👎", "👌", "✌️", "🤞", "🤟", "🤘", "🤙", "👈", "👉", "👆", "👇", "🖕", "☝️", "✋", "🤚", "🖐️", "🖖", "👋", "🤏", "👐", "🙌", "👏", "🤝", "🙏"],
|
||||
|
||||
animals_nature: ["🦊", "🦁", "🐯", "🐮", "🐷", "🐸", "🐵", "🐔", "🐧", "🐦", "🐤", "🦆", "🦅", "🦉", "🦇", "🐺", "🐗", "🐴", "🐝", "🐛", "🦋", "🐌", "🐞", "🐜", "🕷️", "🦂", "🐍", "🦨", "🦩", "🦫", "🦬", "🐻❄️", "🐼", "🐨", "🐕", "🐶", "🐩", "🐈", "🐱"],
|
||||
|
||||
activities_sports: ["⚽", "🏀", "🏈", "🏐", "🏉", "🎾", "🎳", "🏑", "🏒", "🏓", "🏸", "🥊", "🥋", "🥅", "🤾", "🎿", "🏄", "🏂", "🏊", "🏋️", "🤼", "🤸", "🤺", "🤽", "🤹", "🎯", "🎱", "🎽", "🚴", "🚵"],
|
||||
|
||||
technology_objects: ["💻", "⌨️", "🖥️", "🖱️", "🖨️", "📱", "☎️", "📞", "📟", "📠", "📺", "📻", "🎙️", "🎚️", "🎛️", "🧭", "📡", "🔋", "🔌", "💡", "🛢️", "💸", "💵", "💳", "🔑", "🔓", "🔒"],
|
||||
|
||||
mystical_fantasy: ["🧙", "🧚", "🧛", "🧜", "👹", "👺", "👻", "👽", "👾", "🔮", "🪄", "🐉", "🐲", "🦄"],
|
||||
|
||||
nature_weather: ["🌈", "🌞", "🌙", "⭐", "🌟", "⚡", "❄️", "🔥", "💧", "🌊", "🌪️", "🌋"],
|
||||
|
||||
symbols: ["❤️", "💛", "💚", "💙", "💜", "💔", "💕", "💞", "💓", "💗", "💖", "💘", "💝", "💟", "💢", "💣", "💥", "💦", "💨", "💩", "💫", "💬", "💠", "💮"],
|
||||
|
||||
flags: ["🏁", "🚩", "🎌", "🏴", "🏳️", "🏳️🌈", "🏳️⚧️", "🏴☠️", "🇺🇸", "🇨🇦", "🇬🇧", "🇩🇪", "🇫🇷", "🇮🇹", "🇯🇵", "🇰🇷", "🇷🇺", "🇨🇳", "🇮🇳", "🇧🇷", "🇦🇺", "🇪🇸", "🇳🇱", "🇸🇪"]
|
||||
};
|
||||
|
||||
// Define standard emoji categories
|
||||
window.emojiLibrary.CATEGORIES = [
|
||||
{ id: 'all', name: 'All Emojis', icon: '🔍' },
|
||||
{ id: 'smileys', name: 'Smileys & Emotion', icon: '😀' },
|
||||
{ id: 'people', name: 'People & Body', icon: '👋' },
|
||||
{ id: 'animals', name: 'Animals & Nature', icon: '🐵' },
|
||||
{ id: 'food', name: 'Food & Drink', icon: '🍎' },
|
||||
{ id: 'travel', name: 'Travel & Places', icon: '🚗' },
|
||||
{ id: 'activities', name: 'Activities', icon: '⚽' },
|
||||
{ id: 'objects', name: 'Objects', icon: '💡' },
|
||||
{ id: 'symbols', name: 'Symbols', icon: '🔣' },
|
||||
{ id: 'faces_people', name: 'Faces & People', icon: '😀' },
|
||||
{ id: 'gestures', name: 'Gestures', icon: '👍' },
|
||||
{ id: 'animals_nature', name: 'Animals & Nature', icon: '🦊' },
|
||||
{ id: 'activities_sports', name: 'Activities & Sports', icon: '⚽' },
|
||||
{ id: 'technology_objects', name: 'Tech & Objects', icon: '💻' },
|
||||
{ id: 'mystical_fantasy', name: 'Mystical & Fantasy', icon: '🧙' },
|
||||
{ id: 'nature_weather', name: 'Nature & Weather', icon: '🌈' },
|
||||
{ id: 'symbols', name: 'Symbols', icon: '❤️' },
|
||||
{ id: 'flags', name: 'Flags', icon: '🏁' }
|
||||
];
|
||||
|
||||
@@ -455,11 +476,11 @@ window.emojiLibrary.renderEmojiGrid = function(containerId, onEmojiSelect, filte
|
||||
// Clear container
|
||||
container.innerHTML = '';
|
||||
|
||||
// Create grid note
|
||||
const gridNote = document.createElement('div');
|
||||
gridNote.className = 'emoji-grid-note';
|
||||
gridNote.innerHTML = '<i class="fas fa-magic"></i> Click any emoji to automatically copy your hidden message';
|
||||
container.appendChild(gridNote);
|
||||
// Add header with instruction message
|
||||
const emojiHeader = document.createElement('div');
|
||||
emojiHeader.className = 'emoji-header';
|
||||
emojiHeader.innerHTML = '<h3><i class="fas fa-icons"></i> Choose an Emoji</h3><p class="emoji-subtitle"><i class="fas fa-magic"></i> Click any emoji to copy your hidden message</p>';
|
||||
container.appendChild(emojiHeader);
|
||||
|
||||
// Create category tabs
|
||||
const categoryTabs = document.createElement('div');
|
||||
@@ -483,12 +504,30 @@ window.emojiLibrary.renderEmojiGrid = function(containerId, onEmojiSelect, filte
|
||||
const gridContainer = document.createElement('div');
|
||||
gridContainer.className = 'emoji-grid';
|
||||
|
||||
// Combine all emojis for a larger selection
|
||||
const allEmojis = [...window.emojiLibrary.EMOJI_LIST, ...window.emojiLibrary.ADDITIONAL_EMOJIS];
|
||||
// Get the active category
|
||||
let activeCategory = 'all';
|
||||
const activeCategoryTab = container.querySelector('.emoji-category-tab.active');
|
||||
if (activeCategoryTab) {
|
||||
activeCategory = activeCategoryTab.getAttribute('data-category');
|
||||
}
|
||||
|
||||
// Use the provided filtered list if available, otherwise default to full list
|
||||
const emojisToShow = filteredList && filteredList.length > 0 ? filteredList : allEmojis;
|
||||
console.log(`Adding ${emojisToShow.length} emojis to grid`);
|
||||
// Determine which emojis to show based on category and filter
|
||||
let emojisToShow = [];
|
||||
|
||||
if (filteredList && filteredList.length > 0) {
|
||||
// If we have a filtered list (from search), use that
|
||||
emojisToShow = filteredList;
|
||||
} else if (activeCategory === 'all') {
|
||||
// For 'all' category, combine all emojis from the categories
|
||||
Object.values(window.emojiLibrary.EMOJIS).forEach(categoryEmojis => {
|
||||
emojisToShow = [...emojisToShow, ...categoryEmojis];
|
||||
});
|
||||
} else if (window.emojiLibrary.EMOJIS[activeCategory]) {
|
||||
// For specific category, use emojis from that category
|
||||
emojisToShow = window.emojiLibrary.EMOJIS[activeCategory];
|
||||
}
|
||||
|
||||
console.log(`Adding ${emojisToShow.length} emojis to grid for category: ${activeCategory}`);
|
||||
|
||||
// Add emojis to grid with enforced styling
|
||||
emojisToShow.forEach(emoji => {
|
||||
@@ -514,197 +553,21 @@ window.emojiLibrary.renderEmojiGrid = function(containerId, onEmojiSelect, filte
|
||||
container.appendChild(gridContainer);
|
||||
console.log('Emoji grid rendering complete');
|
||||
|
||||
// Helper function to categorize emojis using standard Unicode ranges
|
||||
function categorizeEmoji(emoji) {
|
||||
// Get the code point of the emoji
|
||||
const code = emoji.codePointAt(0);
|
||||
|
||||
// Smileys & Emotion (faces, emotions, hearts)
|
||||
if ((code >= 0x1F600 && code <= 0x1F64F) || // Emoticons
|
||||
(code >= 0x1F910 && code <= 0x1F92F) || // Face-hand
|
||||
(code >= 0x1F970 && code <= 0x1F97A) || // Faces
|
||||
(code >= 0x1F9D0 && code <= 0x1F9DF) || // Faces
|
||||
(code >= 0x2763 && code <= 0x2764) || // Hearts
|
||||
(code >= 0x1F48B && code <= 0x1F49F) || // Hearts and love
|
||||
(code >= 0x1F493 && code <= 0x1F49F) || // Hearts
|
||||
emoji === '😀' || emoji === '😃' || emoji === '😄' || emoji === '😁' || emoji === '😆' ||
|
||||
emoji === '😅' || emoji === '😂' || emoji === '🤣' || emoji === '☺️' || emoji === '😊') {
|
||||
return 'smileys';
|
||||
}
|
||||
|
||||
// People & Body (people, hands, body parts)
|
||||
if ((code >= 0x1F466 && code <= 0x1F487) || // People
|
||||
(code >= 0x1F9D1 && code <= 0x1F9DD) || // People
|
||||
(code >= 0x1F468 && code <= 0x1F469) || // Man/Woman
|
||||
(code >= 0x1F46E && code <= 0x1F9CF) || // People roles
|
||||
(code >= 0x1F44B && code <= 0x1F450) || // Hands
|
||||
(code >= 0x1F918 && code <= 0x1F91F) || // Hand symbols
|
||||
(code >= 0x1F926 && code <= 0x1F937) || // People gestures
|
||||
emoji.includes('👨') || emoji.includes('👩') || emoji.includes('🧑') ||
|
||||
emoji.includes('👶') || emoji.includes('👦') || emoji.includes('👧') ||
|
||||
emoji.includes('🧒') || emoji.includes('👴') || emoji.includes('👵') ||
|
||||
emoji.includes('🧓') || emoji.includes('👮') || emoji.includes('👷')) {
|
||||
return 'people';
|
||||
}
|
||||
|
||||
// Animals & Nature (animals, plants, weather)
|
||||
if ((code >= 0x1F400 && code <= 0x1F43F) || // Animals
|
||||
(code >= 0x1F980 && code <= 0x1F9AF) || // Animals
|
||||
(code >= 0x1F330 && code <= 0x1F33F) || // Plants
|
||||
(code >= 0x1F340 && code <= 0x1F37F) || // More plants
|
||||
(code >= 0x1F300 && code <= 0x1F32C) || // Weather
|
||||
emoji === '🐵' || emoji === '🐒' || emoji === '🦍' || emoji === '🦧' ||
|
||||
emoji === '🐶' || emoji === '🐕' || emoji === '🦮' || emoji === '🐩' ||
|
||||
emoji === '🐺' || emoji === '🦊' || emoji === '🦝' || emoji === '🐱' ||
|
||||
emoji === '🌱' || emoji === '🌲' || emoji === '🌳' || emoji === '🌴' ||
|
||||
emoji === '🌵' || emoji === '🌷' || emoji === '🌸' || emoji === '🌹') {
|
||||
return 'animals';
|
||||
}
|
||||
|
||||
// Food & Drink
|
||||
if ((code >= 0x1F32D && code <= 0x1F37F) || // Food items
|
||||
(code >= 0x1F95F && code <= 0x1F9AA) || // More food
|
||||
(code >= 0x1F950 && code <= 0x1F96F) || // More food
|
||||
emoji === '🍇' || emoji === '🍈' || emoji === '🍉' || emoji === '🍊' ||
|
||||
emoji === '🍋' || emoji === '🍌' || emoji === '🍍' || emoji === '🥭' ||
|
||||
emoji === '🍎' || emoji === '🍏' || emoji === '🍐' || emoji === '🍑' ||
|
||||
emoji === '🍒' || emoji === '🍓' || emoji === '🥝' || emoji === '🍅' ||
|
||||
emoji === '🥥' || emoji === '🥑' || emoji === '🍆' || emoji === '🥔') {
|
||||
return 'food';
|
||||
}
|
||||
|
||||
// Travel & Places (transportation, buildings, maps)
|
||||
if ((code >= 0x1F680 && code <= 0x1F6FF) || // Transport
|
||||
(code >= 0x1F30D && code <= 0x1F32C) || // Earth/Weather
|
||||
(code >= 0x1F3D7 && code <= 0x1F3DB) || // Buildings
|
||||
(code >= 0x1F3E0 && code <= 0x1F3F0) || // Buildings
|
||||
(code >= 0x26E9 && code <= 0x26F5) || // Buildings/Places
|
||||
emoji === '🚗' || emoji === '🚕' || emoji === '🚙' || emoji === '🚌' ||
|
||||
emoji === '🚎' || emoji === '🏎️' || emoji === '🚓' || emoji === '🚑' ||
|
||||
emoji === '🚒' || emoji === '🚐' || emoji === '🛻' || emoji === '🚚' ||
|
||||
emoji === '🚛' || emoji === '🚜' || emoji === '🛵' || emoji === '🏍️' ||
|
||||
emoji === '🛺' || emoji === '🚲' || emoji === '🛴' || emoji === '🚏') {
|
||||
return 'travel';
|
||||
}
|
||||
|
||||
// Activities (sports, music, arts, hobbies)
|
||||
if ((code >= 0x1F380 && code <= 0x1F3A0) || // Events
|
||||
(code >= 0x1F3A3 && code <= 0x1F3BE) || // Sports
|
||||
(code >= 0x1F3BF && code <= 0x1F3C9) || // Sports
|
||||
(code >= 0x1F3CF && code <= 0x1F3D6) || // Sports
|
||||
(code >= 0x1F3F8 && code <= 0x1F3FF) || // Activities
|
||||
(code >= 0x1F93A && code <= 0x1F94F) || // Sports
|
||||
emoji === '⚽' || emoji === '⚾' || emoji === '🏀' || emoji === '🏐' ||
|
||||
emoji === '🏈' || emoji === '🏉' || emoji === '🎾' || emoji === '🥏' ||
|
||||
emoji === '🎳' || emoji === '🏏' || emoji === '🏑' || emoji === '🏒' ||
|
||||
emoji === '🥍' || emoji === '🏓' || emoji === '🏸' || emoji === '🥊') {
|
||||
return 'activities';
|
||||
}
|
||||
|
||||
// Objects (household, office, tools)
|
||||
if ((code >= 0x1F4A1 && code <= 0x1F4CC) || // Office
|
||||
(code >= 0x1F4D0 && code <= 0x1F4F7) || // Office/Tools
|
||||
(code >= 0x1F4FF && code <= 0x1F53D) || // Various objects
|
||||
(code >= 0x1F56F && code <= 0x1F5A4) || // Objects
|
||||
(code >= 0x1F5D1 && code <= 0x1F5FF) || // Office objects
|
||||
(code >= 0x1F6D1 && code <= 0x1F6DF) || // Misc objects
|
||||
emoji === '⌚' || emoji === '📱' || emoji === '📲' || emoji === '💻' ||
|
||||
emoji === '⌨️' || emoji === '🖥️' || emoji === '🖨️' || emoji === '🖱️' ||
|
||||
emoji === '🖲️' || emoji === '🕹️' || emoji === '🗜️' || emoji === '💽' ||
|
||||
emoji === '💾' || emoji === '💿' || emoji === '📀' || emoji === '📼') {
|
||||
return 'objects';
|
||||
}
|
||||
|
||||
// Symbols (punctuation, alphanum, geometric, etc)
|
||||
if ((code >= 0x1F300 && code <= 0x1F320) || // Various symbols
|
||||
(code >= 0x1F170 && code <= 0x1F251) || // Enclosed characters
|
||||
(code >= 0x1F523 && code <= 0x1F5FF) || // Symbols
|
||||
(code >= 0x2600 && code <= 0x26FF) || // Misc symbols
|
||||
(code >= 0x2700 && code <= 0x27BF) || // Dingbats
|
||||
(code >= 0x1F5FB && code <= 0x1F64F) || // Symbols
|
||||
(code >= 0x1F680 && code <= 0x1F6FF) || // Transport symbols
|
||||
emoji === '💯' || emoji === '📛' || emoji === '🔰' || emoji === '⭕' ||
|
||||
emoji === '✅' || emoji === '☑️' || emoji === '✔️' || emoji === '❌' ||
|
||||
emoji === '❎' || emoji === '➰' || emoji === '➿' || emoji === '〽️' ||
|
||||
emoji === '✳️' || emoji === '✴️' || emoji === '❇️' || emoji === '©️') {
|
||||
return 'symbols';
|
||||
}
|
||||
|
||||
// Flags (country flags, flag symbols)
|
||||
if ((code >= 0x1F1E6 && code <= 0x1F1FF) || // Regional indicators for flags
|
||||
emoji === '🏁' || emoji === '🚩' || emoji === '🎌' || emoji === '🏴' ||
|
||||
emoji.includes('🏳️') || // Flag variants
|
||||
emoji.includes('🏴') || // Flag variants
|
||||
// Check for country flags (pairs of regional indicators)
|
||||
(emoji.length >= 2 &&
|
||||
emoji.codePointAt(0) >= 0x1F1E6 && emoji.codePointAt(0) <= 0x1F1FF &&
|
||||
emoji.codePointAt(2) >= 0x1F1E6 && emoji.codePointAt(2) <= 0x1F1FF)) {
|
||||
return 'flags';
|
||||
}
|
||||
|
||||
// Default to 'all' if we can't categorize
|
||||
return 'all';
|
||||
}
|
||||
|
||||
// Add event listeners to category tabs with actual filtering
|
||||
document.querySelectorAll('.emoji-category-tab').forEach(tab => {
|
||||
tab.addEventListener('click', function() {
|
||||
// Remove active class from all tabs
|
||||
document.querySelectorAll('.emoji-category-tab').forEach(t => {
|
||||
t.classList.remove('active');
|
||||
});
|
||||
// Add active class to clicked tab
|
||||
this.classList.add('active');
|
||||
// Add event listeners to category tabs
|
||||
const categoryTabButtons = container.querySelectorAll('.emoji-category-tab');
|
||||
categoryTabButtons.forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
// Update active tab
|
||||
categoryTabButtons.forEach(t => t.classList.remove('active'));
|
||||
tab.classList.add('active');
|
||||
|
||||
const selectedCategory = this.getAttribute('data-category');
|
||||
console.log('Selected category:', selectedCategory);
|
||||
// Re-render the emoji grid with the selected category
|
||||
const selectedCategory = tab.getAttribute('data-category');
|
||||
console.log('Category selected:', selectedCategory);
|
||||
|
||||
// Get all emoji buttons
|
||||
const allEmojis = [...window.emojiLibrary.EMOJI_LIST, ...window.emojiLibrary.ADDITIONAL_EMOJIS];
|
||||
|
||||
// Filter emojis based on selected category
|
||||
let filteredEmojis = allEmojis;
|
||||
if (selectedCategory !== 'all') {
|
||||
filteredEmojis = allEmojis.filter(emoji => {
|
||||
const category = categorizeEmoji(emoji);
|
||||
console.log(`Emoji: ${emoji}, Category: ${category}`);
|
||||
return category === selectedCategory;
|
||||
});
|
||||
}
|
||||
|
||||
// Clear and rebuild the grid with filtered emojis
|
||||
const gridContainer = container.querySelector('.emoji-grid');
|
||||
if (gridContainer) {
|
||||
// Clear existing emojis
|
||||
gridContainer.innerHTML = '';
|
||||
|
||||
// Add filtered emojis
|
||||
filteredEmojis.forEach(emoji => {
|
||||
const emojiButton = document.createElement('button');
|
||||
emojiButton.className = 'emoji-button';
|
||||
emojiButton.textContent = emoji;
|
||||
emojiButton.title = 'Click to encode with this emoji';
|
||||
|
||||
emojiButton.addEventListener('click', () => {
|
||||
if (typeof onEmojiSelect === 'function') {
|
||||
onEmojiSelect(emoji);
|
||||
// Add visual feedback when clicked
|
||||
emojiButton.style.backgroundColor = '#e6f7ff';
|
||||
setTimeout(() => {
|
||||
emojiButton.style.backgroundColor = '';
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
gridContainer.appendChild(emojiButton);
|
||||
});
|
||||
|
||||
// Update the count display
|
||||
const countDisplay = container.querySelector('.emoji-count');
|
||||
if (countDisplay) {
|
||||
countDisplay.textContent = `${filteredEmojis.length} emojis available`;
|
||||
}
|
||||
}
|
||||
// Clear and recreate the grid
|
||||
container.removeChild(gridContainer);
|
||||
window.emojiLibrary.renderEmojiGrid(containerId, onEmojiSelect);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user