11import  {  useEffect  }  from  'react' ; 
2+ import  {  useStore  }  from  '@nanostores/react' ; 
23import  tutorialStore  from  "tutorialkit:store" ; 
4+ import  type  {  PreviewInfo  }  from  '@tutorialkit/runtime' ; 
5+ 
6+ const  ensureRailsServerStarted  =  async  ( preview : PreviewInfo )  =>  { 
7+   if  ( preview . ready )  return ; 
8+ 
9+   const  terminalConfig  =  tutorialStore . terminalConfig . get ( ) 
10+   const  terminal  =  terminalConfig . panels . find ( panel  =>  panel . type  ===  'terminal' ) ; 
11+   if  ( ! terminal  ||  ! terminal . process )  return ; 
12+ 
13+   terminal . input ( `bin/rails s\n` ) ; 
14+ 
15+   await  new  Promise < void > ( ( resolve ,  reject )  =>  { 
16+     const  tid  =  setTimeout ( ( )  =>  { 
17+       clearInterval ( tick ) ; 
18+       reject ( ) ; 
19+     } ,  10000 ) ; 
20+ 
21+     const  tick  =  setInterval ( ( )  =>  { 
22+       if  ( preview . ready )  { 
23+         clearInterval ( tick ) ; 
24+         clearTimeout ( tid ) ; 
25+         resolve ( ) ; 
26+       } 
27+     } ,  200 ) ; 
28+   } ) ; 
29+ } 
330
431export  default  function  RailsPathLinkHandler ( )  { 
32+   const  previews  =  useStore ( tutorialStore . previews ) ; 
33+ 
534  useEffect ( ( )  =>  { 
6-     function  handleClick ( event : MouseEvent )  { 
35+     async   function  handleClick ( event : MouseEvent )  { 
736      const  target  =  event . target  as  HTMLElement ; 
837      const  link  =  target . closest ( '.rails-path-link' ) ; 
938
@@ -14,6 +43,41 @@ export default function RailsPathLinkHandler() {
1443        if  ( railsPath )  { 
1544          tutorialStore . setSelectedFile ( `/workspace/store/${ railsPath }  ) ; 
1645        } 
46+         return ; 
47+       } 
48+ 
49+       if  ( target . tagName  ===  'A' )  { 
50+         const  linkTarget  =  target  as  HTMLAnchorElement ; 
51+ 
52+         if  ( linkTarget . href . startsWith ( 'http://localhost:3000' ) )  { 
53+           event . preventDefault ( ) ; 
54+ 
55+           const  railsPreview  =  previews . find ( ( pr )  =>  pr . port  ===  3000 ) ; 
56+ 
57+           if  ( ! railsPreview )  return ; 
58+ 
59+           try  { 
60+             await  ensureRailsServerStarted ( railsPreview ) 
61+           }  catch ( e )  { 
62+             console . error ( "failed to start Rails server" ,  e ) ; 
63+             return ; 
64+           } 
65+ 
66+           const  input  =  document . querySelector ( 'input[type="text"][name="tutorialkit-preview-navigation"]' )  as  HTMLInputElement ; 
67+           if  ( ! input )  return ; 
68+ 
69+           const  newPath  =  linkTarget . href . replace ( 'http://localhost:3000/' ,  '' ) ; 
70+ 
71+           input . value  =  newPath ; 
72+           const  ev  =  new  KeyboardEvent ( 'keydown' ,  { 
73+             key : 'Enter' , 
74+             code : 'Enter' , 
75+             keyCode : 13 , 
76+             bubbles : true , 
77+             cancelable : true , 
78+           } ) ; 
79+           input . dispatchEvent ( ev ) ; 
80+         } 
1781      } 
1882    } 
1983
@@ -22,7 +86,7 @@ export default function RailsPathLinkHandler() {
2286    return  ( )  =>  { 
2387      document . removeEventListener ( 'click' ,  handleClick ) ; 
2488    } ; 
25-   } ,  [ ] ) ; 
89+   } ,  [ previews ] ) ; 
2690
2791  return  null ; 
2892} 
0 commit comments