mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 14:35:41 +00:00
refactor(dedup): consolidate byte / sanitize / clock / album-duration helpers (Phase L, part 2) (#692)
Findings 5-8 of the dedup audit: - F5 byte formatters: appUpdaterHelpers.fmtBytes + ZipDownloadOverlay .formatMB route through the existing formatBytes; a new formatMb (always-MB) backs playlistDetailHelpers.formatSize, AlbumHeader and the 4 inline DeviceSyncPreSyncModal expressions. SongInfoModal.format Size is intentionally left — it uses decimal (1e6) divisors, not 1024. - F6 sanitizeHtml: extracted to utils/sanitizeHtml.ts; AlbumHeader, ComposerDetail and the (now-empty, deleted) artistDetailHelpers use it directly. nowPlayingHelpers keeps its own export but now delegates to the shared sanitiser and only adds its trailing-link strip on top. - F7 album duration: BecauseYouLikeRail's formatAlbumDuration drops in favour of the shared formatHumanHoursMinutes. Behaviour note: total minutes now floor instead of round (<=1 min display difference, matches every other caller). - F8 clock time: extracted to utils/format/formatClockTime.ts; PlaybackDelayModal + QueueHeader use it (toLocaleTimeString and Intl.DateTimeFormat produced identical output). Behaviour preserved except the two explicitly noted divergences (F7 round->floor; F5 appUpdater/Zip now show GB above 1 GB instead of a large MB number).
This commit is contained in:
committed by
GitHub
parent
0153435787
commit
4b1dd3c29f
@@ -13,11 +13,6 @@ export function isNewer(a: string, b: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function fmtBytes(n: number): string {
|
||||
if (n < 1024 * 1024) return `${(n / 1024).toFixed(0)} KB`;
|
||||
return `${(n / 1024 / 1024).toFixed(1)} MB`;
|
||||
}
|
||||
|
||||
export interface GithubAsset {
|
||||
name: string;
|
||||
browser_download_url: string;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { SubsonicSong } from '../../api/subsonicTypes';
|
||||
import { sanitizeHtml as sanitizeHtmlBase } from '../sanitizeHtml';
|
||||
|
||||
export function formatTime(s: number): string {
|
||||
if (!s || isNaN(s)) return '0:00';
|
||||
@@ -22,21 +23,10 @@ export function formatTotalDuration(s: number): string {
|
||||
return `${sec}s`;
|
||||
}
|
||||
|
||||
/** Shared HTML sanitiser plus a now-playing-specific tweak: strip the trailing
|
||||
* "Read more on Last.fm" style link so clamped bios end cleanly. */
|
||||
export function sanitizeHtml(html: string): string {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, 'text/html');
|
||||
doc.querySelectorAll('script, style, iframe, object, embed, form, input, button, select, base, meta, link').forEach(el => el.remove());
|
||||
doc.querySelectorAll('*').forEach(el => {
|
||||
Array.from(el.attributes).forEach(attr => {
|
||||
const name = attr.name.toLowerCase();
|
||||
const val = attr.value.toLowerCase().trim();
|
||||
if (name.startsWith('on') || (name === 'href' && (val.startsWith('javascript:') || val.startsWith('data:'))) || (name === 'src' && (val.startsWith('javascript:') || val.startsWith('data:')))) {
|
||||
el.removeAttribute(attr.name);
|
||||
}
|
||||
});
|
||||
});
|
||||
// Strip trailing "Read more on Last.fm" style links for cleaner clamped bios.
|
||||
return doc.body.innerHTML.replace(/<a [^>]*>.*?<\/a>\.?\s*$/i, '').trim();
|
||||
return sanitizeHtmlBase(html).replace(/<a [^>]*>.*?<\/a>\.?\s*$/i, '').trim();
|
||||
}
|
||||
|
||||
export function isoToParts(iso: string): { month: string; day: string; weekday: string; time: string } | null {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { SubsonicSong } from '../../api/subsonicTypes';
|
||||
import { formatHumanHoursMinutes } from '../format/formatHumanDuration';
|
||||
import { formatMb } from '../format/formatBytes';
|
||||
|
||||
export function sanitizeFilename(name: string): string {
|
||||
return name
|
||||
@@ -10,8 +11,7 @@ export function sanitizeFilename(name: string): string {
|
||||
}
|
||||
|
||||
export function formatSize(bytes?: number): string {
|
||||
if (!bytes) return '';
|
||||
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
||||
return bytes ? formatMb(bytes) : '';
|
||||
}
|
||||
|
||||
export function totalDurationLabel(songs: SubsonicSong[]): string {
|
||||
|
||||
@@ -4,6 +4,12 @@ export function formatBytes(bytes: number): string {
|
||||
return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB`;
|
||||
}
|
||||
|
||||
/** Always-MB variant for size totals that are conventionally shown in MB
|
||||
* regardless of magnitude (album / playlist / device-sync totals). */
|
||||
export function formatMb(bytes: number): string {
|
||||
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
||||
}
|
||||
|
||||
/** Align hot-cache size slider (step 32 MB) to valid values. */
|
||||
export function snapHotCacheMb(v: number): number {
|
||||
const x = Math.min(20000, Math.max(32, Math.round(v)));
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
/** Localized wall-clock `HH:MM` for a timestamp (sleep-timer / queue-ETA labels). */
|
||||
export function formatClockTime(timestampMs: number): string {
|
||||
return new Date(timestampMs).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
/** Strip dangerous tags/attributes from server-provided HTML */
|
||||
/**
|
||||
* Strip dangerous tags / attributes from server-provided HTML (artist & album
|
||||
* biographies). Removes embedded/active elements and `on*` / `javascript:` /
|
||||
* `data:` handlers before the result is fed to `dangerouslySetInnerHTML`.
|
||||
*/
|
||||
export function sanitizeHtml(html: string): string {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, 'text/html');
|
||||
@@ -7,7 +11,11 @@ export function sanitizeHtml(html: string): string {
|
||||
Array.from(el.attributes).forEach(attr => {
|
||||
const name = attr.name.toLowerCase();
|
||||
const val = attr.value.toLowerCase().trim();
|
||||
if (name.startsWith('on') || (name === 'href' && (val.startsWith('javascript:') || val.startsWith('data:'))) || (name === 'src' && (val.startsWith('javascript:') || val.startsWith('data:')))) {
|
||||
if (
|
||||
name.startsWith('on') ||
|
||||
(name === 'href' && (val.startsWith('javascript:') || val.startsWith('data:'))) ||
|
||||
(name === 'src' && (val.startsWith('javascript:') || val.startsWith('data:')))
|
||||
) {
|
||||
el.removeAttribute(attr.name);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user