Release v2.2.0
This commit is contained in:
+82
-5
@@ -1,3 +1,5 @@
|
||||
// Slide data access helpers, including rich-text normalization and payload building.
|
||||
|
||||
const { fetchTemplateById } = require('./templates');
|
||||
const { parseJsonSafe } = require('./utils');
|
||||
|
||||
@@ -25,6 +27,25 @@ function sanitizeRichText(html) {
|
||||
});
|
||||
}
|
||||
|
||||
function normalizePlainText(value) {
|
||||
return String(value === undefined || value === null ? '' : value)
|
||||
.replace(/<\s*br\s*\/?\s*>/gi, '\n')
|
||||
.replace(/<[^>]*>/g, '')
|
||||
.replace(/ /gi, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
function stripEditorOnlyMarkup(value) {
|
||||
return String(value || '')
|
||||
.replace(/<pre[^>]*class="[^"]*api-region-sample-preview[^"]*"[^>]*>[\s\S]*?<\/pre>/gi, '')
|
||||
.replace(/<details[^>]*class="[^"]*api-region-sample-accordion[^"]*"[^>]*>[\s\S]*?<\/details>/gi, '')
|
||||
.trim();
|
||||
}
|
||||
|
||||
function normalizeEditorMarkup(value) {
|
||||
return String(value === undefined || value === null ? '' : value).trim();
|
||||
}
|
||||
|
||||
async function fetchSlideById(pool, id) {
|
||||
const [slides] = await pool.query(`
|
||||
SELECT s.id, s.title, s.template_id, s.content_json, s.thumbnail_path, s.created_at, s.modified_at,
|
||||
@@ -53,6 +74,14 @@ function getFilesByField(files) {
|
||||
return map;
|
||||
}
|
||||
|
||||
function getSubmittedValue(body, key, fallback) {
|
||||
if (body && Object.prototype.hasOwnProperty.call(body, key)) {
|
||||
return String(body[key] || '').trim();
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function sanitizeTextColor(value, fallback) {
|
||||
const raw = String(value || '').trim();
|
||||
if (/^#[0-9a-fA-F]{6}$/.test(raw) || /^#[0-9a-fA-F]{3}$/.test(raw)) {
|
||||
@@ -88,9 +117,10 @@ function buildTemplateContent(template, body, filesByField, existingContent) {
|
||||
if (region.region_type === 'image') {
|
||||
const uploaded = filesByField[`region_image_${region.id}`];
|
||||
const existing = body[`existing_region_image_${region.id}`];
|
||||
const current = existingContent && existingContent[region.region_key] ? existingContent[region.region_key].value : '';
|
||||
content[region.region_key] = {
|
||||
type: 'image',
|
||||
value: uploaded ? `/media/uploads/${uploaded.filename}` : String(existing || '').trim() || ((existingContent && existingContent[region.region_key]) ? existingContent[region.region_key].value : '')
|
||||
value: uploaded ? `/media/uploads/${uploaded.filename}` : getSubmittedValue(body, `existing_region_image_${region.id}`, current)
|
||||
};
|
||||
} else if (region.region_type === 'video') {
|
||||
const uploaded = filesByField[`region_video_${region.id}`];
|
||||
@@ -99,9 +129,10 @@ function buildTemplateContent(template, body, filesByField, existingContent) {
|
||||
const existingDuration = existingContent && existingContent[region.region_key] ? Number(existingContent[region.region_key].duration_seconds || 0) : 0;
|
||||
const parsedDuration = Number(durationValue || existingDuration || 0);
|
||||
const normalizedDuration = Math.round(parsedDuration * 1000) / 1000;
|
||||
const current = existingContent && existingContent[region.region_key] ? existingContent[region.region_key].value : '';
|
||||
content[region.region_key] = {
|
||||
type: 'video',
|
||||
value: uploaded ? `/media/uploads/${uploaded.filename}` : String(existing || '').trim() || ((existingContent && existingContent[region.region_key]) ? existingContent[region.region_key].value : ''),
|
||||
value: uploaded ? `/media/uploads/${uploaded.filename}` : getSubmittedValue(body, `existing_region_video_${region.id}`, current),
|
||||
duration_seconds: Number.isFinite(normalizedDuration) && normalizedDuration > 0 ? normalizedDuration : null
|
||||
};
|
||||
} else if (region.region_type === 'webpage') {
|
||||
@@ -126,6 +157,15 @@ function buildTemplateContent(template, body, filesByField, existingContent) {
|
||||
type: 'html',
|
||||
value: submitted === undefined ? current : String(submitted || '')
|
||||
};
|
||||
} else if (region.region_type === 'time-date') {
|
||||
const submitted = body[`region_text_${region.id}`];
|
||||
const current = existingContent && existingContent[region.region_key] ? existingContent[region.region_key] : {};
|
||||
const timezoneValue = body[`region_timezone_${region.id}`];
|
||||
content[region.region_key] = {
|
||||
type: 'time-date',
|
||||
value: submitted === undefined ? String(current.value !== undefined ? current.value : current.text !== undefined ? current.text : '') : String(submitted || ''),
|
||||
timezone: timezoneValue === undefined || timezoneValue === null ? String(current.timezone || current.time_zone || '') : String(timezoneValue || '').trim()
|
||||
};
|
||||
} else if (region.region_type === 'rss') {
|
||||
const submitted = body[`region_text_${region.id}`];
|
||||
const current = existingContent && existingContent[region.region_key] ? existingContent[region.region_key] : {};
|
||||
@@ -135,7 +175,7 @@ function buildTemplateContent(template, body, filesByField, existingContent) {
|
||||
const parsedItemNumber = Math.max(1, Number(itemNumber || current.item_number || 1));
|
||||
content[region.region_key] = {
|
||||
type: 'rss',
|
||||
value: submitted === undefined ? String(current.value || '') : String(submitted || ''),
|
||||
value: stripEditorOnlyMarkup(normalizeEditorMarkup(submitted === undefined ? String(current.value || '') : String(submitted || ''))),
|
||||
feed_id: feedId === undefined || feedId === null || feedId === '' ? (current.feed_id || null) : Number(feedId),
|
||||
item_number: Number.isFinite(parsedItemNumber) && parsedItemNumber > 0 ? parsedItemNumber : 1,
|
||||
variable_name: 'item',
|
||||
@@ -149,24 +189,61 @@ function buildTemplateContent(template, body, filesByField, existingContent) {
|
||||
const style = getTextRegionStyle(body, region, existingContent);
|
||||
const sourceId = body[`region_api_source_id_${region.id}`];
|
||||
const itemNumber = body[`region_api_item_number_${region.id}`];
|
||||
const itemsPath = body[`region_api_items_path_${region.id}`];
|
||||
const parsedItemNumber = Math.max(1, Number(itemNumber || current.item_number || 1));
|
||||
content[region.region_key] = {
|
||||
type: 'api',
|
||||
value: submitted === undefined ? String(current.value || '') : String(submitted || ''),
|
||||
value: stripEditorOnlyMarkup(normalizeEditorMarkup(submitted === undefined ? String(current.value || '') : String(submitted || ''))),
|
||||
source_id: sourceId === undefined || sourceId === null || sourceId === '' ? (current.source_id || 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',
|
||||
font_family: style.font_family,
|
||||
font_size: style.font_size,
|
||||
font_color: style.font_color
|
||||
};
|
||||
} else if (!['text', 'image', 'video', 'webpage', 'html', 'rtmp', 'rss', 'api'].includes(String(region.region_type || '').trim())) {
|
||||
const current = existingContent && existingContent[region.region_key] && typeof existingContent[region.region_key] === 'object' ? existingContent[region.region_key] : {};
|
||||
const suffix = '_' + region.id;
|
||||
const generic = {};
|
||||
|
||||
Object.keys(body || {}).forEach((key) => {
|
||||
if (!key.startsWith('region_') || !key.endsWith(suffix)) {
|
||||
return;
|
||||
}
|
||||
const field = key.slice('region_'.length, -suffix.length);
|
||||
if (!field || field === 'type' || field === 'key' || field === 'name' || field === 'label') {
|
||||
return;
|
||||
}
|
||||
generic[field] = body[key];
|
||||
});
|
||||
|
||||
Object.keys(filesByField || {}).forEach((fieldName) => {
|
||||
if (!fieldName.startsWith('region_') || !fieldName.endsWith(suffix)) {
|
||||
return;
|
||||
}
|
||||
const field = fieldName.slice('region_'.length, -suffix.length);
|
||||
if (!field) {
|
||||
return;
|
||||
}
|
||||
generic[field] = `/media/uploads/${filesByField[fieldName].filename}`;
|
||||
});
|
||||
|
||||
Object.keys(current).forEach((key) => {
|
||||
if (generic[key] === undefined) {
|
||||
generic[key] = current[key];
|
||||
}
|
||||
});
|
||||
|
||||
generic.type = region.region_type;
|
||||
content[region.region_key] = generic;
|
||||
} else {
|
||||
const submitted = body[`region_text_${region.id}`];
|
||||
const current = existingContent && existingContent[region.region_key] ? existingContent[region.region_key].value : '';
|
||||
const style = getTextRegionStyle(body, region, existingContent);
|
||||
content[region.region_key] = {
|
||||
type: 'text',
|
||||
value: submitted === undefined ? current : String(submitted),
|
||||
value: stripEditorOnlyMarkup(normalizeEditorMarkup(submitted === undefined ? current : String(submitted || ''))),
|
||||
font_family: style.font_family,
|
||||
font_size: style.font_size,
|
||||
font_color: style.font_color
|
||||
|
||||
Reference in New Issue
Block a user