/*!
 * Authentic Theme (https://github.com/authentic-theme/authentic-theme)
 * Copyright Ilia Rostovtsev <ilia@virtualmin.com>
 * Licensed under MIT (https://github.com/authentic-theme/authentic-theme/blob/master/LICENSE)
 */

/* jshint strict: true */
/* jshint esversion: 6 */
/* jslint bitwise: true */
/* jshint jquery: true */

'use strict';

/**
 * Mail object module
 *
 * @since 19.17
 *
 * @return {object} Reveal folders module API
 * @return {object} Reveal messages module API
 */

const mail = (function() {

    /* jshint -W117 */

    /**
     * Imports globals using abstraction layer (DI)
     *
     * @since 19.17
     *
     * @return {string|function|object}
     */
    const _ = {
            path: {
                origin: v___location_origin,
                prefix: v___location_prefix,
                extensions: v___server_extensions_path,
                css: v___server_css_path,
                js: v___server_js_path
            },
            variable: {
                switch: function() {
                    return $t_uri_webmail;
                },
                module: {
                    name: function() {
                        return 'mailbox';
                    },
                    link: function() {
                        let prefix = v___location_prefix;
                        return prefix ? `${prefix}/${v___module}` : `/${this.name()}`;
                    },
                },
                locale: {
                    short: config_portable_theme_locale_format_short,
                }
            },
            platform: {
                mac: window.navigator.platform === 'MacIntel',
            },
            pjax: {
                fetch: plugins.pjax.page.link.get,
            },
            fetch: {
                options: {
                    headers: { 'x-requested-with': 'XMLHttpRequest' },
                }
            },
            load: load,
            sdata: session.server.data,
            mavailable: core.moduleAvailable,
            lang: theme_language,
            notification: {
                post: plugins.messenger.post,
                hideAll: plugins.messenger.toast.hideAll,
            },
            file_chooser: plugins.chooser.file,
            button: {
                progress: snippets.progressive_button,
                lock: snippets.button_lock,
            },
            rows: page_table_rows_control,
            document_title: theme_title_generate,
            update_mdata: core.updateModuleData,
            uri_param: uri_parse_param,
            error: connection_error,
            error_quota: theme_error_disk_quota_exceeded,
            event: {
                generate: event_generate
            },
            navigation: {
                reset: plugins.navigation.reset
            },
            plugin: {
                json_to_query: Convert.json_to_query,
                serialized_to_json: Convert.serialized_to_json,
                nice_size: Convert.nice_size,
                html_escape: Convert.htmlEscape,
                html_strip: Convert.htmlStrip,
                quote_escape: Convert.quoteEscape,
                arr_prepend: Convert.arrPrepend,
                quill_toolip: quill_toolbar_tooltip,
                setup_modifier: theme_modifiers_replace_for_mac,
                timestamp: snippets.datetime.locale,
                offset_adjust: page.handle.content.offset,
                preloader: {
                    hide: page.handle.content.preloader.hide,
                },
                dayjs: dayjs,

                select: (data, size = '34') => {
                    if (Array.isArray(data)) {
                        data[0].select2(data[1])
                        return
                    }
                    data.select2({
                        minimumResultsForSearch: 5,
                        containerCssClass: `select2-content heighter-${size}`,
                        dropdownCssClass: `select2-content h${size}`
                    });
                    data.next('.select2').addClass('select2-content-container')
                    data.on('select2:open', function() {
                        $('.select2-container').off('click.container')
                            .on('click.container', function(event) {
                                event.stopPropagation();
                            })
                    });
                },
                scroll: (target, options) => {
                    if (typeof target === 'string') {
                        $(target).mCustomScrollbar(options)
                    } else {
                        $(target[0]).mCustomScrollbar('scrollTo', target[1], {
                            scrollOffset: [$(target[0]), 3, 4]
                        })
                    }
                },
                arialabel: () => {
                    let arialabel = 'aria-label';
                    document.querySelectorAll('[data-tooltip="mailbox"]:not(' + arialabel + ')').forEach(
                        t => t.setAttribute(arialabel, t.getAttribute('data-title'))
                    )

                },
                tooltip: (target) => {
                    let $target = target || $('[data-tooltip="mailbox"]');
                    $target.tooltip({
                        html: true,
                        trigger: 'hover',
                        container: 'body',
                        sanitize: false,
                        delay: {
                            show: 600,
                            hide: 30
                        }
                    });
                }
            },
        },

        /* jshint +W117 */

        /**
         * Defines component template
         *
         * @since 19.20
         *
         * @return {string|object}
         */
        $$ = {
            $: {

                /**
                 * Returns set of selectors for generating layout
                 *
                 * @returns {string}
                 */
                layout: {
                    container: 'container-fluid',
                    controls: 'mail-controls',
                    panel: 'panel-mail panel-body',
                    row: {
                        controls: 'row row-controls',
                        messages: 'row row-messages colorify',
                        quota: 'row row-quota',
                        centered: 'row text-center',
                    },
                    column: {
                        3: 'col-xs-3',
                        4: 'col-xs-4',
                        6: 'col-xs-6',
                        8: 'col-xs-8',
                        9: 'col-xs-9',
                        12: 'col-xs-12',
                    },
                    button: {
                        link: 'btn btn-link text-decoration-none',
                        transparent: {
                            plain: 'btn btn-transparent',
                            link: 'btn btn-link btn-transparent',
                        },
                        default: 'btn btn-default',
                        primary: 'btn btn-primary',
                        block: {
                            default: 'btn btn-default btn-block',
                            transparent: 'btn btn-transparent btn-block'
                        },
                        dropdown: {
                            default: 'btn btn-default dropdown-toggle'
                        }
                    }
                },

                /**
                 * Returns used selectors for generating elements
                 *
                 * @returns {string}
                 */
                tree: {
                    container: 'data-mail-folders',
                    active: 'fancytree-active',
                    loader: 'fancytree-loader',
                    title: 'fancytree-title',
                    bubble: 'label label-danger',
                },
                controls: {
                    compose: {
                        button: '[data-compose]',
                        icon: 'fa-fw fa-plus',
                    },
                    select: {
                        dropdown: 'dropdown-select',
                        checkbox: '[data-select] input',
                        menus: '[data-select-mass]',
                    },
                    delete: 'btn btn-default fa fa-trash',
                    forward: 'btn btn-default fa fa-forward',
                    search: {
                        link: '[data-href^="sort.cgi"]',
                        clear: {
                            link: 'search-clear text-danger',
                            icon: 'fa-fw fa-times-circle-o',
                        },
                        dropdown: 'dropdown-search',
                        icon: 'fa-search',
                        data: {
                            form: {
                                action: 'data-form-action',
                                type: 'data-form-action-type',
                                advanced: 'data-form-action-advanced'
                            },
                        },
                        button: {
                            type: '[data-toggle-type="1"]',
                        },
                        caret: {
                            down: 'fa-caret-down',
                            up: 'fa-caret-up',
                        },
                        submit: '[data-search-submit]',
                    },
                    move: {
                        dropdown: 'dropdown-move',
                        checkbox: '[data-copy-only]',
                        icon: 'fa-folder-move',
                        submit: '[data-transfer-submit]',
                    },
                    more: {
                        dropdown: 'dropdown-more',
                        icon: 'fa-dots-vertical',
                        menu: {
                            read: '[data-form-action="markas1"]',
                            unread: '[data-form-action="markas0"]',
                            special: '[data-form-action="markas2"]',
                            spam: '[data-form-action="razor"]',
                            ham: '[data-form-action="ham"]',
                            black: '[data-form-action="black"]',
                            white: '[data-form-action="white"]',
                        },
                    },
                    sort: {
                        dropdown: 'dropdown-sort',
                        icon: 'fa-fw fa-sort',
                    },
                    counter: 'mail-selected-count',
                    refresh: {
                        button: 'btn btn-lg btn-default fa fa-refresh-mdi'
                    },
                    pagination: 'pagination-title',
                    settings: 'btn btn-default fa fa-cog'
                },
                messages: {
                    checkbox: 'input[data-check]',
                    flag: 'mail-list-trow-flag-security',
                    special: {
                        star: 'star',
                        starred: 'fa-star star',
                        unstarred: 'fa-star-o star',
                    },
                    row: {
                        empty: 'fa fa-fw fa-1_50x fa-inbox'
                    }
                },
                compose: {
                    button: {
                        inverse: 'btn-inverse',
                        submit: 'btn-primary',
                        schedule: 'btn-info',
                    },
                    hidden: 'hidden',
                    panel: {
                        content: 'jsPanel-content',
                        container: 'jspCompose',
                        container_shown: 'jspShown',
                        backdrop: 'compose_backdrop',
                    },
                    editor: {
                        compose: 'ql-compose',
                        composer: 'data-composer',
                        scheduled: 'scheduled',
                        content: 'ql-editor',
                        toolbar: 'ql-toolbar',
                        disabled: 'ql-disabled',
                        tb_bold: 'ql-bold',
                        tb_link: 'ql-link',
                        tb_image: 'ql-image',
                        controls: {
                            compose: 'compose-controls',
                            more: 'more-options',
                            extra: {
                                attach: 'e-attachment',
                                link: 'e-ql-link',
                                image: 'e-ql-image',
                                html: 'e-html',
                                discard: 'e-discard',
                            }
                        }
                    },
                    form: {
                        header: 'form-head',
                        recipients: {
                            control: 'recipients-control',
                            fields: 'recipients-control-fields',
                        },
                        name: {
                            tattach: 'tattachments',
                            scheduled: 'scheduled',
                        }
                    },
                    icons: {
                        upload: {
                            server: 'fa fa-fw fa-download-cloud',
                            attach: 'fa2 fa2-attach',
                        }
                    }
                },
                notification: {
                    danger: 'exclamation-triangle',
                    error: 'exclamation-circle',
                    success: 'check-circle',
                    type: {
                        search: 'search',
                        scheduled: 'clock',
                        trash: '- fa2 fa2-trash',
                    }
                },
                class: {
                    events_none: 'pointer-events-none',
                },

                /**
                 * Returns templates
                 *
                 * @returns {string}
                 */
                template: {
                    compose: (data) => {
                        let hidden = ' class="' + data.class.hidden + '"',
                            empty = String(),
                            status = {
                                server_file: empty,
                                abook: empty,
                                crypt: empty,
                                sign: empty,
                                dsn: empty,
                                del: empty,
                                menu: {
                                    server_file: empty,
                                    encrypt: empty,
                                    options: empty,
                                },
                            },
                            value = {
                                server_file: data.toggle.more.server_file,
                                crypt: data.toggle.more.crypt[0],
                                sign: data.toggle.more.sign[0],
                                abook: data.toggle.more.abook,
                                dsn: data.toggle.more.dsn,
                                del: data.toggle.more.del,
                            }

                        value.server_file === null && (status.server_file = hidden);
                        if (value.server_file === null) {
                            status.menu.server_file = hidden
                        }
                        value.crypt === null && (status.crypt = hidden);
                        value.sign === null && (status.sign = hidden);
                        if (value.crypt === null && value.sign === null) {
                            status.menu.encrypt = hidden
                        }

                        value.abook === null && (status.abook = hidden);
                        value.dsn === null && (status.dsn = hidden);
                        value.del === null && (status.del = hidden);
                        if (value.abook === null &&
                            value.dsn === null &&
                            value.del === null) {
                            status.menu.options = hidden
                        }

                        // Toolbar type depending on the mode
                        let toolbar;
                        if (data.toolbar_mode === 'basic') {
                            toolbar = `
                                <span class="ql-formats">
                                  <button class="ql-bold"></button>
                                  <button class="ql-italic"></button>
                                </span>
                                <span class="ql-formats">
                                  <select class="ql-color"></select>
                                </span>
                                <span class="ql-formats">
                                  <button class="ql-blockquote"></button>
                                </span>
                                <span class="ql-formats ${data.class.hidden}">
                                    <button class="ql-link"></button>
                                    <button class="ql-image"></button>
                                </span>
                            `;
                        } else if (data.toolbar_mode === 'advanced') {
                            toolbar = `
                                <span class="ql-formats">
                                    <select class="ql-font">
                                      <option value="initial" selected>${data.language._default}</option>
                                      <option value="monospace">${data.language._fixed_width}</option>
                                    </select>
                                    <select class="ql-size">
                                        <option value="0.75em">${data.language._font_size.small}</option>
                                        <option selected>${data.language._font_size.normal}</option>
                                        <option value="1.15em">${data.language._font_size.medium}</option>
                                        <option value="1.3em">${data.language._font_size.large}</option>
                                    </select>
                                    <select class="ql-header ql-paragraph">
                                        <option value="1">${data.language._heading} 1</option>
                                        <option value="2">${data.language._heading} 2</option>
                                        <option value="3">${data.language._heading} 3</option>
                                        <option value="4">${data.language._heading} 4</option>
                                        <option value="5">${data.language._heading} 5</option>
                                        <option value="6">${data.language._heading} 6</option>
                                        <option selected>${data.language._paragraph}</option>
                                    </select>
                                </span>
                                <span class="ql-formats">
                                    <button class="ql-bold"></button>
                                    <button class="ql-italic"></button>
                                    <button class="ql-underline"></button>
                                </span>
                                <span class="ql-formats">
                                    <select class="ql-color"></select>
                                    <select class="ql-background"></select>
                                </span>
                                <span class="ql-formats">
                                    <select class="ql-align"></select>
                                </span>
                                <span class="ql-formats">
                                    <span class="dropup">
                                        <button class="btn btn-default dropdown-toggle pd-0" type="button" id="extra-${data.id}" data-toggle="dropdown" aria-expanded="true">
                                          <span class="fa fa-lg fa-menu"></span>
                                        </button>
                                        <ul class="dropdown-menu pull-right" role="menu" aria-labelledby="extra-${data.id}">
                                          <li role="presentation">
                                            <button role="menuitem" tabindex="-1" class="ql-strike"></button>
                                          </li>
                                          <li role="presentation">
                                            <button role="menuitem" tabindex="-1" class="ql-blockquote"></button>
                                          </li>
                                          <li role="presentation">
                                            <button role="menuitem" tabindex="-1" class="ql-script" value="sub"></button>
                                          </li>
                                          <li role="presentation">
                                            <button role="menuitem" tabindex="-1" class="ql-script" value="super"></button>
                                          </li>
                                          <li role="presentation">
                                            <button role="menuitem" tabindex="-1" class="ql-list" value="ordered"></button>
                                          </li>
                                          <li role="presentation">
                                            <button role="menuitem" tabindex="-1" class="ql-list" value="bullet"></button>
                                          </li>
                                          <li role="presentation">
                                            <button role="menuitem" tabindex="-1" class="ql-indent" value="-1"></button>
                                          </li>
                                          <li role="presentation">
                                            <button role="menuitem" tabindex="-1" class="ql-indent" value="+1"></button>
                                          </li>
                                          <li role="presentation">
                                            <button role="menuitem" tabindex="-1" class="ql-code"></button>
                                          </li>
                                          <li role="presentation" class="${data.class.hidden}">
                                            <button role="menuitem" tabindex="-1" class="ql-link"></button>
                                          </li>
                                          <li role="presentation" class="${data.class.hidden}">
                                            <button role="menuitem" tabindex="-1" class="ql-image"></button>
                                          </li>
                                          <li role="presentation">
                                            <button role="menuitem" tabindex="-1" class="ql-direction" value="rtl"></button>
                                          </li>
                                          <li role="presentation">
                                            <button role="menuitem" tabindex="-1" class="ql-clean"></button>
                                          </li>
                                        </ul>
                                    </span>
                                </span>
                            `;
                        } else {
                            toolbar = `
                                <span class="ql-formats">
                                    <select class="ql-font">
                                        <option value="initial" selected>${data.language._default}</option>
                                        <option value="monospace">${data.language._fixed_width}</option>
                                    </select>
                                    <select class="ql-size">
                                        <option value="0.75em">${data.language._font_size.small}</option>
                                        <option selected>${data.language._font_size.normal}</option>
                                        <option value="1.15em">${data.language._font_size.medium}</option>
                                        <option value="1.3em">${data.language._font_size.large}</option>
                                    </select>
                                </span>
                                <span class="ql-formats">
                                    <button class="ql-bold"></button>
                                    <button class="ql-italic"></button>
                                    <button class="ql-underline"></button>
                                </span>
                                <span class="ql-formats">
                                    <select class="ql-color"></select>
                                    <select class="ql-background"></select>
                                </span>
                                <span class="ql-formats">
                                    <select class="ql-align"></select>
                                </span>
                                <span class="ql-formats">
                                    <button class="ql-blockquote"></button>
                                </span>
                                <span class="ql-formats">
                                    <button class="ql-clean"></button>
                                </span>
                                <span class="ql-formats ${data.class.hidden}">
                                    <button class="ql-link"></button>
                                    <button class="ql-image"></button>
                                </span>
                            `;
                        }

                        return `
                            <form class="compose" data-pjax="no" action="${data.prefix}/${data.target.send}?id=${data.id}" method="post" enctype="multipart/form-data" accept-charset="${data.charset}">
                                <div class="form-e">
                                    <div class="${data.class.form.header}">
                                        <div class="form-group from">
                                            <div class="flex">
                                                <div class="col-xs-1">
                                                    <label for="c-from-${data.id}">${data.language.real || data.language.from}</label>
                                                </div>
                                                <div class="col-xs-11">
                                                    <span class="btn-group ${data.class.form.recipients.control}">
                                                        <button type="button" class="btn btn-link btn-transparent-link btn-resized btn-link-bordered cc${data.toggle.recipients.cc}">Cc</button>
                                                        <button type="button" class="btn btn-link btn-transparent-link btn-resized btn-link-bordered bcc${data.toggle.recipients.bcc}">Bcc</button>
                                                    </span>
                                                    ${typeof data.from === 'object' ? 
                                                    `<div class="input-group c-from-input-group">
                                                        <input type="text" name="real" id="c-from-${data.id}" value="${data.from.name}" placeholder="${data.language._name}">
                                                        <span class="ltgt">&lt;</span><input type="text" name="user" value="${data.from.user}" placeholder="${data.language._username}">
                                                        <span class="input-group-addon">@${data.from.dom}&gt;</span>
                                                        <input type="hidden" name="dom" value="${data.from.dom}">
                                                    </div>` : 
                                                    data.from
                                                    }
                                                </div>
                                            </div>
                                        </div>
                                        <div class="form-group to">
                                            <div class="flex">
                                                <div class="col-xs-1">
                                                    <label for="c-to-${data.id}">${data.language.to}</label>
                                                </div>
                                                <div class="col-xs-11">
                                                    ${data.to}
                                                </div>
                                            </div>
                                        </div>
                                        <div class="${data.class.form.recipients.fields}">
                                            <div class="form-group cc${data.toggle.recipients.ccf}">
                                                <div class="flex">
                                                    <div class="col-xs-1">
                                                        <label for="c-cc-${data.id}">${data.language.cc}</label>
                                                    </div>
                                                    <div class="col-xs-11">
                                                        ${data.cc}
                                                    </div>
                                                </div>
                                            </div>
                                            <div class="form-group bcc${data.toggle.recipients.bccf}">
                                                <div class="flex">
                                                    <div class="col-xs-1">
                                                        <label for="c-bcc-${data.id}">${data.language.bcc}</label>
                                                    </div>
                                                    <div class="col-xs-11">
                                                        ${data.bcc}
                                                    </div>
                                                </div>
                                            </div>
                                        </div>
                                        <div class="form-group">
                                            <div class="flex">
                                                <div class="col-xs-1">
                                                    <label for="c-subject-${data.id}">${data.language.subject}</label>
                                                </div>
                                                <div class="col-xs-11">
                                                    ${data.subject}
                                                </div>
                                            </div>
                                        </div>
                                        <div class="form-group">
                                            <div class="flex attachments hidden">
                                                <div class="col-xs-1">
                                                    <label for="c-attach-${data.id}">${data.language._attachments}</label>
                                                </div>
                                                <div class="col-xs-11">
                                                    ${data.attachments}
                                                </div>
                                            </div>
                                        </div>
                                    </div>
                                    <div class="compose-controls-block">
                                        <div class="ql-compose-container">
                                          <textarea data-signature="${data.signature}" class="${data.status.text}" ${data.class.editor.composer}="text">\n\n\n${data.signature}</textarea>
                                          <div ${data.class.editor.composer}="html" class="ql-compose ql-container-toolbar-bottom ${data.status.html}">${data.body}</div>
                                          <div id="tb-${data.id}" data-mode="${data.toolbar_mode}">${toolbar}</div>
                                        </div>
                                        <div class="btn-group ${data.class.editor.controls.compose}">
                                          <button type="submit" class="btn btn-primary btn-progress">
                                            <span>
                                                <span>${data.language._send}</span>
                                                <span>
                                                    <span class="progressing"></span>
                                                </span>
                                            </span>
                                          </button>
                                          <button type="button" class="btn btn-primary dropdown-toggle ${data.status.module.schedule}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                                            <span class="fa fa-0_90x fa-clock"></span>
                                          </button>
                                          <ul class="dropdown-menu ${data.class.editor.scheduled} ${data.status.module.schedule}">
                                            <li><a>${$$.create.checkbox(0, 'scheduled', 1)}${data.language._scheduled}</a></li>
                                          </ul>
                                          <button type="button" class="btn btn-link btn-transparent-link ${data.class.editor.controls.extra.attach}" data-title="${data.language._attach}"><i class="fa-fw fa2 fa2-attach fa-md"></i></button>
                                          <button type="button" class="btn btn-link btn-transparent-link ${data.class.editor.controls.extra.link} ${data.status.html}" ${data.class.editor.composer}-h data-title="${data.language._insert_link}"><i class="fa-fw fa2 fa2-link fa-1_25x"></i></button>
                                          <button type="button" class="btn btn-link btn-transparent-link ${data.class.editor.controls.extra.image} ${data.status.html}" ${data.class.editor.composer}-h data-title="${data.language._insert_picture}"><i class="fa fa-fw fa-md fa-image"></i></button>
                                          <button type="button" class="btn btn-link btn-transparent-link ${data.class.editor.controls.extra.html}" data-title="${data.language._toggle}"><i class="fa fa-fw fa-md fa-html"></i></button>
                                        </div>
                                        <div class="btn-group ${data.class.editor.controls.compose} pull-right">
                                            <span class="dropup ${data.class.editor.controls.more}">
                                                <button class="btn btn-link btn-transparent-link dropdown-toggle" type="button" id="${data.class.editor.controls.more}-${data.id}" data-toggle="dropdown" aria-expanded="true">
                                                  <span class="fa fa-lg fa-dots-vertical"></span>
                                                </button>
                                                <ul class="dropdown-menu pull-right" role="menu" aria-labelledby="${data.class.editor.controls.more}-${data.id}">
                                                  <li${status.server_file} role="presentation"><a data-value="server-attach"><i class="fa fa-fw fa-download-cloud"></i>&nbsp;&nbsp;${data.language._server_attach}</a></li>
                                                  <li${status.menu.server_file} class="divider"></li>
                                                  <li${status.menu.encrypt} class="dropdown-submenu right" role="menu">
                                                      <a tabindex="-1">${data.language._encrypt}</a>
                                                      <ul class="dropdown-menu" role="menu" data-type="encrypt">
                                                        <li data-encrypt-container>
                                                            <div class="menu-group">
                                                                <div${status.sign}>
                                                                    <label>${data.language.sign}</label>
                                                                    ${data.toggle.more.sign[1]}
                                                                </div>
                                                                <div${status.crypt}>
                                                                    <label>${data.language.crypt}</label>
                                                                    ${data.toggle.more.crypt[1]}
                                                                </div>
                                                            </div>
                                                        </li>
                                                      </ul>
                                                  </li>
                                                  <li class="dropdown-submenu right" role="menu">
                                                      <a tabindex="-1">${data.language.pri.label}</a>
                                                      <ul class="dropdown-menu" role="menu" data-type="priority">
                                                        <li><a tabindex="-1" data-value="1">${data.language.pri.data[0]}</a></li>
                                                        <li><a tabindex="-1" data-value="2">${data.language.pri.data[1]}</a></li>
                                                        <li><a tabindex="-1"><i class="fa fa-fw fa-check pull-left"></i>${data.language.pri.data[2]}</a></li>
                                                        <li><a tabindex="-1" data-value="4">${data.language.pri.data[3]}</a></li>
                                                        <li><a tabindex="-1" data-value="5">${data.language.pri.data[4]}</a></li>
                                                      </ul>
                                                  </li>
                                                  <li${status.menu.options} class="divider"></li>
                                                  <li${status.menu.options} class="dropdown-submenu right" role="menu">
                                                      <a tabindex="-1">${data.language._options}</a>
                                                      <ul class="dropdown-menu" role="menu" data-type="options">
                                                        <li${status.abook}><a tabindex="-1">${$$.create.checkbox(0, 'abook', 1, 0, value.abook)}${data.language._addrecipients}</a></li>
                                                        <li${status.dsn}><a tabindex="-1">${$$.create.checkbox(0, 'dsn', 1, value.dsn)}${data.language._notifications_dsn}</a></li>
                                                        <li${status.del}><a tabindex="-1">${$$.create.checkbox(0, 'del', 1, value.del)}${data.language._notifications_del}</a></li>
                                                      </ul>
                                                  </li>
                                                </ul>
                                            </span>
                                            <button type="button" class="btn btn-link btn-transparent-link ${data.class.editor.controls.extra.discard}" data-title="${data.language._discard}"><i class="fa fa2 fa-fw fa-sm fa2-trash"></i></button>
                                        </div>
                                    </div>
                                </div>
                            </form>
                        `;
                    },
                    html: {
                        tags: {
                            br: '<br>',
                        }
                    }
                }

            },

            /**
             * Creates HTML element specified by type
             *
             * @since 19.20
             *
             * @param {string} class     Class name for created element
             * @param {mixed}  data      Data attributes to pass
             * @param {string} [type]    Used tag name
             * @param {string} [content] Content of created element
             * @param {string} [icon]    Icon class name to use
             * @param {string} [tooltip] Element tooltip title to show on hover
             */
            create: {

                /**
                 * Generates element with chosen tag name
                 *
                 * @returns {string}
                 */
                $: function(classes, data, type = 'div', content = String(), tooltip = String()) {
                    let attributes = this._attributes(data);

                    classes = this._classes(classes);

                    if (tooltip) {
                        tooltip = 'data-tooltip="mailbox" data-placement="bottom" data-title="' + tooltip + '"'
                    }
                    return '<' + type + ' ' + attributes + ' ' + tooltip + ' class="' + classes + '">' + content + '</' + type + '>';
                },

                /**
                 * Generates icon element
                 *
                 * @returns {string}
                 */
                icon: function(classes, attributes = String()) {
                    let attribute = this._attributes(attributes),
                        icon = this._classes(classes);
                    return '<i class="fa ' + icon + ' ' + attribute + '"></i>';
                },

                /**
                 * Generates button element
                 *
                 * @returns {string}
                 */
                button: function(classes, data, content, icon, tooltip) {
                    icon = this._classes(icon);
                    return this.$(classes, data, 'button', ((icon ? '<i class="fa ' + icon + '"></i>&nbsp;&nbsp;' : '') + content + ''), tooltip);
                },

                /**
                 * Generates input element
                 *
                 * @returns {string}
                 */
                input: function(name = String(), placeholder = String(), value = String(), type = 'text', attributes = String()) {
                    let attribute = this._attributes(attributes),
                        id = name;
                    if (typeof name === 'object') {
                        id = name[1];
                        name = name[0];
                    }
                    return '<input ' + attribute + ' type="' + type + '" name="' + name + '" id="' + id + '" placeholder="' + placeholder + '" value="' + value + '">';
                },

                /**
                 * Generates textarea element
                 *
                 * @returns {string}
                 */
                textarea: function(name = String(), placeholder = String(), value = String(), attributes = String()) {
                    let attribute = this._attributes(attributes),
                        id = name;
                    if (typeof name === 'object') {
                        id = name[1];
                        name = name[0];
                    }
                    return '<textarea ' + attribute + ' name="' + name + '" id="' + id + '" placeholder="' + placeholder + '">' + value + '</textarea>';
                },

                /**
                 * Generates label element
                 *
                 * @returns {string}
                 */
                label: function(target = String(), content = String(), attributes = String()) {
                    let attribute = this._attributes(attributes);
                    return '<label ' + attribute + ' for="' + target + '">' + content + '</label>';
                },

                /**
                 * Generates select element
                 *
                 * @returns {string}
                 */
                select: function(data, attributes = String(), name = String()) {
                    let attribute = this._attributes(attributes),
                        select = '<select ' + attribute + ' name="' + name + '">';
                    for (let [value, text] of Object.entries(data[0])) {
                        select += '<option value="' + value + '"' + (data[1] && data[1] == value ? ' selected' : String()) + '>' + text + '</option>';
                    }
                    select += '</select>';
                    return select;
                },

                /**
                 * Generates checkbox element
                 *
                 * @returns {string}
                 */
                checkbox: function(attributes = String(), name = String(), value = String(), label = '&nbsp;', checked = String()) {
                    let attribute = this._attributes(attributes),
                        checkbox = String(),
                        id = name + '-' + Math.floor(Math.random() * 9e10);

                    !label && (label = '&nbsp;');
                    checked && (checked = 'checked')
                    checkbox += '<span ' + attribute + ' class="awcheckbox awobject">';
                    checkbox += '<input class="iawobject" ' + checked + ' type="checkbox" name="' + name + '" value="' + value + '" id="' + id + '">';
                    checkbox += '<label class="lawobject" for="' + id + '">' + label + '</label>';
                    checkbox += '</span>';
                    return checkbox;
                },

                /**
                 * Generates radio element
                 *
                 * @returns {string}
                 */
                radio: function(attributes = String(), name = String(), value = String(), label = '&nbsp;', id = String(), checked = String()) {
                    let attribute = this._attributes(attributes),
                        checkbox = String();
                    checkbox += '<span ' + attribute + ' class="awradio awobject">';
                    checkbox += '<input class="iawobject" ' + checked + ' type="radio" name="' + name + '" value="' + value + '" id="' + id + '">';
                    checkbox += '<label class="lawobject" for="' + id + '">' + label + '</label>';
                    checkbox += '</span>';
                    return checkbox;
                },

                /**
                 * Generates dropdown element
                 *
                 * @returns {string}
                 */
                dropdown: function(classes, data, button, icon, tooltip, cbfunc) {
                    let dropdown = String();
                    classes = this._classes(classes);
                    dropdown += '<div class="btn-group ' + classes + '">';

                    if (button) {
                        if (/<[a-z][\s\S]*>/i.test(button)) {
                            dropdown += this.$('layout.button.default', false, 'span', button);
                        } else {
                            dropdown += this.button('layout.button.default', false, button);
                        }
                    }
                    dropdown += this.button('layout.button.dropdown.default', {
                        'toggle': 'dropdown'
                    }, '<span class="' + (icon ? ('fa ' + $$.$.controls[icon].icon) : 'caret') + '"></span>', false, tooltip);
                    dropdown += '<ul class="dropdown-menu" role="menu">';
                    for (let [i, v] of data[0].entries()) {
                        if (v) {
                            dropdown += '<li>' + v + '</li>';
                        }
                        if (data[i - 1] && data[1] && data[1] === (i + 1) && data[0].length > data[1]) {
                            dropdown += '<li role="separator" class="divider"></li>';
                        }
                    }
                    dropdown += '</ul>';
                    dropdown += '</div>';
                    if (typeof cbfunc === 'function') {
                        dropdown = cbfunc(dropdown);
                    }
                    return (data[0].length ? dropdown : String());
                },

                /**
                 * Converts passed object of attributes to string representation
                 *
                 * @param {object} data Array or hash to convert to string
                 *
                 * @example ['href="value"', 'title="value"'] or {type: value, title: value}.
                 *
                 * @returns {string}
                 */
                _attributes: function(data) {
                    let attributes = String();

                    if (Array.isArray(data)) {
                        attributes = data.join(' ');
                    } else if (data) {
                        attributes = Object.entries(data).map(([k, v]) => (attributes += (k.startsWith('data-') ? k : ('data-' + k)) + '=' + v + ' ')).slice(-1)[0];
                    }
                    return attributes;
                },

                /**
                 * Converts passed selector to correspondent class name from the component template
                 *
                 * @returns {string}
                 */
                _classes: function(classes) {
                    return classes ? (classes.split('.').reduce((a, b) => {
                        return a ? a[b] : undefined;
                    }, $$.$)) : String();
                },
            },

            /**
             * Returns selector name derived from the component template
             *
             * @returns {string}
             */
            selector: function(selector) {
                selector = this.create._classes(selector);
                return selector.startsWith('[') ? selector :
                    "." + selector.replace(/\s+/g, ".")

            },

            /**
             * Returns DOM object based on passed selector
             *
             * @returns {object}
             */
            element: function(element) {
                return element ? $(this.selector(element)) : String();
            },

        }
    /**
     * Imports configs
     */
    const config = {
            d: {},
            set: function(config) {
                this.d = config;
            }
        },

        /**
         * Load dependencies
         */
        bundles = {
            loaded: false,
            get: function(config) {
                if (typeof config !== 'object' ||
                    (typeof config === 'object' && config.d && !config.d.u)) {
                    return
                }
                this.loaded = true;
                let bundles = ['jquery.jspanel', 'quill:min'];
                ((config.d.u.html_edit_mode === 'advanced') &&
                    (bundles = _.plugin.arr_prepend('highlight/highlight:min', bundles)));
                _.load.bundle(bundles, 1);
            }
        },

        /**
         * Compose object sub-module ;;
         *
         * @since 19.40
         *
         * @return {object} Reveals compose module API
         */
        compose = (function() {
            let xtarget = {};
            xtarget.send = 'send_mail.cgi';
            xtarget.reply = 'reply_mail.cgi';

            // Loading bundles
            !bundles.loaded && bundles.get(config);

            /**
             * Creates new compose message dialog
             *
             * @param {object}  [form]   Refers to form data in case of replied message
             * @param {boolean} [inline] Returns composer without panel
             * @param {object}  [types]  Sets composer type for reply all and forward
             *
             * @returns {string}
             */
            const message = (form = false, inline = false, types = {}) => {
                let path = _.path.prefix,
                    cmodule = _.variable.module.name(),
                    prefix = `${path}/${cmodule}`;

                xtarget.getSize = `${path}/xhr.cgi?xhr-get_size=1&xhr-get_size_nodir=1&xhr-get_size_path=`;
                xtarget.delete = `${prefix}/delete_mail.cgi?confirm=1&delete=1&noredirect=1`;
                xtarget.schedule = `${path}/schedule/save.cgi`;
                xtarget.addressBook = `${prefix}/export.cgi?fmt=csv&dup=0&incgr=1`;

                if (typeof form === 'object' && form.length) {
                    form = $(form).serialize() + '&reply=1';
                    types.new = 0;
                } else {
                    form = 'new=1';
                    types.new = 1;
                }

                // Check for message type
                if (types.reply_all) {
                    form += '&rall=1';
                } else if (types.forward) {
                    form += '&forward=1';
                }

                // Get reply form as provided
                fetch(`${prefix}/${xtarget.reply}?${form}`, _.fetch.options)
                    .then(rs => {
                        return rs.text();
                    }).then(rs => {

                        // Reply data for further send mail
                        let $form = $(rs).find(`[action*="${xtarget.send}"]`),
                            generate = {
                                timestamp: () => {
                                    return _.plugin.dayjs().valueOf() * 1e2
                                },
                                random: () => {
                                    return Math.floor(Math.random() * 9e14);
                                }
                            },
                            id = generate.timestamp(),
                            form_data_lost = $form.find(':checkbox:not(:checked)').attr('value', '0').prop('checked', true).map(function() {
                                return this.name
                            }).get(),
                            form_data = $form.serialize(),
                            $form_textarea = $(rs).find('textarea[name="body"]'),
                            toolbar_mode = $form_textarea.data('html-mode'),
                            signature = $.trim(_.plugin.quote_escape($form_textarea.text()));

                        if  (config.d.u.html_edit == 2 && signature) {
                            signature = `${$$.$.template.html.tags.br.repeat(2) + signature}`;
                        }
                            
                        if (form_data) {
                            form_data = _.plugin.serialized_to_json(form_data);

                            let
                                // Object data for extracted fields
                                data = {
                                    visible: {},
                                    hidden: {},
                                },
                                classes = $$.$.compose,

                                toggle = {
                                    // Toggle visibility of extra fields and its controllers
                                    recipients: (id, data, data_visible) => {
                                        if (typeof data === 'object') {
                                            let target = data[0],
                                                state = data[1],
                                                rcs = `.${classes.form.recipients.control}`,
                                                rcsf = `.${classes.form.recipients.fields}`,
                                                rc = target.querySelector(rcs),
                                                rf = target.querySelector(rcsf);

                                            rc.querySelector(`.${id}`).classList.toggle(classes.button.inverse, !state)
                                            rf.querySelector(`.${id}`).classList.toggle(classes.hidden, state)
                                            return;
                                        } else if (data === 'rc') {
                                            return !data_visible[id] ? String() : ` ${classes.button.inverse}`
                                        } else if (data === 'rf') {
                                            return data_visible[id] ? String() : ` ${classes.hidden}`
                                        }
                                    },

                                    // Toggle visibility of attachment field
                                    attachments: (panel) => {
                                        let a = panel.querySelector(`[name="${classes.form.name.tattach}"]`),
                                            l = a.previousSibling.querySelectorAll('.tag').length;
                                        a.parentNode.parentNode.classList.toggle(classes.hidden, !l);
                                        adjust.contenteditable(panel);
                                    },

                                    // Add backdrop for maximized panel
                                    backdrop: (panel, show) => {
                                        let body = $('body'),
                                            re_zi = 99999,
                                            compose_backdrop = classes.panel.backdrop;

                                        if (show) {
                                            panel[0].dataset.zIndex = panel[0].style.zIndex;
                                            panel[0].style.zIndex = re_zi + 1;
                                            panel[0].setAttribute('maximized', 1);
                                            body.append(`<div class="modal-backdrop fade2 in zi-${re_zi} ${compose_backdrop}"></div>`)
                                        } else {
                                            if (panel[0]) {
                                                panel[0].style.zIndex = panel[0].dataset.zIndex;
                                                panel[0].removeAttribute('maximized');
                                                delete panel[0].dataset.zIndex;
                                            }
                                            body.find(`.modal-backdrop.${compose_backdrop}`).remove();
                                        }
                                    },

                                    // Toggle HTML/text input
                                    formatting: (target, status) => {
                                        let es = classes.editor.composer,
                                            eb = target.querySelectorAll(`[${es}-h]`),
                                            ed = target.querySelectorAll(`[${es}]`);

                                        eb.forEach((b) => {
                                            b.classList.toggle(classes.hidden, !status);
                                        })

                                        ed.forEach((e) => {
                                            if (e.getAttribute(es) === 'text') {
                                                e.classList.toggle(classes.hidden, status)
                                            } else {
                                                e.classList.toggle(classes.hidden, !status)
                                            }
                                        })
                                        adjust.contenteditable(target);
                                    },
                                },
                                adjust = {

                                    // Adjust the size of editable area
                                    contenteditable: (panel) => {
                                        let target = panel.querySelector(`.${classes.panel.content}`),
                                            container = target ? target.getBoundingClientRect().height : window.innerHeight / 4,
                                            top_block = panel.querySelector(`.${classes.form.header}`),
                                            compose_block = panel.querySelector('.compose-controls-block'),
                                            editor = panel.querySelector(`[${classes.editor.composer}]:not(.${classes.hidden})`),
                                            offset = 0,
                                            height,
                                            editor_content,
                                            top_block_height = top_block ? top_block.getBoundingClientRect().height : 0,
                                            editor_height = 0,
                                            compose_block_height = 0;

                                        if (!editor) {
                                            return;
                                        }

                                        editor_height = editor.getBoundingClientRect().height;
                                        compose_block_height = compose_block ? compose_block.getBoundingClientRect().height : 0;

                                        // Derive non-editor space from actual rendered block height.
                                        // This keeps HTML mode (extra toolbar row) and plain mode aligned.
                                        offset = Math.max(compose_block_height - editor_height, 0);
                                        height = Math.max(container - top_block_height - offset - 2, 120);
                                        editor.style.height = `${height}px`;

                                        // Prevent global Quill min-height rules from stretching the compose area.
                                        editor.style.minHeight = '0';
                                        editor_content = editor.querySelector(`.${classes.editor.content}`);
                                        if (editor_content) {
                                            editor_content.style.minHeight = '0';
                                        }
                                    },
                                },
                                check = {
                                    field: (field, object) => {
                                        let value = object[field];
                                        if (value && !isNaN(value)) {
                                            value = ~~value;
                                        }
                                        return typeof value === 'undefined' ? null : value
                                    },
                                },
                                element = {
                                    input: (str, data, readonly = false, no_escape = false, type = 'text') => {
                                        let value = (typeof data === 'object' ? data[str] : data);
                                        if (readonly) {
                                            readonly = ['readonly'];
                                        }
                                        if (!no_escape) {
                                            value = _.plugin.html_escape(value);
                                        }
                                        return $$.create.input([str, `c-${str}-${id}`], String(), value, type, readonly);
                                    },
                                    select: {},
                                    type: {
                                        time: () => {
                                            let ct = new Date(),
                                                format = (s) => {
                                                    return ('0' + s).substr(-2)
                                                },
                                                round = (m) => {
                                                    let r = Math.ceil(m / 10) * 10;
                                                    return r === 60 ? r - 5 : r;
                                                },
                                                h = format(ct.getHours()),
                                                m = round(format(ct.getMinutes()));
                                            return `<input type="time" name="time" step="300" value="${h}:${m}">`;
                                        },
                                        date: () => {
                                            let ct = new Date(),
                                                y = ct.getFullYear(),
                                                m = ct.getMonth() + 1,
                                                d = ct.getDate();
                                            return `<input type="text" name="date" data-value="${y}-${m}-${d}">`;
                                        }
                                    },
                                    composer: function(target) {
                                        let panel = target,
                                            paneled = panel.header ? true : false,
                                            config_html = {
                                                allowed: parseInt(data.hidden.html_edit),
                                                initial: parseInt(data.hidden.html_edit_config),
                                            },
                                            config_update = function(option, value) {
                                                _.update_mdata("/uconfig.cgi?mailbox", "/uconfig_save.cgi", {
                                                    [option]: value
                                                })
                                            },
                                            qs = Quill.import('attributors/style/size'),
                                            qf = Quill.import('attributors/style/font');

                                        // Quill: assign font-size and font-family, rather than using classes
                                        qs.whitelist = ["0.75em", "1.15em", "1.3em"];
                                        Quill.register(qs, true);
                                        qf.whitelist = ["monospace"];
                                        Quill.register(qf, true);

                                        // Redefine the actual target
                                        target = target[0];

                                        let asb = target.querySelector(`.${classes.form.header}`),
                                            ccs = target.querySelectorAll(`.${classes.editor.controls.compose}`),
                                            rcs = target.querySelector(`.${classes.form.recipients.control}`),
                                            qtg = target.querySelector(`.${classes.editor.compose}`),
                                            tcm = target.querySelector(`[${classes.editor.composer}="text"]`),
                                            editor = {
                                                this: new Quill(qtg, {
                                                    modules: {
                                                        syntax: typeof hljs === 'object',
                                                        imageDrop: true,
                                                        imageResize: {
                                                            modules: [
                                                                'DisplaySize',
                                                                'Resize',
                                                            ],
                                                        },
                                                        clipboard: {
                                                            matchVisual: false
                                                        },
                                                        toolbar: target.querySelector(`#tb-${id}`),
                                                    },
                                                    bounds: target,
                                                    theme: 'snow'
                                                }),
                                                get: {
                                                    text: () => {
                                                        return tcm.value
                                                    },
                                                    html: () => {
                                                        return editor.this.root.innerHTML
                                                    },
                                                    data: () => {
                                                        return config_html.allowed ? editor.get.html() : editor.get.text();
                                                    },
                                                },
                                                convert: () => {
                                                    let he = editor.this,
                                                        te = he.root.parentElement.previousElementSibling;

                                                    if (config_html.allowed) {
                                                        he.setText(te.value);
                                                    } else {
                                                        te.value = he.getText();
                                                    }
                                                },
                                                maximized: () => {
                                                    return target.hasAttribute('maximized');
                                                }
                                            },

                                            // Update message title dynamically
                                            title_update = function(ds) {
                                                let sf = asb.querySelector('[name="subject"]'),

                                                    // Trigger title update
                                                    ud = () => {
                                                        sf.dispatchEvent(new Event('input'));
                                                    },

                                                    // Change opacity for notifications
                                                    us = (tg, df) => {
                                                        if (paneled) {
                                                            tg.style.opacity = (df ? 0.7 : 1);
                                                        }
                                                    },

                                                    // Display draft processing notifications
                                                    du = (tg) => {

                                                        if (ds === 1) {
                                                            tg.textContent = _.lang('mail_composer_draft_saving');
                                                            us(tg, true);
                                                        } else if (ds === -1) {
                                                            tg.textContent = _.lang('mail_composer_draft_saved');
                                                            us(tg, true);

                                                            // Change status back to original title
                                                            setTimeout(() => {
                                                                us(tg);
                                                                ud();
                                                            }, 2e3)
                                                        }
                                                    }

                                                if (paneled) {
                                                    let pt = panel.header.title[0],
                                                        pti = pt.textContent;
                                                    if (ds) {
                                                        du(pt, pti);
                                                    } else {
                                                        sf.addEventListener('input', function() {
                                                            pt.textContent = this.value || pti;
                                                        })

                                                        // Update subject on initial load for replied mail
                                                        ud();
                                                    }
                                                }
                                            };

                                        paneled && target.classList.add(classes.panel.container, classes.panel.container_shown);
                                        adjust.contenteditable(target);

                                        // Reflect subject in panel title if exists
                                        title_update();

                                        // Toggle HTML/text editor state
                                        let ctl_tgl = ccs[0].querySelector(`.${classes.editor.controls.extra.html}`);
                                        ctl_tgl.addEventListener('click', () => {
                                            let st = parseInt(config_html.allowed) || 0,
                                                ia = parseInt(config_html.initial) || 0,
                                                sg = +!st,
                                                co = sg ? 2 : (ia === 1 ? 1 : 0);

                                            toggle.formatting(target, sg);
                                            config_html.allowed = sg;

                                            // Change actual config option
                                            config_update('html_edit', co);

                                            // Convert current message to make sure HTML is removed
                                            editor.convert();
                                        })

                                        // Event to automatically adjust adjust real name and username
                                        let from_from = target.querySelector('input[name="from"]:not(disabled)'),
                                            from_name = target.querySelector('input[name="real"]'),
                                            from_user = target.querySelector('input[name="user"]');
                                        if (from_name && from_user) {
                                            $.fn.eW = function(text, font) {
                                                if (!$.fn.eW.fakeEl) {
                                                    $.fn.eW.fakeEl = $('<span data-eW>').hide().appendTo(document.body);
                                                }
                                                $.fn.eW.fakeEl.text(text || this.val() || this.text() || this.attr('placeholder')).css('font', font || this.css('font'));
                                                return $.fn.eW.fakeEl.width() + 7;
                                            };
                                            [from_name, from_user].forEach((i, n) => {
                                                i.addEventListener('input', function() {
                                                    $(this).css({ width: parseInt($(this).eW() + (!n && 3)) })
                                                });
                                                i.dispatchEvent(new Event('input'));
                                            })
                                        }

                                        // Focus editable from input
                                        let from_focus = from_from || from_name;
                                        if (from_focus) {
                                            from_focus.focus();
                                            from_focus.setSelectionRange(-1, -1);
                                        }

                                        // Register controls and its events
                                        setTimeout(() => {
                                            let tb = editor.this.options.modules.toolbar.container,
                                                upload_list = [],
                                                server_list = [],
                                                priority = null,
                                                server_attach_previous = null,
                                                attachments = target.querySelector(`[name="${classes.form.name.tattach}"]`),
                                                content = target.querySelector(`.${classes.editor.content}`),
                                                ctl_att = ccs[0].querySelector(`.${classes.editor.controls.extra.attach}`),
                                                ctl_lnk = ccs[0].querySelector(`.${classes.editor.controls.extra.link}`),
                                                ctl_img = ccs[0].querySelector(`.${classes.editor.controls.extra.image}`),
                                                ctl_dis = ccs[1].querySelector(`.${classes.editor.controls.extra.discard}`),
                                                submit = target.querySelector('button[type="submit"]'),
                                                to_ = target.querySelector('input[name="to"]'),
                                                cc_ = target.querySelector('input[name="cc"]'),
                                                bcc_ = target.querySelector('input[name="bcc"]'),
                                                $more_options = $(target).find(`.${classes.editor.controls.more}`),

                                                scheduled = {
                                                    target: target.querySelector(`[name="${classes.form.name.scheduled}"]`),
                                                    container: target.querySelector(`.${classes.editor.scheduled}`),
                                                    events: function() {

                                                        // Event to prevent closing dropdown for scheduled mail
                                                        this.container.addEventListener('click', (event) => {
                                                            event.stopPropagation();
                                                        });

                                                        // Event to change send/scheduled label for submit button
                                                        this.checkbox().addEventListener('click', function() {
                                                            let s = submit,
                                                                t = s.querySelector('span').querySelector('span'),
                                                                ct = _.lang('mail_composer_schedule'),
                                                                c = this.checked,
                                                                sb = classes.button.submit,
                                                                sc = classes.button.schedule,
                                                                d = s.nextElementSibling,
                                                                st = language._send;

                                                            s.classList.toggle(sc, c);
                                                            s.classList.toggle(sb, !c);
                                                            d.classList.toggle(sc, c);
                                                            d.classList.toggle(sb, !c);
                                                            t.textContent = c ? ct : st;
                                                        });

                                                        // Initialize date picker
                                                        this.datepicker();
                                                    },
                                                    status: function() {
                                                        return this.target.checked;
                                                    },
                                                    checkbox: function() {
                                                        return this.container.querySelector('[type="checkbox"]');
                                                    },
                                                    holder: function() {
                                                        return this.container.querySelector('[data-t]');
                                                    },
                                                    datepicker: function() {

                                                        let tag = this.holder(),
                                                            input = tag.previousSibling;

                                                        // Event to handle change date for scheduled mail
                                                        tag.addEventListener('click', function() {
                                                            $(input).datepicker('show');
                                                        });

                                                        $(input).datepicker({
                                                            language: _.sdata("language"),
                                                            todayHighlight: true,
                                                            autoclose: true,
                                                            startDate: "0d",
                                                        }).on("changeDate", function(l) {
                                                            let today = _.lang('global_today').toLowerCase(),
                                                                tomorrow = _.lang('global_tomorrow').toLowerCase(),
                                                                label = today,
                                                                now = new Date(),
                                                                y = now.getFullYear(),
                                                                m = now.getMonth() + 1,
                                                                d = now.getDate(),
                                                                py = l.date.getFullYear(),
                                                                pm = l.date.getMonth() + 1,
                                                                pd = l.date.getDate(),
                                                                date = l.dates[0],
                                                                date_ = py + '-' + pm + '-' + pd,
                                                                date_formatted = dayjs(date).format(_.variable.locale.short);

                                                            this.dataset.value = date_;

                                                            if (
                                                                y === py && m === pm &&
                                                                (d === pd || d + 1 === pd)
                                                            ) {
                                                                if (d + 1 === pd) {
                                                                    label = tomorrow
                                                                }
                                                            } else {
                                                                label = date_formatted
                                                            }

                                                            tag.textContent = label
                                                        })
                                                    }
                                                },

                                                draft = {

                                                    timeout: {
                                                        update: null,
                                                        discard: null,
                                                    },

                                                    // Draft data
                                                    data: [],

                                                    // Reset draft data
                                                    reset: function() {
                                                        let folder = this.data[0];
                                                        this.data = [];
                                                        if (folder) {
                                                            this.data.push(folder);
                                                        }
                                                    },

                                                    // Test draft data
                                                    test: function() {
                                                        return this.data.length >= 1;
                                                    },

                                                    // Initiate draft save
                                                    save: function() {
                                                        this.terminate();
                                                        this.timeout.update = setTimeout(() => {
                                                            submit.dispatchEvent(new Event('click'));
                                                        }, 2e3);
                                                    },

                                                    // Terminate draft save
                                                    terminate: function() {
                                                        typeof this.timeout.update === 'number' &&
                                                            clearTimeout(this.timeout.update);
                                                    },

                                                    // Discard the draft
                                                    purge: function(id, folder, message) {
                                                        fetch(`${xtarget.delete}&id=${id}&folder=${folder}&d=${message}`, _.fetch.options).then(r => {
                                                            r.text().then(() => {
                                                                draft.refresh();
                                                            });
                                                        })
                                                    },

                                                    // Update draft folder's content if opened
                                                    refresh: function() {
                                                        if (this.test() && folders.check(this.data[0])) {
                                                            folders.refresh();
                                                        }
                                                    },

                                                    clean: function() {
                                                        this.test() && this.purge(this.data[0], this.data[1], this.data[3]);
                                                        this.reset();
                                                        this.terminate();
                                                    },

                                                    control: {

                                                        // Schedule draft for discard
                                                        discard: function() {
                                                            editor.maximized() && panel.normalize();
                                                            draft.timeout.discard = setTimeout(() => {
                                                                draft.test() && draft.purge(draft.data[0], draft.data[1], draft.data[3]);
                                                                draft.reset();
                                                                draft.terminate();
                                                                paneled && panel.close();
                                                            }, 5e3);
                                                        },

                                                        // Undo discarded draft
                                                        undo: function() {
                                                            target.classList.remove(classes.hidden);
                                                            typeof draft.timeout.discard === 'number' &&
                                                                clearTimeout(draft.timeout.discard);
                                                        },
                                                    }

                                                },

                                                // Process added attachment
                                                add_attachment = (type, id, filedata, size, update) => {

                                                    let icon = (type === 'server' ? classes.icons.upload.server : classes.icons.upload.attach),
                                                        name = filedata.name.split("/").pop() || filedata.name;

                                                    $(attachments).tagsinput('add', `[i class="${icon}"][/i]${name} [em](${_.plugin.nice_size(size)})[/em]`);

                                                    // Register reference for inserted tag
                                                    let tags = attachments.previousSibling.querySelectorAll('.tag'),
                                                        last = tags[tags.length - 1];
                                                    last.dataset.reference = id;

                                                    // Store uploaded/attached file
                                                    if (type === 'server') {
                                                        server_list[id] = filedata.name;
                                                    } else {
                                                        upload_list[id] = filedata.file;
                                                    }

                                                    // Update editor on last
                                                    if (update) {
                                                        adjust.contenteditable(target)
                                                        toggle.attachments(target);
                                                    }
                                                };

                                            // Editor Google Mail like key bind for creating numbered list
                                            editor.this.keyboard.addBinding({
                                                key: '7',
                                                shiftKey: true,
                                                ctrlKey: !_.platform.mac,
                                                metaKey: _.platform.mac,
                                            }, function(range, context) {
                                                const currentFormat = this.quill.getFormat(range.index);
                                                if (currentFormat.list === 'ordered') {
                                                    this.quill.format('list', false);
                                                } else {
                                                    this.quill.format('list', 'ordered');
                                                }
                                            });
                                            
                                            // Editor Google Mail like key bind for creating bullet list
                                            editor.this.keyboard.addBinding({
                                                key: '8',
                                                shiftKey: true,
                                                ctrlKey: !_.platform.mac,
                                                metaKey: _.platform.mac,
                                            }, function(range, context) {
                                                const currentFormat = this.quill.getFormat(range.index);
                                                if (currentFormat.list === 'bullet') {
                                                    this.quill.format('list', false);
                                                } else {
                                                    this.quill.format('list', 'bullet');
                                                }
                                            });

                                            // Editor Google Mail like key bind for quoting
                                            editor.this.keyboard.addBinding({
                                                key: '9',
                                                shiftKey: true,
                                                ctrlKey: !_.platform.mac,
                                                metaKey: _.platform.mac,
                                            }, function(range, context) {
                                                const currentFormat = this.quill.getFormat(range.index);
                                                if (currentFormat.blockquote) {
                                                    this.quill.format('blockquote', false);
                                                } else {
                                                    this.quill.format('blockquote', true);
                                                }
                                            });
                                            
                                            // Event for external insert link to editor button
                                            ctl_lnk.addEventListener('click', () => {
                                                tb.querySelector(`.${classes.editor.tb_link}`).dispatchEvent(new Event('click'));
                                            })

                                            // Event for external add images to editor button
                                            ctl_img.addEventListener('click', () => {
                                                tb.querySelector(`.${classes.editor.tb_image}`).dispatchEvent(new Event('click'));
                                            })

                                            // Event for discarding the draft
                                            ctl_dis.addEventListener('click', () => {
                                                draft.control.discard();
                                                let undo = {
                                                    cancel: {
                                                        label: _.lang('global_undo'),
                                                        action: function() {
                                                            draft.control.undo();
                                                        }
                                                    }
                                                };
                                                _.notification.post([$$.$.notification.type.trash, _.lang('mail_composer_discarded_draft')], 10, "warning", `discard-${id}`, 1, ['bottom', 'center'], undo);
                                                target.classList.add(classes.hidden);
                                            });

                                            // Event for controlling visibility of extra recipients fields
                                            rcs.querySelectorAll('button').forEach((b) => {
                                                b.addEventListener('click', () => {
                                                    let enabled = b.classList.contains(classes.button.inverse),
                                                        type = b.classList.contains("bcc") ? 'bcc' : 'cc';
                                                    toggle.recipients(type, [target, enabled]);
                                                    adjust.contenteditable(target);
                                                });
                                            })

                                            // Event for attaching new file(s)
                                            ctl_att.addEventListener('click', () => {
                                                let form = target.querySelector('form'),
                                                    xu = document.createElement('input');

                                                // Create temporary file input and listen for change
                                                xu.type = "file";
                                                xu.setAttribute('multiple', 1);
                                                xu.classList.add(classes.hidden);
                                                xu = form.appendChild(xu);
                                                xu.click();
                                                xu.addEventListener('change', function() {
                                                    Array.from(this.files).forEach((file, i, arr) => {
                                                        let fuid = generate.random() + i,
                                                            size = file.size,
                                                            name = file.name,
                                                            last = (i === arr.length - 1);
                                                        add_attachment('upload', fuid, { name: name, file: file }, size, last);
                                                        last && xu.remove();
                                                    })
                                                })
                                            })

                                            // Events to manage more options menu
                                            $more_options.find('.dropdown-menu').on("click.bs.dropdown", function(event) {
                                                let type = this.dataset.type,
                                                    etarget = event.target,
                                                    action = etarget.dataset.value;

                                                // Attach new file from server
                                                if (action === 'server-attach') {
                                                    let error = {
                                                        read: _.lang('mail_composer_server_attach_error_read'),
                                                        dir: _.lang('mail_composer_server_attach_error_dir')
                                                    };

                                                    _.file_chooser({
                                                        file: server_attach_previous
                                                    }).then(file => {
                                                        if (file) {
                                                            let suid = generate.random();
                                                            fetch(xtarget.getSize + file, _.fetch.options).then(r => {
                                                                r.text().then(rs => {
                                                                    let s = rs.split(`|`),
                                                                        size = s[1].replace(/\s+/g, String());
                                                                    if (size == -1 || size == -2) {
                                                                        let message = size == -1 ? error.read : error.dir
                                                                        _.notification.post([$$.$.notification.danger, message], 10, "error", 0, 1, ['bottom', 'center'])
                                                                    } else {
                                                                        add_attachment('server', suid, { name: file }, size, true);
                                                                    }
                                                                });
                                                            })
                                                        }
                                                        server_attach_previous = file;
                                                    });
                                                    return
                                                }

                                                // Prevent closing dropdown menu on click for all other
                                                event.stopPropagation();

                                                // Change message priority
                                                if (type === 'priority') {
                                                    let check = etarget.closest('ul').querySelector('i');
                                                    check.remove();
                                                    etarget.appendChild(check);
                                                    priority = action ? ~~action : null;
                                                }

                                                // Toggle options checkboxes
                                                if (type === 'options') {
                                                    let cb = etarget.querySelector('input[type="checkbox"]');
                                                    cb && (cb.checked ^= 1);
                                                }
                                            });

                                            // Init attachment tags input
                                            $(attachments).tagsinput({
                                                allowDuplicates: true,
                                                confirmKeys: [13],
                                                delimiter: '\\000',
                                            });

                                            // Remove actual attachments upon removing a tag
                                            $(attachments).on('itemRemoved', (event) => {
                                                let item = event.item[1];
                                                if (item) {
                                                    delete upload_list[item];
                                                    delete server_list[item];
                                                }
                                                toggle.attachments(target);
                                            });

                                            // Init tooltip for compose controls
                                            _.plugin.tooltip($(ctl_att)
                                                .add(ctl_img)
                                                .add(ctl_att)
                                                .add(ctl_lnk)
                                                .add(ctl_tgl)
                                                .add(ctl_dis)
                                            );

                                            // Create tooltip for editor controls
                                            _.plugin.quill_toolip($(editor.this.root).parent().parent()[0]);

                                            // Event to handle change in header
                                            asb.addEventListener('input', function() {

                                                // Save the draft
                                                draft.save();
                                            })

                                            // Event to prevent default submit on input fields
                                            asb.querySelectorAll('input').forEach((input) => {
                                                input.addEventListener('keydown', (event) => {
                                                    if (event.keyCode === 13) {
                                                        event.preventDefault();
                                                        return
                                                    }
                                                    draft.save();
                                                });
                                            })

                                            // Event to handle content change in HTML body
                                            editor.this.on('text-change', function() {

                                                // Save the draft
                                                draft.save();
                                            });

                                            // Event to handle content change in text body
                                            tcm.addEventListener('input', function() {

                                                // Save the draft
                                                draft.save();
                                            });

                                            // Scheduled mail events
                                            scheduled.events();

                                            // Bring address book autocompletion
                                            fetch(xtarget.addressBook, _.fetch.options)
                                                .then(function(rs) {
                                                    return rs.text();
                                                })
                                                .then(function(d) {

                                                    [to_, cc_, bcc_].forEach(input => {

                                                        // Bind tags input
                                                        let tags = $(input).tagsinput({
                                                            confirmKeys: [13, 32],
                                                            addOnBlur: false,
                                                            cancelConfirmKeysOnEmpty: false,
                                                            tagClass: 'label recipient'
                                                        });

                                                        // Initialize autocomplete on received data,
                                                        // if there is something in user's address book
                                                        let a = _.lang('theme_xhred_global_alias'),
                                                            b = d.match(/"(.*)","(.*)"/gm);
                                                        if (b) {
                                                            let book = [];
                                                            b.map(function(en) {
                                                                let gr = en.match(/"-","(.*)"/),
                                                                    em = en.match(/"(.*)","(.*)"/);
                                                                if (gr) {
                                                                    book.push(a + " <" + em[2] + ">");
                                                                } else if (em) {
                                                                    book.push(em[2] + " <" + em[1] + ">");
                                                                }
                                                            });

                                                            !$.isEmptyObject(book) && tags[0].$input.autocomplete({
                                                                lookup: book,
                                                                autoSelectFirst: true,
                                                                position: 'relative',
                                                                appendTo: tags[0].$container,
                                                                onSelect: function(m) {
                                                                    $(input).tagsinput('add', m.value);
                                                                    this.value = String();
                                                                }
                                                            });
                                                        }

                                                        $(input)

                                                            .on('itemAdded itemRemoved', function(event) {

                                                                // Soft validate email address
                                                                let email = event.item,
                                                                    contact;
                                                                if (email) {
                                                                    contact = email.match(/<(.*)>/);
                                                                    if (contact) {
                                                                        email = contact[1];
                                                                    }
                                                                    if (!event.item.startsWith(a) && event.type === 'itemAdded' && !/.+@.+\..+/.test(email)) {
                                                                        $(event.target.previousSibling).find('.recipient').last().addClass('error')
                                                                    }
                                                                }

                                                                // Adjust the container size on adding/removing recipient
                                                                adjust.contenteditable(target);

                                                            })

                                                        // Imitate tab keypress to generate the tag as well
                                                        tags[0].$input.on('keydown blur', function(event) {
                                                            let value = this.value;
                                                            if (event.keyCode === 9 || (event.type === 'blur' && event.relatedTarget)) {

                                                                // Dispatch event to complete the tag
                                                                $(this).trigger(_.event.generate('keypress', 32));

                                                                // Adjust the container size on adding/removing recipient
                                                                adjust.contenteditable(target);

                                                                if (value) {
                                                                    event.preventDefault();
                                                                }
                                                            }
                                                        });
                                                    })
                                                });

                                            // Submitting mail
                                            submit.addEventListener('click', function(event) {
                                                event.preventDefault();

                                                let form = this.closest('form'),
                                                    form_data = new FormData(form),
                                                    trusted = event.isTrusted || ~~submit.dataset.isTrusted,
                                                    draft_status = !trusted;

                                                // Reset trusted state for submit
                                                this.dataset.isTrusted = 0;

                                                // Terminate draft event in case mail is actually submitted
                                                if (trusted) {
                                                    draft.terminate();
                                                }

                                                // Add message body
                                                form_data.append('body', editor.get.data());

                                                // Set message priority
                                                let pri_key = 'pri'
                                                priority ? form_data.set(pri_key, priority) : form_data.delete(pri_key);

                                                // Add hidden entries except ones already in the form
                                                Object.entries(data.hidden).forEach((e) => {
                                                    let key = e[0],
                                                        value = e[1];
                                                    if (!form_data.has(key)) {
                                                        form_data.set(key, value)
                                                    }
                                                });

                                                // Add file uploads
                                                let fsus = Object.values(upload_list);
                                                fsus.length && fsus.forEach((f, i) => {
                                                    form_data.set(`attach${i}`, f)
                                                });

                                                // Add server attachments
                                                let ssus = Object.values(server_list);
                                                ssus.length &&
                                                    ssus.forEach((f, i) => {
                                                        form_data.set(`file${i}`, f)
                                                    });

                                                // Update HTML/text mode status
                                                form_data.set('html_edit', config_html.allowed);

                                                // Force disable spellcheck for new mail composer
                                                form_data.set('spell', 0);

                                                // Check for draft
                                                draft_status && (
                                                    form_data.set('new', 0),
                                                    form_data.set('enew', 1),
                                                    form_data.set('save', 1),
                                                    title_update(1)
                                                );

                                                // Prepare scheduled mail
                                                let schedule = {
                                                    date: {
                                                        get: function(d) {
                                                            let date = this.value,
                                                                t = scheduled.container.querySelector('[name="date"]');
                                                            if (t) {
                                                                date = t.dataset.value.split('-');
                                                            }
                                                            return d === 'y' ? ~~date[0] : d === 'm' ? ~~date[1] : ~~date[2];
                                                        }
                                                    },
                                                    time: {
                                                        value: scheduled.container.querySelector('[type="time"]').value,
                                                        get: function(t) {
                                                            let time = ['12', '00'];
                                                            if (this.value) {
                                                                time = this.value.split(':');
                                                            }
                                                            return t === 'h' ? ~~time[0] : ~~time[1];
                                                        }
                                                    }
                                                }

                                                if (scheduled.status() && !draft_status) {
                                                    let m = {
                                                        body: 'mail',
                                                        is_html: config_html.allowed,
                                                        delete_after: 1,
                                                        enabled: 1,
                                                        status: 1,
                                                        mode: 1,
                                                        hour: schedule.time.get('h'),
                                                        min: schedule.time.get('m'),
                                                        day: schedule.date.get('d'),
                                                        month: schedule.date.get('m'),
                                                        year: schedule.date.get('y'),
                                                    }

                                                    // Extens submitted form data
                                                    Object.entries(m).forEach(function(e, i) {
                                                        if (i) {
                                                            form_data.set(e[0], e[1])
                                                        } else {
                                                            form_data.set(e[1], form_data.get(e[0]));
                                                            form_data.delete(e[0]);
                                                        }
                                                    })
                                                }

                                                // Post mail data
                                                let xhr = new XMLHttpRequest(),
                                                    link = ((scheduled.status() && !draft_status) ? xtarget.schedule : form.getAttribute('action'));
                                                xhr.open("POST", link);
                                                xhr.setRequestHeader('x-requested-with', 'XMLHttpRequest');
                                                xhr.upload.onprogress = (e) => {
                                                    !draft_status &&
                                                        (
                                                            _.button.progress(this, Math.ceil((e.loaded / e.total) * 100)),
                                                            _.button.lock(this, true)
                                                        );
                                                };
                                                xhr.onload = (e) => {
                                                    let rs = e.target.responseText,
                                                        status = String(),
                                                        error = String(),
                                                        error_container = false,
                                                        parser = new DOMParser(),
                                                        _g = function(param) {
                                                            return _.uri_param(param, e.target.responseURL)
                                                        },
                                                        _d = {
                                                            id: _g('id'),
                                                            folder: {
                                                                index: _g('folder'),
                                                                type: _g('folder_type'),
                                                                id: _g('folder_id'),
                                                            },
                                                            input: {
                                                                id: form.querySelector('[name="id"]'),
                                                                folder: form.querySelector('[name="folder"]'),
                                                            },
                                                        };

                                                    // Handle previously saved draft
                                                    if (draft_status) {

                                                        // Update title
                                                        title_update(-1);

                                                        // Store reference for current draft
                                                        draft.data = [_d.folder.id, _d.folder.index, (_d.input.id && _d.input.id.value), _d.id];

                                                        // Clear previous drafts for IMAP and POP
                                                        if (_d.folder.type == 2 || _d.folder.type == 4) {
                                                            if (_d.input.id) {
                                                                draft.purge.apply(null, draft.data)
                                                            } else {
                                                                draft.refresh()
                                                            }
                                                        } else {

                                                            // Refresh drafts folder at once when mail can be edited
                                                            draft.refresh()
                                                        }

                                                        // Remove previously set data
                                                        _d.input.id && _d.input.id.remove();
                                                        _d.input.folder && _d.input.folder.remove();

                                                        // Update form data
                                                        form.insertAdjacentHTML('beforeend', element.input('id', _d.id, false, false, 'hidden'));
                                                        form.insertAdjacentHTML('beforeend', element.input('folder', _d.folder.index, false, false, 'hidden'));
                                                    } else {

                                                        // Handle responses
                                                        rs = parser.parseFromString(rs, 'text/html');
                                                        if (rs) {
                                                            rs = rs.querySelector('.panel-body'),
                                                                error_container = rs.querySelector('h3');
                                                            if (error_container) {

                                                                // Send error notification
                                                                error = error_container.innerHTML.replace(/\s:/, ':&nbsp;');
                                                                if (!_.error_quota(error_container.innerHTML)) {
                                                                    _.notification.post([$$.$.notification.danger, error], 10, "error", 0, 1, ['bottom', 'center']);
                                                                }

                                                                // Reset progress
                                                                _.button.progress(this, 0);

                                                                // Unlock button
                                                                _.button.lock(this, false)

                                                            } else {

                                                                // Send success notification
                                                                status = rs.innerHTML;
                                                                _.notification.post([scheduled.status() ? $$.$.notification.type.scheduled : $$.$.notification.success, status], 10, "success", 0, 1, ['bottom', 'center'])
                                                                paneled && panel.close();

                                                                // Delete previously stored draft message
                                                                draft.clean();
                                                            }
                                                        }
                                                    }
                                                }
                                                xhr.onerror = (e) => {

                                                    // Reset progress
                                                    _.button.progress(this, 0);

                                                    // Unlock button
                                                    _.button.lock(this, false)

                                                    // Display error message
                                                    _.error({
                                                        responseText: e.target.responseText,
                                                        status: xhr.status,
                                                        readyState: xhr.readyState,
                                                        _errConnectionReset: xhr.status === 0 ? true : false,
                                                    }, 1);
                                                }
                                                xhr.send(form_data);

                                            })

                                            // Submit mail using hotkey(%cmd-enter)
                                            target.addEventListener('keydown', e => {
                                                let meta = _.platform.mac ? e.metaKey : e.ctrlKey,
                                                    enter = e.keyCode === 13;
                                                if (meta && enter) {
                                                    submit.dataset.isTrusted = 1;
                                                    submit.dispatchEvent(new Event('click'));
                                                }
                                            });

                                        }, 3e2)
                                    },

                                },
                                language = {},
                                template = {};

                            // Group received fields
                            Object.entries(form_data).filter((f) => {
                                [
                                    'from',
                                    'real',
                                    'to',
                                    'cc',
                                    'bcc',
                                    'subject',
                                    'body'
                                ].includes(f[0]) ? (data.visible[f[0]] = f[1]) : (data.hidden[f[0]] = f[1]);
                            });

                            // Extract language strings for visible fields
                            Object.entries(data.visible).forEach((e) => {
                                let id = e[0];
                                language[id] = $form.find(`[name=${id}]`).parent().prev().text();
                            });

                            // Extract language strings for hidden fields
                            Object.entries(data.hidden).forEach(function(e) {
                                let id = e[0];
                                if (['crypt', 'sign'].includes(id)) {
                                    language[id] = $form.find(`[name=${id}]`).parent().prev().text();
                                } else if (['pri'].includes(id)) {
                                    let data = {};
                                    $form.find(`[name=${id}] option`).map(function(ix) {
                                        data[ix] = this.innerText
                                    });
                                    language[id] = { label: $form.find(`[name=${id}]`).parent().prev().text(), data: data };
                                }
                            });

                            // Extend language with more strings
                            language._attachments = _.lang('global_attachments');
                            language._send = _.lang('mail_composer_send');
                            language._scheduled = _.lang('mail_composer_scheduled')
                                .replace(/%1/, `<span data-i>${element.type.date()}<span data-t>${_.lang('global_today').toLowerCase()}</span></span>`)
                                .replace(/%2/, element.type.time());
                            language._attach = _.lang('mail_composer_attach');
                            language._insert_link =
                                _.plugin.setup_modifier(_.lang('editor_tb_link')
                                    .replace('%cmd', _.platform.mac ? 'Meta' : 'Ctrl'));
                            language._insert_picture = _.lang('editor_tb_image');
                            language._toggle = _.lang('mail_composer_toggle');
                            language._discard = _.lang('mail_composer_discard');
                            language._server_attach = _.lang('mail_composer_server_attach');
                            language._notifications = _.lang('global_notifications');
                            language._notifications_dsn = _.lang('mail_composer_notifications_dsn');
                            language._notifications_del = _.lang('mail_composer_notifications_del');
                            language._encrypt = _.lang('global_encrypt');
                            language._options = _.lang('global_options');
                            language._addrecipients = _.lang('mail_composer_addrecipients');
                            language._default = _.lang('global_default');
                            language._fixed_width = _.lang('editor_tb_font_fixed_width');
                            language._name = _.lang('mail_composer_real_name');
                            language._username = _.lang('mail_composer_username');
                            language._paragraph = _.lang('global_paragraph');
                            language._heading = _.lang('global_heading');
                            language._font_size = {
                                small: _.lang('global_small'),
                                normal: _.lang('global_normal'),
                                medium: _.lang('global_medium'),
                                large: _.lang('global_large'),
                            };

                            // Check if we have composable from email address 
                            let from_name = $form[0].querySelector(`input[name="real"]`),
                                from_user = $form[0].querySelector(`input[name="user"]`),
                                from_dom = $form[0].querySelector(`input[name="dom"]`),
                                from_composable;
                            if (from_dom) {
                                from_composable = {
                                    name: from_name.value,
                                    user: from_user.value,
                                    dom: from_dom.value,
                                }
                            }

                            // Check for form selects
                            element.select.from = $form[0].querySelector(`select[name="from"]`);
                            element.select.sign = $form[0].querySelector(`select[name="sign"]`);
                            element.select.crypt = $form[0].querySelector(`select[name="crypt"]`);
                            if (element.select.from) {
                                element.select.from = element.select.from.outerHTML;
                            }
                            if (element.select.sign) {
                                element.select.sign = element.select.sign.outerHTML;
                            }
                            if (element.select.crypt) {
                                element.select.crypt = element.select.crypt.outerHTML;
                            }

                            // Build mail form the template
                            template.form = $$.$.template.compose({
                                prefix: prefix,
                                target: {
                                    send: xtarget.send
                                },
                                charset: data.hidden.charset,
                                id: id,
                                class: classes,
                                language: language,
                                status: {
                                    text: ~~data.hidden.html_edit ? classes.hidden : String(),
                                    html: ~~data.hidden.html_edit ? String() : classes.hidden,
                                    module: {
                                        schedule: _.mavailable('schedule') ? String() : classes.hidden,
                                    }
                                },
                                toggle: {
                                    recipients: {
                                        cc: toggle.recipients('cc', 'rc', data.visible),
                                        bcc: toggle.recipients('bcc', 'rc', data.visible),
                                        ccf: toggle.recipients('cc', 'rf', data.visible),
                                        bccf: toggle.recipients('bcc', 'rf', data.visible),
                                    },
                                    more: {
                                        server_file: check.field('file0', data.hidden),
                                        abook: check.field('abook', data.hidden),
                                        dsn: check.field('dsn', data.hidden),
                                        del: check.field('del', data.hidden),
                                        sign: [check.field('sign', data.hidden), element.select.sign],
                                        crypt: [check.field('crypt', data.hidden), element.select.crypt],
                                        pri: check.field('pri', data.hidden),
                                    }
                                },
                                from: from_composable || element.select.from || element.input('from', data.visible, !~~config.d.g.edit_from),
                                to: element.input('to', data.visible),
                                cc: element.input('cc', data.visible),
                                bcc: element.input('bcc', data.visible),
                                subject: element.input('subject', data.visible),
                                attachments: element.input(classes.form.name.tattach, data.visible, false, true),
                                body: types.new == 1 ? signature : data.visible.body,
                                signature: signature,
                                toolbar_mode: toolbar_mode
                            })

                            if (inline) {
                                let inlne_form = inline.append(template.form);
                                element.composer(inlne_form);
                            } else {

                                // Create compose panel
                                let composers = $(`.${classes.panel.container} .${classes.editor.compose}`).length,
                                    window_width = window.innerWidth,
                                    small_window_width = window_width < 640,
                                    window_height = window.innerHeight,
                                    small_window_height = window_height < 640,
                                    small_window = small_window_width || small_window_height,
                                    ioffset = -15,
                                    offset = composers ? ioffset * 5 * composers : ioffset,
                                    position = small_window ? {} : { my: "right-bottom", at: "right-bottom", offsetX: offset, offsetY: offset },
                                    panel = $.jsPanel({
                                        position: position,
                                        theme: "#eaeaea",
                                        onwindowresize: true,
                                        panelSize: {
                                            width: (small_window ? window_width + 4 * ioffset : 600),
                                            height: (small_window ? window_height + 4 * ioffset : 600)
                                        },
                                        headerTitle: _.lang('mail_new_message'),
                                        content: template.form,
                                        maximizedMargin: {
                                            top: small_window ? -1 * ioffset : window_height * 0.03,
                                            bottom: small_window ? -1 * ioffset : window_height * 0.03,
                                            left: small_window ? -1 * ioffset : window_height * 0.1,
                                            right: small_window ? -1 * ioffset : window_height * 0.1,
                                        },
                                        footerToolbar: function() {},
                                        dblclicks: {
                                            title: "maximize"
                                        },
                                        onminimized: function() {
                                            toggle.backdrop(this);
                                        },
                                        onclosed: function() {
                                            toggle.backdrop(this);
                                        },
                                        onnormalized: function() {
                                            adjust.contenteditable(this[0]);
                                            toggle.backdrop(this);
                                        },
                                        onmaximized: function() {
                                            adjust.contenteditable(this[0]);
                                            toggle.backdrop(this, 1);
                                        },
                                        callback: function() {
                                            element.composer(this);
                                            if (small_window) {
                                                this.maximize();
                                            }
                                        },
                                    });

                                panel.header.title.addClass('plain');
                            }
                        }
                    });
            }

            // Reveal sub-modules ;;
            return {
                message: message,
            }
        })();

    /**
     * Messages object sub-module ;;
     *
     * @since 19.20
     *
     * @return {object}                    Reveals messages module API
     * @return {void} messages.get         Lists messages with default sorting
     * @return {void} messages.sort        Lists messages with requested sorting
     * @return {object} messages.storage   Accesses messages data storage
     */
    const messages = (function() {

        /**
         * Control the state of previous fetch call
         */
        const fetching = {
            state: false,
            initial: true,
            abort: function() {
                if (this.pending() === true &&
                    this.initial === false) {
                    this.state.abort();
                }
            },
            pending: function() {
                if (typeof this.state.state === "function" && this.state.state() === "pending") {
                    return true;
                }
                return false;
            },
        };

        /**
         * Fetches and renders list of messages for the given folder with particular pagination
         *
         * @returns {void}
         */
        const get = (data) => {
                loader.start();
                fetching.abort();
                fetching.state = $.post(_.path.extensions + '/mail/messages.cgi?' + _.plugin.json_to_query(data),
                    function(data) {
                        render(data);
                        loader.end();
                        _.document_title(0, _.lang('titles_mail'));

                        // Set received config data
                        config.set(data[0].config);

                        fetching.initial = false;

                        // Loading bundles
                        !bundles.loaded && bundles.get(config);
                    });
            },

            /**
             * Displays loader while loading messages
             *
             * @returns {void}
             */
            loader = {
                target: $$.selector('tree.active'),
                start: function() {
                    $(this.target).addClass($$.$.tree.loader)
                },
                end: function() {
                    $(this.target).removeClass($$.$.tree.loader)
                }
            },

            /**
             * Defines the length of the preview to request from the server
             *
             * @returns {number}
             */
            preview_length = () => {
                return parseInt($(window).width() / 10);
            },


            /**
             * Holds temporary data storage for managing selected messages across different pages
             *
             * @returns {object}
             */
            storage = {
                target: '[' + $$.$.tree.container + ']',
                counter: $$.selector('controls.counter'),

                /**
                 * Gets currently selected messages and its data
                 *
                 * @returns {object|array}
                 */
                get: function(status = 0) {
                    let data = $(this.target).data('messages') || {};
                    if (!status) {
                        data = Object.keys(data);
                    }
                    return data;
                },

                /**
                 * Stores just checked/unchecked message and its data
                 *
                 * Updates selected messages counter, and controls display
                 *
                 * @returns {void}
                 */
                set: function(id, state, status, starred, data) {
                    let storage = this.get(1);

                    // Process messages
                    state ? storage[id] = [+status, +starred] : delete storage[id]

                    // Set current messages storage
                    $(this.target).data('messages', storage);

                    // Update counter
                    let selected_count = Object.keys(storage).length;
                    $(this.counter).text(
                        (
                            selected_count ? (selected_count + ' ' + _.lang('global_selected')) : String()
                        )
                        .toLowerCase()
                    );

                    // Show/hide control row
                    let controls = $$.selector('layout.controls');
                    $(controls).toggleClass('hidden', !selected_count);

                },

                /**
                 * Restores messages selection upon listing
                 *
                 * @returns {void}
                 */
                restore: function() {
                    let data = this.get(),
                        checkboxes = $$.$.messages.checkbox;

                    $(checkboxes).filter((i, t) => {
                        data.includes(t.value) && $(t).prop('checked', 1)
                    }).promise().done(function() {
                        $(checkboxes).trigger('change');
                    });

                },

                /**
                 * Resets messages selection storage
                 *
                 * @returns {void}
                 */
                reset: function() {
                    let checkboxes = $$.$.messages.checkbox;
                    $(this.target).data('messages', {})
                    $(checkboxes + ':checked').prop('checked', 0).trigger('change');
                },
            },

            /**
             * Updates message(s) read/unread status
             *
             * @returns {object}
             */
            status = {

                /**
                 * Extracts action name based on type
                 *
                 * @returns {string}
                 */
                action: function(action, string = false) {
                    action = parseInt((action).replace(/^\D+/g, ''));
                    if (string) {
                        return action ? 'read' : 'unread';
                    }
                    return action
                },

                /**
                 * Sets message read/unread state in UI
                 *
                 * @returns {void}
                 */
                set: function(action, messages) {
                    let $messages = $($$.$.messages.checkbox).filter((i, c) => {
                            return messages.includes(c.value)
                        }),
                        $targets = $messages.parent().parents('td').parents('tr');
                    $targets.attr('data-unread', +!this.action(action))
                },

                /**
                 * Writes message read/unread state to the server
                 *
                 * @returns {void}
                 */
                write: function(data, messages = false) {
                    let folder_index = data[1].searched_folder_index || data[1].folder_index;

                    if (messages) {
                        let action = this.action(data[0], 1);
                        messages = `&d=${messages.join('&d=')}`;
                        $.post(_.path.extensions + '/mail/message.cgi?folder=' + folder_index + '&mark=' + action + messages + '');

                    } else {
                        let action = this.action(data[0], 1),
                            server = data[1],
                            messages = storage.get(1),
                            starred = {
                                read: [],
                                unread: []
                            };

                        // Filter out starred messages
                        $.each(messages, function(i, o) {
                            if (o[1] === 1) {
                                o[0] === 1 ? starred.unread.push(i) : starred.read.push(i)
                                delete messages[i];
                            }
                        });

                        // Submit ordinary data
                        submit(server, {
                            [data[0]]: 1
                        }, Object.keys(messages), 0, 1);

                        // Submit data for incompatible states
                        let link = _.path.extensions + '/mail/message.cgi?folder=' + folder_index + '&mark=starred&state=' + action + '';
                        starred.read.length &&
                            $.post(link + `&d=${starred.read.join('&d=')}` + '');
                        starred.unread.length &&
                            $.post(link + '' + `&d=${starred.unread.join('&d=')}` + '');

                    }
                },

            },

            /**
             * Register events
             *
             * @returns {void}
             */
            events = (data) => {

                // Import targets
                let button = {
                        compose: $$.$.controls.compose.button,
                        search: $$.element('controls.search.dropdown'),
                        refresh: $$.element('controls.refresh.button'),
                        delete: $$.element('controls.delete'),
                        forward: $$.element('controls.forward'),
                        special: {
                            star: $$.selector('messages.special.star'),
                            starred: $$.selector('messages.special.starred'),
                            unstarred: $$.selector('messages.special.unstarred'),
                        }
                    },
                    dropdown = {
                        mark: {
                            read: $$.element('controls.more.menu.read'),
                            unread: $$.element('controls.more.menu.unread'),
                            special: $$.element('controls.more.menu.special'),
                            spam: $$.element('controls.more.menu.spam'),
                            ham: $$.element('controls.more.menu.ham'),
                            black: $$.element('controls.more.menu.black'),
                            white: $$.element('controls.more.menu.white'),
                        },
                        select: $$.selector('controls.select.dropdown'),
                        move: $$.element('controls.move.dropdown'),
                        search: $$.element('controls.search.dropdown')
                    },
                    checkbox = $($$.$.controls.select.checkbox),
                    checkboxes = $$.$.messages.checkbox,
                    flags = $$.selector('messages.flag'),
                    evt = {
                        click: 'click',
                        change: 'change',
                        keyup: 'keyup',
                        shown_bs_drpd: 'shown.bs.dropdown',
                    };

                /**
                 * Event listeners for selecting all messages
                 *
                 * @returns {void}
                 */
                checkbox.off(evt.change).on(evt.change, function() {
                    let $this = $(this),
                        state = $this.is(':checked');
                    $(checkboxes).prop('checked', state).trigger(evt.change);
                }).parent().parent().off(evt.click).on(evt.click, function(event) {
                    let $input = $(this).find('input');
                    !$(event.target).is($input) && $input.prop('checked', !$input.is(':checked')).trigger(evt.change);
                })

                /**
                 * Event listeners for selecting all messages
                 *
                 * @returns {void}
                 */
                $(flags).off(evt.click).on(evt.click, function(event) {
                    event.stopPropagation()
                })


                /**
                 * Event listener for selecting specific type of multiple messages
                 *
                 * @returns {void}
                 */
                $(dropdown.select).find($$.$.controls.select.menus).off(evt.click).on(evt.click, function(event) {
                    let _$ = $(event.target).data('type'),
                        $_ = $(checkboxes),
                        $__ = evt.change,
                        __$ = 'checked';

                    // Select all/none
                    if (_$ === 5 || _$ === 4) {
                        $_.prop(__$, (_$ & 1)).trigger($__);
                    }
                    // Select invert
                    else if (_$ === 3) {
                        $_.prop(__$, function() {
                            return !this.checked
                        }).trigger($__);
                    }
                    // Select read/unread
                    else if (_$ === 2 || _$ === 1) {
                        $_.prop(__$, function() {
                            return +$(this).parents('tr').attr('data-unread') === (_$ & 1)
                        }).trigger($__);
                    }
                    // Select starred (special)
                    else if (_$ === 0) {
                        $_.prop(__$, function() {
                            return +$(this).parents('tr').attr('data-starred') === +!(_$ & 1)
                        }).trigger($__);
                    }
                })

                /**
                 * Event listener for selecting single message
                 *
                 * Updates the storage data
                 *
                 * @returns {void}
                 */
                $(checkboxes).off(evt.change).on(evt.change, function() {
                    let $this = $(this),
                        $row = $this.parents('td').parent('tr'),
                        state = $this.is(':checked'),
                        id = $this.val(),
                        checked = (checkboxes + ':checked'),
                        status = $row.attr('data-unread'),
                        starred = $row.attr('data-starred');
                    storage.set(id, state, status, starred, data);
                    $(checked).length === $(checkboxes).length ? checkbox.prop('checked', 1) : checkbox.prop('checked', 0);
                });

                /**
                 * Event listener for deleting message(s)
                 *
                 * @returns {void}
                 */
                button.delete.off(evt.click).on(evt.click, function() {
                    submit(data, {
                        'delete': 1
                    }, storage.get(), 1, 1)
                });

                /**
                 * Event listener for moving/copying message(s)
                 *
                 * @returns {void}
                 */
                let $dropdown_move_select = dropdown.move.find('select');
                $dropdown_move_select.find('option').map((i, o) => {
                    o.value <= -1 && o.remove()
                })
                _.plugin.select($dropdown_move_select);
                $dropdown_move_select.on(evt.change, function() {
                    setTimeout(() => {
                        $($$.$.controls.move.submit).toggleClass('disabled', !this.value).trigger('focus');
                    });
                })
                dropdown.move.find('li').off(evt.click).on(evt.click, function(event) {
                    event.stopPropagation();
                    let $target = $(event.target),
                        $submit = $($$.$.controls.move.submit),
                        $copy = $($$.$.controls.move.checkbox),
                        copy = $copy.is(':checked');

                    if ($target.is($copy)) {
                        $submit.text(copy ? _.lang('global_copy') : _.lang('global_move'));
                    }

                    if ($target.is('button:not(.disabled)')) {
                        let action = copy,
                            target = parseInt($dropdown_move_select.val());

                        submit(data, {
                            [(action ? 'copy' : 'move') + '1']: 1,
                            mfolder1: target
                        }, storage.get(), (+!action || ((data.searched_folder_index || data.folder_index) === target)), 1)

                        dropdown.move.removeClass('open')
                    }
                })
                dropdown.move.off(evt.shown_bs_drpd).on(evt.shown_bs_drpd, function() {
                    _.plugin.select([$dropdown_move_select, 'open']);
                })

                /**
                 * Event listener for forwarding message(s)
                 *
                 * @returns {void}
                 */
                button.forward.off(evt.click).on(evt.click, function() {
                    const messages_ids = storage.get().map(function (message_id) {
                            return encodeURIComponent(message_id);
                        }),
                        messages_link = `&mailforward=${messages_ids.join("&mailforward=")}`,
                        reply = _.path.origin + _.path.prefix + "/mailbox/reply_mail.cgi?folder=",
                        data = JSON.parse(fetching.state.responseText)[0];
                    _.pjax.fetch(reply + data.folder_index + messages_link);
                });

                /**
                 * Event listener for search
                 *
                 * @returns {void}
                 */
                let $dropdown_search_select = dropdown.search.find('select'),
                    $dropdown_search_simple = dropdown.search.find('[data-search-mail]'),
                    $dropdown_search_advanced_all = dropdown.search.find('[name="search-wordsin"]');

                // Set current folder first
                if (data && $dropdown_search_select.length) {
                    $dropdown_search_select[0].value = data.searched_folder_index || data.folder_index;
                }

                // Initialize folders select
                _.plugin.select($dropdown_search_select);
                button.search.find('li').off(evt.click + ' ' + evt.keyup).on(evt.click + ' ' + evt.keyup, function(event) {
                    event.stopPropagation();
                    let $target = $(event.target),
                        $advanced_form = dropdown.search.find('[' + $$.$.controls.search.data.form.advanced + ']'),
                        advanced_form_hidden = () => window.getComputedStyle($advanced_form[0]).display === 'none';

                    // Close and return
                    if (event.keyCode === 27) {
                        button.search.trigger('click')
                        return

                    }
                    // Show/hide advanced search options
                    if (
                        (event.keyCode === 32 ||
                            event.keyCode === 9 ||
                            event.keyCode === 13 ||
                            event.type === 'click') &&
                        $target.is($($$.selector('controls.search.caret.down')).add($$.selector('controls.search.button.type')))
                    ) {
                        let $caret = $target.is('i') ? $target : $target.find('i');
                        $caret.toggleClass($$.$.controls.search.caret.up)
                        $advanced_form.toggleClass('show');
                        $dropdown_search_simple[0].disabled = !advanced_form_hidden();

                        let focus_target = advanced_form_hidden() ? $dropdown_search_simple : $advanced_form[0].querySelector('input');
                        focus_target.focus();

                        if (advanced_form_hidden()) {
                            $dropdown_search_simple.val($dropdown_search_advanced_all.val())
                            $dropdown_search_advanced_all.val(String())
                        } else {
                            $dropdown_search_advanced_all.val($dropdown_search_simple.val())
                            $dropdown_search_simple.val(String())
                        }
                        return
                    }

                    // Submit search query
                    if (
                        (event.type === 'keyup' && event.keyCode === 13 && $target.is('[type="text"], [type="number"]')) ||
                        (event.type === 'click' && $target.is('button:not(.disabled)'))
                    ) {
                        let simple_query = {
                            folder: parseInt($dropdown_search_select[0].value),
                            search: $dropdown_search_simple[0].value
                        };

                        // Submit simple search query
                        if (advanced_form_hidden()) {
                            if (simple_query.search) {
                                $.post(_.path.prefix + '/' + _.variable.module.name() + '/mail_search.cgi?returned_format=json&json-error=1&simple=1&' + _.plugin.json_to_query(simple_query), function(data) {
                                    messages.get(data);
                                });
                            }
                        }

                        // Submit advanced search query
                        else {
                            let $elements_input = $advanced_form.find('input[type="text"]').filter((i, v) => v.value),
                                $elements_radios_status = $advanced_form.find('input[name="status"]'),
                                $elements_limit = $advanced_form.find('input[name^="limit"]'),
                                $elements_attach = $advanced_form.find('input[name="attach"]'),
                                query = {};

                            // Default query params
                            query.all = 1;
                            query.dest_def = 1;
                            query.folder = simple_query.folder;

                            // Create query for all input fields
                            for (let i = 0; i < $elements_input.length; i++) {
                                let value = $elements_input[i].value,
                                    special = $elements_input[i].name.includes('words'),
                                    name = $elements_input[i].name.replace('search-', String());
                                if (value) {
                                    query['what_' + i] = value;
                                    if (special) {
                                        query['field_' + i] = 'all';
                                        query['neg_' + i] = ~~$elements_input[i].name.includes('out');
                                    } else {
                                        query['field_' + i] = name;
                                        query['neg_' + i] = 0;
                                    }
                                }
                            }

                            // Create query with status radios
                            query.status_def = 0;
                            for (let i = 0; i < $elements_radios_status.length; i++) {
                                let $this = $elements_radios_status[i],
                                    value = $this.value,
                                    def = value == -1 ? 1 : 0;
                                if ($this.checked) {
                                    query.status_def = def;
                                    if (!def) {
                                        query.status = value;
                                    }
                                }
                            }

                            // Create query with limit radios
                            for (let i = 0; i < $elements_limit.length; i++) {
                                let $this = $elements_limit[i],
                                    name = $this.name;
                                if ($this.checked && $this.name === 'limit_def' || $this.name !== 'limit_def') {
                                    query[name] = $this.value
                                }
                            }

                            // Create query with attachment status
                            query.attach = ~~$elements_attach[0].checked;

                            // Run the query
                            $.post(_.path.prefix + '/' + _.variable.module.name() + '/mail_search.cgi?returned_format=json&json-error=1&' + _.plugin.json_to_query(query), function(data) {
                                messages.get(data);
                            });
                        }
                    }
                });

                dropdown.search.off(evt.shown_bs_drpd).on(evt.shown_bs_drpd, function() {
                    $dropdown_search_simple.trigger('focus');
                })

                /**
                 * Event listener for refreshing messages list
                 *
                 * @returns {void}
                 */
                button.refresh.off(evt.click).on(evt.click, function() {
                    $$.element('tree.active').click()
                })

                /**
                 * Event listener for marking message starred/unstarred (toggle special state)
                 *
                 * @returns {void}
                 */
                $(button.special.star).off(evt.click).on(evt.click, function(event) {
                    event.stopImmediatePropagation();
                    let $this = $(this),
                        $row = $(event.target).parents('td').parent('tr'),
                        target = $$.$.messages.special,
                        id = $row.find('input[value]').val(),
                        state = $(event.target).is($(button.special.starred)) ? 1 : 0,
                        unread = +$row.attr('data-unread'),
                        text = _.lang('global_' + (state ? 'unstarred' : 'starred') + '');

                    $row.attr('data-starred', +!state);

                    // Submit changes and toggle state
                    submit(data, {
                        ['markas' + (state ? 1 : 2) + '']: 1
                    }, [id])
                    $this
                        .removeClass(target[(state ? 'starred' : 'unstarred')])
                        .addClass(target[(state ? 'unstarred' : 'starred')])
                        .attr('data-original-title', text)
                        .next().remove();

                    // Write message status (redundant)
                    status.write([(+!unread).toString(), data], [id]);
                });

                /**
                 * Event listener for marking message(s) read/unread
                 *
                 * @returns {void}
                 */
                dropdown.mark.read
                    .add(dropdown.mark.unread)
                    .off(evt.click)
                    .on(evt.click, function() {
                        let action = $(this).data('form-action'),
                            messages = storage.get();

                        // Write message status (redundant)
                        status.write([action, data]);

                        // Change messages UI state
                        status.set(action, messages);
                    });

                /**
                 * Event listener for reporting spam/ham and whitelisting/blacklisting message(s)
                 *
                 * @returns {void}
                 */
                dropdown.mark.spam
                    .add(dropdown.mark.ham)
                    .add(dropdown.mark.black)
                    .add(dropdown.mark.white)
                    .off(evt.click)
                    .on(evt.click, function() {
                        let action = $(this).data('form-action'),
                            messages = storage.get(),
                            refetch = /razor|black/.test(action);

                        submit(data, {
                            [action]: 1
                        }, messages, +refetch, 1);
                    })

                /**
                 * Event listener for running search
                 *
                 * @returns {void}
                 */
                $($$.$.controls.search.link).off(evt.click).on(evt.click, function() {
                    let link = this.getAttribute('data-href');
                    fetch(link, _.fetch.options)
                        .then(function(response) {
                            return response.json();
                        })
                        .then(function(data) {
                            messages.get(data)
                        });
                })

                /**
                 * Event listener for composing new message
                 *
                 * @returns {void}
                 */
                $(folders.data.selector.navigation)
                    .off(evt.click, button.compose)
                    .on(evt.click, button.compose, function() {
                        compose.message();
                    })
            },

            /**
             * Submits changes to the server
             *
             * @param {object} data      Response object with data for current page
             * @param {object} actions   Action(s) to be submitted
             * @param {object} messages  Array of message ids to process
             * @param {int}    [refetch] Refetch current folder's content from the server
             * @param {int}    [reset]   Reset message selection
             *
             * @returns {void}
             */
            submit = (data, actions, messages, refetch = 0, reset = 0) => {
                let form = data.form_list,
                    target = _.variable.module.link() + `/${form.target}?`,
                    hidden = form.hidden,
                    searched_index = data.searched_folder_index,
                    mail_system = parseInt(data.mail_system);

                hidden = _.plugin.json_to_query(hidden) + '&noredirect=1&';

                // Focus actual folder instead of virtual
                if (searched_index && (mail_system === 2 || mail_system === 4)) {
                    hidden = hidden.replace(/folder=\d+/, `folder=${searched_index}`)
                }

                actions = _.plugin.json_to_query(actions);
                messages = `&d=${messages.join('&d=')}`;
                refetch && (loader.start(), _.notification.hideAll());
                $.post(target + hidden + actions + encodeURI(messages), function() {
                    if (reset) {
                        storage.reset();
                    }
                    if (refetch || data.folder_counts_allowed) {
                        fetching.abort();
                        $.post(_.path.extensions + '/mail/messages.cgi?' + hidden + 'show_body_len=' + preview_length() + '', function(data) {
                            render(data);
                            loader.end();
                        });
                    }
                });
            },

            /**
             * Render row
             *
             * @param {string} text
             * @param {string} icon
             *
             * @returns {string}
             */
            row = (text, icon) => {

                let row = String(),
                    centered_row = $$.create.$('layout.row.centered');

                row = $(centered_row)
                    .append((icon ? $$.create.icon(icon) : String()) +
                        '<div class="text-uppercase"> ' + text + ' </div>');

                return row;

            },

            /**
             * Render messages and controls
             *
             * @param {object} source Response object with data for current page
             *
             * @returns {void}
             */
            render = (source) => {
                let container = $$.element('layout.container'),
                    data = source[0],
                    messages_list = ((data.list.messages && data.form_list.buttons) ? data.list.messages.replace(/�/g, '') : String());

                // Check for errors first
                if (data.error) {
                    let errors = data.error.error;
                    for (let i = 0; i < errors.length; i++) {
                        _.notification.post([$$.$.notification.error, errors[i]], 20, "error", i, 1, ['bottom', 'center']);
                    }

                    // If redirect requested, follow it
                    if (data.redirect) {
                        fetching.abort();
                        _.pjax.fetch(data.redirect);
                    }
                    return
                }

                let messages_list_available = messages_list.length > 128 ? 1 : 0;
                if (!messages_list_available && data.searched) {
                    _.notification.post([$$.$.notification.type.search, _.lang('mail_search_empty')], 5, "info", 0, 1, ['bottom', 'center'])
                    return
                }

                // Empty current panel and define target
                container.empty().append($$.create.$('layout.panel'));
                let panel = container.find($$.selector('layout.panel'));

                // Inject data to the panel
                if (messages_list_available) {
                    let controls = {
                            select: data.form_list.buttons.select,
                            submit: data.form_list.buttons.submit
                        },
                        pagination = {
                            link: (data.pagination_arrow_last || data.pagination_arrow_first || String()),
                            title: (data.pagination_arrow_last ? _.lang('mail_pagination_last') : (data.pagination_arrow_first ? _.lang('mail_pagination_first') : false))
                        }

                    panel
                        .append($$.create.$('layout.row.controls'))
                        .find($$.selector('layout.row.controls'))
                        .append($$.create.$('layout.column.6'), $$.create.$('layout.column.6'))
                        .find($$.selector('layout.column.6')).first()
                        .append($$.create.dropdown('controls.select.dropdown', [
                            [
                                controls.select.all,
                                controls.select.none,
                                controls.select.invert,
                                controls.select.read,
                                controls.select.unread,
                                controls.select.special
                            ], 3
                        ], $$.create.checkbox({
                            select: 1
                        }), String(), _.lang('global_select')));

                    let $form_controls = $($$.create.$('layout.controls', {
                        'form-controls': 1
                    }, 'div'));
                    Object.entries(controls.submit).map(([type, data]) => {
                        for (let [i, v] of data.entries()) {
                            if (type === 'buttons') {
                                $form_controls.append($$.create.$('controls.' + v[0], {
                                    'form-control': v[0]
                                }, 'span', String(), _.lang('global_' + v[0] + '')));

                            } else if (type === 'dropdowns') {
                                for (let [di, dd] of v.entries()) {
                                    let entries = [];
                                    for (let [index, data] of v[1].entries()) {
                                        data[0] && entries.push($$.create.$(0, {
                                            'form-action': data[0]
                                        }, 'span', data[1]));
                                    }
                                    if (typeof dd === "string") {
                                        $form_controls.append(
                                            $$.create.dropdown('controls.' + dd + '.dropdown', [
                                                entries, 2
                                            ], 0, dd, _.lang('mail_' + dd + '') || _.lang('global_' + dd + ''))

                                        )
                                    }
                                }
                            }
                        }
                    });

                    panel
                        .find($$.selector('layout.column.6')).first()
                        .append(
                            $form_controls,
                            $$.create.$('controls.refresh.button', {
                                'refresh': 1
                            }, 'button', String(), _.lang('global_refresh')),
                            $$.create.dropdown('controls.sort.dropdown', [
                                [
                                    data.list.sort.date,
                                    data.list.sort.from,
                                    data.list.sort.size,
                                    data.list.sort.subject,
                                    data.list.sort.spam,
                                ], 5
                            ], data.list.sorted, 'sort', _.lang('global_sort'), function(dd) {
                                if (dd && dd.match(/<li.*?<a/)) { return dd; }
                                return String();
                            }),
                            $$.create.dropdown('controls.search.dropdown', [
                                [
                                    $$.create.$(0, {
                                            [$$.$.controls.search.data.form.action]: 'search',
                                            [$$.$.controls.search.data.form.type]: 'simple'
                                        }, 'span',
                                        (
                                            $$.create.input('search', _.lang('mail_search_search_mail'), String(), 'text', {
                                                'search-mail': 1
                                            }) +
                                            $$.create.button('layout.button.transparent.link', {
                                                'toggle-type': 1
                                            }, String(), 'controls.search.caret.down')
                                        )
                                    ),
                                    $$.create.$(0, {
                                            [$$.$.controls.search.data.form.action]: 'search',
                                            [$$.$.controls.search.data.form.advanced]: 1
                                        }, 'span',
                                        (
                                            $$.create.$('layout.column.3', {}, 'span', $$.create.label('search-from', _.lang('mail_search_from'))) +
                                            $$.create.$('layout.column.9', {}, 'span', $$.create.input('search-from'))
                                        )
                                    ),
                                    $$.create.$(0, {
                                            [$$.$.controls.search.data.form.action]: 'search',
                                            [$$.$.controls.search.data.form.advanced]: 1
                                        }, 'span',
                                        (
                                            $$.create.$('layout.column.3', 0, 'span', $$.create.label('search-to', _.lang('mail_search_to'))) +
                                            $$.create.$('layout.column.9', 0, 'span', $$.create.input('search-to'))
                                        )
                                    ),
                                    $$.create.$(0, {
                                            [$$.$.controls.search.data.form.action]: 'search',
                                            [$$.$.controls.search.data.form.advanced]: 1
                                        }, 'span',
                                        (
                                            $$.create.$('layout.column.3', 0, 'span', $$.create.label('search-subject', _.lang('mail_search_subject'))) +
                                            $$.create.$('layout.column.9', 0, 'span', $$.create.input('search-subject'))
                                        )
                                    ),
                                    $$.create.$(0, {
                                            [$$.$.controls.search.data.form.action]: 'search',
                                            [$$.$.controls.search.data.form.advanced]: 1
                                        }, 'span',
                                        (
                                            $$.create.$('layout.column.3', 0, 'span', $$.create.label('search-wordsin', _.lang('mail_search_has_words'))) +
                                            $$.create.$('layout.column.9', 0, 'span', $$.create.input('search-wordsin'))
                                        )
                                    ),
                                    $$.create.$(0, {
                                            [$$.$.controls.search.data.form.action]: 'search',
                                            [$$.$.controls.search.data.form.advanced]: 1
                                        }, 'span',
                                        (
                                            $$.create.$('layout.column.3', 0, 'span', $$.create.label('search-wordsout', _.lang('mail_search_doesnt_have_words'))) +
                                            $$.create.$('layout.column.9', 0, 'span', $$.create.input('search-wordsout'))
                                        )
                                    ),
                                    $$.create.$(0, {
                                            [$$.$.controls.search.data.form.action]: 'search',
                                            [$$.$.controls.search.data.form.advanced]: 1
                                        }, 'span',
                                        (
                                            $$.create.$('layout.column.3', 0, 'span', $$.create.label('search-status', _.lang('mail_search_with_status'))) +
                                            $$.create.$('layout.column.9', 0, 'span',
                                                $$.create.radio(0, 'status', -1, _.lang('mail_search_with_status_any'), 'status_def', 'checked') +
                                                $$.create.radio(0, 'status', 0, _.lang('mail_search_with_status_unread'), 'status0') +
                                                $$.create.radio(0, 'status', 1, _.lang('mail_search_with_status_read'), 'status1') +
                                                $$.create.radio(0, 'status', 2, _.lang('mail_search_with_status_special'), 'status2')
                                            )
                                        )
                                    ),
                                    $$.create.$(0, {
                                            [$$.$.controls.search.data.form.action]: 'search',
                                            [$$.$.controls.search.data.form.advanced]: 1,
                                            [$$.$.controls.search.data.form.type]: 'search-in'
                                        }, 'span',
                                        (
                                            $$.create.$('layout.column.3', 0, 'span', $$.create.label('search-folder', _.lang('mail_search_search_in'))) +
                                            $$.create.$('layout.column.9', 0, 'span', data.form_list.buttons.submit.dropdowns[0][1][0][1])
                                        )
                                    ),
                                    $$.create.$(0, {
                                            [$$.$.controls.search.data.form.action]: 'search',
                                            [$$.$.controls.search.data.form.advanced]: 1,
                                            [$$.$.controls.search.data.form.type]: 'limit'
                                        }, 'span',
                                        (
                                            $$.create.$('layout.column.3', 0, 'span', $$.create.label('search-wordsout', _.lang('mail_search_limit_results'))) +
                                            $$.create.$('layout.column.9', 0, 'span',
                                                $$.create.radio(0, 'limit_def', 1, _.lang('global_no'), 'limit_def0', 'checked') +
                                                $$.create.radio(0, 'limit_def', 0, _.lang('mail_search_limit_results_yes') + ' ' + $$.create.input(
                                                    'limit', '', 20, 'number', ['step="10"', 'min="10"']) + ' latest messages', 'limit_def1')
                                            )
                                        )
                                    ),
                                    $$.create.$(0, {
                                            [$$.$.controls.search.data.form.action]: 'search',
                                            [$$.$.controls.search.data.form.advanced]: 1,
                                            [$$.$.controls.search.data.form.type]: 'attach'
                                        }, 'span',
                                        (
                                            $$.create.checkbox(0, 'attach', 1, _.lang('mail_search_has_attach'))
                                        )
                                    ),
                                    $$.create.$(0, {
                                            [$$.$.controls.search.data.form.action]: 'search',
                                            [$$.$.controls.search.data.form.advanced]: 1,
                                            [$$.$.controls.search.data.form.type]: 'submit'
                                        }, 'span',
                                        (
                                            $$.create.button('layout.button.primary', false, _.lang('global_search'), 'controls.search.icon')
                                        )
                                    )
                                ], 5
                            ], (data.searched_message ?
                                $$.create.$(
                                    'controls.search.clear.link', ['href="index.cgi?folder=' + data.searched_folder_index + '"'],
                                    'a',
                                    ($$.create.icon('controls.search.clear.icon') + ' ' + data.searched_message.toLowerCase()),
                                    _.lang('mail_search_clear')) : String()
                            ), 'search', _.lang('global_search')),
                            $$.create.$('controls.counter', 0, 'span')
                        )
                        .end().last()
                        .append(
                            $$.create.$('controls.pagination', (pagination.link ? ['href="' + pagination.link + '"', 'data-href="' + pagination.link + '"'] : false), 'a', data.pagination_message, pagination.title),
                            data.pagination_arrow_left,
                            data.pagination_arrow_right
                        )

                    panel
                        .append($$.create.$('layout.row.messages')).find($$.selector('layout.row.messages'))
                        .append($$.create.$('layout.column.12')).find($$.selector('layout.column.12'))
                        .append(messages_list)

                    if (data.quota) {
                        panel
                            .append($$.create.$('layout.row.quota')).find($$.selector('layout.row.quota')).last()
                            .append($$.create.$('layout.column.12')).find($$.selector('layout.column.12'))
                            .append(data.quota)
                    }

                    _.plugin.timestamp();
                    _.plugin.arialabel();
                    _.plugin.tooltip();
                    _.plugin.offset_adjust(true);
                    _.rows();
                    folders.set(data);
                    folders.update(data);
                    events(data);
                    messages.storage.restore();
                    messages.refresh(panel);

                } else {
                    events();
                    panel.append(row((data.folder_index === 0 ? _.lang('mail_no_new_mail') : _.lang('mail_no_mail')), 'messages.row.empty'));
                    folders.update(data);
                }

                // Dismiss the loader for all calls
                setTimeout(() => {
                    _.plugin.preloader.hide();
                }, 2e2);
            },

            /**
             * Set interval for automatic messages update
             *
             * @returns {void}
             */
            refresh = function(panel, stop) {
                typeof this.refreshTimer === "number" && clearInterval(this.refreshTimer);

                // Clear timer and return
                if (stop) {
                    return;
                }

                // Register last interaction time for smother UX
                let last_interaction_time = Date.now();
                panel[0].addEventListener('mousemove', function() {
                    last_interaction_time = Date.now();
                })

                // Update messages, if conditions are met
                this.refreshTimer = setInterval(() => {
                    let refreshing = () => {
                        !fetching.pending() && panel.find($$.element('controls.refresh.button')).trigger('click');
                    }
                    if (config.d.u) {
                        clearInterval(this.refreshTimer);
                        if (config.d.u.refresh) {

                            // Perform actual refresh
                            this.refreshTimer = setInterval(() => {
                                // Stop refresh if there is no mail list
                                if (!document.querySelector(`.${panel[0].classList[0]}`)) {
                                    this.refresh(false, true);
                                }

                                // Refresh the page if user is not interacting with the page
                                let is_active_search = $$.element('controls.search.clear.icon').length,
                                    is_checked = panel.find('[name="d"]:checked').length,
                                    is_pagination = panel.find('[href*="index.cgi"][href*="start=0"]').length,
                                    is_open = panel.find('.open').length;
                                if (!is_checked && !is_pagination && !is_open && !is_active_search) {
                                    refreshing();
                                }
                            }, parseInt(config.d.u.refresh) * 1000);
                        } else {
                            this.refresh(false, true);
                        }
                    }
                }, 1e2)
            }

        // Reveal sub-modules ;;
        return {
            get: get,
            storage: storage,
            refresh: refresh,
            events: events,
        }
    })();

    /**
     * Folders sub-module ;;
     *
     * @since 19.17
     *
     * @return {object}                    Reveals folders module API
     * @return {void}   folders.get        Retrieve mail folders
     * @return {void}   folders.set        Mark folder as active
     * @return {void}   folders.adjust     Adjust active folder into view
     * @return {object} folders.data       Returns module properties
     */
    const folders = (function() {
        let

            // Define module static properties
            data = {
                file: {
                    fancytree: 'jquery.fancytree'
                },
                selector: {
                    navigation: 'aside .navigation'
                },
                options: {
                    tree: {
                        escapeTitles: false,
                        autoActivate: false,
                        autoScroll: true,
                        keyboard: false,
                        toggleEffect: false,
                    },
                    scroll: {
                        axis: 'x',
                        theme: 'minimal',
                        keyboard: false,
                        scrollInertia: 300,
                        scrollButtons: true,
                        autoHideScrollbar: false,
                    }
                },
                plugin: {
                    tree: (source) => {

                        if (!$.fn.fancytree) {
                            setTimeout(() => {
                                data.plugin.tree(source);
                            }, 4e2);
                            return;
                        }
                        let sourceTreeF = source === 'get' ? 'getTree' : null,
                            sourceActiveNodeF = source === 'node' ? 'getActiveNode' : null;
                        source = (
                            source === 'get' ? 'getTree' :
                            (source === 'node' ? 'getActiveNode' :
                                Object.assign(data.options.tree, {
                                    source: source,
                                    scrollParent: $('[' + $$.$.tree.container + ']'),
                                    click: (e, d) => {
                                        if (d.targetType === 'title') {
                                            setTimeout(() => {
                                                tree.adjust();
                                            }, 1e2);
                                            _.pjax.fetch(data.url.link + encodeURIComponent(d.node.key));
                                            messages.storage.reset();
                                            _.navigation.reset();
                                        }
                                    }
                                })));
                        if ($(tree.container).length) {
                            return sourceTreeF ?
                                $.ui.fancytree.getTree($(tree.container)) :
                                sourceActiveNodeF ?
                                $.ui.fancytree.getTree($(tree.container)).getActiveNode() :
                                $(tree.container).fancytree(source);
                        }
                    }
                },
                url: {
                    link: _.path.origin + _.path.prefix + '/mailbox/index.cgi?id=',
                }
            };

        /**
         * Tree sub-module ;;
         *
         * @return {string|function}
         */
        let tree = {
            fetched: 0,
            container: '[' + $$.$.tree.container + ']',
            container_adjust: function() {
                let container = $(this.container + ' >:first'),
                    content = $(this.container + ' >>:first');
                if (container.height() > content.height()) {
                    container.css('height', content.height())
                }
            },
            init: function(source) {

                // Load dependencies
                if (this.fetched === 0) {
                    this.load();
                    return;
                }

                // Insert tree container and compose button
                if ($(data.selector.navigation + ' ' + this.container).length === 0) {
                    $(data.selector.navigation).prepend('<li><div ' + $$.$.tree.container + '></div></li>');
                    $(data.selector.navigation).prepend('<li>' + $$.create.$('layout.button.block.transparent', {
                        'compose': 1
                    }, 'span', $$.create.icon('controls.compose.icon') + " " + _.lang('mail_new_message')) + '</li>');
                } else {
                    return;
                }

                // Instantiate tree
                data.plugin.tree(source);

                // Make the container scrollable
                _.plugin.scroll(this.container, data.options.scroll);

                // Adjust container height
                this.container_adjust();
            },
            expand: function(node) {
                let expanded = node.isExpanded();
                !expanded && node.toggleExpanded();
            },
            load: function() {
                this.fetched = 1;
                _.load.bundle(data.file.fancytree, 1, (_.variable.switch() ? [get] : 0), 1);
            },
            reload: function(source) {
                let tree = data.plugin.tree('get');
                tree.$container.empty();
                tree.reload(source);
                setTimeout(() => {
                    this.adjust();
                }, 1e2);
            },
            node: function() {
                return data.plugin.tree('node');
            },
            adjust: function() {
                let $_ = this.node();
                if ($_ && $_.li && $($_.li).length) {
                    _.plugin.scroll([this.container, $($_.li)]);
                }
                this.container_adjust();
            }
        }

        /**
         * Retrieves mail folders
         *
         * @param {string} [key] Folder name to be set as active
         *
         * @return {void}
         */
        const get = (key) => {
            key = key ? ('?key=' + key.replace(/&/g, '%26')) : String();
            $.post(_.path.extensions + '/mail/folders.cgi' + key + '', function(source) {
                if (!!key) {
                    tree.reload(source);
                } else {
                    tree.init(source);
                }
            });
        }

        /**
         * Mark mail folder as active
         *
         * @param {string|object} key Extract folder name to set as active
         *
         * @return {void}
         */
        const set = function(key) {
            let tree = data.plugin.tree('get');

            // Detect source 
            if (typeof key === 'object') {
                let search = {
                        id: key.searched_folder_id,
                        file: key.searched_folder_file,
                    },
                    id = key.folder_id;

                // Set active folder
                if (search.file && search.id != null && key.mail_system != 2 && key.mail_system != 4) {
                    key = search.file
                } else {
                    key = search.id || id;
                }

            }

            if (typeof tree === 'object' && typeof tree.activateKey === 'function') {
                tree.activateKey(false);
                tree.activateKey(key);
            } else {
                setTimeout(() => {
                    this.set(key);
                }, 1e2);
            }
        }

        /**
         * Refreshes mail in currently selected folder
         *
         * @returns {void}
         */
        const refresh = () => {
            let node = tree.node();
            node.span.click();
        }

        /**
         * Update mail folder unread counter
         *
         * @param {object} data Response object with data for current page
         *
         * @return {void}
         */
        const update = function(data) {
            let allowed = data.folder_counts_allowed,
                unread_count = data.unread,
                $node_titles = $($$.selector('tree.title')),
                active_node = $$.selector('tree.active'),
                node_bubble = $$.selector('tree.bubble'),
                $active_node_title = $(active_node).find($$.selector('tree.title')),
                $active_node_bubble = $(active_node).find(node_bubble);

            // Update active folder counter
            if (allowed) {
                if (unread_count) {
                    if ($active_node_bubble.length) {
                        $active_node_bubble.text(unread_count)
                    } else {
                        $active_node_title.append($$.create.$('tree.bubble', false, 'span', unread_count))
                    }
                } else {
                    $active_node_bubble.remove();
                }
            } else {
                $node_titles.find(node_bubble).remove();
            }
        }

        /**
         * Check if selected folder is what we are looking for
         *
         * @param {string} folder Folder name to check if currently selected
         *
         * @return {boolean}
         */
        const check = function(folder) {
            let node = tree.node();
            if (node && node.key === folder) {
                return true;
            }
            return false;
        }

        /**
         * Adjust folders into view
         *
         * @return {void}
         */
        const adjust = () => {
            tree.adjust();
        }

        // Reveal sub-modules ;;
        return {
            get: get,
            set: set,
            refresh: refresh,
            update: update,
            check: check,
            adjust: adjust,
            data: data
        }
    })();

    // Reveal modules (API) ;;
    return {
        folders: {
            get: folders.get,
            set: folders.set,
            refresh: folders.refresh,
            update: folders.update,
            check: folders.check,
            adjust: folders.adjust
        },
        messages: {
            get: messages.get,
            sort: messages.sort,
            events: messages.events,
        },
        compose: compose.message
    }
})();