/* global window, document, DWgetSelection, DWsetSelection, pasteText */ (function () { 'use strict'; var Luxtools = window.Luxtools || (window.Luxtools = {}); var DateFix = Luxtools.DateFix || (Luxtools.DateFix = {}); // Month name patterns for regex (English and German) var MONTH_PATTERN = '(?:jan|feb|m[aä]r|apr|ma[iy]|jun|jul|aug|sep|sept|okt|oct|nov|de[cz])[a-z]*\\.?'; // Regex to find date candidates in text for "fix all" feature var CANDIDATE_REGEX = new RegExp( '\\b(?:' + '\\d{4}[-\\/.][\\d]{1,2}[-\\/.][\\d]{1,2}|' + // YYYY-MM-DD '\\d{1,2}[-\\/.][\\d]{1,2}[-\\/.][\\d]{4}|' + // DD-MM-YYYY MONTH_PATTERN + '\\s+\\d{1,2}(?:st|nd|rd|th)?[,]?\\s+\\d{4}|' + // Month DD, YYYY '\\d{1,2}(?:st|nd|rd|th)?\\.?\\s+' + MONTH_PATTERN + '\\s+\\d{4}|' + // DD Month YYYY '\\d{4}\\s+' + MONTH_PATTERN + '\\s+\\d{1,2}' + // YYYY Month DD ')(?:[T\\s]+\\d{1,2}:\\d{2}(?::\\d{2})?(?:\\s*(?:am|pm))?)?\\b', 'gi' ); // Map month names (English and German) to month numbers (1-12) var MONTH_MAP = { 'jan': 1, 'januar': 1, 'january': 1, 'feb': 2, 'februar': 2, 'february': 2, 'mar': 3, 'mär': 3, 'märz': 3, 'march': 3, 'maerz': 3, 'apr': 4, 'april': 4, 'may': 5, 'mai': 5, 'jun': 6, 'juni': 6, 'june': 6, 'jul': 7, 'juli': 7, 'july': 7, 'aug': 8, 'august': 8, 'sep': 9, 'sept': 9, 'september': 9, 'oct': 10, 'okt': 10, 'oktober': 10, 'october': 10, 'nov': 11, 'november': 11, 'dec': 12, 'dez': 12, 'dezember': 12, 'december': 12 }; function pad2(n) { return n < 10 ? '0' + n : String(n); } /** * Look up a month name (English or German) and return its number (1-12). * Returns null if not found. */ function parseMonthName(name) { if (!name) return null; var key = name.toLowerCase().replace(/\.$/, ''); return MONTH_MAP[key] || null; } /** * Preprocess input to make it parseable by Date. * Handles formats Date.parse() doesn't understand natively. */ function preprocess(input) { var s = input .replace(/^\s*[([{"'`]+|[)\]}",'`]+\s*$/g, '') // strip surrounding brackets/quotes .replace(/\s*(Z|[+-]\d{2}:?\d{2})$/i, '') // strip timezone .replace(/(\d)(st|nd|rd|th)\b/gi, '$1') // strip ordinal suffixes .replace(/,/g, ' ') .replace(/\s+/g, ' ') .trim(); // Handle month names (English and German) - convert to YYYY-MM-DD format // Pattern: DD Month YYYY or DD. Month YYYY var monthMatch = s.match(/^(\d{1,2})\.?\s+([a-zäö]+)\.?\s+(\d{4})(.*)$/i); if (monthMatch) { var day = parseInt(monthMatch[1], 10); var monthNum = parseMonthName(monthMatch[2]); var year = monthMatch[3]; var rest = monthMatch[4] || ''; if (monthNum) { return year + '-' + pad2(monthNum) + '-' + pad2(day) + rest; } } // Pattern: Month DD, YYYY monthMatch = s.match(/^([a-zäö]+)\.?\s+(\d{1,2})\s+(\d{4})(.*)$/i); if (monthMatch) { var monthNum = parseMonthName(monthMatch[1]); var day = parseInt(monthMatch[2], 10); var year = monthMatch[3]; var rest = monthMatch[4] || ''; if (monthNum) { return year + '-' + pad2(monthNum) + '-' + pad2(day) + rest; } } // Pattern: YYYY Month DD monthMatch = s.match(/^(\d{4})\s+([a-zäö]+)\.?\s+(\d{1,2})(.*)$/i); if (monthMatch) { var year = monthMatch[1]; var monthNum = parseMonthName(monthMatch[2]); var day = parseInt(monthMatch[3], 10); var rest = monthMatch[4] || ''; if (monthNum) { return year + '-' + pad2(monthNum) + '-' + pad2(day) + rest; } } // DD.MM.YYYY or DD/MM/YYYY -> rearrange to YYYY-MM-DD for Date var match = s.match(/^(\d{1,2})[-\/.](\d{1,2})[-\/.](\d{4})(.*)$/); if (match) { var a = parseInt(match[1], 10); var b = parseInt(match[2], 10); var year = match[3]; var rest = match[4] || ''; // Disambiguate: if first > 12, it must be day (DMY); else assume DMY var day = a > 12 ? a : (b > 12 ? b : a); var month = a > 12 ? b : (b > 12 ? a : b); s = year + '-' + pad2(month) + '-' + pad2(day) + rest; } return s; } /** * Try to parse a date/time string into a Date object. * Returns null if parsing fails or produces an invalid date. */ function tryParse(input) { if (!input || typeof input !== 'string') return null; var preprocessed = preprocess(input); if (!preprocessed) return null; // Try native parsing var ts = Date.parse(preprocessed); if (!isNaN(ts)) return new Date(ts); // Fallback: replace dots/slashes with dashes and try again var normalized = preprocessed.replace(/[\/\.]/g, '-'); ts = Date.parse(normalized); if (!isNaN(ts)) return new Date(ts); return null; } /** * Check if the original input contained a time component. */ function hasTimeComponent(input) { return /\d{1,2}:\d{2}/.test(input); } /** * Format a Date object to YYYY-MM-DD or YYYY-MM-DD HH:MM:SS. */ function formatDate(date, includeTime) { var out = date.getFullYear() + '-' + pad2(date.getMonth() + 1) + '-' + pad2(date.getDate()); if (includeTime) { out += ' ' + pad2(date.getHours()) + ':' + pad2(date.getMinutes()) + ':' + pad2(date.getSeconds()); } return out; } /** * Normalize a timestamp string to YYYY-MM-DD (or YYYY-MM-DD HH:MM:SS if time present). * Returns null if the input cannot be parsed as a valid date. */ function normalizeTimestamp(input) { var date = tryParse(input); if (!date) return null; return formatDate(date, hasTimeComponent(input)); } function getEditor(edid) { return document.getElementById(edid); } function fixSelection(edid) { if (typeof DWgetSelection !== 'function' || typeof pasteText !== 'function') return false; var textarea = getEditor(edid); if (!textarea) return false; var selection = DWgetSelection(textarea); var text = selection.getText(); if (!text || !text.trim()) return false; var normalized = normalizeTimestamp(text); if (!normalized) return false; pasteText(selection, normalized, { nosel: true }); return false; } function fixAll(edid) { var textarea = getEditor(edid); if (!textarea) return false; var selection = typeof DWgetSelection === 'function' ? DWgetSelection(textarea) : null; var original = textarea.value; var replaced = original.replace(CANDIDATE_REGEX, function (match) { var normalized = normalizeTimestamp(match); return normalized || match; }); if (replaced !== original) { textarea.value = replaced; if (selection && typeof DWsetSelection === 'function') { selection.start = Math.min(selection.start, textarea.value.length); selection.end = Math.min(selection.end, textarea.value.length); DWsetSelection(selection); } } return false; } DateFix.normalize = normalizeTimestamp; DateFix.fixSelection = fixSelection; DateFix.fixAll = fixAll; // Toolbar button action handlers // DokuWiki toolbar looks for addBtnAction functions for custom button types. // The buttons are registered via PHP (TOOLBAR_DEFINE hook in action.php). window.addBtnActionLuxtoolsDatefix = function ($btn, props, edid) { $btn.on('click', function () { fixSelection(edid); return false; }); return 'luxtools-datefix'; }; window.addBtnActionLuxtoolsDatefixAll = function ($btn, props, edid) { $btn.on('click', function () { fixAll(edid); return false; }); return 'luxtools-datefix-all'; }; })();