Skip to content

Commit a49e840

Browse files
committed
[+] Share in Base64
1 parent 93c9695 commit a49e840

1 file changed

Lines changed: 141 additions & 0 deletions

File tree

index.html

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,7 @@ <h1>ASCII Punctuation Fixer</h1>
378378
</button>
379379
<button id="btnSwap" title="Swap input and output">Swap</button>
380380
<button id="btnCopy" title="Copy output to clipboard">Copy output</button>
381+
<button id="btnShare" title="Copy a shareable link that loads the text via Base64">Share link</button>
381382
<button id="btnDownload" title="Download output as .txt">Download .txt</button>
382383
</div>
383384

@@ -423,6 +424,113 @@ <h1>ASCII Punctuation Fixer</h1>
423424
"use strict";
424425

425426
const $ = (id) => document.getElementById(id);
427+
// ---------- URL import/export (Base64URL) ----------
428+
// Load text from:
429+
// ?b64=... (base64url recommended)
430+
// #b64=... (recommended for longer text)
431+
// Aliases: text64, t
432+
// Optional flags (only applied if present): nfkc=1 inv=1 lig=1 live=1
433+
function padBase64(b64) {
434+
const mod = b64.length % 4;
435+
return mod === 0 ? b64 : b64 + "=".repeat(4 - mod);
436+
}
437+
438+
function bytesToBinaryString(bytes) {
439+
let bin = "";
440+
const chunk = 0x8000;
441+
for (let i = 0; i < bytes.length; i += chunk) {
442+
bin += String.fromCharCode(...bytes.subarray(i, i + chunk));
443+
}
444+
return bin;
445+
}
446+
447+
function encodeUtf8ToBase64Url(text) {
448+
const bytes = new TextEncoder().encode(text);
449+
const b64 = btoa(bytesToBinaryString(bytes));
450+
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
451+
}
452+
453+
function decodeBase64UrlToUtf8(b64url) {
454+
const normalized = padBase64(
455+
String(b64url).replace(/-/g, "+").replace(/_/g, "/").replace(/\s/g, "")
456+
);
457+
const binary = atob(normalized);
458+
const bytes = new Uint8Array(binary.length);
459+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
460+
return new TextDecoder("utf-8", { fatal: false }).decode(bytes);
461+
}
462+
463+
function getUrlParam(name) {
464+
const qs = new URLSearchParams(window.location.search);
465+
const v1 = qs.get(name);
466+
if (v1 !== null) return v1;
467+
468+
const rawHash = window.location.hash.startsWith("#") ? window.location.hash.slice(1) : window.location.hash;
469+
const hs = new URLSearchParams(rawHash);
470+
return hs.get(name);
471+
}
472+
473+
function hasUrlParam(name) {
474+
return getUrlParam(name) !== null;
475+
}
476+
477+
function parseBoolParam(value) {
478+
if (value === null) return null;
479+
const v = String(value).trim().toLowerCase();
480+
if (["1", "true", "yes", "y", "on"].includes(v)) return true;
481+
if (["0", "false", "no", "n", "off"].includes(v)) return false;
482+
return null;
483+
}
484+
485+
function applyOptionsFromUrl() {
486+
const nfkc = parseBoolParam(getUrlParam("nfkc"));
487+
const inv = parseBoolParam(getUrlParam("inv"));
488+
const lig = parseBoolParam(getUrlParam("lig"));
489+
const live = parseBoolParam(getUrlParam("live"));
490+
491+
if (nfkc !== null) $("optNFKC").checked = nfkc;
492+
if (inv !== null) $("optInvisible").checked = inv;
493+
if (lig !== null) $("optLigatures").checked = lig;
494+
if (live !== null) $("optLive").checked = live;
495+
}
496+
497+
function initFromUrl() {
498+
// Apply checkbox flags first (if present).
499+
if (hasUrlParam("nfkc") || hasUrlParam("inv") || hasUrlParam("lig") || hasUrlParam("live")) {
500+
applyOptionsFromUrl();
501+
}
502+
503+
const b64 = getUrlParam("b64") || getUrlParam("text64") || getUrlParam("t");
504+
if (!b64) return false;
505+
506+
try {
507+
const text = decodeBase64UrlToUtf8(b64);
508+
$("input").value = text;
509+
runConversion();
510+
toast("Loaded input from URL.");
511+
return true;
512+
} catch (err) {
513+
console.error("Failed to decode Base64 from URL:", err);
514+
toast("Could not decode URL input.");
515+
return false;
516+
}
517+
}
518+
519+
function buildShareUrl(text, opts) {
520+
const base = window.location.origin + window.location.pathname;
521+
522+
const params = new URLSearchParams();
523+
params.set("b64", encodeUtf8ToBase64Url(text));
524+
525+
// Persist current options, so the receiver sees the same behavior.
526+
params.set("nfkc", opts.nfkc ? "1" : "0");
527+
params.set("inv", opts.removeInvisible ? "1" : "0");
528+
params.set("lig", opts.ligatures ? "1" : "0");
529+
530+
// Use hash to better tolerate longer payloads and avoid '+' being treated as space.
531+
return base + "#" + params.toString();
532+
}
533+
426534

427535
// ---------- Replacement rules ----------
428536
// Goal: Convert common "smart punctuation" and friends into plain ASCII equivalents.
@@ -651,6 +759,37 @@ <h1>ASCII Punctuation Fixer</h1>
651759
}
652760
});
653761

762+
$("btnShare").addEventListener("click", async () => {
763+
const inputText = $("input").value;
764+
const outputText = $("output").value;
765+
const text = inputText || outputText;
766+
767+
if (!text) { toast("Nothing to share."); return; }
768+
769+
const opts = getOpts();
770+
const url = buildShareUrl(text, opts);
771+
772+
try {
773+
await navigator.clipboard.writeText(url);
774+
toast(url.length > 8000 ? "Share link copied (very long URL)." : "Share link copied.");
775+
} catch {
776+
// Fallback for older browsers / restricted contexts
777+
const ta = document.createElement("textarea");
778+
ta.value = url;
779+
ta.setAttribute("readonly", "");
780+
ta.style.position = "fixed";
781+
ta.style.left = "-9999px";
782+
ta.style.top = "0";
783+
document.body.appendChild(ta);
784+
ta.select();
785+
document.execCommand("copy");
786+
ta.remove();
787+
toast(url.length > 8000 ? "Share link copied (fallback, very long URL)." : "Share link copied (fallback).");
788+
}
789+
790+
// Update the address bar without a reload (handy for manual sharing)
791+
try { history.replaceState(null, "", url); } catch {}
792+
});
654793
$("btnDownload").addEventListener("click", () => {
655794
const text = $("output").value;
656795
if (!text) { toast("Nothing to download."); return; }
@@ -714,6 +853,8 @@ <h1>ASCII Punctuation Fixer</h1>
714853

715854
// Initial state
716855
$("diagWrap").innerHTML = `<div class="muted">Paste text to see diagnostics.</div>`;
856+
// Load input from URL (Base64URL)
857+
initFromUrl();
717858
})();
718859
</script>
719860
</body>

0 commit comments

Comments
 (0)