PageSourceSearch

https://milosrdnice-zagreb.hr/wp-content/plugins/open-user-map/src/js/frontend-opening-hours.js?ver=1.4.52

js milosrdnice-zagreb.hr collected 2026-10-02 14:12:29 UTC 10,187 bytes, 367 lines download raw bytes

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   * English fallbacks used when locale data has not been localized yet
10   */
11  const fallbackDayAbbrs = {
12    mo: 'Mo',
13    tu: 'Tu',
14    we: 'We',
15    th: 'Th',
16    fr: 'Fr',
17    sa: 'Sa',
18    su: 'Su'
19  };
20  const fallbackDayAliases = {
21    mo: 'mo',
22    tu: 'tu',
23    we: 'we',
24    th: 'th',
25    fr: 'fr',
26    sa: 'sa',
27    su: 'su'
28  };
29
30  /**
31   * Locale weekday aliases and abbreviations from wp_localize_script
32   */
33  function getDayConfig() {
34    if (typeof oum_opening_hours !== 'undefined' && oum_opening_hours) {
35      return {
36        aliases: oum_opening_hours.day_aliases || fallbackDayAliases,
37        abbrs: oum_opening_hours.day_abbrs || fallbackDayAbbrs
38      };
39    }
40
41    return {
42      aliases: fallbackDayAliases,
43      abbrs: fallbackDayAbbrs
44    };
45  }
46
47  /**
48   * Normalize a weekday token for alias lookup (trim, strip dots, lowercase, fold accents)
49   * @param {string} alias
50   * @returns {string}
51   */
52  function normalizeDayAlias(alias) {
53    if (!alias || typeof alias !== 'string') {
54      return '';
55    }
56
57    let value = alias.trim().replace(/\.+$/, '').toLowerCase();
58
59    try {
60      value = value.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
61    } catch (e) {
62      // ignore if normalize() is unavailable
63    }
64
65    return value;
66  }
67
68  /**
69   * Resolve a typed weekday token to an internal day key (mo, tu, ...)
70   * @param {string} token
71   * @returns {string|null}
72   */
73  function resolveDayKey(token) {
74    const normalized = normalizeDayAlias(token);
75    const aliases = getDayConfig().aliases;
76
77    if (normalized && aliases[normalized]) {
78      return aliases[normalized];
79    }
80
81    return null;
82  }
83
84  /**
85   * Converts 12-hour format time to 24-hour format
86   * @param {string} time12 - Time in 12-hour format (e.g., "9:00 AM" or "11:30 PM")
87   * @returns {string|null} - Time in 24-hour format (HH:MM) or null if invalid
88   */
89  function convert12To24(time12) {
90    if (!time12 || typeof time12 !== 'string') {
91      return null;
92    }
93    
94    // Pattern: H:MM or HH:MM followed by AM/PM (case insensitive)
95    const pattern = /^(\d{1,2}):(\d{2})\s*(AM|PM)$/i;
96    const match = time12.trim().match(pattern);
97    
98    if (!match) {
99      return null;
100    }
101    
102    let hour = parseInt(match[1], 10);
103    const minute = parseInt(match[2], 10);
104    const period = match[3].toUpperCase();
105    
106    // Validate minute
107    if (minute < 0 || minute > 59) {
108      return null;
109    }
110    
111    // Convert to 24-hour format
112    if (period === 'AM') {
113      if (hour === 12) {
114        hour = 0; // 12:00 AM = 00:00
115      }
116    } else { // PM
117      if (hour !== 12) {
118        hour += 12; // 1:00 PM = 13:00, but 12:00 PM = 12:00
119      }
120    }
121    
122    // Validate hour
123    if (hour < 0 || hour > 23) {
124      return null;
125    }
126    
127    return String(hour).padStart(2, '0') + ':' + String(minute).padStart(2, '0');
128  }
129
130  /**
131   * Validates the opening hours input format
132   * Format: Mo 09:00-18:00 | Di 9:00 AM-5:00 PM | Do 1:00 PM-6:00 PM
133   * Accepts English weekday abbreviations plus the current WordPress locale (e.g. Do for Thursday)
134   * Supports both 24-hour (HH:MM) and 12-hour (H:MM AM/PM) formats
135   * @param {string} input - The input string to validate
136   * @returns {boolean} - True if valid, false otherwise
137   */
138  function validateFormat(input) {
139    if (!input || typeof input !== 'string') {
140      return false;
141    }
142
143    const trimmed = input.trim();
144    if (trimmed === '') {
145      return false;
146    }
147
148    // Split by pipe and validate each block
149    const blocks = trimmed.split('|');
150    // Day token is language-agnostic; aliases are resolved via resolveDayKey()
151    const pattern24 = /^(\S+)\s+(\d{1,2}):(\d{2})-(\d{1,2}):(\d{2})$/;
152    const pattern12 = /^(\S+)\s+(\d{1,2}):(\d{2})\s*(AM|PM)-(\d{1,2}):(\d{2})\s*(AM|PM)$/i;
153
154    for (let block of blocks) {
155      block = block.trim();
156      if (block === '') {
157        continue;
158      }
159
160      const match12 = block.match(pattern12);
161      if (match12) {
162        if (!resolveDayKey(match12[1])) {
163          return false;
164        }
165
166        // Validate 12-hour format times
167        const startTime12 = match12[2] + ':' + match12[3] + ' ' + match12[4];
168        const endTime12 = match12[5] + ':' + match12[6] + ' ' + match12[7];
169        const startTime24 = convert12To24(startTime12);
170        const endTime24 = convert12To24(endTime12);
171        
172        if (!startTime24 || !endTime24) {
173          return false;
174        }
175      } else {
176        // Validate 24-hour format times
177        const match = block.match(pattern24);
178        if (!match || !resolveDayKey(match[1])) {
179          return false;
180        }
181        
182        const startHour = parseInt(match[2], 10);
183        const startMin = parseInt(match[3], 10);
184        const endHour = parseInt(match[4], 10);
185        const endMin = parseInt(match[5], 10);
186        
187        // Check valid time ranges
188        if (startHour < 0 || startHour > 23 || startMin < 0 || startMin > 59 ||
189            endHour < 0 || endHour > 23 || endMin < 0 || endMin > 59) {
190          return false;
191        }
192      }
193    }
194    
195    return true;
196  }
197
198  /**
199   * Converts 24-hour format time to 12-hour format
200   * @param {string} time24 - Time in 24-hour format (HH:MM)
201   * @returns {string} - Time in 12-hour format (H:MM AM/PM)
202   */
203  function convert24To12(time24) {
204    if (!time24 || !time24.match(/^\d{2}:\d{2}$/)) {
205      return time24; // Return as-is if invalid format
206    }
207    
208    const parts = time24.split(':');
209    let hour = parseInt(parts[0], 10);
210    const minute = parts[1];
211    let period = 'AM';
212    
213    if (hour === 0) {
214      hour = 12; // 00:00 = 12:00 AM
215    } else if (hour === 12) {
216      period = 'PM'; // 12:00 = 12:00 PM
217    } else if (hour > 12) {
218      hour -= 12;
219      period = 'PM';
220    }
221    
222    return hour + ':' + minute + ' ' + period;
223  }
224
225  /**
226   * Converts opening hours JSON back to input format
227   * @param {Object} data - The opening hours JSON object
228   * @param {boolean} use12hour - Whether to use 12-hour format
229   * @returns {string} - Input format string
230   */
231  function formatForInput(data, use12hour = false) {
232    if (!data || !data.week) {
233      return '';
234    }
235
236    const dayAbbrs = getDayConfig().abbrs;
237
238    const blocks = [];
239    const days = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su'];
240
241    for (const day of days) {
242      const dayAbbr = dayAbbrs[day];
243      if (!dayAbbr) {
244        continue;
245      }
246
247      const timeBlocks = data.week[day] || [];
248      
249      for (const block of timeBlocks) {
250        if (use12hour) {
251          const startTime = convert24To12(block.start);
252          const endTime = convert24To12(block.end);
253          blocks.push(`${dayAbbr} ${startTime}-${endTime}`);
254        } else {
255          blocks.push(`${dayAbbr} ${block.start}-${block.end}`);
256        }
257      }
258    }
259
260    return blocks.join(' | ');
261  }
262
263  /**
264   * Setup toggle functionality for opening hours headers
265   * @param {HTMLElement} container - Container element to search for headers
266   */
267  function setupToggle(container) {
268    if (!container) return;
269    
270    const headers = container.querySelectorAll('.oum-opening-hours-header');
271    headers.forEach(function(header) {
272      // Skip if already has event listener (check for data attribute)
273      if (header.dataset.oumToggleSetup === 'true') {
274        return;
275      }
276      
277      // Mark as setup
278      header.dataset.oumToggleSetup = 'true';
279      
280      // Add click handler
281      header.addEventListener('click', function(e) {
282        e.preventDefault();
283        toggleOpeningHours(header);
284      });
285      
286      // Add keyboard support
287      header.addEventListener('keydown', function(e) {
288        if (e.key === 'Enter' || e.key === ' ') {
289          e.preventDefault();
290          toggleOpeningHours(header);
291        }
292      });
293    });
294  }
295
296  /**
297   * Toggle the visibility of opening hours wrapper
298   * @param {HTMLElement} header - The header element containing the toggle
299   */
300  function toggleOpeningHours(header) {
301    const wrapper = header.nextElementSibling;
302    if (wrapper && wrapper.classList.contains('oum-opening-hours-wrapper')) {
303      const isExpanded = wrapper.classList.contains('oum-opening-hours-expanded');
304      if (isExpanded) {
305        wrapper.classList.remove('oum-opening-hours-expanded');
306        wrapper.style.display = 'none';
307        header.classList.remove('oum-opening-hours-expanded');
308      } else {
309        wrapper.classList.add('oum-opening-hours-expanded');
310        wrapper.style.display = 'block';
311        header.classList.add('oum-opening-hours-expanded');
312      }
313    }
314  }
315
316  /**
317   * Initialize MutationObserver for dynamically added content
318   */
319  function initializeObserver() {
320    if (observer) {
321      return; // Already initialized
322    }
323
324    observer = new MutationObserver(function(mutations) {
325      mutations.forEach(function(mutation) {
326        if (mutation.addedNodes.length) {
327          mutation.addedNodes.forEach(function(node) {
328            if (node.nodeType === 1) { // Element node
329              // Check if this node or its children contain opening hours header
330              if (node.querySelector && node.querySelector('.oum-opening-hours-header')) {
331                setupToggle(node);
332              }
333              // Also check if the node itself is an opening hours header
334              if (node.classList && node.classList.contains('oum-opening-hours-header')) {
335                setupToggle(node.parentElement);
336              }
337            }
338          });
339        }
340      });
341    });
342    
343    // Start observing
344    observer.observe(document.body, {
345      childList: true,
346      subtree: true
347    });
348  }
349
350  /**
351   * Initialize opening hours UI for existing content
352   */
353  function init() {
354    setupToggle(document.body);
355    initializeObserver();
356  }
357
358  // Public interface
359  return {
360    validateFormat: validateFormat,
361    formatForInput: formatForInput,
362    // UI methods
363    init: init,
364    setupToggle: setupToggle
365  };
366})();
367

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.