Keep media rename file extensions fixed
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile, web, pulse-signage-web) (push) Successful in 1m46s
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile.player, player, pulse-signage-player) (push) Successful in 31s

This commit is contained in:
lzstealth committed 2026-10-18 01:11:40 +01:00
1 parent 70b45debde
commit 8d75b12354
8 files changed
+81 -8

No files matched your search

+6
View File
@@ -2,6 +2,12 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## 2.15.6 - 2026-10-18
### Changed
- Media Library renaming now keeps the file extension fixed while allowing the filename to be edited.
## 2.15.5 - 2026-10-17 ## 2.15.5 - 2026-10-17
### Added ### Added
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "pulse-signage-player", "name": "pulse-signage-player",
"version": "2.15.5", "version": "2.15.6",
"private": false, "private": false,
"description": "Pulse Signage player application bundle", "description": "Pulse Signage player application bundle",
"engines": { "engines": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "pulse-signage-web", "name": "pulse-signage-web",
"version": "2.15.5", "version": "2.15.6",
"private": false, "private": false,
"description": "Pulse Signage web and bridge application bundle", "description": "Pulse Signage web and bridge application bundle",
"engines": { "engines": {
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "pulse-signage", "name": "pulse-signage",
"version": "2.15.5", "version": "2.15.6",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "pulse-signage", "name": "pulse-signage",
"version": "2.15.5", "version": "2.15.6",
"dependencies": { "dependencies": {
"@sparticuz/chromium": "^149.0.0", "@sparticuz/chromium": "^149.0.0",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "pulse-signage", "name": "pulse-signage",
"version": "2.15.5", "version": "2.15.6",
"private": false, "private": false,
"description": "Pulse Signage application with MySQL and media storage", "description": "Pulse Signage application with MySQL and media storage",
"engines": { "engines": {
+9 -3
View File
@@ -556,9 +556,11 @@ function showPreviewModal() {
if (modal && body) { if (modal && body) {
const renameForm = modal.querySelector('[data-media-gallery-rename-form]'); const renameForm = modal.querySelector('[data-media-gallery-rename-form]');
const filenameInput = modal.querySelector('[data-media-gallery-filename]'); const filenameInput = modal.querySelector('[data-media-gallery-filename]');
const extensionLabel = modal.querySelector('[data-media-gallery-extension]');
const renameSave = modal.querySelector('[data-media-gallery-rename-save]'); const renameSave = modal.querySelector('[data-media-gallery-rename-save]');
const renameStatus = modal.querySelector('[data-media-gallery-rename-status]'); const renameStatus = modal.querySelector('[data-media-gallery-rename-status]');
let activePreview = null; let activePreview = null;
let activeExtension = '';
if (renameForm && filenameInput && renameSave && renameStatus) { if (renameForm && filenameInput && renameSave && renameStatus) {
renameForm.addEventListener('submit', async function (event) { renameForm.addEventListener('submit', async function (event) {
@@ -573,7 +575,7 @@ if (modal && body) {
const response = await fetch(action, { const response = await fetch(action, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify({ name: filenameInput.value }) body: JSON.stringify({ name: filenameInput.value + activeExtension })
}); });
const result = await response.json(); const result = await response.json();
if (!response.ok) throw new Error(result.error || 'Unable to rename media.'); if (!response.ok) throw new Error(result.error || 'Unable to rename media.');
@@ -586,7 +588,7 @@ if (modal && body) {
label.title = result.name; label.title = result.name;
} }
if (activePreview === preview) { if (activePreview === preview) {
filenameInput.value = result.name; filenameInput.value = activeExtension ? result.name.slice(0, -activeExtension.length) : result.name;
if (title) title.textContent = result.name; if (title) title.textContent = result.name;
const image = body.querySelector('img'); const image = body.querySelector('img');
if (image) image.alt = result.name; if (image) image.alt = result.name;
@@ -625,7 +627,11 @@ if (modal && body) {
const assetId = column && column.dataset.mediaId; const assetId = column && column.dataset.mediaId;
renameForm.hidden = !assetId; renameForm.hidden = !assetId;
renameForm.action = '/media-library/' + encodeURIComponent(assetId || '') + '/rename'; renameForm.action = '/media-library/' + encodeURIComponent(assetId || '') + '/rename';
filenameInput.value = name; const extensionIndex = name.lastIndexOf('.');
activeExtension = extensionIndex > 0 && extensionIndex < name.length - 1 ? name.slice(extensionIndex) : '';
filenameInput.value = activeExtension ? name.slice(0, extensionIndex) : name;
filenameInput.maxLength = Math.max(1, 255 - activeExtension.length);
if (extensionLabel) extensionLabel.textContent = activeExtension;
filenameInput.disabled = false; filenameInput.disabled = false;
renameSave.disabled = false; renameSave.disabled = false;
renameStatus.hidden = true; renameStatus.hidden = true;
@@ -100,6 +100,7 @@
<label for="media-gallery-filename" class="form-label">Filename</label> <label for="media-gallery-filename" class="form-label">Filename</label>
<div class="input-group"> <div class="input-group">
<input id="media-gallery-filename" name="name" type="text" class="form-control" maxlength="255" required data-media-gallery-filename /> <input id="media-gallery-filename" name="name" type="text" class="form-control" maxlength="255" required data-media-gallery-filename />
<span class="input-group-text" data-media-gallery-extension aria-label="File extension"></span>
<button type="submit" class="btn btn-primary" title="Save filename" data-media-gallery-rename-save><i class="bi bi-floppy" aria-hidden="true"></i><span class="visually-hidden">Save filename</span></button> <button type="submit" class="btn btn-primary" title="Save filename" data-media-gallery-rename-save><i class="bi bi-floppy" aria-hidden="true"></i><span class="visually-hidden">Save filename</span></button>
</div> </div>
<p class="small mb-0 mt-2" role="status" data-media-gallery-rename-status hidden></p> <p class="small mb-0 mt-2" role="status" data-media-gallery-rename-status hidden></p>
+60
View File
@@ -408,5 +408,65 @@ test('media preview offers filename editing only with media update permission',
const editable = renderMediaLibraryPage(data, '', { permissions: ['media-library.read', 'media-library.update'] }); const editable = renderMediaLibraryPage(data, '', { permissions: ['media-library.read', 'media-library.update'] });
assert.match(editable, /data-media-gallery-rename-form/); assert.match(editable, /data-media-gallery-rename-form/);
assert.match(editable, /maxlength="255" required data-media-gallery-filename/); assert.match(editable, /maxlength="255" required data-media-gallery-filename/);
assert.match(editable, /data-media-gallery-extension/);
assert.match(editable, /data-media-gallery-rename-save/); assert.match(editable, /data-media-gallery-rename-save/);
}); });
test('media preview rename keeps the original filename extension fixed', async () => {
const handlers = {};
const submittedNames = [];
const input = { value: '', disabled: false, maxLength: 255 };
const extension = { textContent: '' };
const title = { textContent: '' };
const body = { replaceChildren() {}, appendChild() {}, querySelector() { return null; } };
const renameForm = { action: '', addEventListener(name, callback) { handlers['form:' + name] = callback; } };
const renameSave = { disabled: false };
const renameStatus = { hidden: true, classList: { add() {}, remove() {} } };
const modal = {
querySelector(selector) {
return {
'[data-media-gallery-preview-body]': body,
'#media-gallery-preview-modal-label': title,
'[data-media-gallery-rename-form]': renameForm,
'[data-media-gallery-filename]': input,
'[data-media-gallery-extension]': extension,
'[data-media-gallery-rename-save]': renameSave,
'[data-media-gallery-rename-status]': renameStatus
}[selector] || null;
},
addEventListener(name, callback) { handlers['modal:' + name] = callback; }
};
const column = { dataset: { mediaId: '9' }, querySelector() { return null; } };
const preview = {
dataset: { mediaName: 'original.png' },
getAttribute(name) { return { 'data-media-path': '/media/uploads/id.png', 'data-media-type': 'image', 'data-media-name': 'original.png' }[name]; },
closest() { return column; },
setAttribute() {}
};
const context = vm.createContext({
URLSearchParams,
window: { location: { search: '' }, innerWidth: 1200, pulseModal: { show() {} } },
document: {
getElementById() { return modal; },
querySelector() { return null; },
addEventListener(name, callback) { handlers['document:' + name] = callback; },
createElement() { return {}; }
},
fetch: async (_url, options) => {
if (options && options.body) {
submittedNames.push(JSON.parse(options.body).name);
return { ok: true, async json() { return { name: 'updated.png' }; } };
}
return { ok: true, async json() { return { assets: [], nextOffset: 0, hasMore: false, total: 0 }; } };
}
});
vm.runInContext(fs.readFileSync(path.join(__dirname, '../src/web/public/js/settings/media-library.js'), 'utf8'), context);
handlers['document:click']({ target: { closest() { return preview; } } });
assert.equal(input.value, 'original');
assert.equal(extension.textContent, '.png');
assert.equal(input.maxLength, 251);
input.value = 'updated';
await handlers['form:submit']({ preventDefault() {} });
assert.deepEqual(submittedNames, ['updated.png']);
});