bulk-tag.js (6144B)
1 document.addEventListener('DOMContentLoaded', function () { 2 const fileRangeInput = document.getElementById('file_range'); 3 if (!fileRangeInput) return; 4 const fileForm = fileRangeInput.closest('form'); 5 if (!fileForm) return; 6 7 function updateValueField() { 8 const checkedOp = fileForm.querySelector('input[name="operation"]:checked'); 9 const valueField = fileForm.querySelector('#value'); 10 const valueLabel = fileForm.querySelector('label[for="value"]'); 11 if (!checkedOp || !valueField || !valueLabel) return; 12 if (checkedOp.value === 'add') { 13 valueField.required = true; 14 valueLabel.innerHTML = 'Value <span class="required">(required)</span>:'; 15 } else { 16 valueField.required = false; 17 valueLabel.innerHTML = 'Value:'; 18 } 19 } 20 21 function toggleSelectionMode() { 22 const rangeMode = fileForm.querySelector('input[name="selection_mode"][value="range"]'); 23 if (!rangeMode) return; 24 25 const isRangeMode = rangeMode.checked; 26 const rangeSelection = document.getElementById('range-selection'); 27 const tagSelection = document.getElementById('tag-selection'); 28 const fileRangeField = document.getElementById('file_range'); 29 const tagQueryField = document.getElementById('tag_query'); 30 31 if (isRangeMode && tagQueryField && fileRangeField && !fileRangeField.value && tagQueryField.value) { 32 fileRangeField.value = tagQueryField.value; 33 } else if (!isRangeMode && fileRangeField && tagQueryField && !tagQueryField.value && fileRangeField.value) { 34 tagQueryField.value = fileRangeField.value; 35 } 36 37 if (rangeSelection) rangeSelection.style.display = isRangeMode ? 'block' : 'none'; 38 if (tagSelection) tagSelection.style.display = isRangeMode ? 'none' : 'block'; 39 40 // Update required attributes 41 if (fileRangeField) fileRangeField.required = isRangeMode; 42 if (tagQueryField) tagQueryField.required = !isRangeMode; 43 44 if (isRangeMode && fileRangeField) { 45 fileRangeField.focus(); 46 } else if (!isRangeMode && tagQueryField) { 47 tagQueryField.focus(); 48 } 49 } 50 51 // Set up event listeners for operation radio buttons 52 fileForm.querySelectorAll('input[name="operation"]').forEach(function (radio) { 53 radio.addEventListener('change', updateValueField); 54 }); 55 56 // Set up event listeners for selection mode radio buttons 57 fileForm.querySelectorAll('input[name="selection_mode"]').forEach(function (radio) { 58 radio.addEventListener('change', toggleSelectionMode); 59 }); 60 61 // Initialize on page load 62 updateValueField(); 63 toggleSelectionMode(); 64 65 // Add form validation with selection mode awareness 66 fileForm.addEventListener('submit', function (e) { 67 const selectionModeRadio = fileForm.querySelector('input[name="selection_mode"]:checked'); 68 const selectionMode = selectionModeRadio ? selectionModeRadio.value : 'range'; 69 70 const fileRange = (fileForm.querySelector('#file_range') || { value: '' }).value.trim(); 71 const tagQuery = (fileForm.querySelector('#tag_query') || { value: '' }).value.trim(); 72 const category = (fileForm.querySelector('#category') || { value: '' }).value.trim(); 73 const value = (fileForm.querySelector('#value') || { value: '' }).value.trim(); 74 const checkedOp = fileForm.querySelector('input[name="operation"]:checked'); 75 const operation = checkedOp ? checkedOp.value : ''; 76 77 // Validate based on selection mode 78 if (selectionMode === 'range') { 79 if (!fileRange) { 80 alert('Please enter a file ID range'); 81 e.preventDefault(); 82 return; 83 } 84 const rangePattern = /^[\d\s,-]+$/; 85 if (!rangePattern.test(fileRange)) { 86 alert('File range should only contain numbers, commas, dashes, and spaces'); 87 e.preventDefault(); 88 return; 89 } 90 } else if (selectionMode === 'tags') { 91 if (!tagQuery) { 92 alert('Please enter a tag query'); 93 e.preventDefault(); 94 return; 95 } 96 // Basic validation for tag query format 97 const tagPattern = /^[^:]+:[^:]+(\s+OR\s+[^:]+:[^:]+|,[^:]+:[^:]+)*$/i; 98 if (!tagPattern.test(tagQuery)) { 99 alert('Tag query format should be "category:value" (e.g., "colour:blue" or "colour:blue,size:large")'); 100 e.preventDefault(); 101 return; 102 } 103 } 104 105 if (!category) { 106 alert('Please enter a category'); 107 e.preventDefault(); 108 return; 109 } 110 111 if (operation === 'add' && !value) { 112 alert('Please enter a tag value when adding tags'); 113 e.preventDefault(); 114 return; 115 } 116 }); 117 118 // Hover previews for recent images 119 const IMAGE_EXTENSIONS = /\.(jpe?g|png|gif|webp)$/i; 120 121 const tooltip = document.createElement('div'); 122 tooltip.id = 'thumb-tooltip'; 123 Object.assign(tooltip.style, { 124 position: 'fixed', 125 display: 'none', 126 pointerEvents: 'none', 127 zIndex: '9999', 128 border: '1px solid #888', 129 background: '#fff', 130 padding: '3px', 131 borderRadius: '4px', 132 boxShadow: '0 2px 8px rgba(0,0,0,0.3)', 133 }); 134 const tooltipImg = document.createElement('img'); 135 tooltipImg.style.maxWidth = '200px'; 136 tooltipImg.style.maxHeight = '200px'; 137 tooltipImg.style.display = 'block'; 138 tooltip.appendChild(tooltipImg); 139 document.body.appendChild(tooltip); 140 141 document.querySelectorAll('.file-item a').forEach(function (link) { 142 const filename = link.title || link.textContent.trim(); 143 if (!IMAGE_EXTENSIONS.test(filename)) return; 144 145 const thumbUrl = '/uploads/' + filename; 146 147 link.addEventListener('mouseenter', function () { 148 tooltipImg.src = thumbUrl; 149 tooltip.style.display = 'block'; 150 }); 151 152 link.addEventListener('mousemove', function (e) { 153 const pad = 16; 154 const tw = tooltip.offsetWidth; 155 const th = tooltip.offsetHeight; 156 let x = e.clientX + pad; 157 let y = e.clientY + pad; 158 if (x + tw > window.innerWidth) x = e.clientX - tw - pad; 159 if (y + th > window.innerHeight) y = e.clientY - th - pad; 160 tooltip.style.left = x + 'px'; 161 tooltip.style.top = y + 'px'; 162 }); 163 164 link.addEventListener('mouseleave', function () { 165 tooltip.style.display = 'none'; 166 tooltipImg.src = ''; 167 }); 168 }); 169 });