11( ( ) => {
2- const n = "GitFolderDownloader-modal" ;
3-
4- function e ( e ) {
5- const linkPadding = e === "repo" ? "revert-layer" : "0" ;
6-
7- // Determine context (Repo, Folder, or File)
8- const o = ( function ( ) {
9- const n = location . pathname . split ( "/" ) . filter ( Boolean ) ;
10- return 2 === n . length
11- ? { type : "repo" , name : `${ n [ 0 ] } -${ n [ 1 ] } ` }
12- : n . includes ( "tree" )
13- ? { type : "folder" , name : n [ n . length - 1 ] }
14- : n . includes ( "blob" )
15- ? { type : "file" , name : n [ n . length - 1 ] }
16- : { type : "unknown" , name : "github-download" } ;
17- } ) ( ) ,
18- t = document . createElement ( "li" ) ;
19-
20- var r ;
21- return (
22- ( t . className = "prc-ActionList-ActionListItem-So4vC" ) ,
23- ( t . innerHTML = `<a class="prc-ActionList-ActionListContent-KBb8- prc-Link-Link-9ZwDx" role="menuitem" tabindex="-1" data-GitFolderDownloader="${ e } " style="padding:${ linkPadding } ;">
2+ const MODAL_ID = "GitFolderDownloader-modal" ;
3+
4+ let __dg_prevOverflow = null ;
5+
6+ function lockBodyScroll ( ) {
7+ __dg_prevOverflow = document . body . style . overflow ;
8+ document . body . style . overflow = "hidden" ;
9+ }
10+
11+ function unlockBodyScroll ( ) {
12+ document . body . style . overflow = __dg_prevOverflow || "" ;
13+ }
14+
15+ function createMenuItem ( kind ) {
16+ const linkPadding = kind === "repo" ? "revert-layer" : "0" ;
17+
18+ const ctx = ( function ( ) {
19+ const parts = location . pathname . split ( "/" ) . filter ( Boolean ) ;
20+ return 2 === parts . length
21+ ? { type : "repo" , name : `${ parts [ 0 ] } -${ parts [ 1 ] } ` }
22+ : parts . includes ( "tree" )
23+ ? { type : "folder" , name : parts [ parts . length - 1 ] }
24+ : parts . includes ( "blob" )
25+ ? { type : "file" , name : parts [ parts . length - 1 ] }
26+ : { type : "unknown" , name : "github-download" } ;
27+ } ) ( ) ;
28+
29+ const li = document . createElement ( "li" ) ;
30+ li . className = "prc-ActionList-ActionListItem-So4vC" ;
31+ li . innerHTML = `<a class="prc-ActionList-ActionListContent-KBb8- prc-Link-Link-9ZwDx" role="menuitem" tabindex="-1" data-GitFolderDownloader="${ kind } " style="padding:${ linkPadding } ;">
2432 <span class="prc-ActionList-Spacer-4tR2m"></span>
2533 <span class="prc-ActionList-LeadingVisual-NBr28 prc-ActionList-VisualWrap-bdCsS">
26- ${
27- ( ( r = e ) ,
28- "repo" === r
29- ? '<svg aria-hidden="true" focusable="false" class="octicon octicon-download" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M2.75 14A1.75 1.75 0 0 1 1 12.25v-2.5a.75.75 0 0 1 1.5 0v2.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25v-2.5a.75.75 0 0 1 1.5 0v2.5A1.75 1.75 0 0 1 13.25 14Z"></path><path d="M7.25 7.689V2a.75.75 0 0 1 1.5 0v5.689l1.97-1.969a.749.749 0 1 1 1.06 1.06l-3.25 3.25a.749.749 0 0 1-1.06 0L4.22 6.78a.749.749 0 1 1 1.06-1.06l1.97 1.969Z"></path></svg>'
30- : "folder" === r
31- ? '<svg style="width: 0;height: 0;"></svg>'
32- : '<svg style="width: 0;height: 0;"></svg>' )
34+ ${ kind === "repo"
35+ ? '<svg aria-hidden="true" focusable="false" class="octicon octicon-download" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M2.75 14A1.75 1.75 0 0 1 1 12.25v-2.5a.75.75 0 0 1 1.5 0v2.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25v-2.5a.75.75 0 0 1 1.5 0v2.5A1.75 1.75 0 0 1 13.25 14Z"></path><path d="M7.25 7.689V2a.75.75 0 0 1 1.5 0v5.689l1.97-1.969a.749.749 0 1 1 1.06 1.06l-3.25 3.25a.749.749 0 0 1-1.06 0L4.22 6.78a.749.749 0 1 1 1.06-1.06l1.97 1.969Z"></path></svg>'
36+ : '<svg style="width: 0;height: 0;"></svg>'
3337 }
34- </span>
38+ </span>
3539 <span class="prc-ActionList-ActionListSubContent-gKsFp" data-component="ActionList.Item--DividerContainer">
3640 <span class="prc-ActionList-ItemLabel-81ohH">
37- ${
38- ( ( r = e ) ,
39- "repo" === r
40- ? "Download Repo"
41- : "folder" === r
41+ ${ kind === "repo"
42+ ? "Download Repo"
43+ : kind === "folder"
4244 ? "Download Folder"
43- : "Download" )
45+ : "Download"
4446 }
45- </span></span></a>` ) ,
46- ( t . onclick = ( ) => {
47- // Modal Logic
48- ! ( function ( defaultName , callback ) {
49- if ( document . getElementById ( n ) ) return ;
50- document . documentElement . dataset . colorMode ;
51- const t = document . createElement ( "div" ) ;
52- ( t . id = n ) ,
53- ( t . innerHTML = `
54- <style>
55- #${ n } {position: fixed; inset: 0; z-index: 99999; display: flex; align-items: center; justify-content: center;}
56- .modal-overlay {display: block; position: fixed; width: -webkit-fill-available; height: -webkit-fill-available;}
57- .dg-box {
58- background:var(--overlay-bgColor,var(--color-canvas-overlay));
59- color: var(--fgColor-default);
60- width: 340px; padding: 16px;
61- border-radius: 12px; border: 1px solid var(--color-border-default);
62- box-shadow:var(--shadow-floating-small,var(--color-overlay-shadow));
63- font-family: system-ui;
64- animation:prc-Overlay-overlay-appear-JpFey .2s cubic-bezier(.33,1,.68,1); z-index:10001;
65- }
66- .dg-box h3 {
67- text-align: center; background-color: initial;
68- color: var(--fgColor-default,var(--color-fg-default));
69- font-size: 18px; font-weight: 700; margin: 0 auto 10px;
70- }
71- .dg-label {
72- font-size: 12px; font-weight: 600; margin-top: 8px; display: block;
73- color: var(--fgColor-default, var(--color-fg-default));
74- }
75- .dg-box input {
76- width: 100%; padding: 8px; margin-top: 4px;
77- border-radius: 8px; background: var(--bgColor-disabled);
78- border: 1px solid var(--control-borderColor-rest, var(--color-border-default));
79- outline: none; color: var(--fgColor-default, var(--color-fg-default));
80- }
81- .dg-box input:focus { outline: 2px solid var(--color-accent-fg); }
82- .dg-row { display: flex; gap: 10px; }
83- .dg-col { flex: 1; }
84- .dg-actions {
85- margin-top: 20px; font-weight: 800; display: flex; justify-content: space-around;
86- }
87- .dg-actions button {
88- padding: 8px 16px; border-radius: 6px; border: none; cursor: pointer; font-size: 13px;
89- }
90- .dg-cancel { background: var(--button-default-bgColor-rest); }
91- .dg-cancel:hover { background: var(--button-default-bgColor-hover); }
92- .dg-confirm {
93- background: var(--button-primary-bgColor-rest);
94- color:var(--button-primary-fgColor-rest,var(--color-btn-primary-text));
95- }
96- .dg-confirm:hover { background: var(--button-primary-bgColor-hover); }
97- </style>
98- <div class="modal-overlay"></div>
99- <div class="dg-box">
100- <h3>Download Options</h3>
101-
102- <label class="dg-label">Name</label>
103- <input id="dg-name" value="${ defaultName } " placeholder="File name"/>
104-
105- <div class="dg-row">
106- <div class="dg-col">
107- <label class="dg-label">st (Start)</label>
108- <input id="dg-st" type="number" placeholder="0" />
109- </div>
110- <div class="dg-col">
111- <label class="dg-label">mx (Max)</label>
112- <input id="dg-mx" type="number" placeholder="0" />
113- </div>
114- </div>
115-
116- <div class="dg-actions">
117- <button class="dg-cancel">Cancel</button>
118- <button class="dg-confirm">Download</button>
119- </div>
120- </div>
121- ` ) ,
122- document . body . appendChild ( t ) ,
123- t . querySelector ( ".modal-overlay" ) . addEventListener ( "click" , ( ) => {
124- t . remove ( ) ;
125- } ) ,
126- t . querySelector ( ".dg-cancel" ) . addEventListener ( "click" , ( ) => {
127- t . remove ( ) ;
128- } ) ,
129- t . querySelector ( ".dg-confirm" ) . addEventListener ( "click" , ( ) => {
130- // Retrieve values
131- const nameVal = t . querySelector ( "#dg-name" ) . value . trim ( ) || defaultName ;
132- const stVal = t . querySelector ( "#dg-st" ) . value . trim ( ) ;
133- const mxVal = t . querySelector ( "#dg-mx" ) . value . trim ( ) ;
134-
135- t . remove ( ) ;
136- callback ( nameVal , stVal , mxVal ) ;
137- } ) ,
138- document . addEventListener (
139- "keydown" ,
140- ( n ) => {
141- "Escape" === n . key && t . remove ( ) ;
142- } ,
143- { once : ! 0 }
144- ) ;
145- } ) ( o . name , ( name , st , mx ) => {
146- // Construct URL
147- let finalUrl = `https://GitFolderDownloader.github.io/api/?url=${ encodeURIComponent ( location . href ) } &name=${ encodeURIComponent ( name ) } ` ;
148-
149- if ( st ) finalUrl += `&st=${ encodeURIComponent ( st ) } ` ;
150- if ( mx ) finalUrl += `&mx=${ encodeURIComponent ( mx ) } ` ;
151-
152- window . open ( finalUrl , "_blank" ) ;
153- } ) ;
154- } ) ,
155- t
156- ) ;
47+ </span></span></a>` ;
48+
49+ const anchor = li . querySelector ( "a" ) ;
50+ anchor . addEventListener ( "pointerdown" , ( evt ) => {
51+ if ( evt . button !== 0 ) return ;
52+
53+ evt . preventDefault ( ) ;
54+ evt . stopPropagation ( ) ;
55+ showModalAndOpen ( ctx . name , ctx . type ) ;
56+ } ) ;
57+
58+
59+ return li ;
15760 }
15861
159- function o ( ) {
160- document
161- . querySelectorAll ( "ul.prc-ActionList-ActionList-rPFF2" )
162- . forEach ( ( n ) => {
163- const o = ( function ( n ) {
164- const e = n . innerText ;
165- return e . includes ( "Download ZIP" )
166- ? "repo"
167- : e . includes ( "Copy path" ) && ! e . includes ( "Raw file" )
62+ function showModalAndOpen ( defaultName , type = "repo" ) {
63+ if ( document . getElementById ( MODAL_ID ) ) return ;
64+
65+ const t = document . createElement ( "div" ) ;
66+ t . id = MODAL_ID ;
67+ t . innerHTML = `
68+ <style>
69+ #${ MODAL_ID } {position: fixed; inset: 0; z-index: 99999; display: flex; align-items: center; justify-content: center;}
70+ .modal-overlay {display: block; position: fixed; width: -webkit-fill-available; height: -webkit-fill-available;}
71+ .dg-box {
72+ background:var(--overlay-bgColor,var(--color-canvas-overlay));
73+ color: var(--fgColor-default);
74+ width: 340px; padding: 16px;
75+ border-radius: 12px; border: 1px solid var(--color-border-default);
76+ box-shadow:var(--shadow-floating-small,var(--color-overlay-shadow));
77+ font-family: system-ui;
78+ animation:prc-Overlay-overlay-appear-JpFey .2s cubic-bezier(.33,1,.68,1); z-index:10001;
79+ }
80+ .dg-box h3 {
81+ text-align: center; background-color: initial;
82+ color: var(--fgColor-default,var(--color-fg-default));
83+ font-size: 18px; font-weight: 700; margin: 0 auto 10px;
84+ }
85+ .dg-label {
86+ font-size: 12px; font-weight: 600; margin-top: 8px; display: block;
87+ color: var(--fgColor-default, var(--color-fg-default));
88+ }
89+ .dg-box input {
90+ width: 100%; padding: 8px; margin-top: 4px;
91+ border-radius: 8px; background: var(--bgColor-disabled);
92+ border: 1px solid var(--control-borderColor-rest, var(--color-border-default));
93+ outline: none; color: var(--fgColor-default, var(--color-fg-default));
94+ }
95+ .dg-box input:focus { outline: 2px solid var(--color-accent-fg); }
96+ .dg-row { display: flex; gap: 10px; }
97+ .dg-col { flex: 1; }
98+ .dg-actions {
99+ margin-top: 20px; font-weight: 800; display: flex; justify-content: space-around;
100+ }
101+ .dg-actions button {
102+ padding: 8px 16px; border-radius: 6px; border: none; cursor: pointer; font-size: 13px;
103+ }
104+ .dg-cancel { background: var(--button-default-bgColor-rest); }
105+ .dg-cancel:hover { background: var(--button-default-bgColor-hover); }
106+ .dg-confirm {
107+ background: var(--button-primary-bgColor-rest);
108+ color:var(--button-primary-fgColor-rest,var(--color-btn-primary-text));
109+ }
110+ .dg-confirm:hover { background: var(--button-primary-bgColor-hover); }
111+ </style>
112+ <div class="modal-overlay"></div>
113+ <div class="dg-box">
114+ <h3>Download Options</h3>
115+ <label class="dg-label">Set Name</label>
116+ <input id="dg-name" value="${ defaultName } " placeholder="File name"/>
117+
118+
119+ ${ type !== "file"
120+ ? `
121+ <h5 style="margin: 10px auto 0;">Set files range</h5>
122+ <div class="dg-row">
123+ <div class="dg-col">
124+ <label class="dg-label">From</label>
125+ <input id="dg-st" min="0" type="number" placeholder="from 0" />
126+ </div>
127+ <div class="dg-col">
128+ <label class="dg-label">To</label>
129+ <input id="dg-mx" min="0" type="number" placeholder="to all" />
130+ </div>
131+ </div>
132+ `
133+ : ""
134+ }
135+
136+ <div class="dg-actions">
137+ <button class="dg-cancel">Cancel</button>
138+ <button class="dg-confirm">Download</button>
139+ </div>
140+ </div>
141+
142+ ` ;
143+
144+ document . body . appendChild ( t ) ;
145+ lockBodyScroll ( ) ;
146+
147+ //const removeModal = () => t.remove();
148+ const removeModal = ( ) => {
149+ unlockBodyScroll ( ) ;
150+ t . remove ( ) ;
151+ } ;
152+
153+
154+ t . querySelector ( ".modal-overlay" ) . addEventListener ( "click" , removeModal ) ;
155+ t . querySelector ( ".dg-cancel" ) . addEventListener ( "click" , removeModal ) ;
156+ t . querySelector ( ".dg-confirm" ) . addEventListener ( "click" , ( ) => {
157+ const nameVal = t . querySelector ( "#dg-name" ) . value . trim ( ) || defaultName ;
158+ const stVal = type !== "file" ? t . querySelector ( "#dg-st" ) ?. value . trim ( ) : "" ;
159+ const mxVal = type !== "file" ? t . querySelector ( "#dg-mx" ) ?. value . trim ( ) : "" ;
160+
161+ removeModal ( ) ;
162+
163+ let finalUrl = `https://GitFolderDownloader.github.io/api/?url=${ encodeURIComponent ( location . href ) } &name=${ encodeURIComponent ( nameVal ) } ` ;
164+ if ( stVal ) finalUrl += `&st=${ encodeURIComponent ( stVal ) } ` ;
165+ if ( mxVal ) finalUrl += `&mx=${ encodeURIComponent ( mxVal ) } ` ;
166+ window . open ( finalUrl , "_blank" ) ;
167+ } ) ;
168+
169+ document . addEventListener ( "keydown" , ( ev ) => {
170+ if ( ev . key === "Escape" ) removeModal ( ) ;
171+ } , { once : true } ) ;
172+ }
173+
174+ function injectButtonsIntoMenus ( ) {
175+ document . querySelectorAll ( "ul.prc-ActionList-ActionList-rPFF2" ) . forEach ( ( menu ) => {
176+ const kind = ( function ( menuEl ) {
177+ const text = menuEl . innerText ;
178+ return text . includes ( "Download ZIP" )
179+ ? "repo"
180+ : text . includes ( "Copy path" ) && ! text . includes ( "Raw file" )
168181 ? "folder"
169- : e . includes ( "Raw file content" )
170- ? "file"
171- : null ;
172- } ) ( n ) ;
173- o &&
174- ( n . querySelector ( `[data-GitFolderDownloader="${ o } "]` ) ||
175- n . insertBefore ( e ( o ) , n . firstElementChild ) ) ;
176- } ) ;
182+ : text . includes ( "Raw file content" )
183+ ? "file"
184+ : null ;
185+ } ) ( menu ) ;
186+
187+ if ( ! kind ) return ;
188+
189+ if ( menu . querySelector ( `[data-GitFolderDownloader="${ kind } "]` ) ) return ;
190+
191+ const item = createMenuItem ( kind ) ;
192+ menu . insertBefore ( item , menu . firstElementChild ) ;
193+ } ) ;
177194 }
178195
179- new MutationObserver ( o ) . observe ( document . body , {
180- childList : ! 0 ,
181- subtree : ! 0 ,
182- } ) ,
183- o ( ) ;
184- } ) ( ) ;
196+ document . addEventListener ( "keydown" , ( e ) => {
197+ if ( ! ( e . ctrlKey && e . shiftKey && e . key . toLowerCase ( ) === "d" ) ) return ;
198+ if ( / i n p u t | t e x t a r e a / i. test ( document . activeElement ?. tagName ) ) return ;
199+
200+ const path = location . pathname . split ( "/" ) . filter ( Boolean ) ;
201+
202+ const isFolder = path . includes ( "tree" ) ;
203+ const isFile = path . includes ( "blob" ) ;
204+
205+ if ( ! isFolder && ! isFile ) return ;
206+
207+ e . preventDefault ( ) ;
208+ e . stopPropagation ( ) ;
209+
210+ const name = path [ path . length - 1 ] ;
211+ showModalAndOpen ( name , isFile ? "file" : "folder" ) ;
212+ } ) ;
213+
214+ new MutationObserver ( injectButtonsIntoMenus ) . observe ( document . body , {
215+ childList : true ,
216+ subtree : true ,
217+ } ) ;
218+ injectButtonsIntoMenus ( ) ;
219+ } ) ( ) ;
0 commit comments