@@ -42,11 +42,6 @@ const BREADCRUMB_PREFERRED_CLASS = 'jp-BreadCrumbs-preferred';
4242 */
4343const BREADCRUMB_ITEM_CLASS = 'jp-BreadCrumbs-item' ;
4444
45- /**
46- * Bread crumb paths.
47- */
48- const BREAD_CRUMB_PATHS = [ '/' , '../../' , '../' , '' ] ;
49-
5045/**
5146 * The mime type for a contents drag object.
5247 */
@@ -72,9 +67,14 @@ export class BreadCrumbs extends Widget {
7267 this . _trans = this . translator . load ( 'jupyterlab' ) ;
7368 this . _model = options . model ;
7469 this . _fullPath = options . fullPath || false ;
70+ this . _minimumLeftItems = options . minimumLeftItems ?? 0 ;
71+ this . _minimumRightItems = options . minimumRightItems ?? 2 ;
7572 this . addClass ( BREADCRUMB_CLASS ) ;
7673 this . _crumbs = Private . createCrumbs ( ) ;
77- this . _crumbSeps = Private . createCrumbSeparators ( ) ;
74+ this . _crumbSeps = Private . createCrumbSeparators (
75+ this . _minimumLeftItems ,
76+ this . _minimumRightItems
77+ ) ;
7878 const hasPreferred = PageConfig . getOption ( 'preferredPath' ) ;
7979 this . _hasPreferred = hasPreferred && hasPreferred !== '/' ? true : false ;
8080 if ( this . _hasPreferred ) {
@@ -127,6 +127,28 @@ export class BreadCrumbs extends Widget {
127127 this . _fullPath = value ;
128128 }
129129
130+ /**
131+ * Number of items to show on left of ellipsis
132+ */
133+ get minimumLeftItems ( ) : number {
134+ return this . _minimumLeftItems ;
135+ }
136+
137+ set minimumLeftItems ( value : number ) {
138+ this . _minimumLeftItems = value ;
139+ }
140+
141+ /**
142+ * Number of items to show on right of ellipsis
143+ */
144+ get minimumRightItems ( ) : number {
145+ return this . _minimumRightItems ;
146+ }
147+
148+ set minimumRightItems ( value : number ) {
149+ this . _minimumRightItems = value ;
150+ }
151+
130152 /**
131153 * A message handler invoked on an `'after-attach'` message.
132154 */
@@ -164,7 +186,9 @@ export class BreadCrumbs extends Widget {
164186 const state = {
165187 path : localPath ,
166188 hasPreferred : this . _hasPreferred ,
167- fullPath : this . _fullPath
189+ fullPath : this . _fullPath ,
190+ minimumLeftItems : this . _minimumLeftItems ,
191+ minimumRightItems : this . _minimumRightItems
168192 } ;
169193 if ( this . _previousState && JSONExt . deepEqual ( state , this . _previousState ) ) {
170194 return ;
@@ -203,17 +227,11 @@ export class BreadCrumbs extends Widget {
203227 node . classList . contains ( BREADCRUMB_ITEM_CLASS ) ||
204228 node . classList . contains ( BREADCRUMB_ROOT_CLASS )
205229 ) {
206- let index = ArrayExt . findFirstIndex (
207- this . _crumbs ,
208- value => value === node
209- ) ;
210- let destination = BREAD_CRUMB_PATHS [ index ] ;
211- if (
212- this . _fullPath &&
213- index < 0 &&
214- ! node . classList . contains ( BREADCRUMB_ROOT_CLASS )
215- ) {
216- destination = node . title ;
230+ let destination : string ;
231+ if ( node . classList . contains ( BREADCRUMB_ROOT_CLASS ) ) {
232+ destination = '/' ;
233+ } else {
234+ destination = `/${ node . title } ` ;
217235 }
218236 this . _model
219237 . cd ( destination )
@@ -303,17 +321,22 @@ export class BreadCrumbs extends Widget {
303321 target = target . parentElement ;
304322 }
305323
306- // Get the path based on the target node.
307- const index = ArrayExt . findFirstIndex (
308- this . _crumbs ,
309- node => node === target
310- ) ;
311- if ( index === - 1 ) {
324+ let destinationPath : string | null = null ;
325+ if ( target . classList . contains ( BREADCRUMB_ROOT_CLASS ) ) {
326+ destinationPath = '/' ;
327+ } else if ( target . classList . contains ( BREADCRUMB_PREFERRED_CLASS ) ) {
328+ const preferredPath = PageConfig . getOption ( 'preferredPath' ) ;
329+ destinationPath = preferredPath ? '/' + preferredPath : '/' ;
330+ } else if ( target . title ) {
331+ destinationPath = target . title ;
332+ }
333+
334+ if ( ! destinationPath ) {
312335 return ;
313336 }
314337
315338 const model = this . _model ;
316- const path = PathExt . resolve ( model . path , BREAD_CRUMB_PATHS [ index ] ) ;
339+ const resolvedPath = PathExt . resolve ( model . path , destinationPath ) ;
317340 const manager = model . manager ;
318341
319342 // Move all of the items.
@@ -322,7 +345,7 @@ export class BreadCrumbs extends Widget {
322345 for ( const oldPath of oldPaths ) {
323346 const localOldPath = manager . services . contents . localPath ( oldPath ) ;
324347 const name = PathExt . basename ( localOldPath ) ;
325- const newPath = PathExt . join ( path , name ) ;
348+ const newPath = PathExt . join ( resolvedPath , name ) ;
326349 promises . push ( renameFile ( manager , oldPath , newPath ) ) ;
327350 }
328351 void Promise . all ( promises ) . catch ( err => {
@@ -338,6 +361,8 @@ export class BreadCrumbs extends Widget {
338361 private _crumbSeps : ReadonlyArray < HTMLElement > ;
339362 private _fullPath : boolean ;
340363 private _previousState : Private . ICrumbsState | null = null ;
364+ private _minimumLeftItems : number ;
365+ private _minimumRightItems : number ;
341366}
342367
343368/**
@@ -362,6 +387,16 @@ export namespace BreadCrumbs {
362387 * Show the full file browser path in breadcrumbs
363388 */
364389 fullPath ?: boolean ;
390+
391+ /**
392+ * Number of items to show on left of ellipsis
393+ */
394+ minimumLeftItems ?: number ;
395+
396+ /**
397+ * Number of items to show on right of ellipsis
398+ */
399+ minimumRightItems ?: number ;
365400 }
366401}
367402
@@ -384,10 +419,12 @@ namespace Private {
384419 * Breadcrumbs state.
385420 */
386421 export interface ICrumbsState {
387- [ key : string ] : string | boolean ;
422+ [ key : string ] : string | boolean | number ;
388423 path : string ;
389424 hasPreferred : boolean ;
390425 fullPath : boolean ;
426+ minimumLeftItems : number ;
427+ minimumRightItems : number ;
391428 }
392429
393430 /**
@@ -413,43 +450,77 @@ namespace Private {
413450 node . appendChild ( separators [ 0 ] ) ;
414451 }
415452
416- const parts = state . path . split ( '/' ) ;
417- if ( ! state . fullPath && parts . length > 2 ) {
418- node . appendChild ( breadcrumbs [ Crumb . Ellipsis ] ) ;
419- const grandParent = parts . slice ( 0 , parts . length - 2 ) . join ( '/' ) ;
420- breadcrumbs [ Crumb . Ellipsis ] . title = grandParent ;
421- node . appendChild ( separators [ 1 ] ) ;
422- }
453+ const parts = state . path . split ( '/' ) . filter ( part => part !== '' ) ;
454+ if ( ! state . fullPath && parts . length > 0 ) {
455+ const minimumLeftItems = state . minimumLeftItems ;
456+ const minimumRightItems = state . minimumRightItems ;
457+
458+ // Check if we need ellipsis
459+ if ( parts . length > minimumLeftItems + minimumRightItems ) {
460+ let separatorIndex = 1 ;
423461
424- if ( state . path ) {
425- if ( ! state . fullPath ) {
426- if ( parts . length >= 2 ) {
427- breadcrumbs [ Crumb . Parent ] . textContent = parts [ parts . length - 2 ] ;
428- node . appendChild ( breadcrumbs [ Crumb . Parent ] ) ;
429- const parent = parts . slice ( 0 , parts . length - 1 ) . join ( '/' ) ;
430- breadcrumbs [ Crumb . Parent ] . title = parent ;
431- node . appendChild ( separators [ 2 ] ) ;
462+ // Add left items
463+ for ( let i = 0 ; i < minimumLeftItems ; i ++ ) {
464+ const elemPath = parts . slice ( 0 , i + 1 ) . join ( '/' ) ;
465+ const elem = createBreadcrumbElement ( parts [ i ] , elemPath ) ;
466+ node . appendChild ( elem ) ;
467+ node . appendChild ( separators [ separatorIndex ++ ] ) ;
468+ }
469+
470+ // Add ellipsis
471+ node . appendChild ( breadcrumbs [ Crumb . Ellipsis ] ) ;
472+ const hiddenStartIndex = minimumLeftItems ;
473+ const hiddenEndIndex = parts . length - minimumRightItems ;
474+ const hiddenParts = parts . slice ( hiddenStartIndex , hiddenEndIndex ) ;
475+ const hiddenPath =
476+ hiddenParts . length > 0
477+ ? parts . slice ( 0 , hiddenEndIndex ) . join ( '/' )
478+ : parts . slice ( 0 , minimumLeftItems ) . join ( '/' ) ;
479+ breadcrumbs [ Crumb . Ellipsis ] . title = hiddenPath ;
480+ node . appendChild ( separators [ separatorIndex ++ ] ) ;
481+
482+ // Add right items
483+ const rightStartIndex = parts . length - minimumRightItems ;
484+ for ( let i = rightStartIndex ; i < parts . length ; i ++ ) {
485+ const elemPath = parts . slice ( 0 , i + 1 ) . join ( '/' ) ;
486+ const elem = createBreadcrumbElement ( parts [ i ] , elemPath ) ;
487+ node . appendChild ( elem ) ;
488+ node . appendChild ( separators [ separatorIndex ++ ] ) ;
432489 }
433- breadcrumbs [ Crumb . Current ] . textContent = parts [ parts . length - 1 ] ;
434- node . appendChild ( breadcrumbs [ Crumb . Current ] ) ;
435- breadcrumbs [ Crumb . Current ] . title = state . path ;
436- node . appendChild ( separators [ 3 ] ) ;
437490 } else {
438491 for ( let i = 0 ; i < parts . length ; i ++ ) {
439- const elem = document . createElement ( 'span' ) ;
440- elem . className = BREADCRUMB_ITEM_CLASS ;
441- elem . textContent = parts [ i ] ;
442- const elemPath = `/${ parts . slice ( 0 , i + 1 ) . join ( '/' ) } ` ;
443- elem . title = elemPath ;
492+ const elemPath = parts . slice ( 0 , i + 1 ) . join ( '/' ) ;
493+ const elem = createBreadcrumbElement ( parts [ i ] , elemPath ) ;
444494 node . appendChild ( elem ) ;
445- const separator = document . createElement ( 'span' ) ;
446- separator . textContent = '/' ;
447- node . appendChild ( separator ) ;
495+ node . appendChild ( separators [ i + 1 ] ) ;
448496 }
449497 }
498+ } else if ( state . fullPath && parts . length > 0 ) {
499+ for ( let i = 0 ; i < parts . length ; i ++ ) {
500+ const elemPath = parts . slice ( 0 , i + 1 ) . join ( '/' ) ;
501+ const elem = createBreadcrumbElement ( parts [ i ] , elemPath ) ;
502+ node . appendChild ( elem ) ;
503+ const separator = document . createElement ( 'span' ) ;
504+ separator . textContent = '/' ;
505+ node . appendChild ( separator ) ;
506+ }
450507 }
451508 }
452509
510+ /**
511+ * Create a breadcrumb element for a path part.
512+ */
513+ function createBreadcrumbElement (
514+ pathPart : string ,
515+ fullPath : string
516+ ) : HTMLElement {
517+ const elem = document . createElement ( 'span' ) ;
518+ elem . className = BREADCRUMB_ITEM_CLASS ;
519+ elem . textContent = pathPart ;
520+ elem . title = fullPath ;
521+ return elem ;
522+ }
523+
453524 /**
454525 * Create the breadcrumb nodes.
455526 */
@@ -483,14 +554,14 @@ namespace Private {
483554 /**
484555 * Create the breadcrumb separator nodes.
485556 */
486- export function createCrumbSeparators ( ) : ReadonlyArray < HTMLElement > {
557+ export function createCrumbSeparators (
558+ minimumLeftItems : number ,
559+ minimumRightItems : number
560+ ) : ReadonlyArray < HTMLElement > {
487561 const items : HTMLElement [ ] = [ ] ;
488- // The maximum number of directories that will be shown in the crumbs
489- const MAX_DIRECTORIES = 2 ;
562+ const REQUIRED_SEPARATORS = 1 + minimumLeftItems + 1 + minimumRightItems ;
490563
491- // Make separators for after each directory, one at the beginning, and one
492- // after a possible ellipsis.
493- for ( let i = 0 ; i < MAX_DIRECTORIES + 2 ; i ++ ) {
564+ for ( let i = 0 ; i < REQUIRED_SEPARATORS ; i ++ ) {
494565 const item = document . createElement ( 'span' ) ;
495566 item . textContent = '/' ;
496567 items . push ( item ) ;
0 commit comments