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(/', ``, hasSecondaryActions ? `` : '', hasSecondaryActions ? '' : '', '', ].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 };