@@ -33,6 +33,22 @@ const sections = {};
3333// Build available commands from config
3434const availableCommands = [ 'home' , ...config . sections . map ( s => s . id ) , 'help' , 'download' , 'clear' ] ;
3535
36+ // Helper: escapeHtml and conservative linkify for help text only
37+ function escapeHtml ( str ) {
38+ return String ( str )
39+ . replace ( / & / g, '&' )
40+ . replace ( / < / g, '<' )
41+ . replace ( / > / g, '>' )
42+ . replace ( / " / g, '"' )
43+ . replace ( / ' / g, ''' ) ;
44+ }
45+
46+ function linkify ( text ) {
47+ const escaped = escapeHtml ( text ) ;
48+ const withLinks = escaped . replace ( / ( h t t p s ? : \/ \/ [ ^ \s < ] + ) / g, '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>' ) ;
49+ return withLinks . replace ( / \n / g, '<br>' ) ;
50+ }
51+
3652// Generate welcome message with dynamic padding
3753function generateWelcomeMessage ( ) {
3854 const title = config . conference . title || 'Conference' ;
@@ -367,7 +383,7 @@ Contact: ${config.contact.email || 'N/A'}
367383Website: ${ config . contact . website || 'N/A' }
368384${ config . contact . repository ? `Code base: ${ config . contact . repository } ` : '' }
369385` ;
370- printOutput ( helpText , 'help-text' ) ;
386+ printHelpText ( helpText ) ;
371387 contentDisplay . innerHTML = '' ;
372388 contentDisplay . classList . add ( 'hidden' ) ;
373389}
@@ -503,6 +519,15 @@ function printOutput(text, className = '') {
503519 output . appendChild ( outputDiv ) ;
504520}
505521
522+ // Print help (sanitized, linkified)
523+ function printHelpText ( rawText ) {
524+ const out = document . getElementById ( 'output' ) ;
525+ const block = document . createElement ( 'div' ) ;
526+ block . className = 'output-block help-text' ;
527+ block . innerHTML = linkify ( rawText ) ;
528+ out . appendChild ( block ) ;
529+ }
530+
506531// Scroll to bottom
507532function scrollToBottom ( ) {
508533 setTimeout ( ( ) => {
@@ -551,14 +576,27 @@ window.addEventListener('hashchange', async () => {
551576 }
552577} ) ;
553578
554- // Keep input focused
579+ // Keep input focused when clicking outside interactive areas, but don't steal focus/selection
555580document . addEventListener ( 'click' , ( e ) => {
556- if ( ! e . target . closest ( '#theme-menu' ) &&
557- ! e . target . closest ( '#theme-toggle' ) &&
558- ! e . target . closest ( '.top-nav' ) &&
559- ! e . target . closest ( '#content-display' ) ) {
560- input . focus ( ) ;
581+ // If user clicked inside theme controls, top nav, content display or output, don't force-focus input.
582+ if ( e . target . closest ( '#theme-menu' ) ||
583+ e . target . closest ( '#theme-toggle' ) ||
584+ e . target . closest ( '.top-nav' ) ||
585+ e . target . closest ( '#content-display' ) ||
586+ e . target . closest ( '#output' ) ) {
587+ return ;
588+ }
589+ const cmd = document . getElementById ( 'command-input' ) ;
590+ if ( cmd ) cmd . focus ( ) ;
591+ } ) ;
592+
593+ // Defensive: do not focus input on mousedown (this would steal selection)
594+ document . addEventListener ( 'mousedown' , ( e ) => {
595+ if ( e . target . closest ( '#content-display' ) || e . target . closest ( '#output' ) ) {
596+ // allow native selection to proceed — do not change focus
597+ return ;
561598 }
599+ // otherwise no-op here; click handler will focus on click
562600} ) ;
563601
564602// Make input more obvious - pulse animation
0 commit comments