- Add support for virtual folders
- Fix minor bug fixes
This commit is contained in:
@@ -419,9 +419,192 @@
|
||||
<input type="password" class="form-control webdav-field" name="debrid[${index}].rc_pass" id="debrid[${index}].rc_pass">
|
||||
<small class="form-text text-muted">Rclone RC Password for the webdav server</small>
|
||||
</div>
|
||||
<div class="col mt-3">
|
||||
<h6 class="pb-2">Custom Folders</h6>
|
||||
<div class="col-12">
|
||||
<p class="text-muted small">Create virtual directories with filters to organize your content</p>
|
||||
<div class="directories-container" id="debrid[${index}].directories">
|
||||
<!-- Dynamic directories will be added here -->
|
||||
</div>
|
||||
<button type="button" class="btn btn-secondary mt-2 webdav-field" onclick="addDirectory(${index});">
|
||||
<i class="bi bi-plus"></i> Add Directory
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
// Template for directory entries (with filter buttons for both positive and negative variants)
|
||||
const directoryTemplate = (debridIndex, dirIndex) => `
|
||||
<div class="directory-item mb-3 border rounded p-3 position-relative">
|
||||
<button type="button" class="btn btn-sm btn-danger position-absolute top-0 end-0 m-2"
|
||||
onclick="removeDirectory(this);" title="Remove directory">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Folder Name</label>
|
||||
<input type="text" class="form-control webdav-field"
|
||||
name="debrid[${debridIndex}].directory[${dirIndex}].name"
|
||||
placeholder="e.g., Movies, TV Shows, Spiderman Collection">
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<h6 class="mb-3">
|
||||
Filters
|
||||
<button type="button" class="btn btn-sm btn-link" onclick="showFilterHelp();">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</button>
|
||||
</h6>
|
||||
<div class="filters-container" id="debrid[${debridIndex}].directory[${dirIndex}].filters">
|
||||
<!-- Filters will be added here -->
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<div class="dropdown d-inline-block me-2 mb-2">
|
||||
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button"
|
||||
data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Add Text Filter
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="#" onclick="addFilter(${debridIndex}, ${dirIndex}, 'include'); return false;">Include</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="addFilter(${debridIndex}, ${dirIndex}, 'exclude'); return false;">Exclude</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="addFilter(${debridIndex}, ${dirIndex}, 'starts_with'); return false;">Starts With</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="addFilter(${debridIndex}, ${dirIndex}, 'not_starts_with'); return false;">Not Starts With</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="addFilter(${debridIndex}, ${dirIndex}, 'ends_with'); return false;">Ends With</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="addFilter(${debridIndex}, ${dirIndex}, 'not_ends_with'); return false;">Not Ends With</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="addFilter(${debridIndex}, ${dirIndex}, 'exact_match'); return false;">Exact Match</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="addFilter(${debridIndex}, ${dirIndex}, 'not_exact_match'); return false;">Not Exact Match</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="dropdown d-inline-block me-2 mb-2">
|
||||
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button"
|
||||
data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Add Regex Filter
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="#" onclick="addFilter(${debridIndex}, ${dirIndex}, 'regex'); return false;">Regex Match</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="addFilter(${debridIndex}, ${dirIndex}, 'not_regex'); return false;">Regex Not Match</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="dropdown d-inline-block me-2 mb-2">
|
||||
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button"
|
||||
data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Add Size Filter
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="#" onclick="addFilter(${debridIndex}, ${dirIndex}, 'size_gt'); return false;">Size Greater Than</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="addFilter(${debridIndex}, ${dirIndex}, 'size_lt'); return false;">Size Less Than</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary me-2"
|
||||
onclick="addFilter(${debridIndex}, ${dirIndex}, 'last_added');">
|
||||
Add Last Added Filter
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
// Enhanced filter template with support for all filter types
|
||||
const filterTemplate = (debridIndex, dirIndex, filterIndex, filterType) => {
|
||||
let placeholder, label;
|
||||
|
||||
switch(filterType) {
|
||||
case 'include':
|
||||
placeholder = "Text that should be included in filename";
|
||||
label = "Include";
|
||||
break;
|
||||
case 'exclude':
|
||||
placeholder = "Text that should not be in filename";
|
||||
label = "Exclude";
|
||||
break;
|
||||
case 'regex':
|
||||
placeholder = "Regular expression pattern";
|
||||
label = "Regex Match";
|
||||
break;
|
||||
case 'not_regex':
|
||||
placeholder = "Regular expression pattern that should not match";
|
||||
label = "Regex Not Match";
|
||||
break;
|
||||
case 'exact_match':
|
||||
placeholder = "Exact text to match";
|
||||
label = "Exact Match";
|
||||
break;
|
||||
case 'not_exact_match':
|
||||
placeholder = "Exact text that should not match";
|
||||
label = "Not Exact Match";
|
||||
break;
|
||||
case 'starts_with':
|
||||
placeholder = "Text that filename starts with";
|
||||
label = "Starts With";
|
||||
break;
|
||||
case 'not_starts_with':
|
||||
placeholder = "Text that filename should not start with";
|
||||
label = "Not Starts With";
|
||||
break;
|
||||
case 'ends_with':
|
||||
placeholder = "Text that filename ends with";
|
||||
label = "Ends With";
|
||||
break;
|
||||
case 'not_ends_with':
|
||||
placeholder = "Text that filename should not end with";
|
||||
label = "Not Ends With";
|
||||
break;
|
||||
case 'size_gt':
|
||||
placeholder = "Size in bytes, KB, MB, GB (e.g. 700MB)";
|
||||
label = "Size Greater Than";
|
||||
break;
|
||||
case 'size_lt':
|
||||
placeholder = "Size in bytes, KB, MB, GB (e.g. 700MB)";
|
||||
label = "Size Less Than";
|
||||
break;
|
||||
case 'last_added':
|
||||
placeholder = "Time duration (e.g. 24h, 7d, 30d)";
|
||||
label = "Added in the last";
|
||||
break;
|
||||
default:
|
||||
placeholder = "Filter value";
|
||||
label = filterType.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
||||
}
|
||||
|
||||
// Use a color coding scheme for filter types
|
||||
let badgeClass = "bg-secondary";
|
||||
if (filterType.startsWith('not_') || filterType === 'exclude' || filterType === 'size_lt') {
|
||||
badgeClass = "bg-danger"; // Negative filters
|
||||
} else if (filterType === 'last_added') {
|
||||
badgeClass = "bg-info"; // Time-based filters
|
||||
} else if (filterType === 'size_gt') {
|
||||
badgeClass = "bg-success"; // Size filters
|
||||
} else if (filterType === 'regex' || filterType === 'not_regex') {
|
||||
badgeClass = "bg-warning"; // Regex filters
|
||||
} else if (filterType === 'include' || filterType === 'starts_with' || filterType === 'ends_with' || filterType === 'exact_match') {
|
||||
badgeClass = "bg-primary"; // Positive text filters
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="filter-item row mb-2 align-items-center">
|
||||
<div class="col-md-3">
|
||||
<span class="badge ${badgeClass}">${label}</span>
|
||||
<input type="hidden"
|
||||
name="debrid[${debridIndex}].directory[${dirIndex}].filter[${filterIndex}].type"
|
||||
value="${filterType}">
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control form-control-sm webdav-field"
|
||||
name="debrid[${debridIndex}].directory[${dirIndex}].filter[${filterIndex}].value"
|
||||
placeholder="${placeholder}">
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<button type="button" class="btn btn-sm btn-danger" onclick="removeFilter(this);">
|
||||
<i class="bi bi-x"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
|
||||
const arrTemplate = (index) => `
|
||||
<div class="config-item position-relative mb-3 p-3 border rounded">
|
||||
@@ -467,6 +650,91 @@
|
||||
</div>
|
||||
`;
|
||||
|
||||
const debridDirectoryCounts = {};
|
||||
const directoryFilterCounts = {};
|
||||
|
||||
// Helper function to show a tooltip explaining filter types
|
||||
function showFilterHelp() {
|
||||
const helpContent = `
|
||||
<h5>Filter Types</h5>
|
||||
<ul>
|
||||
<li><strong>Include/Exclude</strong>: Simple text inclusion/exclusion</li>
|
||||
<li><strong>Starts/Ends With</strong>: Matches beginning or end of filename</li>
|
||||
<li><strong>Exact Match</strong>: Match the entire filename</li>
|
||||
<li><strong>Regex</strong>: Use regular expressions for complex patterns</li>
|
||||
<li><strong>Size</strong>: Filter by file size</li>
|
||||
<li><strong>Last Added</strong>: Show only recently added content</li>
|
||||
</ul>
|
||||
<p>Negative filters (Not...) will exclude matches instead of including them.</p>
|
||||
`;
|
||||
|
||||
// Show a modal or tooltip with this content
|
||||
// This will depend on your UI framework
|
||||
// For Bootstrap:
|
||||
$('#filterHelpModal .modal-body').html(helpContent);
|
||||
$('#filterHelpModal').modal('show');
|
||||
}
|
||||
|
||||
function addDirectory(debridIndex, data = {}) {
|
||||
if (!debridDirectoryCounts[debridIndex]) {
|
||||
debridDirectoryCounts[debridIndex] = 0;
|
||||
}
|
||||
|
||||
const dirIndex = debridDirectoryCounts[debridIndex];
|
||||
const container = document.getElementById(`debrid[${debridIndex}].directories`);
|
||||
container.insertAdjacentHTML('beforeend', directoryTemplate(debridIndex, dirIndex));
|
||||
|
||||
// Set up tracking for filters in this directory
|
||||
const dirKey = `${debridIndex}-${dirIndex}`;
|
||||
directoryFilterCounts[dirKey] = 0;
|
||||
|
||||
// Fill with directory name if provided
|
||||
if (data.name) {
|
||||
const nameInput = document.querySelector(`[name="debrid[${debridIndex}].directory[${dirIndex}].name"]`);
|
||||
if (nameInput) nameInput.value = data.name;
|
||||
}
|
||||
|
||||
// Add filters if provided
|
||||
if (data.filters) {
|
||||
Object.entries(data.filters).forEach(([filterType, filterValue]) => {
|
||||
addFilter(debridIndex, dirIndex, filterType, filterValue);
|
||||
});
|
||||
}
|
||||
|
||||
debridDirectoryCounts[debridIndex]++;
|
||||
return dirIndex;
|
||||
}
|
||||
function addFilter(debridIndex, dirIndex, filterType, filterValue = "") {
|
||||
const dirKey = `${debridIndex}-${dirIndex}`;
|
||||
if (!directoryFilterCounts[dirKey]) {
|
||||
directoryFilterCounts[dirKey] = 0;
|
||||
}
|
||||
|
||||
const filterIndex = directoryFilterCounts[dirKey];
|
||||
const container = document.getElementById(`debrid[${debridIndex}].directory[${dirIndex}].filters`);
|
||||
|
||||
if (container) {
|
||||
container.insertAdjacentHTML('beforeend', filterTemplate(debridIndex, dirIndex, filterIndex, filterType));
|
||||
|
||||
// Set filter value if provided
|
||||
if (filterValue) {
|
||||
const valueInput = container.querySelector(`[name="debrid[${debridIndex}].directory[${dirIndex}].filter[${filterIndex}].value"]`);
|
||||
if (valueInput) valueInput.value = filterValue;
|
||||
}
|
||||
|
||||
directoryFilterCounts[dirKey]++;
|
||||
}
|
||||
}
|
||||
|
||||
function removeDirectory(button) {
|
||||
button.closest('.directory-item').remove();
|
||||
}
|
||||
|
||||
// Function to remove a filter
|
||||
function removeFilter(button) {
|
||||
button.closest('.filter-item').remove();
|
||||
}
|
||||
|
||||
// Main functionality
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
let debridCount = 0;
|
||||
@@ -710,6 +978,19 @@
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (data.use_webdav && data.directories) {
|
||||
Object.entries(data.directories).forEach(([dirName, dirData]) => {
|
||||
const dirIndex = addDirectory(debridCount, { name: dirName });
|
||||
|
||||
// Add filters if available
|
||||
if (dirData.filters) {
|
||||
Object.entries(dirData.filters).forEach(([filterType, filterValue]) => {
|
||||
addFilter(debridCount, dirIndex, filterType, filterValue);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
debridCount++;
|
||||
@@ -809,6 +1090,33 @@
|
||||
debrid.rc_url = document.querySelector(`[name="debrid[${i}].rc_url"]`).value;
|
||||
debrid.rc_user = document.querySelector(`[name="debrid[${i}].rc_user"]`).value;
|
||||
debrid.rc_pass = document.querySelector(`[name="debrid[${i}].rc_pass"]`).value;
|
||||
|
||||
//custom folders
|
||||
debrid.directories = {};
|
||||
const dirCount = debridDirectoryCounts[i] || 0;
|
||||
|
||||
for (let j = 0; j < dirCount; j++) {
|
||||
const nameInput = document.querySelector(`[name="debrid[${i}].directory[${j}].name"]`);
|
||||
if (nameInput && nameInput.value) {
|
||||
const dirName = nameInput.value;
|
||||
debrid.directories[dirName] = { filters: {} };
|
||||
|
||||
// Get directory key for filter counting
|
||||
const dirKey = `${i}-${j}`;
|
||||
const filterCount = directoryFilterCounts[dirKey] || 0;
|
||||
|
||||
// Collect all filters for this directory
|
||||
for (let k = 0; k < filterCount; k++) {
|
||||
const filterTypeInput = document.querySelector(`[name="debrid[${i}].directory[${j}].filter[${k}].type"]`);
|
||||
const filterValueInput = document.querySelector(`[name="debrid[${i}].directory[${j}].filter[${k}].value"]`);
|
||||
|
||||
if (filterTypeInput && filterValueInput && filterValueInput.value) {
|
||||
const filterType = filterTypeInput.value;
|
||||
debrid.directories[dirName].filters[filterType] = filterValueInput.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (debrid.name && debrid.api_key) {
|
||||
@@ -864,4 +1172,22 @@
|
||||
document.getElementById('registerMagnetLink').classList.add('bg-white', 'text-black');
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Filter Help Modal -->
|
||||
<div class="modal fade" id="filterHelpModal" tabindex="-1" aria-labelledby="filterHelpModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="filterHelpModalLabel">Directory Filter Help</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- Content will be injected by the showFilterHelp function -->
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
Reference in New Issue
Block a user