Files
2026-02-18 09:14:02 -05:00

872 lines
45 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Custom Formats single view for Movie Hunt and TV Hunt. Combined instance dropdown
* (Movie - X / TV - X, alphabetical). Each instance keeps its own formats; same page linked from both sidebars.
*/
(function() {
'use strict';
window.CustomFormats = {
_list: [],
_editingIndex: null,
_modalMode: null,
_mode: 'movie',
getApiBase: function() {
return this._mode === 'tv' ? './api/tv-hunt/custom-formats' : './api/custom-formats';
},
getInstanceApiBase: function(mode) {
return mode === 'tv' ? './api/tv-hunt' : './api/movie-hunt';
},
refreshList: function() {
var preformattedGrid = document.getElementById('custom-formats-preformatted-grid');
var importedGrid = document.getElementById('custom-formats-imported-grid');
if (!preformattedGrid || !importedGrid) return;
var apiBase = window.CustomFormats.getApiBase();
fetch(apiBase)
.then(function(r) { return r.json(); })
.then(function(data) {
var list = (data && data.custom_formats) ? data.custom_formats : [];
window.CustomFormats._list = list;
var preformattedByGroup = {};
var importedItems = [];
var preformattedCount = 0;
var importedCount = 0;
for (var i = 0; i < list.length; i++) {
var item = list[i];
var isPreformatted = (item.source || 'import').toLowerCase() === 'preformat';
if (isPreformatted) {
var preformatId = item.preformat_id || '';
var groupKey = window.CustomFormats._getGroupFromPreformatId(preformatId);
if (!preformattedByGroup[groupKey]) {
preformattedByGroup[groupKey] = [];
}
preformattedByGroup[groupKey].push({item: item, index: i});
preformattedCount++;
} else {
importedItems.push({item: item, index: i});
importedCount++;
}
}
var preformattedHtml = '';
var sortedGroups = Object.keys(preformattedByGroup).sort();
for (var g = 0; g < sortedGroups.length; g++) {
var groupKey = sortedGroups[g];
var groupItems = preformattedByGroup[groupKey];
var groupName = window.CustomFormats._formatGroupName(groupKey);
preformattedHtml += '<div class="custom-formats-group-header">' +
'<i class="fas fa-folder-open"></i> ' + groupName +
'</div>';
for (var j = 0; j < groupItems.length; j++) {
var entry = groupItems[j];
var item = entry.item;
var i = entry.index;
var title = (item.title || item.name || 'Unnamed').replace(/</g, '&lt;').replace(/>/g, '&gt;');
preformattedHtml += '<div class="custom-format-card instance-card" data-index="' + i + '" data-app-type="custom-format">' +
'<div class="custom-format-card-header">' +
'<div class="custom-format-card-title"><i class="fas fa-code"></i><span>' + title + '</span></div>' +
'</div>' +
'<div class="custom-format-card-footer">' +
'<button type="button" class="btn-card view" data-index="' + i + '"><i class="fas fa-eye"></i> JSON</button>' +
'<button type="button" class="btn-card delete" data-index="' + i + '"><i class="fas fa-trash"></i> Delete</button>' +
'</div></div>';
}
}
var importedHtml = '';
for (var k = 0; k < importedItems.length; k++) {
var entry = importedItems[k];
var item = entry.item;
var i = entry.index;
var title = (item.title || item.name || 'Unnamed').replace(/</g, '&lt;').replace(/>/g, '&gt;');
importedHtml += '<div class="custom-format-card instance-card" data-index="' + i + '" data-app-type="custom-format">' +
'<div class="custom-format-card-header">' +
'<div class="custom-format-card-title"><i class="fas fa-code"></i><span>' + title + '</span></div>' +
'</div>' +
'<div class="custom-format-card-footer">' +
'<button type="button" class="btn-card view" data-index="' + i + '"><i class="fas fa-eye"></i> JSON</button>' +
'<button type="button" class="btn-card edit" data-index="' + i + '"><i class="fas fa-edit"></i> Edit</button>' +
'<button type="button" class="btn-card delete" data-index="' + i + '"><i class="fas fa-trash"></i> Delete</button>' +
'</div></div>';
}
preformattedGrid.innerHTML = preformattedHtml;
importedGrid.innerHTML = importedHtml;
var deletePreBtn = document.getElementById('delete-all-preformatted');
var deleteImpBtn = document.getElementById('delete-all-imported');
if (deletePreBtn) deletePreBtn.disabled = preformattedCount === 0;
if (deleteImpBtn) deleteImpBtn.disabled = importedCount === 0;
window.CustomFormats._bindCards();
})
.catch(function() {
preformattedGrid.innerHTML = '';
importedGrid.innerHTML = '';
window.CustomFormats._bindAddButtons();
});
},
setCurrentInstanceAndRefresh: function(mode, instanceId) {
var self = window.CustomFormats;
self._mode = mode;
var apiBase = self.getInstanceApiBase(mode);
fetch(apiBase + '/instances/current', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ instance_id: parseInt(instanceId, 10) })
}).then(function(r) { return r.json(); }).then(function() {
self.refreshList();
}).catch(function() {
self.refreshList();
});
},
_safeJsonFetch: function(url, fallback) {
return fetch(url, { cache: 'no-store' }).then(function(r) { return r.json(); }).catch(function() { return fallback || {}; });
},
populateCombinedInstanceDropdown: function(preferMode) {
var selectEl = document.getElementById('settings-custom-formats-instance-select');
if (!selectEl) return;
selectEl.innerHTML = '<option value="">Loading...</option>';
var ts = Date.now();
var sf = window.CustomFormats._safeJsonFetch.bind(window.CustomFormats);
Promise.all([
sf('./api/movie-hunt/instances?t=' + ts, { instances: [] }),
sf('./api/tv-hunt/instances?t=' + ts, { instances: [] }),
sf('./api/movie-hunt/instances/current?t=' + ts, { current_instance_id: null }),
sf('./api/tv-hunt/instances/current?t=' + ts, { current_instance_id: null }),
sf('./api/indexer-hunt/indexers?t=' + ts, { indexers: [] }),
sf('./api/movie-hunt/has-clients?t=' + ts, { has_clients: false })
]).then(function(results) {
var movieList = (results[0].instances || []).map(function(inst) {
return { value: 'movie:' + inst.id, label: 'Movie - ' + (inst.name || 'Instance ' + inst.id) };
});
var tvList = (results[1].instances || []).map(function(inst) {
return { value: 'tv:' + inst.id, label: 'TV - ' + (inst.name || 'Instance ' + inst.id) };
});
var combined = movieList.concat(tvList);
combined.sort(function(a, b) { return (a.label || '').localeCompare(b.label || '', undefined, { sensitivity: 'base' }); });
var currentMovie = results[2].current_instance_id != null ? Number(results[2].current_instance_id) : null;
var currentTv = results[3].current_instance_id != null ? Number(results[3].current_instance_id) : null;
selectEl.innerHTML = '';
if (combined.length === 0) {
var emptyOpt = document.createElement('option');
emptyOpt.value = '';
emptyOpt.textContent = 'No Movie or TV Hunt instances';
selectEl.appendChild(emptyOpt);
var noIdxEl = document.getElementById('settings-custom-formats-no-indexers');
var noCliEl = document.getElementById('settings-custom-formats-no-clients');
var wrapperEl = document.getElementById('settings-custom-formats-content-wrapper');
if (noIdxEl) noIdxEl.style.display = 'none';
if (noCliEl) noCliEl.style.display = 'none';
if (wrapperEl) wrapperEl.style.display = '';
return;
}
combined.forEach(function(item) {
var opt = document.createElement('option');
opt.value = item.value;
opt.textContent = item.label;
selectEl.appendChild(opt);
});
var saved = (typeof localStorage !== 'undefined' && localStorage.getItem('media-hunt-custom-formats-last-instance')) || '';
var selected = '';
if (preferMode === 'movie' && currentMovie != null) {
selected = 'movie:' + currentMovie;
if (!combined.some(function(i) { return i.value === selected; })) selected = combined[0].value;
} else if (preferMode === 'tv' && currentTv != null) {
selected = 'tv:' + currentTv;
if (!combined.some(function(i) { return i.value === selected; })) selected = combined[0].value;
} else if (saved && combined.some(function(i) { return i.value === saved; })) {
selected = saved;
} else if (currentMovie != null && combined.some(function(i) { return i.value === 'movie:' + currentMovie; })) {
selected = 'movie:' + currentMovie;
} else if (currentTv != null && combined.some(function(i) { return i.value === 'tv:' + currentTv; })) {
selected = 'tv:' + currentTv;
} else {
selected = combined[0].value;
}
selectEl.value = selected;
var noIdxEl = document.getElementById('settings-custom-formats-no-indexers');
var noCliEl = document.getElementById('settings-custom-formats-no-clients');
var wrapperEl = document.getElementById('settings-custom-formats-content-wrapper');
if (noIdxEl) noIdxEl.style.display = 'none';
if (noCliEl) noCliEl.style.display = 'none';
if (wrapperEl) wrapperEl.style.display = '';
var parts = (selected || '').split(':');
if (parts.length === 2) {
var m = parts[0] === 'tv' ? 'tv' : 'movie';
if (typeof localStorage !== 'undefined') localStorage.setItem('media-hunt-custom-formats-last-instance', selected);
window.CustomFormats.setCurrentInstanceAndRefresh(m, parts[1]);
}
}).catch(function() {
selectEl.innerHTML = '<option value="">Failed to load instances</option>';
var noIdxEl = document.getElementById('settings-custom-formats-no-indexers');
var noCliEl = document.getElementById('settings-custom-formats-no-clients');
var wrapperEl = document.getElementById('settings-custom-formats-content-wrapper');
if (noIdxEl) noIdxEl.style.display = 'none';
if (noCliEl) noCliEl.style.display = '';
if (wrapperEl) wrapperEl.style.display = 'none';
});
},
onCombinedInstanceChange: function() {
var selectEl = document.getElementById('settings-custom-formats-instance-select');
var val = (selectEl && selectEl.value) ? selectEl.value.trim() : '';
if (!val) return;
var parts = val.split(':');
if (parts.length !== 2) return;
var mode = parts[0] === 'tv' ? 'tv' : 'movie';
if (typeof localStorage !== 'undefined') localStorage.setItem('media-hunt-custom-formats-last-instance', val);
window.CustomFormats.setCurrentInstanceAndRefresh(mode, parts[1]);
},
initOrRefresh: function(preferMode) {
var selectEl = document.getElementById('settings-custom-formats-instance-select');
if (!selectEl) return;
if (!selectEl._customFormatsChangeBound) {
selectEl.addEventListener('change', function() { window.CustomFormats.onCombinedInstanceChange(); });
selectEl._customFormatsChangeBound = true;
}
window.CustomFormats.populateCombinedInstanceDropdown(preferMode);
},
_getGroupFromPreformatId: function(preformatId) {
if (!preformatId) return 'Other';
var parts = preformatId.split('.');
return parts[0] || 'Other';
},
_formatGroupName: function(groupKey) {
if (!groupKey || groupKey === 'Other') return 'Other';
var categoryNames = {
'movie-versions': 'Movie Versions',
'hdr-formats': 'HDR Formats',
'audio-formats': 'Audio Formats',
'audio-channels': 'Audio Channels',
'audio-advanced': 'Audio Advanced',
'movie-meta': 'Movie Metadata',
'streaming-services': 'Streaming Services',
'unwanted': 'Unwanted',
'misc': 'Miscellaneous',
'optional': 'Optional'
};
return categoryNames[groupKey] || groupKey.split('-').map(function(s) {
return s.charAt(0).toUpperCase() + s.slice(1);
}).join(' ');
},
_bindCards: function() {
var allCards = document.querySelectorAll('.custom-format-card');
allCards.forEach(function(card) {
var viewBtn = card.querySelector('.btn-card.view');
var editBtn = card.querySelector('.btn-card.edit');
var deleteBtn = card.querySelector('.btn-card.delete');
if (viewBtn) {
viewBtn.onclick = function(e) {
e.stopPropagation();
var idx = parseInt(viewBtn.getAttribute('data-index'), 10);
if (!isNaN(idx)) window.CustomFormats.openViewModal(idx);
};
}
if (editBtn) {
editBtn.onclick = function(e) {
e.stopPropagation();
var idx = parseInt(editBtn.getAttribute('data-index'), 10);
if (!isNaN(idx)) window.CustomFormats.openEditModal(idx);
};
}
if (deleteBtn) {
deleteBtn.onclick = function(e) {
e.stopPropagation();
var idx = parseInt(deleteBtn.getAttribute('data-index'), 10);
if (!isNaN(idx)) window.CustomFormats.deleteFormat(idx);
};
}
});
window.CustomFormats._bindAddButtons();
},
_bindAddButtons: function() {
var addPreformattedBtn = document.getElementById('add-preformatted-btn');
var addImportedBtn = document.getElementById('add-imported-btn');
if (addPreformattedBtn) {
addPreformattedBtn.onclick = function() {
window.CustomFormats.openAddModal('preformat');
};
}
if (addImportedBtn) {
addImportedBtn.onclick = function() {
window.CustomFormats.openAddModal('import');
};
}
},
openViewModal: function(index) {
var list = window.CustomFormats._list;
if (index < 0 || index >= list.length) return;
window.CustomFormats._ensureViewModalInBody();
var item = list[index];
var title = (item.title || item.name || 'Unnamed');
document.getElementById('custom-format-view-modal-title').textContent = 'View JSON: ' + title;
var jsonStr = item.custom_format_json || '{}';
try {
var parsed = JSON.parse(jsonStr);
jsonStr = JSON.stringify(parsed, null, 2);
} catch (e) {
// If parse fails, show as-is
}
document.getElementById('custom-format-view-json').textContent = jsonStr;
document.getElementById('custom-format-view-modal').style.display = 'flex';
document.body.classList.add('custom-format-modal-open');
},
closeViewModal: function() {
document.getElementById('custom-format-view-modal').style.display = 'none';
document.body.classList.remove('custom-format-modal-open');
},
_generateRandomSuffix: function() {
var chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
var suffix = '';
for (var i = 0; i < 4; i++) {
suffix += chars.charAt(Math.floor(Math.random() * chars.length));
}
return suffix;
},
_checkTitleCollision: function(title) {
var list = window.CustomFormats._list || [];
var preformattedTitles = {};
for (var i = 0; i < list.length; i++) {
if ((list[i].source || 'import').toLowerCase() === 'preformat') {
var t = (list[i].title || list[i].name || '').toLowerCase();
if (t) preformattedTitles[t] = true;
}
}
var lowerTitle = title.toLowerCase();
if (preformattedTitles[lowerTitle]) {
return title + '-' + window.CustomFormats._generateRandomSuffix();
}
return title;
},
_ensureAddModalInBody: function() {
var modal = document.getElementById('custom-format-modal');
if (modal && modal.parentNode !== document.body) {
document.body.appendChild(modal);
}
},
_ensureViewModalInBody: function() {
var modal = document.getElementById('custom-format-view-modal');
if (modal && modal.parentNode !== document.body) {
document.body.appendChild(modal);
}
},
openAddModal: function(source) {
window.CustomFormats._editingIndex = null;
window.CustomFormats._modalMode = source;
window.CustomFormats._ensureAddModalInBody();
if (source === 'preformat') {
document.getElementById('custom-format-modal-title').textContent = 'Add Pre-Formatted';
document.getElementById('custom-format-preformat-area').style.display = 'block';
var importArea = document.getElementById('custom-format-import-area');
if (importArea) importArea.style.display = 'none';
window.CustomFormats._loadPreformatTree();
} else {
document.getElementById('custom-format-modal-title').textContent = 'Add Imported';
document.getElementById('custom-format-preformat-area').style.display = 'none';
var importArea = document.getElementById('custom-format-import-area');
if (importArea) importArea.style.display = 'block';
}
document.getElementById('custom-format-modal-save').innerHTML = '<i class="fas fa-plus"></i> Add';
document.getElementById('custom-format-json-textarea').value = '';
document.getElementById('custom-format-modal').style.display = 'flex';
document.body.classList.add('custom-format-modal-open');
},
openEditModal: function(index) {
var list = window.CustomFormats._list;
if (index < 0 || index >= list.length) return;
window.CustomFormats._ensureAddModalInBody();
window.CustomFormats._editingIndex = index;
var item = list[index];
document.getElementById('custom-format-modal-title').textContent = 'Edit Custom Format';
document.getElementById('custom-format-modal-save').innerHTML = '<i class="fas fa-save"></i> Save';
document.getElementById('custom-format-source-import').checked = true;
document.getElementById('custom-format-preformat-area').style.display = 'none';
var importArea = document.getElementById('custom-format-import-area');
if (importArea) importArea.style.display = 'block';
document.getElementById('custom-format-json-textarea').value = item.custom_format_json || '{}';
document.getElementById('custom-format-modal').style.display = 'flex';
document.body.classList.add('custom-format-modal-open');
},
closeModal: function() {
document.getElementById('custom-format-modal').style.display = 'none';
document.body.classList.remove('custom-format-modal-open');
},
_buildPreformatId: function(catId, subId, fmtId) {
if (subId) return catId + '.' + subId + '.' + fmtId;
return catId + '.' + fmtId;
},
_loadPreformatTree: function() {
var treeEl = document.getElementById('custom-format-preformat-tree');
if (!treeEl) return;
treeEl.innerHTML = '<span class="custom-format-loading">Loading…</span>';
var existingIds = {};
(window.CustomFormats._list || []).forEach(function(item) {
if (item.preformat_id) existingIds[item.preformat_id] = true;
});
fetch(window.CustomFormats.getApiBase() + '/preformats')
.then(function(r) { return r.json(); })
.then(function(data) {
var categories = (data && data.categories) ? data.categories : [];
treeEl.innerHTML = '';
if (categories.length === 0) {
var msg = document.createElement('div');
msg.className = 'custom-format-preformat-empty';
msg.innerHTML = 'Pre-formatted list is not available on this server. You can still add formats via <strong>Import</strong> by pasting JSON from <a href="https://trash-guides.info/Radarr/Radarr-collection-of-custom-formats/" target="_blank" rel="noopener">TRaSH Guides</a>.';
treeEl.appendChild(msg);
return;
}
categories.forEach(function(cat) {
var catId = cat.id || '';
var catName = cat.name || catId;
var catDiv = document.createElement('div');
catDiv.className = 'custom-format-cat';
var header = document.createElement('div');
header.className = 'custom-format-cat-header';
header.innerHTML = '<i class="fas fa-chevron-down"></i><span>' + (catName.replace(/</g, '&lt;').replace(/>/g, '&gt;')) + '</span>';
var body = document.createElement('div');
body.className = 'custom-format-cat-body';
var subcats = cat.subcategories || [];
if (subcats.length > 0) {
subcats.forEach(function(sub) {
var subId = sub.id || '';
var subName = sub.name || subId;
var subDiv = document.createElement('div');
subDiv.className = 'custom-format-subcat';
var subLabel = document.createElement('div');
subLabel.className = 'custom-format-subcat-name';
subLabel.textContent = subName;
subDiv.appendChild(subLabel);
var fmtList = document.createElement('div');
fmtList.className = 'custom-format-format-list';
(sub.formats || []).forEach(function(fmt) {
var fid = window.CustomFormats._buildPreformatId(catId, subId, fmt.id || '');
var name = fmt.name || fid;
var already = existingIds[fid];
var label = document.createElement('label');
label.className = 'custom-format-format-item';
var cb = document.createElement('input');
cb.type = 'checkbox';
cb.setAttribute('data-preformat-id', fid);
cb.setAttribute('data-format-name', name);
if (already) { cb.checked = true; cb.disabled = true; }
label.appendChild(cb);
label.appendChild(document.createElement('span')).textContent = name;
fmtList.appendChild(label);
});
subDiv.appendChild(fmtList);
body.appendChild(subDiv);
});
} else {
var fmtList = document.createElement('div');
fmtList.className = 'custom-format-format-list';
(cat.formats || []).forEach(function(fmt) {
var fid = window.CustomFormats._buildPreformatId(catId, null, fmt.id || '');
var name = fmt.name || fid;
var already = existingIds[fid];
var label = document.createElement('label');
label.className = 'custom-format-format-item';
var cb = document.createElement('input');
cb.type = 'checkbox';
cb.setAttribute('data-preformat-id', fid);
cb.setAttribute('data-format-name', name);
if (already) { cb.checked = true; cb.disabled = true; }
label.appendChild(cb);
label.appendChild(document.createElement('span')).textContent = name;
fmtList.appendChild(label);
});
body.appendChild(fmtList);
}
header.onclick = function() {
header.classList.toggle('collapsed');
body.classList.toggle('collapsed');
};
catDiv.appendChild(header);
catDiv.appendChild(body);
treeEl.appendChild(catDiv);
});
})
.catch(function() {
treeEl.innerHTML = '<span class="custom-format-loading" style="color:#f87171;">Failed to load formats.</span>';
});
},
_nameFromJson: function(str) {
if (!str || typeof str !== 'string') return '—';
try {
var obj = JSON.parse(str);
return (obj && obj.name != null) ? String(obj.name).trim() || '—' : '—';
} catch (e) { return '—'; }
},
_onSourceChange: function() {
var isPre = document.getElementById('custom-format-source-preformat').checked;
var preformatArea = document.getElementById('custom-format-preformat-area');
var importArea = document.getElementById('custom-format-import-area');
var jsonTa = document.getElementById('custom-format-json-textarea');
if (preformatArea) preformatArea.style.display = isPre ? 'block' : 'none';
if (importArea) importArea.style.display = isPre ? 'none' : 'block';
if (isPre) {
if (jsonTa) jsonTa.value = '';
window.CustomFormats._loadPreformatTree();
} else {
if (window.CustomFormats._editingIndex != null) {
var list = window.CustomFormats._list;
var idx = window.CustomFormats._editingIndex;
if (list && idx >= 0 && idx < list.length && jsonTa) {
jsonTa.value = list[idx].custom_format_json || '{}';
}
} else if (jsonTa) {
jsonTa.value = '';
}
}
},
saveModal: function() {
var editing = window.CustomFormats._editingIndex;
if (editing != null) {
var jsonRaw = document.getElementById('custom-format-json-textarea').value.trim();
if (!jsonRaw) {
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification('Paste valid JSON to edit.', 'error');
}
return;
}
try { JSON.parse(jsonRaw); } catch (e) {
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification('Invalid JSON.', 'error');
}
return;
}
var title = window.CustomFormats._nameFromJson(jsonRaw);
if (title === '—') title = 'Unnamed';
fetch(window.CustomFormats.getApiBase() + '/' + editing, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: title, custom_format_json: jsonRaw })
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.success) {
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification('Custom format updated.', 'success');
}
window.CustomFormats.closeModal();
window.CustomFormats.refreshList();
} else {
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification(data.message || data.error || 'Update failed', 'error');
}
}
})
.catch(function() {
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification('Update failed', 'error');
}
});
return;
}
var isPre = window.CustomFormats._modalMode === 'preformat';
if (isPre) {
var tree = document.getElementById('custom-format-preformat-tree');
var checkboxes = tree ? tree.querySelectorAll('input[type="checkbox"][data-preformat-id]:checked:not(:disabled)') : [];
var toAdd = [];
checkboxes.forEach(function(cb) {
toAdd.push({ id: cb.getAttribute('data-preformat-id'), name: cb.getAttribute('data-format-name') || cb.getAttribute('data-preformat-id') });
});
if (toAdd.length === 0) {
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification('Check at least one format to add.', 'error');
}
return;
}
var done = 0;
var failed = 0;
var currentIndex = 0;
function addNext() {
if (currentIndex >= toAdd.length) {
if (window.huntarrUI && window.huntarrUI.showNotification) {
if (failed === 0) {
window.huntarrUI.showNotification('Added ' + done + ' format(s).', 'success');
} else {
window.huntarrUI.showNotification('Added ' + done + ', failed ' + failed + '.', failed ? 'error' : 'success');
}
}
window.CustomFormats.closeModal();
window.CustomFormats.refreshList();
return;
}
var item = toAdd[currentIndex];
currentIndex++;
fetch(window.CustomFormats.getApiBase(), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ source: 'preformat', preformat_id: item.id, title: item.name })
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.success) done++; else failed++;
addNext();
})
.catch(function() {
failed++;
addNext();
});
}
addNext();
return;
}
var jsonRaw = document.getElementById('custom-format-json-textarea').value.trim();
if (!jsonRaw) {
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification('Paste Custom Format JSON.', 'error');
}
return;
}
try { JSON.parse(jsonRaw); } catch (e) {
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification('Invalid JSON.', 'error');
}
return;
}
var title = window.CustomFormats._nameFromJson(jsonRaw);
if (title === '—') title = 'Unnamed';
title = window.CustomFormats._checkTitleCollision(title);
var body = { source: 'import', custom_format_json: jsonRaw, title: title };
fetch(window.CustomFormats.getApiBase(), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.success) {
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification('Custom format added.', 'success');
}
window.CustomFormats.closeModal();
window.CustomFormats.refreshList();
} else {
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification(data.message || data.error || 'Add failed', 'error');
}
}
})
.catch(function() {
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification('Add failed', 'error');
}
});
},
deleteFormat: function(index) {
var self = window.CustomFormats;
var doDelete = function() {
fetch(self.getApiBase() + '/' + index, { method: 'DELETE' })
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.success) {
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification('Custom format removed.', 'success');
}
window.CustomFormats.refreshList();
} else {
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification(data.message || 'Delete failed', 'error');
}
}
})
.catch(function() {
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification('Delete failed', 'error');
}
});
};
if (window.HuntarrConfirm && window.HuntarrConfirm.show) {
window.HuntarrConfirm.show({
title: 'Remove Custom Format',
message: 'Remove this custom format?',
confirmLabel: 'Remove',
onConfirm: doDelete
});
} else {
if (!confirm('Remove this custom format?')) return;
doDelete();
}
},
deleteAllByType: function(type) {
var list = window.CustomFormats._list || [];
var toDelete = [];
for (var i = 0; i < list.length; i++) {
var item = list[i];
var isPreformatted = (item.source || 'import').toLowerCase() === 'preformat';
if ((type === 'preformat' && isPreformatted) || (type === 'import' && !isPreformatted)) {
toDelete.push(i);
}
}
if (toDelete.length === 0) {
if (window.huntarrUI && window.huntarrUI.showNotification) {
window.huntarrUI.showNotification('No formats to delete.', 'info');
}
return;
}
var typeName = type === 'preformat' ? 'pre-formatted' : 'imported';
var confirmMsg = 'Delete all ' + toDelete.length + ' ' + typeName + ' custom format(s)?\n\nThis action cannot be undone.';
var deleted = 0;
var failed = 0;
var currentIndex = toDelete.length - 1;
function runDeleteAll() {
currentIndex = toDelete.length - 1;
deleted = 0;
failed = 0;
deleteNext();
}
function deleteNext() {
if (currentIndex < 0) {
if (window.huntarrUI && window.huntarrUI.showNotification) {
if (failed === 0) {
window.huntarrUI.showNotification('Deleted ' + deleted + ' format(s).', 'success');
} else {
window.huntarrUI.showNotification('Deleted ' + deleted + ', failed ' + failed + '.', failed > 0 ? 'error' : 'success');
}
}
window.CustomFormats.refreshList();
return;
}
var idx = toDelete[currentIndex];
currentIndex--;
fetch(window.CustomFormats.getApiBase() + '/' + idx, { method: 'DELETE' })
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.success) deleted++; else failed++;
deleteNext();
})
.catch(function() {
failed++;
deleteNext();
});
}
if (window.HuntarrConfirm && window.HuntarrConfirm.show) {
window.HuntarrConfirm.show({
title: 'Delete All ' + typeName.charAt(0).toUpperCase() + typeName.slice(1) + ' Custom Formats',
message: confirmMsg,
confirmLabel: 'Delete All',
onConfirm: runDeleteAll
});
} else {
if (!confirm(confirmMsg)) return;
runDeleteAll();
}
},
init: function() {
var self = window.CustomFormats;
var modal = document.getElementById('custom-format-modal');
var backdrop = document.getElementById('custom-format-modal-backdrop');
var closeBtn = document.getElementById('custom-format-modal-close');
var cancelBtn = document.getElementById('custom-format-modal-cancel');
var saveBtn = document.getElementById('custom-format-modal-save');
if (backdrop) backdrop.onclick = function() { self.closeModal(); };
if (closeBtn) closeBtn.onclick = function() { self.closeModal(); };
if (cancelBtn) cancelBtn.onclick = function() { self.closeModal(); };
if (saveBtn) saveBtn.onclick = function() { self.saveModal(); };
var viewModal = document.getElementById('custom-format-view-modal');
var viewBackdrop = document.getElementById('custom-format-view-modal-backdrop');
var viewCloseBtn = document.getElementById('custom-format-view-modal-close');
var viewCloseBtnFooter = document.getElementById('custom-format-view-modal-close-btn');
if (viewBackdrop) viewBackdrop.onclick = function() { self.closeViewModal(); };
if (viewCloseBtn) viewCloseBtn.onclick = function() { self.closeViewModal(); };
if (viewCloseBtnFooter) viewCloseBtnFooter.onclick = function() { self.closeViewModal(); };
var deleteAllPreBtn = document.getElementById('delete-all-preformatted');
var deleteAllImpBtn = document.getElementById('delete-all-imported');
if (deleteAllPreBtn) {
deleteAllPreBtn.onclick = function() { self.deleteAllByType('preformat'); };
}
if (deleteAllImpBtn) {
deleteAllImpBtn.onclick = function() { self.deleteAllByType('import'); };
}
document.querySelectorAll('input[name="custom-format-source"]').forEach(function(radio) {
radio.onchange = function() { self._onSourceChange(); };
});
var jsonTa = document.getElementById('custom-format-json-textarea');
if (jsonTa) { /* title is derived from JSON on save */ }
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
if (viewModal && viewModal.style.display === 'flex') {
self.closeViewModal();
} else if (modal && modal.style.display === 'flex') {
self.closeModal();
}
}
});
}
};
document.addEventListener('huntarr:instances-changed', function() {
if (document.getElementById('settingsCustomFormatsSection') && document.getElementById('settingsCustomFormatsSection').classList.contains('active')) {
window.CustomFormats.initOrRefresh();
}
});
document.addEventListener('huntarr:tv-hunt-instances-changed', function() {
if (document.getElementById('settingsCustomFormatsSection') && document.getElementById('settingsCustomFormatsSection').classList.contains('active')) {
window.CustomFormats.initOrRefresh();
}
});
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() { window.CustomFormats.init(); });
} else {
window.CustomFormats.init();
}
})();