(effectiveRoot, selectedFolder)
| 831 | } |
| 832 | |
| 833 | async function chooseInitialFolder(effectiveRoot, selectedFolder) { |
| 834 | // 1) explicit selection |
| 835 | if (selectedFolder && await canViewFolderCached(selectedFolder)) return selectedFolder; |
| 836 | |
| 837 | // 2) sticky lastOpenedFolder |
| 838 | const last = getLastOpenedFolder(); |
| 839 | if (last && await canViewFolderCached(last)) return last; |
| 840 | |
| 841 | // 2b) Ground truth from folder list API (matches getFileList 403 behavior) |
| 842 | try { |
| 843 | const sourceId = getActiveSourceId(); |
| 844 | const sourceParam = sourceId ? `&sourceId=${encodeURIComponent(sourceId)}` : ''; |
| 845 | const res = await fetch(`/api/folder/getFolderList.php?counts=0${sourceParam}`, { credentials: 'include' }); |
| 846 | const data = await res.json().catch(() => []); |
| 847 | const names = Array.isArray(data) |
| 848 | ? Array.from(new Set(data.map(row => { |
| 849 | const f = (row && (row.folder || row)) || ''; |
| 850 | const trimmed = String(f).trim().replace(/^\/+|\/+$/g, ''); |
| 851 | return trimmed === '' ? 'root' : trimmed; |
| 852 | }))) |
| 853 | : []; |
| 854 | |
| 855 | if (names.length) { |
| 856 | const preferred = (last || '').trim(); |
| 857 | if (preferred && names.includes(preferred)) return preferred; |
| 858 | // pick shallowest, then alpha |
| 859 | names.sort((a, b) => { |
| 860 | const depth = (p) => (p === 'root') ? 0 : p.split('/').filter(Boolean).length; |
| 861 | const d = depth(a) - depth(b); |
| 862 | return d !== 0 ? d : a.localeCompare(b); |
| 863 | }); |
| 864 | if (names[0]) return names[0]; |
| 865 | } |
| 866 | } catch (e) { /* best effort */ } |
| 867 | |
| 868 | // 3) NEW: if root itself is viewable, prefer (Root) |
| 869 | if (await canViewFolderCached(effectiveRoot)) return effectiveRoot; |
| 870 | |
| 871 | // 4) first TOP-LEVEL child that’s directly viewable |
| 872 | try { |
| 873 | const { items = [] } = await fetchChildrenOnce(effectiveRoot); |
| 874 | const topNames = items.map(it => (typeof it === 'string' ? it : it?.name)).filter(Boolean); |
| 875 | |
| 876 | for (const name of topNames) { |
| 877 | const child = effectiveRoot === 'root' ? name : `${effectiveRoot}/${name}`; |
| 878 | if (await canViewFolderCached(child)) return child; |
| 879 | } |
| 880 | |
| 881 | // 5) first TOP-LEVEL child with any viewable descendant |
| 882 | for (const name of topNames) { |
| 883 | const child = effectiveRoot === 'root' ? name : `${effectiveRoot}/${name}`; |
| 884 | if (await hasUnlockedDescendant(child, 2)) return child; |
| 885 | } |
| 886 | } catch (e) {} |
| 887 | |
| 888 | // 6) fallback: BFS |
| 889 | return await findFirstAccessibleFolder(effectiveRoot); |
| 890 | } |
no test coverage detected