@arraypress/waveform-bar 1.3.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/waveform-bar.css +61 -1
- package/dist/waveform-bar.esm.js +450 -58
- package/dist/waveform-bar.js +450 -58
- package/dist/waveform-bar.min.css +1 -1
- package/dist/waveform-bar.min.js +6 -6
- package/package.json +9 -3
- package/src/css/waveform-bar.css +61 -1
- package/src/js/actions.js +2 -2
- package/src/js/core.js +529 -57
- package/src/js/dom.js +14 -1
- package/src/js/icons.js +3 -0
- package/src/js/utils.js +49 -4
package/src/js/dom.js
CHANGED
|
@@ -40,6 +40,8 @@ export function buildBarHTML(config) {
|
|
|
40
40
|
left += '</div>';
|
|
41
41
|
|
|
42
42
|
// --- Centre zone: waveform + time ---
|
|
43
|
+
// In classic mode (`waveform: false`) the embedded player renders its own
|
|
44
|
+
// built-in 'seekbar' style into this same container (see _initPlayer).
|
|
43
45
|
const centre = `<div class="wb-centre">
|
|
44
46
|
<div class="wb-waveform-container"></div>
|
|
45
47
|
<div class="wb-time"><span class="wb-time-current">0:00</span> / <span class="wb-time-total">0:00</span></div>
|
|
@@ -74,11 +76,22 @@ export function buildBarHTML(config) {
|
|
|
74
76
|
right += '</div>';
|
|
75
77
|
}
|
|
76
78
|
|
|
79
|
+
if (config.share) {
|
|
80
|
+
right += `<button class="wb-btn wb-btn-sm wb-share" aria-label="Share" title="Copy share link">${ICONS.share}</button>`;
|
|
81
|
+
}
|
|
82
|
+
|
|
77
83
|
if (config.showQueue) {
|
|
78
84
|
right += `<button class="wb-btn wb-btn-sm wb-queue-btn" aria-label="Queue" title="Queue">${ICONS.queue}</button>`;
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
right += '</div>';
|
|
82
88
|
|
|
83
|
-
|
|
89
|
+
// Collapse-to-pill toggle. A direct child of .wb-inner (its own zone) so
|
|
90
|
+
// the collapsed-pill CSS can hide .wb-centre/.wb-right while keeping this
|
|
91
|
+
// button visible as the "expand" affordance.
|
|
92
|
+
const collapse = config.collapsible
|
|
93
|
+
? `<button class="wb-btn wb-btn-sm wb-collapse" aria-label="Collapse" title="Collapse">${ICONS.collapse}</button>`
|
|
94
|
+
: '';
|
|
95
|
+
|
|
96
|
+
return `<div class="wb-inner">${left}${centre}${right}${collapse}</div>`;
|
|
84
97
|
}
|
package/src/js/icons.js
CHANGED
|
@@ -9,7 +9,10 @@ export const ICONS = {
|
|
|
9
9
|
prev: '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M6 6h2v12H6zm3.5 6l8.5 6V6z"/></svg>',
|
|
10
10
|
next: '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z"/></svg>',
|
|
11
11
|
queue: '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z"/></svg>',
|
|
12
|
+
share: '<svg viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/><polyline points="16 6 12 2 8 6"/><line x1="12" y1="2" x2="12" y2="15"/></svg>',
|
|
12
13
|
music: '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor" opacity="0.5"><path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55C7.79 13 6 14.79 6 17s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/></svg>',
|
|
14
|
+
collapse: '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>',
|
|
15
|
+
expand: '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg>',
|
|
13
16
|
volHigh: '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/></svg>',
|
|
14
17
|
volLow: '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M18.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM5 9v6h4l5 5V4L9 9H5z"/></svg>',
|
|
15
18
|
volMute: '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/></svg>',
|
package/src/js/utils.js
CHANGED
|
@@ -27,6 +27,30 @@ export function escapeHtml(str) {
|
|
|
27
27
|
return d.innerHTML;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Whether a URL is safe to navigate to (assign to `location.href`).
|
|
32
|
+
* Allows only `http`/`https` and relative URLs, rejecting `javascript:`,
|
|
33
|
+
* `data:`, `blob:`, `vbscript:` and other script-bearing schemes.
|
|
34
|
+
*
|
|
35
|
+
* TODO(harden): adopt `WaveformPlayer.utils.isSafeHref` once the peer dep
|
|
36
|
+
* is bumped to ^1.8.0 (which ships this helper). Inlined here so the
|
|
37
|
+
* open-redirect / XSS guard is fixed without a peer bump.
|
|
38
|
+
*
|
|
39
|
+
* @param {string} url
|
|
40
|
+
* @returns {boolean}
|
|
41
|
+
*/
|
|
42
|
+
export function isSafeHref(url) {
|
|
43
|
+
if (typeof url !== 'string' || url === '') return false;
|
|
44
|
+
try {
|
|
45
|
+
// Resolve relative URLs against the current document; only the
|
|
46
|
+
// scheme matters for the safety decision.
|
|
47
|
+
const u = new URL(url, location.href);
|
|
48
|
+
return u.protocol === 'http:' || u.protocol === 'https:';
|
|
49
|
+
} catch (e) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
30
54
|
/**
|
|
31
55
|
* Format seconds to M:SS
|
|
32
56
|
* @param {number} seconds
|
|
@@ -48,11 +72,32 @@ export function parseTrackFromElement(el) {
|
|
|
48
72
|
const url = el.dataset.wbUrl || el.dataset.url;
|
|
49
73
|
if (!url) return null;
|
|
50
74
|
|
|
75
|
+
// Parse + shape-coerce. JSON.parse only validates the syntax — a value
|
|
76
|
+
// like '"x"' or '[1,2]' parses cleanly but is the wrong shape and would
|
|
77
|
+
// strand the bar downstream (e.g. `.map()` on a non-array). Coerce each
|
|
78
|
+
// field to the shape the rest of the code expects.
|
|
51
79
|
let meta = {};
|
|
52
|
-
try {
|
|
80
|
+
try {
|
|
81
|
+
const parsed = JSON.parse(el.dataset.wbMeta || el.dataset.meta || '{}');
|
|
82
|
+
meta = (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) ? parsed : {};
|
|
83
|
+
} catch (e) {}
|
|
84
|
+
|
|
85
|
+
let markers = [];
|
|
86
|
+
try {
|
|
87
|
+
const parsed = JSON.parse(el.dataset.wbMarkers || el.dataset.markers || 'null');
|
|
88
|
+
markers = Array.isArray(parsed) ? parsed : [];
|
|
89
|
+
} catch (e) {}
|
|
90
|
+
// Coerce each marker time to a finite number; drop entries that aren't
|
|
91
|
+
// usable objects or whose time can't be parsed.
|
|
92
|
+
markers = markers
|
|
93
|
+
.map(m => (m && typeof m === 'object') ? {...m, time: Number(m.time)} : null)
|
|
94
|
+
.filter(m => m && Number.isFinite(m.time));
|
|
53
95
|
|
|
54
|
-
let
|
|
55
|
-
try {
|
|
96
|
+
let waveform = null;
|
|
97
|
+
try {
|
|
98
|
+
const parsed = JSON.parse(el.dataset.wbWaveform || el.dataset.waveform || 'null');
|
|
99
|
+
waveform = Array.isArray(parsed) ? parsed : null;
|
|
100
|
+
} catch (e) {}
|
|
56
101
|
|
|
57
102
|
return {
|
|
58
103
|
url,
|
|
@@ -65,7 +110,7 @@ export function parseTrackFromElement(el) {
|
|
|
65
110
|
duration: el.dataset.wbDuration || el.dataset.duration || '',
|
|
66
111
|
bpm: el.dataset.wbBpm || el.dataset.bpm || '',
|
|
67
112
|
key: el.dataset.wbKey || el.dataset.key || '',
|
|
68
|
-
waveform
|
|
113
|
+
waveform,
|
|
69
114
|
markers,
|
|
70
115
|
favorited: el.dataset.wbFavorited === 'true',
|
|
71
116
|
inCart: el.dataset.wbInCart === 'true',
|