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; } /* 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: none !important; } /* Improved typography for preview mode */ .component-header h3 { font-size: 1.4rem !important; margin-bottom: 1rem !important; color: #000 !important; border-bottom: 1px solid #eee !important; padding-bottom: 0.5rem !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 module data let allModules = {}; try { // Check if moduleData is defined (from modules.js) if (typeof moduleData !== 'undefined') { allModules = moduleData; console.log('Module data loaded from modules.js'); } } catch (e) { console.error('Error loading module data:', e); allModules = {}; } // Load and process template data let templates = []; try { // Check if raw templates are defined (from templates.js) if (typeof rawProtocolTemplates !== 'undefined' && typeof templateMapper !== 'undefined') { console.log('Raw protocol templates loaded from templates.js'); // Process each template to use module references rawProtocolTemplates.forEach(rawTemplate => { const processedTemplate = templateMapper.convertTemplateToModules(rawTemplate, allModules); templates.push(processedTemplate); }); console.log('Processed templates to use module references:', templates); } } catch (e) { console.error('Error processing protocol templates:', e); templates = []; } // 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 protocolTemplateSelect = document.getElementById('protocol-template'); 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'); // Populate protocol template select if (protocolTemplateSelect && templates.length > 0) { templates.forEach(template => { const option = document.createElement('option'); option.value = template.id; option.textContent = template.title; protocolTemplateSelect.appendChild(option); }); // Add template selection event handler protocolTemplateSelect.addEventListener('change', function() { const selectedTemplateId = this.value; if (selectedTemplateId) { // Find the selected template const selectedTemplate = templates.find(t => t.id === selectedTemplateId); if (selectedTemplate) { console.log('Applying template:', selectedTemplate); console.log('Selected template:', selectedTemplate.title); // Reset protocol data while preserving metadata protocol = { metadata: { communityName: communityNameInput.value || "", summary: protocolSummaryTextarea.value || "" }, stages: {}, templateId: selectedTemplate.id, templateTitle: selectedTemplate.title, templateDescription: selectedTemplate.description }; // If summary is empty, use template description if (!protocol.metadata.summary) { protocolSummaryTextarea.value = selectedTemplate.description; protocol.metadata.summary = selectedTemplate.description; } // Apply the template module references to the form for (const stageId in selectedTemplate.moduleRefs) { if (!protocol.stages[stageId]) { protocol.stages[stageId] = {}; } for (const componentId in selectedTemplate.moduleRefs[stageId]) { if (!protocol.stages[stageId][componentId]) { protocol.stages[stageId][componentId] = {}; } for (const fieldId in selectedTemplate.moduleRefs[stageId][componentId]) { const moduleId = selectedTemplate.moduleRefs[stageId][componentId][fieldId]; const textarea = document.getElementById(fieldId); if (textarea) { // Find the module with this ID let moduleContent = ''; for (const category in allModules) { const foundModule = allModules[category].find(m => m.id === moduleId); if (foundModule) { moduleContent = foundModule.content; break; } } // Apply the module content to the textarea textarea.value = moduleContent; // Store in protocol data protocol.stages[stageId][componentId][fieldId] = moduleContent; // If there's a template selector for this field, update it const moduleSelector = document.querySelector(`select.module-select[data-field-id="${fieldId}"][data-component-id="${componentId}"]`); if (moduleSelector) { moduleSelector.value = moduleId; } } } } } // 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(); } } } else { // Clear all fields if "Create Your Own" is selected document.querySelectorAll('textarea').forEach(textarea => { textarea.value = ''; }); // Reset protocol data protocol = { stages: {} }; // Collapse all sections stageContents.forEach(content => { content.style.display = 'none'; const toggleBtn = content.parentElement.querySelector('.toggle-btn'); if (toggleBtn) { toggleBtn.textContent = '+'; } }); // Update preview mode if active if (previewModeActive) { markComponentsWithContent(); } } }); } // Initialize all module selects populateModuleSelects(); // Module selection handlers moduleSelects.forEach(select => { select.addEventListener('change', function() { const fieldId = this.getAttribute('data-field-id'); const componentId = this.getAttribute('data-component-id'); const targetTextarea = document.getElementById(fieldId); if (targetTextarea && this.value) { // Find the selected module for (const category in allModules) { const selectedModule = allModules[category].find(m => m.id === this.value); if (selectedModule) { targetTextarea.value = selectedModule.content; // Update protocol data updateProtocolData(); // Update preview mode if active if (previewModeActive) { markComponentsWithContent(); } break; } } } }); }); // Function to populate module select dropdowns function populateModuleSelects() { console.log('Populating module selects...'); console.log('Available module categories:', Object.keys(allModules)); // Debugging: Log all modules to check componentId and fieldId console.log('All module mapping:'); for (const category in allModules) { console.log(`Category: ${category}`); allModules[category].forEach(module => { console.log(` Module: ${module.id}, Component: ${module.componentId}, Field: ${module.fieldId}`); }); } moduleSelects.forEach(select => { const fieldId = select.getAttribute('data-field-id'); const componentId = select.getAttribute('data-component-id'); console.log(`Processing module select for fieldId: ${fieldId}, componentId: ${componentId}`); // Clear existing options except the first one while (select.options.length > 1) { select.remove(1); } // Find modules that match this field and component let hasOptions = false; // Always show select first - we'll hide it later if no options found select.closest('.module-selector').style.display = 'flex'; // Check for case matching issues and missing references for (const category in allModules) { let exactMatches = allModules[category].filter(m => m.fieldId === fieldId && m.componentId === componentId ); let caseInsensitiveMatches = allModules[category].filter(m => m.fieldId.toLowerCase() === fieldId.toLowerCase() && m.componentId.toLowerCase() === componentId.toLowerCase() && !exactMatches.includes(m) ); if (caseInsensitiveMatches.length > 0) { console.warn(`Found ${caseInsensitiveMatches.length} case-insensitive matches for ${componentId}/${fieldId}. Consider fixing these module references.`); // Add case-insensitive matches to the collection caseInsensitiveMatches.forEach(module => { // Create a copy with corrected references const correctedModule = { ...module, componentId: componentId, fieldId: fieldId }; // Add to the exact matches exactMatches.push(correctedModule); }); } if (exactMatches.length > 0) { console.log(`Found ${exactMatches.length} modules in category ${category} for ${componentId}/${fieldId}`); hasOptions = true; // Don't use option groups - add options directly to the select // This avoids showing category labels which can be confusing exactMatches.forEach(module => { const option = document.createElement('option'); option.value = module.id; // Use the module title directly from the definition // This relies on proper module titles being defined in modules.js option.textContent = module.title; // Add directly to select instead of to a group select.appendChild(option); }); } } // If no modules found, hide the selector if (!hasOptions) { console.log(`No modules found for ${componentId}/${fieldId}, hiding selector`); select.closest('.module-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(); // Use community name if available, otherwise default const communityName = protocol.metadata.communityName || "Community"; let markdown = `# ${communityName} Dispute Protocol\n\n`; // Include protocol summary if available if (protocol.metadata.summary) { markdown += `${protocol.metadata.summary}\n\n`; markdown += '---\n\n'; } // Include template information if a template was used if (protocol.templateTitle) { markdown += `**Template Used:** ${protocol.templateTitle}\n\n`; markdown += '---\n\n'; } // 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()) { const fieldLabel = document.querySelector(`label[for="${fieldId}"]`).textContent; componentContent += `#### ${fieldLabel}\n\n${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) { markdown += `## ${stageName}\n\n${stageContent}`; } }); // Create and download the file downloadFile('community_dispute_protocol.md', markdown); }); // Export to PDF exportPdfBtn.addEventListener('click', function(e) { e.preventDefault(); updateProtocolData(); // Create a styled HTML version for PDF export const { jsPDF } = window.jspdf; const doc = new jsPDF(); let yPos = 20; // Use community name if available, otherwise default const communityName = protocol.metadata.communityName || "Community"; // Add title doc.setFontSize(18); doc.text(`${communityName} Dispute Protocol`, 105, yPos, { align: 'center' }); yPos += 15; // Add protocol summary if available if (protocol.metadata.summary) { doc.setFontSize(12); const summaryLines = doc.splitTextToSize(protocol.metadata.summary, 180); doc.text(summaryLines, 14, yPos); yPos += summaryLines.length * 7 + 8; } // Add template info if available if (protocol.templateTitle) { doc.setFontSize(10); doc.text(`Template Used: ${protocol.templateTitle}`, 14, yPos); yPos += 10; } // Loop through the stages stageHeaders.forEach(header => { const stageId = header.getAttribute('data-stage'); const stageName = header.querySelector('h2').textContent; let stageHasContent = false; let stageStartYPos = yPos; // Save current position to add stage heading later if content is found if (yPos > 250) { doc.addPage(); stageStartYPos = 20; yPos = 20; } // Skip stage heading for now - we'll add it if the stage has content let currentYPos = stageStartYPos + 10; // Space for the heading // 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) { let componentHasContent = false; let componentStartYPos = currentYPos; // Check for page break if (currentYPos > 250) { doc.addPage(); componentStartYPos = 20; currentYPos = 20; } // Skip component heading for now - add if it has content const componentFieldStartYPos = componentStartYPos + 8; let fieldYPos = componentFieldStartYPos; const componentName = componentCard.querySelector('h3').textContent; // Loop through fields for (const fieldId in stageComponents[componentId]) { const fieldValue = stageComponents[componentId][fieldId]; // Skip empty fields if (fieldValue && fieldValue.trim()) { const fieldLabel = document.querySelector(`label[for="${fieldId}"]`).textContent; // Add page break if needed if (fieldYPos > 250) { doc.addPage(); fieldYPos = 20; } // We have content - if this is the first content in the component, // add the component heading if (!componentHasContent) { componentHasContent = true; stageHasContent = true; // If this is the first content in the stage, add the stage heading if (!stageHasContent) { doc.setFontSize(16); doc.text(stageName, 14, stageStartYPos); } // Add component heading doc.setFontSize(14); doc.text(componentName, 14, componentStartYPos); } // Add field heading doc.setFontSize(12); doc.text(fieldLabel, 14, fieldYPos); fieldYPos += 6; // Split the text into lines to handle wrapping const textLines = doc.splitTextToSize(fieldValue, 180); // Add field content doc.setFontSize(10); doc.text(textLines, 14, fieldYPos); fieldYPos += textLines.length * 5 + 8; } } // Update current Y position if component had content if (componentHasContent) { currentYPos = fieldYPos; } } } } // Update the overall Y position if stage had content if (stageHasContent) { yPos = currentYPos; } }); // Save the PDF doc.save('community_dispute_protocol.pdf'); }); // Export to JSON exportJsonBtn.addEventListener('click', function(e) { e.preventDefault(); updateProtocolData(); const jsonData = JSON.stringify(protocol, null, 2); downloadFile('community_dispute_protocol.json', jsonData); }); // Import from JSON importBtn.addEventListener('click', function() { 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, select that template if (protocol.templateId && protocolTemplateSelect) { protocolTemplateSelect.value = protocol.templateId; // Update template description if (protocol.templateDescription && templateDescription) { templateDescription.textContent = protocol.templateDescription; templateDescription.style.display = 'block'; } // 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, reset the template selector if (protocolTemplateSelect) { protocolTemplateSelect.value = ''; } if (templateDescription) { templateDescription.textContent = ''; templateDescription.style.display = 'none'; } } // 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 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'; 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'); });