/* eslint-disable no-unused-vars */ /* global INDEX_URL */ /* global fetch */ /* global dictionary */ let g_clear_msg_timeouts = {}; // A global object to store timeout IDs let g_last_msg_content = {}; // To prevent 'blinks' in case the same msg is sent // // Helpers // const is_mobile = () => { const has_touch = window.matchMedia( '(pointer: coarse)' ).matches; const is_small_screen = window.matchMedia( '(max-width: 999px)' ).matches; return has_touch || is_small_screen; }; const scroll_to_top = () => { window.scrollTo( { top: 0, behavior: 'smooth' } ); }; const scroll_to_id = ( id ) => { const $target = document.getElementById( id ); if ( !$target ) { return; } const rect = $target.getBoundingClientRect(); const space = is_mobile() ? 100 : 110; const scroll = rect.top + window.scrollY - space; window.scrollTo({ top: scroll, behavior: 'smooth' }); }; const shuffle = ( array ) => { let currentIndex = array.length; let temporaryValue; let randomIndex; while ( currentIndex !== 0 ) { randomIndex = Math.floor( Math.random() * currentIndex ); currentIndex -= 1; temporaryValue = array[ currentIndex ]; array[ currentIndex ] = array[ randomIndex ]; array[ randomIndex ] = temporaryValue; } return array; }; // // Translate // const parse_tags = ( str, tag ) => { if ( !str ) { return []; } const regex = new RegExp( "<" + tag + ">(.*?)", "g" ); const matches = [ ...str.matchAll( regex ) ]; if ( matches.length === 0 ) { return []; } return matches.map( match => match[ 1 ] ); }; const translate = ( str ) => { if ( !str ) return ''; const matches = parse_tags( str , 't' ); if ( matches.length > 0 ) { for ( const part of matches ) { const tr_part = translate( part ); str = str.replace( `${ part }` , tr_part ); } return str; } if ( !dictionary || typeof dictionary !== 'object' ) { return str; } return dictionary[ str ] || str; }; // // Display // function refresh_msg_timeout( id , show_time ) { if ( g_clear_msg_timeouts[id] ) { clearTimeout( g_clear_msg_timeouts[id] ); } const q_show_time = parseInt( show_time ) || 0; if ( q_show_time > 0 ) { g_clear_msg_timeouts[id] = setTimeout( () => { clear_msg( id ); g_last_msg_content[id] = ''; } , q_show_time * 1000 ); } } const show = ( el ) => { if ( !el ) return; if ( typeof el !== 'string' ) { el.classList.remove( 'hidden' ); return; } if ( el.startsWith( '.' ) ) { document.querySelectorAll( el ).forEach( $item => { $item.classList.remove( 'hidden' ); }); return; } const $target = document.getElementById( el ); if ( $target ) { $target.classList.remove( 'hidden' ); } }; const hide = ( el ) => { if ( !el ) return; if ( typeof el !== 'string' ) { el.classList.add( 'hidden' ); return; } if ( el.startsWith( '.' ) ) { document.querySelectorAll( el ).forEach( $item => { $item.classList.add( 'hidden' ); }); return; } const $target = document.getElementById( el ); if ( $target ) { $target.classList.add( 'hidden' ); } }; const display_if = ( el, condition ) => { if ( !el ) return; condition ? show( el ) : hide( el ); }; function display_msg( msg , id , show_time = 0 , icon = '' , scroll_id = '' ) { msg = translate( msg ); if ( icon != '' ) { msg = "" + msg; } if ( g_last_msg_content[id] === msg && $('#'+id).is(':visible') ) { refresh_msg_timeout( id , show_time ); return; } g_last_msg_content[id] = msg; $( '#' + id ).html( msg ); $( '#' + id ).show( 200 ); if ( scroll_id == 'top' ) { scroll_to_top(); } else if ( scroll_id != '' ) { scroll_to_id( scroll_id ); } refresh_msg_timeout( id , show_time ); } function display_err_msg( msg , id , show_time = 0 , scroll_id = '' ) { console.log( 'Displayed error: ' + msg ); display_msg( msg , id , show_time , "fa-exclamation-triangle red" , scroll_id ); } function display_ok_msg( msg , id , show_time = 0 , scroll_id = '' ) { display_msg( msg , id , show_time , "fa-check green" , scroll_id ); } function clear_msg ( id, timeout ) { const delay = ( timeout === undefined ) ? 200 : timeout; const $el = $(`#${id}`); $el.stop( true, true ).hide( delay ); if ( $.trim( $el.html() ) !== '' ) { if ( delay === 0 ) { $el.html( '' ); } else { $el.promise().done( () => { $el.html( '' ); }); } } } const ask_confirm = ( box_id, message ) => { return new Promise( ( resolve ) => { const $box = document.getElementById( box_id ); if ( !$box ) { resolve( confirm( message ) ); return; } const $text = $box.querySelector( '.confirm-text' ); const $btn_yes = $box.querySelector( '.btn-yes' ); const $btn_no = $box.querySelector( '.btn-no' ); if ( !$text || !$btn_yes || !$btn_no ) { resolve( confirm( message ) ); return; } const cleanup = () => { hide( box_id ); $btn_yes.onclick = null; $btn_no.onclick = null; window.removeEventListener( 'keydown', handle_escape ); }; const handle_choice = ( choice ) => { cleanup(); resolve( choice ); }; const handle_escape = ( e ) => { if ( e.key === 'Escape' ) { handle_choice( false ); } }; $text.textContent = message; $btn_yes.onclick = () => handle_choice( true ); $btn_no.onclick = () => handle_choice( false ); window.addEventListener( 'keydown', handle_escape ); show( box_id ); }); }; function dompurify_sanitize( str ) { // // DOMPurify is available and working // if ( typeof DOMPurify !== 'undefined' && typeof DOMPurify.sanitize === 'function' ) { // // Table elements are not included as we usually inject only parts of a table, that dompurify fliters out anyhow // const dom_purify_config = { ALLOWED_TAGS: [ 'h1' , 'h2' , 'h3' , 'p' , 'ul' , 'ol' , 'li' , 'div' , 'br' , 'a' , 'img' , 'b' , 'span' ], ALLOWED_ATTR: [ 'href' , 'src' , 'alt' , 'title' , 'class' , 'id' ], ADD_ATTR: [ /^data-.*/ ], ALLOWED_URI_REGEXP: /^(https?):/ }; return DOMPurify.sanitize( str , dom_purify_config ); } // // Case dompurify does not function well on the user's browser. // else { console.warn('DOMPurify not available — using raw text'); return str; } } // // Ajax // const ajax = async ( params, success = () => {} ) => { const retry_limit = 3; let try_count = 0; let last_error = null; while ( try_count <= retry_limit ) { try { const response = await fetch( INDEX_URL, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' // , 'x-ajax-request': 'True' }, body: params }); if ( response.status === 403 ) { const text = await response.text(); console.log('403 response:', text); alert( translate( 'A block was executed by the firewall – please refresh the page' ) ); return; } const data = await response.json().catch( () => { throw new Error( 'parsererror' ); }); if ( data !== null && data !== undefined ) { try { success( data ); } catch ( callback_error ) { console.error( 'Critical Logic Error in Callback:' , callback_error ); } return data; } if ( !response.ok ) { throw new Error( `HTTP Error: ${ response.status }` ); } throw new Error( `Invalid JSON format: ${ data }` ); } catch ( error ) { last_error = error; if ( error.message === 'parsererror' ) { break; } try_count++; if ( try_count <= retry_limit ) { await new Promise( resolve => setTimeout( resolve, 200 ) ); continue; } } } const error_res = { success: false, error: last_error?.message === 'parsererror' ? translate( `The server returned an invalid response (not JSON): Params: ${ params }` ) : ( last_error?.message || 'Network Error' ) }; if ( last_error?.message === 'parsererror' ) { console.log( 'ERROR : Server returned non-JSON response' ); } else { console.log( `ERROR : Ajax failed after ${ try_count } retries: ${ last_error?.message }` ); } success( error_res ); return error_res; };