Release 2.8.3
This commit is contained in:
@@ -2,6 +2,18 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## 2.8.3 - 2026-08-17
|
||||
|
||||
### Added
|
||||
|
||||
- Added API, RSS, Timetable, and Time / Date placeholder help panels with shared transform and date-token documentation.
|
||||
- Added render-time API and RSS image placeholders with proportional sizing and preview-only bounding boxes.
|
||||
|
||||
### Changed
|
||||
|
||||
- API and RSS regions now preserve authored content when no data source is selected and remain blank when a selected source has no authored content.
|
||||
- Normal WYSIWYG image insertion remains upload-backed and separate from image placeholder transforms.
|
||||
|
||||
## 2.8.2 - 2026-08-16
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pulse-signage-player",
|
||||
"version": "2.8.2",
|
||||
"version": "2.8.3",
|
||||
"private": false,
|
||||
"description": "Pulse Signage player application bundle",
|
||||
"engines": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pulse-signage-web",
|
||||
"version": "2.8.2",
|
||||
"version": "2.8.3",
|
||||
"private": false,
|
||||
"description": "Pulse Signage web and bridge application bundle",
|
||||
"engines": {
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "pulse-signage",
|
||||
"version": "2.8.2",
|
||||
"version": "2.8.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pulse-signage",
|
||||
"version": "2.8.2",
|
||||
"version": "2.8.3",
|
||||
"dependencies": {
|
||||
"@sparticuz/chromium": "^149.0.0",
|
||||
"animate.css": "^4.1.1",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pulse-signage",
|
||||
"version": "2.8.2",
|
||||
"version": "2.8.3",
|
||||
"private": false,
|
||||
"description": "Pulse Signage application with MySQL and media storage",
|
||||
"engines": {
|
||||
|
||||
+2
-2
@@ -418,7 +418,7 @@ async function buildTemplateContent(pool, template, body, filesByField, existing
|
||||
content[region.region_key] = {
|
||||
type: 'rss',
|
||||
value: stripEditorOnlyMarkup(normalizeEditorMarkup(submitted === undefined ? String(current.value || '') : String(submitted || ''))),
|
||||
feed_id: feedId === undefined || feedId === null || feedId === '' ? (current.feed_id || null) : Number(feedId),
|
||||
feed_id: feedId === undefined || feedId === null ? (current.feed_id || null) : (feedId === '' ? null : Number(feedId)),
|
||||
item_number: Math.min(itemCount, Number.isFinite(parsedItemNumber) && parsedItemNumber > 0 ? parsedItemNumber : 1),
|
||||
variable_name: 'item',
|
||||
font_family: style.font_family,
|
||||
@@ -436,7 +436,7 @@ async function buildTemplateContent(pool, template, body, filesByField, existing
|
||||
content[region.region_key] = {
|
||||
type: 'api',
|
||||
value: stripEditorOnlyMarkup(normalizeEditorMarkup(submitted === undefined ? String(current.value || '') : String(submitted || ''))),
|
||||
source_id: sourceId === undefined || sourceId === null || sourceId === '' ? (current.source_id || null) : Number(sourceId),
|
||||
source_id: sourceId === undefined || sourceId === null ? (current.source_id || null) : (sourceId === '' ? null : Number(sourceId)),
|
||||
item_number: Number.isFinite(parsedItemNumber) && parsedItemNumber > 0 ? parsedItemNumber : 1,
|
||||
items_path: itemsPath === undefined || itemsPath === null ? (current.items_path === undefined || current.items_path === null ? '' : String(current.items_path)) : String(itemsPath || '').trim(),
|
||||
variable_name: 'item',
|
||||
|
||||
@@ -67,7 +67,23 @@ function substituteApiVariables(html, item) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return escapeHtml(placeholderUtils.formatPlaceholderValue(placeholderUtils.resolvePlaceholderExpression(item, expression)));
|
||||
var resolved = placeholderUtils.resolvePlaceholderExpression(item, expression);
|
||||
if (typeof placeholderUtils.isImagePlaceholderExpression === 'function' && placeholderUtils.isImagePlaceholderExpression(expression)) {
|
||||
var imageSource = placeholderUtils.formatPlaceholderValue(resolved);
|
||||
if (!/^(?:https?:\/\/|\/media\/|\/assets\/|\/[^/])/i.test(imageSource)) {
|
||||
return '';
|
||||
}
|
||||
var imageConfig = typeof placeholderUtils.getImagePlaceholderConfig === 'function' ? placeholderUtils.getImagePlaceholderConfig(expression) : {};
|
||||
var hasImageBounds = imageConfig.width && imageConfig.height;
|
||||
var imageStyle = hasImageBounds
|
||||
? 'display:block;width:100%;height:100%;object-fit:contain;'
|
||||
: 'display:block;width:auto;height:auto;' + (imageConfig.width ? 'max-width:' + imageConfig.width + 'px;' : '') + (imageConfig.height ? 'max-height:' + imageConfig.height + 'px;' : '');
|
||||
var image = '<img src="' + escapeHtml(imageSource) + '" alt="" style="' + imageStyle + '" />';
|
||||
return hasImageBounds
|
||||
? '<span style="display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;width:' + imageConfig.width + 'px;height:' + imageConfig.height + 'px;">' + image + '</span>'
|
||||
: image;
|
||||
}
|
||||
return escapeHtml(placeholderUtils.formatPlaceholderValue(resolved));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -120,10 +136,8 @@ function renderApiRegion(region, regionContent) {
|
||||
var fontSize = sanitizeFontSize(regionContent.font_size || region.fontSize);
|
||||
var fontColor = sanitizeTextColor(regionContent.font_color || region.fontColor);
|
||||
var item = getApiItem(sourceId, itemNumber, itemsPath);
|
||||
var body = item ? substituteApiVariables(content, item) : '';
|
||||
if (!body && item) {
|
||||
body = getApiPreviewFallback(item);
|
||||
}
|
||||
var hasSource = String(sourceId === undefined || sourceId === null ? '' : sourceId).trim() !== '';
|
||||
var body = hasSource ? (item ? substituteApiVariables(content, item) : '') : content;
|
||||
var contentStyle = 'width:' + region.pixelWidth + 'px;height:' + region.pixelHeight + 'px;transform:scale(' + region.canvasScale + ');transform-origin:top left;' + (fontFamily ? 'font-family:' + escapeHtml(fontFamily) + ';' : '') + 'font-size:' + fontSize + 'px;color:' + escapeHtml(fontColor) + ';';
|
||||
if (!body) {
|
||||
return '';
|
||||
|
||||
+24
-14
@@ -1,6 +1,7 @@
|
||||
// RSS region helpers for resolving feeds, items, and nested values.
|
||||
|
||||
var registry = window.pulsePlayerRegionTypes;
|
||||
var placeholderUtils = window.placeholderUtils || {};
|
||||
|
||||
function getDefaultStyle() {
|
||||
return {
|
||||
@@ -65,12 +66,30 @@ function resolveRssPath(value, path) {
|
||||
|
||||
function substituteRssVariables(html, item) {
|
||||
var source = String(html || '');
|
||||
return source.replace(/\{\{\s*([a-zA-Z0-9_]+)(?:\.([a-zA-Z0-9_.]+))?\s*\}\}/g, function (_match, tokenName, tokenPath) {
|
||||
return source.replace(/\{\{\s*([^{}]+?)\s*\}\}/g, function (_match, expression) {
|
||||
if (!item || typeof item !== 'object') {
|
||||
return '';
|
||||
}
|
||||
var key = tokenPath ? tokenName + '.' + tokenPath : tokenName;
|
||||
return escapeHtml(resolveRssPath(item, key || ''));
|
||||
if (typeof placeholderUtils.resolvePlaceholderExpression !== 'function' || typeof placeholderUtils.formatPlaceholderValue !== 'function') {
|
||||
return '';
|
||||
}
|
||||
var resolved = placeholderUtils.resolvePlaceholderExpression(item, expression);
|
||||
if (typeof placeholderUtils.isImagePlaceholderExpression === 'function' && placeholderUtils.isImagePlaceholderExpression(expression)) {
|
||||
var imageSource = placeholderUtils.formatPlaceholderValue(resolved);
|
||||
if (!/^(?:https?:\/\/|\/media\/|\/assets\/|\/[^/])/i.test(imageSource)) {
|
||||
return '';
|
||||
}
|
||||
var imageConfig = typeof placeholderUtils.getImagePlaceholderConfig === 'function' ? placeholderUtils.getImagePlaceholderConfig(expression) : {};
|
||||
var hasImageBounds = imageConfig.width && imageConfig.height;
|
||||
var imageStyle = hasImageBounds
|
||||
? 'display:block;width:100%;height:100%;object-fit:contain;'
|
||||
: 'display:block;width:auto;height:auto;' + (imageConfig.width ? 'max-width:' + imageConfig.width + 'px;' : '') + (imageConfig.height ? 'max-height:' + imageConfig.height + 'px;' : '');
|
||||
var image = '<img src="' + escapeHtml(imageSource) + '" alt="" style="' + imageStyle + '" />';
|
||||
return hasImageBounds
|
||||
? '<span style="display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;width:' + imageConfig.width + 'px;height:' + imageConfig.height + 'px;">' + image + '</span>'
|
||||
: image;
|
||||
}
|
||||
return escapeHtml(placeholderUtils.formatPlaceholderValue(resolved));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -83,17 +102,8 @@ function renderRssRegion(region, regionContent) {
|
||||
var fontSize = sanitizeFontSize(regionContent.font_size || region.fontSize || defaultStyle.font_size);
|
||||
var fontColor = sanitizeTextColor(regionContent.font_color || region.fontColor || defaultStyle.font_color);
|
||||
var item = getRssFeedItem(feedId, itemNumber);
|
||||
var body = item ? substituteRssVariables(content, item) : '';
|
||||
if (!body && item) {
|
||||
var summaryParts = [];
|
||||
if (item.title) {
|
||||
summaryParts.push('<h3>' + escapeHtml(item.title) + '</h3>');
|
||||
}
|
||||
if (item.description) {
|
||||
summaryParts.push('<div>' + sanitizeRichText(item.description) + '</div>');
|
||||
}
|
||||
body = summaryParts.join('');
|
||||
}
|
||||
var hasFeed = String(feedId === undefined || feedId === null ? '' : feedId).trim() !== '';
|
||||
var body = hasFeed ? (item ? substituteRssVariables(content, item) : '') : content;
|
||||
var contentStyle = 'width:' + region.pixelWidth + 'px;height:' + region.pixelHeight + 'px;transform:scale(' + region.canvasScale + ');transform-origin:top left;' + (fontFamily ? 'font-family:' + escapeHtml(fontFamily) + ';' : '') + 'font-size:' + fontSize + 'px;color:' + escapeHtml(fontColor) + ';';
|
||||
if (!body) {
|
||||
return '';
|
||||
|
||||
@@ -4,7 +4,7 @@ const path = require('path');
|
||||
const PUBLIC_JS_ROOT = path.join(__dirname, '..', 'public', 'js');
|
||||
const REGION_ROOT_DIR = path.join(PUBLIC_JS_ROOT, 'regions');
|
||||
const REGION_TYPE_DIR = path.join(REGION_ROOT_DIR, 'type');
|
||||
const REGION_CORE_SCRIPTS = ['js/shared/placeholder-utils.js', 'js/shared/placeholder-chips.js', 'js/shared/time-date-placeholders.js', 'js/regions/region-utils.js', 'js/regions/region-types.js'];
|
||||
const REGION_CORE_SCRIPTS = ['js/shared/placeholder-utils.js', 'js/shared/placeholder-chips.js', 'js/shared/placeholder-info.js', 'js/shared/time-date-placeholders.js', 'js/regions/region-utils.js', 'js/regions/region-types.js'];
|
||||
|
||||
function withAssetVersion(scriptPath, assetVersion) {
|
||||
if (!assetVersion) {
|
||||
|
||||
@@ -2533,6 +2533,13 @@ html[data-bs-theme='dark'] .template-field-card .tox .tox-collection {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.api-region-placeholder-title-help {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: normal;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.api-region-sample-accordion {
|
||||
padding: 0.85rem 1rem 1rem;
|
||||
margin-bottom: 0.75rem;
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
};
|
||||
}
|
||||
|
||||
function substituteVariables(html, item) {
|
||||
function substituteVariables(html, item, options) {
|
||||
var source = String(html || '');
|
||||
return source.replace(/\{\{\s*([^{}]+?)\s*\}\}/g, function (_match, expression) {
|
||||
if (!item || typeof item !== 'object') {
|
||||
@@ -105,7 +105,23 @@
|
||||
return '';
|
||||
}
|
||||
|
||||
return escapeHtml(placeholderUtils.formatPlaceholderValue(placeholderUtils.resolvePlaceholderExpression(item, expression)));
|
||||
var resolved = placeholderUtils.resolvePlaceholderExpression(item, expression);
|
||||
if (typeof placeholderUtils.isImagePlaceholderExpression === 'function' && placeholderUtils.isImagePlaceholderExpression(expression)) {
|
||||
var imageSource = placeholderUtils.formatPlaceholderValue(resolved);
|
||||
if (!/^(?:https?:\/\/|\/media\/|\/assets\/|\/[^/])/i.test(imageSource)) {
|
||||
return '';
|
||||
}
|
||||
var imageConfig = typeof placeholderUtils.getImagePlaceholderConfig === 'function' ? placeholderUtils.getImagePlaceholderConfig(expression) : {};
|
||||
var imageStyle = options && options.preview && imageConfig.width && imageConfig.height
|
||||
? 'display:block;width:100%;height:100%;object-fit:contain;'
|
||||
: 'display:block;width:auto;height:auto;' + (imageConfig.width ? 'max-width:' + imageConfig.width + 'px;' : '') + (imageConfig.height ? 'max-height:' + imageConfig.height + 'px;' : '');
|
||||
var image = '<img src="' + escapeHtml(imageSource) + '" alt="" style="' + imageStyle + '" />';
|
||||
if (options && options.preview && imageConfig.width && imageConfig.height) {
|
||||
return '<span style="display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;width:' + imageConfig.width + 'px;height:' + imageConfig.height + 'px;border:1px dashed rgba(120,120,120,.75);">' + image + '</span>';
|
||||
}
|
||||
return image;
|
||||
}
|
||||
return escapeHtml(placeholderUtils.formatPlaceholderValue(resolved));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -136,6 +152,24 @@
|
||||
return summary.join('');
|
||||
}
|
||||
|
||||
function renderTransformInfo(regionId) {
|
||||
var offcanvasId = 'api-placeholder-info-' + regionId;
|
||||
return '' +
|
||||
'<button type="button" class="btn btn-link btn-sm p-0 text-decoration-none" data-bs-toggle="offcanvas" data-bs-target="#' + offcanvasId + '" aria-controls="' + offcanvasId + '">More info</button>' +
|
||||
'<div class="offcanvas offcanvas-end fw-normal" tabindex="-1" id="' + offcanvasId + '" aria-labelledby="' + offcanvasId + '-label">' +
|
||||
'<div class="offcanvas-header">' +
|
||||
'<h2 class="offcanvas-title fs-5" id="' + offcanvasId + '-label">API placeholder transforms</h2>' +
|
||||
'<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>' +
|
||||
'</div>' +
|
||||
'<div class="offcanvas-body">' +
|
||||
'<p class="small text-body-secondary">Placeholders read values from the selected API item. Nested fields use dots.</p>' +
|
||||
(window.placeholderInfo ? window.placeholderInfo.renderTextTransforms() : '') +
|
||||
(window.placeholderInfo ? window.placeholderInfo.renderDateFormatTokens() : '') +
|
||||
(window.placeholderInfo ? window.placeholderInfo.renderImageTransform() : '') +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
function normalizeRenderableValue(value) {
|
||||
if (value && typeof value === 'object') {
|
||||
if (value.value !== undefined) {
|
||||
@@ -170,11 +204,8 @@
|
||||
var fontSize = sanitizeFontSize(regionContent.font_size || region.fontSize);
|
||||
var fontColor = sanitizeTextColor(regionContent.font_color || region.fontColor);
|
||||
var item = getItem(sourceId, itemNumber, sources, itemsPath);
|
||||
var body = item ? substituteVariables(content, item) : '';
|
||||
|
||||
if (!body && item) {
|
||||
body = getPreviewFallback(item);
|
||||
}
|
||||
var hasSource = String(sourceId === undefined || sourceId === null ? '' : sourceId).trim() !== '';
|
||||
var body = hasSource ? (item ? substituteVariables(content, item, { preview: true }) : '') : content;
|
||||
|
||||
body = String(body || '')
|
||||
.replace(/<pre[^>]*class="[^"]*api-region-sample-preview[^"]*"[^>]*>[\s\S]*?<\/pre>/gi, '')
|
||||
@@ -239,7 +270,7 @@
|
||||
'<input type="hidden" name="region_font_size_' + region.id + '" value="' + escapeHtml(context.fontSize || '') + '" />' +
|
||||
'<input type="hidden" name="region_text_' + region.id + '" value="' + escapeHtml(current.value !== undefined ? current.value : '') + '" />' +
|
||||
'<div class="api-region-placeholder-section">' +
|
||||
'<div class="api-region-placeholder-title">Available placeholders</div>' +
|
||||
'<div class="api-region-placeholder-title api-region-placeholder-title-help d-flex align-items-center gap-2">Available placeholders ' + renderTransformInfo(region.id) + '</div>' +
|
||||
'<div class="d-flex flex-wrap gap-2" data-placeholder-chips>' + ((placeholderFields.length && window.placeholderChips && typeof window.placeholderChips.renderChips === 'function') ? window.placeholderChips.renderChips(placeholderFields) : placeholderChips || '<span class="muted slide-image-file">No JSON fields available.</span>') + '</div>' +
|
||||
'</div>' +
|
||||
'<details class="api-region-sample-accordion" data-api-sample-data-accordion>' +
|
||||
@@ -290,7 +321,7 @@
|
||||
return fallbackValue;
|
||||
}
|
||||
}())),
|
||||
source_id: (card.querySelector('select[name="region_api_source_id_' + region.id + '"]') || {}).value || current.source_id,
|
||||
source_id: card.querySelector('select[name="region_api_source_id_' + region.id + '"]') ? card.querySelector('select[name="region_api_source_id_' + region.id + '"]').value : current.source_id,
|
||||
item_number: (card.querySelector('input[name="region_api_item_number_' + region.id + '"]') || {}).value || current.item_number,
|
||||
items_path: card.querySelector('input[name="region_api_items_path_' + region.id + '"]') ? (card.querySelector('input[name="region_api_items_path_' + region.id + '"]') || {}).value : current.items_path
|
||||
} : current;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
(function () {
|
||||
var registry = window.pulseRegionTypes;
|
||||
var utils = window.pulseRegionUtils || {};
|
||||
var placeholderUtils = window.placeholderUtils || {};
|
||||
var DEFAULT_STYLE = {
|
||||
font_family: 'Arial',
|
||||
font_size: 32,
|
||||
@@ -114,14 +115,32 @@
|
||||
return current === undefined || current === null ? '' : current;
|
||||
}
|
||||
|
||||
function substituteVariables(html, item) {
|
||||
function substituteVariables(html, item, options) {
|
||||
var source = String(html || '');
|
||||
return source.replace(/\{\{\s*([a-zA-Z0-9_]+)(?:\.([a-zA-Z0-9_.]+))?\s*\}\}/g, function (_match, tokenName, tokenPath) {
|
||||
return source.replace(/\{\{\s*([^{}]+?)\s*\}\}/g, function (_match, expression) {
|
||||
if (!item || typeof item !== 'object') {
|
||||
return '';
|
||||
}
|
||||
var key = tokenPath ? tokenName + '.' + tokenPath : tokenName;
|
||||
return escapeHtml(resolvePath(item, key || ''));
|
||||
if (typeof placeholderUtils.resolvePlaceholderExpression !== 'function' || typeof placeholderUtils.formatPlaceholderValue !== 'function') {
|
||||
return '';
|
||||
}
|
||||
var resolved = placeholderUtils.resolvePlaceholderExpression(item, expression);
|
||||
if (typeof placeholderUtils.isImagePlaceholderExpression === 'function' && placeholderUtils.isImagePlaceholderExpression(expression)) {
|
||||
var imageSource = placeholderUtils.formatPlaceholderValue(resolved);
|
||||
if (!/^(?:https?:\/\/|\/media\/|\/assets\/|\/[^/])/i.test(imageSource)) {
|
||||
return '';
|
||||
}
|
||||
var imageConfig = typeof placeholderUtils.getImagePlaceholderConfig === 'function' ? placeholderUtils.getImagePlaceholderConfig(expression) : {};
|
||||
var imageStyle = options && options.preview && imageConfig.width && imageConfig.height
|
||||
? 'display:block;width:100%;height:100%;object-fit:contain;'
|
||||
: 'display:block;width:auto;height:auto;' + (imageConfig.width ? 'max-width:' + imageConfig.width + 'px;' : '') + (imageConfig.height ? 'max-height:' + imageConfig.height + 'px;' : '');
|
||||
var image = '<img src="' + escapeHtml(imageSource) + '" alt="" style="' + imageStyle + '" />';
|
||||
if (options && options.preview && imageConfig.width && imageConfig.height) {
|
||||
return '<span style="display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;width:' + imageConfig.width + 'px;height:' + imageConfig.height + 'px;border:1px dashed rgba(120,120,120,.75);">' + image + '</span>';
|
||||
}
|
||||
return image;
|
||||
}
|
||||
return escapeHtml(placeholderUtils.formatPlaceholderValue(resolved));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -186,18 +205,8 @@
|
||||
var fontSize = sanitizeFontSize(regionContent.font_size || region.fontSize || defaultStyle.font_size);
|
||||
var fontColor = sanitizeTextColor(regionContent.font_color || region.fontColor || defaultStyle.font_color);
|
||||
var item = getItem(feedId, itemNumber, feeds);
|
||||
var body = item ? substituteVariables(content, item) : '';
|
||||
|
||||
if (!body && item) {
|
||||
var summaryParts = [];
|
||||
if (item.title) {
|
||||
summaryParts.push('<h3>' + escapeHtml(item.title) + '</h3>');
|
||||
}
|
||||
if (item.description) {
|
||||
summaryParts.push('<div>' + sanitizePreviewHtml(item.description) + '</div>');
|
||||
}
|
||||
body = summaryParts.join('');
|
||||
}
|
||||
var hasFeed = String(feedId === undefined || feedId === null ? '' : feedId).trim() !== '';
|
||||
var body = hasFeed ? (item ? substituteVariables(content, item, { preview: true }) : '') : content;
|
||||
|
||||
if (!body) {
|
||||
return '';
|
||||
@@ -206,6 +215,21 @@
|
||||
return renderedBody ? '<div class="template-region rss" style="width:100%;height:100%;overflow:hidden;font-family:' + escapeHtml(fontFamily) + ';font-size:' + fontSize + 'px;color:' + escapeHtml(fontColor) + ';"><div class="template-region-text-scale" style="width:100%;height:100%;overflow:hidden;">' + renderedBody + '</div></div>' : '';
|
||||
}
|
||||
|
||||
function renderPlaceholderInfo(regionId) {
|
||||
if (!window.placeholderInfo) {
|
||||
return '';
|
||||
}
|
||||
return window.placeholderInfo.render(regionId, 'RSS placeholder transforms',
|
||||
'<p class="small text-body-secondary">Placeholders read values from the selected RSS entry. Use the available field chips as the starting point for your message.</p>' +
|
||||
'<h3 class="fs-6 mt-4 fw-normal">Placeholder paths</h3>' +
|
||||
'<p class="small">Nested values use dots. Missing values render as empty text.</p>' +
|
||||
(window.placeholderInfo ? window.placeholderInfo.renderTextTransforms() : '') +
|
||||
'<h3 class="fs-6 mt-4 fw-normal">Date formatting</h3>' +
|
||||
'<p class="small">Use <code>format("MMM D, YYYY")</code> with date fields. Use <code>tz()</code> for a short timezone name and <code>tz_long()</code> for the full timezone name.</p>' +
|
||||
(window.placeholderInfo ? window.placeholderInfo.renderDateFormatTokens() : '') +
|
||||
(window.placeholderInfo ? window.placeholderInfo.renderImageTransform() : ''));
|
||||
}
|
||||
|
||||
function renderEditorCard(context) {
|
||||
var region = context.region;
|
||||
var current = context.current || {};
|
||||
@@ -221,7 +245,7 @@
|
||||
'<span class="chip">RSS</span>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="card-body p-3 d-grid gap-3 pb-0">' +
|
||||
'<div class="card-body p-3 d-grid gap-3">' +
|
||||
'<div class="d-flex justify-content-end">' +
|
||||
'<div class="btn-group btn-group-sm template-editor-size-controls" role="group" aria-label="Editor size controls">' +
|
||||
'<button type="button" class="btn btn-outline-secondary" data-editor-size-action="decrease" aria-label="Decrease editor size">-</button>' +
|
||||
@@ -247,9 +271,8 @@
|
||||
'<input type="hidden" name="region_font_size_' + region.id + '" value="' + escapeHtml(context.fontSize || '') + '" />' +
|
||||
'<input type="hidden" name="region_text_' + region.id + '" value="' + escapeHtml(current.value !== undefined ? current.value : '') + '" />' +
|
||||
'<div class="api-region-placeholder-section">' +
|
||||
'<div class="api-region-placeholder-title">Available placeholders</div>' +
|
||||
'<div class="api-region-placeholder-title api-region-placeholder-title-help d-flex align-items-center gap-2">Available placeholders ' + renderPlaceholderInfo(region.id) + '</div>' +
|
||||
'<div class="d-flex flex-wrap gap-2" data-placeholder-chips>' + (placeholderChips || '<span class="muted slide-image-file">No RSS fields available.</span>') + '</div>' +
|
||||
'<div class="text-body-secondary small mt-1">Placeholder values support transforms, for example <code>{{title.upper()}}</code>, <code>{{title.title()}}</code>, <code>{{title.lower()}}</code>, or <code>{{publishedAt.format("MMM D, YYYY")}}</code>.</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
@@ -291,7 +314,7 @@
|
||||
return fallbackValue;
|
||||
}
|
||||
}())),
|
||||
feed_id: (card.querySelector('select[name="region_rss_feed_id_' + region.id + '"]') || {}).value || current.feed_id,
|
||||
feed_id: card.querySelector('select[name="region_rss_feed_id_' + region.id + '"]') ? card.querySelector('select[name="region_rss_feed_id_' + region.id + '"]').value : current.feed_id,
|
||||
item_number: (card.querySelector('input[name="region_rss_item_number_' + region.id + '"]') || {}).value || current.item_number
|
||||
} : current;
|
||||
|
||||
|
||||
@@ -33,9 +33,9 @@
|
||||
}
|
||||
|
||||
var style = styleOrContext && styleOrContext.style ? styleOrContext.style : styleOrContext || {};
|
||||
var fontFamily = style && style.font_family ? 'font-family:' + escapeHtml(style.font_family) + ';' : '';
|
||||
var color = style && style.font_color ? 'color:' + escapeHtml(style.font_color) + ';' : '';
|
||||
var fontSize = style && style.font_size ? 'font-size:' + Math.max(1, Math.round(Number(style.font_size))) + 'px;' : '';
|
||||
var fontFamily = 'font-family:' + escapeHtml(style && style.font_family || DEFAULT_STYLE.font_family) + ';';
|
||||
var color = 'color:' + escapeHtml(style && style.font_color || DEFAULT_STYLE.font_color) + ';';
|
||||
var fontSize = 'font-size:' + Math.max(1, Math.round(Number(style && style.font_size || DEFAULT_STYLE.font_size))) + 'px;';
|
||||
var width = Math.max(1, Math.round(Number(region && region.width ? region.width : 0) || 1));
|
||||
var height = Math.max(1, Math.round(Number(region && region.height ? region.height : 0) || 1));
|
||||
var wrapperStyle = 'width:' + width + 'px;height:' + height + 'px;overflow:hidden;line-height:1.5;' + fontFamily + fontSize + color;
|
||||
|
||||
@@ -286,6 +286,19 @@
|
||||
return renderTimeDatePlaceholderChips();
|
||||
}
|
||||
|
||||
function renderPlaceholderInfo(regionId) {
|
||||
if (!window.placeholderInfo) {
|
||||
return '';
|
||||
}
|
||||
var placeholders = window.timeDatePlaceholders && typeof window.timeDatePlaceholders.getTokens === 'function'
|
||||
? window.timeDatePlaceholders.getTokens()
|
||||
: [];
|
||||
var table = '<div class="table-responsive"><table class="table table-sm align-middle mb-0"><thead><tr><th>Placeholder</th><th>Output</th><th>Description</th></tr></thead><tbody>' + placeholders.map(function (item) {
|
||||
return '<tr><td><code>{{' + escapeHtml(item.token) + '}}</code></td><td>' + escapeHtml(item.output) + '</td><td>' + escapeHtml(item.description) + '</td></tr>';
|
||||
}).join('') + '</tbody></table></div>';
|
||||
return window.placeholderInfo.render(regionId, 'Time and date placeholders', table);
|
||||
}
|
||||
|
||||
function renderEditorCard(context) {
|
||||
var region = context.region;
|
||||
var current = context.current || {};
|
||||
@@ -317,9 +330,8 @@
|
||||
'<datalist id="region_time_date_timezones_' + region.id + '">' + buildTimezoneOptionsMarkup(timeZone) + '</datalist>' +
|
||||
'</div>' +
|
||||
'<div>' +
|
||||
'<div class="api-region-placeholder-title mb-2">Available placeholders</div>' +
|
||||
'<div class="api-region-placeholder-title api-region-placeholder-title-help d-flex align-items-center gap-2 mb-2">Available placeholders ' + renderPlaceholderInfo(region.id) + '</div>' +
|
||||
'<div class="d-flex flex-wrap gap-2">' + renderPlaceholderChips() + '</div>' +
|
||||
'<div class="text-body-secondary small mt-1">Placeholder values support transforms, for example <code>{{title.upper()}}</code>, <code>{{title.title()}}</code>, or <code>{{title.lower()}}</code>.</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
|
||||
@@ -240,6 +240,21 @@
|
||||
};
|
||||
}
|
||||
|
||||
function renderPlaceholderInfo(regionId) {
|
||||
if (!window.placeholderInfo) {
|
||||
return '';
|
||||
}
|
||||
var body = '<p class="small text-body-secondary">Placeholders read fields from the selected timetable entries. The selected group and display mode determine which entries and fields are available.</p>' +
|
||||
'<h3 class="fs-6 mt-4 fw-normal">Placeholder paths</h3>' +
|
||||
'<p class="small">Use the available field chips. Nested values use dots, and missing values render as empty text.</p>' +
|
||||
window.placeholderInfo.renderTextTransforms() +
|
||||
'<h3 class="fs-6 mt-4 fw-normal">Date and time formatting</h3>' +
|
||||
'<p class="small">Use <code>format("MMM D, YYYY h:mm A")</code> with date fields. Use <code>tz()</code> for a short timezone name and <code>tz_long()</code> for the full timezone name.</p>' +
|
||||
window.placeholderInfo.renderDateFormatTokens() +
|
||||
'';
|
||||
return window.placeholderInfo.render(regionId, 'Timetable placeholder transforms', body);
|
||||
}
|
||||
|
||||
function renderEditorCard(context) {
|
||||
var region = context.region;
|
||||
var current = context.current || {};
|
||||
@@ -298,14 +313,8 @@
|
||||
'</div>' +
|
||||
'<div class="text-body-secondary small">Use the selected group and display mode to choose which timetable entry fields are available.</div>' +
|
||||
'<div class="api-region-placeholder-section schedule-placeholder-section" data-schedule-placeholder-chips>' +
|
||||
'<div class="api-region-placeholder-title">Available placeholders</div>' +
|
||||
'<div class="api-region-placeholder-title api-region-placeholder-title-help d-flex align-items-center gap-2">Available placeholders ' + renderPlaceholderInfo(region.id) + '</div>' +
|
||||
'<div class="d-flex flex-wrap gap-2">' + renderSchedulePlaceholderChips(currentGroup, currentEntries) + '</div>' +
|
||||
'<div class="text-body-secondary small mt-1">Placeholder values support transforms, for example <code>{{title.upper()}}</code>, <code>{{title.title()}}</code>, <code>{{title.lower()}}</code>, <code>{{start.format("MMM D, YYYY h:mm A")}}</code>, <code>{{start.tz()}}</code> for the short zone name, or <code>{{start.tz_long()}}</code> for the full IANA zone name.</div>' +
|
||||
'<details class="mt-2">' +
|
||||
'<summary class="small text-body-secondary">Supported date format tokens</summary>' +
|
||||
'<div class="mt-2">' + renderScheduleFormatTokenTable() + '</div>' +
|
||||
'<div class="text-body-secondary small mt-1">Use these tokens inside <code>.format(...)</code>; for example <code>{{start.format("MMM D, YYYY h:mm A")}}</code>.</div>' +
|
||||
'</details>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// Shared offcanvas renderer for region placeholder documentation.
|
||||
|
||||
(function () {
|
||||
var root = window;
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value === undefined || value === null ? '' : value)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function render(regionId, title, body) {
|
||||
var offcanvasId = String(regionId || 'region') + '-placeholder-info';
|
||||
return '' +
|
||||
'<button type="button" class="btn btn-link btn-sm p-0 text-decoration-none" data-bs-toggle="offcanvas" data-bs-target="#' + offcanvasId + '" aria-controls="' + offcanvasId + '">More info</button>' +
|
||||
'<div class="offcanvas offcanvas-end fw-normal" tabindex="-1" id="' + offcanvasId + '" aria-labelledby="' + offcanvasId + '-label">' +
|
||||
'<div class="offcanvas-header">' +
|
||||
'<h2 class="offcanvas-title fs-5" id="' + offcanvasId + '-label">' + escapeHtml(title) + '</h2>' +
|
||||
'<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>' +
|
||||
'</div>' +
|
||||
'<div class="offcanvas-body">' + body + '</div>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
function renderTextTransforms() {
|
||||
return '<h3 class="fs-6 mt-4 fw-normal">Text transforms</h3>' +
|
||||
'<dl class="small fw-normal">' +
|
||||
'<dt class="fw-normal"><code>upper()</code></dt><dd>Converts text to uppercase.</dd>' +
|
||||
'<dt class="fw-normal"><code>lower()</code></dt><dd>Converts text to lowercase.</dd>' +
|
||||
'<dt class="fw-normal"><code>title()</code></dt><dd>Capitalizes each word.</dd>' +
|
||||
'<dt class="fw-normal"><code>tz()</code></dt><dd>Returns the short timezone name for a date.</dd>' +
|
||||
'<dt class="fw-normal"><code>tz_long()</code></dt><dd>Returns the full timezone name for a date.</dd>' +
|
||||
'</dl>';
|
||||
}
|
||||
|
||||
function renderDateFormatTokens() {
|
||||
return '<h3 class="fs-6 mt-4 fw-normal">Date format tokens</h3>' +
|
||||
'<p class="small">Use the <code>format("...")</code> transform with these tokens. Text inside square brackets is treated as a literal.</p>' +
|
||||
'<div class="table-responsive small"><table class="table table-sm align-middle mb-0"><thead><tr><th>Token</th><th>Meaning</th><th>Example</th></tr></thead><tbody>' +
|
||||
'<tr><td><code>YYYY</code></td><td>Four-digit year</td><td><code>2026</code></td></tr><tr><td><code>YY</code></td><td>Two-digit year</td><td><code>26</code></td></tr>' +
|
||||
'<tr><td><code>MMMM</code></td><td>Full month name</td><td><code>August</code></td></tr><tr><td><code>MMM</code></td><td>Short month name</td><td><code>Aug</code></td></tr>' +
|
||||
'<tr><td><code>MM</code></td><td>Two-digit month</td><td><code>08</code></td></tr><tr><td><code>M</code></td><td>Month number</td><td><code>8</code></td></tr>' +
|
||||
'<tr><td><code>DD</code></td><td>Two-digit day</td><td><code>16</code></td></tr><tr><td><code>D</code></td><td>Day number</td><td><code>16</code></td></tr>' +
|
||||
'<tr><td><code>dddd</code></td><td>Full weekday name</td><td><code>Sunday</code></td></tr><tr><td><code>ddd</code></td><td>Short weekday name</td><td><code>Sun</code></td></tr>' +
|
||||
'<tr><td><code>HH</code> / <code>H</code></td><td>24-hour time</td><td><code>19</code> / <code>7</code></td></tr><tr><td><code>hh</code> / <code>h</code></td><td>12-hour time</td><td><code>07</code> / <code>7</code></td></tr>' +
|
||||
'<tr><td><code>mm</code> / <code>m</code></td><td>Minutes</td><td><code>05</code> / <code>5</code></td></tr><tr><td><code>ss</code> / <code>s</code></td><td>Seconds</td><td><code>09</code> / <code>9</code></td></tr>' +
|
||||
'<tr><td><code>A</code> / <code>a</code></td><td>AM or PM</td><td><code>PM</code> / <code>pm</code></td></tr>' +
|
||||
'</tbody></table></div>';
|
||||
}
|
||||
|
||||
function renderImageTransform() {
|
||||
return '<h3 class="fs-6 mt-4 fw-normal">Image transform</h3>' +
|
||||
'<p class="small">Use <code>image(width, height)</code> to render an image URL inside a proportional bounding box.</p>' +
|
||||
'<ul class="small"><li>The preview shows the width and height boundary.</li><li>The player has no boundary.</li><li>The image keeps its original aspect ratio.</li><li>Both dimensions are required for a bounding box.</li></ul>';
|
||||
}
|
||||
|
||||
root.placeholderInfo = {
|
||||
escapeHtml: escapeHtml,
|
||||
render: render,
|
||||
renderTextTransforms: renderTextTransforms,
|
||||
renderDateFormatTokens: renderDateFormatTokens,
|
||||
renderImageTransform: renderImageTransform
|
||||
};
|
||||
}());
|
||||
@@ -298,6 +298,27 @@
|
||||
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 '';
|
||||
@@ -349,6 +370,8 @@
|
||||
resolvePath: resolvePath,
|
||||
parsePlaceholderExpression: parsePlaceholderExpression,
|
||||
resolvePlaceholderExpression: resolvePlaceholderExpression,
|
||||
isImagePlaceholderExpression: isImagePlaceholderExpression,
|
||||
getImagePlaceholderConfig: getImagePlaceholderConfig,
|
||||
formatPlaceholderValue: formatPlaceholderValue,
|
||||
collectPlaceholderFieldPaths: collectPlaceholderFieldPaths
|
||||
};
|
||||
|
||||
@@ -44,10 +44,6 @@ export function createSlideFormEditorController(options) {
|
||||
return String(value || '');
|
||||
}
|
||||
|
||||
function normalizeEditorMarkup(value) {
|
||||
return String(value === undefined || value === null ? '' : value).trim();
|
||||
}
|
||||
|
||||
function isEmptyRichTextValue(value) {
|
||||
var raw = String(value === undefined || value === null ? '' : value).trim();
|
||||
if (!raw) {
|
||||
@@ -575,7 +571,7 @@ export function createSlideFormEditorController(options) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
source.value = normalizeEditorMarkup(normalizeEditorData(source && source.value !== undefined && source.value !== null ? source.value : (hidden ? hidden.value : '')));
|
||||
source.value = normalizeEditorData(source && source.value !== undefined && source.value !== null ? source.value : (hidden ? hidden.value : ''));
|
||||
|
||||
if (!source.id) {
|
||||
source.id = 'slide-editor-region-' + regionId;
|
||||
|
||||
@@ -198,9 +198,9 @@ export function createSlideFormPreviewHelpers(options) {
|
||||
return module.renderPreview(region, value, style, scale);
|
||||
}
|
||||
|
||||
var fontFamily = style.font_family ? 'font-family:' + escapeHtml(style.font_family) + ';' : '';
|
||||
var color = style.font_color ? 'color:' + escapeHtml(style.font_color) + ';' : '';
|
||||
var fontSize = style.font_size ? 'font-size:' + Math.max(1, Math.round(Number(style.font_size))) + 'px;' : '';
|
||||
var fontFamily = 'font-family:' + escapeHtml(style.font_family || 'Arial') + ';';
|
||||
var color = 'color:' + escapeHtml(style.font_color || '#000000') + ';';
|
||||
var fontSize = 'font-size:' + Math.max(1, Math.round(Number(style.font_size || 32))) + 'px;';
|
||||
var width = Math.max(1, Math.round(Number(region && region.width ? region.width : 0) || 1));
|
||||
var height = Math.max(1, Math.round(Number(region && region.height ? region.height : 0) || 1));
|
||||
var wrapperStyle = 'width:' + width + 'px;height:' + height + 'px;overflow:hidden;' + fontFamily + fontSize + color;
|
||||
|
||||
@@ -123,7 +123,7 @@ export function createSlideFormRegionHelpers(options) {
|
||||
return fields;
|
||||
}
|
||||
|
||||
function buildLimitedPlaceholderChipMarkup(fieldList, emptyLabel) {
|
||||
function buildLimitedPlaceholderChipMarkup(fieldList, emptyLabel, includeTransformHint) {
|
||||
var fields = Array.isArray(fieldList) ? fieldList : [];
|
||||
var visibleFields = fields.slice(0, 8);
|
||||
var overflowFields = fields.slice(visibleFields.length);
|
||||
@@ -137,7 +137,7 @@ export function createSlideFormRegionHelpers(options) {
|
||||
? placeholderChips.renderChip(field)
|
||||
: '<span class="chip">{{' + escapeHtml(field) + '}}</span>';
|
||||
}).join('');
|
||||
var transformHint = '<div class="text-body-secondary small mt-1">Placeholder values support transforms, for example <code>{{title.upper()}}</code>, <code>{{title.title()}}</code>, <code>{{title.lower()}}</code>, <code>{{publishedAt.format("MMM D, YYYY")}}</code>.</div>';
|
||||
var transformHint = includeTransformHint === false ? '' : '<div class="text-body-secondary small mt-1">Placeholder values support transforms, for example <code>{{title.upper()}}</code>, <code>{{title.title()}}</code>, <code>{{title.lower()}}</code>, <code>{{publishedAt.format("MMM D, YYYY")}}</code>.</div>';
|
||||
|
||||
if (!overflowFields.length) {
|
||||
return visibleMarkup + transformHint;
|
||||
@@ -417,7 +417,7 @@ export function createSlideFormRegionHelpers(options) {
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = buildLimitedPlaceholderChipMarkup(getApiFieldList(sourceId, itemsPathOverride), 'No JSON fields available.');
|
||||
container.innerHTML = buildLimitedPlaceholderChipMarkup(getApiFieldList(sourceId, itemsPathOverride), 'No JSON fields available.', false);
|
||||
}
|
||||
|
||||
function updateApiSampleDataPanel(regionId, sourceId, itemNumber, itemsPathOverride) {
|
||||
@@ -561,7 +561,7 @@ export function createSlideFormRegionHelpers(options) {
|
||||
? getRssFieldList(getCurrentRssConfig(region).feed_id).map(function (field) {
|
||||
return '<span class="chip">{{' + escapeHtml(field) + '}}</span>';
|
||||
}).join('')
|
||||
: buildLimitedPlaceholderChipMarkup(getApiFieldList(getCurrentApiConfig(region).source_id, getCurrentApiConfig(region).items_path), 'No JSON fields available.'),
|
||||
: buildLimitedPlaceholderChipMarkup(getApiFieldList(getCurrentApiConfig(region).source_id, getCurrentApiConfig(region).items_path), 'No JSON fields available.', false),
|
||||
placeholderFields: region.region_type === 'api'
|
||||
? getApiFieldList(getCurrentApiConfig(region).source_id, getCurrentApiConfig(region).items_path)
|
||||
: region.region_type === 'rss'
|
||||
@@ -602,7 +602,7 @@ export function createSlideFormRegionHelpers(options) {
|
||||
}).join(''),
|
||||
placeholderChips: region.region_type === 'rss'
|
||||
? buildLimitedPlaceholderChipMarkup(getRssFieldList(rssConfig.feed_id), 'No RSS fields available.')
|
||||
: buildLimitedPlaceholderChipMarkup(getApiFieldList(apiConfig.source_id, apiItemsPath), 'No JSON fields available.'),
|
||||
: buildLimitedPlaceholderChipMarkup(getApiFieldList(apiConfig.source_id, apiItemsPath), 'No JSON fields available.', false),
|
||||
placeholderFields: region.region_type === 'api'
|
||||
? getApiFieldList(apiConfig.source_id, apiItemsPath)
|
||||
: region.region_type === 'rss'
|
||||
|
||||
@@ -654,7 +654,7 @@ import { createSlideFormPreviewHelpers } from '/assets/js/slides/slide-form-prev
|
||||
var content = typeModule && typeof typeModule.renderPreview === 'function'
|
||||
? typeModule.renderPreview(region, previewContext, previewContext)
|
||||
: renderPreviewTextRegion(region, previewContext.value, previewContext.style, scale);
|
||||
if (typeModule && typeof typeModule.renderPreview === 'function') {
|
||||
if (typeModule && typeof typeModule.renderPreview === 'function' && region.region_type !== 'api' && region.region_type !== 'rss') {
|
||||
content = content || renderPreviewTextRegion(region, previewContext.value, previewContext.style, scale);
|
||||
}
|
||||
var selected = region.region_type === 'image'
|
||||
|
||||
@@ -23,6 +23,11 @@ test('slide editor enables server-backed image uploads', () => {
|
||||
assert.ok(slideFormEditorSource.includes('remove_script_host: false'));
|
||||
});
|
||||
|
||||
test('slide editor keeps normal TinyMCE image insertion unchanged', () => {
|
||||
assert.doesNotMatch(slideFormEditorSource, /bindPlaceholderImageSupport|placeholderFallbackSource/);
|
||||
assert.ok(slideFormEditorSource.includes('images_upload_handler: uploadEditorImage'));
|
||||
});
|
||||
|
||||
test('slide editor inserts tables with zero padding and spacing by default', () => {
|
||||
assert.ok(slideFormEditorSource.includes("table_default_attributes: {"));
|
||||
assert.ok(slideFormEditorSource.includes("cellpadding: '0'"));
|
||||
|
||||
Reference in New Issue
Block a user