description.js (5650B)
1 function toggleDescriptionEdit() { 2 const displayDiv = document.getElementById('description-display'); 3 const editDiv = document.getElementById('description-edit'); 4 5 displayDiv.style.display = 'none'; 6 editDiv.style.display = 'block'; 7 8 // Focus the textarea and update character count 9 const textarea = document.getElementById('description-textarea'); 10 textarea.focus(); 11 12 // Move cursor to end of text if there's existing content 13 if (textarea.value) { 14 textarea.setSelectionRange(textarea.value.length, textarea.value.length); 15 } 16 } 17 18 function cancelDescriptionEdit() { 19 const displayDiv = document.getElementById('description-display'); 20 const editDiv = document.getElementById('description-edit'); 21 const textarea = document.getElementById('description-textarea'); 22 23 // Reset textarea to original value 24 const original = displayDiv.dataset.originalDescription || ''; 25 textarea.value = original; 26 27 displayDiv.style.display = 'block'; 28 editDiv.style.display = 'none'; 29 30 // Re-run conversion so any [file/123] becomes clickable again 31 convertFileRefs(); 32 } 33 34 // Auto-resize textarea as content changes 35 document.addEventListener('DOMContentLoaded', function() { 36 const textarea = document.getElementById('description-textarea'); 37 if (textarea) { 38 textarea.addEventListener('input', function() { 39 // Reset height to auto to get the correct scrollHeight 40 this.style.height = 'auto'; 41 // Set the height to match the content, with a minimum of 6 rows 42 const minHeight = parseInt(getComputedStyle(this).lineHeight) * 6; 43 this.style.height = Math.max(minHeight, this.scrollHeight) + 'px'; 44 }); 45 } 46 }); 47 48 // Allow [file/123] and [/file/123] links to become clickable 49 function convertFileRefs() { 50 const el = document.getElementById("current-description"); 51 if (!el) return; 52 53 const pattern = /\[\/?file\/(\d+)\]/g; 54 55 // Walk through text nodes only, preserving existing HTML elements 56 function processTextNodes(node) { 57 if (node.nodeType === Node.TEXT_NODE) { 58 let text = node.textContent; 59 60 // Check if this text node contains file references 61 if (pattern.test(text)) { 62 // Reset regex lastIndex 63 pattern.lastIndex = 0; 64 65 // Replace file references 66 text = text.replace(pattern, (_, id) => { 67 return `<a href="/file/${id}" class="file-link">file/${id}</a>`; 68 }); 69 70 // Create a temporary container and replace the text node 71 const temp = document.createElement('span'); 72 temp.innerHTML = text; 73 const parent = node.parentNode; 74 while (temp.firstChild) { 75 parent.insertBefore(temp.firstChild, node); 76 } 77 parent.removeChild(node); 78 } 79 } else if (node.nodeType === Node.ELEMENT_NODE && node.tagName !== 'A') { 80 // Don't process inside existing anchor tags 81 Array.from(node.childNodes).forEach(processTextNodes); 82 } 83 } 84 85 processTextNodes(el); 86 } 87 88 // Re-init clickable links after submit 89 function reinitDescriptionEnhancements() { 90 const hasCurrentDescription = !!document.getElementById('current-description'); 91 92 if (hasCurrentDescription && typeof makeTimestampsClickable === 'function') { 93 makeTimestampsClickable('current-description', 'videoPlayer', 'imageViewer'); 94 } 95 96 if (hasCurrentDescription && typeof makeLineNumbersClickable === 'function') { 97 makeLineNumbersClickable('current-description', 'text-viewer'); 98 } 99 100 convertFileRefs(); 101 } 102 103 // AJAX-submit instead of usual reload 104 function initDescriptionForm() { 105 const editDiv = document.getElementById('description-edit'); 106 if (!editDiv) return; 107 108 const form = editDiv.querySelector('form'); 109 if (!form || form.dataset.ajaxBound) return; // avoid double-binding 110 form.dataset.ajaxBound = 'true'; 111 form.addEventListener('submit', handleDescriptionSubmit); 112 } 113 114 async function handleDescriptionSubmit(e) { 115 e.preventDefault(); 116 const form = e.target; 117 const submitButton = form.querySelector('button[type="submit"]'); 118 if (submitButton) submitButton.disabled = true; 119 120 const action = form.getAttribute('action') || window.location.pathname; 121 122 try { 123 const response = await fetch(action, { 124 method: 'POST', 125 body: new FormData(form), 126 }); 127 128 if (!response.ok) { 129 throw new Error('Request failed with status ' + response.status); 130 } 131 132 const html = await response.text(); 133 const doc = new DOMParser().parseFromString(html, 'text/html'); 134 135 const newDisplay = doc.getElementById('description-display'); 136 const currentDisplay = document.getElementById('description-display'); 137 if (newDisplay && currentDisplay) { 138 currentDisplay.innerHTML = newDisplay.innerHTML; 139 currentDisplay.dataset.originalDescription = newDisplay.dataset.originalDescription || ''; 140 } 141 142 const newTextarea = doc.getElementById('description-textarea'); 143 const currentTextarea = document.getElementById('description-textarea'); 144 if (newTextarea && currentTextarea) { 145 currentTextarea.value = newTextarea.value; 146 } 147 148 document.getElementById('description-display').style.display = 'block'; 149 document.getElementById('description-edit').style.display = 'none'; 150 151 // Re-run scripts against new description 152 reinitDescriptionEnhancements(); 153 } catch (err) { 154 console.error('Description update failed:', err); 155 alert('Failed to save description. Please try again.'); 156 } finally { 157 if (submitButton) submitButton.disabled = false; 158 } 159 } 160 161 document.addEventListener("DOMContentLoaded", function() { 162 convertFileRefs(); 163 initDescriptionForm(); 164 });