Source: user_interactions.js

/**
 * General file to control dynamic interactions with the user.
 * 
 * @author  HugoFara <Hugo.Farajallah@protonmail.com>
 * @license Unlicense <http://unlicense.org/>
 * @since   2.0.3-fork
 */

/**
 * Redirect the user to a specific page depending on the value
 */
function quickMenuRedirection(value) {
    const qm = document.getElementById('quickmenu');
    qm.selectedIndex = 0;
    if (value == '')
        return; 
    if (value == 'INFO') {
        top.location.href = 'docs/info.html';
    } else if (value == 'rss_import') {
        top.location.href = 'do_feeds.php?check_autoupdate=1';
    } else {
        top.location.href = value + '.php';
    }
}

/**
 * Create an interactable to add a new expression.
 * 
 * WARNING! This function was not properly tested!
 * 
 * @param {string[]} text         An array of words forming the expression 
 * @param {string}   attrs        A group of attributes to add 
 * @param {int}      length       Number of words, should correspond to WoWordCount
 * @param {string}   hex          Lowercase formatted version of the text.
 * @param {bool}     showallwords Set to false if a group if multi-words should be 
 *                                displayed as index
 * @returns {undefined}
 * 
 * @since 2.5.2-fork Don't hide multi-word index when inserting new multi-word. 
 */
function newExpressionInteractable(text, attrs, length, hex, showallwords) {

    const context = window.parent.document;
    for (key in text) {
        const words = $('span[id^="ID-'+ key +'-"]', context).not(".hide"); 
        const text_refresh = (
            words.attr('data_code') !== undefined 
            && words.attr('data_code') <= length);
        $('#ID-' + key + '-' + length, context).remove();
        let i = '';
        for (let j = length - 1; j > 0; j--) {
            if (j==1)
                i = '#ID-' + key + '-1';
            if ($('#ID-' + key + '-' + j, context).length) {
                i = '#ID-' + key + '-' + j;
                break;
            }
        }
        $(i, context)
        .before('<span id="ID-' + key + '-' + length + '"' + attrs + '>' 
        + text[key] + '</span>');
        const el = $('#ID-' + key + '-' + length, context);
        el.addClass('order' + key).attr('data_order', key);
        const txt = el
            .nextUntil(
                $('#ID-' + (parseInt(key) + length * 2 - 1) + '-1', context),
                '[id$="-1"]'
            )
            .map(function() {
                return $(this).text();
            })
            .get().join("");
        const pos = $('#ID-' + key + '-1', context).attr('data_pos');
        el.attr('data_text', txt).attr('data_pos', pos);
        if (!showallwords) {
            if (true || text_refresh) {
                //refresh_text(el);
            } else {
                el.addClass('hide');
            }
        }
    }
}


/** 
 * Prepare the interaction events with the text.
 * 
 * @since 2.0.3-fork
 */
function prepareTextInteractions() {
    $('.word').each(word_each_do_text_text);
    $('.mword').each(mword_each_do_text_text);
    $('.word').on('click', word_click_event_do_text_text);
    $('#thetext').on('selectstart','span',false).on(
        'mousedown','.wsty',
        {annotation: ANNOTATIONS_MODE}, 
        mword_drag_n_drop_select);
    $('#thetext').on('click', '.mword', mword_click_event_do_text_text);
    $('.word').on('dblclick', word_dblclick_event_do_text_text);
    $('#thetext').on('dblclick', '.mword', word_dblclick_event_do_text_text);
    $(document).on('keydown', keydown_event_do_text_text);
    $('#thetext').hoverIntent(
        {
            over: word_hover_over, 
            out: word_hover_out, 
            interval: 150, 
            selector:".wsty,.mwsty"
        }
    );
}


/** 
 * Scroll to a specific reading position
 * 
 * @since 2.0.3-fork
 */
function goToLastPosition() {
    // Last registered position to go to
    const lookPos = POS;
    // Position to scroll to
    let pos = 0;
    if (lookPos > 0) {
        let posObj = $(".wsty[data_pos=" + lookPos + "]").not(".hide").eq(0);
        if (posObj.attr("data_pos") === undefined) {
            pos = $(".wsty").not(".hide").filter(function() {
                return $(this).attr("data_pos") <= lookPos;
            }).eq(-1);
        }
    }
    $(document).scrollTo(pos);
    focus();
    setTimeout(overlib, 10);
    setTimeout(cClick, 100);
}


/**
 * Save the current reading position.
 * 
 * @param {int} text_id Text id
 * @param {int} position Position to save
 * 
 * @since 2.9.0-fork
 */
function saveReadingPosition(text_id, position) {
    $.post(
        'api.php/v1/texts/' + text_id + '/reading-position',
        { position: position }
    );
}


/**
 * Save audio position
 */
function saveAudioPosition(text_id, pos) {
    $.post(
        'api.php/v1/texts/' + text_id + '/audio-position',
        { position: pos }
    );
}

/**
 * Get the phonetic version of a text.
 * 
 * @param {string} text Text to convert to phonetics.
 * @param {string} lang Language, either two letters code or four letters (BCP 47)
 */
function getPhoneticText(text, lang) {
    let phoneticText;
    $.ajax(
        'api.php/v1/phonetic-reading',
        {
            async: false,
            data: {
                text: text,
                lang: lang 
            },
            dataType: "json",
            type: "GET",
        }
    )
    .done(
        function (data) {
            phoneticText = data["phonetic_reading"];
        }
    );
    return phoneticText;
}

/**
 * Get the phonetic version of a text, asynchronous.
 * 
 * @param {string} text Text to convert to phonetics.
 * @param {string} lang Language, either two letters code or four letters (BCP 47)
 */
async function getPhoneticTextAsync(text, lang) {
    return $.getJSON(
        'api.php/v1/phonetic-reading',
        {
            text: text, 
            lang: lang 
        }
    );
}

/**
 * Read a text aloud, only work with a phonetic version.
 * 
 * @param {string} text  Text to read, won't be parsed further.
 * @param {string} lang  Language code with BCP 47 convention  
 *                       (e. g. "en-US" for English with an American accent) 
 * @param {number} rate  Reading rate 
 * @param {number} pitch Pitch value 
 * 
 * @return {SpeechSynthesisUtterance} The spoken message object
 * 
 * @since 2.9.0 Accepts "voice" as a new optional argument
 */
 function readRawTextAloud(text, lang, rate, pitch, voice) {
    let msg = new SpeechSynthesisUtterance();
    const trimmed = lang.substring(0, 2);
    const prefix = 'tts[' + trimmed;
    msg.text = text;
    if (lang) {
        msg.lang = lang;
    }
    // Voice is a string but we have to assign a SpeechSynthesysVoice
    const useVoice = voice || getCookie(prefix + 'Voice]');
    if (useVoice) {
        const voices = window.speechSynthesis.getVoices();
        for (let i = 0; i < voices.length; i++) {
            if (voices[i].name === useVoice) {
                msg.voice = voices[i];
            }
          }
    }
    if (rate) {
        msg.rate = rate;
    } else if (getCookie(prefix + 'Rate]')) {
        msg.rate = parseInt(getCookie(prefix + 'Rate]'), 10);
    }
    if (pitch) {
        msg.pitch = pitch;
    } else if (getCookie(prefix + 'Pitch]')) {
        msg.pitch = parseInt(getCookie(prefix + 'Pitch]'), 10);
    }
    window.speechSynthesis.speak(msg);
    return msg;
}

/**
 * Read a text aloud, may parse the text to get a phonetic version.
 * 
 * @param {string} text   Text to read, do not need to be phonetic
 * @param {string} lang   Language code with BCP 47 convention  
 *                        (e. g. "en-US" for English with an American accent) 
 * @param {number} rate   Reading rate 
 * @param {number} pitch  Pitch value
 * @param {string} voice Optional voice, the result will depend on the browser used
 * 
 * @since 2.9.0 Accepts "voice" as a new optional argument
 */
function readTextAloud(text, lang, rate, pitch, voice) {
    if (lang.startsWith('ja')) {
        getPhoneticTextAsync(text, lang)
            .then(
                function (data) {
                    readRawTextAloud(
                        data.phonetic_reading, lang, rate, pitch, voice
                    );
                }
            );
    } else {
        readRawTextAloud(text, lang, rate, pitch, voice);
    }
}