Online Text Editor
Your simple, powerful, and easy-to-use online text editing tool.
Start typing here...
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, '');
markdown = markdown.replace(/
]*alt="([^"]*)"[^>]*src="([^"]*)"[^>]*>/gi, ''); // Alt first
markdown = markdown.replace(/
]*src="([^"]*)"[^>]*>/gi, ''); // 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.
]*>(.*?)<\/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, '');
markdown = markdown.replace(/
]*alt="([^"]*)"[^>]*src="([^"]*)"[^>]*>/gi, ''); // Alt first
markdown = markdown.replace(/
]*src="([^"]*)"[^>]*>/gi, ''); // 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.
]*>(.*?)<\/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, '');
markdown = markdown.replace(/
]*alt="([^"]*)"[^>]*src="([^"]*)"[^>]*>/gi, ''); // Alt first
markdown = markdown.replace(/
]*src="([^"]*)"[^>]*>/gi, ''); // 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.
- ]*>\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, ''); markdown = markdown.replace(/
]*alt="([^"]*)"[^>]*src="([^"]*)"[^>]*>/gi, ''); // Alt first markdown = markdown.replace(/
]*src="([^"]*)"[^>]*>/gi, ''); // 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.
- ]*>(.*?)<\/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(/