Notepad Online - Advanced Web Text Editor

Notepad Online - Advanced Web Text Editor

Words: 0 | Characters: 0
Not saved yet
Recording... Click mic button again to stop
Select All
Cut
Copy
Paste
Bold
Italic
Underline
Link
Look Up
`; mimeType = 'text/html'; break; case 'md': content = convertHtmlToMarkdownService(activeNote.content); mimeType = 'text/markdown'; break; case 'json': content = JSON.stringify({ id: activeNote.id, title: activeNote.title, content: activeNote.content, // Save HTML content for JSON created: activeNote.created, modified: activeNote.modified, tags: activeNote.tags, reminder: activeNote.reminder ? JSON.parse(activeNote.reminder) : null }, null, 2); mimeType = 'application/json'; break; } const blob = new Blob([content], { type: mimeType }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${name}.${extension}`; document.body.appendChild(a); a.click(); setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); }, 100); closeModal(saveFileModal); showToast('File Downloaded', `${name}.${extension} downloaded.`, 'success'); }function printNote() { window.print(); }// Share functions function openShareModal() { const note = notes.find(note => note.id === activeNoteId); if (note) { const baseUrl = `${window.location.protocol}//${window.location.host}${window.location.pathname}`; // For simplicity, we'll just use note ID. A more robust solution might involve a backend. const shareableUrl = `${baseUrl}#note=${note.id}`; // Use fragment for client-side routing idea shareLink.value = shareableUrl; } openModal(shareModal); } function copyShareLink() { shareLink.select(); shareLink.setSelectionRange(0, 99999); try { // Modern clipboard API navigator.clipboard.writeText(shareLink.value) .then(() => { showToast('Link Copied', 'Share link copied to clipboard!', 'success'); }) .catch(err => { // Fallback for older browsers document.execCommand('copy'); showToast('Link Copied', 'Share link copied (fallback).', 'success'); }); } catch (err) { showToast('Copy Failed', 'Could not copy link.', 'error'); } } function shareToSocialMedia(platform) { const note = notes.find(note => note.id === activeNoteId); if (!note) return; const baseUrl = `${window.location.protocol}//${window.location.host}${window.location.pathname}`; const shareableUrl = encodeURIComponent(`${baseUrl}#note=${note.id}`); // Using fragment const title = encodeURIComponent(note.title); const text = encodeURIComponent(`Check out my note: "${note.title}"`); let shareUrl = ''; switch (platform) { case 'twitter': shareUrl = `https://twitter.com/intent/tweet?text=${text}&url=${shareableUrl}`; break; case 'facebook': shareUrl = `https://www.facebook.com/sharer/sharer.php?u=${shareableUrl}"e=${text}`; break; case 'linkedin': // LinkedIn requires a proper URL, fragments might not work well for previews. // For a real app, a backend would generate a unique page for each shared note. shareUrl = `https://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent(window.location.href)}&title=${title}&summary=${text}`; break; case 'whatsapp': shareUrl = `https://wa.me/?text=${text}%20${shareableUrl}`; break; case 'telegram': shareUrl = `https://t.me/share/url?url=${shareableUrl}&text=${text}`; break; default: showToast('Share Error', 'Invalid platform.', 'error'); return; } window.open(shareUrl, '_blank', 'width=600,height=400,noopener,noreferrer'); } // Rich Text Editing Functions function formatText(command, value = null) { document.execCommand(command, false, value); editor.focus(); onEditorInput(); // To trigger save and update UI }function formatAlignment(alignment) { formatText('justify' + alignment.charAt(0).toUpperCase() + alignment.slice(1)); }function formatList(type) { formatText(type === 'bullet' ? 'insertUnorderedList' : 'insertOrderedList'); }function formatIndent(direction) { formatText(direction === 'increase' ? 'indent' : 'outdent'); }function applyFontStyle() { // This directly styles the editor, which is fine for overall font. // For specific selections, execCommand('fontName', false, fontFamily.value) and // execCommand('fontSize', false, index) (1-7 for sizes) would be needed, // but direct styling is simpler for a global change. editor.style.fontFamily = fontFamily.value; editor.style.fontSize = fontSize.value; // To make this apply to new text or persist, would need to wrap content in spans or use CSS more deeply. // For simplicity, this global style change is kept. }function insertLink() { const url = linkUrl.value.trim(); const text = linkText.value.trim() || url; // Default to URL if text is empty const target = openNewTab.checked ? '_blank' : ''; if (!url.startsWith('http://') && !url.startsWith('https://')) { showToast('Invalid URL', 'Please enter a valid URL starting with http:// or https://', 'error'); return; } const linkHtml = `${text}`; formatText('insertHTML', linkHtml); closeModal(linkModal); }function insertImage() { let url = imageUrl.value.trim(); const alt = imageAlt.value.trim() || 'Image'; if (!url && imageUpload.files.length === 0) { showToast('No Image', 'Please enter an image URL or upload a file.', 'error'); return; } if (imageUpload.files.length > 0) { const file = imageUpload.files[0]; if (!file.type.startsWith('image/')) { showToast('Invalid File', 'Please upload an image file.', 'error'); return; } const reader = new FileReader(); reader.onload = function(e) { const imgHtml = `${alt}`; formatText('insertHTML', imgHtml); showToast('Image Inserted', 'Image added to note.', 'success'); }; reader.onerror = () => showToast('File Error', 'Could not read image file.', 'error'); reader.readAsDataURL(file); // Reset file input for next upload imageUpload.value = ''; } else { // URL case if (!url.startsWith('http://') && !url.startsWith('https://')) { showToast('Invalid URL', 'Please enter a valid image URL.', 'error'); return; } const imgHtml = `${alt}`; formatText('insertHTML', imgHtml); } closeModal(imageModal); imageUrl.value = ''; imageAlt.value = ''; }function insertTable() { // Using a custom modal for this would be better. For now, `prompt` is used. const rowsInput = window.prompt('Enter number of rows (e.g., 3):', '3'); const colsInput = window.prompt('Enter number of columns (e.g., 3):', '3');const rows = parseInt(rowsInput); const cols = parseInt(colsInput); if (isNaN(rows) || isNaN(cols) || rows <= 0 || cols <= 0 || rows > 20 || cols > 10) { // Added some limits showToast('Invalid Input', 'Rows (1-20), Cols (1-10).', 'warning'); return; } let tableHTML = ''; tableHTML += ''; for (let j = 0; j < cols; j++) { tableHTML += ``; } tableHTML += ''; // Create data rows (rows input includes header, so rows-1 for body) for (let i = 0; i < Math.max(0, rows -1) ; i++) { tableHTML += ''; for (let j = 0; j < cols; j++) { tableHTML += ``; } tableHTML += ''; } tableHTML += '
Header ${j+1}
Cell

 

'; // Add some space after table formatText('insertHTML', tableHTML); }// Emoji functions function loadEmojis() { const emojiContainer = document.getElementById('emojiContainer'); emojiContainer.innerHTML = ''; // Clear previous // Save current selection before opening emoji picker saveCurrentSelection(); // Use the existing function const emojis = [ // Sample, can be expanded '😀', '😃', '😄', '😁', '😆', '😅', '😂', '🤣', '😊', '😇', '🙂', '🙃', '😉', '😌', '😍', '�', '😘', '😗', '😙', '😚', '😋', '😛', '😝', '😜', '🤪', '🤨', '🧐', '🤓', '😎', '🤩', '🥳', '😏', '😒', '😞', '😔', '😟', '😕', '🙁', '☹️', '😣', '😖', '😫', '😩', '🥺', '😢', '😭', '😤', '😠', '😡', '🤬', '🤯', '😳', '🥵', '🥶', '😱', '😨', '😰', '😥', '😓', '🤗', '🤔', '🤭', '🤫', '🤥', '😶', '😐', '😑', '😬', '🙄', '😯', '😦', '😧', '😮', '😲', '🥱', '😴', '🤤', '😪', '😵', '🤐', '🥴', '🤢', '🤮', '🤧', '😷', '🤒', '🤕', '🤑', '🤠', '❤️', '🧡', '💛', '💚', '💙', '💜', '🖤', '🤍', '🤎', '💔', '❣️', '💕', '💞', '💓', '💗', '💖', '💘', '💝', '👍', '👎', '👌', '✌️', '🤞', '🤟', '🤘', '👏', '🎉', '🎈', '🎁', '✨', '🌟', '☀️', '🌙', '⭐', '🚀', '🚗', '💡', '💻', '📱' ]; emojis.forEach(emoji => { const emojiItem = document.createElement('div'); emojiItem.className = 'emoji-item'; emojiItem.textContent = emoji; emojiItem.setAttribute('data-emoji', emoji); emojiItem.setAttribute('role', 'button'); emojiItem.setAttribute('aria-label', `Insert emoji ${emoji}`); emojiItem.addEventListener('click', function() { // Single click to select document.querySelectorAll('.emoji-item.selected').forEach(item => item.classList.remove('selected')); this.classList.add('selected'); }); emojiItem.addEventListener('dblclick', function(e) { // Double click to insert e.preventDefault(); e.stopPropagation(); const emojiChar = this.getAttribute('data-emoji'); editor.focus(); if (window.lastSelectionRange) { // Restore selection if possible const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(window.lastSelectionRange); } formatText('insertText', emojiChar); closeModal(emojiModal); }); emojiContainer.appendChild(emojiItem); }); } function filterEmojis() { const searchTerm = document.getElementById('emojiSearch').value.toLowerCase(); const emojiItems = document.querySelectorAll('.emoji-item'); emojiItems.forEach(item => { item.style.display = item.getAttribute('data-emoji').includes(searchTerm) ? 'flex' : 'none'; }); } function insertSelectedEmoji() { const selectedEmoji = document.querySelector('.emoji-item.selected'); if (selectedEmoji) { const emoji = selectedEmoji.getAttribute('data-emoji'); editor.focus(); if (window.lastSelectionRange) { // Restore selection const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(window.lastSelectionRange); } formatText('insertText', emoji); closeModal(emojiModal); selectedEmoji.classList.remove('selected'); } else { showToast('No Emoji Selected', 'Please select an emoji.', 'warning'); } } function insertCodeBlock() { // Using a custom modal for this would be better. For now, `prompt` is used. const language = window.prompt('Enter language (e.g., javascript, python) or leave blank for generic:', ''); const langClass = language ? `language-${language.trim().toLowerCase()}` : ''; // Basic placeholder, user can replace const placeholderCode = language ? `// ${language} code\nfunction example() {\n console.log("Hello, ${language}!");\n}` : 'Your code here...'; const codeBlockHtml = `
${placeholderCode}

 

`; formatText('insertHTML', codeBlockHtml); }// Tag Management function renderTagList(tagsArray = []) { tagList.innerHTML = ''; // Clear existing tags if (!tagsArray || tagsArray.length === 0) { tagList.innerHTML = '
No tags for this note.
'; return; } tagsArray.forEach(tag => { const tagEl = document.createElement('div'); tagEl.className = 'tag'; tagEl.style.position = 'relative'; tagEl.style.paddingRight = '20px'; const removeBtn = document.createElement('span'); removeBtn.innerHTML = '×'; removeBtn.style.cssText = 'position:absolute; right:5px; top:50%; transform:translateY(-50%); cursor:pointer; font-size:1.1em;'; removeBtn.title = `Remove tag: ${tag}`; removeBtn.onclick = (e) => { e.stopPropagation(); removeTag(tag); }; tagEl.textContent = tag; tagEl.appendChild(removeBtn); tagList.appendChild(tagEl); }); }function addTag() { const tagValue = tagInput.value.trim(); if (!tagValue) return; const activeNote = notes.find(note => note.id === activeNoteId); if (!activeNote) return; if (!activeNote.tags) activeNote.tags = []; if (activeNote.tags.includes(tagValue)) { showToast('Tag Exists', 'Tag already added.', 'info'); return; } activeNote.tags.push(tagValue); tagInput.value = ''; // Clear input renderTagList(activeNote.tags); saveActiveNote(); // Save note to persist tags showToast('Tag Added', `"${tagValue}" added.`, 'success'); }function removeTag(tagToRemove) { const activeNote = notes.find(note => note.id === activeNoteId); if (!activeNote || !activeNote.tags) return; activeNote.tags = activeNote.tags.filter(tag => tag !== tagToRemove); renderTagList(activeNote.tags); saveActiveNote(); // Save note showToast('Tag Removed', `"${tagToRemove}" removed.`, 'info'); }// Reminder Functions function prepareDateTimeForReminder(reminderObjStr) { reminderDate.value = ''; reminderTime.value = ''; reminderNote.value = ''; removeReminderBtn.disabled = true; if (reminderObjStr) { try { const reminder = JSON.parse(reminderObjStr); const date = new Date(reminder.date); if (!isNaN(date)) { reminderDate.value = date.toISOString().split('T')[0]; reminderTime.value = `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`; reminderNote.value = reminder.note || ''; removeReminderBtn.disabled = false; } else { throw new Error("Invalid date in reminder"); } } catch (e) { console.error("Error parsing reminder:", e); showToast('Reminder Error', 'Could not load reminder.', 'error'); setDefaultReminderDateTime(); } } else { setDefaultReminderDateTime(); } }function setDefaultReminderDateTime() { const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); tomorrow.setHours(9, 0, 0, 0); // Default to 9 AM tomorrow reminderDate.value = tomorrow.toISOString().split('T')[0]; reminderTime.value = '09:00'; }function setReminder() { const dateVal = reminderDate.value; const timeVal = reminderTime.value; const noteVal = reminderNote.value.trim(); if (!dateVal || !timeVal) { showToast('Invalid Date/Time', 'Select date and time.', 'error'); return; } const reminderDateTime = new Date(`${dateVal}T${timeVal}`); if (isNaN(reminderDateTime.getTime())) { showToast('Invalid Date/Time', 'Selected date/time is invalid.', 'error'); return; } if (reminderDateTime < new Date()) { showToast('Past Date/Time', 'Select a future date/time.', 'error'); return; } const reminderToSave = { date: reminderDateTime.toISOString(), note: noteVal }; const activeNote = notes.find(note => note.id === activeNoteId); if (activeNote) { activeNote.reminder = JSON.stringify(reminderToSave); saveActiveNote(); // Save note scheduleNotification(reminderDateTime, activeNote.title, noteVal); showToast('Reminder Set', `Set for ${formatDate(reminderDateTime.toISOString())}.`, 'success'); closeModal(reminderModal); } }function removeReminder() { const activeNote = notes.find(note => note.id === activeNoteId); if (activeNote) { activeNote.reminder = null; saveActiveNote(); // Save note showToast('Reminder Removed', 'Reminder cleared.', 'info'); closeModal(reminderModal); prepareDateTimeForReminder(null); // Reset modal fields } }function scheduleNotification(date, title, message) { const timeDiff = date.getTime() - new Date().getTime(); if (timeDiff > 0 && 'Notification' in window) { Notification.requestPermission().then(permission => { if (permission === 'granted') { setTimeout(() => { new Notification(`Reminder: ${title}`, { body: message || 'Your scheduled note reminder.', icon: 'https://placehold.co/48x48/6366f1/ffffff?text=N&fontsize=16' }); }, timeDiff); } else if (permission === 'denied') { showToast('Notifications Denied', 'Reminders won\'t show as notifications.', 'warning'); } }); } }// Voice Recording let recognition; // Make recognition instance accessible function toggleVoiceRecording() { if (!isRecording) startVoiceRecording(); else stopVoiceRecording(); }function startVoiceRecording() { if (!('webkitSpeechRecognition' in window || 'SpeechRecognition' in window)) { showToast('Not Supported', 'Voice recognition not supported.', 'error'); return; } const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; recognition = new SpeechRecognition(); // Assign to the outer scope variable recognition.continuous = true; recognition.interimResults = true; recognition.lang = navigator.language || 'en-US'; recognition.onstart = () => { isRecording = true; voiceRecording.classList.add('show'); voiceBtn.style.color = 'var(--danger)'; voiceBtn.title = "Stop Recording"; }; recognition.onresult = (event) => { let finalTranscript = ''; for (let i = event.resultIndex; i < event.results.length; ++i) { if (event.results[i].isFinal) { finalTranscript += event.results[i][0].transcript; } } if (finalTranscript) { formatText('insertText', finalTranscript.trim() + ' '); } }; recognition.onerror = (event) => { console.error('Speech recognition error:', event.error); let msg = 'Speech recognition error.'; if (event.error === 'no-speech') msg = 'No speech detected.'; else if (event.error === 'audio-capture') msg = 'Microphone problem.'; else if (event.error === 'not-allowed') msg = 'Mic permission denied.'; showToast('Voice Error', msg, 'error'); stopVoiceRecordingCleanup(); }; recognition.onend = stopVoiceRecordingCleanup; // Cleanup when stops try { recognition.start(); } catch (e) { showToast('Voice Error', 'Could not start voice recognition.', 'error'); stopVoiceRecordingCleanup(); } } function stopVoiceRecordingCleanup() { isRecording = false; voiceRecording.classList.remove('show'); voiceBtn.style.color = ''; // Reset color voiceBtn.title = "Voice to Text"; if (recognition) { // Ensure recognition exists before trying to call methods on it recognition.onstart = null; recognition.onresult = null; recognition.onerror = null; recognition.onend = null; recognition = null; // Clear instance } }function stopVoiceRecording() { if (recognition && isRecording) { recognition.stop(); // This will trigger 'onend' which calls cleanup } else { stopVoiceRecordingCleanup(); // Manual cleanup if not actively recording } }// Spell Check function toggleSpellCheck() { spellCheckEnabled = !spellCheckEnabled; editor.setAttribute('spellcheck', spellCheckEnabled.toString()); spellCheckBtn.style.color = spellCheckEnabled ? 'var(--primary)' : ''; spellCheckBtn.title = spellCheckEnabled ? "Disable Spell Check" : "Enable Spell Check"; showToast('Spell Check', `Browser spell check ${spellCheckEnabled ? 'enabled' : 'disabled'}.`, 'info'); editor.focus(); }// Context Menu function showContextMenu(e) { const selection = window.getSelection(); const isTextSelected = selection && selection.toString().length > 0; const isClickInsideEditor = editor.contains(e.target);if (!isClickInsideEditor && !isTextSelected && !editor.isSameNode(e.target)) { // Allow if click is on editor itself hideContextMenu(); return; } e.preventDefault(); saveCurrentSelection(); // Save for context menu actions window.lastSelectionRangeForContextMenu = window.lastSelectionRange; // Use the general onelet left = e.clientX; let top = e.clientY; const menuWidth = contextMenu.offsetWidth || 200; const menuHeight = contextMenu.offsetHeight || 300; // Approximate if (left + menuWidth > window.innerWidth) left = window.innerWidth - menuWidth - 5; if (top + menuHeight > window.innerHeight) top = window.innerHeight - menuHeight - 5; contextMenu.style.left = `${Math.max(0, left)}px`; // Ensure not off-screen contextMenu.style.top = `${Math.max(0, top)}px`; contextMenu.classList.add('show'); }function hideContextMenu() { contextMenu.classList.remove('show'); }function setupContextMenuActions() { contextMenu.querySelectorAll('.context-menu-item').forEach(item => { item.addEventListener('click', () => { const action = item.dataset.action; editor.focus(); if (window.lastSelectionRangeForContextMenu) { // Restore selection const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(window.lastSelectionRangeForContextMenu); } switch (action) { case 'selectAll': formatText('selectAll'); break; case 'cut': formatText('cut'); break; case 'copy': formatText('copy'); break; case 'paste': navigator.clipboard.readText() .then(text => { if (text) formatText('insertText', text); else formatText('paste'); // Fallback }) .catch(() => formatText('paste')); // Fallback break; case 'bold': formatText('bold'); break; case 'italic': formatText('italic'); break; case 'underline': formatText('underline'); break; case 'link': const selectedText = window.getSelection().toString(); if (selectedText) linkText.value = selectedText; else linkText.value = ''; linkUrl.value = ''; // Clear URL field openModal(linkModal); break; case 'lookup': lookupSelection(); break; } hideContextMenu(); }); }); }// Search Notes function searchNotes() { const query = searchInput.value.toLowerCase().trim(); if (!query) { renderNoteList(); return; } const results = notes.filter(note => note.title.toLowerCase().includes(query) || note.content.replace(/<[^>]*>/g, '').toLowerCase().includes(query) || (note.tags && note.tags.some(tag => tag.toLowerCase().includes(query))) ); renderNoteList(results); }// Toast Notifications function showToast(title, message, type = 'info') { const toast = document.createElement('div'); toast.className = `toast ${type}`; toast.setAttribute('role', 'alert'); let iconClass = type === 'success' ? 'fa-check-circle' : type === 'error' ? 'fa-exclamation-circle' : type === 'warning' ? 'fa-exclamation-triangle' : 'fa-info-circle'; toast.innerHTML = `
${title}
${message}
`; toastsContainer.appendChild(toast); toast.querySelector('.toast-close').onclick = () => toast.remove(); setTimeout(() => { if (toast.parentNode) toast.remove(); }, 3000); }// Editor Event Handlers function onEditorInput() { updateWordCount(); isNoteModified = true; lastSaved.textContent = 'Unsaved changes...'; clearTimeout(window.autoSaveTimeout); window.autoSaveTimeout = setTimeout(saveActiveNote, 2500); // Autosave after 2.5s }function onEditorKeyDown(e) { if (e.key === 's' && (e.ctrlKey || e.metaKey)) { // Ctrl+S or Cmd+S e.preventDefault(); saveActiveNote(); } if (e.key === 'Tab') { // Handle Tab key for indent/outdent const selection = window.getSelection(); if (selection && selection.rangeCount > 0) { const range = selection.getRangeAt(0); const commonAncestor = range.commonAncestorContainer; let parentListItem = commonAncestor.nodeType === Node.ELEMENT_NODE ? commonAncestor.closest('li') : commonAncestor.parentElement?.closest('li'); let parentCodeBlock = commonAncestor.nodeType === Node.ELEMENT_NODE ? commonAncestor.closest('pre > code') : commonAncestor.parentElement?.closest('pre > code');if (parentListItem) { e.preventDefault(); formatText(e.shiftKey ? 'outdent' : 'indent'); } else if (parentCodeBlock) { e.preventDefault(); formatText('insertText', ' '); // Insert 4 spaces for tab in code } // else: allow default tab behavior if not in list or code } } }// Utility Functions function formatDate(dateString) { const date = new Date(dateString); const now = new Date(); const diffMs = now - date; const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); const timeOptions = { hour: 'numeric', minute: 'numeric', hour12: true };if (diffDays === 0 && date.getDate() === now.getDate()) { return `Today at ${date.toLocaleTimeString(navigator.language, timeOptions)}`; } const yesterday = new Date(now); yesterday.setDate(now.getDate() - 1); if (diffDays <= 1 && date.getDate() === yesterday.getDate() && date.getMonth() === yesterday.getMonth() && date.getFullYear() === yesterday.getFullYear()) { return `Yesterday at ${date.toLocaleTimeString(navigator.language, timeOptions)}`; } if (diffDays < 7) { const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; return `${days[date.getDay()]} at ${date.toLocaleTimeString(navigator.language, timeOptions)}`; } return date.toLocaleDateString(navigator.language, { year: 'numeric', month: 'short', day: 'numeric' }); }function formatTimeAgo(date) { const now = new Date(); const diffMs = now - date; const diffSecs = Math.round(diffMs / 1000); if (diffSecs < 5) return 'just now'; if (diffSecs < 60) return `${diffSecs}s ago`; const diffMins = Math.round(diffSecs / 60); if (diffMins < 60) return `${diffMins}m ago`; const diffHours = Math.round(diffMins / 60); if (diffHours < 24) return `${diffHours}h ago`; const diffDays = Math.round(diffHours / 24); if (diffDays < 30) return `${diffDays}d ago`; const diffMonths = Math.round(diffDays / 30.44); // Average days in month if (diffMonths < 12) return `${diffMonths}mo ago`;const diffYears = Math.round(diffDays/365.25); return `${diffYears}y ago`; }function convertHtmlToMarkdownService(html) { if (turndownService) { try { // Add a rule for
to ensure they become newlines in Markdown turndownService.addRule('brToNewline', { filter: 'br', replacement: function () { return '\n'; } }); // Add a rule for paragraphs to ensure double newline after them turndownService.addRule('paragraph', { filter: 'p', replacement: function (content) { return '\n\n' + content + '\n\n'; } }); // Clean up excessive newlines that might result let markdown = turndownService.turndown(html); markdown = markdown.replace(/\n{3,}/g, '\n\n'); // Replace 3+ newlines with 2 return markdown.trim();} catch(e) { showToast('Markdown Error', 'Could not convert to Markdown.', 'error'); console.error("Turndown conversion error:", e); // Basic fallback: strip tags, try to preserve line breaks from

and
let text = html.replace(//gi, '\n'); text = text.replace(/<\/p>/gi, '\n\n'); const tempDiv = document.createElement('div'); tempDiv.innerHTML = text; // Use browser to strip remaining tags return (tempDiv.textContent || tempDiv.innerText || "").trim(); } } // Absolute fallback if Turndown is not loaded const tempDiv = document.createElement('div'); tempDiv.innerHTML = html.replace(//gi, '\n').replace(/<\/p>/gi, '\n\n'); return (tempDiv.textContent || tempDiv.innerText || "").trim(); }function lookupSelection() { const selection = window.getSelection().toString().trim(); if (selection) { const searchUrl = `https://www.google.com/search?q=${encodeURIComponent(selection)}`; window.open(searchUrl, '_blank', 'noopener,noreferrer'); } else { showToast('No Selection', 'Select text to look up.', 'info'); } }// Storage Functions function saveNotesToStorage() { try { localStorage.setItem('notes-app-data', JSON.stringify(notes)); // Changed key for uniqueness } catch (e) { console.error('Error saving notes to localStorage:', e); let msg = 'Could not save notes. Storage full/disabled?'; if (e.name === 'QuotaExceededError') msg = 'Local storage is full.'; showToast('Storage Error', msg, 'error'); } }function loadNotesFromStorage() { try { const storedNotes = localStorage.getItem('notes-app-data'); // Use new key if (storedNotes) { notes = JSON.parse(storedNotes); if (notes.length > 0) { // Ensure all notes have necessary fields (for backward compatibility if structure changes) notes = notes.map(note => ({ tags: [], // Default empty array for tags reminder: null, // Default null for reminder ...note // Spread existing note properties, overriding defaults if present })); activeNoteId = notes[0].id; // Default to first note // Check if URL has a note ID to load if (window.location.hash && window.location.hash.startsWith('#note=')) { const noteIdFromUrl = parseInt(window.location.hash.substring(6)); const noteToLoad = notes.find(n => n.id === noteIdFromUrl); if (noteToLoad) activeNoteId = noteIdFromUrl; } renderNoteList(); loadNote(activeNoteId); } else { // Stored notes array is empty const note = createNewNote('Welcome Note', defaultContent); loadNote(note.id); } } else { // No notes found in storage const note = createNewNote('Welcome Note', defaultContent); loadNote(note.id); } } catch (e) { console.error('Error loading notes from localStorage:', e); showToast('Storage Error', 'Could not load notes.', 'error'); notes = []; // Clear potentially corrupted notes const note = createNewNote('Welcome Note', defaultContent); // Fallback to default loadNote(note.id); } } // Handle loading note from URL hash on initial load and hash change window.addEventListener('hashchange', () => { if (window.location.hash && window.location.hash.startsWith('#note=')) { const noteIdFromUrl = parseInt(window.location.hash.substring(6)); const noteExists = notes.some(n => n.id === noteIdFromUrl); if (noteExists) { loadNote(noteIdFromUrl); } else { showToast('Note Not Found', 'The note specified in the URL does not exist.', 'warning'); if(notes.length > 0) loadNote(notes[0].id); // Load first note if specified one not found else { // Create default if no notes exist const note = createNewNote('Welcome Note', defaultContent); loadNote(note.id); } } } });// Initialize the app init(); Related Tools Section - Embed Friendly