1/** 2 * Opening Hours Module - Handles parsing, validation, formatting, and UI interactions for opening hours 3 */ 4const OUMOpeningHours = (function() { 5 // Private variables 6 let observer = null; 7 8 /** 9 * Converts 12-hour format time to 24-hour format 10 * @param {string} time12 - Time in 12-hour format (e.g., "9:00 AM" or "11:30 PM") 11 * @returns {string|null} - Time in 24-hour format (HH:MM) or null if invalid 12 */ 13 function convert12To24(time12) { 14 if (!time12 || typeof time12 !== 'string') { 15 return null; 16 } 17 18 // Pattern: H:MM or HH:MM followed by AM/PM (case insensitive) 19 const pattern = /^(\d{1,2}):(\d{2})\s*(AM|PM)$/i; 20 const match = time12.trim().match(pattern); 21 22 if (!match) { 23 return null; 24 } 25 26 let hour = parseInt(match[1], 10); 27 const minute = parseInt(match[2], 10); 28 const period = match[3].toUpperCase(); 29 30 // Validate minute 31 if (minute < 0 || minute > 59) { 32 return null; 33 } 34 35 // Convert to 24-hour format 36 if (period === 'AM') { 37 if (hour === 12) { 38 hour = 0; // 12:00 AM = 00:00 39 } 40 } else { // PM 41 if (hour !== 12) { 42 hour += 12; // 1:00 PM = 13:00, but 12:00 PM = 12:00 43 } 44 } 45 46 // Validate hour 47 if (hour < 0 || hour > 23) { 48 return null; 49 } 50 51 return String(hour).padStart(2, '0') + ':' + String(minute).padStart(2, '0'); 52 } 53 54 /** 55 * Validates the opening hours input format 56 * Format: Mo 09:00-18:00 | Tu 9:00 AM-5:00 PM | Tu 1:00 PM-6:00 PM 57 * Supports both 24-hour (HH:MM) and 12-hour (H:MM AM/PM) formats 58 * @param {string} input - The input string to validate 59 * @returns {boolean} - True if valid, false otherwise 60 */ 61 function validateFormat(input) { 62 if (!input || typeof input !== 'string') { 63 return false; 64 } 65 66 const trimmed = input.trim(); 67 if (trimmed === '') { 68 return false; 69 } 70 71 // Split by pipe and validate each block 72 const blocks = trimmed.split('|'); 73 // Pattern for 24-hour format 74 const pattern24 = /^(Mo|Tu|We|Th|Fr|Sa|Su)\s+(\d{1,2}):(\d{2})-(\d{1,2}):(\d{2})$/i; 75 // Pattern for 12-hour format 76 const pattern12 = /^(Mo|Tu|We|Th|Fr|Sa|Su)\s+(\d{1,2}):(\d{2})\s*(AM|PM)-(\d{1,2}):(\d{2})\s*(AM|PM)$/i; 77 78 for (let block of blocks) { 79 block = block.trim(); 80 if (block === '') { 81 continue; 82 } 83 84 const match12 = block.match(pattern12); 85 if (match12) { 86 // Validate 12-hour format times 87 const startTime12 = match12[2] + ':' + match12[3] + ' ' + match12[4]; 88 const endTime12 = match12[5] + ':' + match12[6] + ' ' + match12[7]; 89 const startTime24 = convert12To24(startTime12); 90 const endTime24 = convert12To24(endTime12); 91 92 if (!startTime24 || !endTime24) { 93 return false; 94 } 95 } else { 96 // Validate 24-hour format times 97 const match = block.match(pattern24); 98 if (!match) { 99 return false; 100 } 101 102 const startHour = parseInt(match[2], 10); 103 const startMin = parseInt(match[3], 10); 104 const endHour = parseInt(match[4], 10); 105 const endMin = parseInt(match[5], 10); 106 107 // Check valid time ranges 108 if (startHour < 0 || startHour > 23 || startMin < 0 || startMin > 59 || 109 endHour < 0 || endHour > 23 || endMin < 0 || endMin > 59) { 110 return false; 111 } 112 } 113 } 114 115 return true; 116 } 117 118 /** 119 * Converts 24-hour format time to 12-hour format 120 * @param {string} time24 - Time in 24-hour format (HH:MM) 121 * @returns {string} - Time in 12-hour format (H:MM AM/PM) 122 */ 123 function convert24To12(time24) { 124 if (!time24 || !time24.match(/^\d{2}:\d{2}$/)) { 125 return time24; // Return as-is if invalid format 126 } 127 128 const parts = time24.split(':'); 129 let hour = parseInt(parts[0], 10); 130 const minute = parts[1]; 131 let period = 'AM'; 132 133 if (hour === 0) { 134 hour = 12; // 00:00 = 12:00 AM 135 } else if (hour === 12) { 136 period = 'PM'; // 12:00 = 12:00 PM 137 } else if (hour > 12) { 138 hour -= 12; 139 period = 'PM'; 140 } 141 142 return hour + ':' + minute + ' ' + period; 143 } 144 145 /** 146 * Converts opening hours JSON back to input format 147 * @param {Object} data - The opening hours JSON object 148 * @param {boolean} use12hour - Whether to use 12-hour format 149 * @returns {string}
149 - Input format string 150 */ 151 function formatForInput(data, use12hour = false) { 152 if (!data || !data.week) { 153 return ''; 154 } 155 156 const dayAbbrs = { 157 mo: 'Mo', 158 tu: 'Tu', 159 we: 'We', 160 th: 'Th', 161 fr: 'Fr', 162 sa: 'Sa', 163 su: 'Su' 164 }; 165 166 const blocks = []; 167 const days = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']; 168 169 for (const day of days) { 170 const dayAbbr = dayAbbrs[day]; 171 const timeBlocks = data.week[day] || []; 172 173 for (const block of timeBlocks) { 174 if (use12hour) { 175 const startTime = convert24To12(block.start); 176 const endTime = convert24To12(block.end); 177 blocks.push(`${dayAbbr} ${startTime}-${endTime}`); 178 } else { 179 blocks.push(`${dayAbbr} ${block.start}-${block.end}`); 180 } 181 } 182 } 183 184 return blocks.join(' | '); 185 } 186 187 /** 188 * Setup toggle functionality for opening hours headers 189 * @param {HTMLElement} container - Container element to search for headers 190 */ 191 function setupToggle(container) { 192 if (!container) return; 193 194 const headers = container.querySelectorAll('.oum-opening-hours-header'); 195 headers.forEach(function(header) { 196 // Skip if already has event listener (check for data attribute) 197 if (header.dataset.oumToggleSetup === 'true') { 198 return; 199 } 200 201 // Mark as setup 202 header.dataset.oumToggleSetup = 'true'; 203 204 // Add click handler 205 header.addEventListener('click', function(e) { 206 e.preventDefault(); 207 toggleOpeningHours(header); 208 }); 209 210 // Add keyboard support 211 header.addEventListener('keydown', function(e) { 212 if (e.key === 'Enter' || e.key === ' ') { 213 e.preventDefault(); 214 toggleOpeningHours(header); 215 } 216 }); 217 }); 218 } 219 220 /** 221 * Toggle the visibility of opening hours wrapper 222 * @param {HTMLElement} header - The header element containing the toggle 223 */ 224 function toggleOpeningHours(header) { 225 const wrapper = header.nextElementSibling; 226 if (wrapper && wrapper.classList.contains('oum-opening-hours-wrapper')) { 227 const isExpanded = wrapper.classList.contains('oum-opening-hours-expanded'); 228 if (isExpanded) { 229 wrapper.classList.remove('oum-opening-hours-expanded'); 230 wrapper.style.display = 'none'; 231 header.classList.remove('oum-opening-hours-expanded'); 232 } else { 233 wrapper.classList.add('oum-opening-hours-expanded'); 234 wrapper.style.display = 'block'; 235 header.classList.add('oum-opening-hours-expanded'); 236 } 237 } 238 } 239 240 /** 241 * Initialize MutationObserver for dynamically added content 242 */ 243 function initializeObserver() { 244 if (observer) { 245 return; // Already initialized 246 } 247 248 observer = new MutationObserver(function(mutations) { 249 mutations.forEach(function(mutation) { 250 if (mutation.addedNodes.length) { 251 mutation.addedNodes.forEach(function(node) { 252 if (node.nodeType === 1) { // Element node 253 // Check if this node or its children contain opening hours header 254 if (node.querySelector && node.querySelector('.oum-opening-hours-header')) { 255 setupToggle(node); 256 } 257 // Also check if the node itself is an opening hours header 258 if (node.classList && node.classList.contains('oum-opening-hours-header')) { 259 setupToggle(node.parentElement); 260 } 261 } 262 }); 263 } 264 }); 265 }); 266 267 // Start observing 268 observer.observe(document.body, { 269 childList: true, 270 subtree: true 271 }); 272 } 273 274 /** 275 * Initialize opening hours UI for existing content 276 */ 277 function init() { 278 setupToggle(document.body); 279 initializeObserver(); 280 } 281 282 // Public interface 283 return { 284 validateFormat: validateFormat, 285 formatForInput: formatForInput, 286 // UI methods 287 init: init, 288 setupToggle: setupToggle 289 }; 290})(); 291
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.