@@ -54,6 +54,81 @@ export default () => {
5454 window . parent . postMessage ( window . location . href , "*" ) ;
5555 } , [ ] ) ;
5656
57+ // Handle file download links
58+ useEffect ( ( ) => {
59+ const DOWNLOAD_EXTENSIONS = [ '.ipynb' , '.zip' , '.tgz' , '.tar.gz' , '.sh' , '.py' , '.sql' ] ;
60+
61+ const getPathname = ( href : string ) : string => {
62+ try {
63+ return new URL ( href , window . location . origin ) . pathname ;
64+ } catch {
65+ return href . split ( '?' ) [ 0 ] . split ( '#' ) [ 0 ] ;
66+ }
67+ } ;
68+
69+ const shouldDownload = ( pathname : string ) : boolean => {
70+ const lowerPath = pathname . toLowerCase ( ) ;
71+ return DOWNLOAD_EXTENSIONS . some ( ext => lowerPath . endsWith ( ext ) ) ;
72+ } ;
73+
74+ const downloadFile = ( url : string , filename : string ) => {
75+ const isSameOrigin = url . startsWith ( '/' ) || url . startsWith ( window . location . origin ) ;
76+
77+ if ( isSameOrigin ) {
78+ fetch ( url )
79+ . then ( res => res . ok ? res . blob ( ) : Promise . reject ( new Error ( 'Failed to fetch' ) ) )
80+ . then ( blob => {
81+ const blobUrl = window . URL . createObjectURL ( blob ) ;
82+ const a = document . createElement ( 'a' ) ;
83+ a . href = blobUrl ;
84+ a . download = filename ;
85+ a . style . display = 'none' ;
86+ document . body . appendChild ( a ) ;
87+ a . click ( ) ;
88+ setTimeout ( ( ) => {
89+ window . URL . revokeObjectURL ( blobUrl ) ;
90+ document . body . removeChild ( a ) ;
91+ } , 100 ) ;
92+ } )
93+ . catch ( ( ) => console . warn ( 'Failed to download file:' , url ) ) ;
94+ } else {
95+ const a = document . createElement ( 'a' ) ;
96+ a . href = url ;
97+ a . download = filename ;
98+ a . style . display = 'none' ;
99+ document . body . appendChild ( a ) ;
100+ a . click ( ) ;
101+ setTimeout ( ( ) => document . body . removeChild ( a ) , 100 ) ;
102+ }
103+ } ;
104+
105+ const handleDownloadLinks = ( ) => {
106+ document . querySelectorAll ( 'a[href]:not([data-download-handled])' ) . forEach ( ( link ) => {
107+ const href = link . getAttribute ( 'href' ) ;
108+ if ( ! href ) return ;
109+
110+ const pathname = getPathname ( href ) ;
111+ if ( ! shouldDownload ( pathname ) ) return ;
112+
113+ link . setAttribute ( 'data-download-handled' , 'true' ) ;
114+ link . addEventListener ( 'click' , ( e : Event ) => {
115+ e . preventDefault ( ) ;
116+ e . stopPropagation ( ) ;
117+ e . stopImmediatePropagation ( ) ;
118+ downloadFile ( href , pathname . split ( '/' ) . pop ( ) || 'download' ) ;
119+ } , { capture : true } ) ;
120+ } ) ;
121+ } ;
122+
123+ handleDownloadLinks ( ) ;
124+ const observer = new MutationObserver ( ( ) => setTimeout ( handleDownloadLinks , 100 ) ) ;
125+ observer . observe ( document . querySelector ( '.rspress-doc-content' ) || document . body , {
126+ childList : true ,
127+ subtree : true
128+ } ) ;
129+ return ( ) => observer . disconnect ( ) ;
130+ } , [ ] ) ;
131+
57132 return (
58133 < Layout
59134 uiSwitch = { uiSwitch }
0 commit comments