Online Text Editor - Free & Easy Text Editing | Ezytoolz

Online Text Editor

Your simple, powerful, and easy-to-use online text editing tool.

Start typing here...
0 characters 0 words
\n`; downloadFile('document.html', fullHtml, 'text/html;charset=utf-8'); showMessage('Downloading as .html...'); downloadDropdown.parentElement.classList.remove('show'); });downloadMdButton.addEventListener('click', () => { let htmlContent = isSourceView ? sourceCodeTextarea.value : editor.innerHTML; let markdown = htmlContent;// More robust HTML to Markdown conversion (simplified, consider a library for complex cases) // Order of replacement matters. Start with block elements. // Paragraphs (handle

tags carefully, as innerText/textContent might be better for stripping tags first) markdown = markdown.replace(/]*>(.*?)<\/p>/gi, '\n$1\n\n'); // Add newlines before/after// Headings markdown = markdown.replace(/]*>(.*?)<\/h1>/gi, '# $1\n'); markdown = markdown.replace(/]*>(.*?)<\/h2>/gi, '## $1\n'); markdown = markdown.replace(/]*>(.*?)<\/h3>/gi, '### $1\n'); markdown = markdown.replace(/]*>(.*?)<\/h4>/gi, '#### $1\n'); markdown = markdown.replace(/]*>(.*?)<\/h5>/gi, '##### $1\n'); markdown = markdown.replace(/]*>(.*?)<\/h6>/gi, '###### $1\n');// Lists (handle nesting carefully if needed, this is basic) markdown = markdown.replace(/]*>\s*([\s\S]*?)\s*<\/ul>/gi, (match, p1) => p1.replace(/]*>(.*?)<\/li>/gi, '* $1\n') + '\n'); markdown = markdown.replace(/]*>\s*([\s\S]*?)\s*<\/ol>/gi, (match, p1) => { let count = 1; return p1.replace(/]*>(.*?)<\/li>/gi, (liMatch, liContent) => `${count++}. ${liContent}\n`) + '\n'; });// Bold and Italic (Semantic and class-based) markdown = markdown.replace(/<(strong|b)>(.*?)<\/(strong|b)>/gi, '**$2**'); markdown = markdown.replace(/(.*?)<\/span>/gi, '**$1**'); markdown = markdown.replace(/<(em|i)>(.*?)<\/(em|i)>/gi, '*$2*'); markdown = markdown.replace(/(.*?)<\/span>/gi, '*$1*');// Strikethrough markdown = markdown.replace(/]*>(.*?)<\/strike>/gi, '~~$1~~'); markdown = markdown.replace(/]*>(.*?)<\/s>/gi, '~~$1~~'); // Also handles // Links markdown = markdown.replace(/]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi, '[$2]($1)');// Images markdown = markdown.replace(/]*src="([^"]*)"[^>]*alt="([^"]*)"[^>]*>/gi, '![$2]($1)'); markdown = markdown.replace(/]*alt="([^"]*)"[^>]*src="([^"]*)"[^>]*>/gi, '![$1]($2)'); // Alt first markdown = markdown.replace(/]*src="([^"]*)"[^>]*>/gi, '![]($1)'); // No alt// Horizontal Rule markdown = markdown.replace(/]*>/gi, '\n---\n');// Line breaks markdown = markdown.replace(//gi, ' \n'); // Markdown for line break (two spaces then newline)// Strip remaining HTML tags by converting to text const tempElem = document.createElement('div'); tempElem.innerHTML = markdown; markdown = tempElem.textContent || tempElem.innerText || "";// Clean up excessive newlines markdown = markdown.replace(/\n{3,}/g, '\n\n'); markdown = markdown.trim();downloadFile('document.md', markdown, 'text/markdown;charset=utf-8'); showMessage('Downloading as .md...'); downloadDropdown.parentElement.classList.remove('show'); });// --- Autosave --- function triggerAutosave() { clearTimeout(autosaveTimeout); // Clear existing timeout autosaveTimeout = setTimeout(() => { saveContent(); }, 2000); // Short delay after last input before saving }function setupAutosave() { // Autosave periodically setInterval(() => { saveContent(true); // Pass true for periodic save (less frequent status updates) }, AUTOSAVE_INTERVAL); // Also save before user leaves the page window.addEventListener('beforeunload', () => saveContent(false, true)); }function saveContent(isPeriodic = false, isUnload = false) { // Don't save if in source view or if editor is showing placeholder and hasn't been modified if (isSourceView || (editor.classList.contains('placeholder') && editor.innerHTML === placeholderHTML && !localStorage.getItem('autosavedContentEzytoolz'))) { // If placeholder is active AND there's no prior autosaved content, don't save the placeholder itself. // If placeholder is active BUT there IS prior autosaved content, it means user cleared it, so save empty. if (editor.classList.contains('placeholder') && !localStorage.getItem('autosavedContentEzytoolz')) return; }const content = editor.classList.contains('placeholder') ? "" : editor.innerHTML; // Save empty if placeholder localStorage.setItem('autosavedContentEzytoolz', content);if (!isPeriodic || isUnload) { // Only show status for explicit or important saves const now = new Date(); autosaveStatusDisplay.textContent = `Saved at ${now.toLocaleTimeString()}`; if(!isUnload) { // Don't clear message if unloading setTimeout(() => { autosaveStatusDisplay.textContent = ''; }, 3000); } } }function loadContent() { const savedContent = localStorage.getItem('autosavedContentEzytoolz'); if (savedContent) { editor.innerHTML = savedContent; if (editor.innerHTML.trim() === '' || editor.innerHTML === '


') { // Check if loaded content is effectively empty editor.innerHTML = placeholderHTML; editor.classList.add('placeholder'); } else { editor.classList.remove('placeholder'); } showMessage('Previously saved content loaded.'); } else { // No saved content, ensure placeholder is set if editor is empty if (!editor.innerText.trim() && !editor.querySelector('img') && !editor.querySelector('hr')) { editor.innerHTML = placeholderHTML; editor.classList.add('placeholder'); } } updateCounts(); updateToolbarStates(); }// --- Keyboard Shortcuts --- function setupKeyboardShortcuts() { editor.addEventListener('keydown', handleShortcut); sourceCodeTextarea.addEventListener('keydown', handleShortcut); // Also for textarea }function handleShortcut(event) { const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0; const ctrlCmd = isMac ? event.metaKey : event.ctrlKey;if (ctrlCmd) { switch (event.key.toLowerCase()) { case 'b': event.preventDefault(); if(!isSourceView) toggleCustomStyle('bold'); break; case 'i': event.preventDefault(); if(!isSourceView) toggleCustomStyle('italic'); break; case 'u': event.preventDefault(); if(!isSourceView) execCmd('underline'); break; case 's': event.preventDefault(); saveContent(); // Trigger manual save showMessage('Content saved!'); break; case 'f': event.preventDefault(); findReplaceToggleButton.click(); if (findReplaceBar.style.display !== 'none') findTextInput.focus(); break; case 'z': if (!isSourceView) { // Basic undo for rich text editor event.preventDefault(); document.execCommand('undo'); } // For textarea, browser handles undo (Ctrl+Z) natively break; case 'y': if (!isSourceView) { // Basic redo for rich text editor event.preventDefault(); document.execCommand('redo'); } // For textarea, browser handles redo (Ctrl+Y or Ctrl+Shift+Z) natively break; } } }// --- Misc --- // Prevent label click from stealing focus from color inputs document.querySelectorAll('.toolbar-button label').forEach(label => { label.addEventListener('mousedown', (e) => { // If the click target is the label itself (and not the input inside it), prevent default. // This helps keep the editor focused when clicking the icon part of the color picker button. if (e.target.tagName === 'LABEL' || e.target.tagName === 'svg' || e.target.tagName === 'path') { e.preventDefault(); // Manually trigger click on the input if it's a color picker const input = label.querySelector('input[type="color"]'); if (input) input.click(); } }); }); // Ensure editor regains focus after color selection for immediate typing. foreColorInput.addEventListener('change', () => editor.focus()); backColorInput.addEventListener('change', () => editor.focus());initializeEditor(); // editor.focus(); // Initial focus can be removed if placeholder logic handles it well. Related Tools Section - Embed Friendly