@@ -7,9 +7,12 @@ document.addEventListener('DOMContentLoaded', () => {
77 githubRepo : 'unclecode/crawl4ai' ,
88 githubBranch : 'main' ,
99 docsPath : 'docs/md_v2' ,
10- excludePaths : [ '/apps/c4a-script/' , '/apps/llmtxt/' , '/apps/crawl4ai-assistant/' ] , // Don't show on app pages
10+ excludePaths : [ '/apps/c4a-script/' , '/apps/llmtxt/' , '/apps/crawl4ai-assistant/' , '/core/ask-ai/' ] , // Don't show on app pages
1111 } ;
1212
13+ let cachedMarkdown = null ;
14+ let cachedMarkdownPath = null ;
15+
1316 // Check if we should show the button on this page
1417 function shouldShowButton ( ) {
1518 const currentPath = window . location . pathname ;
@@ -19,6 +22,17 @@ document.addEventListener('DOMContentLoaded', () => {
1922 return false ;
2023 }
2124
25+ // Don't show on 404 pages
26+ if ( document . title && document . title . toLowerCase ( ) . includes ( '404' ) ) {
27+ return false ;
28+ }
29+
30+ // Require mkdocs main content container
31+ const mainContent = document . getElementById ( 'terminal-mkdocs-main-content' ) ;
32+ if ( ! mainContent ) {
33+ return false ;
34+ }
35+
2236 // Don't show on excluded paths (apps)
2337 for ( const excludePath of config . excludePaths ) {
2438 if ( currentPath . includes ( excludePath ) ) {
@@ -53,6 +67,56 @@ document.addEventListener('DOMContentLoaded', () => {
5367 return `${ path } .md` ;
5468 }
5569
70+ async function loadMarkdownContent ( ) {
71+ const mdPath = getCurrentMarkdownPath ( ) ;
72+
73+ if ( ! mdPath ) {
74+ throw new Error ( 'Invalid markdown path' ) ;
75+ }
76+
77+ const rawUrl = getGithubRawUrl ( ) ;
78+ const response = await fetch ( rawUrl ) ;
79+
80+ if ( ! response . ok ) {
81+ throw new Error ( `Failed to fetch markdown: ${ response . status } ` ) ;
82+ }
83+
84+ const markdown = await response . text ( ) ;
85+ cachedMarkdown = markdown ;
86+ cachedMarkdownPath = mdPath ;
87+ return markdown ;
88+ }
89+
90+ async function ensureMarkdownCached ( ) {
91+ const mdPath = getCurrentMarkdownPath ( ) ;
92+
93+ if ( ! mdPath ) {
94+ return false ;
95+ }
96+
97+ if ( cachedMarkdown && cachedMarkdownPath === mdPath ) {
98+ return true ;
99+ }
100+
101+ try {
102+ await loadMarkdownContent ( ) ;
103+ return true ;
104+ } catch ( error ) {
105+ console . warn ( 'Page Actions: Markdown not available for this page.' , error ) ;
106+ cachedMarkdown = null ;
107+ cachedMarkdownPath = null ;
108+ return false ;
109+ }
110+ }
111+
112+ async function getMarkdownContent ( ) {
113+ const available = await ensureMarkdownCached ( ) ;
114+ if ( ! available ) {
115+ throw new Error ( 'Markdown not available for this page.' ) ;
116+ }
117+ return cachedMarkdown ;
118+ }
119+
56120 // Get GitHub raw URL for current page
57121 function getGithubRawUrl ( ) {
58122 const mdPath = getCurrentMarkdownPath ( ) ;
@@ -180,19 +244,11 @@ document.addEventListener('DOMContentLoaded', () => {
180244
181245 // Copy markdown to clipboard
182246 async function copyMarkdownToClipboard ( link ) {
183- const rawUrl = getGithubRawUrl ( ) ;
184-
185247 // Add loading state
186248 link . classList . add ( 'loading' ) ;
187249
188250 try {
189- const response = await fetch ( rawUrl ) ;
190-
191- if ( ! response . ok ) {
192- throw new Error ( `Failed to fetch markdown: ${ response . status } ` ) ;
193- }
194-
195- const markdown = await response . text ( ) ;
251+ const markdown = await getMarkdownContent ( ) ;
196252
197253 // Copy to clipboard
198254 await navigator . clipboard . writeText ( markdown ) ;
@@ -221,126 +277,141 @@ document.addEventListener('DOMContentLoaded', () => {
221277 window . open ( githubUrl , '_blank' , 'noopener,noreferrer' ) ;
222278 }
223279
224- // Initialize
225- const { button, dropdown, overlay } = createPageActionsUI ( ) ;
226-
227- // Event listeners
228- button . addEventListener ( 'click' , ( e ) => {
229- e . stopPropagation ( ) ;
230- toggleDropdown ( button , dropdown , overlay ) ;
231- } ) ;
232-
233- overlay . addEventListener ( 'click' , ( ) => {
234- closeDropdown ( button , dropdown , overlay ) ;
235- } ) ;
236-
237- // Copy markdown action
238- document . getElementById ( 'action-copy-markdown' ) . addEventListener ( 'click' , async ( e ) => {
239- e . preventDefault ( ) ;
240- e . stopPropagation ( ) ;
241- await copyMarkdownToClipboard ( e . currentTarget ) ;
242- } ) ;
243-
244- // View markdown action
245- document . getElementById ( 'action-view-markdown' ) . addEventListener ( 'click' , ( e ) => {
246- e . preventDefault ( ) ;
247- e . stopPropagation ( ) ;
248- viewMarkdown ( ) ;
249- closeDropdown ( button , dropdown , overlay ) ;
250- } ) ;
251-
252- // Ask AI action (disabled for now)
253- document . getElementById ( 'action-ask-ai' ) . addEventListener ( 'click' , ( e ) => {
254- e . preventDefault ( ) ;
255- e . stopPropagation ( ) ;
256- // Future: Integrate with Ask AI feature
257- // For now, do nothing (disabled state)
258- } ) ;
259-
260- // Close on ESC key
261- document . addEventListener ( 'keydown' , ( e ) => {
262- if ( e . key === 'Escape' && dropdown . classList . contains ( 'active' ) ) {
263- closeDropdown ( button , dropdown , overlay ) ;
280+ ( async ( ) => {
281+ if ( ! shouldShowButton ( ) ) {
282+ return ;
264283 }
265- } ) ;
266284
267- // Close when clicking outside
268- document . addEventListener ( 'click' , ( e ) => {
269- if ( ! dropdown . contains ( e . target ) && ! button . contains ( e . target ) ) {
270- closeDropdown ( button , dropdown , overlay ) ;
285+ const markdownAvailable = await ensureMarkdownCached ( ) ;
286+ if ( ! markdownAvailable ) {
287+ return ;
271288 }
272- } ) ;
273289
274- // Prevent dropdown from closing when clicking inside
275- dropdown . addEventListener ( 'click' , ( e ) => {
276- // Only stop propagation if not clicking on a link
277- if ( ! e . target . closest ( '.page-action-link' ) ) {
278- e . stopPropagation ( ) ;
290+ const ui = createPageActionsUI ( ) ;
291+ if ( ! ui ) {
292+ return ;
279293 }
280- } ) ;
281294
282- // Close dropdown on link click (except for copy which handles itself)
283- dropdown . querySelectorAll ( '.page-action-link:not(#action-copy-markdown)' ) . forEach ( link => {
284- link . addEventListener ( 'click' , ( ) => {
285- if ( ! link . classList . contains ( 'disabled' ) ) {
286- setTimeout ( ( ) => {
287- closeDropdown ( button , dropdown , overlay ) ;
288- } , 100 ) ;
295+ const { button, dropdown, overlay } = ui ;
296+
297+ // Event listeners
298+ button . addEventListener ( 'click' , ( e ) => {
299+ e . stopPropagation ( ) ;
300+ toggleDropdown ( button , dropdown , overlay ) ;
301+ } ) ;
302+
303+ overlay . addEventListener ( 'click' , ( ) => {
304+ closeDropdown ( button , dropdown , overlay ) ;
305+ } ) ;
306+
307+ // Copy markdown action
308+ document . getElementById ( 'action-copy-markdown' ) . addEventListener ( 'click' , async ( e ) => {
309+ e . preventDefault ( ) ;
310+ e . stopPropagation ( ) ;
311+ await copyMarkdownToClipboard ( e . currentTarget ) ;
312+ } ) ;
313+
314+ // View markdown action
315+ document . getElementById ( 'action-view-markdown' ) . addEventListener ( 'click' , ( e ) => {
316+ e . preventDefault ( ) ;
317+ e . stopPropagation ( ) ;
318+ viewMarkdown ( ) ;
319+ closeDropdown ( button , dropdown , overlay ) ;
320+ } ) ;
321+
322+ // Ask AI action (disabled for now)
323+ document . getElementById ( 'action-ask-ai' ) . addEventListener ( 'click' , ( e ) => {
324+ e . preventDefault ( ) ;
325+ e . stopPropagation ( ) ;
326+ // Future: Integrate with Ask AI feature
327+ // For now, do nothing (disabled state)
328+ } ) ;
329+
330+ // Close on ESC key
331+ document . addEventListener ( 'keydown' , ( e ) => {
332+ if ( e . key === 'Escape' && dropdown . classList . contains ( 'active' ) ) {
333+ closeDropdown ( button , dropdown , overlay ) ;
289334 }
290335 } ) ;
291- } ) ;
292-
293- // Handle window resize
294- let resizeTimer ;
295- window . addEventListener ( 'resize' , ( ) => {
296- clearTimeout ( resizeTimer ) ;
297- resizeTimer = setTimeout ( ( ) => {
298- // Close dropdown on resize to prevent positioning issues
299- if ( dropdown . classList . contains ( 'active' ) ) {
336+
337+ // Close when clicking outside
338+ document . addEventListener ( 'click' , ( e ) => {
339+ if ( ! dropdown . contains ( e . target ) && ! button . contains ( e . target ) ) {
300340 closeDropdown ( button , dropdown , overlay ) ;
301341 }
302- } , 250 ) ;
303- } ) ;
342+ } ) ;
304343
305- // Accessibility: Focus management
306- button . addEventListener ( 'keydown' , ( e ) => {
307- if ( e . key === 'Enter' || e . key === ' ' ) {
308- e . preventDefault ( ) ;
309- toggleDropdown ( button , dropdown , overlay ) ;
344+ // Prevent dropdown from closing when clicking inside
345+ dropdown . addEventListener ( 'click' , ( e ) => {
346+ // Only stop propagation if not clicking on a link
347+ if ( ! e . target . closest ( '.page-action-link' ) ) {
348+ e . stopPropagation ( ) ;
349+ }
350+ } ) ;
310351
311- // Focus first menu item when opening
312- if ( dropdown . classList . contains ( 'active' ) ) {
313- const firstLink = dropdown . querySelector ( '.page-action-link:not(.disabled)' ) ;
314- if ( firstLink ) {
315- setTimeout ( ( ) => firstLink . focus ( ) , 100 ) ;
352+ // Close dropdown on link click (except for copy which handles itself)
353+ dropdown . querySelectorAll ( '.page-action-link:not(#action-copy-markdown)' ) . forEach ( link => {
354+ link . addEventListener ( 'click' , ( ) => {
355+ if ( ! link . classList . contains ( 'disabled' ) ) {
356+ setTimeout ( ( ) => {
357+ closeDropdown ( button , dropdown , overlay ) ;
358+ } , 100 ) ;
316359 }
317- }
318- }
319- } ) ;
360+ } ) ;
361+ } ) ;
320362
321- // Arrow key navigation within menu
322- dropdown . addEventListener ( 'keydown' , ( e ) => {
323- if ( ! dropdown . classList . contains ( 'active' ) ) return ;
363+ // Handle window resize
364+ let resizeTimer ;
365+ window . addEventListener ( 'resize' , ( ) => {
366+ clearTimeout ( resizeTimer ) ;
367+ resizeTimer = setTimeout ( ( ) => {
368+ // Close dropdown on resize to prevent positioning issues
369+ if ( dropdown . classList . contains ( 'active' ) ) {
370+ closeDropdown ( button , dropdown , overlay ) ;
371+ }
372+ } , 250 ) ;
373+ } ) ;
324374
325- const links = Array . from ( dropdown . querySelectorAll ( '.page-action-link:not(.disabled)' ) ) ;
326- const currentIndex = links . indexOf ( document . activeElement ) ;
375+ // Accessibility: Focus management
376+ button . addEventListener ( 'keydown' , ( e ) => {
377+ if ( e . key === 'Enter' || e . key === ' ' ) {
378+ e . preventDefault ( ) ;
379+ toggleDropdown ( button , dropdown , overlay ) ;
380+
381+ // Focus first menu item when opening
382+ if ( dropdown . classList . contains ( 'active' ) ) {
383+ const firstLink = dropdown . querySelector ( '.page-action-link:not(.disabled)' ) ;
384+ if ( firstLink ) {
385+ setTimeout ( ( ) => firstLink . focus ( ) , 100 ) ;
386+ }
387+ }
388+ }
389+ } ) ;
327390
328- if ( e . key === 'ArrowDown' ) {
329- e . preventDefault ( ) ;
330- const nextIndex = ( currentIndex + 1 ) % links . length ;
331- links [ nextIndex ] . focus ( ) ;
332- } else if ( e . key === 'ArrowUp' ) {
333- e . preventDefault ( ) ;
334- const prevIndex = ( currentIndex - 1 + links . length ) % links . length ;
335- links [ prevIndex ] . focus ( ) ;
336- } else if ( e . key === 'Home' ) {
337- e . preventDefault ( ) ;
338- links [ 0 ] . focus ( ) ;
339- } else if ( e . key === 'End' ) {
340- e . preventDefault ( ) ;
341- links [ links . length - 1 ] . focus ( ) ;
342- }
343- } ) ;
391+ // Arrow key navigation within menu
392+ dropdown . addEventListener ( 'keydown' , ( e ) => {
393+ if ( ! dropdown . classList . contains ( 'active' ) ) return ;
394+
395+ const links = Array . from ( dropdown . querySelectorAll ( '.page-action-link:not(.disabled)' ) ) ;
396+ const currentIndex = links . indexOf ( document . activeElement ) ;
397+
398+ if ( e . key === 'ArrowDown' ) {
399+ e . preventDefault ( ) ;
400+ const nextIndex = ( currentIndex + 1 ) % links . length ;
401+ links [ nextIndex ] . focus ( ) ;
402+ } else if ( e . key === 'ArrowUp' ) {
403+ e . preventDefault ( ) ;
404+ const prevIndex = ( currentIndex - 1 + links . length ) % links . length ;
405+ links [ prevIndex ] . focus ( ) ;
406+ } else if ( e . key === 'Home' ) {
407+ e . preventDefault ( ) ;
408+ links [ 0 ] . focus ( ) ;
409+ } else if ( e . key === 'End' ) {
410+ e . preventDefault ( ) ;
411+ links [ links . length - 1 ] . focus ( ) ;
412+ }
413+ } ) ;
344414
345- console . log ( 'Page Actions initialized for:' , getCurrentMarkdownPath ( ) ) ;
415+ console . log ( 'Page Actions initialized for:' , getCurrentMarkdownPath ( ) ) ;
416+ } ) ( ) ;
346417} ) ;
0 commit comments