Add date fixer functions
This commit is contained in:
235
js/date-fix.js
Normal file
235
js/date-fix.js
Normal file
@@ -0,0 +1,235 @@
|
||||
/* 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<Type> 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';
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user