@@ -202,12 +202,18 @@ function buildFullGrantsForAllFolders(folders) {
202202
203203let __allFoldersCache = new Map ( ) ;
204204
205- async function getAllFolders ( force = false , sourceId = "" ) {
206- const key = sourceId || getFolderAccessSourceId ( ) || '' ;
207- if ( ! force && __allFoldersCache . has ( key ) ) return __allFoldersCache . get ( key ) . slice ( ) ;
208-
209- const url = '/api/folder/getFolderList.php?counts=0&ts=' + Date . now ( )
210- + ( key ? `&sourceId=${ encodeURIComponent ( key ) } ` : '' ) ;
205+ async function getAllFolders ( force = false , sourceId = "" , parent = "root" ) {
206+ // Load a single level (shallow: folder=<parent>&counts=0) instead of the full recursive tree.
207+ // The full list walks every folder and renders one row per folder, which freezes the editor on
208+ // large libraries. The backend already supports shallow listing (getFolderListLocalShallow); it
209+ // just was never requested here. parent='root' = top level; drill into a subfolder by passing parent=<path>.
210+ const sid = sourceId || getFolderAccessSourceId ( ) || '' ;
211+ const p = parent || 'root' ;
212+ const cacheKey = sid + '::' + p ;
213+ if ( ! force && __allFoldersCache . has ( cacheKey ) ) return __allFoldersCache . get ( cacheKey ) . slice ( ) ;
214+
215+ const url = '/api/folder/getFolderList.php?counts=0&folder=' + encodeURIComponent ( p ) + '&ts=' + Date . now ( )
216+ + ( sid ? `&sourceId=${ encodeURIComponent ( sid ) } ` : '' ) ;
211217 const res = await fetch ( url , {
212218 credentials : 'include' ,
213219 cache : 'no-store' ,
@@ -223,7 +229,7 @@ async function getAllFolders(force = false, sourceId = "") {
223229 . filter ( f => f && ! hidden . has ( f . toLowerCase ( ) ) )
224230 . sort ( ( a , b ) => ( a === 'root' ? - 1 : b === 'root' ? 1 : a . localeCompare ( b ) ) ) ;
225231
226- __allFoldersCache . set ( key , cleaned ) ;
232+ __allFoldersCache . set ( cacheKey , cleaned ) ;
227233 return cleaned . slice ( ) ;
228234}
229235
@@ -239,7 +245,7 @@ async function getUserGrants(username, sourceId = "") {
239245 return ( grants && typeof grants === 'object' && ! Array . isArray ( grants ) ) ? grants : { } ;
240246}
241247
242- function renderFolderGrantsUI ( principal , container , folders , grants ) {
248+ function renderFolderGrantsUI ( principal , container , folders , grants , parent = 'root' , sourceId = '' ) {
243249 if ( ! Array . isArray ( folders ) || ! container ) return ;
244250
245251 const grantsMap = ( grants && typeof grants === 'object' && ! Array . isArray ( grants ) ) ? grants : { } ;
@@ -301,6 +307,45 @@ function renderFolderGrantsUI(principal, container, folders, grants) {
301307
302308 container . innerHTML = '' ;
303309 container . appendChild ( toolbar ) ;
310+
311+ // Drill-in into subfolders: load one level at a time (shallow). getAllFolders takes a parent arg.
312+ const __sid = sourceId || ( typeof getFolderAccessSourceId === 'function' ? getFolderAccessSourceId ( ) : '' ) || '' ;
313+ const __parent = parent || 'root' ;
314+ async function navigateFolderAccess ( newParent ) {
315+ // Collect in-progress edits into the fallback map before navigating so they survive the
316+ // re-render (save is batched via collectGrantsFrom).
317+ const acc = collectGrantsFrom ( container , container . __grantsFallback || { } ) ;
318+ let f = [ ] ;
319+ try { f = await getAllFolders ( true , __sid , newParent || 'root' ) ; } catch ( e ) { f = [ ] ; }
320+ renderFolderGrantsUI ( principal , container , f , acc , newParent || 'root' , __sid ) ;
321+ }
322+ const crumb = document . createElement ( 'div' ) ;
323+ crumb . className = 'folder-access-breadcrumb' ;
324+ crumb . style . cssText = 'display:flex;flex-wrap:wrap;gap:4px;align-items:center;margin:4px 0 8px;font-size:13px;' ;
325+ ( ( ) => {
326+ const mkLink = ( label , target ) => {
327+ const a = document . createElement ( 'a' ) ;
328+ a . href = '#' ; a . textContent = label ;
329+ a . style . cssText = 'cursor:pointer;text-decoration:underline;' ;
330+ a . addEventListener ( 'click' , ( e ) => { e . preventDefault ( ) ; navigateFolderAccess ( target ) ; } ) ;
331+ return a ;
332+ } ;
333+ crumb . appendChild ( mkLink ( tf ( 'root_folder' , 'root' ) + ' /' , 'root' ) ) ;
334+ const parts = ( __parent === 'root' ) ? [ ] : __parent . split ( '/' ) . filter ( Boolean ) ;
335+ let accPath = '' ;
336+ parts . forEach ( ( seg , i ) => {
337+ accPath = accPath ? ( accPath + '/' + seg ) : seg ;
338+ const sep = document . createElement ( 'span' ) ; sep . textContent = '›' ; sep . style . opacity = '0.5' ;
339+ crumb . appendChild ( sep ) ;
340+ if ( i === parts . length - 1 ) {
341+ const cur = document . createElement ( 'strong' ) ; cur . textContent = seg ;
342+ crumb . appendChild ( cur ) ;
343+ } else {
344+ crumb . appendChild ( mkLink ( seg , accPath ) ) ;
345+ }
346+ } ) ;
347+ } ) ( ) ;
348+ container . appendChild ( crumb ) ;
304349 container . appendChild ( list ) ;
305350
306351 const rowHtml = ( folder , idx ) => {
@@ -330,7 +375,7 @@ function renderFolderGrantsUI(principal, container, folders, grants) {
330375 g . shareFolder = g . shareFolder !== false ;
331376 }
332377
333- const name = folder === "root" ? `${ t ( "root_folder" ) } /` : folder ;
378+ const name = folder === "root" ? `${ t ( "root_folder" ) } /` : folder . split ( '/' ) . pop ( ) ;
334379 const shareFolderDisabled = isAdmin ? true : undefined ;
335380
336381 const toggle = ( cap , label , checked , disabled , title = "" ) => `
@@ -356,6 +401,7 @@ function renderFolderGrantsUI(principal, container, folders, grants) {
356401 <div class="folder-badge">
357402 <i class="material-icons" style="font-size:18px;">folder</i>
358403 <span class="folder-name-text">${ name } </span>
404+ ${ ( folder !== 'root' && folder !== __parent ) ? `<button type="button" class="fa-drill-btn" data-drill="${ folder } " title="${ tf ( 'open_subfolders' , 'Open subfolders' ) } " style="margin-left:6px;border:none;background:transparent;cursor:pointer;padding:0;opacity:0.7;display:inline-flex;align-items:center;"><i class="material-icons" style="font-size:18px;">chevron_right</i></button>` : '' }
359405 <span class="inherited-tag" style="display:none;"></span>
360406 <span class="inherit-flag-note pill-note" style="display:none;"></span>
361407 <span class="group-flag-note pill-note" style="display:none;"></span>
@@ -546,6 +592,13 @@ function renderFolderGrantsUI(principal, container, folders, grants) {
546592 }
547593
548594 function wireRow ( row ) {
595+ const drillBtn = row . querySelector ( '.fa-drill-btn' ) ;
596+ if ( drillBtn ) {
597+ drillBtn . addEventListener ( 'click' , ( e ) => {
598+ e . preventDefault ( ) ; e . stopPropagation ( ) ;
599+ navigateFolderAccess ( drillBtn . dataset . drill ) ;
600+ } ) ;
601+ }
549602 const isAdminRow = row . dataset . admin === '1' ;
550603 const cbView = row . querySelector ( 'input[data-cap="view"]' ) ;
551604 const cbViewOwn = row . querySelector ( 'input[data-cap="viewOwn"]' ) ;
0 commit comments