Reslator - LNMTL Plugin

6/1/2020, 1:51:50 PM

// ==UserScript==
// @name        Reslator - LNMTL Plugin
// @author      Spawner
// @version     1.2.7
// @namespace   reader_translators
// @match       https://lnmtl.com/chapter/*
// @grant       GM_xmlhttpRequest
// @grant       GM_addStyle
// @grant       GM_setValue
// @grant       GM_getValue
// @license     MIT
// @run-at      document-start
// @connect     fanyi.sogou.com
// @connect     fanyi.baidu.com
// @connect     test.niutrans.vip
// @connect     translate.googleapis.com
// @connect     fanyi.yeekit.com
// @require     http://code.jquery.com/jquery-3.4.1.min.js
// @description 6/1/2020, 1:51:50 PM
// ==/UserScript==

/*

        | Plugin      : Reslator
        | Version     : 1.2.7
        | Author      : Spawner

        | Description : Reader & Translator, which gives the user a better experience.


        CHANGELOG :

        1.0     -   Initial release
        
        1.1     -   Add Glossary support
        
        1.2     -   + Fixed : Word higlighter 
                    + Raw on click delay
        
        1.2.1   -   Fixed comments section
          
        1.2.2   -   + Improve performance 
                    + Fixing a small issue in the glossary
        
        1.2.3   -   + Performance improvement
                    + Add secondary theme switcher 
                    + Save raw/original text state
        
        1.2.4   -   Improve baidu speed + Fix translation display
        
        1.2.5   -   Fix niutrans API
          
        1.2.6   -   Added a new provider
        
        1.2.7   -   + Navigation between chapters has become much smoother.
                    + Changing the main font of the reader for a better a better one.
                    + Adding a font size controller.
                    + Fixed font delay problem.
*/

let iframeHandler;

let uniqueWords             = new Map();

let autoThemeState          = GM_SuperValue.get( 'autoThemeState' , false );
let autoReaderState         = GM_SuperValue.get( 'autoReaderState', false );
let autoRawState            = GM_SuperValue.get( 'autoRawState'   , false );

let providerOriginalState   = GM_SuperValue.get( 'providerOriginalState', false );
let providerGoogleState     = GM_SuperValue.get( 'providerGoogleState'  , false );
let providerSogouState      = GM_SuperValue.get( 'providerSogouState'   , false );
let providerNiutransState   = GM_SuperValue.get( 'providerNiutransState', false );
let providerBaiduState      = GM_SuperValue.get( 'providerBaiduState'   , false );
let providerYeekitState     = GM_SuperValue.get( 'providerYeekitState'  , false );

let secondaryTheme          = GM_SuperValue.get( 'secondaryTheme'       , "" );
let fontSizeValue           = GM_SuperValue.get( 'fontSizeValue'        , "" );
let fontSizeValueDefault    = GM_SuperValue.get( 'fontSizeValueDefault' , "" );

const providers             = [ "Google", "Sogou", "Niutrans", "Baidu", "Yeekit" ];

let isTranslated    =
{
    Google    : [providerGoogleState  , false],
    Niutrans  : [providerNiutransState, false],
    Sogou     : [providerSogouState   , false],
    Baidu     : [providerBaiduState   , false],
    Yeekit    : [providerYeekitState  , false]
};

const providerObject =
{
    Google:
    {
        maxSize   : 5000,
        timeout   : 0,
        color     : ['48, 175, 219', 0.04]
    },
    Niutrans:
    {
        maxSize   : 4000,
        timeout   : 400,
        color     : ['235, 77, 75', 0.06]
    },
    Sogou:
    {
        maxSize   : 4000,
        timeout   : 1700,
        color     : ['255, 164, 58', 0.06]
    },
    Baidu:
    {
        maxSize   : 2000,
        timeout   : 30,
        color     : ['140, 122, 230', 0.1]
    },
    Yeekit:
    {
        maxSize   : 4000,
        timeout   : 500,
        color     : ['120, 224, 143', 0.1]
    },    
    DeepL:
    {
        maxSize   : 4000,
        timeout   : 500,
        color     : ['75, 75, 75', 0.1]
    },
}

/**
     *   Show text
     *
     *   If no translation is enabled, show the original text
     *   otherwise hide it.
*/
if( !providerGoogleState && !providerSogouState && !providerNiutransState && !providerBaiduState )
    providerOriginalState = true;

if( fontSizeValue === "" || fontSizeValueDefault === "" )
{
    fontSizeValue = "20"; 
    fontSizeValueDefault = "19";
}

/**
     *   Apply the theme properly.
     *
     *   The reason to set the display as 'none' and opacity as '0'
     *   is to avoid the iframe onload screen flash, we also need to hide the scrollbar
     *   since we will be using the iframe one.
*/
if ( autoReaderState )
{
    GM_addStyle(
        `
        html
        {
            background-color  : ${autoThemeState ? '#E9E9E9' : '#25282F'};
        }
        #app
        {
            display           : none;
            opacity           : 0;
            overflow-y        : hidden;
        }
      `
    );
}

/*
    *   Initializing our CSS Styles
    *
    *   The iframe ( #pageContainer ) needs to be created at document-start
    *   to avoid any performance issues.
*/
GM_addStyle( `
    #pageContainer, #pagePreload
    {
        font-family     : Open sans;
        position        : fixed;
        top             : 0px;
        left            : 0px;
        overflow-y      : scroll;
        width           : 100%;
        height          : 100%;
        display         : block;
        border-width    : 0px;
    }
`);

/*  *   Global variables for theme & settings Colors
    *
    *   Using the :root selector to easily
    *   switch between colors.
*/
let CSSRoot = `
    :root
    {
        --background-color          : #E9E9E9;
        --close-btn-back-color      : #fff;
        --close-btn-fore-color      : #000;
        --title-color               : #000;
        --chapter-color             : #000;
        --raw-color                 : #424242;
        --settings-t-clicked-color  : #E9E9E9;
        --settings-label-color      : rgba(51, 47, 53, 0.4);
        --settings-check-back-color : linear-gradient(to right, #000000, #434343);

        --checked-background        : #029992;
        --checked-background-rgb    : 2, 153, 146;
    }

    [data-theme="dark"]
    {
        --background-color          : #25282F;
        --close-btn-back-color      : #000;
        --close-btn-fore-color      : #fff;
        --title-color               : #fff;
        --chapter-color             : #E9E9E9;
        --raw-color                 : #424242;
        --settings-t-clicked-color  : #25282F;
        --settings-label-color      : #E9E9E9;
        --settings-check-back-color : #E9E9E847;

        --checked-background        : #029992;
        --checked-background-rgb    : 2, 153, 146;
    }

    [data-sec-theme="gold"]
    {
        --checked-background        : #F39B3A;
        --checked-background-rgb    : 243, 155, 58;
    }

    [data-sec-theme="moon"]
    {
        --checked-background        : #9c88ff;
        --checked-background-rgb    : 156, 136, 255;
    }

    [data-sec-theme="bomb"]
    {
        --checked-background        : #eb4d4b;
        --checked-background-rgb    : 235, 77, 75;
    }

    [data-sec-theme="default"]
    {
        --checked-background        : #029992;
        --checked-background-rgb    : 2, 153, 146;
    }
`;

/*  *  Our iframe body styles
    *
    *  Note : To avoid the scroll flickering issue we will
    *  be using will-change : transform as a temporary solution.
*/
let CSSContainer = `

    html
    {
        background-color    : var(--background-color);
    }

    @keyframes fadein
    {
        from
        {
            opacity        : 0;
        }
        to
        {
            opacity        : 1;
        }
    }

    .chapterBody
    {
        position          : relative    !important;
        font-family       : "Source Sans Pro" !important;
        text-align        : justify     !important;
        animation         : fadein 3s;
        will-change       : transform;
        overflow          : visible;
        color             : var(--chapter-color);
        max-width         : 72%;
        min-height        : 100%;
        margin            : 0 auto;
        padding-bottom    : 80px;
    }

    .pageContainerBackground
    {
        animation         : fadein 1s;
        min-height        : 100%;
    }

    .pageContainerSettings
    {
        transition        : all 0.35s ease-in-out;
        background-color  : transparent !important;
        margin            : auto;
    }

    .pageSplit
    {
        animation         : fadein 3s;
        border-bottom     : 1px solid rgba(var(--checked-background-rgb), 0.3)  !important;
        /*margin            : 0px 0px 10px;*/
    }

    h1.header-title
    {
        color             : var(--checked-background);
        animation         : fadein 3s;
        font-family       : Poppins;
        font-weight       : 300;
        font-size         : 2.25em;
        text-align        : center;
        padding-bottom    : .3em;
        line-height       : 1.2;
        margin            : 3em auto .2em;
        color             : var(--title-color);
        text-shadow       : 0 0 4px rgba(0, 0, 100,.5);
    }
`;

let CSSForm = `

    .settingsForm
    {
        will-change       : transform;
        position          : relative;
        justify-content   : center;
        margin-left       : -16;
        margin-top        : 20;
        flex-wrap         : wrap;
    }

    form
    {
        font-size         : 13px;
        letter-spacing    : .2em;
        font-family       : sans-serif;
        display           : flex;
        margin-top        : 0em;
    }

    label
    {
        user-select       : none;
        color             : var(--settings-label-color);
        text-transform    : uppercase;
        font-family       : sans-serif;
        display           : flex;
        font-weight       : bold;
        padding           : 7px 16px;
        /* margin-right   : -4px; */
    }

    input[type=checkbox],
    input[type=radio]
    {
        color             : #000;
        position          : normal;
        visibility        : hidden;
        display           : none;
    }

    input[type=checkbox][value=o-translator-p]:checked+label
    {
        background        : #fff;
        cursor            : pointer;
    }
    input[type=checkbox][value=o-translator-p]:hover+label
    {
        color             : #000;
        cursor            : pointer;
    }

    input[type=checkbox][value=g-translator-p]:checked+label
    {
        color             : #fff;
        background        : var(--settings-check-back-color);
        cursor            : pointer;
    }

    input[type=checkbox][value=s-translator-p]:checked+label
    {
        color             : #fff;
        background        : var(--settings-check-back-color);
        cursor            : pointer;
    }

    input[type=checkbox][value=n-translator-p]:checked+label
    {
        color             : #fff;
        background        : var(--settings-check-back-color);
        cursor            : pointer;
    }

    input[type=checkbox][value=b-translator-p]:checked+label
    {
        color             : #fff;
        background        : var(--settings-check-back-color);
        cursor            : pointer;
    }

    input[type=checkbox][value=y-translator-p]:checked+label
    {
        color             : #fff;
        background        : var(--settings-check-back-color);
        cursor            : pointer;
    }

    input[type=checkbox][name=g-translator-p]:checked+label,
    input[type=radio][name=g-translator-p]:checked+label
    {
        color             : #fff;
        background        : #000;
        cursor            : pointer;
    }

    input[type=checkbox]+label:hover,
    input[type=radio]+label:hover
    {
        /* transition     : all 0.25s ease-in-out; */
        color             : #fff;
        cursor            : pointer;
    }

    input[type=checkbox]+label,
    input[type=radio]+label
    {
        transition        : all 0.15s cubic-bezier(0.4, 0, 0.6, 1) 0s;
        background        : var(--settings-t-clicked-color);
        color             : #545454;
    }

    input[type=radio][value=raw]:checked+label
    {
        color             : #fff;
        background        : #7DD7FB;
        cursor            : default;
    }

    input[type=radio][value=mode-enabled]:checked+label
    {
        transition        : all 200ms ease;
        background        : var(--checked-background);
        color             : #fff;
        cursor            : default;
    }

    input[type=radio][value=mode-disabled]:checked+label
    {
        background        : #25282F;
        color             : #fff;
        cursor            : default;
    }

    input[type=radio][value=theme-enabled]:checked+label
    {
        background        : #fff;
        color             : #000;
        cursor            : default;
    }

    input[type=radio][value=theme-disabled]:checked+label
    {
        background        : #000;
        color             : #fff;
        cursor            : default;
    }

    .radio-group,
    .checkbox-group
    {
        font-size         : 9px;
        letter-spacing    : .2em;
        border            : solid 0px rgb(125, 214, 255);
        display           : flex;
        margin            : 0px 0px 30px;
        border-radius     : 20px;
        overflow          : hidden;
        box-shadow        : rgba(0, 0, 0, 0.3) 0px 8px 16px 0px;
        opacity           : 0.7;
    }

    .radio-group:hover,
    .checkbox-group
    {
        opacity           : 1;
        transition        : 1.0s;
    }
`;

let CSSChapterBody = `

    .sentence
    {
              animation         : fadein 2s;
    }
    .sentence.or
    {
        margin-block-end: -0.5em !important;
        word-spacing      : 1px;
        text-align        : initial;
        font-weight       : 300;
        line-height       : 1.7;
        margin-top        : 3.1em;
        font-size         : 20px;
    }

    .sentence.google.default
    {
        padding           : 0px;
        background        : transparent;
        border-left-style : none;
        word-spacing      : 1px;
        text-align        : initial;
        font-weight       : 300;
        line-height       : 1.7;
        margin-top        : 3.1em;
        font-size         : 20px;
    }

    .sentence.sogou.default
    {
        padding           : 0px;
        background        : transparent;
        border-left-style : none;
        word-spacing      : 1px;
        text-align        : initial;
        font-weight       : 300;
        line-height       : 1.7;
        margin-top        : 3.1em;
        font-size         : 20px;
    }

    .sentence.niutrans.default
    {
        padding           : 0px;
        background        : transparent;
        border-left-style : none;
        word-spacing      : 1px;
        text-align        : initial;
        font-weight       : 300;
        line-height       : 1.7;
        margin-top        : 3.1em;
        font-size         : 20px;
    }

    .sentence.baidu.default
    {
        padding           : 0px;
        background        : transparent;
        border-left-style : none;
        word-spacing      : 1px;
        text-align        : initial;
        font-weight       : 300;
        line-height       : 1.7;
        margin-top        : 3.1em;
        font-size         : 20px;
    }

    .sentence.yeekit
    {
        margin-block-end: -0.5em !important;
        word-spacing      : 1px;
        text-align        : initial;
        animation         : fadein 0.4s;
        line-height       : 1.7;
        font-size         : 17px;
        border-left-style : solid;
        border-color      : transparent transparent transparent rgb(${providerObject["Yeekit"]["color"][0]});
        padding           : 10 10px;
        background        : rgba(${providerObject["Yeekit"]["color"][0]}, ${providerObject["Yeekit"]["color"][1]});
        margin            : 25 0;
    }

    .sentence.yeekit.default
    {
        padding           : 0px;
        background        : transparent;
        border-left-style : none;
        word-spacing      : 1px;
        text-align        : initial;
        font-weight       : 300;
        line-height       : 1.7;
        margin-top        : 3.1em;
        font-size         : 20px;
    }

    .sentence.sogou
    {
        margin-block-end: -0.5em !important;
        word-spacing      : 1px;
        text-align        : initial;
        animation         : fadein 1s;
        line-height       : 1.7;
        font-size         : 17px;
        border-left-style : solid;
        border-color      : transparent transparent transparent rgb(${providerObject["Sogou"]["color"][0]});
        padding           : 10 10px;
        background        : rgba(${providerObject["Sogou"]["color"][0]}, ${providerObject["Sogou"]["color"][1]});
        margin            : 25 0;
    }

    .sentence.google
    {
        margin-block-end: -0.5em !important;
        word-spacing      : 1px;
        text-align        : initial;
        animation         : fadein 1s;
        line-height       : 1.7;
        font-size         : 18px;
        border-left-style : solid;
        border-color      : transparent transparent transparent rgb(${providerObject["Google"]["color"][0]});
        padding           : 10 10px;
        background        : rgba(${providerObject["Google"]["color"][0]}, ${providerObject["Google"]["color"][1]});
        margin            : 25 0;
    }

    .sentence.niutrans
    {
        margin-block-end: -0.5em !important;
        animation         : fadein 1s;
        line-height       : 1.7;
        font-size         : 18px;
        border-left-style : solid;
        border-color      : transparent transparent transparent rgb(${providerObject["Niutrans"]["color"][0]});
        padding           : 10 10px;
        background        : rgba(${providerObject["Niutrans"]["color"][0]}, ${providerObject["Niutrans"]["color"][1]});
        margin            : 25 0;
    }

    .sentence.baidu
    {
        margin-block-end: -0.5em !important;
        word-spacing      : 1px;
        text-align        : initial;
        animation         : fadein 1s;
        line-height       : 1.7;
        font-size         : 18px;
        border-left-style : solid;
        border-color      : transparent transparent transparent rgb(${providerObject["Baidu"]["color"][0]});
        padding           : 10 10px;
        background        : rgba(${providerObject["Baidu"]["color"][0]}, ${providerObject["Baidu"]["color"][1]});
        margin            : 25 0;
    }

    .sentence.deepl
    {
        margin-block-end: -0.5em !important;
        word-spacing      : 1px;
        text-align        : initial;
        animation         : fadein 1s;
        line-height       : 1.7;
        font-size         : 18px;
        border-left-style : solid;
        border-color      : transparent transparent transparent rgb(${providerObject["DeepL"]["color"][0]});
        padding           : 10 10px;
        background        : rgba(${providerObject["DeepL"]["color"][0]}, ${providerObject["DeepL"]["color"][1]});
        margin            : 25 0;
    }

    .sentence.edit 
    {
        overflow: hidden;
        background: none;
        outline: none;
        resize: none;
        margin-block-end: -0.5em !important;
        word-spacing: 1px;
        text-align: initial;
        animation: fadein 0.5s;
        line-height: 1.7;
        font-size: 18px;
        border-left-style: solid;
        padding: 10 20px;
        margin: 25 0;
        background-color: var(--background-color);
        box-shadow: 0 10px 10px 0 rgba(0, 0, 0, 0.1), 0 0 1px 0 rgba(0, 0, 0, 0.2);
        color: var(--title-color);
        border-radius: 13px;
        border-left-color: var(--checked-background);
    }

    .original
    {
        display           : inline;
        word-spacing      : 1px;
        text-align        : initial;
        font-weight       : 600;
        font-size         : 19px;
        /*margin-bottom     : 3.8em;*/
        color             : var(--checked-background);
    }

    .highlighter
    {
        font-weight       : 500;
        /*animation         : fadein 1s;*/
    }
`;

let CSSTooltip = `
    .tooltip
    {
        position          : relative;
        display           : inline;
    }

    .tooltip .tooltip-text
    {
        visibility        : hidden;
        width             : 120px;
        background-color  : #555;
        color             : #fff;
        text-align        : center;
        /* border-radius  : 6px; */
        padding           : 3px 0;
        position          : absolute;
        z-index           : 1;
        opacity           : 0;
        transition        : opacity 0.3s;
    }

    /* Tooltip top content */
    .top .tooltip-text
    {
        bottom            : 100%;
        left              : 50%;
        margin-left       : -60px; /* 120/2 = 60 */
    }

    .tooltip .tooltip-text::after
    {
        content           : "";
        position          : absolute;
        border-width      : 5px;
        border-style      : solid;
    }

    /* Tooltip top arrow */
    .top .tooltip-text::after
    {
        margin-left       : -5px;
        left              : 50%;
        top               : 100%;
        border-color      : var(--checked-background) transparent transparent transparent;
    }

    .tooltip:hover .tooltip-text
    {
        visibility        : visible;
        opacity           : 1;
    }

    .highlighter-default
    {
          
        /* animation         : fadein 1s; */
        /* font-weight       : 600; */
    }
`;

let CSSNextPrevArrow = `

    .arrow
    {
        position          : fixed;
        top               : 90%;
        width             : 4vmin;
        height            : 4vmin;
        background        : transparent;
        border-top        : 1vmin solid gray;
        border-right      : 1vmin solid black;
        box-shadow        : 0 0 0 black;
        transition        : all 200ms ease;
        opacity           : 0.4;
    }

    .arrow.left
    {
        cursor            : pointer;
        /* left           : 55px; */
        right             : 3%;
        top               : 88%;
        transform         : translate3d(0, -50%, 0) rotate(-135deg);
    }

    .arrow.right
    {
        cursor            : pointer;
        right             : 4%;
        top               : 78%;
        transform         : translate3d(0,-50%,0) rotate(45deg);
    }

    .arrow:hover
    {
        border-color      : #2d3436;
        box-shadow        : 0.5vmin -0.5vmin 0 white;
        opacity           : 1;
    }

    .arrow: before
    {
        content           : '';
        position          : absolute;
        top               : 50%;
        left              : 50%;
        transform         : translate(-40%,-60%) rotate(45deg);
        width             : 200%;
        height            : 200%;
    }
`;

let CSSCloseBtn =
    `
    .close-button
    {
        box-shadow        : rgba(0, 0, 0, 0.4) 0px 8px 16px 0px;
        width             : 5vmin;
        height            : 5vmin;
        box-shadow        : 0px 10 10px 10px rgba(0, 0, 0, 0.25);
        border-radius     : 50%;
        background        : var(--close-btn-back-color);
        position          : fixed;
        right             : 3.6%;
        top               : 67%;
        display           : block;
        z-index           : 200;
        text-indent       : -9999px;
        cursor            : pointer;
    }

    .close-button:before,
    .close-button:after
    {
        content           : '';
        width             : 55%;
        height            : 2px;
        background        : var(--close-btn-fore-color);
        position          : absolute;
        top               : 48%;
        left              : 22%;
        -webkit-transform : rotate(-45deg);
        -moz-transform    : rotate(-45deg);
        -ms-transform     : rotate(-45deg);
        -o-transform      : rotate(-45deg);
        transform         : rotate(-45deg);
        -webkit-transition: all 0.3s ease-out;
        -moz-transition   : all 0.3s ease-out;
        -ms-transition    : all 0.3s ease-out;
        -o-transition     : all 0.3s ease-out;
        transition        : all 0.3s ease-out;
    }

    .close-button:after
    {
        -webkit-transform : rotate(45deg);
        -moz-transform    : rotate(45deg);
        -ms-transform     : rotate(45deg);
        -o-transform      : rotate(45deg);
        transform         : rotate(45deg);
        -webkit-transition: all 0.3s ease-out;
        -moz-transition   : all 0.3s ease-out;
        -ms-transition    : all 0.3s ease-out;
        -o-transition     : all 0.3s ease-out;
        transition        : all 0.3s ease-out;
    }

    .close-button:hover:before,
    .close-button:hover:after
    {
        -webkit-transform: rotate(180deg);
        -moz-transform   : rotate(180deg);
        -ms-transform    : rotate(180deg);
        -o-transform     : rotate(180deg);
        transform        : rotate(180deg);
    }
`;

let CSSBtnTheme = `
    .fa,
    .fab,
    .fal,
    .far,
    .fas 
    {
        line-height: 0;
    }

    ul 
    {
        animation: fadein 2s;
        padding: 0;
        position: relative;
        margin: 0 0 100 0;
        transform: scale(0.65);
        justify-content: start;
        display: inline-block;
        flex-direction: row;
        flex-wrap: nowrap;
        /* justify-content: end; */
        align-items: unsafe;
        /* align-content: end; */
        margin: 0 -5vmin;
        /* margin: 0 500px; */
        /* margin-bottom: 7.8em !important; */
        padding-inline-start: 0px !important;
        display: inline-flex;
        flex-direction: row;
        flex-wrap: nowrap;
        justify-content: flex-start;
        align-items: baseline;
        align-content: stretch;
    }


    ul li 
    {
        animation    : fadein 1s;

        list-style   : none;
        margin       : 0 7px;
        display      : block;
    }

    ul li:after, .rr-font--update.rr-inc:after
    {
        pointer-events: none;
        content: "";
        position: absolute;
        top: -140px;
        bottom: -230px;
        right: 0;
        z-index: 10;
        /* padding-left: 320; */
        border-right: 1.0px solid rgba(var(--checked-background-rgb), 0.3);
        transform: rotate(30deg);
    }

    ul li a 
    {
        position     : relative;
        display      : list-item;
        width        : 60px;
        height       : 60px;
        text-align   : center;
        line-height  : 63px;
        background   : var(--background-color);
        border-radius: 50%;
        font-size    : 35px;
        color        : darkgray;
        transition   : .5s;
    }

    ul li a:hover 
    {
        cursor       : pointer;
    }

    ul li a::before 
    {
        content      : '';
        position     : absolute;
        top          : 0;
        left         : 0;
        width        : 100%;
        height       : 100%;
        border-radius: 50%;
        background   : #009992;
        transition   : .5s;
        transform    : scale(.9);
        z-index      : -1;
    }
    
    ul li a:hover
    {
        color      : black;
    }

    ul li a:hover::before 
    {
        transform : scale(1.1);
        background: #009992;
    }

    ul li:nth-child(1) a::before 
    {
        background: #ffee10;
    }

    ul li:nth-child(1) a:hover::before 
    {
        box-shadow: 0 0 10px #e67e22;
    }

    ul li:nth-child(1) a:hover 
    {
        box-shadow : 0 0 5px #e67e22;
        text-shadow: 0 0 5px #e67e22;
    }

    ul li:nth-child(2) a::before 
    {
        background: #95a5a6;
    }

    ul li:nth-child(2) a:hover::before 
    {
        box-shadow: 0 0 10px #9b59b6;
    }

    ul li:nth-child(2) a:hover 
    {
        box-shadow : 0 0 5px #9b59b6;
        text-shadow: 0 0 5px #9b59b6;
    }

    ul li:nth-child(3) a::before 
    {
        background: #e74c3c;
    }

    ul li:nth-child(3) a:hover::before 
    {
        box-shadow: 0 0 10px #e74c3c;
    }

    ul li:nth-child(3) a:hover 
    {
        box-shadow : 0 0 5px #e74c3c;
        text-shadow: 0 0 5px #e74c3c;
    }

    ul li:nth-child(4) a:hover::before 
    {
        box-shadow: 0 0 10px #029992;
    }

    ul li:nth-child(4) a:hover 
    {
        box-shadow : 0 0 5px #029992;
        text-shadow: 0 0 5px #029992;
    }


    /*ul li a
    {
        color: transparent;
    }*/
`;

let CSSTagBtn = `

    .settings-container 
    {
        animation: fadein 3s;
        overflow: hidden;
        border-bottom: 1px solid rgba(var(--checked-background-rgb), 0.3);
        margin-bottom: 150px;
          display: flex;
        flex-direction: row;
        flex-wrap: nowrap;
        justify-content: flex-start;
        align-items: center;
        align-content: stretch;
    }

    .raw-block 
    {
        /* margin-bottom: 4.8em; */
        margin: 16px 0;
        /* display: -webkit-box; */
        align-items: center;
        margin-bottom: 4.5em;
    }

    .tag-num
    {
        user-select   : none;
        font-size     : 10px;
        line-height   : 18px;
        position      : relative;
        display       : inline-flex;
        height        : 18px;
        padding       : 0 6px;
        letter-spacing: .25px;
        color         : black;
        border-radius : 18px;
        background    : #c0c2cc;
        margin        : 0 7px;
    }

    .tag-num:hover
    {
        cursor      : pointer;
        transition  : all 400ms ease;
        background  : black;
        color       : white;
    }

    .rr-font--update.rr-dec {
        font-size: 15px;
        margin-left: 30px;
    }

    .rr-font--update.rr-inc 
    {
        font-size: 22px;
        margin-right: 20px;
        position: relative;
        margin-left: 15px;
    }

    .rr-font--update.rr-inc:hover 
    {
        /*transform: scale(1.3);*/
        color: var(--checked-background);
        transition: all 0.3s ease-out;
    }

    .rr-font--update.rr-dec:hover 
    {
        color: var(--checked-background);
        transition: all 0.3s ease-out;
    }

    .rr-font--update 
    {
        user-select : none;
        color       : darkgray;
        font-family : Open sans;
        width       : 30px;
        font-weight : 700;
        font-size   : 18px;
        cursor      : pointer;
    }

    .btn-custom 
    {
        font-family: "Source sans Pro";
        outline: 0;
        border-radius: 16px;
        border: 1px solid var(--checked-background);
        background: transparent;
        color: var(--checked-background);
        width: 120px;
        height: 24px;
        cursor: pointer;
        text-transform: uppercase;
        margin-left: 30px;
    }

    .btn-custom:hover
    {
        transition: background 0.3s ease-in-out;
        background: white;
    }

    #font-indicator
    {
        color: var(--checked-background);
        font-family: Source sans pro;
        font-weight: 600;
    }
    .sentence.or,[class^="default"]
    {
        font-size: ${fontSizeValue};
    }
    
    [class^="sentence"]:not(.or)
    {
        font-weight: 300;
        font-size: ${fontSizeValueDefault};
    }
`;
/*
    | Plugin Mobile support.
*/
let CSSMediaQuery = `
    @media only screen and (max-width: 600px)
    {
        label
            { display: inline-block; font-size: 95%; }

        .radio-group, .checkbox-group
            { display: grid; }

        .chapterBody
            { margin: 7%;max-width: 82%; }

        .sentence.or
            { font-size: 92%; text-align: initial; font-weight: 300; }

        .settingsForm
            { flex-wrap: wrap; display: contents; }

        .original
            { font-size: 87%; text-align: initial; font-weight: 500; }

        .sentence.google
            { font-size: 87%; text-align: initial; font-weight: 300; }
        .sentence.google.default
            { font-size: 92%; text-align: initial; font-weight: 300; }

        .sentence.niutrans
            { font-size: 87%; text-align: initial; font-weight: 300;}
        .sentence.niutrans.default
            { font-size: 92%; text-align: initial; font-weight: 300; }

        .sentence.baidu
            { font-size: 87%; text-align: initial; font-weight: 300; }
        .sentence.baidu.default
            { font-size: 92%; text-align: initial; font-weight: 300; }

        .sentence.sogou
            { font-size: 87%; text-align: initial; font-weight: 300; }
        .sentence.sogou.default
            { font-size: 92%; text-align: initial; font-weight: 300; }

        .sentence.yeekit
            { font-size: 87%; text-align: initial; font-weight: 300; }
        .sentence.yeekit.default
            { font-size: 92%; text-align: initial; font-weight: 300; }

        .close-button
            { max-width: 7vmin; max-height: 7vmin; min-width: 7vmin; min-height: 7vmin; right: 3%; top: 57%; background: #393A3D; border-radius: 50%; }

        .close-button:before, .close-button:after
            { background:white; }

        .arrow.left
            { top: 76%; }

        .arrow.right
            { top: 68%; right: 5%; }

        .tooltip
            { display: inline; }

        h1.header-title
            { font-size: 150% }

        .rr-font--update.rr-dec 
            { font-size: 15px !important; margin-left: 35px !important; }

        .rr-font--update.rr-inc 
            { margin-right: 15px !important; margin-left: 5px !important; }

        .btn-custom 
            { height: 54px !important; text-align: center !important;}

        ul li 
            { margin: 0 12px 4 !important; }

        ul li:after, .rr-font--update.rr-inc:after 
            { border-right: 2px solid rgba(var(--checked-background-rgb), 1); }
        ul
            { animation:fadein 2s;padding:0!important;position:relative!important;margin:0 0 100!important;transform:scale(.65)!important;justify-content:center!important;display:flex!important;flex-direction:row!important;align-items:unsafe!important;margin:0 -10vm!important;margin:0 -10vmin!important;flex-wrap:wrap!important;justify-content:space-evenly!important;align-items:baseline!important;align-content:center!important}
    }
`
;

window.onload = async function ()
{     
    /*
        | Adding Glossary support
    */
    await new Promise( resolve => setTimeout( resolve, 10 ) );
    
    if (   $('style[type="text/css"]').text().includes( 'noWordWrapping' )  &&
           !(  window.sessionStorage.getItem("userjs_UGMTLComplete") &&
               window.sessionStorage.getItem("userjs_UGMTLComplete") > window.performance.timing.fetchStart )
       )
    {
        await new Promise ( resolve => document.addEventListener( 'userjs_UGMTLComplete' , resolve ) );
    }
    else
    {
        /*
            | Swaping Chinese/English words to have a better translation.
        */
        mainBodyWordsReplacer( );
    }

    /*
        | Inject the iframe to the DOM.
    */
    $( '#app' ).get( 0 ).insertAdjacentHTML( 'afterEnd', `<iframe id="pageContainer" style="display:${autoReaderState ? 'block' : 'none'}">` );

    /*
        | Get raw & original text from main page.
    */
    const originalSentences = $( '.chapter-body' ).find( '.translated' ).text().replace( /(\xAD)/g, '' ).trim().split( '\n' );
    const originalRaw       = $( '.chapter-body' ).find( '.original' ).text().trim().split( '\n' );
    const chapterTitle      = $( '.chapter-title' )[ 0 ].textContent;
    /*
        | Add a button for the reader mode.
    */
    $( '.js-toggle-original' ).after( '<button class="btn btn-enabled reader-mode">READER MODE</button>' );
    $( '.btn.btn-enabled.reader-mode' ).css( 'boxShadow', 'rgb(236, 240, 241) 0px 0px 8px' );
    $( '.btn.btn-enabled.reader-mode' ).css( 'color'    , 'black' );

    /*
        | Reader btn onClick event.
    */
    $( '.reader-mode' ).click( function ()
    {
        $( '#app' ).css( { 'display': 'none', 'opacity': '0' } );
        $( '#pageContainer' ).show();
    } );

    /*
          | We will need this for the tooltip & highlighting words.
          | The values produces non-ut8 characters so we must remove them
          | so that there will be no problem replacing them using regex.
    */
    $( '.translated t' ).each( function ( index )
    {
        let chineseValue  = $( this ).attr( 'data-title' );
        let value         = $( this )
                            .attr( 'data-title', $( this ).text() )
                            .text()
                            .trimLeft()
        ;

        const replacedValue = value.replace( /[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/(\xAD)]/g, '' );
        
        if( replacedValue != "" || replacedValue.length != 0 )
            uniqueWords.set( replacedValue , chineseValue );
    } );
    
    /*
        | This what will be using to control the iframe DOM
    */
    iframeHandler = $( "#pageContainer" ).contents();

    /*
        | Toggle theme state.
    */
    autoThemeState ?

        iframeHandler.find( ':root' ).eq( 0 ).attr( 'data-theme', 'light' ) :
        iframeHandler.find( ':root' ).eq( 0 ).attr( 'data-theme', 'dark' )

    ;

    /*
        | Append the needed styles for the iframe.
    */
    addStyles( );
      
    /*
        | Add iframe form.
    */
    addForm( );

    /*
        | Apply secondary theme.
    */
    iframeHandler.find( ':root' ).eq( 0 ).attr( 'data-sec-theme', secondaryTheme );

    /*
        | Toggle secondary colors.
    */
    iframeHandler.find( '.drag' ).click( function()   { iframeHandler.find( ':root' ).eq( 0 ).attr( 'data-sec-theme', 'gold'    ); GM_SuperValue.set( 'secondaryTheme', "gold" );  });
    iframeHandler.find( '.moon' ).click( function()   { iframeHandler.find( ':root' ).eq( 0 ).attr( 'data-sec-theme', 'moon'    ); GM_SuperValue.set( 'secondaryTheme', "moon" ); });
    iframeHandler.find( '.bomb' ).click( function()   { iframeHandler.find( ':root' ).eq( 0 ).attr( 'data-sec-theme', 'bomb'    ); GM_SuperValue.set( 'secondaryTheme', "bomb" ); });
    iframeHandler.find( '.norm' ).click( function()   { iframeHandler.find( ':root' ).eq( 0 ).attr( 'data-sec-theme', 'default' ); GM_SuperValue.set( 'secondaryTheme', "" ); });

    /*
        | If one provider is running then removed color/border translation identifier.
    */
    iframeHandler.find( "input[name='items[]']" ).change(function(e)  { onlyOneRunning(); });
  
    for( let index in providers )
    {
        let provider = providers[ index ];
        
        if( eval( 'provider' + provider + 'State' ) )
        {
            providerSelector( originalRaw, provider.toLowerCase() , providerObject[ provider ][ "maxSize" ] );
        }
    }
    
    iframeHandler.find( '.close-button' ).click( function ()
    {
          $( '#app' ).css( { 'display': 'block', 'opacity': '1', 'overflow-y': 'scroll' } );
          $( '#pageContainer' ).hide();
    } );

    /*
        | Hooking next/prev page on click
        | Replacing href results a bug, so I'll be using this approach.
    */
    iframeHandler.find( '.arrow.right' ).click( function ()   { window.location.assign( $( this ).attr( 'value' ) );  } );
    iframeHandler.find( '.arrow.left' ).click(  function ()   { window.location.assign( $( this ).attr( 'value' ) );  } );

    /*
        | Add original and raw text ( with display:none )
        | Also there is a non-utf8 coming out of nowhere so we must clean that
    */
    for ( let i in originalSentences )
    {
        for ( const [key, value] of uniqueWords.entries() )
        {
              originalSentences[i] = originalSentences[i].replace
              (
                      new RegExp( '(?<![<>])(' + key + ')(?![<>])' , 'g' ),
                      `<span class="highlighter"><div class="tooltip top">$&<span class="tooltip-text">${value}</span></div></span>`
              )
            ;
        }
        
        iframeHandler.find( '.chapterBody' ).append( `<div class="sentence or">${originalSentences[i]}</div>` );
        iframeHandler.find( '.chapterBody' ).append( `<div class="raw-block"><p class="original">${originalRaw[i]}</p><a class="tag-num">${i}</a></div>` );
    }
  
    if( !autoRawState )
        iframeHandler.find( '.raw-block' ).hide();
  
    if ( !providerOriginalState )
        iframeHandler.find( '.sentence.or' ).hide();
    
    // Should be optimized... 
    iframeHandler.find( '.rr-dec' ).click( function() 
    {
        let currentFontSize         = parseInt( iframeHandler.find( '.sentence.or,[class^="default"]' ).css('font-size') ) - 1;  
        let currentFontSizeDefault  = parseInt( iframeHandler.find( '[class^="sentence"]:not(.or,.default)' ).css('font-size') ) - 1;  
        
        if ( currentFontSize <= 17 )
            return;
        
        iframeHandler.find( '[class^="sentence"]:not(.or)' ).css( 'font-size', currentFontSizeDefault );
        iframeHandler.find( '.sentence.or,[class^="default"]' ).css( 'font-size', currentFontSize );
      
        iframeHandler.find( '#font-indicator' ).text( currentFontSize );
        
        GM_SuperValue.set( 'fontSizeValueDefault', currentFontSizeDefault );
        GM_SuperValue.set( 'fontSizeValue', currentFontSize );
    } );
    iframeHandler.find( '.rr-inc' ).click( function() 
    {
        let currentFontSize         = parseInt( iframeHandler.find( '.sentence.or,[class^="default"]' ).css('font-size') ) + 1;  
        let currentFontSizeDefault  = parseInt( iframeHandler.find( '[class^="sentence"]:not(.or,.default)' ).css('font-size') ) + 1;  

        if ( currentFontSize >= 30 )
            return;
        
        iframeHandler.find( '[class^="sentence"]:not(.or)' ).css( 'font-size', currentFontSizeDefault );
        iframeHandler.find( '.sentence.or' ).css( 'font-size', currentFontSize );
      
        iframeHandler.find( '#font-indicator' ).text( currentFontSize );

        GM_SuperValue.set( 'fontSizeValueDefault', currentFontSizeDefault );
        GM_SuperValue.set( 'fontSizeValue', currentFontSize );
    } );

    providerChangedEvent( originalRaw );
    settingsChangedEvent( );
    
    iframeHandler.find( '.btn-custom' ).on( 'click', function()
    {
        let self = $( this );
        let stringBuilder = `${chapterTitle}\n\n\n`;

        self.text( 'Copied ;-)' );
        
        setTimeout( function ( ) { self.text( 'Copy Edited Text' ); }, 600 );

        let rawEdits = iframeHandler.find( '.sentence.edit' ).toArray( ).map( ( p ) => p.textContent );

        for ( let index in rawEdits )
        {
            stringBuilder += `${rawEdits[ index ]}\n\n\n`;
        }

        window.navigator.clipboard.writeText( stringBuilder );
    } );
    
  
    iframeHandler.find( '.tag-num' ).on( 'click', function()
    {
        let index = parseInt( this.textContent );

        if ( $( this ).attr( 'value' ) )
        {
            iframeHandler.find( '.tag-num' ).eq( index ).next( ).filter( '.sentence.edit' ).fadeToggle(
                200 );
            return;
        }

        iframeHandler.find( '.raw-block' ).eq( index ).append(
            '<div class="sentence edit" contenteditable="plaintext-only" spellcheck="false"/>'
        );

        $( this ).attr( 'value', 'clicked' );
    } );  
}

function addStyles()
{
      iframeHandler.find( 'head' ).append( $
        (
            `
                <meta name="viewport" content="width=device-width, initial-scale=1">
                <link rel="preload" onload="this.rel = 'stylesheet'" as="style" href='https://fonts.googleapis.com/css?family=Open+Sans:300,400,600|Poppins|Source+Sans+Pro:300,400,500,600'>
                <link rel="preload" onload="this.rel = 'stylesheet'" as="style" href='https://use.fontawesome.com/releases/v5.6.3/css/all.css'>  
                ${$( '.next > a' ).attr( 'href' ) != undefined ? `<link rel="prefetch" href="${$( '.next > a' ).attr( 'href' )}">` : '' }
                <style type='text/css'>
                          ${CSSRoot}
                          ${CSSContainer}
                          ${CSSCloseBtn}
                          ${CSSForm}
                          ${CSSChapterBody}
                          ${CSSTooltip}
                          ${CSSNextPrevArrow}
                          ${CSSMediaQuery}
                          ${CSSBtnTheme}
                          ${CSSTagBtn}
                </style>
            `
        )
    );
}

function addForm( )
{
    const chapterTitle      = $( '.chapter-title' )[ 0 ].textContent.split( ':' );

    const readerState       = autoReaderState ? '' : 'checked';
    const themeState        = autoThemeState  ? '' : 'checked';
    const rawState          = autoRawState    ? '' : 'checked';

    const pageNext          = $( '.next > a' ).attr( 'href' );
    const pagePrevious      = $( '.previous > a' ).attr( 'href' );
    const nextChapterButton = pageNext != null      ? (   '<a value="'  + pageNext      + '" class="arrow right"></a>'  ) : '';
    const prevChapterButton = pagePrevious != null  ? (   '<a value="'  + pagePrevious  + '" class="arrow left"></a>'   ) : '';

    const isOriginalEnabled = providerOriginalState ? 'checked' : '';
    const isGoogleEnabled   = providerGoogleState   ? 'checked' : '';
    const isSogouEnabled    = providerSogouState    ? 'checked' : '';
    const isNiutransEnabled = providerNiutransState ? 'checked' : '';
    const isBaiduEnabled    = providerBaiduState    ? 'checked' : '';
    const isYeekitEnabled   = providerYeekitState   ? 'checked' : '';

    iframeHandler.find( 'body' ).append
        (
            `
              <div id="clean-reader">
              <div class="pageContainerBackground">
                    <a class="close-button">Close</a>
                    ${prevChapterButton}
                    ${nextChapterButton}
                    <form class="settingsForm">

                          <label>Provider</label>
                          <div class="radio-group">

                              <input type="checkbox" id="Original-translator" value="o-translator-p" name="items[]" ${isOriginalEnabled}>
                              <label for="Original-translator">Original</label>

                              <input type="checkbox" id="Google-translator" value="g-translator-p" name="items[]" ${isGoogleEnabled}>
                              <label for="Google-translator"><span style="color:rgb(${providerObject["Google"]["color"][0]});text-shadow: 0 0 3px #30AFDB, 0 0 5px #30AFDB;padding-right: 6">◼ </span> Google</label>

                              <input type="checkbox" id="Sogou-translator" value="s-translator-p" name="items[]" ${isSogouEnabled}>
                              <label for="Sogou-translator"><span style="color:rgb(${providerObject["Sogou"]["color"][0]});text-shadow: 0 0 3px #e74c3c, 0 0 5px #e74c3c;padding-right: 6">◼ </span> Sogou</label>

                              <input type="checkbox" id="Niutrans-translator" value="n-translator-p" name="items[]" ${isNiutransEnabled}>
                              <label for="Niutrans-translator"><span style="color:rgb(${providerObject["Niutrans"]["color"][0]});text-shadow: 0 0 3px #e74c3c, 0 0 5px #e74c3c;padding-right: 6">◼ </span> Niutrans</label>

                              <input type="checkbox" id="Baidu-translator" value="b-translator-p" name="items[]" ${isBaiduEnabled}>
                              <label for="Baidu-translator"><span style="color:rgb(${providerObject["Baidu"]["color"][0]});text-shadow: 0 0 3px #6F68F2, 0 0 5px #6F68F2;padding-right: 6">◼ </span> Baidu</label>
                              
                              <input type="checkbox" id="Yeekit-translator" value="y-translator-p" name="items[]" ${isYeekitEnabled}>
                              <label for="Yeekit-translator"><span style="color:rgb(${providerObject["Yeekit"]["color"][0]});text-shadow: 0 0 3px #6F68F2, 0 0 5px #78e08f;padding-right: 6">◼ </span> Yeekit</label>

                          </div>

                          <label>Raw</label>
                          <div class="radio-group">
                              <input type="radio" id="option-one-a" name="raw" value="mode-enabled" checked>
                              <label for="option-one-a">Enable</label>

                              <input type="radio" id="option-two-2" name="raw" value="mode-disabled" ${rawState}>
                              <label for="option-two-2">Disable</label>
                          </div>

                          <label>Reader</label>
                          <div class="radio-group">
                              <input type="radio" id="option-1" name="mode" value="mode-enabled" data-theme="dark" checked>
                              <label for="option-1">Always</label>
                              <input type="radio" id="option-2" name="mode" value="mode-disabled" data-theme="light" ${readerState}>
                              <label for="option-2">Never</label>
                          </div>

                          <label>Theme</label>
                          <div class="radio-group theme">
                              <input type="radio" id="option-1-1" name="theme" value="theme-enabled" checked>
                              <label for="option-1-1">Light</label>
                              <input type="radio" id="option-2-2" name="theme" value="theme-disabled" ${themeState}>
                              <label for="option-2-2">Dark</label>
                          </div>

                    </form>
                    <h1 class="header-title">${chapterTitle[1]} - <span style="transition : all 200ms ease;color:var(--checked-background);font-weight:bold">${chapterTitle[0].replace( '#', '' )}</h1>
                    <div class="pageSplit"></div>
                    <div class="settings-container">
                    <ul>
                      <li>
                        <a class="drag"><i class="fab fa-d-and-d"></i></a>
                      </li>
                      <li>
                        <a class="moon"><i class="fas fa-bowling-ball"></i></a>
                        </li>
                      <li>
                        <a class="bomb"><i class="fas fa-bomb"></i></a>
                      </li>
                      <li>
                        <a class="norm"><i class="fas fa-tint"></i> </a>
                      </li>
                    </ul><span class="rr-font--update rr-dec">A-</span><div id="font-indicator">${fontSizeValue}</div><span class="rr-font--update rr-inc">A+</span>
                      <button class="btn-custom">Copy edited text</button></div>
                  <div class="chapterBody">
                  </div>
              </div>
            </div>
            `
        )
    ;

}

function providerChangedEvent ( raws )
{
    const addEventHandler = ( iframe, provider ) =>
    {
        const capitalizeProvider = provider.charAt( 0 ).toUpperCase( ) + provider.slice( 1 );

        iframe.find( `#${capitalizeProvider}-translator` ).change( function ( )
        {
            GM_SuperValue.set( `provider${capitalizeProvider}State`, this.checked );

            this.checked && !isTranslated[ capitalizeProvider ][ 1 ] ? 
              
                providerSelector( raws, provider, providerObject[ capitalizeProvider ][ 'maxSize' ] ) : 
                iframe.find( `.sentence.${provider}` ).fadeToggle( 300 )
            ;
        } );
    };

    addEventHandler( iframeHandler, 'google' );
    addEventHandler( iframeHandler, 'sogou' );
    addEventHandler( iframeHandler, 'niutrans' );
    addEventHandler( iframeHandler, 'baidu' );
    addEventHandler( iframeHandler, 'yeekit' );

    iframeHandler.find( '#Original-translator' ).change( function ( )
    {
        GM_SuperValue.set( 'providerOriginalState', this.checked );
        iframeHandler.find( '.sentence.or' ).delay( 100 ).fadeToggle( 100 );
    } );
}

function settingsChangedEvent ( )
{
    /* READER CHANGING STATE */
    iframeHandler.find( 'input[type=radio][name=mode]' ).change( function ( )
    {
        autoReaderState = $( this ).val( ).includes( 'enabled' );
        GM_SuperValue.set( 'autoReaderState', autoReaderState );
    } );

    /* RAW CHANGING STATE */
    iframeHandler.find( 'input[type=radio][name=raw]' ).click( function ( )
    {
        autoRawState = $( this ).val( ).includes( 'enabled' );
        GM_SuperValue.set( 'autoRawState', autoRawState );

        autoRawState ? 
          
          iframeHandler.find( '.raw-block' ).delay( 100 ).fadeIn( 300 ) :
          iframeHandler.find( '.raw-block' ).fadeOut( 200 );
    } );

    /* THEME CHANGING STATE */
    iframeHandler.find( 'input[type=radio][name=theme]' ).click( function ( )
    {
        autoThemeState = $( this ).val( ).includes( 'enabled' );
        GM_SuperValue.set( 'autoThemeState', autoThemeState );

        autoThemeState ? 
          
            iframeHandler.find( ':root' ).eq( 0 ).attr( 'data-theme', 'light' ) :
            iframeHandler.find( ':root' ).eq( 0 ).attr( 'data-theme', 'dark' );
    } );
}

function onlyOneRunning ()
{
    let runningProviders = [];

    iframeHandler.find( "input[name='items[]']:checked" ).each( function () { runningProviders.push( $( this ).attr( 'id' ).replace( '-translator', '' ).toLowerCase() ); } );
    
    if( runningProviders.length == 1 )
    {
        iframeHandler.find( `.sentence.${ runningProviders[0] }` ).attr( 'class', `sentence ${ runningProviders[0] } default` );
        iframeHandler.find( '.highlighter-default' ).css( 'font-weight', '500' );
    }
    else
    {
        for( let id in runningProviders )
        {
            iframeHandler.find( `.sentence.${ runningProviders[ id ] }.default` ).attr( 'class', `sentence ${ runningProviders[ id ] }` );
            iframeHandler.find( '.highlighter-default' ).css( 'font-weight', '500' );
        }
    }
        
    return runningProviders.length;
}

const disableCheckbox = ( elementId, status ) => iframeHandler.find( elementId ).prop( 'disabled', status );
async function providerSelector ( raw, name, chunksize )
{
    const taskDelay = ( m ) => new Promise( r => setTimeout( r, m ) )

    let translatedChunks = [];
    let chunks = []

    chunks = separateIntoChunks( raw, chunksize );
    chunks[0] = chunks[0].trimLeft();

    switch ( name )
    {
        case "sogou":
            {
                disableCheckbox( '#Sogou-translator', true );

                for ( let id in chunks )
                {
                    let value = await sogouSetCookies();
                    translatedChunks[id] = await sogouTranslator( chunks[id], value );

                    await taskDelay( providerObject["Sogou"]["timeout"] );
                }

                // Translation is finished.
                isTranslated["Sogou"][1] = true;
                disableCheckbox( '#Sogou-translator', false );
            }
            break;
        case "google":
            {
                disableCheckbox( '#Google-translator', true );

                for ( let id in chunks )
                {
                    translatedChunks[id] = await googleTranslator( chunks[id] );
                    await taskDelay( providerObject["Google"]["timeout"] );
                }

                // Translation is finished.
                isTranslated["Google"][1] = true;
                disableCheckbox( '#Google-translator', false );
            }
            break;
        case "niutrans":
            {
                disableCheckbox( '#Niutrans-translator', true );

                for ( let id in chunks )
                {
                    translatedChunks[id] = await niutransTranslator( chunks[id] );
                    await taskDelay( providerObject["Niutrans"]["timeout"] );
                }

                // Translation is finished.
                isTranslated["Niutrans"][1] = true;
                disableCheckbox( '#Niutrans-translator', false );
            }
            break;
        case "baidu":
            {
                disableCheckbox( '#Baidu-translator', true );

                let timer = -performance.now();

                const tokens = await baiduReceiveTokens();
                for ( let id in chunks )
                {
                    translatedChunks[id] = await baiduTranslator( chunks[id], tokens );
                    await taskDelay( providerObject["Baidu"]["timeout"] );
                }

                timer += performance.now();
                console.log( "Time: " + ( timer / 1000 ).toFixed( 5 ) + " sec." )

                // Translation is finished.
                isTranslated["Baidu"][1] = true;
                disableCheckbox( '#Baidu-translator', false );
            }
        case "yeekit":
            {
                disableCheckbox( '#Yeekit-translator', true );

                for ( let id in chunks )
                {
                    translatedChunks[id] = await yeekitTranslator( chunks[id] );
                    await taskDelay( providerObject["Yeekit"]["timeout"] );
                }

                // Translation is finished.
                isTranslated["Yeekit"][1] = true;
                disableCheckbox( '#Yeekit-translator', false );
            }
            break;
    }

    let finalResult = seperateChunksIntoPars( translatedChunks );
    createSentence( finalResult, name, onlyOneRunning() );
}

function mainBodyWordsReplacer ()
{
    $( '.original t' ).each
        (
            function ()
            {
                const textSpace = $( this ).get( 0 ).previousSibling.nodeName == 'T' ? ' ' : '';
                const textValue = $( this ).text( );

                $( this ).text( textSpace + $( this ).attr( 'data-title' ) );
                $( this ).attr( 'data-title', textValue );
            }
        );
}

function createSentence ( paragraphs, provider, runningProviders )
{
    if( runningProviders == 1 )
    {
        provider.concat( " default" ); 
    }   
    
    paragraphs.forEach( ( sentence, index ) =>
    {
        for ( const [key, value] of uniqueWords.entries() )
            sentence = sentence.replace( new RegExp( '(?<![<>])(' + key + ')(?![<>])' , 'g' ), `<span class="highlighter-default">$&</span>` );

        iframeHandler.find( '.raw-block' ).eq( index ).before( `<p class="sentence ${provider}">` + sentence + '</p>' );
    } );
    
    onlyOneRunning( );
}

function googleTranslator ( text )
{
    return new Promise(
        ( resolve ) => $.ajax( 'https://translate.googleapis.com/translate_a/single?client=gtx&sl=zh-CN&tl=en&dt=t',
            {
                method: 'POST',
                data  : { q: text },
                dataType: "json"
            } ).done( ( t ) =>
            {
                let paragraph = "";

                for ( let i = 0; i < t[0].length; i++ )
                {
                    paragraph += t[0][i][0];
                }

                resolve( paragraph );
            } )
                .fail( function( xhr, textStatus, errorThrown )
                {
                    GM_SuperValue.set( 'providerGoogleState', false );
                    disableCheckbox( '#Google-translator', false );
                    iframeHandler.find( '#Google-translator' ).prop( 'checked', false );
                } )
    );
}

function sogouTranslator ( text, id )
{
    const userId = () =>
    {
        return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace( /[xy]/g, function ( name )
        {
            let M   = 0 | 16 * Math.random();
            var pid = "x" == name ? M : 8 | 3 & M;
            return pid.toString( 16 );
        } );
    };

    return new Promise( ( resolve, reject ) =>
    {
        var formData =
        {
            'from'        : 'zh-CHS',
            'to'          : 'en',
            'text'        : text,
            'client'      : 'pc',
            'fr'          : 'browser_pc',
            'pid'         : 'sogou-dict-vr',
            'dict'        : 'true',
            'word_group'  : 'true',
            'second_query': 'true',
            'uuid'        : userId,
            'needQc'      : '1',
            's'           : md5( 'zh-CHS' + 'en' + text + '8511813095152' )
        };

        GM_xmlhttpRequest(
            {
                method : "POST",
                url    : "https://fanyi.sogou.com/reventondc/translateV2",
                data   : $.param( formData ),
                headers:
                {
                    "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
                    "Accept"      : "application/json",
                    "Referer"     : "https://fanyi.sogou.com/",
                    "User-agent"  : window.useragent,
                    "Cookie"      : "SNUID=" + id
                }
                ,
                onload: function ( result )
                {
                    try
                    {
                        var jsonObj = JSON.parse( result.response );
                        resolve( jsonObj.data.translate.dit );
                    }
                    catch ( error )
                    {
                        GM_SuperValue.set( 'providerSogouState', false );
                        disableCheckbox( '#Sogou-translator', false );
                        iframeHandler.find( '#Sogou-translator' ).prop( 'checked', false );
                    }
                }
            } );
    } );
}

function sogouSetCookies ()
{
    let dateExpires = new Date;
    let match       = ".sogou.com";
    //let random = Math.floor( Math.random() * ( 9000000 - 1000 ) ) + 1000
    let id = "1";  // Must be randomized in the next version

    return new Promise( ( resolve ) =>
    {
        GM_xmlhttpRequest(
            {
                method : "GET",
                url    : "https://fanyi.sogou.com/",
                headers:
                {
                    "User-agent": window.useragent,
                    "Cookie"    : setCookie( "SNUID", id, dateExpires.toGMTString(), match, "/" )
                }
                ,
                onload: function ( result )
                {
                    resolve( id )
                }
            } );
    } );
}

function niutransTranslator ( text )
{
    return new Promise( ( resolve ) =>
    {
        GM_xmlhttpRequest(
            {
                method : "GET",
                url    : "http://test.niutrans.vip/NiuTransServer/testtrans?&from=auto&to=en&src_text=" + encodeURIComponent( text ),
                headers:
                {
                    "Accept-Encoding" : "gzip, deflate",
                    "User-Agent": window.useragent
                }
                ,
                onload: function ( result )
                {
                    try
                    {
                        var myResponse = JSON.parse( result.response ).tgt_text;
                        resolve( [...myResponse.split( '\n \n' )].join( '\n\n' ) );
                        //console.log(result);
                    }
                    catch ( error )
                    {
                        GM_SuperValue.set( 'providerNiutransState', false );
                        disableCheckbox( '#Niutrans-translator', false );
                        iframeHandler.find( '#Niutrans-translator' ).prop( 'checked', false );
                    }
                }
            } );
    } );
}

async function baiduTranslator ( text, _tokens )
{
    const formData =
    {
        'from'             : 'zh',
        'to'               : 'en',
        'query'            : text,
        'transtype'        : 'realtime',
        'simple_means_flag': 3,
        'sign'             : sign( text, _tokens[0] ),
        'token'            : _tokens[1],
        'domain'           : 'common'
    };

    return new Promise( ( resolve ) =>
    {
        GM_xmlhttpRequest(
            {
                method : "POST",
                url    : "https://fanyi.baidu.com/v2transapi",
                data   : $.param( formData ),
                headers:
                {
                    "Content-Type"    : "application/x-www-form-urlencoded; charset=UTF-8",
                    "Accept"          : "application/json",
                    "Referer"         : "https://fanyi.baidu.com",
                    "Accept-Encoding" : "gzip, deflate",
                    "User-Agent"      : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"
                }
                ,
                onload: function ( result )
                {
                    try
                    {
                        const jsonObj = JSON.parse( result.responseText )
                        resolve( jsonObj.trans_result.data.map( p => p.dst ).join( '\n\n' ) );
                    }
                    catch ( error )
                    {
                        GM_SuperValue.set( 'providerBaiduState', false );
                        disableCheckbox( '#Baidu-translator', false );
                        iframeHandler.find( '#Baidu-translator' ).prop( 'checked', false );
                    }
                }
            } );
    } );
}

function baiduReceiveTokens ()
{
    return new Promise( ( resolve ) =>
    {
        GM_xmlhttpRequest(
            {
                method : "GET",
                url    : "https://fanyi.baidu.com/",
                headers:
                {
                    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"
                }
                ,
                onload: function ( result )
                {
                    try
                    {
                        const windowToken = result.responseText.match( /window\.gtk = '(.*?)'/ )[1];
                        const commonToken = result.responseText.match( /token: '(.*?)',/ )[1];

                        resolve( [windowToken, commonToken] );
                    }
                    catch ( error )
                    {
                        disableCheckbox( '#Baidu-translator', false );
                        iframeHandler.find( '#Baidu-translator' ).prop( 'checked', false );
                    }
                }
            } );
    } );
}

function yeekitTranslator( text )
{
    return new Promise( ( resolve ) =>
    {
        GM_xmlhttpRequest(
          {
              method: 'POST',
              url: 'http://fanyi.yeekit.com/zyyt/translate/translate',
              data: JSON.stringify(
                  {
                      "srcl"      : "nzh",
                      "tgtl"      : "nen",
                      "app_source": 9001,
                      "text"      : text,
                      "domain"    : "auto"
                  }),
              headers: 
              {
                "Content-Type": "application/json;charset=UTF-8", 
                "Referer"     : "http://fanyi.yeekit.com/",
                "Origin"      :"http://fanyi.yeekit.com",
                "User-Agent"  : window.useragent
              },

              onload: function ( result ) 
              {
                  try
                  {
                      let jsonObj = JSON.parse( result.response );
                      resolve( jsonObj.data )
                  }
                  catch (error)
                  {
                      GM_SuperValue.set( 'providerYeekitState', false );
                      disableCheckbox( '#Yeekit-translator', false );
                      iframeHandler.find( '#Yeekit-translator' ).prop( 'checked', false );
                  }
              }

          });
    } );
}

/*
    | Utilities
*/

function setCookie ( a, val, url, c, name )
{
    return a = [a, "=", val], url && a.push( ";expires=", url ), c && a.push( ";domain=", c ), name && a.push( ";path=", name ), document.cookie = a.join( "" ), a.join( "" );
}

function separateIntoChunks ( paragraphs, size )
{
    let chunks = [];
    let currentchunk = "";
    for ( let i = 0; i < paragraphs.length; i++ )
    {
        if ( ( currentchunk + paragraphs[i] ).length >= size )
        {
            chunks.push( currentchunk );
            currentchunk = paragraphs[i];
        }
        else
        {
            currentchunk = currentchunk + "\n\n" + paragraphs[i];
        }
    }
    if ( paragraphs.length != 0 )
    {
        chunks.push( currentchunk );
    }
    return chunks;
}

function seperateChunksIntoPars ( chunks, splitby = "\n\n" )
{
    let pars = [];
    chunks.forEach( ( chunk ) => chunk.split( splitby ).forEach( ( par ) => pars.push( par ) ) );
    return pars;
}

function a ( r )
{
    if ( Array.isArray( r ) )
    {
        for ( var o = 0, t = Array( r.length ); o < r.length; o++ )
            t[o] = r[o];
        return t
    }
    return Array.from( r )
}

function n ( a, o )
{
    var s = 0;
    for ( ; s < o.length - 2; s = s + 3 )
    {
        var d = o.charAt( s + 2 );
        d = d >= "a" ? d.charCodeAt( 0 ) - 87 : Number( d );
        d = "+" === o.charAt( s + 1 ) ? a >>> d : a << d;
        a = "+" === o.charAt( s ) ? a + d & 4294967295 : a ^ d;
    }
    return a;
}

function sign ( r, gtk = 0 )
{
    var i = null;
    var o = r.match( /[\uD800-\uDBFF][\uDC00-\uDFFF]/g );
    if ( null === o )
    {
        var t = r.length;
        t > 30 && ( r = "" + r.substr( 0, 10 ) + r.substr( Math.floor( t / 2 ) - 5, 10 ) + r.substr( -10, 10 ) )
    } else
    {
        for ( var e = r.split( /[\uD800-\uDBFF][\uDC00-\uDFFF]/ ), C = 0, h = e.length, f = []; h > C; C++ )
            "" !== e[C] && f.push.apply( f, a( e[C].split( "" ) ) ),
                C !== h - 1 && f.push( o[C] );
        var g = f.length;
        g > 30 && ( r = f.slice( 0, 10 ).join( "" ) + f.slice( Math.floor( g / 2 ) - 5, Math.floor( g / 2 ) + 5 ).join( "" ) + f.slice( -10 ).join( "" ) )
    }
    var u = void 0
        , l = "" + String.fromCharCode( 103 ) + String.fromCharCode( 116 ) + String.fromCharCode( 107 );
    u = null !== i ? i : ( i = gtk || "" ) || "";
    for ( var d = u.split( "." ), m = Number( d[0] ) || 0, s = Number( d[1] ) || 0, S = [], c = 0, v = 0; v < r.length; v++ )
    {
        var A = r.charCodeAt( v );
        128 > A ? S[c++] = A : ( 2048 > A ? S[c++] = A >> 6 | 192 : ( 55296 === ( 64512 & A ) && v + 1 < r.length && 56320 === ( 64512 & r.charCodeAt( v + 1 ) ) ? ( A = 65536 + ( ( 1023 & A ) << 10 ) + ( 1023 & r.charCodeAt( ++v ) ),
            S[c++] = A >> 18 | 240,
            S[c++] = A >> 12 & 63 | 128 ) : S[c++] = A >> 12 | 224,
            S[c++] = A >> 6 & 63 | 128 ),
            S[c++] = 63 & A | 128 )
    }
    for ( var p = m, F = "" + String.fromCharCode( 43 ) + String.fromCharCode( 45 ) + String.fromCharCode( 97 ) + ( "" + String.fromCharCode( 94 ) + String.fromCharCode( 43 ) + String.fromCharCode( 54 ) ), D = "" + String.fromCharCode( 43 ) + String.fromCharCode( 45 ) + String.fromCharCode( 51 ) + ( "" + String.fromCharCode( 94 ) + String.fromCharCode( 43 ) + String.fromCharCode( 98 ) ) + ( "" + String.fromCharCode( 43 ) + String.fromCharCode( 45 ) + String.fromCharCode( 102 ) ), b = 0; b < S.length; b++ )
        p += S[b],
            p = n( p, F );
    return p = n( p, D ),
        p ^= s,
        0 > p && ( p = ( 2147483647 & p ) + 2147483648 ),
        p %= 1e6,
        p.toString() + "." + ( p ^ m )
}

function md5 ( str )
{
    var k = [],
        i = 0;

    for ( i = 0; i < 64; )
        k[i] = 0 | ( Math.abs( Math.sin( ++i ) ) * 4294967296 );

    var b, c, d, j,
        x = [],
        str2 = unescape( encodeURI( str ) ),
        a = str2.length,
        h = [( b = 1732584193 ), ( c = -271733879 ), ~b, ~c];

    for ( i = 0; i <= a; )
        x[i >> 2] |= ( str2.charCodeAt( i ) || 128 ) << ( 8 * ( i++ % 4 ) );

    x[( str = ( ( a + 8 ) >> 6 ) * 16 + 14 )] = a * 8;
    i = 0;

    for ( ; i < str; i += 16 )
    {
        a = h;
        j = 0;

        for ( ; j < 64; )
        {
            a = [
                ( d = a[3] ),
                ( b = a[1] | 0 ) +
                ( ( ( d =
                    a[0] + [
                        ( b & ( c = a[2] ) ) | ( ~b & d ),
                        ( d & b ) | ( ~d & c ),
                        b ^ c ^ d,
                        c ^ ( b | ~d )
                    ][( a = j >> 4 )] +
                    ( k[j] + ( x[( [j, 5 * j + 1, 3 * j + 5, 7 * j][a] % 16 ) + i] | 0 ) ) ) <<
                    ( a = [7, 12, 17, 22, 5, 9, 14, 20, 4, 11, 16, 23, 6, 10, 15, 21][
                        4 * a + ( j++ % 4 )
                    ] ) ) |
                    ( d >>> ( 32 - a ) ) ),
                b,
                c
            ];
        }

        for ( j = 4; j; )
        {
            h[--j] = h[j] + a[j];
        }
    }

    str = "";

    for ( ; j < 32; )
    {
        str += ( ( h[j >> 3] >> ( ( 1 ^ ( j++ & 7 ) ) * 4 ) ) & 15 ).toString( 16 );
    }

    return str;
}