AmitJ82's picture
Update index.html
25d3f03 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TextCraft Pro - Ultimate</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/feather-icons"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.2.12/marked.min.js"></script>
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
colors: {
darkbg: '#121212',
lightbg: '#ffffff',
}
}
}
}
</script>
<style>
/* Custom Scrollbar */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }
.dark ::-webkit-scrollbar-thumb { background: #475569; }
/* Print styles specific */
@media print {
body { background: white; color: black; }
.no-print { display: none !important; }
#markdownPreview { display: block !important; width: 100% !important; border: none; }
}
</style>
</head>
<body class="bg-lightbg text-black dark:bg-darkbg dark:text-white transition-colors duration-300 h-screen flex flex-col overflow-hidden">
<header class="flex-none px-6 py-3 border-b border-gray-300 dark:border-gray-700 flex justify-between items-center bg-white dark:bg-darkbg z-10">
<div class="flex items-center gap-2">
<h1 class="text-xl font-bold tracking-tight text-fuchsia-600 dark:text-fuchsia-400">TextCraft Pro</h1>
<span class="text-sm px-2 py-0.5 rounded bg-gray-200 dark:bg-gray-800 text-gray-600 dark:text-gray-300">v3.0 (Fixed)</span>
</div>
<div class="flex items-center gap-3">
<button id="themeToggle" class="p-2 rounded-full hover:bg-gray-200 dark:hover:bg-gray-800 transition-colors">
<i data-feather="moon" class="w-5 h-5"></i>
</button>
<button id="fullscreenBtn" class="p-2 rounded-full hover:bg-gray-200 dark:hover:bg-gray-800 transition-colors hidden md:block">
<i data-feather="maximize-2" class="w-5 h-5"></i>
</button>
</div>
</header>
<div class="flex-none px-4 py-2 bg-gray-50 dark:bg-[#1a1a1a] border-b border-gray-300 dark:border-gray-700 flex flex-wrap gap-2 items-center justify-between">
<div class="flex items-center gap-4">
<div class="flex items-center cursor-pointer select-none">
<input type="checkbox" id="modeToggle" class="sr-only peer">
<label for="modeToggle" class="relative w-10 h-5 bg-gray-300 peer-focus:outline-none rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-fuchsia-600 cursor-pointer"></label>
<span id="modeLabel" class="ml-2 text-sm font-medium">Plain Text</span>
</div>
<button id="previewToggle" class="hidden text-sm flex items-center gap-1 text-gray-600 dark:text-gray-300 hover:text-fuchsia-500">
<i data-feather="eye" class="w-4 h-4"></i> <span class="hidden sm:inline">Preview</span>
</button>
</div>
<div class="flex items-center gap-1 bg-gray-200 dark:bg-gray-800 rounded-lg p-1">
<button id="undoBtn" class="p-1.5 rounded hover:bg-white dark:hover:bg-gray-700 disabled:opacity-30" title="Undo">
<i data-feather="rotate-ccw" class="w-4 h-4"></i>
</button>
<button id="redoBtn" class="p-1.5 rounded hover:bg-white dark:hover:bg-gray-700 disabled:opacity-30" title="Redo">
<i data-feather="rotate-cw" class="w-4 h-4"></i>
</button>
<div class="w-px h-4 bg-gray-400 dark:bg-gray-600 mx-1"></div>
<button id="copyBtn" class="p-1.5 rounded hover:bg-white dark:hover:bg-gray-700" title="Copy">
<i data-feather="copy" class="w-4 h-4"></i>
</button>
<button id="pasteBtn" class="p-1.5 rounded hover:bg-white dark:hover:bg-gray-700" title="Paste">
<i data-feather="clipboard" class="w-4 h-4"></i>
</button>
<button id="clearBtn" class="p-1.5 rounded hover:bg-white dark:hover:bg-gray-700 text-red-500" title="Clear All">
<i data-feather="trash-2" class="w-4 h-4"></i>
</button>
</div>
<div class="flex items-center gap-3">
<button id="newDocBtn" class="text-sm flex items-center gap-1 hover:text-fuchsia-500 transition-colors">
<i data-feather="file-plus" class="w-4 h-4"></i> <span class="hidden sm:inline">New</span>
</button>
<div id="wordCountBtn" class="text-xs px-2 py-1 bg-white dark:bg-gray-800 border dark:border-gray-700 rounded text-gray-500 dark:text-gray-400 font-mono">
<span id="wordCount">0</span> w | <span id="charCount">0</span> c
</div>
</div>
</div>
<main class="flex-1 flex overflow-hidden relative">
<div class="flex-1 h-full relative">
<div id="textEditor"
contenteditable="true"
spellcheck="false"
class="w-full h-full p-6 text-lg font-mono outline-none overflow-y-auto resize-none bg-lightbg text-black dark:bg-darkbg dark:text-white placeholder:text-gray-400"
placeholder="Start typing..."></div>
</div>
<div id="markdownPreview" class="hidden flex-1 h-full p-8 overflow-y-auto border-l border-gray-200 dark:border-gray-700 prose prose-slate dark:prose-invert max-w-none bg-gray-50 dark:bg-[#151515]">
</div>
</main>
<footer class="flex-none p-2 bg-gray-50 dark:bg-[#1a1a1a] border-t border-gray-300 dark:border-gray-700 flex justify-center gap-2">
<button id="exportTxtBtn" class="flex items-center gap-2 px-4 py-1.5 text-sm rounded-md bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 hover:bg-fuchsia-50 dark:hover:bg-gray-700 transition-all shadow-sm">
<span class="font-bold text-fuchsia-600 dark:text-fuchsia-400">TXT</span> <i data-feather="download" class="w-3 h-3"></i>
</button>
<button id="exportMdBtn" class="flex items-center gap-2 px-4 py-1.5 text-sm rounded-md bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 hover:bg-fuchsia-50 dark:hover:bg-gray-700 transition-all shadow-sm">
<span class="font-bold text-fuchsia-600 dark:text-fuchsia-400">MD</span> <i data-feather="download" class="w-3 h-3"></i>
</button>
<button id="exportPdfBtn" class="flex items-center gap-2 px-4 py-1.5 text-sm rounded-md bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 hover:bg-fuchsia-50 dark:hover:bg-gray-700 transition-all shadow-sm">
<span class="font-bold text-fuchsia-600 dark:text-fuchsia-400">PDF</span> <i data-feather="download" class="w-3 h-3"></i>
</button>
</footer>
<div id="pdf-container" style="display: none;"></div>
<script>
feather.replace();
// DOM Elements
const textEditor = document.getElementById('textEditor');
const markdownPreview = document.getElementById('markdownPreview');
const modeToggle = document.getElementById('modeToggle');
const modeLabel = document.getElementById('modeLabel');
const previewToggle = document.getElementById('previewToggle');
const wordCountBtn = document.getElementById('wordCountBtn');
const wordCount = document.getElementById('wordCount');
const charCount = document.getElementById('charCount');
const themeToggle = document.getElementById('themeToggle');
const fullscreenBtn = document.getElementById('fullscreenBtn');
const pasteBtn = document.getElementById('pasteBtn');
const clearBtn = document.getElementById('clearBtn');
const copyBtn = document.getElementById('copyBtn');
const undoBtn = document.getElementById('undoBtn');
const redoBtn = document.getElementById('redoBtn');
const newDocBtn = document.getElementById('newDocBtn');
const exportTxtBtn = document.getElementById('exportTxtBtn');
const exportMdBtn = document.getElementById('exportMdBtn');
const exportPdfBtn = document.getElementById('exportPdfBtn');
// State variables
let isMarkdownMode = false;
let isPreviewVisible = true;
let editHistory = [];
let historyPointer = -1;
let isDarkMode = document.documentElement.classList.contains('dark');
let debounceTimer; // For performance
function initEditor() {
const savedContent = localStorage.getItem('textcraftContent');
const savedMode = localStorage.getItem('textcraftMode');
if (savedContent) textEditor.innerHTML = savedContent;
if (savedMode === 'markdown') {
modeToggle.checked = true;
toggleMarkdownMode(true);
}
updateCounts();
// Immediate update on load
if(savedContent) updatePreview(true);
saveToHistory();
}
function toggleMarkdownMode(force = null) {
if (force !== null) {
isMarkdownMode = force;
modeToggle.checked = isMarkdownMode;
} else {
isMarkdownMode = modeToggle.checked;
}
if (isMarkdownMode) {
modeLabel.textContent = 'Markdown';
previewToggle.classList.remove('hidden');
markdownPreview.classList.remove('hidden');
textEditor.parentElement.classList.add('w-1/2');
localStorage.setItem('textcraftMode', 'markdown');
updatePreview(true); // Force update when switching mode
} else {
modeLabel.textContent = 'Plain Text';
previewToggle.classList.add('hidden');
markdownPreview.classList.add('hidden');
textEditor.parentElement.classList.remove('w-1/2');
localStorage.setItem('textcraftMode', 'plaintext');
}
}
function togglePreview() {
isPreviewVisible = !isPreviewVisible;
if(isPreviewVisible) {
markdownPreview.classList.remove('hidden');
textEditor.parentElement.classList.remove('w-full');
updatePreview(true); // Update when reopening
} else {
markdownPreview.classList.add('hidden');
textEditor.parentElement.classList.add('w-full');
}
previewToggle.innerHTML = isPreviewVisible ?
'<i data-feather="eye" class="w-4 h-4 mr-1 inline"></i> <span class="hidden sm:inline">Preview</span>' :
'<i data-feather="eye-off" class="w-4 h-4 mr-1 inline"></i> <span class="hidden sm:inline">Editor Only</span>';
feather.replace();
}
function updateCounts() {
const text = textEditor.innerText;
const words = text.trim() === '' ? 0 : text.trim().split(/\s+/).length;
const chars = text.length;
wordCount.textContent = words.toLocaleString();
charCount.textContent = chars.toLocaleString();
}
// UPDATED: Debounced Preview for Large Files
function handleInput() {
updateCounts();
saveContent();
// Clear existing timer
clearTimeout(debounceTimer);
// Set new timer - waits 300ms after you stop typing to render markdown
// This prevents freezing on large files
debounceTimer = setTimeout(() => {
updatePreview();
}, 300);
}
function updatePreview(force = false) {
if (isMarkdownMode || force) {
// Render Markdown to HTML
const rawText = textEditor.innerText;
const renderedHTML = marked.parse(rawText);
markdownPreview.innerHTML = renderedHTML;
}
}
function saveContent() {
localStorage.setItem('textcraftContent', textEditor.innerHTML);
}
function saveToHistory() {
if (historyPointer < editHistory.length - 1) {
editHistory = editHistory.slice(0, historyPointer + 1);
}
editHistory.push(textEditor.innerHTML);
historyPointer++;
if (editHistory.length > 50) { // Limit history to save memory
editHistory.shift();
historyPointer--;
}
updateUndoRedoButtons();
}
function undo() {
if (historyPointer > 0) {
historyPointer--;
textEditor.innerHTML = editHistory[historyPointer];
updateCounts();
updatePreview(true);
updateUndoRedoButtons();
}
}
function redo() {
if (historyPointer < editHistory.length - 1) {
historyPointer++;
textEditor.innerHTML = editHistory[historyPointer];
updateCounts();
updatePreview(true);
updateUndoRedoButtons();
}
}
function updateUndoRedoButtons() {
undoBtn.disabled = historyPointer <= 0;
redoBtn.disabled = historyPointer >= editHistory.length - 1;
undoBtn.style.opacity = undoBtn.disabled ? '0.3' : '1';
redoBtn.style.opacity = redoBtn.disabled ? '0.3' : '1';
}
function toggleTheme() {
isDarkMode = !isDarkMode;
if (isDarkMode) {
document.documentElement.classList.add('dark');
themeToggle.innerHTML = '<i data-feather="sun" class="w-5 h-5"></i>';
} else {
document.documentElement.classList.remove('dark');
themeToggle.innerHTML = '<i data-feather="moon" class="w-5 h-5"></i>';
}
localStorage.setItem('textcraftTheme', isDarkMode ? 'dark' : 'light');
feather.replace();
}
function toggleFullscreen() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().catch(err => {
console.error(`Error: ${err.message}`);
});
fullscreenBtn.innerHTML = '<i data-feather="minimize-2" class="w-5 h-5"></i>';
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
fullscreenBtn.innerHTML = '<i data-feather="maximize-2" class="w-5 h-5"></i>';
}
}
feather.replace();
}
async function pasteText() {
try {
const text = await navigator.clipboard.readText();
textEditor.focus();
document.execCommand('insertText', false, text);
saveToHistory();
handleInput();
} catch (err) {
alert('Use Ctrl+V');
}
}
function clearEditor() {
if (confirm('Clear everything?')) {
textEditor.innerHTML = '';
saveToHistory();
handleInput();
updatePreview(true);
}
}
async function copyText() {
try {
await navigator.clipboard.writeText(textEditor.innerText);
const originalHTML = copyBtn.innerHTML;
copyBtn.innerHTML = '<i data-feather="check" class="w-4 h-4 text-green-500"></i>';
setTimeout(() => {
copyBtn.innerHTML = originalHTML;
feather.replace();
}, 2000);
feather.replace();
} catch (err) {
alert('Use Ctrl+C');
}
}
function newDocument() {
if (textEditor.innerText.trim() === '' || confirm('Start new document? Unsaved changes will be lost.')) {
textEditor.innerHTML = '';
saveToHistory();
handleInput();
updatePreview(true);
}
}
function exportAsTxt() {
const text = textEditor.innerText;
const blob = new Blob([text], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `textcraft-export.txt`;
a.click();
URL.revokeObjectURL(url);
}
function exportAsMd() {
const text = textEditor.innerText;
const blob = new Blob([text], { type: 'text/markdown' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `textcraft-export.md`;
a.click();
URL.revokeObjectURL(url);
}
// --- MAJOR FIX: IMPROVED PDF EXPORT ---
function exportAsPdf() {
const originalBtnText = exportPdfBtn.innerHTML;
exportPdfBtn.innerHTML = '<span class="animate-pulse">Saving...</span>';
// 1. Get content. If in Markdown mode, use the preview HTML.
// If in plain text mode, parse the text first so it looks good.
let contentToPrint;
// Force parse current text to ensure we have the latest HTML
const rawText = textEditor.innerText;
const renderedHTML = marked.parse(rawText);
// Create a temporary container to style the PDF specifically
const element = document.createElement('div');
element.innerHTML = renderedHTML;
// Apply styling for the PDF (Prose styling + White background)
// We use the 'prose' class from Tailwind Typography to make it look beautiful
element.className = 'prose prose-slate max-w-none p-8 bg-white text-black';
element.style.fontFamily = 'ui-sans-serif, system-ui, sans-serif';
element.style.fontSize = '12pt';
element.style.lineHeight = '1.5';
// Options for html2pdf
const opt = {
margin: [10, 15, 10, 15], // Top, Left, Bottom, Right
filename: 'textcraft-document.pdf',
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 2, useCORS: true }, // Scale 2 for better quality
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' },
pagebreak: { mode: ['avoid-all', 'css', 'legacy'] } // Helps avoid cutting lines in half
};
// Generate PDF
html2pdf().set(opt).from(element).save().then(() => {
exportPdfBtn.innerHTML = originalBtnText;
feather.replace();
}).catch(err => {
console.error(err);
alert('Error generating PDF. For very large files, try using Ctrl+P and "Save as PDF".');
exportPdfBtn.innerHTML = originalBtnText;
feather.replace();
});
}
// Listeners
textEditor.addEventListener('input', handleInput); // Use the debounced handler
textEditor.addEventListener('keydown', (e) => {
if (e.key === ' ' || e.key === 'Enter') setTimeout(saveToHistory, 500);
});
modeToggle.addEventListener('change', () => toggleMarkdownMode());
previewToggle.addEventListener('click', togglePreview);
themeToggle.addEventListener('click', toggleTheme);
fullscreenBtn.addEventListener('click', toggleFullscreen);
pasteBtn.addEventListener('click', pasteText);
clearBtn.addEventListener('click', clearEditor);
copyBtn.addEventListener('click', copyText);
undoBtn.addEventListener('click', undo);
redoBtn.addEventListener('click', redo);
newDocBtn.addEventListener('click', newDocument);
exportTxtBtn.addEventListener('click', exportAsTxt);
exportMdBtn.addEventListener('click', exportAsMd);
exportPdfBtn.addEventListener('click', exportAsPdf);
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'z') { e.preventDefault(); undo(); }
if ((e.ctrlKey && e.key === 'y') || (e.ctrlKey && e.shiftKey && e.key === 'z')) { e.preventDefault(); redo(); }
if (e.ctrlKey && e.key === 's') { e.preventDefault(); saveContent(); }
// Added Ctrl+P shortcut to trigger browser print as backup
if (e.ctrlKey && e.key === 'p') {
e.preventDefault();
// Temporarily show preview, print, then revert?
// Simple version:
window.print();
}
});
// Init Theme
const savedTheme = localStorage.getItem('textcraftTheme');
if (savedTheme === 'dark' || (!savedTheme && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
themeToggle.innerHTML = '<i data-feather="sun" class="w-5 h-5"></i>';
} else {
document.documentElement.classList.remove('dark');
themeToggle.innerHTML = '<i data-feather="moon" class="w-5 h-5"></i>';
}
initEditor();
</script>
</body>
</html>