Skip to content

Commit e0e7314

Browse files
Add Docker branch folder deletion and clone regression tests
1 parent b6744d1 commit e0e7314

7 files changed

Lines changed: 522 additions & 3 deletions

File tree

archive/folderview.plus-2026.03.27.14.txz.sha256

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
2e50919724345599044aadda70d74bab2830c4a6c5bf07ee30ecb26b875e0a55 folderview.plus-2026.03.29.11.txz

folderview.plus.plg

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@
66
<!ENTITY launch "Settings/FolderViewPlus">
77
<!ENTITY plugdir "/usr/local/emhttp/plugins/&name;">
88
<!ENTITY pluginURL "https://raw.githubusercontent.com/&github;/dev/folderview.plus.plg">
9-
<!ENTITY version "2026.03.29.10">
10-
<!ENTITY md5 "c444ec2ee928781929076fd070f76b70">
9+
<!ENTITY version "2026.03.29.11">
10+
<!ENTITY md5 "3823ae3651dbae4395f304b29c7c149f">
1111
]>
1212

1313
<PLUGIN name="&name;" author="&author;" version="&version;" launch="&launch;" pluginURL="&pluginURL;" icon="folder-icon.png" support="https://forums.unraid.net/topic/197631-plugin-folderview-plus/" min="7.0.0">
1414
<CHANGES>
1515

16+
###2026.03.29.11
17+
- Feature: Add `Delete branch folders` under Docker branch actions to remove a root folder and every nested child folder in that branch.
18+
- Quality: Add behavior-level regression coverage for clone-of-clone branch cloning, deep-copy payload safety, rollback ordering, and branch delete ordering.
19+
1620
###2026.03.29.10
1721
- Fix: Restore Docker branch hover submenus by allowing runtime context menus to keep submenu overflow visible.
1822
- Feature: Add a Docker `Clone` submenu with `Clone folder` plus `Clone branch`, including nested branch cloning with preserved parent-child structure.

src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/scripts/docker.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4512,6 +4512,95 @@ const rmFolder = (id) => {
45124512
});
45134513
};
45144514

4515+
const getLockedDockerBranchFolderIds = (id) => {
4516+
const branchIds = [id, ...getFolderDescendants(id)];
4517+
return branchIds.filter((folderId) => isDockerFolderLocked(folderId));
4518+
};
4519+
4520+
const ensureDockerBranchUnlocked = (id, actionLabel = 'This action') => {
4521+
const lockedIds = getLockedDockerBranchFolderIds(id);
4522+
if (!lockedIds.length) {
4523+
return true;
4524+
}
4525+
const previewNames = lockedIds
4526+
.slice(0, 4)
4527+
.map((folderId) => escapeHtml(String(globalFolders?.[folderId]?.name || folderId)))
4528+
.join(', ');
4529+
const hiddenCount = Math.max(0, lockedIds.length - Math.min(4, lockedIds.length));
4530+
const lockedLabel = lockedIds.length === 1 ? 'A folder in this branch is locked.' : `${lockedIds.length} folders in this branch are locked.`;
4531+
const hiddenSuffix = hiddenCount > 0 ? ` (+${hiddenCount} more)` : '';
4532+
const detailsLine = previewNames ? `<br><strong>Locked:</strong> ${previewNames}${hiddenSuffix}` : '';
4533+
swal({
4534+
title: 'Folder branch locked',
4535+
text: `${escapeHtml(actionLabel)} is blocked while ${lockedLabel}${detailsLine}<br>Unlock the locked folder rows first and try again.`,
4536+
type: 'info',
4537+
html: true,
4538+
confirmButtonText: 'OK'
4539+
});
4540+
return false;
4541+
};
4542+
4543+
const deleteDockerFolderBranch = async (id) => {
4544+
const deleteIds = [...getFolderDescendants(id)].reverse();
4545+
deleteIds.push(id);
4546+
for (const deleteId of deleteIds) {
4547+
await $.post('/plugins/folderview.plus/server/delete.php', { type: 'docker', id: deleteId }).promise();
4548+
}
4549+
};
4550+
4551+
const rmFolderBranch = (id) => {
4552+
if (FOLDER_VIEW_DEBUG_MODE) console.log(`[FV3_DEBUG] rmFolderBranch (id: ${id}): Entry.`);
4553+
if (!ensureDockerBranchUnlocked(id, 'Delete branch folders')) {
4554+
return;
4555+
}
4556+
const folder = globalFolders[id] || {};
4557+
const folderName = escapeHtml(String(folder.name || id));
4558+
const descendantIds = getFolderDescendants(id);
4559+
const branchIds = [id, ...descendantIds];
4560+
const previewNames = branchIds
4561+
.slice(0, 5)
4562+
.map((folderId) => escapeHtml(String(globalFolders?.[folderId]?.name || folderId)))
4563+
.join(', ');
4564+
const hiddenCount = Math.max(0, branchIds.length - Math.min(5, branchIds.length));
4565+
const impactLines = [
4566+
`Delete branch folders: ${folderName}`,
4567+
`This will permanently delete <strong>${branchIds.length}</strong> folder${branchIds.length === 1 ? '' : 's'} in this branch.`,
4568+
`Nested child folders will be deleted with the root folder and will <strong>not</strong> be re-parented.`
4569+
];
4570+
if (previewNames) {
4571+
impactLines.push(`<strong>Branch:</strong> ${previewNames}${hiddenCount > 0 ? ` (+${hiddenCount} more)` : ''}`);
4572+
}
4573+
swal({
4574+
title: $.i18n('are-you-sure'),
4575+
text: impactLines.join('<br>'),
4576+
type: 'warning',
4577+
html: true,
4578+
showCancelButton: true,
4579+
confirmButtonText: $.i18n('yes-delete'),
4580+
cancelButtonText: $.i18n('cancel'),
4581+
showLoaderOnConfirm: true
4582+
},
4583+
async (confirmed) => {
4584+
if (FOLDER_VIEW_DEBUG_MODE) console.log(`[FV3_DEBUG] rmFolderBranch (id: ${id}): Swal callback. Confirmed: ${confirmed}`);
4585+
if (!confirmed) { setTimeout(loadlist, 0); return; }
4586+
$('div.spinner.fixed').show('slow');
4587+
try {
4588+
const result = await runDockerGuardedAction('delete-folder-branch', async () => {
4589+
await deleteDockerFolderBranch(id);
4590+
setTimeout(loadlist, 500);
4591+
}, {
4592+
userMessage: getDockerMenuLabel('delete-branch-folders-failed', 'Failed to delete branch folders.'),
4593+
userVisible: true
4594+
});
4595+
if (!result.ok) {
4596+
setTimeout(loadlist, 0);
4597+
}
4598+
} finally {
4599+
$('div.spinner.fixed').hide('slow');
4600+
}
4601+
});
4602+
};
4603+
45154604
/**
45164605
* Redirect to the page to edit the folder
45174606
* @param {string} id the id of the folder
@@ -5483,6 +5572,14 @@ const addDockerFolderContext = (id) => {
54835572
}
54845573
});
54855574
}
5575+
branchSubMenu.push({
5576+
text: 'Delete branch folders',
5577+
icon: 'fa-trash',
5578+
action: (evt) => {
5579+
evt.preventDefault();
5580+
rmFolderBranch(id);
5581+
}
5582+
});
54865583
if (branchSubMenu.length > 0) {
54875584
opts.push({
54885585
text: 'Branch actions',

0 commit comments

Comments
 (0)