mirror of
https://github.com/elder-plinius/P4RS3LT0NGV3.git
synced 2026-09-24 10:10:45 +02:00
Tokenade UX: live length estimator, single-carrier mode with carrier input and drag/paste payload; safer ZW char handling; updated labels
This commit is contained in:
+26
-2
@@ -173,6 +173,30 @@
|
||||
Auto-copy
|
||||
<input type="checkbox" v-model="tbAutoCopy" />
|
||||
</label>
|
||||
<label title="Condense payload into a single emoji carrier">
|
||||
Single carrier mode
|
||||
<input type="checkbox" v-model="tbSingleCarrier" />
|
||||
</label>
|
||||
<label v-if="tbSingleCarrier" title="Pick the carrier emoji">
|
||||
Carrier
|
||||
<input type="text" v-model="tbCarrier" placeholder="Drop or type an emoji" @focus="setCarrierFromSelected" />
|
||||
</label>
|
||||
</div>
|
||||
<div v-if="tbSingleCarrier" class="tokenade-payload-drop">
|
||||
<div class="drop-zone"
|
||||
@dragover.prevent
|
||||
@drop="handleTokenadeDrop"
|
||||
@paste="handleTokenadePaste"
|
||||
title="Drag/Drop or paste emojis here to embed into the carrier">
|
||||
Drop or paste emojis to embed →
|
||||
</div>
|
||||
<div class="payload-list" v-if="tbPayloadEmojis.length">
|
||||
<div class="payload-item" v-for="(em, idx) in tbPayloadEmojis" :key="idx">
|
||||
<span class="payload-emoji">{{ em }}</span>
|
||||
<button class="decode-button" @click="removeTokenadePayloadAt(idx)" title="Remove">✖</button>
|
||||
</div>
|
||||
<button class="decode-button" @click="clearTokenadePayload" title="Clear all">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="token-bomb-actions">
|
||||
<button class="transform-button" @click="generateTokenBomb" title="Build the tokenade with current settings">
|
||||
@@ -181,9 +205,9 @@
|
||||
<button class="copy-button" v-if="tokenBombOutput" @click="copyToClipboard(tokenBombOutput)">
|
||||
<i class="fas fa-copy"></i> Copy
|
||||
</button>
|
||||
<div class="output-instructions" v-if="tokenBombOutput">
|
||||
<div class="output-instructions">
|
||||
<small>
|
||||
Length: {{ tokenBombOutput.length.toLocaleString() }} chars · Tip: Increasing depth/breadth grows size <em>multiplicatively</em>.
|
||||
Estimated length: {{ estimateTokenadeLength().toLocaleString() }} chars
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -61,6 +61,9 @@ window.app = new Vue({
|
||||
tbIncludeNoise: true,
|
||||
tbRandomizeEmojis: true,
|
||||
tbAutoCopy: false,
|
||||
tbSingleCarrier: false,
|
||||
tbCarrier: '',
|
||||
tbPayloadEmojis: [],
|
||||
tokenBombOutput: '',
|
||||
|
||||
// History of copied content
|
||||
@@ -1681,7 +1684,7 @@ window.app = new Vue({
|
||||
const depth = Math.max(1, Math.min(8, Number(this.tbDepth) || 1));
|
||||
const breadth = Math.max(1, Math.min(12, Number(this.tbBreadth) || 1));
|
||||
const repeats = Math.max(1, Math.min(100, Number(this.tbRepeats) || 1));
|
||||
const sep = this.tbSeparator === 'zwj' ? '\u200D' : this.tbSeparator === 'zwnj' ? '\u200C' : this.tbSeparator === 'zwsp' ? '\u200B' : '';
|
||||
const sep = this.tbSeparator === 'zwj' ? '\u200D'.replace('\\u','\u') : this.tbSeparator === 'zwnj' ? '\u200C'.replace('\\u','\u') : this.tbSeparator === 'zwsp' ? '\u200B'.replace('\\u','\u') : '';
|
||||
const includeVS = !!this.tbIncludeVS;
|
||||
const includeNoise = !!this.tbIncludeNoise;
|
||||
const randomize = !!this.tbRandomizeEmojis;
|
||||
@@ -1700,8 +1703,8 @@ window.app = new Vue({
|
||||
function addVS(str) {
|
||||
if (!includeVS) return str;
|
||||
// Alternate VS16/VS15 to maximize tokenization churn
|
||||
const vs16 = '\uFE0F';
|
||||
const vs15 = '\uFE0E';
|
||||
const vs16 = '\uFE0F'.replace('\\u','\u');
|
||||
const vs15 = '\uFE0E'.replace('\\u','\u');
|
||||
let out = '';
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const ch = str[i];
|
||||
@@ -1712,7 +1715,7 @@ window.app = new Vue({
|
||||
|
||||
function noise() {
|
||||
if (!includeNoise) return '';
|
||||
const parts = ['\u200B','\u200C','\u200D','\u2060','\u2062','\u2063'];
|
||||
const parts = ['\u200B'.replace('\\u','\u'),'\u200C'.replace('\\u','\u'),'\u200D'.replace('\\u','\u'),'\u2060'.replace('\\u','\u'),'\u2062'.replace('\\u','\u'),'\u2063'.replace('\\u','\u')];
|
||||
let s = '';
|
||||
const n = 1 + Math.floor(Math.random() * 3);
|
||||
for (let i = 0; i < n; i++) s += parts[Math.floor(Math.random() * parts.length)];
|
||||
@@ -1731,17 +1734,58 @@ window.app = new Vue({
|
||||
}
|
||||
return items.join(sep);
|
||||
}
|
||||
if (this.tbSingleCarrier) {
|
||||
const carrier = (this.tbCarrier && String(this.tbCarrier)) || (this.selectedEmoji ? String(this.selectedEmoji) : '💥');
|
||||
function countUnits(level) {
|
||||
if (level === 0) return breadth;
|
||||
return breadth * countUnits(level - 1);
|
||||
}
|
||||
const unitsPerBlock = countUnits(depth - 1);
|
||||
const totalUnits = Math.max(1, repeats * unitsPerBlock);
|
||||
|
||||
let block = buildLevel(depth - 1);
|
||||
// Repeat the block to increase token length
|
||||
const blocks = [];
|
||||
for (let i = 0; i < repeats; i++) {
|
||||
blocks.push(block + noise());
|
||||
let payload = [];
|
||||
if (this.tbPayloadEmojis && this.tbPayloadEmojis.length > 0) {
|
||||
for (let i = 0; i < totalUnits; i++) {
|
||||
payload.push(String(this.tbPayloadEmojis[i % this.tbPayloadEmojis.length]));
|
||||
}
|
||||
} else {
|
||||
payload = pickEmojis(totalUnits);
|
||||
}
|
||||
|
||||
function toTagSeqForEmojiChar(ch) {
|
||||
const cp = ch.codePointAt(0);
|
||||
const hex = cp.toString(16);
|
||||
let seq = '';
|
||||
for (const d of hex) {
|
||||
if (d >= '0' && d <= '9') {
|
||||
const base = 0xE0030 + (d.charCodeAt(0) - '0'.charCodeAt(0));
|
||||
seq += String.fromCodePoint(base);
|
||||
} else {
|
||||
const base = 0xE0061 + (d.charCodeAt(0) - 'a'.charCodeAt(0));
|
||||
seq += String.fromCodePoint(base);
|
||||
}
|
||||
}
|
||||
seq += String.fromCodePoint(0xE007F);
|
||||
return seq;
|
||||
}
|
||||
|
||||
const vs16 = includeVS ? '\uFE0F'.replace('\\u','\u') : '';
|
||||
let out = carrier + vs16;
|
||||
for (let i = 0; i < payload.length; i++) {
|
||||
out += sep + toTagSeqForEmojiChar(payload[i]) + noise();
|
||||
}
|
||||
this.tokenBombOutput = out;
|
||||
} else {
|
||||
let block = buildLevel(depth - 1);
|
||||
const blocks = [];
|
||||
for (let i = 0; i < repeats; i++) {
|
||||
blocks.push(block + noise());
|
||||
}
|
||||
this.tokenBombOutput = blocks.join(sep);
|
||||
}
|
||||
this.tokenBombOutput = blocks.join(sep);
|
||||
|
||||
// Provide a quick visual confirmation
|
||||
this.showNotification('<i class="fas fa-bomb"></i> Token bomb generated', 'success');
|
||||
this.showNotification('<i class="fas fa-bomb"></i> Tokenade generated', 'success');
|
||||
|
||||
if (this.tbAutoCopy && this.tokenBombOutput) {
|
||||
this.$nextTick(() => this.copyToClipboard(this.tokenBombOutput));
|
||||
@@ -1760,6 +1804,57 @@ window.app = new Vue({
|
||||
this.tbIncludeVS = true; this.tbIncludeNoise = true; this.tbRandomizeEmojis = true;
|
||||
}
|
||||
this.showNotification('<i class="fas fa-sliders-h"></i> Preset applied', 'success');
|
||||
},
|
||||
|
||||
estimateTokenadeLength() {
|
||||
const depth = Math.max(1, Math.min(8, Number(this.tbDepth) || 1));
|
||||
const breadth = Math.max(1, Math.min(12, Number(this.tbBreadth) || 1));
|
||||
const repeats = Math.max(1, Math.min(100, Number(this.tbRepeats) || 1));
|
||||
const sepLen = this.tbSeparator === 'none' ? 0 : 1;
|
||||
const vsPerEmoji = this.tbIncludeVS ? 1 : 0;
|
||||
const noiseAvg = this.tbIncludeNoise ? 2 : 0;
|
||||
|
||||
function lenLevel(level) {
|
||||
if (level === 0) {
|
||||
return breadth * (1 + vsPerEmoji);
|
||||
}
|
||||
const inner = lenLevel(level - 1);
|
||||
return breadth * (inner + noiseAvg) + Math.max(0, breadth - 1) * sepLen;
|
||||
}
|
||||
|
||||
if (this.tbSingleCarrier) {
|
||||
function countUnits(level) { return level === 0 ? breadth : breadth * countUnits(level - 1); }
|
||||
const unitsPerBlock = countUnits(depth - 1);
|
||||
const totalUnits = Math.max(1, repeats * unitsPerBlock);
|
||||
const avgDigits = 5;
|
||||
const perUnit = avgDigits + 1 + sepLen + (this.tbIncludeNoise ? 2 : 0);
|
||||
const carrierLen = 1 + (this.tbIncludeVS ? 1 : 0);
|
||||
return carrierLen + totalUnits * perUnit;
|
||||
} else {
|
||||
const blockLen = lenLevel(depth - 1);
|
||||
return repeats * (blockLen + noiseAvg) + Math.max(0, repeats - 1) * sepLen;
|
||||
}
|
||||
},
|
||||
|
||||
setCarrierFromSelected() {
|
||||
if (this.selectedEmoji) this.tbCarrier = String(this.selectedEmoji);
|
||||
},
|
||||
clearTokenadePayload() { this.tbPayloadEmojis = []; },
|
||||
removeTokenadePayloadAt(idx) { this.tbPayloadEmojis.splice(idx, 1); },
|
||||
handleTokenadeDrop(e) {
|
||||
e.preventDefault();
|
||||
const text = e.dataTransfer && (e.dataTransfer.getData('text/plain') || e.dataTransfer.getData('text'));
|
||||
if (!text) return;
|
||||
const parts = window.emojiLibrary.splitEmojis(text);
|
||||
const onlyEmojis = parts.filter(p => /\p{Extended_Pictographic}/u.test(p));
|
||||
this.tbPayloadEmojis.push(...onlyEmojis);
|
||||
},
|
||||
handleTokenadePaste(e) {
|
||||
const text = (e.clipboardData && e.clipboardData.getData('text')) || '';
|
||||
if (!text) return;
|
||||
const parts = window.emojiLibrary.splitEmojis(text);
|
||||
const onlyEmojis = parts.filter(p => /\p{Extended_Pictographic}/u.test(p));
|
||||
this.tbPayloadEmojis.push(...onlyEmojis);
|
||||
}
|
||||
},
|
||||
// Initialize theme and components
|
||||
|
||||
Reference in New Issue
Block a user