Compare commits

..

No commits in common. "85cc4957d8474dcb6f16e0d5eb2a8fe462c2f31f" and "82df586c9ed9632ea7e053de12be8e223b4ebc1b" have entirely different histories.

7 changed files with 19 additions and 19 deletions

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "4.6.115", "version": "4.6.114",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "4.6.115", "version": "4.6.114",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"axios": "^1.6.0", "axios": "^1.6.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "4.6.115", "version": "4.6.114",
"description": "Twitch VOD Manager - Download Twitch VODs easily", "description": "Twitch VOD Manager - Download Twitch VODs easily",
"main": "dist/main.js", "main": "dist/main.js",
"author": "xRangerDE", "author": "xRangerDE",

View File

@ -119,10 +119,10 @@ function renderArchiveSearchResults(result: ArchiveSearchResult): void {
const typeBadge = `<span class="archive-type-badge ${hit.type === 'live' ? 'live' : 'vod'}">${hit.type === 'live' ? 'LIVE' : 'VOD'}</span>`; const typeBadge = `<span class="archive-type-badge ${hit.type === 'live' ? 'live' : 'vod'}">${hit.type === 'live' ? 'LIVE' : 'VOD'}</span>`;
const safeFullAttr = hit.fullPath.replace(/\\/g, '\\\\').replace(/'/g, "\\'"); const safeFullAttr = hit.fullPath.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
const chatBtn = hit.chatPath const chatBtn = hit.chatPath
? `<button type="button" class="queue-detail-btn" onclick="openEventsOrChat('${safeFullAttr.replace(/\.(mp4|mkv|ts|m4v)$/i, '.chat.jsonl')}', '${escapeArchiveHtml(hit.fileName)}', 'chat')">${escapeArchiveHtml(UI_TEXT.static.archiveViewChat || 'Chat')}</button>` ? `<button class="queue-detail-btn" onclick="openEventsOrChat('${safeFullAttr.replace(/\.(mp4|mkv|ts|m4v)$/i, '.chat.jsonl')}', '${escapeArchiveHtml(hit.fileName)}', 'chat')">${escapeArchiveHtml(UI_TEXT.static.archiveViewChat || 'Chat')}</button>`
: ''; : '';
const eventsBtn = hit.eventsPath const eventsBtn = hit.eventsPath
? `<button type="button" class="queue-detail-btn" onclick="openEventsOrChat('${(hit.eventsPath || '').replace(/\\/g, '\\\\').replace(/'/g, "\\'")}', '${escapeArchiveHtml(hit.fileName)}', 'events')">${escapeArchiveHtml(UI_TEXT.static.archiveViewEvents || 'Events')}</button>` ? `<button class="queue-detail-btn" onclick="openEventsOrChat('${(hit.eventsPath || '').replace(/\\/g, '\\\\').replace(/'/g, "\\'")}', '${escapeArchiveHtml(hit.fileName)}', 'events')">${escapeArchiveHtml(UI_TEXT.static.archiveViewEvents || 'Events')}</button>`
: ''; : '';
return ` return `
<div class="archive-result-row"> <div class="archive-result-row">
@ -136,8 +136,8 @@ function renderArchiveSearchResults(result: ArchiveSearchResult): void {
<div class="archive-result-size">${escapeArchiveHtml(formatBytesForArchive(hit.size))}</div> <div class="archive-result-size">${escapeArchiveHtml(formatBytesForArchive(hit.size))}</div>
</div> </div>
<div class="archive-result-actions"> <div class="archive-result-actions">
<button type="button" class="queue-detail-btn" onclick="openFilePath('${safeFullAttr}')">${escapeArchiveHtml(UI_TEXT.static.archiveOpen || 'Oeffnen')}</button> <button class="queue-detail-btn" onclick="openFilePath('${safeFullAttr}')">${escapeArchiveHtml(UI_TEXT.static.archiveOpen || 'Oeffnen')}</button>
<button type="button" class="queue-detail-btn" onclick="showFileInFolder('${safeFullAttr}')">${escapeArchiveHtml(UI_TEXT.static.archiveShowInFolder || 'Ordner')}</button> <button class="queue-detail-btn" onclick="showFileInFolder('${safeFullAttr}')">${escapeArchiveHtml(UI_TEXT.static.archiveShowInFolder || 'Ordner')}</button>
${chatBtn} ${chatBtn}
${eventsBtn} ${eventsBtn}
</div> </div>

View File

@ -132,7 +132,7 @@ function renderStreamerProfileCard(p: StreamerProfile): void {
</div> </div>
${p.currentTitle ? `<div class="streamer-profile-live-title">${escapeProfileHtml(p.currentTitle)}</div>` : ''} ${p.currentTitle ? `<div class="streamer-profile-live-title">${escapeProfileHtml(p.currentTitle)}</div>` : ''}
${p.currentGame ? `<div class="streamer-profile-live-game">${escapeProfileHtml(p.currentGame)}</div>` : ''} ${p.currentGame ? `<div class="streamer-profile-live-game">${escapeProfileHtml(p.currentGame)}</div>` : ''}
<button type="button" class="streamer-profile-btn primary streamer-profile-live-rec-btn" onclick="event.stopPropagation(); triggerLiveRecordingFromProfile('${safeLogin}')">${escapeProfileHtml(UI_TEXT.profile.recordNow)}</button> <button class="streamer-profile-btn primary streamer-profile-live-rec-btn" onclick="event.stopPropagation(); triggerLiveRecordingFromProfile('${safeLogin}')">${escapeProfileHtml(UI_TEXT.profile.recordNow)}</button>
</div> </div>
</div> </div>
` : ''; ` : '';
@ -157,8 +157,8 @@ function renderStreamerProfileCard(p: StreamerProfile): void {
</div> </div>
</div> </div>
<div class="streamer-profile-actions"> <div class="streamer-profile-actions">
<button type="button" class="streamer-profile-btn primary" onclick="openTwitchChannel('${safeUrl}')">${escapeProfileHtml(UI_TEXT.profile.openTwitch)}</button> <button class="streamer-profile-btn primary" onclick="openTwitchChannel('${safeUrl}')">${escapeProfileHtml(UI_TEXT.profile.openTwitch)}</button>
<button type="button" class="streamer-profile-btn" onclick="refreshStreamerProfile('${safeLogin}')">${escapeProfileHtml(UI_TEXT.profile.refresh)}</button> <button class="streamer-profile-btn" onclick="refreshStreamerProfile('${safeLogin}')">${escapeProfileHtml(UI_TEXT.profile.refresh)}</button>
</div> </div>
</div> </div>
${liveCard} ${liveCard}

View File

@ -21,23 +21,23 @@ function renderQueueItemFileActions(item: QueueItem): string {
// full VOD download). For multi-part downloads "open the first part" is // full VOD download). For multi-part downloads "open the first part" is
// surprising — the user almost always wants the folder. // surprising — the user almost always wants the folder.
if (item.outputFiles.length === 1) { if (item.outputFiles.length === 1) {
buttons.push(`<button type="button" class="queue-detail-btn" onclick="invokeOpenFile('${safeFirstAttr}')">${escapeHtml(UI_TEXT.queue.openFile)}</button>`); buttons.push(`<button class="queue-detail-btn" onclick="invokeOpenFile('${safeFirstAttr}')">${escapeHtml(UI_TEXT.queue.openFile)}</button>`);
} }
buttons.push(`<button type="button" class="queue-detail-btn" onclick="invokeShowInFolder('${safeFirstAttr}')">${escapeHtml(UI_TEXT.queue.showInFolder)}</button>`); buttons.push(`<button class="queue-detail-btn" onclick="invokeShowInFolder('${safeFirstAttr}')">${escapeHtml(UI_TEXT.queue.showInFolder)}</button>`);
// Surface a "View chat" button when a sibling chat file exists in the // Surface a "View chat" button when a sibling chat file exists in the
// outputs list. Single click opens the in-app viewer modal. // outputs list. Single click opens the in-app viewer modal.
const chatFile = item.outputFiles.find((f) => /\.chat\.json(l)?$/i.test(f)); const chatFile = item.outputFiles.find((f) => /\.chat\.json(l)?$/i.test(f));
if (chatFile) { if (chatFile) {
const safeChatAttr = chatFile.replace(/'/g, "\\'").replace(/"/g, '&quot;'); const safeChatAttr = chatFile.replace(/'/g, "\\'").replace(/"/g, '&quot;');
buttons.push(`<button type="button" class="queue-detail-btn" onclick="openChatViewer('${safeChatAttr}', '${escapeHtml(item.title || item.streamer || '').replace(/'/g, "\\'")}')">${escapeHtml(UI_TEXT.queue.viewChat)}</button>`); buttons.push(`<button class="queue-detail-btn" onclick="openChatViewer('${safeChatAttr}', '${escapeHtml(item.title || item.streamer || '').replace(/'/g, "\\'")}')">${escapeHtml(UI_TEXT.queue.viewChat)}</button>`);
} }
// Same pattern for the .events.jsonl sidecar — title/game change timeline. // Same pattern for the .events.jsonl sidecar — title/game change timeline.
const eventsFile = item.outputFiles.find((f) => /\.events\.jsonl$/i.test(f)); const eventsFile = item.outputFiles.find((f) => /\.events\.jsonl$/i.test(f));
if (eventsFile) { if (eventsFile) {
const safeEventsAttr = eventsFile.replace(/'/g, "\\'").replace(/"/g, '&quot;'); const safeEventsAttr = eventsFile.replace(/'/g, "\\'").replace(/"/g, '&quot;');
buttons.push(`<button type="button" class="queue-detail-btn" onclick="openEventsViewer('${safeEventsAttr}', '${escapeHtml(item.title || item.streamer || '').replace(/'/g, "\\'")}')">${escapeHtml(UI_TEXT.queue.viewEvents)}</button>`); buttons.push(`<button class="queue-detail-btn" onclick="openEventsViewer('${safeEventsAttr}', '${escapeHtml(item.title || item.streamer || '').replace(/'/g, "\\'")}')">${escapeHtml(UI_TEXT.queue.viewEvents)}</button>`);
} }
const fileLabel = item.outputFiles.length === 1 const fileLabel = item.outputFiles.length === 1

View File

@ -266,8 +266,8 @@ function buildVodCardHtml(vod: VOD, streamer: string, downloadedIds?: Set<string
</div> </div>
</div> </div>
<div class="vod-actions"> <div class="vod-actions">
<button type="button" class="vod-btn secondary" data-vod-action="trim">${escapeHtml(UI_TEXT.vods.trimButton)}</button> <button class="vod-btn secondary" data-vod-action="trim">${escapeHtml(UI_TEXT.vods.trimButton)}</button>
<button type="button" class="vod-btn primary" data-vod-action="queue">${escapeHtml(UI_TEXT.vods.addQueue)}</button> <button class="vod-btn primary" data-vod-action="queue">${escapeHtml(UI_TEXT.vods.addQueue)}</button>
</div> </div>
</div> </div>
`; `;

View File

@ -1639,9 +1639,9 @@ function renderMergeFiles(): void {
<div class="file-order">${index + 1}</div> <div class="file-order">${index + 1}</div>
<div class="file-name" title="${file}">${name}</div> <div class="file-name" title="${file}">${name}</div>
<div class="file-actions"> <div class="file-actions">
<button type="button" class="file-btn" onclick="moveMergeFile(${index}, -1)" ${index === 0 ? 'disabled' : ''}>&#9650;</button> <button class="file-btn" onclick="moveMergeFile(${index}, -1)" ${index === 0 ? 'disabled' : ''}>&#9650;</button>
<button type="button" class="file-btn" onclick="moveMergeFile(${index}, 1)" ${index === mergeFiles.length - 1 ? 'disabled' : ''}>&#9660;</button> <button class="file-btn" onclick="moveMergeFile(${index}, 1)" ${index === mergeFiles.length - 1 ? 'disabled' : ''}>&#9660;</button>
<button type="button" class="file-btn remove" onclick="removeMergeFile(${index})">x</button> <button class="file-btn remove" onclick="removeMergeFile(${index})">x</button>
</div> </div>
</div> </div>
`; `;