mirror of
https://github.com/plexguide/Huntarr.io.git
synced 2026-04-22 16:46:51 -04:00
872 lines
45 KiB
JavaScript
872 lines
45 KiB
JavaScript
/**
|
||
* 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, '<').replace(/>/g, '>');
|
||
|
||
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, '<').replace(/>/g, '>');
|
||
|
||
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, '<').replace(/>/g, '>')) + '</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();
|
||
}
|
||
})();
|