11import { JupyterFrontEnd , JupyterFrontEndPlugin } from '@jupyterlab/application' ;
2+ import { ILiteRouter } from '@jupyterlite/application' ;
23import { INotebookTracker , INotebookWidgetFactory } from '@jupyterlab/notebook' ;
34import { INotebookContent } from '@jupyterlab/nbformat' ;
45import { SidebarIcon } from '../ui-components/SidebarIcon' ;
@@ -41,16 +42,26 @@ export const notebookPlugin: JupyterFrontEndPlugin<void> = {
4142 IToolbarWidgetRegistry ,
4243 INotebookWidgetFactory
4344 ] ,
45+ optional : [ ILiteRouter ] ,
4446 activate : (
4547 app : JupyterFrontEnd ,
4648 tracker : INotebookTracker ,
4749 readonlyTracker : IViewOnlyNotebookTracker ,
48- toolbarRegistry : IToolbarWidgetRegistry
50+ toolbarRegistry : IToolbarWidgetRegistry ,
51+ router ?: ILiteRouter | null
4952 ) => {
5053 const { commands, shell, serviceManager } = app ;
5154 const { contents } = serviceManager ;
5255
5356 const params = new URLSearchParams ( window . location . search ) ;
57+
58+ // Are we landing on the Files tab directly? In this case, we won't
59+ // auto-create a new notebook or activate the notebook sidebar.
60+ const nowUrl = new URL ( window . location . href ) ;
61+ const onFilesPath = / \/ l a b \/ f i l e s (?: \/ | $ ) / . test ( nowUrl . pathname ) ;
62+ const onFilesTab = nowUrl . searchParams . get ( 'tab' ) === 'files' ;
63+ const onFilesIntent = onFilesPath || onFilesTab ;
64+
5465 let notebookId = params . get ( 'notebook' ) ;
5566 const uploadedNotebookId = params . get ( 'uploaded-notebook' ) ;
5667
@@ -210,7 +221,7 @@ export const notebookPlugin: JupyterFrontEndPlugin<void> = {
210221 void loadSharedNotebook ( notebookId ) ;
211222 } else if ( uploadedNotebookId ) {
212223 void openUploadedNotebook ( uploadedNotebookId ) ;
213- } else {
224+ } else if ( ! onFilesIntent ) {
214225 void createNewNotebook ( ) ;
215226 }
216227
@@ -230,19 +241,35 @@ export const notebookPlugin: JupyterFrontEndPlugin<void> = {
230241 const sidebarItem = new SidebarIcon ( {
231242 label : 'Notebook' ,
232243 icon : EverywhereIcons . notebook ,
244+ pathName : `${ ( router ?. base || '' ) . replace ( / \/ $ / , '' ) } /lab/index.html` ,
233245 execute : ( ) => {
234246 if ( readonlyTracker . currentWidget ) {
235- return shell . activateById ( readonlyTracker . currentWidget . id ) ;
247+ const id = readonlyTracker . currentWidget . id ;
248+ shell . activateById ( id ) ;
249+ return SidebarIcon . delegateNavigation ;
236250 }
237251 if ( tracker . currentWidget ) {
238- return shell . activateById ( tracker . currentWidget . id ) ;
252+ const id = tracker . currentWidget . id ;
253+ shell . activateById ( id ) ;
254+ return SidebarIcon . delegateNavigation ;
239255 }
256+
257+ // If we don't have a notebook yet (likely we started on /lab/files/) -> create one now.
258+ void ( async ( ) => {
259+ await app . commands . execute ( 'notebook:create-new' , { kernelName : 'python' } ) ;
260+ if ( tracker . currentWidget ) {
261+ shell . activateById ( tracker . currentWidget . id ) ;
262+ }
263+ } ) ( ) ;
264+ return SidebarIcon . delegateNavigation ;
240265 }
241266 } ) ;
242267 shell . add ( sidebarItem , 'left' , { rank : 100 } ) ;
243268
244- app . shell . activateById ( sidebarItem . id ) ;
245- app . restored . then ( ( ) => app . shell . activateById ( sidebarItem . id ) ) ;
269+ if ( ! onFilesIntent ) {
270+ app . shell . activateById ( sidebarItem . id ) ;
271+ app . restored . then ( ( ) => app . shell . activateById ( sidebarItem . id ) ) ;
272+ }
246273
247274 for ( const toolbarName of [ 'Notebook' , 'ViewOnlyNotebook' ] ) {
248275 toolbarRegistry . addFactory (
@@ -293,5 +320,33 @@ export const notebookPlugin: JupyterFrontEndPlugin<void> = {
293320 ( ) => new KernelSwitcherDropdownButton ( commands , tracker )
294321 ) ;
295322 }
323+
324+ // Canonicalise the URL if we are directly at /lab/.
325+ void app . restored . then ( ( ) => {
326+ const url = new URL ( window . location . href ) ;
327+ if ( / \/ l a b \/ $ / . test ( url . pathname ) ) {
328+ url . pathname = url . pathname . replace ( / \/ l a b \/ $ / , '/lab/index.html' ) ;
329+ window . history . replaceState ( { } , '' , url . toString ( ) ) ;
330+ }
331+
332+ const after = new URL ( window . location . href ) ;
333+ if ( after . searchParams . get ( 'tab' ) === 'notebook' ) {
334+ const id = document . querySelector ( '.jp-NotebookPanel' ) ?. id ;
335+ if ( id ) {
336+ app . shell . activateById ( id ) ;
337+ after . searchParams . delete ( 'tab' ) ;
338+ const base = ( router ?. base || '' ) . replace ( / \/ $ / , '' ) ;
339+ const canonical = new URL ( `${ base } /lab/index.html` , window . location . origin ) ;
340+ canonical . hash = after . hash ;
341+ // Keep any other non-tab params off; Notebook page doesn't need them
342+ if (
343+ after . pathname + after . search + after . hash !==
344+ canonical . pathname + canonical . search + canonical . hash
345+ ) {
346+ window . history . replaceState ( null , 'Notebook' , canonical . toString ( ) ) ;
347+ }
348+ }
349+ }
350+ } ) ;
296351 }
297352} ;
0 commit comments