document.addEventListener('DOMContentLoaded', function() { console.log('DOM loaded - initializing builder'); // Initialize preview mode state let previewModeActive = false; // Preview Mode Toggle const previewToggle = document.getElementById('preview-toggle'); if (previewToggle) { console.log('Preview toggle found, adding event listener'); document.addEventListener('click', function(e) { // Hacky way: find any click anywhere near the toggle and check status if (e.target.closest('.toggle-switch') || e.target.id === 'preview-toggle') { // Give time for the checkbox to update setTimeout(function() { previewModeActive = previewToggle.checked; console.log('Preview toggle changed to:', previewModeActive); // Add a direct style change to visually confirm toggle works document.querySelector('.toggle-label').style.color = previewModeActive ? 'blue' : ''; togglePreviewMode(previewModeActive); }, 50); } }); } else { console.error('Preview toggle element not found!'); } // Function to toggle preview mode function togglePreviewMode(active) { console.log('Toggle preview mode called with active =', active); // Use inline styles for preview mode // This directly styles elements without relying on classes const styleId = 'preview-mode-style'; let styleElement = document.getElementById(styleId); if (active) { // Create style element if it doesn't exist if (!styleElement) { styleElement = document.createElement('style'); styleElement.id = styleId; document.head.appendChild(styleElement); } // Find all textareas with content to mark for display const contentTextareas = []; document.querySelectorAll('textarea').forEach(textarea => { if (textarea.value && textarea.value.trim()) { // Get the ID for later targeting contentTextareas.push('#' + textarea.id); // Mark parents for visibility const field = textarea.closest('.field'); if (field) field.classList.add('has-content'); const component = textarea.closest('.component-card'); if (component) component.classList.add('has-content'); const stage = textarea.closest('.stage-section'); if (stage) stage.classList.add('has-content'); // Ensure stage is expanded if (stage) { const stageBody = stage.querySelector('.stage-body'); if (stageBody) stageBody.style.display = 'block'; } } }); // Apply direct CSS to create preview mode styleElement.textContent = ` /* Hide module selectors and empty fields */ .module-selector { display: none !important; } .field:not(.has-content) { display: none !important; } /* Hide empty components and sections */ .component-card:not(.has-content) { display: none !important; } .stage-section:not(.has-content) { display: none !important; } /* Hide template selector */ .protocol-template-selector { display: none !important; } .template-body { display: none !important; } /* Expand all sections */ .stage-body { display: block !important; } /* Make textareas read-only appearance */ textarea { border: none !important; background-color: transparent !important; padding: 0 !important; min-height: unset !important; height: auto !important; resize: none !important; pointer-events: none !important; outline: none !important; box-shadow: none !important; } /* Only show filled textareas */ textarea:not(${contentTextareas.join(',')}) { display: none !important; } /* Clean styling for components */ .component-header { background-color: transparent !important; border-bottom: none !important; padding-bottom: 0 !important; } .component-short-label { display: block !important; } .component-description { display: none !important; } /* Improved typography for preview mode */ .component-short-label { font-size: 1.2rem !important; margin-bottom: 1rem !important; color: #000 !important; border-bottom: 1px solid #eee !important; padding-bottom: 0.5rem !important; font-weight: 600 !important; } `; // Replace textareas with divs for better display in preview mode updatePreviewContent(); // Make other fields read-only document.querySelectorAll('#community-name, #protocol-summary').forEach(el => { el.setAttribute('readonly', 'readonly'); }); } else { // Remove preview styles if (styleElement) { styleElement.textContent = ''; } // Remove preview content divs and show textareas again document.querySelectorAll('.preview-content').forEach(div => { const textareaId = div.dataset.forTextarea; if (textareaId) { const textarea = document.getElementById(textareaId); if (textarea) { textarea.style.display = ''; } } div.parentNode.removeChild(div); }); // Make fields editable again document.querySelectorAll('textarea, #community-name, #protocol-summary').forEach(el => { el.removeAttribute('readonly'); }); // Remove content markers document.querySelectorAll('.has-content').forEach(el => { el.classList.remove('has-content'); }); // Reset any display properties that were directly set document.querySelectorAll('.stage-body').forEach(el => { el.style.display = ''; }); } } // Function to mark components and stages that have content function markComponentsWithContent() { // First reset all markers document.querySelectorAll('.has-content').forEach(el => { el.classList.remove('has-content'); }); // Mark fields with content document.querySelectorAll('textarea').forEach(textarea => { if (textarea.value && textarea.value.trim()) { const field = textarea.closest('.field'); if (field) field.classList.add('has-content'); const component = textarea.closest('.component-card'); if (component) component.classList.add('has-content'); const stage = textarea.closest('.stage-section'); if (stage) stage.classList.add('has-content'); } }); // Show all expanded sections that have content document.querySelectorAll('.stage-section.has-content').forEach(stage => { const stageBody = stage.querySelector('.stage-body'); if (stageBody) { stageBody.style.display = 'block'; const toggleBtn = stage.querySelector('.toggle-btn'); if (toggleBtn) { toggleBtn.textContent = '-'; } } }); } // Load template data let templates = []; // Function to fetch templates - simplified approach function fetchTemplates() { console.log('Fetching templates...'); // Load all templates at startup try { // First try loading from the standard path fetch('/js/templates/index.js') .then(response => { if (!response.ok) { throw new Error(`Failed to fetch templates: ${response.status} ${response.statusText}`); } return response.text(); }) .then(moduleText => { // Add a script element to the document const script = document.createElement('script'); script.type = 'module'; script.textContent = moduleText; document.head.appendChild(script); // Set a timeout to check for templates setTimeout(() => { // See if templates were loaded if (window.allTemplates && Array.isArray(window.allTemplates)) { console.log('Templates loaded successfully:', window.allTemplates.length, 'templates found'); templates = window.allTemplates; // Populate the template selector populateTemplateSelector(templates); // Template selection is now handled by buttons in the template options console.log('Template selection will be handled by buttons in the template options'); } else { console.error('Templates not available after loading script'); } }, 500); }) .catch(error => { console.error('Error fetching templates:', error); // Try alternate loading method with ES modules import console.log('Trying alternate loading method with ES modules...'); import('/js/templates/index.js') .then(module => { templates = module.default; console.log('Templates loaded successfully using ES modules:', templates.length, 'templates found'); // Populate the template selector populateTemplateSelector(templates); // Template selection is now handled by buttons in the template options console.log('Template selection will be handled by buttons in the template options'); }) .catch(importError => { console.error('ES module import also failed:', importError); console.error('Could not load templates through either method'); }); }); } catch (e) { console.error('Error in template loading:', e); } } // Function to populate the template selector with cards function populateTemplateSelector(templatesList) { if (!templatesList || templatesList.length === 0) { console.error('Cannot populate template selector - missing templates'); return; } console.log('Populating template selector with', templatesList.length, 'templates'); // Find the template options container const templateOptionsContainer = document.querySelector('.template-options'); if (!templateOptionsContainer) { console.error('Template options container not found'); return; } // Clear existing template options templateOptionsContainer.innerHTML = ''; // Create the "Create Your Own" option first const createYourOwnOption = document.createElement('div'); createYourOwnOption.className = 'template-option'; createYourOwnOption.setAttribute('data-template-id', ''); const createYourOwnBtn = document.createElement('button'); createYourOwnBtn.className = 'template-select-btn'; createYourOwnBtn.textContent = 'Create Your Own Protocol'; createYourOwnBtn.setAttribute('type', 'button'); const createYourOwnDesc = document.createElement('p'); createYourOwnDesc.className = 'template-description'; createYourOwnDesc.textContent = 'Start with a blank protocol and build it from scratch.'; createYourOwnOption.appendChild(createYourOwnBtn); createYourOwnOption.appendChild(createYourOwnDesc); createYourOwnOption.addEventListener('click', function() { console.log('Create your own option clicked'); clearAllFields(); }); templateOptionsContainer.appendChild(createYourOwnOption); // Verify templates have required properties let validTemplateCount = 0; templatesList.forEach(template => { if (!template.id || !template.title || !template.description) { console.warn('Template missing required fields:', template); } else { validTemplateCount++; } }); console.log(`Found ${validTemplateCount} valid templates out of ${templatesList.length} total`); // Add template options as cards templatesList.forEach(template => { if (!template.id || !template.title || !template.description) { return; // Skip invalid templates } const templateOption = document.createElement('div'); templateOption.className = 'template-option'; templateOption.setAttribute('data-template-id', template.id); // Make the entire card clickable templateOption.addEventListener('click', function() { // For debugging console.log('Template option clicked for:', template.id); // Find and apply the template const selectedTemplate = templates.find(t => t.id === template.id); if (selectedTemplate) { applyTemplate(selectedTemplate); // Close the template section after selection const templateSection = document.querySelector('.protocol-template-selector'); if (templateSection) { const templateBody = templateSection.querySelector('.template-body'); const toggleBtn = templateSection.querySelector('.toggle-btn'); if (templateBody && toggleBtn) { templateBody.style.display = 'none'; toggleBtn.textContent = '+'; } } } else { console.error('Template not found:', template.id); } }); const selectButton = document.createElement('button'); selectButton.className = 'template-select-btn'; selectButton.textContent = template.title; selectButton.setAttribute('type', 'button'); const description = document.createElement('p'); description.className = 'template-description'; description.textContent = template.description; templateOption.appendChild(selectButton); templateOption.appendChild(description); templateOptionsContainer.appendChild(templateOption); console.log('Added template option:', template.title, 'with ID:', template.id); console.log(' > Description:', template.description ? template.description.substring(0, 50) + '...' : 'MISSING'); }); // We've already set up the click handler for "Create Your Own" when creating it } // Function to apply a template by ID function applyTemplateById(templateId) { console.log('Applying template by ID:', templateId); if (templateId) { // Find the selected template from our loaded templates const selectedTemplate = templates.find(t => t.id === templateId); if (selectedTemplate) { console.log('Found template:', selectedTemplate.title); applyTemplate(selectedTemplate); // Close the template section after selection const templateSection = document.querySelector('.protocol-template-selector'); if (templateSection) { const templateBody = templateSection.querySelector('.template-body'); const toggleBtn = templateSection.querySelector('.toggle-btn'); if (templateBody && toggleBtn) { templateBody.style.display = 'none'; toggleBtn.textContent = '+'; } } } else { console.error('Template not found:', templateId); } } } // Function to clear all fields function clearAllFields() { // Clear all textareas document.querySelectorAll('textarea').forEach(textarea => { textarea.value = ''; }); // Clear community name input if (communityNameInput) { communityNameInput.value = ''; } // Clear protocol summary if (protocolSummaryTextarea) { protocolSummaryTextarea.value = ''; } // Reset protocol data protocol = { metadata: { communityName: "", summary: "" }, stages: {} }; // Collapse all sections stageContents.forEach(content => { content.style.display = 'none'; const toggleBtn = content.parentElement.querySelector('.toggle-btn'); if (toggleBtn) { toggleBtn.textContent = '+'; } }); // Close the template section after clearing const templateSection = document.querySelector('.protocol-template-selector'); if (templateSection) { const templateBody = templateSection.querySelector('.template-body'); const toggleBtn = templateSection.querySelector('.toggle-btn'); if (templateBody && toggleBtn) { templateBody.style.display = 'none'; toggleBtn.textContent = '+'; } } // Update preview mode if active if (previewModeActive) { markComponentsWithContent(); } console.log('All fields cleared'); } // Function to apply a template to the form function applyTemplate(selectedTemplate) { if (!selectedTemplate) { console.error('Cannot apply template: template is null or undefined'); return; } console.log('Applying template:', selectedTemplate.title); console.log('Template data structure:', selectedTemplate); // Reset protocol data while preserving metadata protocol = { metadata: { communityName: communityNameInput.value || "", summary: protocolSummaryTextarea.value || "" }, stages: {}, templateId: selectedTemplate.id, templateTitle: selectedTemplate.title, templateDescription: selectedTemplate.description }; // Always use the template description for summary when applying a template protocolSummaryTextarea.value = selectedTemplate.description; protocol.metadata.summary = selectedTemplate.description; console.log('Set summary to template description:', selectedTemplate.description); // Check if template has data.stages if (!selectedTemplate.data || !selectedTemplate.data.stages) { console.error('Template has invalid structure - missing data.stages:', selectedTemplate); return; } console.log('Template has these stages:', Object.keys(selectedTemplate.data.stages)); // Count fields that will be populated let fieldsPopulated = 0; // Apply the template content directly to the form for (const stageId in selectedTemplate.data.stages) { console.log(`Processing stage ${stageId}...`); if (!protocol.stages[stageId]) { protocol.stages[stageId] = {}; } for (const componentId in selectedTemplate.data.stages[stageId]) { console.log(` Processing component ${componentId}...`); if (!protocol.stages[stageId][componentId]) { protocol.stages[stageId][componentId] = {}; } for (const fieldId in selectedTemplate.data.stages[stageId][componentId]) { const content = selectedTemplate.data.stages[stageId][componentId][fieldId]; const textarea = document.getElementById(fieldId); console.log(` Processing field ${fieldId}:`); console.log(` Content exists: ${Boolean(content)}`); console.log(` Field element exists: ${Boolean(textarea)}`); if (textarea && content) { // Apply the content to the textarea textarea.value = content; // Height adjustment is now handled by the global script // Store in protocol data protocol.stages[stageId][componentId][fieldId] = content; fieldsPopulated++; console.log(` ✓ Populated field ${fieldId} with content (${content.length} chars)`); } else { if (!textarea) { console.warn(` ✗ Textarea element not found for field ID: ${fieldId}`); } if (!content) { console.warn(` ✗ No content for field ID: ${fieldId}`); } } } } } console.log(`Template application complete: ${fieldsPopulated} fields populated`); // Expand all sections to show the populated content stageContents.forEach(content => { content.style.display = 'block'; const toggleBtn = content.parentElement.querySelector('.toggle-btn'); if (toggleBtn) { toggleBtn.textContent = '-'; } }); // Update preview mode if active if (previewModeActive) { markComponentsWithContent(); } // Dispatch custom event for textarea auto-resize document.dispatchEvent(new CustomEvent('templateApplied')); } // No helper functions needed anymore as we've simplified the approach // Start loading templates fetchTemplates(); // Protocol data structure let protocol = { metadata: { communityName: "", summary: "" }, stages: {} }; // DOM elements const stageHeaders = document.querySelectorAll('.stage-header'); console.log('Found stage headers:', stageHeaders.length); const stageContents = document.querySelectorAll('.stage-body'); console.log('Found stage bodies:', stageContents.length); const moduleSelects = document.querySelectorAll('.module-select'); console.log('Found module selects:', moduleSelects.length); const communityNameInput = document.getElementById('community-name'); const protocolSummaryTextarea = document.getElementById('protocol-summary'); const exportBtn = document.getElementById('export-btn'); const exportMdBtn = document.getElementById('export-md'); const exportPdfBtn = document.getElementById('export-pdf'); const exportJsonBtn = document.getElementById('export-json'); const importJsonInput = document.getElementById('import-json'); const importBtn = document.getElementById('import-btn'); // This function is no longer needed with the new template UI // Keeping an empty function to avoid errors if it's called elsewhere function initializeTemplateSelector(templatesList) { console.log('initializeTemplateSelector is deprecated, using new UI instead'); } // Hide module selectors since we're using templates directly document.querySelectorAll('.module-selector').forEach(selector => { selector.style.display = 'none'; }); // Update protocol data from form inputs function updateProtocolData() { // Update metadata protocol.metadata = { communityName: communityNameInput.value || "", summary: protocolSummaryTextarea.value || "" }; // Reset the stages data protocol.stages = {}; // Get all textareas and their values const textareas = document.querySelectorAll('textarea'); textareas.forEach(textarea => { const fieldId = textarea.id; const fieldValue = textarea.value; // Skip empty fields if (!fieldValue || !fieldValue.trim()) return; // Find the component and stage for this field const componentCard = textarea.closest('.component-card'); if (!componentCard) return; const componentId = componentCard.id.replace('component-', ''); const stageSection = textarea.closest('.stage-section'); if (!stageSection) return; const stageId = stageSection.id.replace('stage-', ''); // Initialize stage and component if they don't exist if (!protocol.stages[stageId]) { protocol.stages[stageId] = {}; } if (!protocol.stages[stageId][componentId]) { protocol.stages[stageId][componentId] = {}; } // Set the field value protocol.stages[stageId][componentId][fieldId] = fieldValue; }); // If a template is selected, preserve the template information const selectedTemplateId = protocolTemplateSelect ? protocolTemplateSelect.value : ''; if (selectedTemplateId) { const selectedTemplate = templates.find(t => t.id === selectedTemplateId); if (selectedTemplate) { protocol.templateId = selectedTemplateId; protocol.templateTitle = selectedTemplate.title; protocol.templateDescription = selectedTemplate.description; } } // Remove empty components and stages for (const stageId in protocol.stages) { // Check if stage has any non-empty components const stageComponents = protocol.stages[stageId]; let stageHasContent = false; for (const componentId in stageComponents) { // Check if component has any fields const component = stageComponents[componentId]; const fieldCount = Object.keys(component).length; if (fieldCount > 0) { stageHasContent = true; } else { // Remove empty component delete stageComponents[componentId]; } } // If stage has no content, remove it if (!stageHasContent) { delete protocol.stages[stageId]; } } } // Export to Markdown exportMdBtn.addEventListener('click', function(e) { e.preventDefault(); updateProtocolData(); try { // Use community name if available, otherwise default const communityName = protocol.metadata.communityName || "Community"; let markdown = `# ${communityName}\n# CommunityDispute\n\n`; // Include protocol summary if available if (protocol.metadata.summary) { markdown += `${protocol.metadata.summary}\n\n`; markdown += '---\n\n'; } // Template information removed as requested // Loop through the stages in order stageHeaders.forEach(header => { const stageId = header.getAttribute('data-stage'); const stageName = header.querySelector('h2').textContent; let stageContent = ''; let hasContent = false; // Get components for this stage const stageComponents = protocol.stages[stageId]; if (stageComponents) { // Loop through components for (const componentId in stageComponents) { const componentCard = document.getElementById(`component-${componentId}`); if (componentCard) { const componentName = componentCard.querySelector('h3').textContent; let componentContent = ''; let componentHasContent = false; // Loop through fields for (const fieldId in stageComponents[componentId]) { const fieldValue = stageComponents[componentId][fieldId]; // Skip empty fields if (fieldValue && fieldValue.trim()) { // Skip the field label, just add the content componentContent += `${fieldValue}\n\n`; componentHasContent = true; hasContent = true; } } // Only add component if it has content if (componentHasContent) { stageContent += `### ${componentName}\n\n${componentContent}`; } } } } // Only add stage if it has content if (hasContent) { // Get the stage section ID (e.g., "Intake") const stageSection = header.closest('.stage-section'); const stageSectionId = stageSection ? stageSection.id.replace('stage-', '') : ''; const stageSectionTitle = stageSectionId ? stageSectionId.charAt(0).toUpperCase() + stageSectionId.slice(1) : ''; // Use just the section title to avoid duplication if (stageSectionTitle) { markdown += `## ${stageSectionTitle}\n\n${stageContent}`; } else { markdown += `## ${stageName}\n\n${stageContent}`; } } }); // Create and download the file downloadFile('communityDispute.md', markdown); } catch (error) { console.error('Error generating Markdown:', error); alert('Failed to generate Markdown. Please try again.'); } }); // Export to PDF exportPdfBtn.addEventListener('click', function(e) { e.preventDefault(); updateProtocolData(); try { // Check if jspdf is properly loaded if (typeof window.jspdf === 'undefined') { console.error('jsPDF library not loaded properly'); alert('PDF export is currently unavailable. The required library failed to load.'); return; } // Create a styled HTML version for PDF export const { jsPDF } = window.jspdf; const doc = new jsPDF(); // Set consistent text width parameters const margins = { left: 14, right: 14, pageWidth: 210, // A4 width in mm (portrait) width: 182 // We'll use this as the consistent text width - should be pageWidth - left - right }; let yPos = 20; // Use community name if available, otherwise default const communityName = protocol.metadata.communityName || "Community"; // Add title with line break doc.setFontSize(18); doc.text(`${communityName}\nCommunityDispute`, 105, yPos, { align: 'center' }); yPos += 25; // Page width indicator removed // Add protocol summary if available if (protocol.metadata.summary) { doc.setFontSize(12); const summaryLines = doc.splitTextToSize(protocol.metadata.summary, margins.width); doc.text(summaryLines, margins.left, yPos); yPos += summaryLines.length * 7 + 8; } // Template information removed as requested // Set up direct rendering approach to avoid duplication const sectionsToProcess = []; // Find all unique stage sections with content document.querySelectorAll('.stage-section').forEach(section => { const sectionId = section.id.replace('stage-', ''); const sectionTitle = sectionId.charAt(0).toUpperCase() + sectionId.slice(1); const componentsWithContent = []; // Look for components with content in this section section.querySelectorAll('.component-card').forEach(componentCard => { const componentId = componentCard.id.replace('component-', ''); // Check if this component has content in the protocol data const stageId = section.id.replace('stage-', ''); if (protocol.stages[stageId] && protocol.stages[stageId][componentId] && Object.keys(protocol.stages[stageId][componentId]).length > 0) { componentsWithContent.push({ id: componentId, element: componentCard, name: componentCard.querySelector('h3').textContent }); } }); // Only include sections that have components with content if (componentsWithContent.length > 0) { sectionsToProcess.push({ id: sectionId, title: sectionTitle, components: componentsWithContent }); } }); // Process each section directly to avoid duplication sectionsToProcess.forEach(section => { // Add page break if needed if (yPos > 250) { doc.addPage(); yPos = 20; } // Add section header only once (e.g., "Intake") doc.setFontSize(18); doc.text(section.title, margins.left, yPos); yPos += 15; // Process components in this section section.components.forEach(component => { // Add page break if needed if (yPos > 250) { doc.addPage(); yPos = 20; } // Add component heading doc.setFontSize(14); doc.text(component.name, margins.left, yPos); yPos += 8; // Get fields for this component const stageId = section.id; const componentId = component.id; if (protocol.stages[stageId] && protocol.stages[stageId][componentId]) { // Loop through fields for (const fieldId in protocol.stages[stageId][componentId]) { const fieldValue = protocol.stages[stageId][componentId][fieldId]; // Skip empty fields if (fieldValue && fieldValue.trim()) { // Add page break if needed if (yPos > 250) { doc.addPage(); yPos = 20; } // Format field content with larger font size (12) doc.setFontSize(12); // Process markdown to plaintext for PDF let processedContent = fieldValue; try { // Strip HTML tags from rendered markdown to get clean text const temp = document.createElement('div'); temp.innerHTML = marked.parse(fieldValue); // Convert links to format: text (url) const links = temp.querySelectorAll('a'); links.forEach(link => { const linkText = link.textContent; const href = link.getAttribute('href'); // Only modify if href is different from text if (href && href !== linkText) { const replacement = `${linkText} (${href})`; link.textContent = replacement; } }); // Bold text handling const boldElements = temp.querySelectorAll('strong, b'); boldElements.forEach(el => { el.textContent = `*${el.textContent}*`; // Surround with asterisks }); // Get plain text content processedContent = temp.textContent; } catch (error) { console.error('Error processing Markdown for PDF:', error); } // Use consistent width for all text const textLines = doc.splitTextToSize(processedContent, margins.width); doc.text(textLines, margins.left, yPos); yPos += textLines.length * 6 + 8; // Slightly increase spacing for larger font } } } }); }); // Save the PDF doc.save('communityDispute.pdf'); } catch (error) { console.error('Error generating PDF:', error); alert('Failed to generate PDF. Please try again or use another export format.'); } }); // Export to JSON exportJsonBtn.addEventListener('click', function(e) { e.preventDefault(); updateProtocolData(); const jsonData = JSON.stringify(protocol, null, 2); downloadFile('communityDispute.json', jsonData); }); // Import from JSON importBtn.addEventListener('click', function(e) { e.preventDefault(); // Prevent the default anchor behavior importJsonInput.click(); }); importJsonInput.addEventListener('change', function(event) { const file = event.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = function(e) { try { const importedProtocol = JSON.parse(e.target.result); protocol = importedProtocol; // Populate metadata fields if present if (protocol.metadata) { if (protocol.metadata.communityName) { communityNameInput.value = protocol.metadata.communityName; } if (protocol.metadata.summary) { protocolSummaryTextarea.value = protocol.metadata.summary; } } // Populate the component fields with the imported data for (const stageId in protocol.stages) { for (const componentId in protocol.stages[stageId]) { for (const fieldId in protocol.stages[stageId][componentId]) { const textarea = document.getElementById(fieldId); if (textarea) { textarea.value = protocol.stages[stageId][componentId][fieldId]; } } } } // If the imported protocol has template information, highlight the template if (protocol.templateId) { // Highlight the selected template option const templateOptions = document.querySelectorAll('.template-option'); templateOptions.forEach(option => { const templateId = option.getAttribute('data-template-id'); if (templateId === protocol.templateId) { option.classList.add('selected'); } else { option.classList.remove('selected'); } }); // Expand all sections stageContents.forEach(content => { content.style.display = 'block'; const toggleBtn = content.parentElement.querySelector('.toggle-btn'); if (toggleBtn) { toggleBtn.textContent = '-'; } }); } else { // If no template, remove any highlights document.querySelectorAll('.template-option').forEach(option => { option.classList.remove('selected'); }); } // Update preview mode if active if (previewModeActive) { markComponentsWithContent(); } alert('Protocol imported successfully!'); } catch (error) { alert('Failed to import protocol. Invalid JSON format.'); console.error(error); } }; reader.readAsText(file); } }); // Helper function to download a file function downloadFile(filename, content) { const element = document.createElement('a'); element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(content)); element.setAttribute('download', filename); element.style.display = 'none'; document.body.appendChild(element); element.click(); document.body.removeChild(element); } // Save data when inputs change document.querySelectorAll('textarea').forEach(textarea => { textarea.addEventListener('input', function() { updateProtocolData(); // Update preview mode if active if (previewModeActive) { markComponentsWithContent(); } }); }); // Function to update preview content with Markdown support function updatePreviewContent() { // First, clear any existing preview divs document.querySelectorAll('.preview-content').forEach(div => { div.parentNode.removeChild(div); }); // Show all textareas again document.querySelectorAll('textarea').forEach(textarea => { textarea.style.display = ''; }); // Then create new preview divs for all textareas with content document.querySelectorAll('textarea').forEach(textarea => { if (textarea.value && textarea.value.trim()) { // Create a div to replace the textarea for preview const previewDiv = document.createElement('div'); previewDiv.className = 'preview-content'; // Parse Markdown and set as HTML content try { previewDiv.innerHTML = marked.parse(textarea.value); } catch (error) { console.error('Error parsing Markdown:', error); previewDiv.textContent = textarea.value; } previewDiv.dataset.forTextarea = textarea.id; // Hide the textarea and insert the div textarea.style.display = 'none'; textarea.parentNode.insertBefore(previewDiv, textarea.nextSibling); } }); } // Update preview content when preview mode is toggled document.getElementById('preview-toggle').addEventListener('change', function() { if (this.checked) { // Wait for the preview mode styles to apply, then update content setTimeout(updatePreviewContent, 100); } }); // Update preview content when module content changes document.querySelectorAll('.module-select').forEach(select => { select.addEventListener('change', function() { setTimeout(() => { if (document.getElementById('preview-toggle').checked) { updatePreviewContent(); } }, 100); }); }); // Update preview content on window resize window.addEventListener('resize', function() { if (document.getElementById('preview-toggle').checked) { updatePreviewContent(); } }); console.log('Builder initialization complete'); });