162 lines
6.8 KiB
JavaScript
162 lines
6.8 KiB
JavaScript
const fs = require('fs');
|
|
const path = require('path');
|
|
const Handlebars = require('handlebars');
|
|
const { version: appVersion } = require('../../package.json');
|
|
const { hasPermission, hasAnyPermission } = require('../rbac');
|
|
|
|
const VIEWS_ROOT = path.join(__dirname, 'views');
|
|
const cache = new Map();
|
|
const SIGNAGE_VIEW_PREFIXES = new Set(['dashboard', 'clients', 'screens', 'playlists', 'slides', 'canvas-sizes', 'templates']);
|
|
|
|
function inferMessageVariant(message, fallbackVariant) {
|
|
const text = String(message || '').trim();
|
|
if (/\b(?:unable to|cannot|can't|could not|failed to)\s+delete\b/i.test(text) || /\bdelete\b.*\b(?:before|first)\b/i.test(text) || /\bstill (?:in use|linked|assigned|used)\b/i.test(text)) {
|
|
return 'danger';
|
|
}
|
|
|
|
if (/\b(?:already exists|already exist|already taken|duplicate|must be unique|name already exists)\b/i.test(text)) {
|
|
return 'warning';
|
|
}
|
|
|
|
return String(fallbackVariant || '').trim().toLowerCase() || 'info';
|
|
}
|
|
|
|
Handlebars.registerHelper('eq', function (left, right) {
|
|
return left === right;
|
|
});
|
|
|
|
Handlebars.registerHelper('isExternalUrl', function (value) {
|
|
return /^https?:\/\//i.test(String(value || ''));
|
|
});
|
|
|
|
Handlebars.registerHelper('playerUrl', function (slug) {
|
|
const base = (process.env.PLAYER_PUBLIC_BASE_URL || process.env.PLAYER_BASE_URL || 'http://localhost:3001').replace(/\/$/, '');
|
|
return `${base}/screen/${encodeURIComponent(slug)}`;
|
|
});
|
|
|
|
Handlebars.registerHelper('json', function (value) {
|
|
return new Handlebars.SafeString(JSON.stringify(value).replace(/</g, '\\u003c'));
|
|
});
|
|
|
|
Handlebars.registerHelper('hasPermission', function (currentUser, permissionKey) {
|
|
return hasPermission(currentUser, permissionKey);
|
|
});
|
|
|
|
Handlebars.registerHelper('anyPermission', function (currentUser) {
|
|
const args = Array.prototype.slice.call(arguments, 1);
|
|
args.pop();
|
|
return hasAnyPermission(currentUser, args);
|
|
});
|
|
|
|
Handlebars.registerHelper('usernameInitial', function (username) {
|
|
const value = String(username || '').trim();
|
|
if (!value) {
|
|
return 'A';
|
|
}
|
|
return value.charAt(0).toUpperCase();
|
|
});
|
|
|
|
Handlebars.registerHelper('userInitial', function (name, username) {
|
|
const value = String(name || '').trim() || String(username || '').trim();
|
|
if (!value) {
|
|
return 'A';
|
|
}
|
|
return value.charAt(0).toUpperCase();
|
|
});
|
|
|
|
Handlebars.registerHelper('truncateText', function (value, maxLength) {
|
|
const text = String(value || '').trim();
|
|
const limit = Number(maxLength);
|
|
if (!text || !Number.isFinite(limit) || limit <= 0 || text.length <= limit) {
|
|
return text;
|
|
}
|
|
|
|
return `${text.slice(0, Math.max(0, limit - 1)).trimEnd()}…`;
|
|
});
|
|
|
|
Handlebars.registerHelper('saveActionButtons', function (options) {
|
|
const hash = options && options.hash ? options.hash : {};
|
|
const formId = String(hash.formId || '').trim();
|
|
const saveLabel = String(hash.saveLabel || 'Save');
|
|
const saveButtonClass = String(hash.saveButtonClass || 'btn btn-success');
|
|
const saveActionName = String(hash.saveActionName || 'save_action');
|
|
const saveActionValue = String(hash.saveActionValue || 'save');
|
|
const closeLabel = String(hash.saveAndCloseLabel || 'Save and Close');
|
|
const closeValue = String(hash.saveAndCloseValue || 'close');
|
|
const newLabel = String(hash.saveAndNewLabel || 'Save and New');
|
|
const newValue = String(hash.saveAndNewValue || 'new');
|
|
const showSaveAndClose = hash.showSaveAndClose === undefined ? true : String(hash.showSaveAndClose).toLowerCase() !== 'false';
|
|
const showSaveAndNew = hash.showSaveAndNew === undefined ? true : String(hash.showSaveAndNew).toLowerCase() !== 'false';
|
|
const ariaLabel = String(hash.ariaLabel || 'Save actions');
|
|
const hasSecondaryActions = showSaveAndClose || showSaveAndNew;
|
|
|
|
if (!formId) {
|
|
return '';
|
|
}
|
|
|
|
const escape = Handlebars.escapeExpression;
|
|
const formAttr = ` form="${escape(formId)}"`;
|
|
|
|
return new Handlebars.SafeString([
|
|
'<div class="btn-group save-action-group" role="group">',
|
|
`<button type="submit" class="${escape(saveButtonClass)}"${formAttr} name="${escape(saveActionName)}" value="${escape(saveActionValue)}">${escape(saveLabel)}</button>`,
|
|
hasSecondaryActions ? `<button type="button" class="btn btn-success dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false"><span class="visually-hidden">${escape(ariaLabel)}</span></button>` : '',
|
|
hasSecondaryActions ? '<div class="dropdown-menu dropdown-menu-end">' : '',
|
|
hasSecondaryActions && showSaveAndClose ? `<button type="submit" class="dropdown-item"${formAttr} name="${escape(saveActionName)}" value="${escape(closeValue)}">${escape(closeLabel)}</button>` : '',
|
|
hasSecondaryActions && showSaveAndNew ? `<button type="submit" class="dropdown-item"${formAttr} name="${escape(saveActionName)}" value="${escape(newValue)}">${escape(newLabel)}</button>` : '',
|
|
hasSecondaryActions ? '</div>' : '',
|
|
'</div>',
|
|
].join(''));
|
|
});
|
|
|
|
Handlebars.registerPartial('modal-shell', fs.readFileSync(path.join(VIEWS_ROOT, 'shared', 'modal-shell.hbs'), 'utf8'));
|
|
Handlebars.registerPartial('table-pagination', fs.readFileSync(path.join(VIEWS_ROOT, 'shared', 'table-pagination.hbs'), 'utf8'));
|
|
|
|
function resolveTemplatePath(relativePath) {
|
|
const firstSegment = String(relativePath || '').split(/[\\/]/)[0];
|
|
if (SIGNAGE_VIEW_PREFIXES.has(firstSegment)) {
|
|
return path.join(VIEWS_ROOT, 'signage', relativePath);
|
|
}
|
|
|
|
return path.join(VIEWS_ROOT, relativePath);
|
|
}
|
|
|
|
function loadTemplate(relativePath) {
|
|
const filePath = resolveTemplatePath(relativePath);
|
|
const stat = fs.statSync(filePath);
|
|
const cached = cache.get(filePath);
|
|
if (cached && cached.mtimeMs === stat.mtimeMs) {
|
|
return cached.template;
|
|
}
|
|
const template = Handlebars.compile(fs.readFileSync(filePath, 'utf8'));
|
|
cache.set(filePath, { mtimeMs: stat.mtimeMs, template: template });
|
|
return template;
|
|
}
|
|
|
|
function renderView(viewName, context) {
|
|
const viewContext = Object.assign({ stylesheets: [], scripts: [], appVersion: appVersion }, context || {});
|
|
if (!viewContext.messageVariant) {
|
|
viewContext.messageVariant = inferMessageVariant(viewContext.message, 'primary');
|
|
}
|
|
const page = loadTemplate(`${viewName}.hbs`);
|
|
const layout = loadTemplate(path.join('shared', 'layout.hbs'));
|
|
const body = page(viewContext);
|
|
return layout(Object.assign({}, viewContext, { body: body }));
|
|
}
|
|
|
|
function renderFragment(viewName, context) {
|
|
const viewContext = Object.assign({ stylesheets: [], scripts: [], appVersion: appVersion }, context || {});
|
|
if (!viewContext.messageVariant) {
|
|
viewContext.messageVariant = inferMessageVariant(viewContext.message, 'primary');
|
|
}
|
|
const page = loadTemplate(`${viewName}.hbs`);
|
|
const layout = loadTemplate(path.join('shared', 'frame-layout.hbs'));
|
|
const body = page(viewContext);
|
|
return layout(Object.assign({}, viewContext, { body: body }));
|
|
}
|
|
|
|
module.exports = {
|
|
renderView,
|
|
renderFragment
|
|
};
|