// ==UserScript==
// @name TheTVDB Shortcut & Overlay toolkit
// @namespace http://tampermonkey.net/
// @version 1.14
// @description Adds various floating buttons to make life easier when adding Shows and Movies to TVDB, Also includes a 'One Fill/Click' Series & Movie Creator along with the ability to add 25 episodes in just a couple of clicks.
// @author GiGo
// @license MIT
// @match https://*.thetvdb.com/series/*/seasons/*
// @match https://thetvdb.com/series/*/seasons/*
// @match https://*.thetvdb.com/movies/create*
// @match https://thetvdb.com/movies/create*
// @match https://*.thetvdb.com/series/create*
// @match https://thetvdb.com/series/create*
// @match https://*.thetvdb.com/movies/*/edit*
// @match https://thetvdb.com/movies/*/edit*
// @match https://*.thetvdb.com/series/*/edit*
// @match https://thetvdb.com/series/*/edit*
// @match https://thetvdb.com/search*
// @match https://www.thetvdb.com/search*
// @match https://thetvdb.com/movies/*
// @match https://www.thetvdb.com/movies/*
// @match https://thetvdb.com/series/*
// @match https://www.thetvdb.com/series/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
// ------------------------------
// SETTINGS
// ------------------------------
// Change this if you prefer a different default country label
const PREFERRED_COUNTRY = 'Great Britain';
const PREFERRED_VALUE = 'gbr';
// --- THE DATA BELOW HERE IS EDITABLE, USE THE SCRAPERS I HAVE MADE TO COPY TO THE CLIPBAORD AND PASTE IT BELOW ----
// --- EDITABLE DATA: up to 25 episodes ---
const episodes = [
{ name: "Pilot", overview: "Diagnosed with lung cancer, high-school chemistry teacher Walter White teams with ex-student Jesse to cook meth in an RV, using science -- and sudden violence -- to secure their first batch and escape two lethal dealers.", firstAired: "28/09/08", runtime: "" },
{ name: "Cat's in the Bag...", overview: "One body dissolves in acid while another dealer lies captive. Walt and Jesse scramble to erase evidence, and Walt faces whether he can kill to keep the fledgling operation -- and Jesse -- safe.", firstAired: "05/10/08", runtime: "" },
{ name: "...And the Bag's in the River", overview: "Chemo looming, Walt chains wounded Krazy-8 in Jesse's basement, weighing murder against morality. A smashed dinner plate decides his darkest step yet.", firstAired: "12/10/08", runtime: "" },
{ name: "Cancer Man", overview: "Walt's diagnosis rocks the family while flashbacks reveal how Gray Matter upended his career. Resentment nudges him further into crime as Hank's DEA war stories fascinate Walt Jr.", firstAired: "19/10/08", runtime: "" },
{ name: "Gray Matter", overview: "Ex-partners Elliott and Gretchen offer to fund treatment, but pride makes Walt refuse. Cash-strapped, he reignites the meth venture while Skyler digs into Jesse's drug past.", firstAired: "26/10/08", runtime: "" },
{ name: "Crazy Handful of Nothin'", overview: "Shaved and reborn as Heisenberg, Walt crashes Tuco's lair with explosive mercury fulminate, swapping chemistry for intimidation. The violent payoff bankrolls chemo but draws Hank's attention.", firstAired: "02/11/08", runtime: "" },
{ name: "A No-Rough-Stuff-Type Deal", overview: "Under pressure from a dangerous new customer, Walt and Jesse race to expand their operation, hatching an audacious plan for crucial supplies as Walt struggles to keep his double life from collapsing.", firstAired: "09/11/08", runtime: "" }
];
// --- END EDITABLE DATA ---
// --- THE DATA ABOVE HERE IS EDITABLE, USE THE SCRAPERS I HAVE MADE TO COPY TO THE CLIPBAORD AND PASTE IT ABOVE ----
// Helper: wait for an element using a selector
function waitFor(selector, timeout = 10000) {
return new Promise((resolve, reject) => {
const t0 = Date.now();
(function check() {
const el = document.querySelector(selector);
if (el) return resolve(el);
if (Date.now() - t0 > timeout) return reject(new Error('Timed out waiting for ' + selector));
requestAnimationFrame(check);
})();
});
}
// Precise episode-row helper: only count multirow-item elements that contain episode fields
function getEpisodeRows() {
return Array.from(document.querySelectorAll('.multirow-item'))
.filter(r => r.querySelector('input[name="name[]"], textarea[name="overview[]"], input[name="date[]"], input[name="runtime[]"]'));
}
// wait until number of episode rows >= target or timeout
function waitForRowCount(targetCount, timeout = 10000) {
return new Promise((resolve, reject) => {
const t0 = Date.now();
(function check() {
const rows = getEpisodeRows();
if (rows.length >= targetCount) return resolve(rows);
if (Date.now() - t0 > timeout) return reject(new Error('Timed out waiting for rows: ' + targetCount));
requestAnimationFrame(check);
})();
});
}
// Robust ensureRows: retries until desiredTotal episode rows exist (cap 25), dismisses limit modal, retries final clicks
async function ensureRows(desiredTotal) {
const MAX_TOTAL = 25;
const targetDesired = Math.min(Math.max(Number(desiredTotal) || 0, 0), MAX_TOTAL);
if (targetDesired <= 0) return;
const getExistingCount = () => getEpisodeRows().length;
const addSelectors = [
'button.btn.btn-info.multirow-add',
'button.multirow-add',
'.multirow-add',
'button[type="button"].btn.btn-info',
'button.btn-info',
'button'
];
function findByText(text) {
const candidates = Array.from(document.querySelectorAll('button, a'));
return candidates.find(el => (el.textContent || '').trim().toLowerCase() === text.toLowerCase()) || null;
}
function findAddButton() {
for (const sel of addSelectors) {
const el = document.querySelector(sel);
if (el && el.offsetParent !== null) return el;
}
const byText = findByText('Add Another') || findByText('Add another') || findByText('Add episode') || findByText('Add');
if (byText && byText.offsetParent !== null) return byText;
return null;
}
// Try to dismiss limit modal (best-effort)
function dismissLimitModal() {
const modalSelectors = ['dialog', '.modal', '.tvdb-modal', '.ui-dialog', '.modal-dialog', '.alert'];
for (const sel of modalSelectors) {
const nodes = Array.from(document.querySelectorAll(sel));
for (const n of nodes) {
if (!n.offsetParent) continue;
const txt = (n.textContent || '').replace(/\s+/g, ' ').trim();
if (/limit reached/i.test(txt) || /unable to add another/i.test(txt)) {
const ok = Array.from(n.querySelectorAll('button, a')).find(b => /ok|close|dismiss|cancel/i.test((b.textContent||'')));
if (ok) { try { ok.click(); } catch(e){ ok.dispatchEvent(new MouseEvent('click',{ bubbles:true, cancelable:true })); } }
else {
const docOk = Array.from(document.querySelectorAll('button, a')).find(b => /ok|close|dismiss/i.test((b.textContent||'')));
if (docOk) { try { docOk.click(); } catch(e){ docOk.dispatchEvent(new MouseEvent('click',{ bubbles:true, cancelable:true })); } }
}
console.info('ensureRows: dismissed limit modal');
return true;
}
}
}
return false;
}
// click helper
async function tryClickAdd(btn) {
if (!btn) return false;
try { btn.click(); } catch (e) { btn.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true })); }
await new Promise(r => setTimeout(r, 220)); // wait for DOM update/modal
if (dismissLimitModal()) {
await new Promise(r => setTimeout(r, 260));
return false; // modal blocked this add
}
return true;
}
const start = Date.now();
const TIMEOUT = 15000;
const MAX_ATTEMPTS = 40;
let attempts = 0;
// keep trying until we reach desired rows, timeout, or attempts exhausted
while (getExistingCount() < targetDesired && (Date.now() - start) < TIMEOUT && attempts < MAX_ATTEMPTS) {
attempts++;
const addBtn = findAddButton();
if (!addBtn) {
await new Promise(r => setTimeout(r, 180));
continue;
}
await tryClickAdd(addBtn);
await new Promise(r => setTimeout(r, 120));
}
// final verification: if still short, do a final sweep of clicking any visible add buttons a few times
let finalAttempts = 0;
while (getExistingCount() < targetDesired && finalAttempts < 6) {
finalAttempts++;
const addBtn = findAddButton();
if (!addBtn) break;
await tryClickAdd(addBtn);
await new Promise(r => setTimeout(r, 220));
}
const finalCount = getExistingCount();
if (finalCount < targetDesired) {
console.warn(`ensureRows: final rows ${finalCount} < requested ${targetDesired}. TheTVDB may have blocked creation or reached account limit.`);
} else {
console.info(`ensureRows: created rows: ${finalCount}`);
}
await new Promise(r => setTimeout(r, 120));
}
// ------------------------------
// Bulk-add: Insert Fill button
// ------------------------------
async function insertFillButton() {
try {
// Only show on bulk-add-like pages (series seasons bulk add URLs tend to include "seasons" path)
if (!location.href.includes('bulkadd') && !/\/seasons\//.test(location.pathname)) return;
const header = await waitFor('.container, .content, form, .bulk-add-header, #content', 8000).catch(() => null);
if (!header) {
console.warn('Fill button: insertion point not found, aborting.');
return;
}
if (document.getElementById('tm-fill-episodes-btn')) return;
const btn = document.createElement('button');
btn.id = 'tm-fill-episodes-btn';
btn.type = 'button';
btn.textContent = 'Fill episodes';
Object.assign(btn.style, {
position: 'fixed',
bottom: '20px',
right: '20px',
zIndex: '2147483000',
padding: '10px 16px',
background: '#007bff',
color: '#fff',
border: 'none',
borderRadius: '6px',
boxShadow: '0 2px 6px rgba(0,0,0,0.3)',
cursor: 'pointer',
fontSize: '15px',
fontWeight: '600'
});
btn.addEventListener('click', async () => {
if (!confirm('Fill up to 25 visible rows with prepared episode data? This will not submit the form.')) return;
try {
await ensureRows(episodes.length);
fillRows();
} catch (err) {
console.error(err);
alert('Error filling rows: ' + err.message);
}
});
document.body.appendChild(btn);
} catch (err) {
console.error('Insert Fill button error:', err);
}
}
// Fill function (do not set number input; robust date normalisation to YYYY-MM-DD)
function fillRows() {
const rows = getEpisodeRows().slice(0, 25);
if (!rows.length) {
alert('No episode rows found. Make sure you are on the TheTVDB bulk-add page and episode rows are visible.');
return;
}
const limit = Math.min(rows.length, episodes.length, 25);
// helper: try to parse many human dates and return YYYY-MM-DD or '' if unknown
function toIsoDate(s) {
if (!s) return '';
s = String(s).trim();
const isoMatch = s.match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (isoMatch) return `${isoMatch[1]}-${isoMatch[2]}-${isoMatch[3]}`;
let m = s.match(/^(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2,4})$/);
if (m) {
let d = Number(m[1]), mo = Number(m[2]), y = Number(m[3]);
if (y < 100) { y = (y >= 70 ? 1900 + y : 2000 + y); }
if (d && mo && y) {
return `${String(y).padStart(4,'0')}-${String(mo).padStart(2,'0')}-${String(d).padStart(2,'0')}`;
}
}
m = s.match(/(\d{1,2})\s+([A-Za-z]+)\s+(\d{2,4})/);
if (m) {
const monthNames = { jan:1,feb:2,mar:3,apr:4,may:5,jun:6,jul:7,aug:8,sep:9,oct:10,nov:11,dec:12 };
const d = Number(m[1]);
const mo = monthNames[m[2].toLowerCase().slice(0,3)];
let y = Number(m[3]);
if (y < 100) y = (y >= 70 ? 1900 + y : 2000 + y);
if (d && mo && y) return `${String(y).padStart(4,'0')}-${String(mo).padStart(2,'0')}-${String(d).padStart(2,'0')}`;
}
m = s.match(/([A-Za-z]+)\s+(\d{1,2}),\s*(\d{2,4})/);
if (m) {
const monthNames = { jan:1,feb:2,mar:3,apr:4,may:5,jun:6,jul:7,aug:8,sep:9,oct:10,nov:11,dec:12 };
const mo = monthNames[m[1].toLowerCase().slice(0,3)];
const d = Number(m[2]);
let y = Number(m[3]);
if (y < 100) y = (y >= 70 ? 1900 + y : 2000 + y);
if (d && mo && y) return `${String(y).padStart(4,'0')}-${String(mo).padStart(2,'0')}-${String(d).padStart(2,'0')}`;
}
const parsed = new Date(s);
if (!Number.isNaN(parsed.getTime())) {
const dd = String(parsed.getDate()).padStart(2,'0');
const mm = String(parsed.getMonth() + 1).padStart(2,'0');
const yy = String(parsed.getFullYear()).padStart(4,'0');
return `${yy}-${mm}-${dd}`;
}
console.warn('toIsoDate: unable to parse date string:', s);
return '';
}
for (let i = 0; i < limit; i++) {
const row = rows[i];
const ep = episodes[i];
// NOTE: intentionally NOT setting the episode number input (TVDB generates it)
// Name
const nameInput = row.querySelector('input[name="name[]"]');
if (nameInput) {
nameInput.value = ep.name;
nameInput.dispatchEvent(new Event('input', { bubbles: true }));
nameInput.dispatchEvent(new Event('change', { bubbles: true }));
}
// Overview
const overviewInput = row.querySelector('textarea[name="overview[]"]');
if (overviewInput) {
overviewInput.value = ep.overview;
overviewInput.dispatchEvent(new Event('input', { bubbles: true }));
overviewInput.dispatchEvent(new Event('change', { bubbles: true }));
}
// Date (convert to YYYY-MM-DD for type="date" inputs)
const dateInput = row.querySelector('input[type="date"][name="date[]"]') || row.querySelector('input[name="date[]"]');
if (dateInput) {
const iso = toIsoDate(ep.firstAired);
if (dateInput.type === 'date') {
dateInput.value = iso;
} else {
dateInput.value = iso || (ep.firstAired || '');
}
dateInput.dispatchEvent(new Event('input', { bubbles: true }));
dateInput.dispatchEvent(new Event('change', { bubbles: true }));
}
// Runtime
const runtimeInput = row.querySelector('input[name="runtime[]"]') || row.querySelectorAll('input[type="text"], input[type="number"]')[1] || null;
if (runtimeInput) {
runtimeInput.value = ep.runtime;
runtimeInput.dispatchEvent(new Event('input', { bubbles: true }));
runtimeInput.dispatchEvent(new Event('change', { bubbles: true }));
}
}
alert(`Filled ${limit} row(s). Review values and submit the form manually when ready.`);
}
// ------------------------------
// Create pages: robust body-anchored overview guard
// ------------------------------
function startOverviewGuard() {
const OVERVIEW_SELECTOR = 'textarea#overview, textarea[name="overview"]';
let currentTA = null;
let overlayRoot = null;
let resizeObs = null;
function createOverlay() {
if (overlayRoot) return overlayRoot;
const root = document.createElement('div');
root.className = 'tm-overview-overlay-root';
Object.assign(root.style, {
position: 'fixed',
pointerEvents: 'none',
zIndex: 2147483000,
fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial',
transition: 'top 0.08s, left 0.08s'
});
const badge = document.createElement('div');
badge.className = 'tm-overview-badge-root';
Object.assign(badge.style, {
background: 'rgba(0,0,0,0.75)',
color: '#fff',
padding: '6px 10px',
borderRadius: '6px',
fontSize: '13px',
fontWeight: '600',
display: 'inline-flex',
gap: '10px',
alignItems: 'center',
pointerEvents: 'none'
});
const count = document.createElement('span');
count.className = 'tm-overview-count';
count.textContent = '0';
count.style.minWidth = '36px';
count.style.textAlign = 'right';
const status = document.createElement('span');
status.className = 'tm-overview-status';
status.textContent = 'OK';
Object.assign(status.style, {
background: 'green',
color: '#fff',
padding: '2px 6px',
borderRadius: '4px',
fontSize: '12px',
fontWeight: '700'
});
badge.appendChild(count);
badge.appendChild(status);
root.appendChild(badge);
document.body.appendChild(root);
overlayRoot = { root, count, status };
return overlayRoot;
}
function attachTo(textarea) {
if (!textarea) return;
if (currentTA === textarea) return;
detach();
currentTA = textarea;
const overlay = createOverlay();
function update() {
if (!currentTA) return;
const len = currentTA.value.length;
overlay.count.textContent = String(len);
if (len > 500) {
overlay.status.textContent = 'OVER 500';
overlay.status.style.background = '#c62828';
overlay.root.firstChild.style.background = 'rgba(198,40,40,0.9)';
} else {
overlay.status.textContent = 'OK';
overlay.status.style.background = 'green';
overlay.root.firstChild.style.background = 'rgba(0,0,0,0.75)';
}
const rect = currentTA.getBoundingClientRect();
const top = Math.max(8, rect.top + window.scrollY - 6);
const left = Math.min(window.innerWidth - 10 - overlay.root.offsetWidth, rect.right + 8 + window.scrollX);
overlay.root.style.top = top + 'px';
overlay.root.style.left = left + 'px';
}
const inputHandler = update;
currentTA.addEventListener('input', inputHandler);
currentTA.addEventListener('change', inputHandler);
resizeObs = new ResizeObserver(update);
resizeObs.observe(currentTA);
const scrollHandler = () => update();
window.addEventListener('scroll', scrollHandler, true);
window.addEventListener('resize', update);
setTimeout(update, 0);
currentTA._tmOverviewCleanup = () => {
currentTA.removeEventListener('input', inputHandler);
currentTA.removeEventListener('change', inputHandler);
window.removeEventListener('scroll', scrollHandler, true);
window.removeEventListener('resize', update);
if (resizeObs) { resizeObs.disconnect(); resizeObs = null; }
currentTA = null;
};
}
function detach() {
if (currentTA && currentTA._tmOverviewCleanup) {
try { currentTA._tmOverviewCleanup(); } catch (e) { /*ignore*/ }
currentTA._tmOverviewCleanup = null;
}
}
function scanAndAttach() {
const ta = document.querySelector(OVERVIEW_SELECTOR);
if (ta) {
attachTo(ta);
} else {
detach();
}
}
scanAndAttach();
const mo = new MutationObserver(scanAndAttach);
mo.observe(document.body, { childList: true, subtree: true });
window.addEventListener('beforeunload', () => {
try { mo.disconnect(); } catch(e) {}
try { detach(); } catch(e) {}
});
}
// Init core filler + guard
(function initCore() {
insertFillButton();
const observer = new MutationObserver(() => insertFillButton());
observer.observe(document.body, { childList: true, subtree: true });
if (/\/movies\/create|\/series\/create/.test(location.pathname + location.search)) {
setTimeout(startOverviewGuard, 250);
}
})();
// ------------------------------
// Separate module appended: IMDb injector (clipboard + manual paste + toast)
// Namespaced to avoid collisions with core functions
// ------------------------------
(function imdbInjectorModule() {
// Unique helpers prefixed with imdb_
async function imdb_readClipboardText() {
try {
return await navigator.clipboard.readText();
} catch (e) {
console.warn('imdb: Clipboard read failed', e);
return '';
}
}
function imdb_findIMDbIdInText(text) {
if (!text) return null;
const m = text.match(/tt\d{7,8}/);
return m ? m[0] : null;
}
function imdb_showToast(message, type = 'info') {
const toast = document.createElement('div');
toast.textContent = message;
Object.assign(toast.style, {
position: 'fixed',
bottom: '20px',
right: '20px',
background: type === 'error' ? '#dc3545' : '#28a745',
color: '#fff',
padding: '10px 16px',
borderRadius: '6px',
fontSize: '14px',
fontWeight: '600',
zIndex: 2147483001,
boxShadow: '0 2px 6px rgba(0,0,0,0.3)'
});
document.body.appendChild(toast);
setTimeout(() => {
try { toast.remove(); } catch (e) {}
}, 3000);
}
function imdb_injectIMDbIdIntoInput(imdbId) {
const input = document.querySelector('input#imdb.remoteid-unique-multiple, input[name^="remoteid"][id="imdb"], input[name="remoteid[2]"], input#imdb');
if (!input) return false;
input.value = imdbId;
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
return true;
}
async function imdb_attemptAutoInjectIMDb() {
// Only attempt on create pages where IMDb field exists
if (!/\/movies\/create|\/series\/create/.test(location.pathname + location.search)) return;
// 1) Try clipboard first
const clipboardText = await imdb_readClipboardText();
const clipId = imdb_findIMDbIdInText(clipboardText);
if (clipId) {
const ok = imdb_injectIMDbIdIntoInput(clipId);
if (ok) { imdb_showToast(`✅ IMDb ID "${clipId}" pasted from clipboard`, 'success'); return; }
}
// 2) Fallback: check if the page already has an IMDb hint text somewhere (some pages show sample)
const pageText = document.body.innerText || '';
const pageMatch = imdb_findIMDbIdInText(pageText);
if (pageMatch) {
const ok = imdb_injectIMDbIdIntoInput(pageMatch);
if (ok) { imdb_showToast(`✅ IMDb ID "${pageMatch}" found on page and pasted`, 'success'); return; }
}
// 3) No ID found - show a small Paste button so user can choose to paste (also triggers clipboard read)
imdb_insertPasteIMDbButton();
}
function imdb_insertPasteIMDbButton() {
if (document.getElementById('tm-paste-imdb-btn')) return;
const btn = document.createElement('button');
btn.id = 'tm-paste-imdb-btn';
btn.type = 'button';
btn.textContent = 'Paste IMDb ID';
Object.assign(btn.style, {
position: 'fixed',
bottom: '80px',
right: '20px',
zIndex: 2147483000,
padding: '10px 16px',
background: '#28a745',
color: '#fff',
border: 'none',
borderRadius: '6px',
boxShadow: '0 2px 6px rgba(0,0,0,0.3)',
cursor: 'pointer',
fontSize: '15px',
fontWeight: '600'
});
btn.addEventListener('click', async () => {
btn.disabled = true;
btn.textContent = 'Pasting...';
const txt = await imdb_readClipboardText();
const id = imdb_findIMDbIdInText(txt);
if (id) {
const ok = imdb_injectIMDbIdIntoInput(id);
if (ok) imdb_showToast(`✅ IMDb ID "${id}" pasted from clipboard`, 'success');
else imdb_showToast('❌ IMDb input field not found.', 'error');
} else {
imdb_showToast('❌ No IMDb ID found in clipboard', 'error');
}
btn.remove();
});
document.body.appendChild(btn);
}
// Initialize IMDb module after core init
setTimeout(() => {
imdb_attemptAutoInjectIMDb();
// keep a light observer for dynamic create pages to re-run injection attempt once
if (/\/movies\/create|\/series\/create/.test(location.pathname + location.search)) {
const mo = new MutationObserver((mutations) => {
const added = mutations.some(m => m.addedNodes && m.addedNodes.length);
if (!added) return;
imdb_attemptAutoInjectIMDb();
mo.disconnect();
});
mo.observe(document.body, { childList: true, subtree: true });
}
}, 400);
})();
// ------------------------------
// Edit Layout Reflow module (move fieldset + move Save) appended at bottom
// ------------------------------
(function editLayoutReflowModule() {
// Utility: wait for selector to appear
function waitForSelector(selector, timeout = 10000) {
return new Promise((resolve, reject) => {
const t0 = Date.now();
(function check() {
const el = document.querySelector(selector);
if (el) return resolve(el);
if (Date.now() - t0 > timeout) return reject(new Error('Timed out: ' + selector));
requestAnimationFrame(check);
})();
});
}
// Heuristic: find the fieldset that contains "Country" and "Release Date"
function findTargetFieldset() {
const fsets = Array.from(document.querySelectorAll('fieldset'));
for (const f of fsets) {
const text = (f.textContent || '').toLowerCase();
if (text.includes('country') && text.includes('release date')) return f;
}
return null;
}
// Move the identified fieldset to after the "required fields" paragraph
async function moveFieldset() {
try {
const para = await waitForSelector('p.small', 8000).catch(() => null);
if (!para) return console.warn('Reflow: required-fields paragraph not found');
const fset = findTargetFieldset();
if (!fset) return console.warn('Reflow: target fieldset not found');
if (para.nextElementSibling === fset) return; // already moved
para.parentNode.insertBefore(fset, para.nextSibling);
fset.style.transition = 'box-shadow 220ms ease, transform 220ms ease';
fset.style.boxShadow = '0 8px 26px rgba(0,0,0,0.08)';
fset.style.transform = 'translateY(-6px)';
setTimeout(() => {
fset.style.transform = '';
fset.style.boxShadow = '';
}, 420);
console.info('Reflow: fieldset moved after required-fields paragraph');
} catch (e) {
console.warn('Reflow: moveFieldset failed', e);
}
}
// Robust Move Save button: locate POST form's Save and insert before Release Dates heading
async function moveSaveButton() {
try {
const form = await waitForSelector('form[method="POST"], form[action][method="POST"]', 10000).catch(() => null);
if (!form) return console.warn('Reflow: POST form not found');
const saveBtn = Array.from(form.querySelectorAll('button, input[type="submit"]'))
.find(el => {
const txt = ((el.textContent || el.value || '') + '').trim().toLowerCase();
const hasClass = ((el.className || '') + '').toLowerCase().includes('btn-success');
return hasClass || txt === 'save' || txt === 'submit';
});
if (!saveBtn) return console.warn('Reflow: Save button inside form not found');
const releaseH3 = Array.from(document.querySelectorAll('h3')).find(h => (h.textContent || '').trim().toLowerCase() === 'release dates');
if (!releaseH3) return console.warn('Reflow: Release Dates heading not found');
// Avoid re-inserting if already adjacent
if (releaseH3.previousElementSibling === saveBtn) return;
releaseH3.parentNode.insertBefore(saveBtn, releaseH3);
saveBtn.style.transition = 'transform 180ms ease, box-shadow 180ms ease';
saveBtn.style.transform = 'translateY(-6px)';
saveBtn.style.boxShadow = '0 8px 20px rgba(0,0,0,0.08)';
setTimeout(() => {
saveBtn.style.transform = '';
saveBtn.style.boxShadow = '';
}, 260);
console.info('Reflow: moved Save button above Release Dates heading');
} catch (e) {
console.warn('Reflow: moveSaveButton failed', e);
}
}
// Apply both moves
function applyReflow() {
moveFieldset();
moveSaveButton();
}
// Mutation observer to re-run when the page mutates (SPA/dynamic loads)
function watchAndApplyReflow() {
applyReflow();
const mo = new MutationObserver((mutations) => {
const added = mutations.some(m => m.addedNodes && m.addedNodes.length);
if (!added) return;
applyReflow();
});
mo.observe(document.body, { childList: true, subtree: true });
// Stop observing after 2 minutes (page should be stable by then)
setTimeout(() => {
try { mo.disconnect(); } catch (e) {}
}, 120000);
}
// Kick off only on edit pages
if (/\/movies\/.*\/edit|\/series\/.*\/edit/.test(location.pathname + location.search)) {
setTimeout(watchAndApplyReflow, 300);
}
})();
// ------------------------------
// Search page: floating Add Series / Add Movie buttons
// ------------------------------
(function addSearchFloatingButtons() {
const ID_SERIES = 'tm-add-series-btn';
const ID_MOVIE = 'tm-add-movie-btn';
function isSearchPage() {
return /\/search(?:$|[?#])/.test(location.pathname + location.search);
}
function createButton(id, text, href, bottomOffset, bg) {
if (document.getElementById(id)) return null;
const btn = document.createElement('a');
btn.id = id;
btn.href = href;
btn.textContent = text;
btn.setAttribute('role','button');
btn.style.position = 'fixed';
btn.style.right = '20px';
btn.style.bottom = `${bottomOffset}px`;
btn.style.zIndex = '2147483000';
btn.style.padding = '10px 16px';
btn.style.background = bg || '#17a2b8';
btn.style.color = '#fff';
btn.style.border = 'none';
btn.style.borderRadius = '8px';
btn.style.boxShadow = '0 2px 6px rgba(0,0,0,0.3)';
btn.style.cursor = 'pointer';
btn.style.fontSize = '14px';
btn.style.fontWeight = '700';
btn.style.textDecoration = 'none';
btn.style.display = 'inline-flex';
btn.style.alignItems = 'center';
btn.style.gap = '8px';
// keep default anchor behaviour; the site will handle navigation
document.body.appendChild(btn);
return btn;
}
function insertButtonsIfNeeded() {
if (!isSearchPage()) return;
if (!document.getElementById(ID_SERIES)) createButton(ID_SERIES, 'Add Series', '/series/create', 80, '#007bff');
if (!document.getElementById(ID_MOVIE)) createButton(ID_MOVIE, 'Add Movie', '/movies/create', 140, '#28a745');
}
// run once then observe for SPA-rendered changes
insertButtonsIfNeeded();
const mo = new MutationObserver((mutations) => {
const added = mutations.some(m => m.addedNodes && m.addedNodes.length);
if (!added) return;
insertButtonsIfNeeded();
});
mo.observe(document.body, { childList: true, subtree: true });
// cleanup when navigating away (simple guard)
window.addEventListener('popstate', () => {
const s = document.getElementById(ID_SERIES);
const m = document.getElementById(ID_MOVIE);
if (s) s.remove();
if (m) m.remove();
// re-run insertion in a short while in case SPA navigation lands back on search
setTimeout(insertButtonsIfNeeded, 250);
});
// also remove automatically if user leaves the search path after 2 minutes
setTimeout(() => {
if (!isSearchPage()) {
const s = document.getElementById(ID_SERIES);
const m = document.getElementById(ID_MOVIE);
if (s) s.remove();
if (m) m.remove();
}
}, 120000);
})();
// ------------------------------
// Copy TVDB ID Floating Button
// ------------------------------
(function copyTvdbIdModule() {
'use strict';
// Only run if we find the info list on a detail page
if (!document.querySelector('#series_basic_info, #movie_basic_info')) return;
// Find the numeric ID from the info list
function getTvdbId() {
const infoBlock = document.querySelector('#series_basic_info, #movie_basic_info');
if (!infoBlock) return null;
const items = Array.from(infoBlock.querySelectorAll('li.list-group-item'));
for (const li of items) {
const label = li.querySelector('strong')?.textContent || '';
if (/thetvdb\.com\s+(?:movie|series)\s+id/i.test(label)) {
return li.querySelector('span')?.textContent.trim() || null;
}
}
return null;
}
// Show a temporary toast message
function showToast(message, success = true) {
const toast = document.createElement('div');
toast.textContent = message;
Object.assign(toast.style, {
position : 'fixed',
bottom : '20px',
right : '20px',
padding : '10px 16px',
background : success ? '#28a745' : '#dc3545',
color : '#fff',
borderRadius: '6px',
fontSize : '14px',
fontWeight : '600',
zIndex : 2147483001,
boxShadow : '0 2px 6px rgba(0,0,0,0.3)',
}); // ← Note the comma after the last property
document.body.appendChild(toast);
setTimeout(() => toast.remove(), 3000);
}
// Create and insert the floating “Copy ID” button
function insertCopyButton(tvdbId) {
if (!tvdbId || document.getElementById('tm-copy-tvdbid-btn')) return;
const btn = document.createElement('button');
btn.id = 'tm-copy-tvdbid-btn';
btn.type = 'button';
btn.textContent = 'Copy TVDB ID';
Object.assign(btn.style, {
position : 'fixed',
bottom : '80px',
right : '20px',
zIndex : '2147483000',
padding : '10px 16px',
background : '#007bff',
color : '#fff',
border : 'none',
borderRadius: '6px',
boxShadow : '0 2px 6px rgba(0,0,0,0.3)',
cursor : 'pointer',
fontSize : '15px',
fontWeight : '600',
});
btn.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(tvdbId);
// use back‐ticks here for template literal
showToast(`Copied ID ${tvdbId}`);
} catch (err) {
console.error('Copy TVDB ID failed:', err);
showToast('Failed to copy ID', false);
}
});
document.body.appendChild(btn);
}
// Kick off
const id = getTvdbId();
if (id) insertCopyButton(id);
})();
// ------------------------------
// Preferred Country module (value match "gbr" then text fallback)
// ------------------------------
(function preferredCountryModule() {
const DONE_FLAG = 'tm-preferred-country-applied';
function isCreatePage() {
return /\/movies\/create|\/series\/create/.test(location.pathname + location.search);
}
function findCountrySelect() {
return document.querySelector('select[name="country"], select.form-control[name="country"], select#country');
}
function optionTextMatches(optText, target) {
if (!optText || !target) return false;
return optText.toLowerCase().includes(target.toLowerCase());
}
function applyPreferredCountry() {
const sel = findCountrySelect();
if (!sel) return false;
// do not override if user already selected something meaningful
const current = (sel.value || '').trim();
if (current && current !== '' && !/^(?:select|choose|none|-)$/.test(current.toLowerCase())) {
return true;
}
// 1) prefer an option by exact value (gbr)
let matched = Array.from(sel.options).find(o => (o.value || '').toLowerCase() === PREFERRED_VALUE.toLowerCase());
// 2) fallback: match by visible text containing the preferred country label
if (!matched) {
matched = Array.from(sel.options).find(o => optionTextMatches(o.textContent || o.innerText || '', PREFERRED_COUNTRY));
}
// 3) final fallback: try exact value equal to the label (case-insensitive)
if (!matched) {
matched = Array.from(sel.options).find(o => (o.value || '').toLowerCase() === PREFERRED_COUNTRY.toLowerCase());
}
if (matched) {
sel.value = matched.value;
sel.dispatchEvent(new Event('input', { bubbles: true }));
sel.dispatchEvent(new Event('change', { bubbles: true }));
sel.dataset[ DONE_FLAG ] = '1';
console.info(`PreferredCountry: set "${matched.textContent.trim()}" (${matched.value}) on country select`);
return true;
}
return false;
}
function runOnceWhenReady() {
if (!isCreatePage()) return;
// attempt immediately
if (applyPreferredCountry()) return;
// wait for select to appear
waitFor('select[name="country"], select.form-control[name="country"], select#country', 8000)
.then(() => applyPreferredCountry())
.catch(() => {/* no country select found within timeout */});
// observe for dynamic injection and try once more on first insertion
const mo = new MutationObserver((mutations, obs) => {
const added = mutations.some(m => m.addedNodes && m.addedNodes.length);
if (!added) return;
if (applyPreferredCountry()) obs.disconnect();
});
mo.observe(document.body, { childList: true, subtree: true });
// stop observing after 2 minutes
setTimeout(() => { try { mo.disconnect(); } catch (e) {} }, 120000);
}
// Initial run
setTimeout(runOnceWhenReady, 300);
// Also run again on SPA navigation
window.addEventListener('popstate', () => setTimeout(runOnceWhenReady, 250));
})();
(function(){
'use strict';
// ── Shared Helpers ──
function makeOverlay() {
const ov = document.createElement('div');
ov.textContent = 'One Click adder';
Object.assign(ov.style, {
position: 'fixed',
top: '60px',
right: '20px',
width: '300px',
maxHeight: '80vh',
overflowY: 'auto',
padding: '12px',
background: 'rgba(40,167,69,0.9)',
border: '1px solid #ccc',
borderRadius:'6px',
boxShadow: '0 2px 8px rgba(0,0,0,0.2)',
zIndex: 2147483002,
fontFamily: 'system-ui',
fontSize: '14px'
});
return ov;
}
function addField(form, labelText, el) {
el.style.backgroundColor = '#013220';
el.style.color = '#ccc';
el.style.border = '1px solid #666';
el.style.padding = '4px 6px';
el.style.borderRadius = '4px';
el.style.marginBottom = '6px';
el.style.width = '100%';
const wrap = document.createElement('div');
wrap.style.margin = '8px 0';
const lbl = document.createElement('label');
lbl.textContent = labelText;
lbl.style.display = 'block';
lbl.style.fontWeight = '600';
lbl.style.marginBottom = '4px';
wrap.append(lbl, el);
form.appendChild(wrap);
}
// ── Static Data (declare once) ──
// ───────────────────────────────────────────────
// STATIC DATA FOR OVERLAY DROPDOWNS & CHECKBOXES
// ───────────────────────────────────────────────
const LANGUAGES = [
{ value: "abk", text: "Abkhaz (аҧсуа бызшәа)" },
{ value: "aar", text: "Afar (Afaraf)" },
{ value: "afr", text: "Afrikaans" },
{ value: "aka", text: "Akan" },
{ value: "sqi", text: "Albanian (gjuha shqipe)" },
{ value: "amh", text: "Amharic (አማርኛ)" },
{ value: "ara", text: "Arabic (العربية)" },
{ value: "arg", text: "Aragonese (aragonés)" },
{ value: "hye", text: "Armenian (Հայերեն)" },
{ value: "asm", text: "Assamese (অসমীয়া)" },
{ value: "ava", text: "Avaric (авар мацӀ)" },
{ value: "ave", text: "Avestan (avesta)" },
{ value: "aym", text: "Aymara (aymar aru)" },
{ value: "aze", text: "Azerbaijani (azərbaycan dili)" },
{ value: "bam", text: "Bambara (bamanankan)" },
{ value: "bak", text: "Bashkir (башҡорт теле)" },
{ value: "eus", text: "Basque (euskara)" },
{ value: "bel", text: "Belarusian (беларуская мова)" },
{ value: "ben", text: "Bengali (বাংলা)" },
{ value: "bih", text: "Bihari (भोजपुरी)" },
{ value: "bis", text: "Bislama" },
{ value: "bos", text: "Bosnian (bosanski jezik)" },
{ value: "bre", text: "Breton (brezhoneg)" },
{ value: "bul", text: "Bulgarian (български език)" },
{ value: "mya", text: "Burmese" },
{ value: "cat", text: "Catalan (català)" },
{ value: "cha", text: "Chamorro (Chamoru)" },
{ value: "che", text: "Chechen (нохчийн мотт)" },
{ value: "nya", text: "Chewa (chiCheŵa)" },
{ value: "yue", text: "Chinese – Cantonese (粵語)" },
{ value: "zho", text: "Chinese – China (大陆简体)" },
{ value: "zhtw", text: "Chinese – Taiwan (臺灣國語)" },
{ value: "chv", text: "Chuvash (чӑваш чӗлхи)" },
{ value: "cor", text: "Cornish (Kernewek)" },
{ value: "cos", text: "Corsican (corsu)" },
{ value: "cre", text: "Cree (ᓀᐦᐃᔭᐍᐏᐣ)" },
{ value: "hrv", text: "Croatian (hrvatski jezik)" },
{ value: "ces", text: "Czech (čeština)" },
{ value: "dan", text: "Danish (dansk)" },
{ value: "div", text: "Divehi (ދިވެހި)" },
{ value: "nld", text: "Dutch (Nederlands)" },
{ value: "dzo", text: "Dzongkha (རྫོང་ཁ)" },
{ value: "eng", text: "English" },
{ value: "epo", text: "Esperanto" },
{ value: "est", text: "Estonian (eesti)" },
{ value: "ewe", text: "Ewe (Eʋegbe)" },
{ value: "fao", text: "Faroese (føroyskt)" },
{ value: "fij", text: "Fijian (vosa Vakaviti)" },
{ value: "fin", text: "Finnish (suomi)" },
{ value: "fra", text: "French (français)" },
{ value: "ful", text: "Fula (Fulfulde)" },
{ value: "glg", text: "Galician (galego)" },
{ value: "kat", text: "Georgian (ქართული)" },
{ value: "deu", text: "German (Deutsch)" },
{ value: "ell", text: "Greek (ελληνική γλώσσα)" },
{ value: "grn", text: "Guaraní (Avañe'ẽ)" },
{ value: "guj", text: "Gujarati (ગુજરાતી)" },
{ value: "hat", text: "Haitian (Kreyòl ayisyen)" },
{ value: "hau", text: "Hausa (هَوُسَ)" },
{ value: "heb", text: "Hebrew (עברית)" },
{ value: "her", text: "Herero (Otjiherero)" },
{ value: "hin", text: "Hindi (हिन्दी)" },
{ value: "hmo", text: "Hiri Motu" },
{ value: "hun", text: "Hungarian (Magyar)" },
{ value: "isl", text: "Icelandic (Íslenska)" },
{ value: "ido", text: "Ido" },
{ value: "ibo", text: "Igbo (Asụsụ Igbo)" },
{ value: "ind", text: "Indonesian (Bahasa Indonesia)" },
{ value: "ina", text: "Interlingua" },
{ value: "ile", text: "Interlingue" },
{ value: "iku", text: "Inuktitut (ᐃᓄᒃᑎᑐᑦ)" },
{ value: "ipk", text: "Inupiaq (Iñupiaq)" },
{ value: "gle", text: "Irish (Gaeilge)" },
{ value: "ita", text: "Italian (italiano)" },
{ value: "jpn", text: "Japanese (日本語)" },
{ value: "jav", text: "Javanese (basa Jawa)" },
{ value: "kal", text: "Kalaallisut (kalaallisut)" },
{ value: "kan", text: "Kannada (ಕನ್ನಡ)" },
{ value: "kau", text: "Kanuri" },
{ value: "kas", text: "Kashmiri (कश्मीरी)" },
{ value: "kaz", text: "Kazakh (қазақ тілі)" },
{ value: "khm", text: "Khmer (ខ្មែរ)" },
{ value: "kik", text: "Kikuyu (Gĩkũyũ)" },
{ value: "kin", text: "Kinyarwanda (Ikinyarwanda)" },
{ value: "kir", text: "Kirghiz (кыргыз тили)" },
{ value: "run", text: "Kirundi (Ikirundi)" },
{ value: "kom", text: "Komi (коми кыв)" },
{ value: "kon", text: "Kongo (KiKongo)" },
{ value: "kor", text: "Korean (한국어)" },
{ value: "kur", text: "Kurdish (Kurdî)" },
{ value: "kua", text: "Kwanyama (Kuanyama)" },
{ value: "lao", text: "Lao (ພາສາລາວ)" },
{ value: "lat", text: "Latin (latine)" },
{ value: "lav", text: "Latvian (latviešu valoda)" },
{ value: "lim", text: "Limburgish (Limburgs)" },
{ value: "lin", text: "Lingala (Lingála)" },
{ value: "lit", text: "Lithuanian (lietuvių kalba)" },
{ value: "lub", text: "Luba-Katanga" },
{ value: "lug", text: "Luganda" },
{ value: "ltz", text: "Luxembourgish (Lëtzebuergesch)" },
{ value: "mkd", text: "Macedonian (македонски јазик)" },
{ value: "mlg", text: "Malagasy (Malagasy fiteny)" },
{ value: "msa", text: "Malay (bahasa Melayu)" },
{ value: "mal", text: "Malayalam (മലയാളം)" },
{ value: "mlt", text: "Maltese (Malti)" },
{ value: "glv", text: "Manx (Gaelg)" },
{ value: "mri", text: "Māori (te reo Māori)" },
{ value: "mar", text: "Marathi (मराठी)" },
{ value: "mah", text: "Marshallese (Kajin M̧ajeļ)" },
{ value: "mon", text: "Mongolian (монгол)" },
{ value: "nau", text: "Nauru (Ekakairũ Naoero)" },
{ value: "nav", text: "Navajo (Diné bizaad)" },
{ value: "ndo", text: "Ndonga (Owambo)" },
{ value: "nep", text: "Nepali (नेपाली)" },
{ value: "nde", text: "North Ndebele (isiNdebele)" },
{ value: "sme", text: "Northern Sami (Davvisámegiella)" },
{ value: "nor", text: "Norwegian (Norsk bokmål)" },
{ value: "iii", text: "Nuosu (Nuosuhxop)" },
{ value: "oci", text: "Occitan (occitan)" },
{ value: "oji", text: "Ojibwe (ᐊᓂᔑᓈᐯᒧᐎᓐ)" },
{ value: "chu", text: "Old Church Slavonic (ѩзыкъ словѣньскъ)" },
{ value: "ori", text: "Oriya (ଓଡ଼ିଆ)" },
{ value: "orm", text: "Oromo (Afaan Oromo)" },
{ value: "oss", text: "Ossetian (ирон æвзаг)" },
{ value: "pli", text: "Pāli (पाऴि)" },
{ value: "pan", text: "Panjabi (ਪੰਜਾਬੀ)" },
{ value: "pus", text: "Pashto (پښتو)" },
{ value: "fas", text: "Persian (فارسی)" },
{ value: "pol", text: "Polish (język polski)" },
{ value: "pt", text: "Portuguese – Brazil (Português – Brasil)" },
{ value: "por", text: "Portuguese – Portugal (Português – Portugal)" },
{ value: "que", text: "Quechua (Runa Simi)" },
{ value: "ron", text: "Romanian (limba română)" },
{ value: "roh", text: "Romansh (rumantsch grischun)" },
{ value: "rus", text: "Russian (русский язык)" },
{ value: "smo", text: "Samoan (gagana fa'a Samoa)" },
{ value: "sag", text: "Sango (yângâ tî sängö)" },
{ value: "san", text: "Sanskrit (संस्कृतम्)" },
{ value: "srd", text: "Sardinian (sardu)" },
{ value: "gla", text: "Scottish Gaelic (Gàidhlig)" },
{ value: "srp", text: "Serbian (српски језик)" },
{ value: "sna", text: "Shona (chiShona)" },
{ value: "snd", text: "Sindhi (सिन्धी)" },
{ value: "sin", text: "Sinhala (සිංහල)" },
{ value: "slk", text: "Slovak (slovenčina)" },
{ value: "slv", text: "Slovene (slovenski jezik)" },
{ value: "som", text: "Somali (Soomaaliga)" },
{ value: "nbl", text: "South Ndebele (isiNdebele)" },
{ value: "sot", text: "Southern Sotho (Sesotho)" },
{ value: "spa", text: "Spanish (español)" },
{ value: "sun", text: "Sundanese (Basa Sunda)" },
{ value: "swa", text: "Swahili (Kiswahili)" },
{ value: "ssw", text: "Swati (SiSwati)" },
{ value: "swe", text: "Swedish (svenska)" },
{ value: "tgl", text: "Tagalog (Wikang Tagalog)" },
{ value: "tah", text: "Tahitian (Reo Tahiti)" },
{ value: "tgk", text: "Tajik (тоҷикӣ)" },
{ value: "tam", text: "Tamil (தமிழ்)" },
{ value: "tat", text: "Tatar (татар теле)" },
{ value: "tel", text: "Telugu (తెలుగు)" },
{ value: "tha", text: "Thai (ไทย)" },
{ value: "bod", text: "Tibetan Standard (བོད་ཡིག)" },
{ value: "tir", text: "Tigrinya (ትግርኛ)" },
{ value: "ton", text: "Tonga (faka Tonga)" },
{ value: "tso", text: "Tsonga (Xitsonga)" },
{ value: "tsn", text: "Tswana (Setswana)" },
{ value: "tur", text: "Turkish (Türkçe)" },
{ value: "tuk", text: "Turkmen (Türkmen)" },
{ value: "twi", text: "Twi" },
{ value: "uig", text: "Uighur (Uyƣurqə)" },
{ value: "ukr", text: "Ukrainian (українська мова)" },
{ value: "urd", text: "Urdu (اردو)" },
{ value: "ven", text: "Venda (Tshivenḓa)" },
{ value: "vie", text: "Vietnamese (Tiếng Việt)" },
{ value: "vol", text: "Volapük" },
{ value: "wln", text: "Walloon (walon)" },
{ value: "cym", text: "Welsh (Cymraeg)" },
{ value: "fry", text: "Western Frisian (Frysk)" },
{ value: "wol", text: "Wolof (Wollof)" },
{ value: "xho", text: "Xhosa (isiXhosa)" },
{ value: "yid", text: "Yiddish (ייִדיש)" },
{ value: "yor", text: "Yoruba (Yorùbá)" },
{ value: "zha", text: "Zhuang (Saɯ cueŋƅ)" },
{ value: "zul", text: "Zulu (isiZulu)" }
];
const ORIGINAL_COUNTRIES = [
{ value: "afg", text: "Afghanistan" },
{ value: "ala", text: "Åland Islands" },
{ value: "alb", text: "Albania" },
{ value: "dza", text: "Algeria" },
{ value: "asm", text: "American Samoa" },
{ value: "and", text: "Andorra" },
{ value: "ago", text: "Angola" },
{ value: "aia", text: "Anguilla" },
{ value: "ata", text: "Antarctica" },
{ value: "atg", text: "Antigua and Barbuda" },
{ value: "arg", text: "Argentina" },
{ value: "arm", text: "Armenia" },
{ value: "abw", text: "Aruba" },
{ value: "aus", text: "Australia" },
{ value: "aut", text: "Austria" },
{ value: "aze", text: "Azerbaijan" },
{ value: "bhs", text: "Bahamas" },
{ value: "bhr", text: "Bahrain" },
{ value: "bgd", text: "Bangladesh" },
{ value: "brb", text: "Barbados" },
{ value: "blr", text: "Belarus" },
{ value: "bel", text: "Belgium" },
{ value: "blz", text: "Belize" },
{ value: "ben", text: "Benin" },
{ value: "bmu", text: "Bermuda" },
{ value: "btn", text: "Bhutan" },
{ value: "bol", text: "Bolivia" },
{ value: "bes", text: "Bonaire, Sint Eustatius and Saba" },
{ value: "bih", text: "Bosnia and Herzegovina" },
{ value: "bwa", text: "Botswana" },
{ value: "bvt", text: "Bouvet Island" },
{ value: "bra", text: "Brazil" },
{ value: "iot", text: "British Indian Ocean Territory" },
{ value: "vgb", text: "British Virgin Islands" },
{ value: "brn", text: "Brunei Darussalam" },
{ value: "bgr", text: "Bulgaria" },
{ value: "bfa", text: "Burkina Faso" },
{ value: "bdi", text: "Burundi" },
{ value: "khm", text: "Cambodia" },
{ value: "cmr", text: "Cameroon" },
{ value: "can", text: "Canada" },
{ value: "cpv", text: "Cape Verde" },
{ value: "cym", text: "Cayman Islands" },
{ value: "caf", text: "Central African Republic" },
{ value: "tcd", text: "Chad" },
{ value: "chl", text: "Chile" },
{ value: "chn", text: "China" },
{ value: "cxr", text: "Christmas Island" },
{ value: "cck", text: "Cocos (Keeling) Islands" },
{ value: "col", text: "Colombia" },
{ value: "com", text: "Comoros" },
{ value: "cod", text: "Congo" },
{ value: "cok", text: "Cook Islands" },
{ value: "cri", text: "Costa Rica" },
{ value: "hrv", text: "Croatia" },
{ value: "cub", text: "Cuba" },
{ value: "cuw", text: "Curaçao" },
{ value: "cyp", text: "Cyprus" },
{ value: "cze", text: "Czech Republic" },
{ value: "dnk", text: "Denmark" },
{ value: "dji", text: "Djibouti" },
{ value: "dma", text: "Dominica" },
{ value: "dom", text: "Dominican Republic" },
{ value: "ecu", text: "Ecuador" },
{ value: "egy", text: "Egypt" },
{ value: "slv", text: "El Salvador" },
{ value: "gnq", text: "Equatorial Guinea" },
{ value: "eri", text: "Eritrea" },
{ value: "est", text: "Estonia" },
{ value: "eth", text: "Ethiopia" },
{ value: "fji", text: "Fiji" },
{ value: "fin", text: "Finland" },
{ value: "fra", text: "France" },
{ value: "guf", text: "French Guiana" },
{ value: "pyf", text: "French Polynesia" },
{ value: "atf", text: "French Southern Territories" },
{ value: "gab", text: "Gabon" },
{ value: "gmb", text: "Gambia" },
{ value: "geo", text: "Georgia" },
{ value: "deu", text: "Germany" },
{ value: "gha", text: "Ghana" },
{ value: "gib", text: "Gibraltar" },
{ value: "gbr", text: "Great Britain" },
{ value: "grc", text: "Greece" },
{ value: "grl", text: "Greenland" },
{ value: "grd", text: "Grenada" },
{ value: "glp", text: "Guadeloupe" },
{ value: "gum", text: "Guam" },
{ value: "gtm", text: "Guatemala" },
{ value: "ggy", text: "Guernsey" },
{ value: "gin", text: "Guinea" },
{ value: "gnb", text: "Guinea-Bissau" },
{ value: "guy", text: "Guyana" },
{ value: "hti", text: "Haiti" },
{ value: "hmd", text: "Heard Island and McDonald Islands" },
{ value: "hnd", text: "Honduras" },
{ value: "hkg", text: "Hong Kong" },
{ value: "hun", text: "Hungary" },
{ value: "isl", text: "Iceland" },
{ value: "ind", text: "India" },
{ value: "idn", text: "Indonesia" },
{ value: "irn", text: "Iran" },
{ value: "irq", text: "Iraq" },
{ value: "irl", text: "Ireland" },
{ value: "imn", text: "Isle of Man" },
{ value: "isr", text: "Israel" },
{ value: "ita", text: "Italy" },
{ value: "jam", text: "Jamaica" },
{ value: "jpn", text: "Japan" },
{ value: "jey", text: "Jersey" },
{ value: "jor", text: "Jordan" },
{ value: "kaz", text: "Kazakhstan" },
{ value: "ken", text: "Kenya" },
{ value: "kir", text: "Kiribati" },
{ value: "unk", text: "Kosovo" },
{ value: "kwt", text: "Kuwait" },
{ value: "kgz", text: "Kyrgyzstan" },
{ value: "lao", text: "Laos" },
{ value: "lva", text: "Latvia" },
{ value: "lbn", text: "Lebanon" },
{ value: "lso", text: "Lesotho" },
{ value: "lbr", text: "Liberia" },
{ value: "lby", text: "Libya" },
{ value: "lie", text: "Liechtenstein" },
{ value: "ltu", text: "Lithuania" },
{ value: "lux", text: "Luxembourg" },
{ value: "mac", text: "Macao" },
{ value: "mkd", text: "Macedonia" },
{ value: "mdg", text: "Madagascar" },
{ value: "mwi", text: "Malawi" },
{ value: "mys", text: "Malaysia" },
{ value: "mdv", text: "Maldives" },
{ value: "mli", text: "Mali" },
{ value: "mlt", text: "Malta" },
{ value: "mhl", text: "Marshall Islands" },
{ value: "mtq", text: "Martinique" },
{ value: "mrt", text: "Mauritania" },
{ value: "mus", text: "Mauritius" },
{ value: "myt", text: "Mayotte" },
{ value: "mex", text: "Mexico" },
{ value: "fsm", text: "Micronesia" },
{ value: "mda", text: "Moldova" },
{ value: "mco", text: "Monaco" },
{ value: "mng", text: "Mongolia" },
{ value: "mne", text: "Montenegro" },
{ value: "msr", text: "Montserrat" },
{ value: "mar", text: "Morocco" },
{ value: "moz", text: "Mozambique" },
{ value: "mmr", text: "Myanmar" },
{ value: "nam", text: "Namibia" },
{ value: "nru", text: "Nauru" },
{ value: "npl", text: "Nepal" },
{ value: "ncl", text: "New Caledonia" },
{ value: "nzl", text: "New Zealand" },
{ value: "nic", text: "Nicaragua" },
{ value: "ner", text: "Niger" },
{ value: "nga", text: "Nigeria" },
{ value: "niu", text: "Niue" },
{ value: "nfk", text: "Norfolk Island" },
{ value: "prk", text: "North Korea" },
{ value: "mnp", text: "Northern Mariana Islands" },
{ value: "nor", text: "Norway" },
{ value: "omn", text: "Oman" },
{ value: "pak", text: "Pakistan" },
{ value: "pse", text: "Palestine, State of" },
{ value: "pan", text: "Panama" },
{ value: "png", text: "Papua New Guinea" },
{ value: "pry", text: "Paraguay" },
{ value: "per", text: "Peru" },
{ value: "phl", text: "Philippines" },
{ value: "pcn", text: "Pitcairn" },
{ value: "pol", text: "Poland" },
{ value: "prt", text: "Portugal" },
{ value: "pri", text: "Puerto Rico" },
{ value: "qat", text: "Qatar" },
{ value: "plw", text: "Republic of Palau" },
{ value: "cog", text: "Republic of the Congo" },
{ value: "reu", text: "Réunion" },
{ value: "rou", text: "Romania" },
{ value: "rus", text: "Russia" },
{ value: "rwa", text: "Rwanda" },
{ value: "blm", text: "Saint Barthélemy" },
{ value: "kna", text: "Saint Christopher and Nevis" },
{ value: "shn", text: "Saint Helena, Ascension and Tristan da Cunha" },
{ value: "lca", text: "Saint Lucia" },
{ value: "maf", text: "Saint Martin" },
{ value: "spm", text: "Saint Pierre and Miquelon" },
{ value: "vct", text: "Saint Vincent and the Grenadines" },
{ value: "wsm", text: "Samoa" },
{ value: "smr", text: "San Marino" },
{ value: "stp", text: "São Tomé and Príncipe" },
{ value: "sau", text: "Saudi Arabia" },
{ value: "sen", text: "Senegal" },
{ value: "srb", text: "Serbia" },
{ value: "syc", text: "Seychelles" },
{ value: "sle", text: "Sierra Leone" },
{ value: "sgp", text: "Singapore" },
{ value: "sxm", text: "Sint Maarten" },
{ value: "svk", text: "Slovakia" },
{ value: "svn", text: "Slovenia" },
{ value: "slb", text: "Solomon Islands" },
{ value: "som", text: "Somali Republic" },
{ value: "zaf", text: "South Africa" },
{ value: "sgs", text: "South Georgia" },
{ value: "kor", text: "South Korea" },
{ value: "ssd", text: "South Sudan" },
{ value: "esp", text: "Spain" },
{ value: "lka", text: "Sri Lanka" },
{ value: "sdn", text: "Sudan" },
{ value: "sur", text: "Suriname" },
{ value: "sjm", text: "Svalbard and Jan Mayen" },
{ value: "swz", text: "Swaziland" },
{ value: "swe", text: "Sweden" },
{ value: "che", text: "Switzerland" },
{ value: "syr", text: "Syrian Arab Republic" },
{ value: "twn", text: "Taiwan" },
{ value: "tjk", text: "Tajikistan" },
{ value: "tza", text: "Tanzania" },
{ value: "tha", text: "Thailand" },
{ value: "flk", text: "The Falkland Islands" },
{ value: "fro", text: "The Faroe Islands" },
{ value: "nld", text: "The Netherlands" },
{ value: "tls", text: "Timor-Leste" },
{ value: "tgo", text: "Togo" },
{ value: "tkl", text: "Tokelau" },
{ value: "ton", text: "Tonga" },
{ value: "tto", text: "Trinidad and Tobago" },
{ value: "tun", text: "Tunisia" },
{ value: "tur", text: "Turkey" },
{ value: "tkm", text: "Turkmenistan" },
{ value: "tca", text: "Turks and Caicos Islands" },
{ value: "tuv", text: "Tuvalu" },
{ value: "uga", text: "Uganda" },
{ value: "ukr", text: "Ukraine" },
{ value: "are", text: "United Arab Emirates" },
{ value: "umi", text: "United States Minor Outlying Islands" },
{ value: "usa", text: "United States of America" },
{ value: "ury", text: "Uruguay" },
{ value: "uzb", text: "Uzbekistan" },
{ value: "vut", text: "Vanuatu" },
{ value: "vat", text: "Vatican City" },
{ value: "ven", text: "Venezuela" },
{ value: "vnm", text: "Vietnam" },
{ value: "vir", text: "Virgin Islands of the United States" },
{ value: "wlf", text: "Wallis and Futuna" },
{ value: "esh", text: "Western Sahara" },
{ value: "yem", text: "Yemen" },
{ value: "yug", text: "Yugoslavia" },
{ value: "zmb", text: "Zambia" },
{ value: "zwe", text: "Zimbabwe" }
];
const GENRES = [
{ value: "19", text: "Action" },
{ value: "18", text: "Adventure" },
{ value: "17", text: "Animation" },
{ value: "27", text: "Anime" },
{ value: "36", text: "Awards Show" },
{ value: "16", text: "Children" },
{ value: "15", text: "Comedy" },
{ value: "14", text: "Crime" },
{ value: "13", text: "Documentary" },
{ value: "12", text: "Drama" },
{ value: "11", text: "Family" },
{ value: "10", text: "Fantasy" },
{ value: "9", text: "Food" },
{ value: "8", text: "Game Show" },
{ value: "33", text: "History" },
{ value: "7", text: "Home and Garden" },
{ value: "6", text: "Horror" },
{ value: "32", text: "Indie" },
{ value: "35", text: "Martial Arts" },
{ value: "5", text: "Mini-Series" },
{ value: "29", text: "Musical" },
{ value: "31", text: "Mystery" },
{ value: "4", text: "News" },
{ value: "30", text: "Podcast" },
{ value: "3", text: "Reality" },
{ value: "28", text: "Romance" },
{ value: "2", text: "Science Fiction" },
{ value: "1", text: "Soap" },
{ value: "21", text: "Sport" },
{ value: "22", text: "Suspense" },
{ value: "23", text: "Talk Show" },
{ value: "24", text: "Thriller" },
{ value: "25", text: "Travel" },
{ value: "34", text: "War" },
{ value: "26", text: "Western" }
];
const STATUS_OPTIONS = [
{ value: "1", text: "Announced" },
{ value: "2", text: "Pre-Production" },
{ value: "3", text: "Filming / Post-Production" },
{ value: "4", text: "Completed" },
{ value: "5", text: "Released" }
];
const SERIES_STATUS_OPTIONS = [
{ value: '1', text: 'Continuing' },
{ value: '2', text: 'Ended' },
{ value: '3', text: 'Upcoming' }
];
// ------------------------------
// ONE-PAGE Movie Create → Auto-Edit Flow
// ------------------------------
;(function movieOnePageFlow() {
'use strict';
const STORAGE_KEY = 'tm_new_movie_data';
const path = location.pathname;
const isCreate = /^\/movies\/create(?:$|[?\/])/.test(path);
const isStep2 = /^\/movies\/create-step2/.test(path);
const isSlug = /^\/movies\/[^/]+(?:$|[?#])/.test(path) && !isStep2 && !isCreate;
const isEdit = /^\/movies\/[^/]+\/edit/.test(path);
// STEP 1: /movies/create → show overlay
if (isCreate) {
const overlay = makeOverlay();
const form = document.createElement('form');
const fields = {};
// Title
const titleIn = document.createElement('input');
titleIn.type = 'text';
addField(form, 'Title', titleIn);
fields.title = titleIn;
// Overview w/ 500-char count
const ov = document.createElement('textarea');
ov.rows = 4;
ov.maxLength = 500;
addField(form, 'Overview (max 500)', ov);
fields.overview = ov;
const badge = document.createElement('div');
badge.style.cssText = 'text-align:right;font-size:12px;';
badge.textContent = '0/500';
ov.addEventListener('input', () => {
badge.textContent = `${ov.value.length}/500`;
});
form.appendChild(badge);
// Language (default English)
const ls = document.createElement('select');
LANGUAGES.forEach(o => {
const opt = document.createElement('option');
opt.value = o.value;
opt.textContent = o.text;
ls.append(opt);
});
ls.value = 'eng';
addField(form, 'Language', ls);
fields.language = ls;
// Original Country (default GBR)
const cs = document.createElement('select');
ORIGINAL_COUNTRIES.forEach(o => {
const opt = document.createElement('option');
opt.value = o.value;
opt.textContent = o.text;
cs.append(opt);
});
cs.value = 'gbr';
addField(form, 'Original Country', cs);
fields.country = cs;
// Status
const ss = document.createElement('select');
STATUS_OPTIONS.forEach(o => {
const opt = document.createElement('option');
opt.value = o.value;
opt.textContent = o.text;
ss.append(opt);
});
ss.value = STATUS_OPTIONS[STATUS_OPTIONS.length - 1].value;
addField(form, 'Status', ss);
fields.status = ss;
// Genres (multi-select)
const gs = document.createElement('select');
gs.multiple = true;
gs.size = 6;
GENRES.forEach(o => {
const opt = document.createElement('option');
opt.value = o.value;
opt.textContent = o.text;
gs.append(opt);
});
addField(form, 'Genres', gs);
fields.genres = gs;
// Release Country
const rc = document.createElement('select');
ORIGINAL_COUNTRIES.forEach(o => {
const opt = document.createElement('option');
opt.value = o.value;
opt.textContent = o.text;
rc.append(opt);
});
addField(form, 'Release Country', rc);
fields.releaseCountry = rc;
// Release Date
const rd = document.createElement('input');
rd.type = 'text';
rd.placeholder = 'YYYY-MM-DD';
addField(form, 'Release Date', rd);
fields.releaseDate = rd;
// Runtime
const rt = document.createElement('input');
rt.type = 'number';
rt.min = '0';
addField(form, 'Runtime (min)', rt);
fields.runtime = rt;
// Create & Continue button
const btn = document.createElement('button');
btn.type = 'submit';
btn.textContent = '▶ Create & Continue';
Object.assign(btn.style, {
width : '100%',
padding : '8px',
marginTop : '10px',
background : '#007bff',
color : '#fff',
border : 'none',
borderRadius: '4px',
fontWeight : '600',
cursor : 'pointer'
});
form.appendChild(btn);
overlay.appendChild(form);
document.body.appendChild(overlay);
form.addEventListener('submit', e => {
e.preventDefault();
const data = {
title : fields.title.value.trim(),
overview : fields.overview.value.trim(),
language : fields.language.value,
country : fields.country.value,
status : fields.status.value,
genres : Array.from(fields.genres.selectedOptions).map(o => o.value),
releaseCountry : fields.releaseCountry.value,
releaseDate : fields.releaseDate.value,
runtime : fields.runtime.value
};
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(data));
// fill native Step-1 form
const t1 = document.querySelector('input.ais-SearchBox-input');
if (t1) {
t1.value = data.title;
t1.dispatchEvent(new Event('input', { bubbles: true }));
}
['overview','language','country','status'].forEach(name => {
const el = document.querySelector(`[name="${name}"]`);
if (el) el.value = data[name];
});
const cont = document.querySelector('form button.btn-success');
if (cont) cont.click();
});
return;
}
// STEP 2: /movies/create-step2 → fill & Save
if (isStep2 && sessionStorage.getItem(STORAGE_KEY)) {
const data = JSON.parse(sessionStorage.getItem(STORAGE_KEY));
const saveBtn = document.querySelector('button.btn.btn-success');
if (saveBtn) {
const f2 = saveBtn.closest('form');
// Name can be a hidden field outside the <form>
let nameFld = f2.querySelector('input[name="name"]');
if (!nameFld) {
nameFld = document.querySelector('input[type="hidden"][name="name"]');
}
if (nameFld) {
nameFld.value = data.title;
nameFld.dispatchEvent(new Event('input', { bubbles: true }));
}
// Overview
const ovFld = f2.querySelector('textarea[name="overview"]');
if (ovFld) {
ovFld.value = data.overview;
ovFld.dispatchEvent(new Event('input', { bubbles: true }));
}
// Language, Country, Status
[['select[name="language"]', data.language],
['select[name="country"]', data.country],
['select[name="status"]', data.status]
].forEach(([sel,val]) => {
const el = f2.querySelector(sel);
if (el) el.value = val;
});
// Genres checkboxes
f2.querySelectorAll('input[name="genres[]"]').forEach(chk => {
chk.checked = data.genres.includes(chk.value);
});
saveBtn.click();
}
return;
}
// SLUG → EDIT redirect
if (isSlug && sessionStorage.getItem(STORAGE_KEY)) {
window.location.replace(path.replace(/\/$/, '') + '/edit' + location.search);
return;
}
// FINAL EDIT: fill release, date & runtime → Save
if (isEdit && sessionStorage.getItem(STORAGE_KEY)) {
const { releaseCountry, releaseDate, runtime: movieRuntime } =
JSON.parse(sessionStorage.getItem(STORAGE_KEY));
sessionStorage.removeItem(STORAGE_KEY);
console.log('movieOnePageFlow: applying final edit', {
releaseCountry, releaseDate, movieRuntime
});
setTimeout(() => {
// Release Country
const rcSel = document.querySelector(
'select[name="releasecountries[]"], select[name="releasecountries"]'
);
if (rcSel) {
rcSel.value = releaseCountry;
rcSel.dispatchEvent(new Event('change', { bubbles: true }));
}
// Release Date
const rdInput = document.querySelector(
'input[name="releasedates[]"], input[name="releasedates"]'
);
if (rdInput) {
rdInput.value = releaseDate;
rdInput.dispatchEvent(new Event('input', { bubbles: true }));
rdInput.dispatchEvent(new Event('change', { bubbles: true }));
}
// Runtime
const rtInput = document.querySelector(
'input[name="movie_runtime"], input[name="runtime"]'
);
if (rtInput) {
rtInput.value = movieRuntime;
rtInput.dispatchEvent(new Event('input', { bubbles: true }));
rtInput.dispatchEvent(new Event('change', { bubbles: true }));
}
const saveBtn2 = document.querySelector('button.btn-success, input[type="submit"]');
if (saveBtn2) {
console.log('movieOnePageFlow: clicking Save…');
saveBtn2.click();
}
}, 400);
}
})(); // end movieOnePageFlow
// ─────────────────────────────────────────────────────────────────────
// ONE-PAGE Series Create → Auto-Edit Flow
// ─────────────────────────────────────────────────────────────────────
;(function seriesOnePageFlow() {
'use strict';
const STORAGE_KEY = 'tm_new_series_data';
const path = location.pathname;
const isCreate = /^\/series\/create(?:$|[?\/])/.test(path);
const isStep2 = /^\/series\/create-step2/.test(path);
const isSlug = /^\/series\/[^/]+(?:$|[?#])/.test(path) && !isStep2 && !isCreate;
const isEdit = /^\/series\/[^/]+\/edit/.test(path);
// STEP 1: /series/create → overlay
if (isCreate) {
const overlay = makeOverlay();
const form = document.createElement('form');
const fields = {};
// Title
const titleIn = document.createElement('input');
titleIn.type = 'text';
addField(form, 'Title', titleIn);
fields.title = titleIn;
// Overview
const ov = document.createElement('textarea');
ov.rows = 4;
ov.maxLength = 500;
addField(form, 'Overview (max 500)', ov);
fields.overview = ov;
const badge2 = document.createElement('div');
badge2.style.cssText = 'text-align:right;font-size:12px;';
badge2.textContent = '0/500';
ov.addEventListener('input', () => {
badge2.textContent = `${ov.value.length}/500`;
});
form.appendChild(badge2);
// Language
const ls2 = document.createElement('select');
LANGUAGES.forEach(o => {
const opt = document.createElement('option');
opt.value = o.value;
opt.textContent = o.text;
ls2.append(opt);
});
ls2.value = 'eng';
addField(form, 'Language', ls2);
fields.language = ls2;
// Original Country
const cs2 = document.createElement('select');
ORIGINAL_COUNTRIES.forEach(o => {
const opt = document.createElement('option');
opt.value = o.value;
opt.textContent = o.text;
cs2.append(opt);
});
cs2.value = 'gbr';
addField(form, 'Original Country', cs2);
fields.country = cs2;
// Status (Continuing / Ended / Upcoming)
const ss2 = document.createElement('select');
SERIES_STATUS_OPTIONS.forEach(o => {
const opt = document.createElement('option');
opt.value = o.value;
opt.textContent = o.text;
if (o.value === '3') opt.selected = true; // default "Upcoming"
ss2.append(opt);
});
addField(form, 'Status', ss2);
fields.status = ss2;
// Genres
const gs2 = document.createElement('select');
gs2.multiple = true;
gs2.size = 6;
GENRES.forEach(o => {
const opt = document.createElement('option');
opt.value = o.value;
opt.textContent = o.text;
gs2.append(opt);
});
addField(form, 'Genres', gs2);
fields.genres = gs2;
// Continue button
const btn2 = document.createElement('button');
btn2.type = 'submit';
btn2.textContent = '▶ Create & Continue';
Object.assign(btn2.style, {
width:'100%',
padding:'8px',
marginTop:'10px',
background:'#007bff',
color:'#fff',
border:'none',
borderRadius:'4px',
fontWeight:'600',
cursor:'pointer'
});
form.appendChild(btn2);
overlay.appendChild(form);
document.body.appendChild(overlay);
form.addEventListener('submit', e => {
e.preventDefault();
const data = {
title: fields.title.value.trim(),
overview: fields.overview.value.trim(),
language: fields.language.value,
country: fields.country.value,
status: fields.status.value,
genres: Array.from(fields.genres.selectedOptions).map(o => o.value)
};
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(data));
// two-step fill Step-1 for series
const t1s = document.querySelector('input.ais-SearchBox-input');
if (t1s) {
t1s.value = data.title;
t1s.dispatchEvent(new Event('input',{bubbles:true}));
}
['overview','language','country','status'].forEach(name => {
const el = document.querySelector(`[name="${name}"]`);
if (el) el.value = data[name];
});
document.querySelector('form button.btn-success')?.click();
});
return;
}
// STEP 2: /series/create-step2 → fill & Save
if (isStep2 && sessionStorage.getItem(STORAGE_KEY)) {
const data = JSON.parse(sessionStorage.getItem(STORAGE_KEY));
const saveBtn = document.querySelector('button.btn.btn-success');
if (saveBtn) {
const f2s = saveBtn.closest('form');
const setVal = (sel,val) => {
const el = f2s.querySelector(sel);
if (el) el.value = val;
};
setVal('input[name="name"]', data.title);
setVal('textarea[name="overview"]', data.overview);
setVal('select[name="language"]', data.language);
setVal('select[name="country"]', data.country);
setVal('select[name="status"]', data.status);
f2s.querySelectorAll('input[name="genres[]"]').forEach(chk => {
chk.checked = data.genres.includes(chk.value);
});
saveBtn.click();
}
return;
}
// SLUG → EDIT redirect
if (isSlug && sessionStorage.getItem(STORAGE_KEY)) {
window.location.replace(path.replace(/\/$/, '') + '/edit' + location.search);
return;
}
// FINAL EDIT: fill release & runtime → Save
if (isEdit && sessionStorage.getItem(STORAGE_KEY)) {
const { releaseCountry, releaseDate, runtime } =
JSON.parse(sessionStorage.getItem(STORAGE_KEY));
sessionStorage.removeItem(STORAGE_KEY);
setTimeout(() => {
const rc = document.querySelector('select[name="releasecountries[]"]');
if (rc) rc.value = releaseCountry;
const rd = document.querySelector('input[name="releasedates[]"]');
if (rd) rd.value = releaseDate;
const rt = document.querySelector('input[name="series_runtime"], input[name="movie_runtime"]');
if (rt) rt.value = runtime;
document.querySelector('button.btn-success, input[type="submit"]')?.click();
}, 400);
}
})(); // end seriesOnePageFlow
})();
// ------------------------------
// Floating “All Episodes” Button on Series Pages
// ------------------------------
;(function allEpisodesButtonModule() {
'use strict';
// only run on /series/:slug pages
if (!/^\/series\/[^/]+(?:$|[?#])/.test(location.pathname)) return;
// find the “All Seasons” link in the DOM
const allLink = document.querySelector('a[href*="/allseasons/"]');
if (!allLink) return;
// avoid dupes
if (document.getElementById('tm-all-episodes-btn')) return;
// build our button
const btn = document.createElement('a');
btn.id = 'tm-all-episodes-btn';
btn.href = allLink.href;
btn.textContent = 'All Episodes';
Object.assign(btn.style, {
position : 'fixed',
right : '20px',
bottom : '20px',
padding : '10px 14px',
background : '#17a2b8',
color : '#fff',
borderRadius : '6px',
textDecoration: 'none',
boxShadow : '0 2px 6px rgba(0,0,0,0.3)',
zIndex : '2147483001',
fontWeight : '600'
});
document.body.appendChild(btn);
})();
})();