diff --git a/website/sass/style.scss b/website/sass/style.scss index deb1963..47fd654 100644 --- a/website/sass/style.scss +++ b/website/sass/style.scss @@ -422,6 +422,31 @@ body:has(.playground-page) footer { border-color: var(--accent); } +.share-btn { + padding: 0.375rem 1rem; + background: var(--accent); + color: white; + border: none; + border-radius: 0.25rem; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: background 0.2s, transform 0.1s; +} + +.share-btn:hover { + background: var(--accent-hover); + transform: translateY(-1px); +} + +.share-btn:active { + transform: translateY(0); +} + +.share-btn.copied { + background: #10b981; +} + .tabs { display: flex; gap: 0.5rem; diff --git a/website/static/js/app.js b/website/static/js/app.js index 2c4359b..9803f04 100644 --- a/website/static/js/app.js +++ b/website/static/js/app.js @@ -131,6 +131,9 @@ async function init() { initEditor(); setupEventListeners(); + // Load from URL if share parameter exists + loadFromUrl(); + // Generate initial output handleCheck(); } catch (error) { @@ -419,6 +422,12 @@ function setupEventListeners() { timeout = setTimeout(handleCheck, 500); }); } + + // Share button + const shareBtn = document.getElementById('share-btn'); + if (shareBtn) { + shareBtn.addEventListener('click', handleShare); + } } function switchTab(tabName) { @@ -547,6 +556,68 @@ function showValidateError(message) { resultDiv.style.display = 'block'; } +// Base64url encoding (URL-safe base64) +function base64urlEncode(str) { + // Convert string to base64 + const base64 = btoa(unescape(encodeURIComponent(str))); + // Make it URL-safe by replacing +/= with -_~ + return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '~'); +} + +function base64urlDecode(str) { + // Convert URL-safe base64 back to regular base64 + const base64 = str.replace(/-/g, '+').replace(/_/g, '/').replace(/~/g, '='); + // Decode base64 to string + return decodeURIComponent(escape(atob(base64))); +} + +function loadFromUrl() { + const urlParams = new URLSearchParams(window.location.search); + const shareParam = urlParams.get('share'); + + if (shareParam) { + try { + const decoded = base64urlDecode(shareParam); + const textarea = document.getElementById('mlf-editor'); + if (textarea) { + textarea.value = decoded; + updateLineNumbers(decoded); + updateHighlighting(decoded); + } + } catch (error) { + console.error('Failed to load shared code:', error); + showError('Failed to load shared code. The link may be corrupted.'); + } + } +} + +function handleShare() { + const source = getEditorContent(); + const encoded = base64urlEncode(source); + + // Build share URL + const baseUrl = window.location.origin + window.location.pathname; + const shareUrl = `${baseUrl}?share=${encoded}`; + + // Copy to clipboard + navigator.clipboard.writeText(shareUrl).then(() => { + // Show success feedback + const shareBtn = document.getElementById('share-btn'); + const originalText = shareBtn.textContent; + shareBtn.textContent = 'Copied!'; + shareBtn.classList.add('copied'); + + setTimeout(() => { + shareBtn.textContent = originalText; + shareBtn.classList.remove('copied'); + }, 2000); + }).catch(err => { + console.error('Failed to copy to clipboard:', err); + // Fallback: show the URL in a prompt + prompt('Copy this URL to share:', shareUrl); + }); +} + // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); diff --git a/website/templates/playground.html b/website/templates/playground.html index 4391625..7bcd92f 100644 --- a/website/templates/playground.html +++ b/website/templates/playground.html @@ -8,6 +8,7 @@