btg-sandbox-markdown-formatter/js/script.js
DJP 5e4d4d0115 Initial commit: Secure Markdown to HTML converter with Azure AD authentication
- Web-based Markdown formatter with real-time conversion
- Microsoft Azure AD authentication with PKCE flow
- Server-side JWT validation with httpOnly cookies
- Clipboard functionality for HTML/text output
- PHP backend with Composer dependency management
- Comprehensive README with installation instructions

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-17 17:33:35 -04:00

137 lines
No EOL
5.1 KiB
JavaScript
Executable file

document.addEventListener('DOMContentLoaded', function() {
const markdownInput = document.getElementById('markdown-input');
const htmlOutput = document.getElementById('html-output');
const selectAllBtn = document.getElementById('select-all');
const copyBtn = document.getElementById('copy');
function updateOutput() {
const markdown = markdownInput.value;
if (!markdown.trim()) {
htmlOutput.innerHTML = '';
return;
}
// Convert markdown to HTML client-side
const html = marked(markdown);
htmlOutput.innerHTML = html;
// Apply alternating column colors to tables
const tables = htmlOutput.querySelectorAll('table');
tables.forEach(table => {
const rows = table.querySelectorAll('tr');
rows.forEach(row => {
const cells = row.querySelectorAll('td');
cells.forEach((cell, index) => {
if (index % 2 === 0) {
cell.style.backgroundColor = 'rgba(0, 0, 0, 0.25)';
}
});
});
});
}
markdownInput.addEventListener('input', updateOutput);
selectAllBtn.addEventListener('click', function() {
if (!htmlOutput.innerHTML.trim()) {
alert('No content to select');
return;
}
const range = document.createRange();
range.selectNodeContents(htmlOutput);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
});
copyBtn.addEventListener('click', function() {
if (!htmlOutput.innerHTML.trim()) {
alert('No content to copy');
return;
}
// Use modern clipboard API if available
if (navigator.clipboard && window.isSecureContext) {
// Copy both HTML and plain text formats
const htmlContent = htmlOutput.innerHTML;
const textContent = htmlOutput.textContent || htmlOutput.innerText;
const clipboardItem = new ClipboardItem({
'text/html': new Blob([htmlContent], { type: 'text/html' }),
'text/plain': new Blob([textContent], { type: 'text/plain' })
});
navigator.clipboard.write([clipboardItem]).then(() => {
showNotification('Rich text copied to clipboard!');
}).catch(err => {
console.error('Failed to copy rich text:', err);
// Fallback to plain text if rich text copying fails
navigator.clipboard.writeText(textContent).then(() => {
showNotification('Text copied to clipboard!');
}).catch(() => {
fallbackCopy();
});
});
} else {
fallbackCopy();
}
});
function fallbackCopy() {
const range = document.createRange();
range.selectNodeContents(htmlOutput);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
try {
document.execCommand('copy');
showNotification('Rich text copied to clipboard!');
} catch (err) {
console.error('Fallback copy failed:', err);
showNotification('Failed to copy to clipboard', 'error');
}
selection.removeAllRanges();
}
function showNotification(message, type = 'success') {
// Create notification element
const notification = document.createElement('div');
notification.textContent = message;
// Set styles individually to avoid template literal issues
notification.style.position = 'fixed';
notification.style.top = '20px';
notification.style.right = '20px';
notification.style.background = type === 'error' ? '#f44336' : '#4caf50';
notification.style.color = 'white';
notification.style.padding = '12px 20px';
notification.style.borderRadius = '4px';
notification.style.fontFamily = "'Montserrat', Arial, sans-serif";
notification.style.zIndex = '1000';
notification.style.animation = 'slideIn 0.3s ease';
// Add CSS animation
const style = document.createElement('style');
style.textContent = '@keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }';
document.head.appendChild(style);
document.body.appendChild(notification);
// Remove notification after 3 seconds
setTimeout(() => {
notification.remove();
style.remove();
}, 3000);
}
// Handle authentication errors gracefully
window.addEventListener('unhandledrejection', function(event) {
if (event.reason && event.reason.message && event.reason.message.includes('authentication')) {
showNotification('Authentication error. Please refresh and login again.', 'error');
}
});
});