feat: Jellyfin Plugin UI Integration #51

Merged
devitq merged 13 commits from feature/jellyfin-plugin-ui-integration-2323592149917875874 into feat/implement-jellyfin-plugin-46 2026-05-22 13:36:25 +00:00
Showing only changes of commit e0b922b39d - Show all commits
1
@@ -113,10 +113,11 @@
const overlay = document.createElement('div');
copilot-pull-request-reviewer[bot] commented 2026-05-20 17:42:37 +00:00 (Migrated from github.com)
Review

itemId is taken directly from the page URL and then used as {filmId} in the rating POST path. Elsewhere in the plugin (sync/playback events) Jellyfin ids are serialized using ToString("N") (no dashes), so this can lead to inconsistent identifiers being sent to the backend depending on URL format. Consider normalizing the URL id to the same canonical format before calling the API (and applying encodeURIComponent when interpolating path segments).

`itemId` is taken directly from the page URL and then used as `{filmId}` in the rating POST path. Elsewhere in the plugin (sync/playback events) Jellyfin ids are serialized using `ToString("N")` (no dashes), so this can lead to inconsistent identifiers being sent to the backend depending on URL format. Consider normalizing the URL `id` to the same canonical format before calling the API (and applying `encodeURIComponent` when interpolating path segments).
overlay.className = 'dialogBackdrop dialogBackdropOpened';
overlay.style.zIndex = '99998';
overlay.style.backgroundColor = 'rgba(0,0,0,0.6)';
overlay.style.backgroundColor = 'rgba(0,0,0,0.7)';
overlay.style.position = 'fixed';
overlay.style.top = '0'; overlay.style.left = '0'; overlay.style.right = '0'; overlay.style.bottom = '0';
overlay.style.backdropFilter = 'blur(4px)';
overlay.style.backdropFilter = 'blur(8px)';
overlay.style.opacity = '1';
return overlay;
}
@@ -127,19 +128,20 @@
dialog.style.top = '50%'; dialog.style.left = '50%';
dialog.style.transform = 'translate(-50%, -50%)';
dialog.style.zIndex = '99999';
dialog.style.padding = '2em';
dialog.style.minWidth = '320px';
dialog.style.padding = '2.5em';
dialog.style.minWidth = '350px';
dialog.style.backgroundColor = '#1a1a1a';
dialog.style.borderRadius = '1.5em';
dialog.style.color = 'white';
dialog.style.boxShadow = '0 10px 25px rgba(0,0,0,0.5)';
dialog.style.border = '1px solid #333';
dialog.style.boxShadow = '0 20px 50px rgba(0,0,0,0.8)';
dialog.style.border = '1px solid #444';
dialog.style.opacity = '1';
dialog.innerHTML = `
<h2 style="margin-top:0; text-align:center; font-weight:400;">${title}</h2>
<div class="dialog-content" style="margin:1.5em 0;"></div>
<div class="dialog-footer" style="display:flex; gap:1em;">
<button is="emby-button" class="emby-button button-flat btnCancel" style="flex:1; color: white;">Cancel</button>
<h2 style="margin-top:0; text-align:center; font-weight:400; color:white; opacity:1;">${title}</h2>
<div class="dialog-content" style="margin:1.5em 0; opacity:1;"></div>
<div class="dialog-footer" style="display:flex; gap:1em; opacity:1;">
<button is="emby-button" class="emby-button button-flat btnCancel" style="flex:1; color: white !important; opacity:1;">Cancel</button>
</div>
`;
return dialog;
@@ -150,7 +152,7 @@
const dialog = createDialogBase('Rate on MovieNight');
const content = dialog.querySelector('.dialog-content');
content.innerHTML = `<div class="rating-grid" style="display:grid; grid-template-columns:repeat(5, 1fr); gap:0.6em;"></div>`;
content.innerHTML = `<div class="rating-grid" style="display:grid; grid-template-columns:repeat(5, 1fr); gap:0.8em;"></div>`;
const grid = content.querySelector('.rating-grid');
const cleanup = () => { if (overlay.parentNode) document.body.removeChild(overlay); };
@@ -161,6 +163,11 @@
btn.className = 'emby-button raised';
btn.innerText = i;
btn.style.padding = '0.8em 0';
btn.style.textAlign = 'center';
btn.style.display = 'flex';
btn.style.alignItems = 'center';
btn.style.justifyContent = 'center';
btn.style.fontSize = '1.2em';
btn.onclick = async () => { cleanup(); await submitRating(itemId, i); };
grid.appendChild(btn);
}
@@ -178,19 +185,20 @@
const footer = dialog.querySelector('.dialog-footer');
content.innerHTML = `
<div style="margin-bottom:1em;">
<label style="display:block; margin-bottom:0.4em; font-size:0.9em; opacity:0.8;">Movie Title</label>
<input type="text" class="emby-input txtTitle" style="width:100%; box-sizing:border-box;" placeholder="e.g. Inception">
<div style="margin-bottom:1.5em;">
<label style="display:block; margin-bottom:0.6em; font-size:1em; opacity:0.9; color:white;">Movie Title</label>
<input type="text" class="emby-input txtTitle" style="width:100%; box-sizing:border-box; background:#333; border:1px solid #555; color:white; padding:0.6em;" placeholder="e.g. Inception">
</div>
<div>
<label style="display:block; margin-bottom:0.4em; font-size:0.9em; opacity:0.8;">Stream URL (Optional)</label>
<input type="text" class="emby-input txtUrl" style="width:100%; box-sizing:border-box;" placeholder="http://...">
<label style="display:block; margin-bottom:0.6em; font-size:1em; opacity:0.9; color:white;">Stream URL (Optional)</label>
<input type="text" class="emby-input txtUrl" style="width:100%; box-sizing:border-box; background:#333; border:1px solid #555; color:white; padding:0.6em;" placeholder="http://...">
</div>
`;
const btnAdd = document.createElement('button');
btnAdd.className = 'emby-button raised button-submit';
btnAdd.style.flex = '2';
btnAdd.style.backgroundColor = '#0064d2';
btnAdd.innerHTML = '<span>Add Film</span>';
footer.insertBefore(btnAdd, footer.firstChild);