Skip to content

Commit 4c8c823

Browse files
author
FolderView Plus Test
committed
Add timestamp and descending folder sort modes
1 parent 7798e4a commit 4c8c823

13 files changed

Lines changed: 218 additions & 17 deletions

archive/folderview.plus-2026.04.05.06.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+
7d6eaf5daa380758ab0ab0c329d48447f319db18c0f2e2182f49524ea954226b folderview.plus-2026.04.06.17.txz

folderview.plus.plg

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,22 @@
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.04.06.16">
10-
<!ENTITY md5 "538eacb1f75067af2113a0fe39fa68d5">
9+
<!ENTITY version "2026.04.06.17">
10+
<!ENTITY md5 "d860b0e553470296af956d5c42b9b09a">
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.04.06.17
17+
- Fix: Docker runtime rows, folder state, and container interactions.
18+
- UX: Folder editor flows, previews, and bootstrap behavior.
19+
- UX: Settings workspace layout, section flows, and table behavior.
20+
- Feature: Setup Assistant, rules, smart-detect, and starter-template workflows.
21+
- Refactor: Shared runtime contracts, request plumbing, and cross-page foundations.
22+
- Fix: Server endpoints, runtime payloads, and persistence or validation paths.
23+
24+
1625
###2026.04.06.16
1726
- Fix: Docker runtime rows, folder state, and container interactions.
1827
- Fix: Server endpoints, runtime payloads, and persistence or validation paths.

src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/FolderViewPlus.page

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,12 @@ if (!empty($fvplusRuntimeConflicts)) {
100100
<label class="sort-label" for="docker-sort-mode">Sort mode</label>
101101
<select id="docker-sort-mode" onchange="changeSortMode('docker', this.value)">
102102
<option value="created">Created order</option>
103+
<option value="created_newest">Created newest first</option>
104+
<option value="created_oldest">Created oldest first</option>
105+
<option value="updated_newest">Last updated newest first</option>
103106
<option value="manual">Manual (buttons)</option>
104107
<option value="alpha">Name (A-Z)</option>
108+
<option value="name_desc">Name (Z-A)</option>
105109
</select>
106110
<span class="sort-hint-chip" title="Use manual mode with up/down and tree move controls in the first column to set custom nested order."><i class="fa fa-info-circle"></i> Manual uses up/down + tree move in Order column</span>
107111
<span class="tree-visibility-controls">
@@ -335,8 +339,12 @@ if (!empty($fvplusRuntimeConflicts)) {
335339
<label class="sort-label" for="vm-sort-mode">Sort mode</label>
336340
<select id="vm-sort-mode" onchange="changeSortMode('vm', this.value)">
337341
<option value="created">Created order</option>
342+
<option value="created_newest">Created newest first</option>
343+
<option value="created_oldest">Created oldest first</option>
344+
<option value="updated_newest">Last updated newest first</option>
338345
<option value="manual">Manual (buttons)</option>
339346
<option value="alpha">Name (A-Z)</option>
347+
<option value="name_desc">Name (Z-A)</option>
340348
</select>
341349
<span class="sort-hint-chip" title="Use manual mode with up/down and tree move controls in the first column to set custom nested order."><i class="fa fa-info-circle"></i> Manual uses up/down + tree move in Order column</span>
342350
<span class="tree-visibility-controls">

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1897,7 +1897,7 @@ const reorderFolderSlotsInBaseOrder = (baseOrder, folders, prefs) => {
18971897
? baseOrder.map((item) => String(item || ''))
18981898
: Object.values(baseOrder || {}).map((item) => String(item || ''));
18991899
const folderMap = folders && typeof folders === 'object' ? folders : {};
1900-
const sortMode = ['manual', 'alpha'].includes(String(prefs?.sortMode || '').trim().toLowerCase())
1900+
const sortMode = ['manual', 'alpha', 'name_desc', 'created_newest', 'created_oldest', 'updated_newest'].includes(String(prefs?.sortMode || '').trim().toLowerCase())
19011901
? String(prefs.sortMode).trim().toLowerCase()
19021902
: 'created';
19031903
const hasPinnedFolders = Array.isArray(prefs?.pinnedFolderIds) && prefs.pinnedFolderIds.length > 0;

src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/scripts/folderviewplus.utils.js

Lines changed: 80 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -569,9 +569,19 @@
569569
return orderedIds;
570570
};
571571

572+
const FOLDER_SORT_MODES = Object.freeze([
573+
'created',
574+
'created_newest',
575+
'created_oldest',
576+
'updated_newest',
577+
'manual',
578+
'alpha',
579+
'name_desc'
580+
]);
581+
572582
const normalizePrefs = (prefs) => {
573583
const incoming = isPlainObject(prefs) ? prefs : {};
574-
const sortMode = ['created', 'manual', 'alpha'].includes(incoming.sortMode) ? incoming.sortMode : 'created';
584+
const sortMode = FOLDER_SORT_MODES.includes(incoming.sortMode) ? incoming.sortMode : 'created';
575585
const manualOrder = Array.isArray(incoming.manualOrder) ? incoming.manualOrder.filter((id) => typeof id === 'string' && id !== '') : [];
576586
const autoRulesRaw = Array.isArray(incoming.autoRules) ? incoming.autoRules : [];
577587
const defaultSchedule = {
@@ -796,6 +806,32 @@
796806
const orderFoldersByPrefs = (folders, prefs) => {
797807
const normalizedFolders = normalizeFolderMap(folders);
798808
const normalizedPrefs = normalizePrefs(prefs);
809+
const baseOrderIds = Object.keys(normalizedFolders);
810+
const baseOrderIndex = new Map(baseOrderIds.map((id, index) => [id, index]));
811+
const normalizeSortTimestamp = (value) => {
812+
const raw = typeof value === 'string' ? value.trim() : '';
813+
if (!raw) {
814+
return null;
815+
}
816+
const parsed = Date.parse(raw);
817+
return Number.isFinite(parsed) ? parsed : null;
818+
};
819+
const buildSortedMapFromKeys = (keys) => {
820+
const ordered = {};
821+
for (const key of keys) {
822+
ordered[key] = normalizedFolders[key];
823+
}
824+
return ordered;
825+
};
826+
const sortKeysWithComparator = (comparator) => (
827+
baseOrderIds.slice().sort((leftId, rightId) => {
828+
const compared = comparator(leftId, rightId);
829+
if (compared !== 0) {
830+
return compared;
831+
}
832+
return (baseOrderIndex.get(leftId) ?? 0) - (baseOrderIndex.get(rightId) ?? 0);
833+
})
834+
);
799835
const resolvePinnedBranchRootId = (id) => {
800836
const safeId = String(id || '').trim();
801837
if (!safeId || !Object.prototype.hasOwnProperty.call(normalizedFolders, safeId)) {
@@ -842,16 +878,55 @@
842878
};
843879

844880
if (normalizedPrefs.sortMode === 'alpha') {
845-
const keys = Object.keys(normalizedFolders).sort((a, b) => {
881+
const keys = sortKeysWithComparator((a, b) => {
846882
const nameA = String(normalizedFolders[a]?.name ?? a).toLowerCase();
847883
const nameB = String(normalizedFolders[b]?.name ?? b).toLowerCase();
848884
const cmp = nameA.localeCompare(nameB, undefined, { numeric: true, sensitivity: 'base' });
849885
return cmp !== 0 ? cmp : a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' });
850886
});
851-
const ordered = {};
852-
for (const key of keys) {
853-
ordered[key] = normalizedFolders[key];
887+
const ordered = buildSortedMapFromKeys(keys);
888+
const pinnedApplied = applyPinnedOrder(ordered);
889+
const nestedIds = buildNestedFolderOrderIdsFromMap(pinnedApplied);
890+
const nestedOrdered = {};
891+
for (const key of nestedIds) {
892+
nestedOrdered[key] = pinnedApplied[key];
854893
}
894+
return nestedOrdered;
895+
}
896+
897+
if (normalizedPrefs.sortMode === 'name_desc') {
898+
const keys = sortKeysWithComparator((a, b) => {
899+
const nameA = String(normalizedFolders[a]?.name ?? a).toLowerCase();
900+
const nameB = String(normalizedFolders[b]?.name ?? b).toLowerCase();
901+
const cmp = nameB.localeCompare(nameA, undefined, { numeric: true, sensitivity: 'base' });
902+
return cmp !== 0 ? cmp : b.localeCompare(a, undefined, { numeric: true, sensitivity: 'base' });
903+
});
904+
const ordered = buildSortedMapFromKeys(keys);
905+
const pinnedApplied = applyPinnedOrder(ordered);
906+
const nestedIds = buildNestedFolderOrderIdsFromMap(pinnedApplied);
907+
const nestedOrdered = {};
908+
for (const key of nestedIds) {
909+
nestedOrdered[key] = pinnedApplied[key];
910+
}
911+
return nestedOrdered;
912+
}
913+
914+
if (
915+
normalizedPrefs.sortMode === 'created_newest'
916+
|| normalizedPrefs.sortMode === 'created_oldest'
917+
|| normalizedPrefs.sortMode === 'updated_newest'
918+
) {
919+
const timestampField = normalizedPrefs.sortMode === 'updated_newest' ? 'updatedAt' : 'createdAt';
920+
const descending = normalizedPrefs.sortMode !== 'created_oldest';
921+
const keys = sortKeysWithComparator((a, b) => {
922+
const timeA = normalizeSortTimestamp(normalizedFolders[a]?.[timestampField]);
923+
const timeB = normalizeSortTimestamp(normalizedFolders[b]?.[timestampField]);
924+
if (timeA === null || timeB === null || timeA === timeB) {
925+
return 0;
926+
}
927+
return descending ? timeB - timeA : timeA - timeB;
928+
});
929+
const ordered = buildSortedMapFromKeys(keys);
855930
const pinnedApplied = applyPinnedOrder(ordered);
856931
const nestedIds = buildNestedFolderOrderIdsFromMap(pinnedApplied);
857932
const nestedOrdered = {};

src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/scripts/folderviewplus.wizard.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ const normalizeSetupAssistantEnvironmentPreset = (value) => (
487487
const normalizeSetupAssistantBehaviorFromValue = (type, value = null) => {
488488
const base = createSetupAssistantBehavior(type);
489489
const incoming = value && typeof value === 'object' ? value : {};
490-
const normalizedSortMode = ['created', 'manual', 'alpha'].includes(String(incoming.sortMode || ''))
490+
const normalizedSortMode = ['created', 'created_newest', 'created_oldest', 'updated_newest', 'manual', 'alpha', 'name_desc'].includes(String(incoming.sortMode || ''))
491491
? String(incoming.sortMode)
492492
: base.sortMode;
493493
const statusWarnRaw = Number(incoming.statusWarnStoppedPercent);
@@ -2103,7 +2103,7 @@ const applySetupAssistantProfileToPrefs = (prefs, profileId) => {
21032103

21042104
const applySetupAssistantBehaviorToPrefs = (prefs, behavior) => {
21052105
const source = behavior && typeof behavior === 'object' ? behavior : {};
2106-
const normalizedSortMode = ['created', 'manual', 'alpha'].includes(String(source.sortMode || ''))
2106+
const normalizedSortMode = ['created', 'created_newest', 'created_oldest', 'updated_newest', 'manual', 'alpha', 'name_desc'].includes(String(source.sortMode || ''))
21072107
? String(source.sortMode)
21082108
: 'created';
21092109
const statusWarnRaw = Number(source.statusWarnStoppedPercent);
@@ -2927,8 +2927,12 @@ const renderSetupAssistantBehaviorTypeCard = (type) => {
29272927
<span>Sort mode</span>
29282928
<select data-fv-setup-behavior-sort="${resolvedType}">
29292929
<option value="created" ${behavior.sortMode === 'created' ? 'selected' : ''}>Created order</option>
2930+
<option value="created_newest" ${behavior.sortMode === 'created_newest' ? 'selected' : ''}>Created newest first</option>
2931+
<option value="created_oldest" ${behavior.sortMode === 'created_oldest' ? 'selected' : ''}>Created oldest first</option>
2932+
<option value="updated_newest" ${behavior.sortMode === 'updated_newest' ? 'selected' : ''}>Last updated newest first</option>
29302933
<option value="manual" ${behavior.sortMode === 'manual' ? 'selected' : ''}>Manual</option>
29312934
<option value="alpha" ${behavior.sortMode === 'alpha' ? 'selected' : ''}>Name (A-Z)</option>
2935+
<option value="name_desc" ${behavior.sortMode === 'name_desc' ? 'selected' : ''}>Name (Z-A)</option>
29322936
</select>
29332937
</label>
29342938
${isExpert ? `

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

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1981,6 +1981,25 @@ function reorderFoldersByIdList(string $type, array $orderedIds): array {
19811981
function reorderFolderMapByPrefs(string $type, array $folders): array {
19821982
$prefs = readTypePrefs($type);
19831983
$sortMode = $prefs['sortMode'] ?? 'created';
1984+
$sortKeysByComparator = function(array $sourceKeys, callable $comparator): array {
1985+
$originalIndex = array_flip($sourceKeys);
1986+
usort($sourceKeys, function($left, $right) use ($comparator, $originalIndex) {
1987+
$compared = $comparator($left, $right);
1988+
if ($compared !== 0) {
1989+
return $compared;
1990+
}
1991+
return ($originalIndex[$left] ?? 0) <=> ($originalIndex[$right] ?? 0);
1992+
});
1993+
return $sourceKeys;
1994+
};
1995+
$normalizeSortTimestamp = function($value): ?int {
1996+
$raw = trim((string)$value);
1997+
if ($raw === '') {
1998+
return null;
1999+
}
2000+
$parsed = strtotime($raw);
2001+
return $parsed === false ? null : (int)$parsed;
2002+
};
19842003
$applyPinnedOrder = function(array $ordered) use ($prefs): array {
19852004
$pinnedIds = normalizeStringIdList($prefs['pinnedFolderIds'] ?? []);
19862005
if (count($pinnedIds) === 0) {
@@ -2001,7 +2020,7 @@ function reorderFolderMapByPrefs(string $type, array $folders): array {
20012020

20022021
if ($sortMode === 'alpha') {
20032022
$keys = array_keys($folders);
2004-
usort($keys, function($a, $b) use ($folders) {
2023+
$keys = $sortKeysByComparator($keys, function($a, $b) use ($folders) {
20052024
$nameA = strtolower(trim((string)($folders[$a]['name'] ?? $a)));
20062025
$nameB = strtolower(trim((string)($folders[$b]['name'] ?? $b)));
20072026
$cmp = strnatcmp($nameA, $nameB);
@@ -2014,6 +2033,40 @@ function reorderFolderMapByPrefs(string $type, array $folders): array {
20142033
return $applyPinnedOrder($ordered);
20152034
}
20162035

2036+
if ($sortMode === 'name_desc') {
2037+
$keys = array_keys($folders);
2038+
$keys = $sortKeysByComparator($keys, function($a, $b) use ($folders) {
2039+
$nameA = strtolower(trim((string)($folders[$a]['name'] ?? $a)));
2040+
$nameB = strtolower(trim((string)($folders[$b]['name'] ?? $b)));
2041+
$cmp = strnatcmp($nameB, $nameA);
2042+
return $cmp !== 0 ? $cmp : strnatcmp((string)$b, (string)$a);
2043+
});
2044+
$ordered = [];
2045+
foreach ($keys as $key) {
2046+
$ordered[$key] = $folders[$key];
2047+
}
2048+
return $applyPinnedOrder($ordered);
2049+
}
2050+
2051+
if (in_array($sortMode, ['created_newest', 'created_oldest', 'updated_newest'], true)) {
2052+
$timestampField = $sortMode === 'updated_newest' ? 'updatedAt' : 'createdAt';
2053+
$descending = $sortMode !== 'created_oldest';
2054+
$keys = array_keys($folders);
2055+
$keys = $sortKeysByComparator($keys, function($a, $b) use ($folders, $normalizeSortTimestamp, $timestampField, $descending) {
2056+
$timeA = $normalizeSortTimestamp($folders[$a][$timestampField] ?? '');
2057+
$timeB = $normalizeSortTimestamp($folders[$b][$timestampField] ?? '');
2058+
if ($timeA === null || $timeB === null || $timeA === $timeB) {
2059+
return 0;
2060+
}
2061+
return $descending ? ($timeB <=> $timeA) : ($timeA <=> $timeB);
2062+
});
2063+
$ordered = [];
2064+
foreach ($keys as $key) {
2065+
$ordered[$key] = $folders[$key];
2066+
}
2067+
return $applyPinnedOrder($ordered);
2068+
}
2069+
20172070
if ($sortMode === 'manual') {
20182071
$ordered = [];
20192072
$manualOrder = $prefs['manualOrder'] ?? [];

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ function normalizeThemeCompatibilityMode($value): string {
199199
function normalizeTypePrefs(array $prefs): array {
200200
$normalized = defaultTypePrefs();
201201
$sortMode = $prefs['sortMode'] ?? $normalized['sortMode'];
202-
if (!in_array($sortMode, ['created', 'manual', 'alpha'], true)) {
202+
if (!in_array($sortMode, ['created', 'created_newest', 'created_oldest', 'updated_newest', 'manual', 'alpha', 'name_desc'], true)) {
203203
$sortMode = 'created';
204204
}
205205
$normalized['sortMode'] = $sortMode;

0 commit comments

Comments
 (0)