From 55ee5dc366d5a2a6ef6a908b064a66dd31779d02 Mon Sep 17 00:00:00 2001 From: Mark Rapson Date: Sat, 24 Oct 2026 21:27:51 +0100 Subject: [PATCH] Release v2.19.2: reorganize shared UI modules --- CHANGELOG.md | 6 ++++++ build/package.player.json | 2 +- build/package.web.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- src/web/public/css/theme-custom.css | 6 +++--- .../{rbac-permissions.js => rbac/permissions.js} | 0 src/web/public/js/regions/type/time-date.js | 2 +- src/web/public/js/regions/type/timetable.js | 2 +- src/web/public/js/regions/type/weather.js | 4 ++-- src/web/public/js/shared/placeholder-chips.js | 2 +- src/web/public/js/{ => shared}/system-status.js | 0 src/web/public/js/{ => shared}/theme-init.js | 0 src/web/public/js/{ => shared/ui}/icon-picker.js | 0 src/web/public/js/{ => shared/ui}/media-picker.js | 0 src/web/public/js/{ => shared/ui}/toast.js | 0 src/web/public/js/{ => shared}/web-ui-helpers.js | 0 src/web/public/js/slides/slide-form-regions.js | 6 +++--- src/web/routes/settings/index.js | 2 +- src/web/routes/settings/rbac/form-view-model.js | 4 ++-- src/web/view.js | 2 +- src/web/views/shared/frame-layout.hbs | 8 ++++---- src/web/views/shared/layout.hbs | 14 +++++++------- src/web/views/signage/announcements/form.hbs | 2 +- src/web/views/signage/slides/form.hbs | 2 +- src/web/views/signage/templates/form.hbs | 2 +- test/player-time-date-region.test.js | 2 +- test/rbac-permissions.test.js | 2 +- test/slide-form-editor.test.js | 2 +- test/system-status.test.js | 4 ++-- test/timetable-migrations.test.js | 2 +- test/toast-refresh.test.js | 2 +- test/web-ui-helpers.test.js | 2 +- 33 files changed, 48 insertions(+), 42 deletions(-) rename src/web/public/js/{rbac-permissions.js => rbac/permissions.js} (100%) rename src/web/public/js/{ => shared}/system-status.js (100%) rename src/web/public/js/{ => shared}/theme-init.js (100%) rename src/web/public/js/{ => shared/ui}/icon-picker.js (100%) rename src/web/public/js/{ => shared/ui}/media-picker.js (100%) rename src/web/public/js/{ => shared/ui}/toast.js (100%) rename src/web/public/js/{ => shared}/web-ui-helpers.js (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56a103e..f859e3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to this project will be documented in this file. +## 2.19.2 - 2026-10-24 + +### Fixed + +- Updated the server-side toast helper reference after organizing browser UI modules into shared and RBAC namespaces, preventing web startup failure. + ## 2.19.1 - 2026-10-24 ### Fixed diff --git a/build/package.player.json b/build/package.player.json index c5b870e..7cc682c 100644 --- a/build/package.player.json +++ b/build/package.player.json @@ -1,6 +1,6 @@ { "name": "pulse-signage-player", - "version": "2.19.1", + "version": "2.19.2", "private": false, "description": "Pulse Signage player application bundle", "engines": { diff --git a/build/package.web.json b/build/package.web.json index 2cab916..b5afd5f 100644 --- a/build/package.web.json +++ b/build/package.web.json @@ -1,6 +1,6 @@ { "name": "pulse-signage-web", - "version": "2.19.1", + "version": "2.19.2", "private": false, "description": "Pulse Signage web and bridge application bundle", "engines": { diff --git a/package-lock.json b/package-lock.json index 7709cb7..22e2bbf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pulse-signage", - "version": "2.19.1", + "version": "2.19.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pulse-signage", - "version": "2.19.1", + "version": "2.19.2", "dependencies": { "animate.css": "^4.1.1", "bootstrap-icons": "1.13.1", diff --git a/package.json b/package.json index f1c6982..3885b81 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pulse-signage", - "version": "2.19.1", + "version": "2.19.2", "private": false, "description": "Pulse Signage application with MySQL and media storage", "engines": { diff --git a/src/web/public/css/theme-custom.css b/src/web/public/css/theme-custom.css index a41eba6..b52ab24 100644 --- a/src/web/public/css/theme-custom.css +++ b/src/web/public/css/theme-custom.css @@ -3944,7 +3944,7 @@ html[data-bs-theme='dark'] .template-field-card .tox .tox-collection { text-transform: none; } -.api-region-placeholder-section .chip { +.placeholder-chip { padding: 0.2rem 0.45rem; border: 1px solid var(--bs-border-color); border-radius: 0.3rem; @@ -3954,12 +3954,12 @@ html[data-bs-theme='dark'] .template-field-card .tox .tox-collection { font-size: 0.75rem; } -.api-region-placeholder-section .chip:hover { +.placeholder-chip:hover { border-color: var(--bs-secondary-color); background: var(--bs-tertiary-bg); } -.api-region-placeholder-section .chip:focus-visible { +.placeholder-chip:focus-visible { outline: 2px solid var(--bs-primary); outline-offset: 2px; } diff --git a/src/web/public/js/rbac-permissions.js b/src/web/public/js/rbac/permissions.js similarity index 100% rename from src/web/public/js/rbac-permissions.js rename to src/web/public/js/rbac/permissions.js diff --git a/src/web/public/js/regions/type/time-date.js b/src/web/public/js/regions/type/time-date.js index 09e9cd6..38b28a3 100644 --- a/src/web/public/js/regions/type/time-date.js +++ b/src/web/public/js/regions/type/time-date.js @@ -208,7 +208,7 @@ } return tokens.map(function (token) { - return '{{' + token + '}}'; + return '{{' + token + '}}'; }).join(''); } diff --git a/src/web/public/js/regions/type/timetable.js b/src/web/public/js/regions/type/timetable.js index 8653fe9..5d6c063 100644 --- a/src/web/public/js/regions/type/timetable.js +++ b/src/web/public/js/regions/type/timetable.js @@ -215,7 +215,7 @@ } return tokens.map(function (token) { - return '{{' + escapeHtml(token) + '}}'; + return '{{' + escapeHtml(token) + '}}'; }).join(''); } diff --git a/src/web/public/js/regions/type/weather.js b/src/web/public/js/regions/type/weather.js index 87f4074..d0228f1 100644 --- a/src/web/public/js/regions/type/weather.js +++ b/src/web/public/js/regions/type/weather.js @@ -64,14 +64,14 @@ if (visibleCount >= fields.length) return fields.map(function (field) { return window.placeholderChips && typeof window.placeholderChips.renderChip === 'function' ? window.placeholderChips.renderChip(field) - : '{{' + escapeHtml(field) + '}}'; + : '{{' + escapeHtml(field) + '}}'; }).join(''); var visibleFields = fields.slice(0, visibleCount); var overflowFields = fields.slice(visibleFields.length); var renderChip = function (field) { return window.placeholderChips && typeof window.placeholderChips.renderChip === 'function' ? window.placeholderChips.renderChip(field) - : '{{' + escapeHtml(field) + '}}'; + : '{{' + escapeHtml(field) + '}}'; }; var visibleMarkup = visibleFields.map(renderChip).join(''); if (!overflowFields.length) return visibleMarkup; diff --git a/src/web/public/js/shared/placeholder-chips.js b/src/web/public/js/shared/placeholder-chips.js index a02f29a..5e17e9b 100644 --- a/src/web/public/js/shared/placeholder-chips.js +++ b/src/web/public/js/shared/placeholder-chips.js @@ -98,7 +98,7 @@ var escapedCopyText = escapeHtml(copyText); ensureCopyHandler(); - return '' + escapedCopyText + ''; + return '' + escapedCopyText + ''; } function renderChips(tokens) { diff --git a/src/web/public/js/system-status.js b/src/web/public/js/shared/system-status.js similarity index 100% rename from src/web/public/js/system-status.js rename to src/web/public/js/shared/system-status.js diff --git a/src/web/public/js/theme-init.js b/src/web/public/js/shared/theme-init.js similarity index 100% rename from src/web/public/js/theme-init.js rename to src/web/public/js/shared/theme-init.js diff --git a/src/web/public/js/icon-picker.js b/src/web/public/js/shared/ui/icon-picker.js similarity index 100% rename from src/web/public/js/icon-picker.js rename to src/web/public/js/shared/ui/icon-picker.js diff --git a/src/web/public/js/media-picker.js b/src/web/public/js/shared/ui/media-picker.js similarity index 100% rename from src/web/public/js/media-picker.js rename to src/web/public/js/shared/ui/media-picker.js diff --git a/src/web/public/js/toast.js b/src/web/public/js/shared/ui/toast.js similarity index 100% rename from src/web/public/js/toast.js rename to src/web/public/js/shared/ui/toast.js diff --git a/src/web/public/js/web-ui-helpers.js b/src/web/public/js/shared/web-ui-helpers.js similarity index 100% rename from src/web/public/js/web-ui-helpers.js rename to src/web/public/js/shared/web-ui-helpers.js diff --git a/src/web/public/js/slides/slide-form-regions.js b/src/web/public/js/slides/slide-form-regions.js index 710330e..fe8d42f 100644 --- a/src/web/public/js/slides/slide-form-regions.js +++ b/src/web/public/js/slides/slide-form-regions.js @@ -137,7 +137,7 @@ export function createSlideFormRegionHelpers(options) { var visibleMarkup = visibleFields.map(function (field) { return typeof placeholderChips.renderChip === 'function' ? placeholderChips.renderChip(field) - : '{{' + escapeHtml(field) + '}}'; + : '{{' + escapeHtml(field) + '}}'; }).join(''); var transformHint = includeTransformHint === false ? '' : '
Placeholder values support transforms, for example {{title.upper()}}, {{title.title()}}, {{title.lower()}}, {{publishedAt.format("MMM D, YYYY")}}, or {{amount.multiply(10)}}.
'; @@ -153,7 +153,7 @@ export function createSlideFormRegionHelpers(options) { '
' + overflowFields.map(function (field) { return typeof placeholderChips.renderChip === 'function' ? placeholderChips.renderChip(field) - : '{{' + escapeHtml(field) + '}}'; + : '{{' + escapeHtml(field) + '}}'; }).join('') + '
' + ''; } @@ -226,7 +226,7 @@ export function createSlideFormRegionHelpers(options) { var chips = Array.isArray(fieldList) ? fieldList.map(function (field) { return typeof placeholderChips.renderChip === 'function' ? placeholderChips.renderChip(field) - : '{{' + escapeHtml(field) + '}}'; + : '{{' + escapeHtml(field) + '}}'; }).join('') : ''; container.innerHTML = chips || '' + escapeHtml(emptyLabel) + ''; diff --git a/src/web/routes/settings/index.js b/src/web/routes/settings/index.js index 02975ea..9a7fb5c 100644 --- a/src/web/routes/settings/index.js +++ b/src/web/routes/settings/index.js @@ -14,6 +14,6 @@ module.exports = function renderSettingsPage(settings, suggestedAnnouncementIcon defaultAnnouncementIconSuggestions: iconOptions.filter(function (option) { return option.isSelected; }), mediaSettings: mediaSettings || {}, diagnostics: diagnostics || {}, - scripts: ['js/icon-picker.js', 'js/settings/settings-page.js'] + scripts: ['js/shared/ui/icon-picker.js', 'js/settings/settings-page.js'] }); }; diff --git a/src/web/routes/settings/rbac/form-view-model.js b/src/web/routes/settings/rbac/form-view-model.js index 1d31a27..28b5938 100644 --- a/src/web/routes/settings/rbac/form-view-model.js +++ b/src/web/routes/settings/rbac/form-view-model.js @@ -119,7 +119,7 @@ function buildRbacAddViewModel(message, currentUser, formValues, permissionGroup users: users || [], pagination: pagination || null, roleEditBasePath: '/settings/roles/new', - scripts: ['js/rbac-permissions.js'], + scripts: ['js/rbac/permissions.js'], formAttrs: 'data-async-save data-async-save-close-url="/settings/roles" data-async-save-new-redirect="response-url" data-async-save-new-url="/settings/roles/new"' }; } @@ -155,7 +155,7 @@ function buildRbacEditViewModel(role, message, currentUser, permissionGroups, us users: users || [], pagination: pagination || null, roleEditBasePath: '/settings/roles/' + Number(role && role.id) + '/edit', - scripts: ['js/rbac-permissions.js'], + scripts: ['js/rbac/permissions.js'], formAttrs: 'data-async-save data-async-save-close-url="/settings/roles" data-async-save-new-url="/settings/roles/new"' }; } diff --git a/src/web/view.js b/src/web/view.js index 572fcd6..1270586 100644 --- a/src/web/view.js +++ b/src/web/view.js @@ -6,7 +6,7 @@ const Handlebars = require('handlebars'); const { version: appVersion } = require('#root/package.json'); const { hasPermission, hasAnyPermission, hasAssignedPermission } = require('#src/rbac'); const { registerHandlebars } = require('#src/web/handlebars'); -const { getMessageVariant: inferMessageVariant } = require('#src/web/public/js/toast'); +const { getMessageVariant: inferMessageVariant } = require('#src/web/public/js/shared/ui/toast'); const VIEWS_ROOT = path.join(__dirname, 'views'); const cache = new Map(); diff --git a/src/web/views/shared/frame-layout.hbs b/src/web/views/shared/frame-layout.hbs index dbac89c..a48bc16 100644 --- a/src/web/views/shared/frame-layout.hbs +++ b/src/web/views/shared/frame-layout.hbs @@ -5,7 +5,7 @@ {{title}} - Pulse - + @@ -83,12 +83,12 @@ {{/if}} - - + + - + {{#if scripts.length}} {{#each scripts}} diff --git a/src/web/views/shared/layout.hbs b/src/web/views/shared/layout.hbs index f4d6622..bca761e 100644 --- a/src/web/views/shared/layout.hbs +++ b/src/web/views/shared/layout.hbs @@ -5,7 +5,7 @@ {{title}} - Pulse - + @@ -26,7 +26,7 @@ - + {{else if authShell}}
{{#if message}} @@ -56,9 +56,9 @@ {{{body}}}
- + - + {{else}}
{{#if message}} @@ -413,8 +413,8 @@ - - + + @@ -427,7 +427,7 @@ {{/if}} {{/each}} {{/if}} - + {{/if}} \ No newline at end of file diff --git a/src/web/views/signage/announcements/form.hbs b/src/web/views/signage/announcements/form.hbs index 7316984..3076ee2 100644 --- a/src/web/views/signage/announcements/form.hbs +++ b/src/web/views/signage/announcements/form.hbs @@ -174,7 +174,7 @@
- + diff --git a/src/web/views/signage/slides/form.hbs b/src/web/views/signage/slides/form.hbs index 58118b4..f237526 100644 --- a/src/web/views/signage/slides/form.hbs +++ b/src/web/views/signage/slides/form.hbs @@ -247,5 +247,5 @@ {{/each}} - + diff --git a/src/web/views/signage/templates/form.hbs b/src/web/views/signage/templates/form.hbs index 7572b44..3e1b3d1 100644 --- a/src/web/views/signage/templates/form.hbs +++ b/src/web/views/signage/templates/form.hbs @@ -213,4 +213,4 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/test/player-time-date-region.test.js b/test/player-time-date-region.test.js index 99df74c..46baceb 100644 --- a/test/player-time-date-region.test.js +++ b/test/player-time-date-region.test.js @@ -4,7 +4,7 @@ const fs = require('node:fs'); const vm = require('node:vm'); function loadTimeDateModule() { - const webUiHelpersScript = fs.readFileSync(require.resolve('../src/web/public/js/web-ui-helpers.js'), 'utf8'); + const webUiHelpersScript = fs.readFileSync(require.resolve('../src/web/public/js/shared/web-ui-helpers.js'), 'utf8'); const sharedRenderingScript = fs.readFileSync(require.resolve('../src/player/rendering-shared.js'), 'utf8'); const renderingScript = fs.readFileSync(require.resolve('../src/player/public/js/player-page-rendering.js'), 'utf8'); const placeholderScript = fs.readFileSync(require.resolve('../src/web/public/js/shared/placeholder-utils.js'), 'utf8'); diff --git a/test/rbac-permissions.test.js b/test/rbac-permissions.test.js index 27ba7dd..b075bac 100644 --- a/test/rbac-permissions.test.js +++ b/test/rbac-permissions.test.js @@ -2,7 +2,7 @@ const test = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); -const rbacPermissionsScript = fs.readFileSync(require.resolve('../src/web/public/js/rbac-permissions.js'), 'utf8'); +const rbacPermissionsScript = fs.readFileSync(require.resolve('../src/web/public/js/rbac/permissions.js'), 'utf8'); const rbacFormTemplate = fs.readFileSync(require.resolve('../src/web/views/settings/rbac/form.hbs'), 'utf8'); const rbacRoutesSource = fs.readFileSync(require.resolve('../src/web/routes/admin/rbac.js'), 'utf8'); const { buildPermissionSections, buildRbacEditViewModel } = require('../src/web/routes/settings/rbac/form-view-model'); diff --git a/test/slide-form-editor.test.js b/test/slide-form-editor.test.js index 388841d..67777e1 100644 --- a/test/slide-form-editor.test.js +++ b/test/slide-form-editor.test.js @@ -6,7 +6,7 @@ const vm = require('node:vm'); const slideFormEditorSource = fs.readFileSync(require.resolve('../src/web/public/js/slides/slide-form-editor.js'), 'utf8'); const slideFormSource = fs.readFileSync(require.resolve('../src/web/public/js/slides/slide-form.js'), 'utf8'); const slideFormTemplateSource = fs.readFileSync(require.resolve('../src/web/views/signage/slides/form.hbs'), 'utf8'); -const mediaPickerSource = fs.readFileSync(require.resolve('../src/web/public/js/media-picker.js'), 'utf8'); +const mediaPickerSource = fs.readFileSync(require.resolve('../src/web/public/js/shared/ui/media-picker.js'), 'utf8'); const slideThumbnailPreviewSource = fs.readFileSync(require.resolve('../src/web/lib/media/slide-thumbnail-preview.js'), 'utf8'); const slideThumbnailsSource = fs.readFileSync(require.resolve('../src/web/lib/media/slide-thumbnails.js'), 'utf8'); const slideHtmlRegionSource = fs.readFileSync(require.resolve('../src/web/public/js/regions/type/html.js'), 'utf8'); diff --git a/test/system-status.test.js b/test/system-status.test.js index 74f4793..641f015 100644 --- a/test/system-status.test.js +++ b/test/system-status.test.js @@ -4,7 +4,7 @@ const fs = require('node:fs'); const vm = require('node:vm'); test('system status shows connected player count in the live feed label', () => { - const script = fs.readFileSync(require.resolve('../src/web/public/js/system-status.js'), 'utf8'); + const script = fs.readFileSync(require.resolve('../src/web/public/js/shared/system-status.js'), 'utf8'); const elements = { 'sidebar-status-dot': { classList: { @@ -74,7 +74,7 @@ test('system status shows connected player count in the live feed label', () => }); test('system status shows an explicit idle label when no players are connected', () => { - const script = fs.readFileSync(require.resolve('../src/web/public/js/system-status.js'), 'utf8'); + const script = fs.readFileSync(require.resolve('../src/web/public/js/shared/system-status.js'), 'utf8'); const elements = { 'sidebar-status-dot': { classList: { diff --git a/test/timetable-migrations.test.js b/test/timetable-migrations.test.js index 5606d8f..5b6dfed 100644 --- a/test/timetable-migrations.test.js +++ b/test/timetable-migrations.test.js @@ -284,7 +284,7 @@ test('schema startup runs the 2.19.1 repair on its existing locked connection', assert.equal(acquisitions, 1); const commitIndex = connection.queries.findIndex(([sql]) => sql === 'COMMIT'); - const versionIndex = connection.queries.findIndex(([sql, params]) => sql.startsWith('UPDATE o_app_state') && params[0] === '2.19.1'); + const versionIndex = connection.queries.findIndex(([sql, params]) => sql.startsWith('UPDATE o_app_state') && params[0] === require('../package.json').version); assert.ok(commitIndex > 0); assert.ok(versionIndex > commitIndex); assert.deepEqual(connection.queries.slice(-2), [ diff --git a/test/toast-refresh.test.js b/test/toast-refresh.test.js index 4d383bc..ba3837f 100644 --- a/test/toast-refresh.test.js +++ b/test/toast-refresh.test.js @@ -130,7 +130,7 @@ test('pending toast is shown after a refresh and cleared from storage', () => { }; sandbox.window = sandbox; - const scriptPath = path.join(__dirname, '..', 'src', 'web', 'public', 'js', 'toast.js'); + const scriptPath = path.join(__dirname, '..', 'src', 'web', 'public', 'js', 'shared', 'ui', 'toast.js'); const script = fs.readFileSync(scriptPath, 'utf8'); vm.runInNewContext(script, sandbox, { filename: scriptPath }); diff --git a/test/web-ui-helpers.test.js b/test/web-ui-helpers.test.js index b29b797..821a945 100644 --- a/test/web-ui-helpers.test.js +++ b/test/web-ui-helpers.test.js @@ -23,7 +23,7 @@ test('client row keys prefer the client name', () => { }; sandbox.window = sandbox; - loadScript(path.join(__dirname, '..', 'src', 'web', 'public', 'js', 'web-ui-helpers.js'), sandbox); + loadScript(path.join(__dirname, '..', 'src', 'web', 'public', 'js', 'shared', 'web-ui-helpers.js'), sandbox); const helpers = sandbox.window.webUiHelpers; assert.equal(helpers.getClientRowKey({ client_name: 'Conference Left', screen_slug: 'alpha', deviceId: 'device-123', id: 'conn-1', clientId: 'client-1' }), 'conn-1');