Release v2.2.0
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
// Shared placeholder chip markup and copy-to-clipboard behavior.
|
||||
|
||||
(function () {
|
||||
var root = window;
|
||||
var CHIP_ATTR = 'data-placeholder-chip';
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value === undefined || value === null ? '' : value)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function copyTextToClipboard(text) {
|
||||
var value = String(text === undefined || text === null ? '' : text);
|
||||
if (!value) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function' && window.isSecureContext) {
|
||||
return navigator.clipboard.writeText(value).then(function () {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise(function (resolve) {
|
||||
var textarea = document.createElement('textarea');
|
||||
textarea.value = value;
|
||||
textarea.setAttribute('readonly', '');
|
||||
textarea.style.position = 'fixed';
|
||||
textarea.style.left = '-9999px';
|
||||
textarea.style.top = '0';
|
||||
document.body.appendChild(textarea);
|
||||
textarea.focus();
|
||||
textarea.select();
|
||||
|
||||
var success = false;
|
||||
try {
|
||||
success = document.execCommand('copy');
|
||||
} catch (_error) {
|
||||
success = false;
|
||||
}
|
||||
|
||||
document.body.removeChild(textarea);
|
||||
resolve(success);
|
||||
});
|
||||
}
|
||||
|
||||
function findChipElement(target) {
|
||||
if (!target || typeof target.closest !== 'function') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return target.closest('[' + CHIP_ATTR + ']');
|
||||
}
|
||||
|
||||
function activateChipCopy(chip) {
|
||||
if (!chip) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
return copyTextToClipboard(chip.getAttribute('data-copy-text') || chip.textContent || '');
|
||||
}
|
||||
|
||||
function handleChipInteraction(event) {
|
||||
var chip = findChipElement(event && event.target);
|
||||
if (!chip) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.type === 'keydown') {
|
||||
var key = String(event.key || '');
|
||||
if (key !== 'Enter' && key !== ' ') {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
activateChipCopy(chip);
|
||||
}
|
||||
|
||||
function ensureCopyHandler() {
|
||||
if (root.__placeholderChipCopyHandlerInstalled) {
|
||||
return;
|
||||
}
|
||||
|
||||
root.__placeholderChipCopyHandlerInstalled = true;
|
||||
document.addEventListener('click', handleChipInteraction, true);
|
||||
document.addEventListener('keydown', handleChipInteraction, true);
|
||||
}
|
||||
|
||||
function renderChip(token) {
|
||||
var copyText = '{{' + String(token || '') + '}}';
|
||||
var escapedCopyText = escapeHtml(copyText);
|
||||
ensureCopyHandler();
|
||||
|
||||
return '<span class="chip" ' + CHIP_ATTR + '="1" data-copy-text="' + escapedCopyText + '" role="button" tabindex="0" title="Click to copy ' + escapedCopyText + '" aria-label="Copy ' + escapedCopyText + '">' + escapedCopyText + '</span>';
|
||||
}
|
||||
|
||||
function renderChips(tokens) {
|
||||
var list = Array.isArray(tokens) ? tokens : [];
|
||||
return list.map(function (token) {
|
||||
return renderChip(token);
|
||||
}).join('');
|
||||
}
|
||||
|
||||
root.placeholderChips = {
|
||||
renderChip: renderChip,
|
||||
renderChips: renderChips,
|
||||
copyTextToClipboard: copyTextToClipboard,
|
||||
ensureCopyHandler: ensureCopyHandler
|
||||
};
|
||||
}());
|
||||
@@ -0,0 +1,131 @@
|
||||
// Shared placeholder parsing and resolution helpers for browser-rendered API content.
|
||||
|
||||
(function () {
|
||||
var root = window;
|
||||
var transformPattern = /^(upper|lower|title)\(\)$/i;
|
||||
|
||||
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();
|
||||
if (!transformPattern.test(candidate)) {
|
||||
break;
|
||||
}
|
||||
|
||||
transforms.unshift(candidate.replace(/\(\)$/g, '').toLowerCase());
|
||||
segments.pop();
|
||||
}
|
||||
|
||||
return {
|
||||
path: segments.join('.'),
|
||||
transforms: transforms
|
||||
};
|
||||
}
|
||||
|
||||
function applyTransform(value, transform) {
|
||||
var text = String(value === undefined || value === null ? '' : value);
|
||||
|
||||
if (transform === 'lower') {
|
||||
return text.toLowerCase();
|
||||
}
|
||||
|
||||
if (transform === 'upper') {
|
||||
return text.toUpperCase();
|
||||
}
|
||||
|
||||
if (transform === 'title') {
|
||||
return text.toLowerCase().replace(/\b([a-z])/g, function (match, letter) {
|
||||
return String(letter || '').toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
function resolvePlaceholderExpression(value, expression) {
|
||||
var parsed = parsePlaceholderExpression(expression);
|
||||
var resolved = resolvePath(value, parsed.path);
|
||||
|
||||
parsed.transforms.forEach(function (transform) {
|
||||
resolved = applyTransform(resolved, transform);
|
||||
});
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
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,
|
||||
formatPlaceholderValue: formatPlaceholderValue,
|
||||
collectPlaceholderFieldPaths: collectPlaceholderFieldPaths
|
||||
};
|
||||
}());
|
||||
@@ -0,0 +1,48 @@
|
||||
// Shared time/date placeholder definitions used by editor renderers.
|
||||
|
||||
(function () {
|
||||
var root = window;
|
||||
var PLACEHOLDERS = [
|
||||
{ token: 'h', label: 'h' },
|
||||
{ token: 'hh', label: 'hh' },
|
||||
{ token: 'm', label: 'm' },
|
||||
{ token: 'mm', label: 'mm' },
|
||||
{ token: 's', label: 's' },
|
||||
{ token: 'ss', label: 'ss' },
|
||||
{ token: 'd', label: 'd' },
|
||||
{ token: 'dd', label: 'dd' },
|
||||
{ token: 'M', label: 'M' },
|
||||
{ token: 'MM', label: 'MM' },
|
||||
{ token: 'y', label: 'y' },
|
||||
{ token: 'yyyy', label: 'yyyy' },
|
||||
{ token: 'yy', label: 'yy' },
|
||||
{ token: 'ddd', label: 'ddd' },
|
||||
{ token: 'dddd', label: 'dddd' },
|
||||
{ token: 'MMM', label: 'MMM' },
|
||||
{ token: 'MMMM', label: 'MMMM' },
|
||||
{ token: 'a', label: 'a' },
|
||||
{ token: 'tz', label: 'tz' },
|
||||
{ token: 'tz_short', label: 'tz_short' }
|
||||
];
|
||||
|
||||
function getTokens() {
|
||||
return PLACEHOLDERS.slice();
|
||||
}
|
||||
|
||||
function renderChips() {
|
||||
if (root.placeholderChips && typeof root.placeholderChips.renderChips === 'function') {
|
||||
return root.placeholderChips.renderChips(PLACEHOLDERS.map(function (item) {
|
||||
return item.token;
|
||||
}));
|
||||
}
|
||||
|
||||
return PLACEHOLDERS.map(function (item) {
|
||||
return '<span class="chip">{{' + item.token + '}}</span>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
root.timeDatePlaceholders = {
|
||||
getTokens: getTokens,
|
||||
renderChips: renderChips
|
||||
};
|
||||
}());
|
||||
Reference in New Issue
Block a user