UI: Auto-copy transformed/hidden text

- Add automatic clipboard copy when text is transformed
- Add automatic clipboard copy when text is hidden/encoded
- Add automatic clipboard copy when text is decoded
- Remove manual copy buttons and keyboard shortcuts
- Fix transform button functionality
This commit is contained in:
EP
2025-03-09 17:50:23 -04:00
parent 92245931cd
commit 1637dd3603
4 changed files with 378 additions and 21 deletions
+6 -21
View File
@@ -43,7 +43,6 @@
<button
:class="{ active: activeTab === 'transforms' }"
@click="activeTab = 'transforms'"
@keyup.t="activeTab = 'transforms'"
title="Transform text (T)"
>
<i class="fas fa-font"></i> Transform
@@ -51,7 +50,6 @@
<button
:class="{ active: activeTab === 'steganography' }"
@click="activeTab = 'steganography'"
@keyup.h="activeTab = 'steganography'"
title="Hide text (H)"
>
<i class="fas fa-eye-slash"></i> Hide
@@ -102,9 +100,7 @@
<div class="output-section" v-if="encodedMessage">
<div class="output-container">
<textarea readonly v-model="encodedMessage"></textarea>
<button class="copy-button" @click="copyToClipboard(encodedMessage)" title="Copy to clipboard">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
@@ -118,9 +114,7 @@
></textarea>
<div class="decoded-message" v-if="decodedMessage">
{{ decodedMessage }}
<button class="copy-button" @click="copyToClipboard(decodedMessage)" title="Copy decoded text">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
</div>
@@ -144,16 +138,15 @@
<button
v-for="(transform, index) in transforms"
:key="transform.name"
@click="applyTransform(transform.func)"
@keyup="index < 9 ? applyTransform(transform.func) : null"
@click="applyTransform(transform)"
class="transform-button"
:class="{ active: activeTransform === transform.name }"
:class="{ active: activeTransform === transform }"
:title="transform.name + (index < 9 ? ' (' + (index + 1) + ')' : '')"
:data-shortcut="index < 9 ? index + 1 : ''"
>
{{ transform.name }}
<small class="transform-preview" v-if="transformInput">
{{ transform.preview ? transform.preview(transformInput.slice(0, 10)) : '' }}
{{ transform.preview(transformInput.slice(0, 10)) }}
</small>
<kbd v-if="index < 9">{{ index + 1 }}</kbd>
</button>
@@ -167,15 +160,7 @@
v-model="transformOutput"
aria-label="Transformed text output"
></textarea>
<button
class="copy-button"
@click="copyToClipboard(transformOutput)"
@keyup.c="copyToClipboard(transformOutput)"
title="Copy to clipboard (C)"
aria-label="Copy transformed text to clipboard"
>
<i class="fas fa-copy"></i>
</button>
</div>
</div>
</div>
+147
View File
@@ -0,0 +1,147 @@
// Initialize Vue 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,
activeSteg: null,
carriers: window.steganography.carriers,
showDecoder: true
},
methods: {
// Theme Toggle
toggleTheme() {
this.isDarkTheme = !this.isDarkTheme;
document.body.classList.toggle('light-theme');
},
// Transform Methods
applyTransform(transform) {
if (this.transformInput) {
this.activeTransform = transform;
this.transformOutput = transform.func(this.transformInput);
this.copyToClipboard(this.transformOutput);
}
},
autoTransform() {
if (this.transformInput && this.activeTransform) {
this.transformOutput = this.activeTransform.func(this.transformInput);
this.copyToClipboard(this.transformOutput);
}
},
// Steganography Methods
selectCarrier(carrier) {
this.selectedCarrier = carrier;
this.activeSteg = 'emoji';
this.autoEncode();
},
setStegMode(mode) {
this.activeSteg = mode;
this.autoEncode();
},
autoEncode() {
if (!this.emojiMessage) {
this.encodedMessage = '';
return;
}
if (this.activeSteg === 'invisible') {
this.encodedMessage = window.steganography.encodeInvisible(this.emojiMessage);
this.copyToClipboard(this.encodedMessage);
} else if (this.selectedCarrier) {
this.encodedMessage = window.steganography.encodeEmoji(
this.selectedCarrier.emoji,
this.emojiMessage
);
this.copyToClipboard(this.encodedMessage);
}
},
autoDecode() {
if (!this.decodeInput) {
this.decodedMessage = '';
return;
}
// Try invisible text decoding
let decoded = window.steganography.decodeInvisible(this.decodeInput);
if (decoded) {
this.decodedMessage = decoded;
this.copyToClipboard(decoded);
return;
}
// Try emoji decoding
decoded = window.steganography.decodeEmoji(this.decodeInput);
if (decoded) {
this.decodedMessage = decoded;
this.copyToClipboard(decoded);
return;
}
this.decodedMessage = 'No hidden message found';
},
previewInvisible(text) {
return '[invisible]';
},
// Utility Methods
async copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
} catch (err) {
console.error('Failed to copy text:', err);
}
}
},
// Initialize theme
mounted() {
if (this.isDarkTheme) {
document.body.classList.add('dark-theme');
}
},
// 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]);
}
}
});
}
});
+72
View File
@@ -0,0 +1,72 @@
// Steganography carriers
const carriers = [
{ emoji: '🐍', name: 'SNAKE', desc: 'Classic Snake', preview: text => `🐍${text}` },
{ emoji: '🐉', name: 'DRAGON', desc: 'Mystical Dragon', preview: text => `🐉${text}` },
{ emoji: '🦎', name: 'LIZARD', desc: 'Sneaky Lizard', preview: text => `🦎${text}` },
{ emoji: '🐊', name: 'CROCODILE', desc: 'Dangerous Croc', preview: text => `🐊${text}` }
];
// Variation selector functions
function toVariationSelector(byte) {
return String.fromCodePoint(0xFE00 + byte);
}
function fromVariationSelector(codePoint) {
return codePoint - 0xFE00;
}
// Emoji encoding/decoding
function encodeEmoji(emoji, text) {
if (!text) return '';
const bytes = new TextEncoder().encode(text);
let result = emoji;
for (const byte of bytes) {
result += toVariationSelector(byte);
}
return result;
}
function decodeEmoji(text) {
if (!text) return '';
const matches = [...text.matchAll(/[\uFE00-\uFE0F]/g)];
if (!matches.length) return '';
const bytes = new Uint8Array(matches.map(m => fromVariationSelector(m[0].codePointAt(0))));
return new TextDecoder().decode(bytes);
}
// Invisible text encoding/decoding
function encodeInvisible(text) {
if (!text) return '';
const bytes = new TextEncoder().encode(text);
return Array.from(bytes)
.map(byte => String.fromCodePoint(0xE0000 + byte))
.join('');
}
function decodeInvisible(text) {
if (!text) return '';
const matches = [...text.matchAll(/[\uE0000-\uE007F]/g)];
if (!matches.length) return '';
const bytes = new Uint8Array(
matches.map(m => m[0].codePointAt(0) - 0xE0000)
);
return new TextDecoder().decode(bytes);
}
// Export for use in app.js
window.steganography = {
carriers,
encodeEmoji,
decodeEmoji,
encodeInvisible,
decodeInvisible
};
+153
View File
@@ -0,0 +1,153 @@
// Text transformation functions
const transforms = {
// Basic transforms
upside_down: {
name: 'Upside Down',
map: {
'a': 'ɐ', 'b': 'q', 'c': 'ɔ', 'd': 'p', 'e': 'ǝ', 'f': 'ɟ', 'g': 'ƃ', 'h': 'ɥ', 'i': 'ᴉ',
'j': 'ɾ', 'k': 'ʞ', 'l': 'l', 'm': 'ɯ', 'n': 'u', 'o': 'o', 'p': 'd', 'q': 'b', 'r': 'ɹ',
's': 's', 't': 'ʇ', 'u': 'n', 'v': 'ʌ', 'w': 'ʍ', 'x': 'x', 'y': 'ʎ', 'z': 'z',
'A': '∀', 'B': 'B', 'C': 'Ɔ', 'D': 'D', 'E': 'Ǝ', 'F': 'Ⅎ', 'G': 'פ', 'H': 'H', 'I': 'I',
'J': 'ſ', 'K': 'K', 'L': '˥', 'M': 'W', 'N': 'N', 'O': 'O', 'P': 'Ԁ', 'Q': 'Q', 'R': 'R',
'S': 'S', 'T': '┴', 'U': '∩', 'V': 'Λ', 'W': 'M', 'X': 'X', 'Y': '⅄', 'Z': 'Z',
'0': '0', '1': 'Ɩ', '2': 'ᄅ', '3': 'Ɛ', '4': 'ㄣ', '5': 'ϛ', '6': '9', '7': 'ㄥ',
'8': '8', '9': '6', '.': '˙', ',': "'", '?': '¿', '!': '¡', '"': ',,', "'": ',',
'(': ')', ')': '(', '[': ']', ']': '[', '{': '}', '}': '{', '<': '>', '>': '<',
'&': '⅋', '_': '‾'
},
func: function(text) {
return [...text].map(c => this.map[c] || c).reverse().join('');
},
preview: function(text) {
return this.func(text);
}
},
elder_futhark: {
name: 'Elder Futhark',
map: {
'a': 'ᚨ', 'b': 'ᛒ', 'c': 'ᛲ', 'd': 'ᛞ', 'e': '', 'f': 'ᚠ', 'g': '', 'h': 'ᚺ', 'i': '',
'j': 'ᛃ', 'k': 'ᛲ', 'l': 'ᛚ', 'm': 'ᛗ', 'n': 'ᚾ', 'o': 'ᛟ', 'p': 'ᛈ', 'q': 'ᛲᛩ', 'r': 'ᚱ',
's': 'ᛋ', 't': 'ᛏ', 'u': 'ᚢ', 'v': 'ᛩ', 'w': 'ᛩ', 'x': 'ᛲᛋ', 'y': '', 'z': 'ᛉ'
},
func: function(text) {
return [...text.toLowerCase()].map(c => this.map[c] || c).join('');
},
preview: function(text) {
return this.func(text);
}
},
vaporwave: {
name: 'Vaporwave',
func: function(text) {
return [...text].join(' ');
},
preview: function(text) {
return this.func(text);
}
},
zalgo: {
name: 'Zalgo',
marks: [
'\u0300', '\u0301', '\u0302', '\u0303', '\u0304', '\u0305', '\u0306', '\u0307', '\u0308',
'\u0309', '\u030A', '\u030B', '\u030C', '\u030D', '\u030E', '\u030F', '\u0310', '\u0311',
'\u0312', '\u0313', '\u0314', '\u0315', '\u031A', '\u031B', '\u033D', '\u033E', '\u033F'
],
func: function(text) {
return [...text].map(c => {
let result = c;
for (let i = 0; i < Math.floor(Math.random() * 3) + 1; i++) {
result += this.marks[Math.floor(Math.random() * this.marks.length)];
}
return result;
}).join('');
},
preview: function(text) {
return this.func(text);
}
},
small_caps: {
name: 'Small Caps',
map: {
'a': 'ᴀ', 'b': 'ʙ', 'c': '', 'd': 'ᴅ', 'e': 'ᴇ', 'f': 'ꜰ', 'g': 'ɢ', 'h': 'ʜ', 'i': 'ɪ',
'j': 'ᴊ', 'k': 'ᴋ', 'l': 'ʟ', 'm': 'ᴍ', 'n': 'ɴ', 'o': '', 'p': 'ᴘ', 'q': 'ǫ', 'r': 'ʀ',
's': 's', 't': 'ᴛ', 'u': '', 'v': '', 'w': '', 'x': 'x', 'y': 'ʏ', 'z': ''
},
func: function(text) {
return [...text.toLowerCase()].map(c => this.map[c] || c).join('');
},
preview: function(text) {
return this.func(text);
}
},
braille: {
name: 'Braille',
map: {
'a': '⠁', 'b': '⠃', 'c': '⠉', 'd': '⠙', 'e': '⠑', 'f': '⠋', 'g': '⠛', 'h': '⠓', 'i': '⠊',
'j': '⠚', 'k': '⠅', 'l': '⠇', 'm': '⠍', 'n': '⠝', 'o': '⠕', 'p': '⠏', 'q': '⠟', 'r': '⠗',
's': '⠎', 't': '⠞', 'u': '⠥', 'v': '⠧', 'w': '⠺', 'x': '⠭', 'y': '⠽', 'z': '⠵',
'0': '⠼⠚', '1': '⠼⠁', '2': '⠼⠃', '3': '⠼⠉', '4': '⠼⠙', '5': '⠼⠑',
'6': '⠼⠋', '7': '⠼⠛', '8': '⠼⠓', '9': '⠼⠊'
},
func: function(text) {
return [...text.toLowerCase()].map(c => this.map[c] || c).join('');
},
preview: function(text) {
return this.func(text);
}
},
bubble: {
name: 'Bubble',
map: {
'a': 'ⓐ', 'b': 'ⓑ', 'c': 'ⓒ', 'd': 'ⓓ', 'e': 'ⓔ', 'f': 'ⓕ', 'g': 'ⓖ', 'h': 'ⓗ', 'i': 'ⓘ',
'j': 'ⓙ', 'k': 'ⓚ', 'l': 'ⓛ', 'm': 'ⓜ', 'n': 'ⓝ', 'o': 'ⓞ', 'p': 'ⓟ', 'q': 'ⓠ', 'r': 'ⓡ',
's': 'ⓢ', 't': 'ⓣ', 'u': 'ⓤ', 'v': 'ⓥ', 'w': 'ⓦ', 'x': 'ⓧ', 'y': 'ⓨ', 'z': 'ⓩ',
'A': 'Ⓐ', 'B': 'Ⓑ', 'C': 'Ⓒ', 'D': 'Ⓓ', 'E': 'Ⓔ', 'F': 'Ⓕ', 'G': 'Ⓖ', 'H': 'Ⓗ', 'I': 'Ⓘ',
'J': 'Ⓙ', 'K': 'Ⓚ', 'L': 'Ⓛ', 'M': 'Ⓜ', 'N': 'Ⓝ', 'O': 'Ⓞ', 'P': 'Ⓟ', 'Q': 'Ⓠ', 'R': 'Ⓡ',
'S': 'Ⓢ', 'T': 'Ⓣ', 'U': 'Ⓤ', 'V': 'Ⓥ', 'W': 'Ⓦ', 'X': 'Ⓧ', 'Y': 'Ⓨ', 'Z': 'Ⓩ'
},
func: function(text) {
return [...text].map(c => this.map[c] || c).join('');
},
preview: function(text) {
return this.func(text);
}
},
morse: {
name: 'Morse Code',
map: {
'a': '.-', 'b': '-...', 'c': '-.-.', 'd': '-..', 'e': '.', 'f': '..-.',
'g': '--.', 'h': '....', 'i': '..', 'j': '.---', 'k': '-.-', 'l': '.-..',
'm': '--', 'n': '-.', 'o': '---', 'p': '.--.', 'q': '--.-', 'r': '.-.',
's': '...', 't': '-', 'u': '..-', 'v': '...-', 'w': '.--', 'x': '-..-',
'y': '-.--', 'z': '--..', '0': '-----', '1': '.----', '2': '..---',
'3': '...--', '4': '....-', '5': '.....', '6': '-....', '7': '--...',
'8': '---..', '9': '----.'
},
func: function(text) {
return [...text.toLowerCase()].map(c => this.map[c] || c).join(' ');
},
preview: function(text) {
return this.func(text);
}
},
binary: {
name: 'Binary',
func: function(text) {
return [...text].map(c => c.charCodeAt(0).toString(2).padStart(8, '0')).join(' ');
},
preview: function(text) {
return this.func(text);
}
}
};
// Export transforms for use in app.js
window.transforms = transforms;