Skip to content

Commit 09250ec

Browse files
author
FolderView Plus Test
committed
Deep-link folder editor rules workspace
1 parent e05e7e8 commit 09250ec

8 files changed

Lines changed: 79 additions & 8 deletions

File tree

archive/folderview.plus-2026.04.15.19.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+
dd432c39770d796457b0ed7882dbdde852665034312a39b8a593730237de1057 folderview.plus-2026.04.17.02.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.04.17.01">
10-
<!ENTITY md5 "610fc9335e10973e0401c1bf032b0b02">
9+
<!ENTITY version "2026.04.17.02">
10+
<!ENTITY md5 "77edaa4af1469bc0165406b021449844">
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.17.02
17+
- Fix: Folder editor Rules tab now opens Settings directly into Advanced > Rules > Auto-assignment instead of restoring the last basic tab.
18+
- UX: Settings bootstrap now honors explicit launch overrides for advanced tab, target section, and Docker or VM rules workspace source.
19+
20+
1621
###2026.04.17.01
1722
- UX: Folder editor flows, previews, and bootstrap behavior.
1823
- Refactor: Shared runtime contracts, request plumbing, and cross-page foundations.

src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/scripts/folder.editor.rules.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
const rulePatternPlaceholders = rawRulesConfig.patternPlaceholders && typeof rawRulesConfig.patternPlaceholders === 'object'
6363
? rawRulesConfig.patternPlaceholders
6464
: defaultRulesConfig.patternPlaceholders;
65+
const fullRulesWorkspaceHref = `/Settings/FolderViewPlus?fvMode=advanced&fvAdvancedTab=rules&fvSection=auto-assignment&fvRulesType=${encodeURIComponent(type)}`;
6566

6667
let folderEditorPrefs = normalizePrefs({});
6768
let folderEditorPrefsLoaded = false;
@@ -347,7 +348,7 @@
347348
<strong>Rules targeting this folder</strong>
348349
<span>Create regex-based plugin rules directly from the folder editor without leaving this page.</span>
349350
</div>
350-
<a class="fv-folder-auto-rules-link" href="/Settings/FolderViewPlus">Open full Rules workspace</a>
351+
<a class="fv-folder-auto-rules-link" href="${escapeHtml(fullRulesWorkspaceHref)}">Open full Rules workspace</a>
351352
</div>
352353
${buildFolderAutoRulesPanelStatusHtml()}
353354
${bodyHtml}

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

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,6 +1150,33 @@ const normalizeAdvancedGroup = (value) => (
11501150
: 'operations'
11511151
);
11521152

1153+
const readSettingsLaunchOverrides = () => {
1154+
if (typeof URLSearchParams === 'undefined' || !window?.location) {
1155+
return null;
1156+
}
1157+
const params = new URLSearchParams(window.location.search || '');
1158+
const modeRaw = String(params.get('fvMode') || '').trim().toLowerCase();
1159+
const advancedTabRaw = String(params.get('fvAdvancedTab') || '').trim().toLowerCase();
1160+
const sectionKey = String(params.get('fvSection') || '').trim().toLowerCase();
1161+
const rulesTypeRaw = String(params.get('fvRulesType') || '').trim().toLowerCase();
1162+
const overrides = {};
1163+
if (modeRaw === 'advanced' || modeRaw === 'basic') {
1164+
overrides.mode = modeRaw;
1165+
}
1166+
if (ADVANCED_GROUPS.includes(advancedTabRaw)) {
1167+
overrides.advancedTab = advancedTabRaw;
1168+
}
1169+
if (sectionKey) {
1170+
overrides.sectionKey = sectionKey;
1171+
}
1172+
if (rulesTypeRaw === 'docker' || rulesTypeRaw === 'vm') {
1173+
overrides.rulesType = rulesTypeRaw;
1174+
}
1175+
return Object.keys(overrides).length > 0 ? overrides : null;
1176+
};
1177+
1178+
const settingsLaunchOverrides = readSettingsLaunchOverrides();
1179+
11531180
const normalizeAdvancedSearchMap = (value) => {
11541181
const source = value && typeof value === 'object' ? value : {};
11551182
const next = {};
@@ -1335,6 +1362,28 @@ const setAdvancedTab = (tab, persist = true) => {
13351362
}
13361363
};
13371364

1365+
const applySettingsLaunchOverrides = ({ persist = false } = {}) => {
1366+
if (!settingsLaunchOverrides) {
1367+
return false;
1368+
}
1369+
if (settingsLaunchOverrides.mode === 'advanced' || settingsLaunchOverrides.mode === 'basic') {
1370+
settingsUiState.mode = settingsLaunchOverrides.mode;
1371+
}
1372+
if (settingsLaunchOverrides.advancedTab) {
1373+
setAdvancedTab(settingsLaunchOverrides.advancedTab, persist);
1374+
}
1375+
if (settingsLaunchOverrides.sectionKey) {
1376+
settingsUiState.activeSectionKey = settingsLaunchOverrides.sectionKey;
1377+
}
1378+
if (settingsLaunchOverrides.rulesType) {
1379+
activeRulesWorkspaceType = normalizeRulesWorkspaceType(settingsLaunchOverrides.rulesType);
1380+
if (persist) {
1381+
writeSettingsStorage(RULES_WORKSPACE_STORAGE_KEY, activeRulesWorkspaceType, { delayMs: 60, idle: true });
1382+
}
1383+
}
1384+
return true;
1385+
};
1386+
13381387
const captureSettingsBaseline = () => {
13391388
if (dirtyTracker && typeof dirtyTracker.captureBaseline === 'function') {
13401389
dirtyTracker.captureBaseline(
@@ -9240,6 +9289,7 @@ settingsActionSupportModule.registerWindowActions(window, {
92409289
settingsUiState.knownAdvancedSections = new Set();
92419290
}
92429291
restoreTableUiState();
9292+
applySettingsLaunchOverrides({ persist: false });
92439293
});
92449294
await withFatalBannerPhase({
92459295
phase: 'bootstrap-ui',
@@ -9298,14 +9348,19 @@ settingsActionSupportModule.registerWindowActions(window, {
92989348
const storedMode = String(localStorage.getItem(UI_MODE_STORAGE_KEY) || '').trim();
92999349
const hasLocalModePreference = storedMode === 'advanced' || storedMode === 'basic';
93009350
const serverMode = getServerSettingsMode();
9301-
if (!hasLocalModePreference && serverMode) {
9351+
if (!hasLocalModePreference && serverMode && !settingsLaunchOverrides?.mode) {
93029352
settingsUiState.mode = serverMode;
93039353
}
93049354
refreshSettingsUx();
93059355
captureSettingsBaseline();
93069356
if (settingsUiState.mode) {
93079357
setSettingsMode(settingsUiState.mode);
93089358
}
9359+
if (settingsLaunchOverrides?.sectionKey) {
9360+
window.requestAnimationFrame(() => {
9361+
scrollToSectionKey(settingsLaunchOverrides.sectionKey);
9362+
});
9363+
}
93099364
if (hasLocalModePreference && serverMode && serverMode !== settingsUiState.mode) {
93109365
void persistSetupPrefsToServer({ mode: settingsUiState.mode });
93119366
}
@@ -9355,4 +9410,3 @@ settingsActionSupportModule.registerWindowActions(window, {
93559410
})();
93569411

93579412

9358-

tests/settings-bindings.test.mjs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,17 @@ test('settings page exposes theme fallback controls and runtime self-heal action
158158
assert.match(libPrefsPhp, /function normalizeRuntimePageViewMode\(\$value\): string \{[\s\S]*\['folderview', 'host', 'command', 'tree-explorer'\]/);
159159
});
160160

161+
test('settings runtime honors explicit launch overrides for advanced rules workspace deep links', () => {
162+
assert.match(script, /const readSettingsLaunchOverrides = \(\) => \{/);
163+
assert.match(script, /const params = new URLSearchParams\(window\.location\.search \|\| ''\);/);
164+
assert.match(script, /const settingsLaunchOverrides = readSettingsLaunchOverrides\(\);/);
165+
assert.match(script, /const applySettingsLaunchOverrides = \(\{ persist = false \} = \{\}\) => \{/);
166+
assert.match(script, /activeRulesWorkspaceType = normalizeRulesWorkspaceType\(settingsLaunchOverrides\.rulesType\);/);
167+
assert.match(script, /applySettingsLaunchOverrides\(\{ persist: false \}\);/);
168+
assert.match(script, /if \(!hasLocalModePreference && serverMode && !settingsLaunchOverrides\?\.mode\) \{/);
169+
assert.match(script, /window\.requestAnimationFrame\(\(\) => \{\s*scrollToSectionKey\(settingsLaunchOverrides\.sectionKey\);\s*\}\);/);
170+
});
171+
161172
test('backup endpoint supports scheduler and rollback actions', () => {
162173
assert.match(backupPhp, /action\s*===\s*'run_schedule'/);
163174
assert.match(backupPhp, /runScheduledBackups/);
@@ -444,7 +455,7 @@ test('settings mode switches persist the user basic or advanced view choice', ()
444455
assert.match(script, /if \(persistServer === true && previousMode !== settingsUiState\.mode\) \{\s*void persistSetupPrefsToServer\(\{ mode: settingsUiState\.mode \}\);\s*\}/);
445456
assert.match(script, /const storedMode = String\(localStorage\.getItem\(UI_MODE_STORAGE_KEY\) \|\| ''\)\.trim\(\);/);
446457
assert.match(script, /const hasLocalModePreference = storedMode === 'advanced' \|\| storedMode === 'basic';/);
447-
assert.match(script, /if \(!hasLocalModePreference && serverMode\) \{\s*settingsUiState\.mode = serverMode;\s*\}/);
458+
assert.match(script, /if \(!hasLocalModePreference && serverMode && !settingsLaunchOverrides\?\.mode\) \{\s*settingsUiState\.mode = serverMode;\s*\}/);
448459
assert.match(script, /if \(hasLocalModePreference && serverMode && serverMode !== settingsUiState\.mode\) \{\s*void persistSetupPrefsToServer\(\{ mode: settingsUiState\.mode \}\);\s*\}/);
449460
assert.match(script, /setSettingsMode\(mode, \{ persistServer: true \}\);/);
450461
assert.match(script, /setSettingsMode\('basic', \{ persistServer: true \}\);/);

tests/ui-smoke-layout.test.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -594,12 +594,12 @@ test('folder editor exposes folder-scoped advanced auto-rules for saved folders'
594594
assert.match(folderRulesJs, /panel\.className = 'basic fv-modern-field-row is-rules-row fv-folder-auto-rules-panel';/);
595595
assert.match(folderRulesJs, /Save this folder first to create advanced rules\./);
596596
assert.match(folderRulesJs, /Open full Rules workspace/);
597+
assert.match(folderRulesJs, /const fullRulesWorkspaceHref = `\/Settings\/FolderViewPlus\?fvMode=advanced&fvAdvancedTab=rules&fvSection=auto-assignment&fvRulesType=\$\{encodeURIComponent\(type\)\}`;/);
597598
assert.match(folderRulesJs, /Create regex-based plugin rules directly from the folder editor without leaving this page\./);
598599
assert.match(folderRulesJs, /<dt>Advanced auto-rules:<\/dt>/);
599600
assert.match(folderRulesJs, /<blockquote class="inline_help">/);
600601
assert.match(folderRulesJs, /\/plugins\/folderview\.plus\/server\/prefs\.php\?type=\$\{encodeURIComponent\(type\)\}/);
601602
assert.match(folderRulesJs, /requestClient\.postJson\('\/plugins\/folderview\.plus\/server\/prefs\.php'/);
602-
assert.match(folderRulesJs, /href="\/Settings\/FolderViewPlus"/);
603603
assert.match(folderPage, /'\/plugins\/folderview\.plus\/scripts\/folder\.editor\.rules\.js'/);
604604
assert.match(folderCss, /#fvFolderAutoRulesPanel\.fv-folder-auto-rules-panel/);
605605
assert.match(folderCss, /#fvFolderAutoRulesPanel\.fv-folder-auto-rules-panel > dl > dd/);

0 commit comments

Comments
 (0)