378 lines
11 KiB
JavaScript
378 lines
11 KiB
JavaScript
// Shared placeholder parsing and resolution helpers for browser-rendered API content.
|
|
|
|
(function () {
|
|
var root = window;
|
|
var transformPattern = /^([a-z_][a-z0-9_]*)\((.*)\)$/i;
|
|
|
|
var monthNamesShort = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
var monthNamesLong = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
|
var dayNamesShort = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
var dayNamesLong = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
|
|
|
function resolvePath(value, path) {
|
|
var current = value;
|
|
if (!path) {
|
|
return current;
|
|
}
|
|
|
|
String(path).split('.').forEach(function (segment) {
|
|
if (current === undefined || current === null) {
|
|
current = '';
|
|
return;
|
|
}
|
|
|
|
current = current[segment];
|
|
});
|
|
|
|
return current === undefined || current === null ? '' : current;
|
|
}
|
|
|
|
function parsePlaceholderExpression(expression) {
|
|
var raw = String(expression || '').trim();
|
|
var segments = raw ? raw.split('.') : [];
|
|
var transforms = [];
|
|
|
|
while (segments.length) {
|
|
var candidate = String(segments[segments.length - 1] || '').trim();
|
|
var match = candidate.match(transformPattern);
|
|
if (!match) {
|
|
break;
|
|
}
|
|
|
|
transforms.unshift({
|
|
name: String(match[1] || '').trim().toLowerCase(),
|
|
args: match[2] ? splitTransformArgs(match[2]) : []
|
|
});
|
|
segments.pop();
|
|
}
|
|
|
|
return {
|
|
path: segments.join('.'),
|
|
transforms: transforms
|
|
};
|
|
}
|
|
|
|
function splitTransformArgs(value) {
|
|
var source = String(value || '').trim();
|
|
if (!source) {
|
|
return [];
|
|
}
|
|
|
|
if ((source[0] === '"' && source[source.length - 1] === '"') || (source[0] === '\'' && source[source.length - 1] === '\'')) {
|
|
return [source.slice(1, -1)];
|
|
}
|
|
|
|
return source.split(',').map(function (item) {
|
|
return String(item || '').trim();
|
|
}).filter(Boolean);
|
|
}
|
|
|
|
function padNumber(value, size) {
|
|
var text = String(Math.abs(Number(value || 0)));
|
|
while (text.length < size) {
|
|
text = '0' + text;
|
|
}
|
|
return text;
|
|
}
|
|
|
|
function getDefaultTimeZone() {
|
|
try {
|
|
return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
|
|
} catch (_error) {
|
|
return 'UTC';
|
|
}
|
|
}
|
|
|
|
function getDateValue(value) {
|
|
return value instanceof Date ? value : new Date(value);
|
|
}
|
|
|
|
function resolveTimeZone(value) {
|
|
var raw = String(value || '').trim();
|
|
if (!raw) {
|
|
return getDefaultTimeZone();
|
|
}
|
|
|
|
try {
|
|
new Intl.DateTimeFormat('en-GB', { timeZone: raw }).format(new Date());
|
|
return raw;
|
|
} catch (_error) {
|
|
return getDefaultTimeZone();
|
|
}
|
|
}
|
|
|
|
function getDatePartsFromLocalTime(date) {
|
|
var dayPeriod = date.getHours() >= 12 ? 'PM' : 'AM';
|
|
return {
|
|
year: String(date.getFullYear()),
|
|
month: padNumber(date.getMonth() + 1, 2),
|
|
day: padNumber(date.getDate(), 2),
|
|
hour24: padNumber(date.getHours(), 2),
|
|
hour12: padNumber(date.getHours() % 12 || 12, 2),
|
|
minute: padNumber(date.getMinutes(), 2),
|
|
second: padNumber(date.getSeconds(), 2),
|
|
weekdayLong: dayNamesLong[date.getDay()],
|
|
weekdayShort: dayNamesShort[date.getDay()],
|
|
monthLong: monthNamesLong[date.getMonth()],
|
|
monthShort: monthNamesShort[date.getMonth()],
|
|
dayPeriod: dayPeriod
|
|
};
|
|
}
|
|
|
|
function getDatePartsFromTimeZone(date, timeZone) {
|
|
var resolvedTimeZone = resolveTimeZone(timeZone);
|
|
var baseFormatter = new Intl.DateTimeFormat('en-GB', {
|
|
timeZone: resolvedTimeZone,
|
|
hour12: false,
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
second: '2-digit',
|
|
day: '2-digit',
|
|
month: '2-digit',
|
|
year: 'numeric'
|
|
});
|
|
var weekdayLongFormatter = new Intl.DateTimeFormat('en-GB', {
|
|
timeZone: resolvedTimeZone,
|
|
weekday: 'long'
|
|
});
|
|
var weekdayShortFormatter = new Intl.DateTimeFormat('en-GB', {
|
|
timeZone: resolvedTimeZone,
|
|
weekday: 'short'
|
|
});
|
|
var monthLongFormatter = new Intl.DateTimeFormat('en-GB', {
|
|
timeZone: resolvedTimeZone,
|
|
month: 'long'
|
|
});
|
|
var monthShortFormatter = new Intl.DateTimeFormat('en-GB', {
|
|
timeZone: resolvedTimeZone,
|
|
month: 'short'
|
|
});
|
|
var ampmFormatter = new Intl.DateTimeFormat('en-GB', {
|
|
timeZone: resolvedTimeZone,
|
|
hour12: true,
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
});
|
|
var numericParts = baseFormatter.formatToParts(date);
|
|
var weekdayLong = weekdayLongFormatter.formatToParts(date);
|
|
var weekdayShort = weekdayShortFormatter.formatToParts(date);
|
|
var monthLong = monthLongFormatter.formatToParts(date);
|
|
var monthShort = monthShortFormatter.formatToParts(date);
|
|
var ampm = ampmFormatter.formatToParts(date);
|
|
|
|
function getPart(parts, type) {
|
|
var match = parts.find(function (part) {
|
|
return part && part.type === type;
|
|
});
|
|
return match ? String(match.value || '') : '';
|
|
}
|
|
|
|
var dayPeriod = getPart(ampm, 'dayPeriod');
|
|
|
|
return {
|
|
year: getPart(numericParts, 'year'),
|
|
month: getPart(numericParts, 'month'),
|
|
day: getPart(numericParts, 'day'),
|
|
hour24: getPart(numericParts, 'hour'),
|
|
hour12: getPart(ampm, 'hour'),
|
|
minute: getPart(numericParts, 'minute'),
|
|
second: getPart(numericParts, 'second'),
|
|
weekdayLong: getPart(weekdayLong, 'weekday'),
|
|
weekdayShort: getPart(weekdayShort, 'weekday'),
|
|
monthLong: getPart(monthLong, 'month'),
|
|
monthShort: getPart(monthShort, 'month'),
|
|
dayPeriod: String(dayPeriod || '').toUpperCase(),
|
|
timeZone: resolvedTimeZone
|
|
};
|
|
}
|
|
|
|
function formatDateValue(value, pattern, timeZone) {
|
|
var date = getDateValue(value);
|
|
if (Number.isNaN(date.getTime())) {
|
|
return '';
|
|
}
|
|
|
|
var format = String(pattern || 'YYYY-MM-DD HH:mm').trim() || 'YYYY-MM-DD HH:mm';
|
|
var parts = timeZone ? getDatePartsFromTimeZone(date, timeZone) : getDatePartsFromLocalTime(date);
|
|
var hours24 = parts.hour24;
|
|
var hours12 = parts.hour12;
|
|
var tokenMap = {
|
|
YYYY: parts.year,
|
|
YY: parts.year.slice(-2),
|
|
MMMM: parts.monthLong,
|
|
MMM: parts.monthShort,
|
|
MM: parts.month,
|
|
M: String(Number(parts.month) || 0),
|
|
DD: parts.day,
|
|
D: String(Number(parts.day) || 0),
|
|
dddd: parts.weekdayLong,
|
|
ddd: parts.weekdayShort,
|
|
HH: hours24,
|
|
H: String(Number(hours24) || 0),
|
|
hh: hours12,
|
|
h: String(Number(hours12) || 0),
|
|
mm: parts.minute,
|
|
m: String(Number(parts.minute) || 0),
|
|
ss: parts.second,
|
|
s: String(Number(parts.second) || 0),
|
|
A: parts.dayPeriod,
|
|
a: String(parts.dayPeriod || '').toLowerCase()
|
|
};
|
|
|
|
return format.replace(/\[([^\]]+)\]|YYYY|YY|MMMM|MMM|MM|M|DD|D|dddd|ddd|HH|H|hh|h|mm|m|ss|s|A|a/g, function (match, literal) {
|
|
return literal || tokenMap[match] || match;
|
|
});
|
|
}
|
|
|
|
function getTimeZoneShortName(value, timeZone) {
|
|
var date = getDateValue(value);
|
|
if (Number.isNaN(date.getTime())) {
|
|
return '';
|
|
}
|
|
|
|
var resolvedTimeZone = resolveTimeZone(timeZone);
|
|
try {
|
|
var parts = new Intl.DateTimeFormat('en-GB', {
|
|
timeZone: resolvedTimeZone,
|
|
timeZoneName: 'short'
|
|
}).formatToParts(date);
|
|
var match = parts.find(function (part) {
|
|
return part && part.type === 'timeZoneName';
|
|
});
|
|
return match ? String(match.value || '') : resolvedTimeZone;
|
|
} catch (_error) {
|
|
return resolvedTimeZone;
|
|
}
|
|
}
|
|
|
|
function getTimeZoneLongName(timeZone) {
|
|
return getTransformTimeZone([], { timeZone: timeZone });
|
|
}
|
|
|
|
function getTransformTimeZone(args, options) {
|
|
return resolveTimeZone((args && args[0]) || (options && options.timeZone) || '');
|
|
}
|
|
|
|
function applyTransform(value, transform, options) {
|
|
var text = String(value === undefined || value === null ? '' : value);
|
|
var name = String(transform && transform.name || '').trim().toLowerCase();
|
|
var args = Array.isArray(transform && transform.args) ? transform.args : [];
|
|
|
|
if (name === 'lower') {
|
|
return text.toLowerCase();
|
|
}
|
|
|
|
if (name === 'upper') {
|
|
return text.toUpperCase();
|
|
}
|
|
|
|
if (name === 'title') {
|
|
return text.toLowerCase().replace(/\b([a-z])/g, function (match, letter) {
|
|
return String(letter || '').toUpperCase();
|
|
});
|
|
}
|
|
|
|
if (name === 'format' || name === 'date' || name === 'datetime' || name === 'time') {
|
|
return formatDateValue(value, args[0] || (name === 'time' ? 'h:mm A' : name === 'date' ? 'MMM D, YYYY' : 'MMM D, YYYY h:mm A'), getTransformTimeZone(args.slice(1), options));
|
|
}
|
|
|
|
if (name === 'tz') {
|
|
return getTimeZoneShortName(value, getTransformTimeZone(args, options));
|
|
}
|
|
|
|
if (name === 'tz_long') {
|
|
return getTimeZoneLongName(getTransformTimeZone(args, options));
|
|
}
|
|
|
|
return text;
|
|
}
|
|
|
|
function resolvePlaceholderExpression(value, expression, options) {
|
|
var parsed = parsePlaceholderExpression(expression);
|
|
var resolved = resolvePath(value, parsed.path);
|
|
|
|
parsed.transforms.forEach(function (transform) {
|
|
resolved = applyTransform(resolved, transform, options || {});
|
|
});
|
|
|
|
return resolved;
|
|
}
|
|
|
|
function isImagePlaceholderExpression(expression) {
|
|
var parsed = parsePlaceholderExpression(expression);
|
|
return parsed.transforms.some(function (transform) {
|
|
return transform && transform.name === 'image';
|
|
});
|
|
}
|
|
|
|
function getImagePlaceholderConfig(expression) {
|
|
var parsed = parsePlaceholderExpression(expression);
|
|
var imageTransform = parsed.transforms.find(function (transform) {
|
|
return transform && transform.name === 'image';
|
|
});
|
|
var args = imageTransform && Array.isArray(imageTransform.args) ? imageTransform.args : [];
|
|
var width = Number(args[0]);
|
|
var height = Number(args[1]);
|
|
return {
|
|
width: Number.isFinite(width) && width > 0 ? Math.round(width) : 0,
|
|
height: Number.isFinite(height) && height > 0 ? Math.round(height) : 0
|
|
};
|
|
}
|
|
|
|
function formatPlaceholderValue(value) {
|
|
if (value === undefined || value === null) {
|
|
return '';
|
|
}
|
|
|
|
if (typeof value === 'object') {
|
|
try {
|
|
return JSON.stringify(value);
|
|
} catch (_error) {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
return String(value);
|
|
}
|
|
|
|
function collectPlaceholderFieldPaths(value) {
|
|
var output = [];
|
|
|
|
function walk(currentValue, prefix) {
|
|
if (!currentValue || typeof currentValue !== 'object') {
|
|
return;
|
|
}
|
|
|
|
if (Array.isArray(currentValue)) {
|
|
return;
|
|
}
|
|
|
|
Object.keys(currentValue).forEach(function (key) {
|
|
var nextPath = prefix ? prefix + '.' + key : key;
|
|
var nextValue = currentValue[key];
|
|
|
|
if (nextValue && typeof nextValue === 'object') {
|
|
walk(nextValue, nextPath);
|
|
return;
|
|
}
|
|
|
|
if (output.indexOf(nextPath) === -1) {
|
|
output.push(nextPath);
|
|
}
|
|
});
|
|
}
|
|
|
|
walk(value, '');
|
|
return output;
|
|
}
|
|
|
|
root.placeholderUtils = {
|
|
resolvePath: resolvePath,
|
|
parsePlaceholderExpression: parsePlaceholderExpression,
|
|
resolvePlaceholderExpression: resolvePlaceholderExpression,
|
|
isImagePlaceholderExpression: isImagePlaceholderExpression,
|
|
getImagePlaceholderConfig: getImagePlaceholderConfig,
|
|
formatPlaceholderValue: formatPlaceholderValue,
|
|
collectPlaceholderFieldPaths: collectPlaceholderFieldPaths
|
|
};
|
|
}()); |