Save worktree changes

This commit is contained in:
2026-07-25 02:29:19 +01:00
parent 8d3b7d557b
commit db9d718cd8
170 changed files with 11719 additions and 3414 deletions
+511 -20
View File
@@ -25,6 +25,8 @@ import {
import { createTemplateSelectorLockController } from '/assets/js/slides/slide-form-template-lock.js';
(function () {
var DEFAULT_FONT_SIZE = 32;
var dataElement = document.getElementById('slide-editor-data');
if (!dataElement) {
return;
@@ -38,6 +40,8 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
}
var templates = Array.isArray(slideEditorData.templates) ? slideEditorData.templates : [];
var rssFeeds = Array.isArray(slideEditorData.rssFeeds) ? slideEditorData.rssFeeds : [];
var apiSources = Array.isArray(slideEditorData.apiSources) ? slideEditorData.apiSources : [];
var existingTemplateId = slideEditorData.existingTemplateId !== undefined ? slideEditorData.existingTemplateId : null;
var existingContent = slideEditorData.existingContent || {};
var templateSelect = document.getElementById('template-select');
@@ -94,7 +98,7 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
function getCurrentFontSizeValue() {
var value = fontSizeCommand.value;
if (value === null || value === undefined || value === '') {
return '';
return String(DEFAULT_FONT_SIZE);
}
var parsed = Math.round(Number(String(value).replace(/[^0-9.]/g, '')));
@@ -118,6 +122,7 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
view.element.inputMode = 'numeric';
view.element.autocomplete = 'off';
view.element.spellcheck = false;
view.element.style.width = '3.5em';
syncValue();
});
@@ -406,6 +411,15 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
return '<iframe class="slide-preview-webpage-frame" src="' + escapeHtml(src) + '" title="Webpage preview" loading="eager" referrerpolicy="no-referrer" scrolling="no"></iframe>';
}
function renderPreviewRtmpRegion(value, disableAudio) {
var src = String(value || '').trim();
var label = src ? 'RTMP' : 'RTMP stream';
if (disableAudio) {
label += ' (muted)';
}
return '<div class="slide-preview-placeholder">' + label + '</div>';
}
function renderPreviewHtmlRegion(value) {
var html = String(value || '').trim();
if (!html) {
@@ -434,19 +448,286 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
}
function normalizeFontSizeValue(value) {
var size = Math.max(1, Math.round(Number(String(value || '').replace(/[^0-9.]/g, ''))));
return size ? String(size) : '';
var raw = String(value || '').trim().toLowerCase();
if (/^\d+(?:\.\d+)?px$/.test(raw)) {
return String(Math.max(1, Math.round(Number(raw.replace(/px$/, '')))));
}
if (/^\d+(?:\.\d+)?$/.test(raw)) {
return String(Math.max(1, Math.round(Number(raw))));
}
return '';
}
function syncEditorFontSizeHidden(regionId, editor) {
var fontSizeHidden = getEditorFontSizeHiddenInput(regionId);
if (!fontSizeHidden || !editor) {
return;
}
var fontSizeCommand = editor.commands.get('fontSize');
fontSizeHidden.value = normalizeFontSizeValue(fontSizeCommand && fontSizeCommand.value) || String(DEFAULT_FONT_SIZE);
}
function getCurrentTextStyle(region) {
var current = existingContent[region.region_key] || {};
return {
font_family: String(current.font_family || region.font_family || 'Arial').trim() || 'Arial',
font_size: Math.max(8, Number(current.font_size || 24)),
font_size: Math.max(8, Number(current.font_size || DEFAULT_FONT_SIZE)),
font_color: String(current.font_color || region.font_color || '#000000').trim() || '#000000'
};
}
function getRssFeedById(feedId) {
var normalizedId = Number(feedId || 0);
return rssFeeds.find(function (feed) {
return Number(feed.id) === normalizedId;
}) || null;
}
function getRssFieldList(feedId) {
var feed = getRssFeedById(feedId);
var sampleItem = feed && Array.isArray(feed.items) ? feed.items[0] : null;
var sampleJson = sampleItem && sampleItem.itemJson && typeof sampleItem.itemJson === 'object' ? sampleItem.itemJson : null;
function walkFields(value, prefix, output) {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return;
}
Object.keys(value).forEach(function (key) {
if (key === 'rawXml') {
return;
}
var nextPath = prefix ? prefix + '.' + key : key;
var nextValue = value[key];
if (nextValue && typeof nextValue === 'object' && !Array.isArray(nextValue)) {
walkFields(nextValue, nextPath, output);
return;
}
if (output.indexOf(nextPath) === -1) {
output.push(nextPath);
}
});
}
var fields = [];
walkFields(sampleJson, '', fields);
return fields;
}
function updatePlaceholderChipList(card, fieldList, emptyLabel) {
if (!card) {
return;
}
var container = card.querySelector('[data-placeholder-chips]');
if (!container) {
return;
}
var chips = Array.isArray(fieldList) ? fieldList.map(function (field) {
return '<span class="chip">{{' + escapeHtml(field) + '}}</span>';
}).join('') : '';
container.innerHTML = chips || '<span class="muted slide-image-file">' + escapeHtml(emptyLabel) + '</span>';
}
function updateRssPlaceholderChips(regionId, feedId) {
var card = templateFields.querySelector('[data-region-id="' + regionId + '"]');
updatePlaceholderChipList(card, getRssFieldList(feedId), 'No RSS fields available.');
}
function getCurrentRssConfig(region) {
var current = existingContent[region.region_key] || {};
var parsedItemNumber = Math.max(1, Number(current.item_number || 1));
return {
feed_id: current.feed_id === undefined || current.feed_id === null || current.feed_id === '' ? '' : Number(current.feed_id),
item_number: Number.isFinite(parsedItemNumber) && parsedItemNumber > 0 ? parsedItemNumber : 1,
value: current.value !== undefined ? current.value : ''
};
}
function resolveRssPath(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 normalizeRssVariableName(value) {
var next = String(value || '').trim();
return next || 'item';
}
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) {
if (!item || typeof item !== 'object') {
return '';
}
var key = tokenName === 'item' && tokenPath ? tokenPath : tokenName;
return escapeHtml(resolveRssPath(item, key || ''));
});
}
function getRssItem(region, rssConfig) {
var feed = getRssFeedById(rssConfig.feed_id);
var items = feed && Array.isArray(feed.items) ? feed.items : [];
var parsedItemNumber = Math.max(1, Number(rssConfig.item_number || 1));
var index = Number.isFinite(parsedItemNumber) && parsedItemNumber > 0 ? parsedItemNumber - 1 : 0;
return items[index] || null;
}
function getRssPreviewFallback(item) {
if (!item || typeof item !== 'object') {
return '<div class="slide-preview-placeholder">RSS item</div>';
}
var title = String(item.title || '').trim();
var description = String(item.description || '').trim();
var summary = [];
if (title) {
summary.push('<h3>' + escapeHtml(title) + '</h3>');
}
if (description) {
summary.push('<p>' + sanitizePreviewHtml(description) + '</p>');
}
if (!summary.length) {
return '<div class="slide-preview-placeholder">RSS item</div>';
}
return summary.join('');
}
function getApiSourceById(sourceId) {
var normalizedId = Number(sourceId || 0);
return apiSources.find(function (source) {
return Number(source.id) === normalizedId;
}) || null;
}
function getApiSourceItems(sourceId) {
var source = getApiSourceById(sourceId);
var responseJson = source && source.responseJson && typeof source.responseJson === 'object' ? source.responseJson : null;
if (Array.isArray(responseJson)) {
return responseJson;
}
if (responseJson && Array.isArray(responseJson.items)) {
return responseJson.items;
}
if (responseJson && Array.isArray(responseJson.results)) {
return responseJson.results;
}
if (responseJson && Array.isArray(responseJson.data)) {
return responseJson.data;
}
return responseJson ? [responseJson] : [];
}
function getApiFieldList(sourceId) {
var sampleItem = getApiSourceItems(sourceId)[0] || null;
function walkFields(value, prefix, output) {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return;
}
Object.keys(value).forEach(function (key) {
var nextPath = prefix ? prefix + '.' + key : key;
var nextValue = value[key];
if (nextValue && typeof nextValue === 'object' && !Array.isArray(nextValue)) {
walkFields(nextValue, nextPath, output);
return;
}
if (output.indexOf(nextPath) === -1) {
output.push(nextPath);
}
});
}
var fields = [];
walkFields(sampleItem && typeof sampleItem === 'object' ? sampleItem : null, '', fields);
return fields;
}
function updateApiPlaceholderChips(regionId, sourceId) {
var card = templateFields.querySelector('[data-region-id="' + regionId + '"]');
updatePlaceholderChipList(card, getApiFieldList(sourceId), 'No JSON fields available.');
}
function getCurrentApiConfig(region) {
var current = existingContent[region.region_key] || {};
var parsedItemNumber = Math.max(1, Number(current.item_number || 1));
return {
source_id: current.source_id === undefined || current.source_id === null || current.source_id === '' ? '' : Number(current.source_id),
item_number: Number.isFinite(parsedItemNumber) && parsedItemNumber > 0 ? parsedItemNumber : 1,
value: current.value !== undefined ? current.value : ''
};
}
function resolveApiPath(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 substituteApiVariables(html, item) {
var source = String(html || '');
return source.replace(/\{\{\s*([a-zA-Z0-9_]+)(?:\.([a-zA-Z0-9_.]+))?\s*\}\}/g, function (_match, tokenName, tokenPath) {
if (!item || typeof item !== 'object') {
return '';
}
var key = tokenName === 'item' && tokenPath ? tokenPath : tokenName;
return escapeHtml(resolveApiPath(item, key || ''));
});
}
function getApiItem(sourceId, itemNumber) {
var items = getApiSourceItems(sourceId);
var parsedItemNumber = Math.max(1, Number(itemNumber || 1));
var index = Number.isFinite(parsedItemNumber) && parsedItemNumber > 0 ? parsedItemNumber - 1 : 0;
return items[index] || null;
}
function getApiPreviewFallback(item) {
if (!item || typeof item !== 'object') {
return '<div class="slide-preview-placeholder">API item</div>';
}
var title = String(item.title || item.name || '').trim();
var description = String(item.description || item.summary || item.text || '').trim();
var summary = [];
if (title) {
summary.push('<h3>' + escapeHtml(title) + '</h3>');
}
if (description) {
summary.push('<p>' + sanitizePreviewHtml(description) + '</p>');
}
if (!summary.length) {
return '<div class="slide-preview-placeholder">API item</div>';
}
return summary.join('');
}
function getTextStyleFromCard(_card, region) {
return getCurrentTextStyle(region);
}
@@ -724,10 +1005,25 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
var imageHidden = card && card.querySelector('input[type="hidden"][name="existing_region_image_' + region.id + '"]');
var imageFileInput = card && card.querySelector('input[type="file"][name="region_image_' + region.id + '"]');
var webpageInput = card && card.querySelector('input[type="url"][name="region_webpage_' + region.id + '"]');
var rtmpInput = card && card.querySelector('input[type="url"][name="region_rtmp_' + region.id + '"]');
var disableAudioInput = card && card.querySelector('input[type="checkbox"][name="region_disable_audio_' + region.id + '"]');
var rssFeedInput = card && card.querySelector('select[name="region_rss_feed_id_' + region.id + '"]');
var rssItemInput = card && card.querySelector('input[name="region_rss_item_number_' + region.id + '"]');
var apiSourceInput = card && card.querySelector('select[name="region_api_source_id_' + region.id + '"]');
var apiItemInput = card && card.querySelector('input[name="region_api_item_number_' + region.id + '"]');
var existingRtmp = existingContent[region.region_key] || {};
var existingRss = existingContent[region.region_key] || {};
var existingApi = existingContent[region.region_key] || {};
var value = region.region_type === 'image'
? ((imageFileInput && imageFileInput.dataset.previewUrl) ? imageFileInput.dataset.previewUrl : (imageHidden ? imageHidden.value : ''))
: region.region_type === 'webpage'
? (webpageInput ? webpageInput.value : '')
: region.region_type === 'rtmp'
? (rtmpInput ? rtmpInput.value : '')
: region.region_type === 'rss'
? (hiddenInput ? hiddenInput.value : '')
: region.region_type === 'api'
? (hiddenInput ? hiddenInput.value : '')
: region.region_type === 'html'
? (htmlInput ? htmlInput.value : '')
: (hiddenInput ? hiddenInput.value : '');
@@ -735,6 +1031,8 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
? null
: region.region_type === 'webpage'
? null
: region.region_type === 'rtmp'
? null
: region.region_type === 'html'
? null
: getTextStyleFromCard(card, region);
@@ -742,6 +1040,23 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
? (value ? '<img class="slide-preview-image" src="' + escapeHtml(value) + '" alt="" />' : '<div class="slide-preview-placeholder">Image</div>')
: region.region_type === 'webpage'
? renderPreviewWebpageRegion(value)
: region.region_type === 'rtmp'
? renderPreviewRtmpRegion(value, disableAudioInput ? disableAudioInput.checked : (existingRtmp.disable_audio === undefined ? true : Boolean(existingRtmp.disable_audio)))
: region.region_type === 'rss'
? (function () {
var item = getRssItem(region, {
feed_id: rssFeedInput ? rssFeedInput.value : existingRss.feed_id,
item_number: rssItemInput ? rssItemInput.value : existingRss.item_number
});
var rendered = substituteRssVariables(value, item);
return renderPreviewTextRegion(region, rendered ? rendered : getRssPreviewFallback(item), style, scale);
}())
: region.region_type === 'api'
? (function () {
var item = getApiItem(apiSourceInput ? apiSourceInput.value : existingApi.source_id, apiItemInput ? apiItemInput.value : existingApi.item_number);
var rendered = substituteApiVariables(value, item);
return renderPreviewTextRegion(region, rendered ? rendered : getApiPreviewFallback(item), style, scale);
}())
: region.region_type === 'html'
? renderPreviewHtmlRegion(value)
: renderPreviewTextRegion(region, value, style, scale);
@@ -749,6 +1064,12 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
? ' slide-preview-image-region'
: region.region_type === 'webpage'
? ' slide-preview-webpage-region'
: region.region_type === 'rtmp'
? ' slide-preview-rtmp-region'
: region.region_type === 'rss'
? ' slide-preview-rss-region'
: region.region_type === 'api'
? ' slide-preview-api-region'
: region.region_type === 'html'
? ' slide-preview-html-region'
: ' slide-preview-text-region';
@@ -779,6 +1100,96 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
'</div>';
}
function renderRssRegion(region) {
var current = existingContent[region.region_key] || {};
var config = getCurrentRssConfig(region);
var fieldList = getRssFieldList(config.feed_id);
var placeholderChips = fieldList.map(function (field) {
return '<span class="chip">{{' + escapeHtml(field) + '}}</span>';
}).join('');
var feedOptions = rssFeeds.map(function (feed) {
var selected = Number(feed.id) === Number(config.feed_id) ? ' selected' : '';
return '<option value="' + escapeHtml(feed.id) + '"' + selected + '>' + escapeHtml(feed.name || ('Feed ' + feed.id)) + '</option>';
}).join('');
return '' +
'<div class="card card-outline card-secondary admin-form-card template-field-card mb-3" data-region-id="' + region.id + '">' +
'<div class="card-header template-field-head">' +
'<strong>' + escapeHtml(region.label) + '</strong>' +
'<div class="template-field-actions">' +
'<button type="button" class="btn btn-sm btn-outline-secondary template-editor-toggle" data-toggle-editor-height="' + region.id + '">Expand</button>' +
'<span class="chip">RSS</span>' +
'</div>' +
'</div>' +
'<div class="card-body p-3 d-grid gap-3">' +
'<div class="ckeditor-holder" data-region-id="' + region.id + '">' +
'<textarea class="ckeditor-source" rows="10">' + escapeHtml(current.value !== undefined ? current.value : '') + '</textarea>' +
'</div>' +
'<div class="row g-3 align-items-end">' +
'<div class="col-12 col-md-8">' +
'<label class="form-label" for="region_rss_feed_id_' + region.id + '">RSS feed</label>' +
'<select name="region_rss_feed_id_' + region.id + '" class="form-select">' +
'<option value="">Select a feed</option>' +
feedOptions +
'</select>' +
'</div>' +
'<div class="col-6 col-md-4">' +
'<label class="form-label" for="region_rss_item_number_' + region.id + '">Entry number</label>' +
'<input type="number" min="1" step="1" name="region_rss_item_number_' + region.id + '" class="form-control" value="' + escapeHtml(config.item_number) + '" />' +
'</div>' +
'</div>' +
'<input type="hidden" name="region_font_size_' + region.id + '" value="' + escapeHtml(getCurrentTextStyle(region).font_size) + '" />' +
'<input type="hidden" name="region_text_' + region.id + '" value="' + escapeHtml(current.value !== undefined ? current.value : '') + '" />' +
'<div class="muted slide-image-file">Use placeholders like {{title}} or {{link}}. Available placeholders:</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>' +
'</div>';
}
function renderApiRegion(region) {
var current = existingContent[region.region_key] || {};
var config = getCurrentApiConfig(region);
var fieldList = getApiFieldList(config.source_id);
var placeholderChips = fieldList.map(function (field) {
return '<span class="chip">{{' + escapeHtml(field) + '}}</span>';
}).join('');
var sourceOptions = apiSources.map(function (source) {
var selected = Number(source.id) === Number(config.source_id) ? ' selected' : '';
return '<option value="' + escapeHtml(source.id) + '"' + selected + '>' + escapeHtml(source.name || ('Source ' + source.id)) + '</option>';
}).join('');
return '' +
'<div class="card card-outline card-secondary admin-form-card template-field-card mb-3" data-region-id="' + region.id + '">' +
'<div class="card-header template-field-head">' +
'<strong>' + escapeHtml(region.label) + '</strong>' +
'<div class="template-field-actions">' +
'<button type="button" class="btn btn-sm btn-outline-secondary template-editor-toggle" data-toggle-editor-height="' + region.id + '">Expand</button>' +
'<span class="chip">API</span>' +
'</div>' +
'</div>' +
'<div class="card-body p-3 d-grid gap-3">' +
'<div class="ckeditor-holder" data-region-id="' + region.id + '">' +
'<textarea class="ckeditor-source" rows="10">' + escapeHtml(current.value !== undefined ? current.value : '') + '</textarea>' +
'</div>' +
'<div class="row g-3 align-items-end">' +
'<div class="col-12 col-md-8">' +
'<label class="form-label" for="region_api_source_id_' + region.id + '">API source</label>' +
'<select name="region_api_source_id_' + region.id + '" class="form-select">' +
'<option value="">Select a source</option>' +
sourceOptions +
'</select>' +
'</div>' +
'<div class="col-6 col-md-4">' +
'<label class="form-label" for="region_api_item_number_' + region.id + '">Item number</label>' +
'<input type="number" min="1" step="1" name="region_api_item_number_' + region.id + '" class="form-control" value="' + escapeHtml(config.item_number) + '" />' +
'</div>' +
'</div>' +
'<input type="hidden" name="region_font_size_' + region.id + '" value="' + escapeHtml(getCurrentTextStyle(region).font_size) + '" />' +
'<input type="hidden" name="region_text_' + region.id + '" value="' + escapeHtml(current.value !== undefined ? current.value : '') + '" />' +
'<div class="muted slide-image-file">Use placeholders like {{title}} or {{description}}. Available placeholders:</div>' +
'<div class="d-flex flex-wrap gap-2" data-placeholder-chips>' + (placeholderChips || '<span class="muted slide-image-file">No JSON fields available.</span>') + '</div>' +
'</div>' +
'</div>';
}
function renderWebpageRegion(region) {
var current = getCurrentRegionValue(region);
return '' +
@@ -796,6 +1207,29 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
'</div>';
}
function renderRtmpRegion(region) {
var current = existingContent[region.region_key] || {};
var url = String(current.value || '').trim();
var disableAudio = current.disable_audio === undefined ? true : Boolean(current.disable_audio);
return '' +
'<div class="card card-outline card-secondary admin-form-card template-field-card mb-3" data-region-id="' + region.id + '">' +
'<div class="card-header template-field-head">' +
'<strong>' + escapeHtml(region.label) + '</strong>' +
'<span class="chip">RTMP</span>' +
'</div>' +
'<div class="card-body p-3 d-grid gap-3">' +
'<label style="display:block;">RTMP URL' +
'<input type="url" name="region_rtmp_' + region.id + '" class="form-control" value="' + escapeHtml(url) + '" placeholder="rtmp://example.com/live/stream" />' +
'</label>' +
'<label class="form-check m-0">' +
'<input class="form-check-input" type="checkbox" name="region_disable_audio_' + region.id + '" value="1"' + (disableAudio ? ' checked' : '') + ' />' +
'<span class="form-check-label">Disable audio</span>' +
'</label>' +
'<div class="muted slide-image-file">Player controls stay hidden and the stream is not interactive.</div>' +
'</div>' +
'</div>';
}
function renderHtmlRegion(region) {
var current = getCurrentRegionValue(region);
return '' +
@@ -851,6 +1285,15 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
if (region.region_type === 'webpage') {
return renderWebpageRegion(region);
}
if (region.region_type === 'rtmp') {
return renderRtmpRegion(region);
}
if (region.region_type === 'rss') {
return renderRssRegion(region);
}
if (region.region_type === 'api') {
return renderApiRegion(region);
}
if (region.region_type === 'html') {
return renderHtmlRegion(region);
}
@@ -869,8 +1312,6 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
'undo',
'redo',
'|',
'heading',
'|',
'fontSizeInput',
'fontFamily',
'fontColor',
@@ -904,7 +1345,6 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
FontFamily,
FontSize,
FontSizeInputUI,
Heading,
HorizontalLine,
Indent,
IndentBlock,
@@ -922,29 +1362,19 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
options: [20, 24, 28, 32, 36, 40, 44],
supportAllValues: true
},
heading: {
options: [
{ model: 'paragraph', title: 'Paragraph', class: 'ck-heading_paragraph' },
{ model: 'heading1', view: 'h1', title: 'Heading 1', class: 'ck-heading_heading1' },
{ model: 'heading2', view: 'h2', title: 'Heading 2', class: 'ck-heading_heading2' },
{ model: 'heading3', view: 'h3', title: 'Heading 3', class: 'ck-heading_heading3' },
{ model: 'heading4', view: 'h4', title: 'Heading 4', class: 'ck-heading_heading4' }
]
},
initialData: normalizeEditorData((source && source.value) || (hidden ? hidden.value : ''))
};
ClassicEditor.create(source || holder, editorConfig).then(function (editor) {
editorInstances.set(regionId, editor);
var fontSizeHidden = getEditorFontSizeHiddenInput(regionId);
var fontSizeCommand = editor.commands.get('fontSize');
if (hidden) {
hidden.value = editor.getData();
}
if (fontSizeHidden && fontSizeCommand) {
fontSizeHidden.value = normalizeFontSizeValue(fontSizeCommand.value) || fontSizeHidden.value;
syncEditorFontSizeHidden(regionId, editor);
if (fontSizeCommand) {
fontSizeCommand.on('change:value', function () {
fontSizeHidden.value = normalizeFontSizeValue(fontSizeCommand.value) || fontSizeHidden.value;
syncEditorFontSizeHidden(regionId, editor);
});
}
editor.model.document.on('change:data', function () {
@@ -990,6 +1420,42 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
requestPreviewRender();
});
});
templateFields.querySelectorAll('select[name^="region_rss_feed_id_"], input[name^="region_rss_item_number_"], select[name^="region_api_source_id_"], input[name^="region_api_item_number_"]').forEach(function (input) {
input.addEventListener('input', function () {
var regionId = input.name.replace(/^region_(?:rss_feed_id|rss_item_number|api_source_id|api_item_number)_/, '');
if (input.name.indexOf('region_rss_') === 0) {
var rssFeed = input.name.indexOf('region_rss_feed_id_') === 0 ? input.value : (templateFields.querySelector('select[name="region_rss_feed_id_' + regionId + '"]') || {}).value;
updateRssPlaceholderChips(regionId, rssFeed);
}
if (input.name.indexOf('region_api_') === 0) {
var apiSource = input.name.indexOf('region_api_source_id_') === 0 ? input.value : (templateFields.querySelector('select[name="region_api_source_id_' + regionId + '"]') || {}).value;
updateApiPlaceholderChips(regionId, apiSource);
}
templateSelectorLock.markEdited();
requestPreviewRender();
});
input.addEventListener('change', function () {
var regionId = input.name.replace(/^region_(?:rss_feed_id|rss_item_number|api_source_id|api_item_number)_/, '');
if (input.name.indexOf('region_rss_') === 0) {
var rssFeed = input.name.indexOf('region_rss_feed_id_') === 0 ? input.value : (templateFields.querySelector('select[name="region_rss_feed_id_' + regionId + '"]') || {}).value;
updateRssPlaceholderChips(regionId, rssFeed);
}
if (input.name.indexOf('region_api_') === 0) {
var apiSource = input.name.indexOf('region_api_source_id_') === 0 ? input.value : (templateFields.querySelector('select[name="region_api_source_id_' + regionId + '"]') || {}).value;
updateApiPlaceholderChips(regionId, apiSource);
}
templateSelectorLock.markEdited();
requestPreviewRender();
});
});
if (existingTemplateId) {
templateSelectorLock.arm();
templateSelectorLock.markEdited();
requestPreviewRender();
return;
}
window.requestAnimationFrame(function () {
templateSelectorLock.arm();
});
@@ -1001,6 +1467,8 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
return;
}
event.preventDefault();
var slideFormUrl = new URL(slideForm.action, window.location.href);
var isCreateForm = slideFormUrl.pathname === '/slides';
var submitterValue = event.submitter && event.submitter.name === 'save_action'
? String(event.submitter.value || '').trim().toLowerCase()
: '';
@@ -1011,6 +1479,7 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
if (!editor || !hidden) {
return Promise.resolve();
}
syncEditorFontSizeHidden(regionId, editor);
hidden.value = editor.getData();
return Promise.resolve();
});
@@ -1043,6 +1512,20 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
return;
}
if (isCreateForm) {
try {
if (response.url) {
window.location.replace(response.url);
return;
}
} catch (_error) {
// Fall through and show a toast if the redirect URL cannot be resolved.
}
slideForm.dataset.dirty = 'false';
showToast('Saved slide.', 'success');
return;
}
var responseText = await response.text();
var savedMessage = '';
try {
@@ -1057,6 +1540,13 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
showToast(savedMessage || 'Saved slide.', 'success');
slideForm.dataset.dirty = 'false';
} catch (error) {
if (typeof showToast === 'function') {
var variant = Number(error && error.status) >= 400 && Number(error && error.status) < 500 ? 'warning' : 'danger';
showToast(error && error.message ? error.message : 'Unable to save slide.', variant);
} else {
window.alert(error && error.message ? error.message : 'Unable to save slide.');
}
} finally {
submitting = false;
}
@@ -1105,3 +1595,4 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
}
});
})();