/* 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 + ">(.*?)" + 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;
};