// Markdown Editor Application (function () { 'use strict'; // State management let currentFile = null; let editor = null; let isScrollingSynced = true; let scrollTimeout = null; let isDarkMode = false; // Dark mode management function initDarkMode() { // Check for saved preference const savedMode = localStorage.getItem('darkMode'); if (savedMode === 'true') { enableDarkMode(); } } function enableDarkMode() { isDarkMode = true; document.body.classList.add('dark-mode'); document.getElementById('darkModeIcon').innerHTML = ''; localStorage.setItem('darkMode', 'true'); // Update mermaid theme mermaid.initialize({ startOnLoad: false, theme: 'dark', securityLevel: 'loose' }); // Re-render preview if there's content if (editor && editor.getValue()) { updatePreview(); } } function disableDarkMode() { isDarkMode = false; document.body.classList.remove('dark-mode'); // document.getElementById('darkModeIcon').textContent = '🌙'; localStorage.setItem('darkMode', 'false'); // Update mermaid theme mermaid.initialize({ startOnLoad: false, theme: 'default', securityLevel: 'loose' }); // Re-render preview if there's content if (editor && editor.getValue()) { updatePreview(); } } function toggleDarkMode() { if (isDarkMode) { disableDarkMode(); } else { enableDarkMode(); } } // Initialize Mermaid mermaid.initialize({ startOnLoad: false, theme: 'default', securityLevel: 'loose' }); // Configure marked.js for markdown parsing marked.setOptions({ breaks: true, gfm: true, headerIds: true, mangle: false, sanitize: false, // Allow HTML in markdown smartLists: true, smartypants: true, xhtml: false }); // Handle image upload async function uploadImage(file) { const formData = new FormData(); formData.append('file', file); try { const response = await fetch('/api/upload-image', { method: 'POST', body: formData }); if (!response.ok) throw new Error('Upload failed'); const result = await response.json(); return result.url; } catch (error) { console.error('Error uploading image:', error); showNotification('Error uploading image', 'danger'); return null; } } // Handle drag and drop function setupDragAndDrop() { const editorElement = document.querySelector('.CodeMirror'); // Prevent default drag behavior ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { editorElement.addEventListener(eventName, preventDefaults, false); }); function preventDefaults(e) { e.preventDefault(); e.stopPropagation(); } // Highlight drop zone ['dragenter', 'dragover'].forEach(eventName => { editorElement.addEventListener(eventName, () => { editorElement.classList.add('drag-over'); }, false); }); ['dragleave', 'drop'].forEach(eventName => { editorElement.addEventListener(eventName, () => { editorElement.classList.remove('drag-over'); }, false); }); // Handle drop editorElement.addEventListener('drop', async (e) => { const files = e.dataTransfer.files; if (files.length === 0) return; // Filter for images only const imageFiles = Array.from(files).filter(file => file.type.startsWith('image/') ); if (imageFiles.length === 0) { showNotification('Please drop image files only', 'warning'); return; } showNotification(`Uploading ${imageFiles.length} image(s)...`, 'info'); // Upload images for (const file of imageFiles) { const url = await uploadImage(file); if (url) { // Insert markdown image syntax at cursor const cursor = editor.getCursor(); const imageMarkdown = ``; editor.replaceRange(imageMarkdown, cursor); editor.setCursor(cursor.line, cursor.ch + imageMarkdown.length); showNotification(`Image uploaded: ${file.name}`, 'success'); } } }, false); // Also handle paste events for images editorElement.addEventListener('paste', async (e) => { const items = e.clipboardData?.items; if (!items) return; for (const item of items) { if (item.type.startsWith('image/')) { e.preventDefault(); const file = item.getAsFile(); if (file) { showNotification('Uploading pasted image...', 'info'); const url = await uploadImage(file); if (url) { const cursor = editor.getCursor(); const imageMarkdown = ``; editor.replaceRange(imageMarkdown, cursor); showNotification('Image uploaded successfully', 'success'); } } } } }); } // Initialize CodeMirror editor function initEditor() { const textarea = document.getElementById('editor'); editor = CodeMirror.fromTextArea(textarea, { mode: 'markdown', theme: 'monokai', lineNumbers: true, lineWrapping: true, autofocus: true, extraKeys: { 'Ctrl-S': function () { saveFile(); }, 'Cmd-S': function () { saveFile(); } } }); // Update preview on change editor.on('change', debounce(updatePreview, 300)); // Setup drag and drop after editor is ready setTimeout(setupDragAndDrop, 100); // Sync scroll editor.on('scroll', handleEditorScroll); } // Debounce function to limit update frequency function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // Update preview with markdown content async function updatePreview() { const content = editor.getValue(); const previewDiv = document.getElementById('preview'); if (!content.trim()) { previewDiv.innerHTML = `
Start typing in the editor to see the preview
([\s\S]*?)<\/code><\/pre>/g,
                '$1'
            );
            previewDiv.innerHTML = html;
            // Apply syntax highlighting to code blocks
            const codeBlocks = previewDiv.querySelectorAll('pre code');
            codeBlocks.forEach(block => {
                // Detect language from class name
                const languageClass = Array.from(block.classList).find(cls => cls.startsWith('language-'));
                if (languageClass && languageClass !== 'language-mermaid') {
                    Prism.highlightElement(block);
                }
            });
            // Render mermaid diagrams
            const mermaidElements = previewDiv.querySelectorAll('.mermaid');
            if (mermaidElements.length > 0) {
                try {
                    await mermaid.run({
                        nodes: mermaidElements,
                        suppressErrors: false
                    });
                } catch (error) {
                    console.error('Mermaid rendering error:', error);
                }
            }
        } catch (error) {
            console.error('Preview rendering error:', error);
            previewDiv.innerHTML = `
                
                    Error rendering preview: ${error.message}
                
            `;
        }
    }
    // Handle editor scroll for synchronized scrolling
    function handleEditorScroll() {
        if (!isScrollingSynced) return;
        clearTimeout(scrollTimeout);
        scrollTimeout = setTimeout(() => {
            const editorScrollInfo = editor.getScrollInfo();
            const editorScrollPercentage = editorScrollInfo.top / (editorScrollInfo.height - editorScrollInfo.clientHeight);
            const previewPane = document.querySelector('.preview-pane');
            const previewScrollHeight = previewPane.scrollHeight - previewPane.clientHeight;
            if (previewScrollHeight > 0) {
                previewPane.scrollTop = editorScrollPercentage * previewScrollHeight;
            }
        }, 10);
    }
    // Load file list from server
    async function loadFileList() {
        try {
            const response = await fetch('/api/files');
            if (!response.ok) throw new Error('Failed to load file list');
            const files = await response.json();
            const fileListDiv = document.getElementById('fileList');
            if (files.length === 0) {
                fileListDiv.innerHTML = 'No files yet';
                return;
            }
            fileListDiv.innerHTML = files.map(file => `
                
                    ${file.filename}
                    ${formatFileSize(file.size)}
                
            `).join('');
            // Add click handlers
            document.querySelectorAll('.file-item').forEach(item => {
                item.addEventListener('click', (e) => {
                    e.preventDefault();
                    const filename = item.dataset.filename;
                    loadFile(filename);
                });
            });
        } catch (error) {
            console.error('Error loading file list:', error);
            showNotification('Error loading file list', 'danger');
        }
    }
    // Load a specific file
    async function loadFile(filename) {
        try {
            const response = await fetch(`/api/files/${filename}`);
            if (!response.ok) throw new Error('Failed to load file');
            const data = await response.json();
            currentFile = data.filename;
            // Update UI
            document.getElementById('filenameInput').value = data.filename;
            editor.setValue(data.content);
            // Update active state in file list
            document.querySelectorAll('.file-item').forEach(item => {
                item.classList.toggle('active', item.dataset.filename === filename);
            });
            updatePreview();
            showNotification(`Loaded ${filename}`, 'success');
        } catch (error) {
            console.error('Error loading file:', error);
            showNotification('Error loading file', 'danger');
        }
    }
    // Save current file
    async function saveFile() {
        const filename = document.getElementById('filenameInput').value.trim();
        if (!filename) {
            showNotification('Please enter a filename', 'warning');
            return;
        }
        const content = editor.getValue();
        try {
            const response = await fetch('/api/files', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ filename, content })
            });
            if (!response.ok) throw new Error('Failed to save file');
            const result = await response.json();
            currentFile = result.filename;
            showNotification(`Saved ${result.filename}`, 'success');
            loadFileList();
        } catch (error) {
            console.error('Error saving file:', error);
            showNotification('Error saving file', 'danger');
        }
    }
    // Delete current file
    async function deleteFile() {
        const filename = document.getElementById('filenameInput').value.trim();
        if (!filename) {
            showNotification('No file selected', 'warning');
            return;
        }
        if (!confirm(`Are you sure you want to delete ${filename}?`)) {
            return;
        }
        try {
            const response = await fetch(`/api/files/${filename}`, {
                method: 'DELETE'
            });
            if (!response.ok) throw new Error('Failed to delete file');
            showNotification(`Deleted ${filename}`, 'success');
            // Clear editor
            currentFile = null;
            document.getElementById('filenameInput').value = '';
            editor.setValue('');
            updatePreview();
            loadFileList();
        } catch (error) {
            console.error('Error deleting file:', error);
            showNotification('Error deleting file', 'danger');
        }
    }
    // Create new file
    function newFile() {
        currentFile = null;
        document.getElementById('filenameInput').value = '';
        editor.setValue('');
        updatePreview();
        // Remove active state from all file items
        document.querySelectorAll('.file-item').forEach(item => {
            item.classList.remove('active');
        });
        showNotification('New file created', 'info');
    }
    // Format file size for display
    function formatFileSize(bytes) {
        if (bytes === 0) return '0 B';
        const k = 1024;
        const sizes = ['B', 'KB', 'MB', 'GB'];
        const i = Math.floor(Math.log(bytes) / Math.log(k));
        return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
    }
    // Show notification
    function showNotification(message, type = 'info') {
        // Create toast notification
        const toastContainer = document.getElementById('toastContainer') || createToastContainer();
        const toast = document.createElement('div');
        toast.className = `toast align-items-center text-white bg-${type} border-0`;
        toast.setAttribute('role', 'alert');
        toast.setAttribute('aria-live', 'assertive');
        toast.setAttribute('aria-atomic', 'true');
        toast.innerHTML = `
            
                ${message}
                
            
        `;
        toastContainer.appendChild(toast);
        const bsToast = new bootstrap.Toast(toast, { delay: 3000 });
        bsToast.show();
        toast.addEventListener('hidden.bs.toast', () => {
            toast.remove();
        });
    }
    // Create toast container if it doesn't exist
    function createToastContainer() {
        const container = document.createElement('div');
        container.id = 'toastContainer';
        container.className = 'toast-container position-fixed top-0 end-0 p-3';
        container.style.zIndex = '9999';
        document.body.appendChild(container);
        return container;
    }
    // Initialize application
    function init() {
        initDarkMode();
        initEditor();
        loadFileList();
        // Set up event listeners
        document.getElementById('saveBtn').addEventListener('click', saveFile);
        document.getElementById('deleteBtn').addEventListener('click', deleteFile);
        document.getElementById('newFileBtn').addEventListener('click', newFile);
        document.getElementById('darkModeToggle').addEventListener('click', toggleDarkMode);
        // Keyboard shortcuts
        document.addEventListener('keydown', (e) => {
            if ((e.ctrlKey || e.metaKey) && e.key === 's') {
                e.preventDefault();
                saveFile();
            }
        });
        console.log('Markdown Editor initialized');
    }
    // Start application when DOM is ready
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();