From 1d340c08b80b97baebf5875c7fc8ad8545f09fb6 Mon Sep 17 00:00:00 2001 From: 4gray Date: Mon, 20 Apr 2026 00:24:59 +0200 Subject: [PATCH] feat: implement file title normalization and path handling in playlist upload Entire-Checkpoint: c6e522b4276c --- .../add-playlist-dialog.component.spec.ts | 33 +++++++++++++++++-- .../add-playlist-dialog.component.ts | 14 +++++++- .../lib/file-upload/file-upload.component.ts | 13 +++++++- 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/libs/playlist/import/feature/src/lib/add-playlist-dialog/add-playlist-dialog.component.spec.ts b/libs/playlist/import/feature/src/lib/add-playlist-dialog/add-playlist-dialog.component.spec.ts index 80e097fcc..a194499af 100644 --- a/libs/playlist/import/feature/src/lib/add-playlist-dialog/add-playlist-dialog.component.spec.ts +++ b/libs/playlist/import/feature/src/lib/add-playlist-dialog/add-playlist-dialog.component.spec.ts @@ -3,6 +3,7 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MatSnackBar } from '@angular/material/snack-bar'; import { Store } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; +import { PlaylistActions } from 'm3u-state'; import { DataService } from 'services'; import { PLAYLIST_PARSE_BY_URL } from 'shared-interfaces'; import { AddPlaylistDialogComponent } from './add-playlist-dialog.component'; @@ -11,6 +12,7 @@ describe('AddPlaylistDialogComponent', () => { let component: AddPlaylistDialogComponent; let dataService: { sendIpcEvent: jest.Mock }; let dialogRef: { close: jest.Mock }; + let store: { dispatch: jest.Mock }; beforeEach(() => { dataService = { @@ -19,6 +21,9 @@ describe('AddPlaylistDialogComponent', () => { dialogRef = { close: jest.fn(), }; + store = { + dispatch: jest.fn(), + }; TestBed.configureTestingModule({ providers: [ @@ -32,9 +37,7 @@ describe('AddPlaylistDialogComponent', () => { }, { provide: Store, - useValue: { - dispatch: jest.fn(), - }, + useValue: store, }, { provide: MatSnackBar, @@ -102,4 +105,28 @@ describe('AddPlaylistDialogComponent', () => { ); expect(dialogRef.close).toHaveBeenCalled(); }); + + it('strips playlist extensions from uploaded file titles', () => { + component.handlePlaylist({ + uploadEvent: { + target: { + result: '#EXTM3U', + }, + } as unknown as Event, + file: { + name: 'Local Source.m3u', + path: '/tmp/Local Source.m3u', + } as File, + }); + + expect(store.dispatch).toHaveBeenCalledWith( + PlaylistActions.parsePlaylist({ + uploadType: 'FILE', + playlist: '#EXTM3U', + title: 'Local Source', + path: '/tmp/Local Source.m3u', + }) + ); + expect(dialogRef.close).toHaveBeenCalled(); + }); }); diff --git a/libs/playlist/import/feature/src/lib/add-playlist-dialog/add-playlist-dialog.component.ts b/libs/playlist/import/feature/src/lib/add-playlist-dialog/add-playlist-dialog.component.ts index fe259f373..7c09dc0f1 100644 --- a/libs/playlist/import/feature/src/lib/add-playlist-dialog/add-playlist-dialog.component.ts +++ b/libs/playlist/import/feature/src/lib/add-playlist-dialog/add-playlist-dialog.component.ts @@ -122,7 +122,7 @@ export class AddPlaylistDialogComponent { PlaylistActions.parsePlaylist({ uploadType: 'FILE', playlist, - title: payload.file.name, + title: this.normalizeImportedFileTitle(payload.file.name), path: (payload.file as File & { path?: string }).path, }) ); @@ -181,4 +181,16 @@ export class AddPlaylistDialogComponent { const normalizedValue = value?.trim(); return normalizedValue ? normalizedValue : undefined; } + + private normalizeImportedFileTitle(filename: string): string { + const normalizedFilename = filename.trim(); + if (!normalizedFilename) { + return filename; + } + + return ( + normalizedFilename.replace(/\.(m3u8?|pls|txt)$/i, '') || + normalizedFilename + ); + } } diff --git a/libs/playlist/import/feature/src/lib/file-upload/file-upload.component.ts b/libs/playlist/import/feature/src/lib/file-upload/file-upload.component.ts index 5c63d47af..8607ce291 100644 --- a/libs/playlist/import/feature/src/lib/file-upload/file-upload.component.ts +++ b/libs/playlist/import/feature/src/lib/file-upload/file-upload.component.ts @@ -38,10 +38,21 @@ export class FileUploadComponent { } onPicked(input: HTMLInputElement): void { - const file = input.files?.[0]; + const file = input.files?.[0] as (File & { path?: string }) | undefined; + const pathOverride = input.dataset['filePathOverride']; + + if (file && !file.path && pathOverride) { + Object.defineProperty(file, 'path', { + configurable: true, + value: pathOverride, + }); + } + if (file) { this.setFile(file); } + + delete input.dataset['filePathOverride']; input.value = ''; }