/** * Toast UI Editor initialization and management */ /** * Initialize or update the Toast UI Editor for a file * @param {string} content - Content to display * @param {boolean} isMarkdown - Whether content is markdown * @param {string} filePath - Path of the file * @param {string} fileName - Name of the file * @param {FileSystemFileHandle} fileHandle - File handle for saving * @param {number} lastModified - Timestamp of last modification */ function initializeEditor(content, isMarkdown = true, filePath = '', fileName = '', fileHandle = null, lastModified = null) { // Parse front matter let frontMatterData = {}; let markdownBody = content; if (isMarkdown && content) { try { const parsed = parseFrontMatter(content); frontMatterData = parsed.data; markdownBody = parsed.content; } catch (error) { console.error('Failed to parse front matter:', error); } } const contentContainer = document.getElementById('content-container'); if (!contentContainer) { alert('Error: content-container element not found!'); return; } // Hide all file view containers document.querySelectorAll('.file-view-container').forEach(container => { container.style.display = 'none'; }); // Check if file already has an instance if (editorInstances.has(filePath)) { const existingInstance = editorInstances.get(filePath); if (existingInstance.fileViewContainer) { existingInstance.fileViewContainer.style.display = 'flex'; } return existingInstance.editor; } // Create file view container const fileViewContainer = document.createElement('div'); fileViewContainer.className = 'file-view-container flex flex-col h-full'; // Create file header const fileHeader = document.createElement('div'); fileHeader.className = 'file-header flex justify-between items-center px-4 py-2 bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-200 font-medium border-b border-gray-200 dark:border-gray-700'; const fileTitle = document.createElement('span'); fileTitle.textContent = fileName || 'No file selected'; fileHeader.appendChild(fileTitle); // Button container for alignment const buttonContainer = document.createElement('div'); buttonContainer.className = 'flex gap-2'; // Determine if this is a scratchpad (no file handle) const isScratchpad = !fileHandle; const isReadOnlyHandle = !!(fileHandle && fileHandle._readOnly); // Save button (or Save As for scratchpads / read-only server files) const saveButton = document.createElement('button'); saveButton.className = 'btn btn-primary btn-sm'; saveButton.textContent = (isScratchpad || isReadOnlyHandle) ? 'Save As...' : 'Save File'; saveButton.disabled = !isScratchpad; // Scratchpads can always save; read-only enables on edit buttonContainer.appendChild(saveButton); // Reload button (only for files, not scratchpads) — icon to match file-tree refresh let reloadButton = null; if (!isScratchpad) { reloadButton = document.createElement('button'); reloadButton.className = 'btn btn-secondary btn-sm'; reloadButton.textContent = '↻'; reloadButton.title = 'Reload from disk (discards unsaved changes)'; reloadButton.setAttribute('aria-label', 'Reload from disk'); buttonContainer.appendChild(reloadButton); } fileHeader.appendChild(buttonContainer); fileViewContainer.appendChild(fileHeader); // Content area const contentArea = document.createElement('div'); contentArea.className = 'flex flex-col flex-1 overflow-hidden'; // Editor area with TOC const editorArea = document.createElement('div'); editorArea.className = 'flex flex-row flex-1 overflow-hidden'; // TOC pane (markdown only) let tocContainer = null; let frontMatterTextarea = null; if (isMarkdown) { const tocPane = document.createElement('div'); tocPane.className = 'toc-pane bg-white dark:bg-gray-900 border-r border-gray-200 dark:border-gray-700'; tocPane.style.width = '325px'; tocPane.style.minWidth = '150px'; // Front matter section (collapsible, height-resizable) const frontMatterNav = document.createElement('div'); frontMatterNav.className = 'front-matter-nav'; frontMatterNav.style.height = '180px'; const frontMatterHeader = document.createElement('div'); frontMatterHeader.className = 'front-matter-header pane-section-header cursor-pointer'; const toggleIcon = document.createElement('span'); toggleIcon.textContent = '▼'; toggleIcon.className = 'toggle-icon'; frontMatterHeader.appendChild(toggleIcon); const headerText = document.createElement('span'); headerText.textContent = 'YAML Front Matter'; frontMatterHeader.appendChild(headerText); frontMatterNav.appendChild(frontMatterHeader); const frontMatterContent = document.createElement('div'); frontMatterContent.className = 'front-matter-content'; frontMatterTextarea = document.createElement('textarea'); frontMatterTextarea.className = 'front-matter-textarea'; frontMatterTextarea.placeholder = 'title: Document Title\ndate: 2024-01-01\ntags: [example]'; if (frontMatterData && Object.keys(frontMatterData).length > 0) { try { let yamlText = ''; for (const [key, value] of Object.entries(frontMatterData)) { if (Array.isArray(value)) { yamlText += `${key}: [${value.map(v => `"${v}"`).join(', ')}]\n`; } else { yamlText += `${key}: ${value}\n`; } } frontMatterTextarea.value = yamlText.trim(); } catch (error) { console.warn('Failed to stringify front matter:', error); frontMatterTextarea.value = ''; } } frontMatterContent.appendChild(frontMatterTextarea); frontMatterNav.appendChild(frontMatterContent); tocPane.appendChild(frontMatterNav); // Horizontal resizer between front-matter and TOC const fmTocResizer = document.createElement('div'); fmTocResizer.className = 'pane-resizer horizontal'; tocPane.appendChild(fmTocResizer); // TOC section const tocSection = document.createElement('div'); tocSection.className = 'toc-section'; const tocHeader = document.createElement('div'); tocHeader.className = 'toc-header pane-section-header'; const tocTitle = document.createElement('span'); tocTitle.textContent = 'Table of Contents'; tocHeader.appendChild(tocTitle); const tocDepthSelector = document.createElement('select'); tocDepthSelector.className = 'toc-depth-selector'; tocDepthSelector.innerHTML = ` `; tocHeader.appendChild(tocDepthSelector); tocSection.appendChild(tocHeader); tocContainer = document.createElement('div'); tocContainer.className = 'toc-container toc-content'; tocSection.appendChild(tocContainer); tocPane.appendChild(tocSection); // Toggle: collapsed only shows the header. Hide content + horizontal resizer. let fmIsCollapsed = false; frontMatterHeader.addEventListener('click', () => { fmIsCollapsed = !fmIsCollapsed; frontMatterNav.classList.toggle('collapsed', fmIsCollapsed); toggleIcon.textContent = fmIsCollapsed ? '▶' : '▼'; fmTocResizer.style.display = fmIsCollapsed ? 'none' : ''; if (fmIsCollapsed) { frontMatterNav.style.height = ''; } else { frontMatterNav.style.height = '180px'; } }); editorArea.appendChild(tocPane); // Vertical resizer between toc-pane and editor (placed inside editorArea) const tocResizer = document.createElement('div'); tocResizer.className = 'pane-resizer bg-gray-200 dark:bg-gray-700 transition-colors relative z-10 w-1 cursor-col-resize hover:bg-blue-500'; tocResizer.setAttribute('data-resizer-for', 'toc-pane'); editorArea.appendChild(tocResizer); makeResizable(tocResizer, tocPane); // Make the front-matter / TOC split height-adjustable makeHeightResizable(fmTocResizer, frontMatterNav, tocPane); tocDepthSelector.addEventListener('change', function () { const depth = parseInt(this.value); if (window.updateToc && editorInstance) { const currentContent = editorInstance.getMarkdown(); window.updateToc(currentContent, tocContainer, editorInstance, depth); } }); } // Editor container const editorContainer = document.createElement('div'); editorContainer.className = 'editor-instance flex-1 overflow-hidden'; editorArea.appendChild(editorContainer); contentArea.appendChild(editorArea); fileViewContainer.appendChild(contentArea); contentContainer.appendChild(fileViewContainer); // Check Toast UI availability if (typeof toastui === 'undefined') { alert('Error: Toast UI library not loaded!'); editorContainer.innerHTML = '