@@ -11,8 +11,11 @@ const outputConsole = document.getElementById('output-console');
1111const clearConsoleBtn = document . getElementById ( 'clear-console' ) ;
1212const refreshFilesBtn = document . getElementById ( 'refresh-files' ) ;
1313const fileSearchInput = document . getElementById ( 'file-search' ) ;
14+ const clearSearchBtn = document . getElementById ( 'clear-search' ) ;
1415const copyBtn = document . getElementById ( 'copy-btn' ) ;
1516
17+ let currentSearchTerm = '' ;
18+
1619const CACHE_KEY = 'php_repos_cache_v1' ;
1720const CACHE_DURATION = 5 * 60 * 1000 ; // 5 minutes
1821
@@ -36,8 +39,23 @@ document.addEventListener('DOMContentLoaded', () => {
3639 // Search functionality
3740 if ( fileSearchInput ) {
3841 fileSearchInput . addEventListener ( 'input' , ( e ) => {
39- const searchTerm = e . target . value . toLowerCase ( ) ;
40- filterFiles ( searchTerm ) ;
42+ currentSearchTerm = e . target . value . toLowerCase ( ) ;
43+ filterFiles ( currentSearchTerm ) ;
44+
45+ // Toggle clear button
46+ if ( clearSearchBtn ) {
47+ clearSearchBtn . style . display = currentSearchTerm ? 'flex' : 'none' ;
48+ }
49+ } ) ;
50+ }
51+
52+ if ( clearSearchBtn ) {
53+ clearSearchBtn . addEventListener ( 'click' , ( ) => {
54+ fileSearchInput . value = '' ;
55+ currentSearchTerm = '' ;
56+ filterFiles ( '' ) ;
57+ clearSearchBtn . style . display = 'none' ;
58+ fileSearchInput . focus ( ) ;
4159 } ) ;
4260 }
4361
@@ -152,19 +170,43 @@ async function fetchFiles(isBackground = false) {
152170 li . onclick = ( ) => loadFile ( file , li ) ;
153171 fileListEl . appendChild ( li ) ;
154172 } ) ;
173+
174+ // Re-apply search filter if active
175+ if ( currentSearchTerm ) {
176+ filterFiles ( currentSearchTerm ) ;
177+ }
155178}
156179
157180// Filter files based on search term
158181function filterFiles ( term ) {
159182 const items = fileListEl . querySelectorAll ( '.file-item' ) ;
183+ let foundCount = 0 ;
184+
160185 items . forEach ( item => {
161- const fileName = item . innerText . toLowerCase ( ) ;
186+ const fileName = item . innerText . trim ( ) . toLowerCase ( ) ;
162187 if ( fileName . includes ( term ) ) {
163- item . style . display = 'flex' ;
188+ item . style . setProperty ( 'display' , 'flex' , 'important' ) ;
189+ foundCount ++ ;
164190 } else {
165- item . style . display = 'none' ;
191+ item . style . setProperty ( ' display' , 'none' , 'important' ) ;
166192 }
167193 } ) ;
194+
195+ // Handle "No results" message
196+ const existingNoResults = fileListEl . querySelector ( '.no-results' ) ;
197+ if ( foundCount === 0 && term !== '' ) {
198+ if ( ! existingNoResults ) {
199+ const noRes = document . createElement ( 'li' ) ;
200+ noRes . className = 'file-item no-results' ;
201+ noRes . style . cursor = 'default' ;
202+ noRes . style . color = 'var(--text-muted)' ;
203+ noRes . style . justifyContent = 'center' ;
204+ noRes . innerHTML = 'No matches found' ;
205+ fileListEl . appendChild ( noRes ) ;
206+ }
207+ } else if ( existingNoResults ) {
208+ existingNoResults . remove ( ) ;
209+ }
168210}
169211
170212// Poll for updates every 5 minutes (reduced from 60s to save API calls)
@@ -202,7 +244,7 @@ async function loadFile(file, element) {
202244
203245 runBtn . disabled = false ;
204246 runBtn . innerHTML = '<ion-icon name="play"></ion-icon> Run Code' ;
205-
247+
206248 if ( copyBtn ) copyBtn . disabled = false ;
207249
208250 // Log to terminal
@@ -216,33 +258,70 @@ async function loadFile(file, element) {
216258}
217259
218260// Copy Code to Clipboard
261+ // Copy Code to Clipboard with Fallback
219262async function copyToClipboard ( ) {
220263 if ( ! currentCode ) return ;
221264
222- try {
223- await navigator . clipboard . writeText ( currentCode ) ;
224-
265+ const copySuccess = ( ) => {
225266 // Show success tooltip
226267 const tooltip = document . createElement ( 'div' ) ;
227268 tooltip . className = 'copy-tooltip' ;
228269 tooltip . innerText = 'Copied!' ;
229270 copyBtn . parentElement . appendChild ( tooltip ) ;
230-
271+
231272 setTimeout ( ( ) => tooltip . classList . add ( 'show' ) , 10 ) ;
232273 setTimeout ( ( ) => {
233274 tooltip . classList . remove ( 'show' ) ;
234275 setTimeout ( ( ) => tooltip . remove ( ) , 300 ) ;
235276 } , 2000 ) ;
236277
237278 // Change icon temporarily
238- const originalIcon = copyBtn . querySelector ( 'ion-icon' ) . name ;
239- copyBtn . querySelector ( 'ion-icon' ) . name = 'checkmark-outline' ;
240- setTimeout ( ( ) => {
241- copyBtn . querySelector ( 'ion-icon' ) . name = originalIcon ;
242- } , 2000 ) ;
279+ const iconEl = copyBtn . querySelector ( 'ion-icon' ) ;
280+ if ( iconEl ) {
281+ const originalIcon = iconEl . name ;
282+ iconEl . name = 'checkmark-outline' ;
283+ setTimeout ( ( ) => {
284+ iconEl . name = originalIcon ;
285+ } , 2000 ) ;
286+ }
287+ } ;
288+
289+ // Try modern API first
290+ if ( navigator . clipboard && navigator . clipboard . writeText ) {
291+ try {
292+ await navigator . clipboard . writeText ( currentCode ) ;
293+ copySuccess ( ) ;
294+ return ;
295+ } catch ( err ) {
296+ console . warn ( 'Clipboard API failed, trying fallback...' , err ) ;
297+ }
298+ }
243299
300+ // Fallback: Temporary Textarea
301+ try {
302+ const textArea = document . createElement ( 'textarea' ) ;
303+ textArea . value = currentCode ;
304+
305+ // Ensure textarea is not visible but part of DOM
306+ textArea . style . position = 'fixed' ;
307+ textArea . style . left = '-9999px' ;
308+ textArea . style . top = '0' ;
309+ document . body . appendChild ( textArea ) ;
310+
311+ textArea . focus ( ) ;
312+ textArea . select ( ) ;
313+
314+ const successful = document . execCommand ( 'copy' ) ;
315+ document . body . removeChild ( textArea ) ;
316+
317+ if ( successful ) {
318+ copySuccess ( ) ;
319+ } else {
320+ throw new Error ( 'execCommand copy failed' ) ;
321+ }
244322 } catch ( err ) {
245- console . error ( 'Failed to copy: ' , err ) ;
323+ console . error ( 'Copy fallback failed: ' , err ) ;
324+ alert ( 'Could not copy code. Please select and copy manually.' ) ;
246325 }
247326}
248327
0 commit comments