Fix managed fonts and quoted styles in slide previews
This commit is contained in:
1 parent
5b5af1a7b9
commit
ccd3535e7c
4 files changed
+61
-2
No files matched your search
@@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
- Fixed playlist schedule summaries showing weekday text and omitting boundary times when dates are returned as `Date` objects.
|
- Fixed playlist schedule summaries showing weekday text and omitting boundary times when dates are returned as `Date` objects.
|
||||||
- Preserved saved API region items-path overrides when reopening the slide editor instead of replacing them with the source default.
|
- Preserved saved API region items-path overrides when reopening the slide editor instead of replacing them with the source default.
|
||||||
|
- Fixed managed-font loading in thumbnail popup previews and preserved entity-encoded font-family quotes across preview sanitization.
|
||||||
|
|
||||||
## 2.20.4 - 2026-10-25
|
## 2.20.4 - 2026-10-25
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,12 @@
|
|||||||
.replace(/'/g, ''');
|
.replace(/'/g, ''');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function decodeAttributeValue(value) {
|
||||||
|
var decoder = document.createElement('textarea');
|
||||||
|
decoder.innerHTML = String(value || '').replace(/</g, '<');
|
||||||
|
return decoder.value;
|
||||||
|
}
|
||||||
|
|
||||||
function sanitizePreviewHtml(html) {
|
function sanitizePreviewHtml(html) {
|
||||||
var output = String(html || '');
|
var output = String(html || '');
|
||||||
output = output.replace(/<script[\s\S]*?<\/script>/gi, '');
|
output = output.replace(/<script[\s\S]*?<\/script>/gi, '');
|
||||||
@@ -123,7 +129,7 @@
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
var value = doubleQuoted !== undefined ? doubleQuoted : singleQuoted !== undefined ? singleQuoted : bareValue !== undefined ? bareValue : '';
|
var value = decodeAttributeValue(doubleQuoted !== undefined ? doubleQuoted : singleQuoted !== undefined ? singleQuoted : bareValue !== undefined ? bareValue : '');
|
||||||
if (lowerKey === 'href' && /^(?:\s*javascript:|\s*data:)/i.test(String(value || ''))) {
|
if (lowerKey === 'href' && /^(?:\s*javascript:|\s*data:)/i.test(String(value || ''))) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -185,7 +191,7 @@
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
var value = doubleQuoted !== undefined ? doubleQuoted : singleQuoted !== undefined ? singleQuoted : bareValue !== undefined ? bareValue : '';
|
var value = decodeAttributeValue(doubleQuoted !== undefined ? doubleQuoted : singleQuoted !== undefined ? singleQuoted : bareValue !== undefined ? bareValue : '');
|
||||||
if (lowerKey === 'href' && /^(?:\s*javascript:|\s*data:)/i.test(String(value || ''))) {
|
if (lowerKey === 'href' && /^(?:\s*javascript:|\s*data:)/i.test(String(value || ''))) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ function registerAuthAndAccountRoutes(app, deps) {
|
|||||||
pool: deps.pool,
|
pool: deps.pool,
|
||||||
common: deps.common,
|
common: deps.common,
|
||||||
pages: deps.pages,
|
pages: deps.pages,
|
||||||
|
mediaDir: deps.mediaDir,
|
||||||
requirePermission: deps.requirePermission,
|
requirePermission: deps.requirePermission,
|
||||||
buildDashboardState: deps.buildDashboardState,
|
buildDashboardState: deps.buildDashboardState,
|
||||||
fetchScreensByPlaylistId: deps.fetchScreensByPlaylistId,
|
fetchScreensByPlaylistId: deps.fetchScreensByPlaylistId,
|
||||||
|
|||||||
@@ -35,6 +35,57 @@ function loadEditorTimeDateModule() {
|
|||||||
return modules.get('time-date');
|
return modules.get('time-date');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test('preview sanitizers preserve entity-quoted font families without double escaping', () => {
|
||||||
|
const entities = { apos: "'", quot: '"', amp: '&', lt: '<', gt: '>' };
|
||||||
|
const sandbox = {
|
||||||
|
window: {},
|
||||||
|
document: {
|
||||||
|
createElement(tag) {
|
||||||
|
assert.equal(tag, 'textarea');
|
||||||
|
return {
|
||||||
|
set innerHTML(value) {
|
||||||
|
this.value = value.replace(/&(#x[\da-f]+|#\d+|apos|quot|amp|lt|gt);/gi, (_match, entity) => {
|
||||||
|
if (entity[0] !== '#') return entities[entity];
|
||||||
|
return String.fromCodePoint(entity[1].toLowerCase() === 'x' ? parseInt(entity.slice(2), 16) : Number(entity.slice(1)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
vm.runInNewContext(fs.readFileSync(require.resolve('../src/web/public/js/regions/region-utils'), 'utf8'), sandbox);
|
||||||
|
|
||||||
|
for (const sanitize of [sandbox.window.pulseRegionUtils.sanitizePreviewHtml, sandbox.window.pulseRegionUtils.sanitizeRichText]) {
|
||||||
|
for (const quotes of ["'Ubuntu Regular'", ''Ubuntu Regular'', '"Ubuntu Regular"']) {
|
||||||
|
const markup = sanitize('<span style="font-family: ' + quotes + ';">Player</span>');
|
||||||
|
assert.doesNotMatch(markup, /&#39;|&quot;/);
|
||||||
|
assert.equal(sanitize(markup), markup);
|
||||||
|
assert.match(markup, /font-family: (?:'Ubuntu Regular'|"Ubuntu Regular");/);
|
||||||
|
}
|
||||||
|
const unsafe = sanitize('<a href="javascript:alert(1)" style="background:url(test)">Player</a>');
|
||||||
|
assert.doesNotMatch(unsafe, /href=|style=/);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('page route wiring preserves the configured media directory for popup font stylesheets', () => {
|
||||||
|
let pageDependencies;
|
||||||
|
const sandbox = {
|
||||||
|
module: { exports: {} },
|
||||||
|
require(name) {
|
||||||
|
if (name === '#src/rbac') return { PERMISSIONS: [], normalizePermissionKeys: value => value };
|
||||||
|
return function (_app, dependencies) {
|
||||||
|
if (name === '#src/web/routes/admin/pages') pageDependencies = dependencies;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
vm.runInNewContext(fs.readFileSync(require.resolve('../src/web/routes/register'), 'utf8'), sandbox);
|
||||||
|
sandbox.module.exports.registerRoutes({}, {
|
||||||
|
mediaDir: '/configured/signage-media',
|
||||||
|
playerActionService: {}
|
||||||
|
});
|
||||||
|
assert.equal(pageDependencies.mediaDir, '/configured/signage-media');
|
||||||
|
});
|
||||||
|
|
||||||
test('thumbnail payload passes saved region geometry and content to the popup renderer', () => {
|
test('thumbnail payload passes saved region geometry and content to the popup renderer', () => {
|
||||||
const payload = buildThumbnailPreviewPayload({
|
const payload = buildThumbnailPreviewPayload({
|
||||||
template: {
|
template: {
|
||||||
|
|||||||
Reference in new issue
Block a user