Skip to content

Commit de7fd12

Browse files
Fix Docker folder order persistence
1 parent 92dc336 commit de7fd12

6 files changed

Lines changed: 132 additions & 10 deletions

File tree

14.2 MB
Binary file not shown.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
564dfcf82f3f63dfbe2e887f29e0e1f3a000fd49a5c69c7612634c4b39bb7067 folderview.plus-2026.03.29.07.txz

folderview.plus.plg

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,18 @@
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.06">
10-
<!ENTITY md5 "fbcbf84f31aa881f80a8d6e684f0b075">
9+
<!ENTITY version "2026.03.29.07">
10+
<!ENTITY md5 "0d0c50ec4da1ed297a6d44ac6107bebf">
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.07
17+
- Fix: Preserve dragged Docker folder order across refreshes instead of re-sorting folder placeholders back to folder definition order.
18+
- Quality: Add regression coverage for Docker runtime folder-slot repair and server-side order sync placeholder preservation.
19+
20+
1621
###2026.03.29.06
1722
- Fix: Stop Docker WebUI buttons and Open all WebUIs from using unresolved template URLs like http://[IP]:[PORT:80] during the lightweight runtime pass.
1823
- Maintenance: Rebuild folder runtime WebUI targets after full Docker detail hydration so resolved ports replace stale placeholder values.

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

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1779,15 +1779,32 @@ const reorderFolderSlotsInBaseOrder = (baseOrder, folders, prefs) => {
17791779
if (!desiredFolderTokens.length) {
17801780
return order;
17811781
}
1782-
let desiredIndex = 0;
1782+
const liveFolderTokens = new Set();
1783+
order.forEach((entry) => {
1784+
if (!folderRegex.test(entry)) {
1785+
return;
1786+
}
1787+
const folderId = entry.replace(folderRegex, '');
1788+
if (Object.prototype.hasOwnProperty.call(folderMap, folderId)) {
1789+
liveFolderTokens.add(entry);
1790+
}
1791+
});
1792+
const missingDesiredTokens = desiredFolderTokens.filter((token) => !liveFolderTokens.has(token));
1793+
const usedFolderTokens = new Set();
1794+
let missingIndex = 0;
17831795
return order.map((entry) => {
17841796
if (!folderRegex.test(entry)) {
17851797
return entry;
17861798
}
1787-
while (desiredIndex < desiredFolderTokens.length) {
1788-
const candidate = desiredFolderTokens[desiredIndex++];
1789-
const candidateId = candidate.replace(folderRegex, '');
1790-
if (Object.prototype.hasOwnProperty.call(folderMap, candidateId)) {
1799+
const folderId = entry.replace(folderRegex, '');
1800+
if (Object.prototype.hasOwnProperty.call(folderMap, folderId) && !usedFolderTokens.has(entry)) {
1801+
usedFolderTokens.add(entry);
1802+
return entry;
1803+
}
1804+
while (missingIndex < missingDesiredTokens.length) {
1805+
const candidate = missingDesiredTokens[missingIndex++];
1806+
if (!usedFolderTokens.has(candidate)) {
1807+
usedFolderTokens.add(candidate);
17911808
return candidate;
17921809
}
17931810
}

src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/server/lib.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5183,11 +5183,13 @@ function syncContainerOrder(string $type): void {
51835183
$newOrder = [];
51845184
$seen = [];
51855185
$folderPlaceholders = array_keys($folderContainers);
5186+
$orderedFolderPlaceholders = [];
51865187

5187-
// Preserve non-folder container order from userprefs, but always rebuild
5188-
// folder placeholder order from docker.json to avoid stale/reversed order.
51895188
foreach ($currentOrder as $item) {
51905189
if (in_array($item, $folderPlaceholders, true)) {
5190+
if (!in_array($item, $orderedFolderPlaceholders, true)) {
5191+
$orderedFolderPlaceholders[] = $item;
5192+
}
51915193
continue;
51925194
}
51935195
if (in_array($item, $assignedContainers, true)) {
@@ -5206,8 +5208,15 @@ function syncContainerOrder(string $type): void {
52065208
}
52075209
}
52085210

5209-
// Append folders in folder definition order.
52105211
foreach ($folderPlaceholders as $placeholder) {
5212+
if (!in_array($placeholder, $orderedFolderPlaceholders, true)) {
5213+
$orderedFolderPlaceholders[] = $placeholder;
5214+
}
5215+
}
5216+
5217+
// Preserve existing folder placeholder order from userprefs and only
5218+
// fall back to folder definition order for placeholders that are missing.
5219+
foreach ($orderedFolderPlaceholders as $placeholder) {
52115220
foreach ($folderContainers[$placeholder] as $ct) {
52125221
if (!in_array($ct, $seen, true)) {
52135222
$newOrder[] = $ct;
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import test from 'node:test';
2+
import assert from 'node:assert/strict';
3+
import fs from 'node:fs';
4+
import path from 'node:path';
5+
6+
const repoRoot = path.resolve(process.cwd());
7+
const dockerJs = fs.readFileSync(
8+
path.join(repoRoot, 'src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/scripts/docker.js'),
9+
'utf8'
10+
);
11+
const libPhp = fs.readFileSync(
12+
path.join(repoRoot, 'src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/server/lib.php'),
13+
'utf8'
14+
);
15+
16+
const reorderFolderSlotsMatch = dockerJs.match(/const reorderFolderSlotsInBaseOrder = \(baseOrder, folders, prefs\) => \{([\s\S]*?)\n\};/);
17+
18+
test('docker runtime preserves live folder placeholder order from host order on refresh', () => {
19+
assert.ok(reorderFolderSlotsMatch, 'reorderFolderSlotsInBaseOrder definition should exist');
20+
const reorderFolderSlotsInBaseOrder = new Function(
21+
'baseOrder',
22+
'folders',
23+
'prefs',
24+
'folderRegex',
25+
'getPrefsOrderedFolderMap',
26+
`${reorderFolderSlotsMatch[1]}`
27+
);
28+
29+
const folderRegex = /^folder-/;
30+
const folders = {
31+
a: { name: '07' },
32+
b: { name: '08' },
33+
c: { name: '09' }
34+
};
35+
const prefs = {
36+
sortMode: 'created',
37+
manualOrder: []
38+
};
39+
const getPrefsOrderedFolderMap = () => ({
40+
a: folders.a,
41+
b: folders.b,
42+
c: folders.c
43+
});
44+
45+
const baseOrder = ['folder-a', 'folder-c', 'folder-b'];
46+
const nextOrder = reorderFolderSlotsInBaseOrder(baseOrder, folders, prefs, folderRegex, getPrefsOrderedFolderMap);
47+
48+
assert.deepEqual(nextOrder, ['folder-a', 'folder-c', 'folder-b']);
49+
});
50+
51+
test('docker runtime only backfills missing folder placeholders instead of reordering valid ones', () => {
52+
assert.ok(reorderFolderSlotsMatch, 'reorderFolderSlotsInBaseOrder definition should exist');
53+
const reorderFolderSlotsInBaseOrder = new Function(
54+
'baseOrder',
55+
'folders',
56+
'prefs',
57+
'folderRegex',
58+
'getPrefsOrderedFolderMap',
59+
`${reorderFolderSlotsMatch[1]}`
60+
);
61+
62+
const folderRegex = /^folder-/;
63+
const folders = {
64+
a: { name: '07' },
65+
b: { name: '08' },
66+
c: { name: '09' }
67+
};
68+
const getPrefsOrderedFolderMap = () => ({
69+
a: folders.a,
70+
b: folders.b,
71+
c: folders.c
72+
});
73+
74+
const baseOrder = ['folder-stale', 'folder-c', 'folder-a'];
75+
const nextOrder = reorderFolderSlotsInBaseOrder(baseOrder, folders, { sortMode: 'created' }, folderRegex, getPrefsOrderedFolderMap);
76+
77+
assert.deepEqual(nextOrder, ['folder-b', 'folder-c', 'folder-a']);
78+
});
79+
80+
test('docker order sync preserves current folder placeholder sequence before appending missing placeholders', () => {
81+
assert.match(
82+
libPhp,
83+
/\$orderedFolderPlaceholders = \[\];[\s\S]*?foreach \(\$currentOrder as \$item\) \{[\s\S]*?if \(in_array\(\$item, \$folderPlaceholders, true\)\) \{[\s\S]*?\$orderedFolderPlaceholders\[\] = \$item;[\s\S]*?continue;[\s\S]*?\}/
84+
);
85+
assert.match(
86+
libPhp,
87+
/foreach \(\$folderPlaceholders as \$placeholder\) \{[\s\S]*?if \(!in_array\(\$placeholder, \$orderedFolderPlaceholders, true\)\) \{[\s\S]*?\$orderedFolderPlaceholders\[\] = \$placeholder;[\s\S]*?\}[\s\S]*?\}/
88+
);
89+
assert.match(libPhp, /foreach \(\$orderedFolderPlaceholders as \$placeholder\) \{/);
90+
});

0 commit comments

Comments
 (0)