tagliatelle

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

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 });