hwmAdvancedMenu

Расширенное меню

// ==UserScript==
// @name           hwmAdvancedMenu
// @namespace      Tamozhnya1
// @author         Tamozhnya1
// @description    Расширенное меню
// @version        2.7
// @include        *heroeswm.ru/*
// @include        *lordswm.com/*
// @exclude        */rightcol.php*
// @exclude        */ch_box.php*
// @exclude        */chat*
// @exclude        */ticker.html*
// @exclude        */frames*
// @exclude        */brd.php*
// @grant          GM_deleteValue
// @grant          GM_getValue
// @grant          GM_setValue
// @grant       GM.xmlHttpRequest
// @license        MIT
// ==/UserScript==

const StoredForumTreadsAmount = 10;
const playerIdMatch = document.cookie.match(/pl_id=(\d+)/);
if(!playerIdMatch) {
    return;
}
const PlayerId = playerIdMatch[1];
const isEn = document.documentElement.lang == "en";
const isNewInterface = document.querySelector("div#hwm_header") ? true : false;

main();
async function main() {
    initUserName();
    processHouses();
    processQuickLinks();
    processForum();
    checkMilitaryClan();
    // После рынка вставим ссылки на ресурсы, с задержкой в пол секунды
    const auctionRef = isNewInterface ? document.querySelector("div.sh_dd_container a[href='auction.php']") : getParent(document.querySelector("li > a[href='auction.php']"), "li");
    if(auctionRef) {
        let resourcesShowTimer;
        let resourcesShown = false;
        const resources = [ { type: "1", name: isEn ? 'Wood' : 'Древесина' }, { type: "2", name: isEn ? 'Ore' : 'Руда' }, { type: "3", name: isEn ? 'Mercury' : 'Ртуть' }, { type: "4", name: isEn ? 'Sulfur' : 'Сера' }, { type: "5", name: isEn ? 'Crystals' : 'Кристаллы' }, { type: "6", name: isEn ? 'Gems' : 'Самоцветы' } ];
        auctionRef.addEventListener("mouseover", function() { if(!resourcesShown) resourcesShowTimer = setTimeout(function() {
            const html = resources.reduce((t, x) => t + getMenuItemTemplate(`auction.php?cat=res&sort=0&type=${x.type}`, `  ${x.name}`), "");
            auctionRef.insertAdjacentHTML('afterend', html);
            resourcesShown = true;
        }, 500) } );
        auctionRef.addEventListener("mouseout", function() { clearTimeout(resourcesShowTimer); });
    }
    // После передачи ресурсов вставим основные ссылки персонажа
    const transferRef = isNewInterface ? document.querySelector("div.sh_dd_container a[href='transfer.php']") : getParent(document.querySelector("li > a[href='transfer.php']"), "li");
    if(transferRef) {
        const personalReferences = [
            { href: `el_transfer.php`, text: isEn ? 'Transfer elements' : 'Передача элементов' },
            { href: 'javascript:void(0);', text: "" },
            { href: `pl_info.php?id=${PlayerId}`, text: getPlayerValue("UserName") || (isEn ? 'Character' : 'Персонаж') },
            { href: `pl_transfers.php?id=${PlayerId}`, text: isEn ? 'Transfer log' : 'Протокол передач' },
            { href: `pl_warlog.php?id=${PlayerId}`, text: isEn ? 'Combat log' : 'Протокол боев' },
            { href: `pl_cardlog.php?id=${PlayerId}`, text: isEn ? 'Game log' : 'Протокол игр' },
            { href: `friends.php`, text: isEn ? 'Your friends' : 'Ваши друзья' },
            { href: `/pl_clans.php`, text: isEn ? 'Your clans' : 'Ваши кланы' },
            { href: `ephoto_albums.php`, text: isEn ? 'Your photos' : 'Ваш фотоальбом' },
            { href: 'javascript:void(0);', text: "" },
            { href: `logout.php?${Math.round( Math.random()* 100000 )}`, text: isEn ? 'Logout' : 'Выход' }
        ];
        const html = personalReferences.reduce((t, x) => t + getMenuItemTemplate(x.href, x.text), "");
        transferRef.insertAdjacentHTML('afterend', html);
    }
    // Расширение карты
    const mapMenuContainer = isNewInterface ? document.querySelector("div.sh_dd_container a[href='map.php?st=hs']") : getParent(document.querySelector("li > a[href='map.php?st=hs']"), "li");
    if(mapMenuContainer) {
        const housesInfo = JSON.parse(getPlayerValue("PlayerHouses", "{}"));
        let mapExtenders = Object.keys(housesInfo).map(x => ({ href: `house_info.php?id=${x}`, text: housesInfo[x].replace(" ", " ") }));
        // Арендованные дома. Берутся из скрипта Transporter
        for(let locationNumber = 1; locationNumber <= 27; locationNumber++) {
            const guestInfo = JSON.parse(getValue(`GuestInfo${locationNumber}`, "{}"));
            for(const key in guestInfo) {
                mapExtenders = [...mapExtenders, { href: `house_info.php?id=${key}`, text: guestInfo[key].HostInfo, title: `до ${(new Date(guestInfo[key].ExpireDate)).toLocaleString()}` }];
            }
        }
        //
        mapExtenders = [...mapExtenders, { href: 'javascript:void(0);', text: "" }, { href: 'ecostat.php', text: isEn ? 'Economic statistics' : 'Эконом. статистика' }];
        // Ссылки по боевому клану. Появляются после захода на страницу кланов
        const clanId = getPlayerValue("MilitaryClanId");
        if(clanId) {
            const clanName = getPlayerValue("MilitaryClanName");
            mapExtenders = [...mapExtenders, { href: 'javascript:void(0);', text: "" },
                { href: `/clan_info.php?id=${clanId}`, text: clanName || (isEn ? 'Military clan' : 'Боевой клан') },
                { href: `/sklad_info.php?clan_id=${clanId}`, text: isEn ? 'Clan depository' : 'Клан-склад' },
                { href: `/sms_clans.php?clan_id=${clanId}`, text: isEn ? 'Clan post' : 'Клановая рассылка' }
            ];
        }
        const html = mapExtenders.reduce((t, x) => t + getMenuItemTemplate(x.href, x.text, x.title), "");
        //console.log(mapExtenders)
        mapMenuContainer.insertAdjacentHTML('afterend', html);
    }
    const leaderGuildRef = isNewInterface ? document.querySelector("div.sh_dd_container a[href='leader_guild.php']") : getParent(document.querySelector("li > a[href='leader_guild.php']"), "li");
    if(leaderGuildRef) {
        const leaderGuildReferences = [
            { href: `leader_army.php`, text: isEn ? 'Recruiting' : 'Набор армии' },
            { href: `leader_army_exchange.php`, text: isEn ? 'Creature Exchange' : 'Обмен существ' }
        ];
        const html = leaderGuildReferences.reduce((t, x) => t + getMenuItemTemplate(x.href, x.text), "");
        leaderGuildRef.insertAdjacentHTML('afterend', html);
    }
    // Добавим для ГЛ набор армии и объмены
    // Добавим форумов и дейли
    const forumsContainer = isNewInterface ? document.querySelector("div.sh_dd_container a[href='forum.php#t1']") : getParent(document.querySelector("li > a[href='forum.php#t1']"), "li");
    if(forumsContainer) {
        const forumExtenders = [
            { href: `forum_thread.php?id=${isEn ? '103' : '3'}`, text: isEn ? 'Ideas and suggestions' : 'Идеи и предложения' },
            { href: `forum_thread.php?id=${isEn ? '121' : '22'}`, text: isEn ? 'Smiths and Ench. services' : 'Услуги кузнецов и оруж.' },
            { href: 'javascript:void(0);', text: "" },
            { href: `${isEn ? 'http://daily.heroeswm.ru/newscom.php' : 'http://daily.heroeswm.ru/'}`, text: isEn ? 'HWM Daily ENG' : 'Геройская лента' }
        ];
        let html = forumExtenders.reduce((t, x) => t + getMenuItemTemplate(x.href, x.text), "");
        const lastForumTreads = JSON.parse(getValue("LastForumTreads", "[]"));
        if(lastForumTreads.length > 0) {
            html += getMenuItemTemplate('javascript:void(0);', "");
        }
        html += lastForumTreads.reduce((t, x) => t + getMenuItemTemplate(`/forum_messages.php?tid=${x.threadId}${x.pageIndex ? `&page=${x.pageIndex}` : ""}`, x.threadName, "", `forumReference${x.threadId}`), "");
        forumsContainer.insertAdjacentHTML('afterend', html);
        Array.from(document.querySelectorAll(`span[id^='forumReference']`)).forEach(x => x.addEventListener("click", deleteForumReferenceMenuItem));
    }
    // После чатов добавим быстрые ссылки
    const framesContainer = isNewInterface ? document.querySelector("div.sh_dd_container a[href='frames.php?room=4']") : getParent(document.querySelector("li > a[href='frames.php?room=4']"), "li");
    if(framesContainer) {
        //GM_deleteValue(`QuickLinks${PlayerId}`);
        let quickLinks = JSON.parse(getPlayerValue("QuickLinks", "[]")).filter(x => x.Name != "" && x.Reference != "");
        quickLinks.push({ Name: isEn ? "Settings" : "Настройки", Reference: "javascript:void(0);", id: `${GM_info.script.name}Settings` });
        if(quickLinks.length > 0) {
            quickLinks = [{ Reference: "javascript:void(0);", Name: "" }, ...quickLinks];
            const html = quickLinks.reduce((t, x) => t + getMenuItemTemplate(x.Reference, x.Name, undefined, undefined, x.id), "");
            framesContainer.insertAdjacentHTML('afterend', html);
        }
        document.getElementById(`${GM_info.script.name}Settings`).addEventListener("click", showSettings);
    }
    if(getPlayerBool("hideTavern")) {
        if(isNewInterface) {
            document.querySelector("div.mm_item.mm_item_blue > a[href='tavern.php']").closest("div").remove();
            document.querySelector("a[href='frames.php?room=4']").remove();
        } else {
            const menuItem = getParent(document.querySelector("li a[href='tavern.php']"), "td", 3);
            menuItem.previousElementSibling.remove();
            menuItem.remove();
            document.querySelector("a[href='frames.php?room=4']").closest("li").remove();
        }
        if(location.pathname == "/tavern.php") {
            location.href = "/home.php";
        }
        if(location.pathname == "/frames.php" && getUrlParamValue(location.href, "room") == "4") {
            location.href = "/home.php";
        }
    }
    if(getPlayerBool("hideRoulette")) {
        if(isNewInterface) {
            document.querySelector("div.mm_item.mm_item_blue > a[href='roulette.php']").closest("div").remove();
            document.querySelector("a[href='frames.php?room=3']").remove();
        } else {
            const menuItem = getParent(document.querySelector("li a[href='roulette.php']"), "td", 3);
            menuItem.previousElementSibling.remove();
            menuItem.remove();
            document.querySelector("a[href='frames.php?room=3']").closest("li").remove();
        }
        if(location.pathname == "/roulette.php") {
            location.href = "/home.php";
        }
        if(location.pathname == "/frames.php" && getUrlParamValue(location.href, "room") == "3") {
            location.href = "/home.php";
        }
        Array.from(document.querySelectorAll("a[href*='roulette.php']")).forEach(x => x.remove());
    }
}
function deleteForumReferenceMenuItem(e) {
    //e.stopPropagation();
    e.preventDefault();
    const threadId = e.target.id.replace("forumReference", "");
    //console.log(`threadId: ${threadId}`)
    const lastForumTreads = JSON.parse(getValue("LastForumTreads", "[]")).filter(x => x.threadId != threadId);
    setValue("LastForumTreads", JSON.stringify(lastForumTreads));
    
    const menuItem = isNewInterface ? getParent(e.target, "a") : getParent(e.target, "li");
    //console.log(menuItem)
    menuItem.remove();
}
function processForum() {
    if(location.pathname == '/forum_messages.php') {
        if(getValue("LastForumTreads")) {
            const saved = JSON.parse(getValue("LastForumTreads"));
            if(!Array.isArray(saved)) {
                GM_deleteValue("LastForumTreads");
            }
        }
        //https://www.heroeswm.ru/forum_messages.php?tid=2964583&page=5
        const threadId = getUrlParamValue(location.href, "tid");
        const pageIndex = getUrlParamValue(location.href, "page");
        const threadName = document.querySelector(`a[href='forum_messages.php?tid=${threadId}'`).innerText;
        const newThread = { threadId: threadId, pageIndex: pageIndex || 0, threadName: threadName, viewTime: Date.now() };
        let lastForumTreads = JSON.parse(getValue("LastForumTreads", "[]"));
        const thisThread = lastForumTreads.find(x => x.threadId == newThread.threadId && x.pageIndex == newThread.pageIndex);
        if(thisThread) {
            window.scrollTo(0, thisThread.scrollPosition); 
        }
        newThread.scrollPosition = window.scrollY;
        lastForumTreads = lastForumTreads.filter(x => x.threadId != newThread.threadId);
        lastForumTreads.unshift(newThread);
        lastForumTreads = lastForumTreads.slice(0, StoredForumTreadsAmount);
        setValue("LastForumTreads", JSON.stringify(lastForumTreads));
        
        document.addEventListener("scroll", (event) => {
            const lastForumTreads = JSON.parse(getValue("LastForumTreads", "[]"))
            const scrolledTread = lastForumTreads.find(x => x.threadId == newThread.threadId);
            if(scrolledTread) {
                scrolledTread.scrollPosition = window.scrollY;
                setValue("LastForumTreads", JSON.stringify(lastForumTreads));
            }
        });
    }
}
function getMenuItemTemplate(href, text, title, deleteId, id) {
    if(text.length > 30) {
        if(!title) {
            title = text;
        }
        text = text.substring(0, 30) + "...";
    }
    if(!isNewInterface) {
        if(text == "") {
            return "<hr>";
        }
        let deleteElementText = "";
        if(deleteId) {
            deleteElementText = `<span id="${deleteId}" title='${isEn ? "Delete" : "Удалить"}' style="cursor: pointer; display: inline; color: yellow;">[x]</span>`;
        }
        return `<li><a${id ? ` id="${id}"` : ""} href='${href}' title='${title || ""}' style="">${text}${deleteElementText}</a></li>`;
    } else {
        let deleteElementText = "";
        if(deleteId) {
            deleteElementText = `<span id="${deleteId}" title='${isEn ? "Delete" : "Удалить"}' style="cursor: pointer; display: inline; float: right;">[x]</span>`;
        }
        return `<a${id ? ` id="${id}"` : ""} href='${href}' title='${title || ""}' style='text-decoration: none;'><div style='${text == "" ? "padding: 0; height: 2px;" : ""}'>${text}${deleteElementText}</div></a>`;
    }
}
function processHouses() {
    if(location.pathname == "/pl_info_realty.php" && getUrlParamValue(location.href, "id") == PlayerId) {
        const housesInfo = Array.from(document.querySelectorAll("a[href^='house_info.php']")).reduce((t, x) => ({...t, [getUrlParamValue(x.href, "id")]: getParent(x, "tr").cells[4].innerText }), {});
        //console.log(housesInfo);
        setPlayerValue("PlayerHouses", JSON.stringify(housesInfo));
    }
}
function processQuickLinks() {
    if(location.pathname == "/pers_navlinks.php") {
        const tbody = getParent(document.querySelector("form[action='pers_navlinks.php']"), "tbody");
        const tr = addElement("tr", undefined, tbody);
        const td = addElement("td", undefined, tr);
        const table = addElement("table", undefined, td);
        
        const quickLinkAmount = 10;
        let quickLinks = JSON.parse(getPlayerValue("QuickLinks", `[${Array(quickLinkAmount).fill('{"Name":"","Reference":""}').join()}]`));
        if(quickLinks.length != quickLinkAmount) {
            deletePlayerValue("QuickLinks");
            quickLinks = JSON.parse(`[${Array(quickLinkAmount).fill('{"Name":"","Reference":""}').join()}]`);
        }
        //console.log(quickLinks)
        let i = 0;
        for(const quickLink of quickLinks) {
            const html = `
<tr>
    <td>
        <input id="linkName${i}" name="linkName" type="text" style="width: 200px;" />
        <input id="linkValue${i}" name="linkValue" type="text" style="width: 300px;" />
    </td>
</tr>`;
            table.insertAdjacentHTML('beforeend', html);
            table.querySelector(`#linkName${i}`).value = quickLink.Name;
            table.querySelector(`#linkValue${i}`).value = quickLink.Reference;
            table.querySelector(`#linkName${i}`).addEventListener("change", saveQuickLinks);
            table.querySelector(`#linkValue${i}`).addEventListener("change", saveQuickLinks);
            i++;
        }
    }
}
function saveQuickLinks() {
    const quickLinks = Array.from(document.querySelectorAll("input[name=linkName]")).map(x => ({ Name: x.value, Reference: document.getElementById(x.id.replace("linkName", "linkValue")).value }));
    //console.log(quickLinks)
    setPlayerValue("QuickLinks", JSON.stringify(quickLinks));
}
async function checkMilitaryClan() {
    if(location.pathname == '/pl_clans.php') {
        const doc = location.pathname == '/pl_clans.php' ? document : await getRequest(`/pl_clans.php`);
        const clanInfos = Array.from(doc.querySelectorAll("td > li > a[href^='clan_info.php']")).map(x => { return { Id: getUrlParamValue(x.href, "id"), Name: x.firstChild.innerText, Ref: x.href }; });
        for(const clanInfo of clanInfos) {
            const clanInfoDoc = await getRequest(clanInfo.Ref);
            if(clanInfoDoc.body.innerHTML.includes(isEn ? "[Military clan]" : "[боевой клан]")) {
                var militaryClanId = clanInfo.Id;
                var clanName = clanInfo.Name;
                break;
            }
        }
        if(militaryClanId) {
            setPlayerValue("MilitaryClanId", militaryClanId);
            setPlayerValue("MilitaryClanName", clanName);
        } else {
            deletePlayerValue("MilitaryClanId");
            deletePlayerValue("MilitaryClanName");
        }
    }
    if(!getPlayerValue("MilitaryClanId")) {
        console.log("Вы не состоите в боевом клане");
        return false;
    }
    return true;
}
async function initUserName() {
    if(location.pathname == "/pl_info.php" && getUrlParamValue(location.href, "id") == PlayerId) {
        //console.log(document.querySelector("h1").innerText)
        setPlayerValue("UserName", document.querySelector("h1").innerText);
    }
    if(location.pathname == "/home.php") {
        //console.log(document.querySelector(`a[href='pl_info.php?id=${PlayerId}'] > b`).innerText)
        setPlayerValue("UserName", document.querySelector(`a[href='pl_info.php?id=${PlayerId}'] > b`).innerText);
    }
    if(!getPlayerValue("UserName")) {
        const doc = await getRequest(`/pl_info.php?id=${PlayerId}`);
        setPlayerValue("UserName", doc.querySelector("h1").innerText);
    }
}
function showSettings() {
    if(showPupupPanel(GM_info.script.name)) {
        return;
    }
    const fieldsMap = [];

    const hideTavernLable = addElement("label", { for: "hideTavernCheckbox", innerText: isEn ? "Hide tavern" : "Скрыть таверну" });
    const hideTavernCheckbox = addElement("input", { id: "hideTavernCheckbox", type: "checkbox" });
    hideTavernCheckbox.checked = getPlayerBool("hideTavern");
    hideTavernCheckbox.addEventListener("change", function() { setPlayerValue("hideTavern", this.checked); }, false);
    fieldsMap.push([hideTavernLable, hideTavernCheckbox]);

    const hideRouletteLable = addElement("label", { for: "hideRouletteCheckbox", innerText: isEn ? "Hide roulette" : "Скрыть рулетку" });
    const hideRouletteCheckbox = addElement("input", { id: "hideRouletteCheckbox", type: "checkbox" });
    hideRouletteCheckbox.checked = getPlayerBool("hideRoulette");
    hideRouletteCheckbox.addEventListener("change", function() { setPlayerValue("hideRoulette", this.checked); }, false);
    fieldsMap.push([hideRouletteLable, hideRouletteCheckbox]);

    createPupupPanel(GM_info.script.name, getScriptReferenceHtml() + " " + getSendErrorMailReferenceHtml(), fieldsMap);
}
// API
function addElement(type, data = {}, parent = undefined, insertPosition = "beforeend") {
    const el = document.createElement(type);
    for(const key in data) {
        if(key == "innerText" || key == "innerHTML") {
            el[key] = data[key];
        } else {
            el.setAttribute(key, data[key]);
        }
    }
    if(parent) {
        parent.insertAdjacentElement(insertPosition, el);
    }
    return el;
}
function getParent(element, parentType, number = 1) {
    if(!element) {
        return;
    }
    let result = element;
    let foundNumber = 0;
    while(result = result.parentNode) {
        if(result.nodeName.toLowerCase() == parentType.toLowerCase()) {
            foundNumber++;
            if(foundNumber == number) {
                return result;
            }
        }
    }
}
function getUrlParamValue(url, paramName) { return (new URLSearchParams(url.split("?")[1])).get(paramName); }
function getRequest(url) {
    return new Promise((resolve, reject) => {
        GM.xmlHttpRequest({ method: "GET", url: url, overrideMimeType: "text/html; charset=windows-1251",
            onload: function(response) { resolve((new DOMParser).parseFromString(response.responseText, "text/html")); },
            onerror: function(error) { reject(error); }
        });
    });
}
function getValue(key, defaultValue) { return GM_getValue(key, defaultValue); };
function setValue(key, value) { GM_setValue(key, value); };
function deleteValue(key) { return GM_deleteValue(key); };
function getPlayerValue(key, defaultValue) { return GM_getValue(`${key}${PlayerId}`, defaultValue); };
function setPlayerValue(key, value) { GM_setValue(`${key}${PlayerId}`, value); };
function deletePlayerValue(key) { return GM_deleteValue(`${key}${PlayerId}`); };
function getValue(key, defaultValue) { return GM_getValue(key, defaultValue); };
function setValue(key, value) { GM_setValue(key, value); };
function deleteValue(key) { return GM_deleteValue(key); };
function getPlayerValue(key, defaultValue) { return GM_getValue(`${key}${PlayerId}`, defaultValue); };
function setPlayerValue(key, value) { GM_setValue(`${key}${PlayerId}`, value); };
function deletePlayerValue(key) { return GM_deleteValue(`${key}${PlayerId}`); };
function listValues() { return GM_listValues(); }
function getPlayerBool(valueName, defaultValue = false) { return getBool(valueName + PlayerId, defaultValue); }
function getBool(valueName, defaultValue = false) {
    const value = getValue(valueName);
    //console.log(`valueName: ${valueName}, value: ${value}, ${typeof(value)}`)
    if(value != undefined) {
        if(typeof(value) == "string") {
            return value == "true";
        }
        if(typeof(value) == "boolean") {
            return value;
        }
    }
    return defaultValue;
}
function getScriptLastAuthor() {
    let authors = GM_info.script.author;
    if(!authors) {
        const authorsMatch = GM_info.scriptMetaStr.match(/@author(.+)\n/);
        authors = authorsMatch ? authorsMatch[1] : "";
    }
    const authorsArr = authors.split(",").map(x => x.trim()).filter(x => x);
    return authorsArr[authorsArr.length - 1];
}
function getDownloadUrl() {
    let result = GM_info.script.downloadURL;
    if(!result) {
        const downloadURLMatch = GM_info.scriptMetaStr.match(/@downloadURL(.+)\n/);
        result = downloadURLMatch ? downloadURLMatch[1] : "";
        result = result.trim();
    }
    return result;
}
function createPupupPanel(panelName, panelTitle, fieldsMap, panelToggleHandler) {
    const backgroundPopupPanel = addElement("div", { id: panelName + "1", style: "position: fixed; left: 0pt; width: 100%; background: none repeat scroll 0% 0% gray; opacity: 0.5; top: 0px; height: 100%; display: block; z-index: 200;" }, document.body);
    backgroundPopupPanel.addEventListener("click", function() { hidePupupPanel(panelName, panelToggleHandler); });

    const popupPanel = addElement("div", { id: panelName + "2", style: `position: fixed; width: 650px; background: none repeat scroll 0% 0%; background-image: linear-gradient(to right, #eea2a2 0%, #bbc1bf 19%, #57c6e1 42%, #b49fda 79%, #7ac5d8 100%); left: ${((document.body.offsetWidth - 650) / 2)}px; top: 150px; display: block; z-index: 200; border: 4mm ridge rgba(211, 220, 50, .6);` }, document.body);
    const contentDiv = addElement("div", { id: panelName + "3", style: "border: 1px solid #abc; padding: 5px; margin: 2px; display: flex; flex-wrap: wrap;" }, popupPanel);

    if(panelTitle) {
        addElement("b", { innerHTML: panelTitle, style: "text-align: center; margin: auto; width: 90%; display: block;" }, contentDiv);
    }
    const divClose = addElement("div", { id: panelName + "close", title: "Close", innerText: "x", style: "border: 1px solid #abc; width: 15px; height: 15px; text-align: center; cursor: pointer;" }, contentDiv);
    divClose.addEventListener("click", function() { hidePupupPanel(panelName, panelToggleHandler); });

    addElement("div", { style: "flex-basis: 100%; height: 0;"}, contentDiv);

    if(fieldsMap) {
        let contentTable = addElement("table", undefined, contentDiv);
        for(const rowData of fieldsMap) {
            if(rowData.length == 0) { // Спомощью передачи пустой стороки-массива, указываем, что надо начать новую таблицу после брейка
                addElement("div", { style: "flex-basis: 100%; height: 0;"}, contentDiv);
                contentTable = addElement("table", undefined, contentDiv);
                continue;
            }
            const row = addElement("tr", undefined, contentTable);
            for(const cellData of rowData) {
                const cell = addElement("td", undefined, row);
                if(cellData) {
                    if(typeof(cellData) == "string") {
                        cell.innerText = cellData;
                    } else {
                        cell.appendChild(cellData);
                    }
                }
            }
        }
    }
    if(panelToggleHandler) {
        panelToggleHandler(true);
    }
    return contentDiv;
}
function showPupupPanel(panelName, panelToggleHandler) {
    let backgroundPopupPanel = document.getElementById(panelName + "1");
    let popupPanel = document.getElementById(panelName + "2");
    if(backgroundPopupPanel) {
        backgroundPopupPanel.style.display = popupPanel.style.display = 'block';
        if(panelToggleHandler) {
            panelToggleHandler(true);
        }
        return true;
    }
    return false;
}
function hidePupupPanel(panelName, panelToggleHandler) {
    let backgroundPopupPanel = document.getElementById(panelName + "1");
    let popupPanel = document.getElementById(panelName + "2");
    backgroundPopupPanel.style.display = popupPanel.style.display = 'none';
    if(panelToggleHandler) {
        panelToggleHandler(false);
    }
}
function getScriptReferenceHtml() { return `<a href="${getDownloadUrl()}" title="${isEn ? "Check for update" : "Проверить обновление скрипта"}" target=_blanc>${GM_info.script.name} ${GM_info.script.version}</a>`; }
function getSendErrorMailReferenceHtml() { return `<a href="sms-create.php?mailto=${getScriptLastAuthor()}&subject=${isEn ? "Error in" : "Ошибка в"} ${GM_info.script.name} ${GM_info.script.version} (${GM_info.scriptHandler} ${GM_info.version})" target=_blanc>${isEn ? "Bug report" : "Сообщить об ошибке"}</a>`; }
function getParent(element, parentType, number = 1) {
    if(!element) {
        return;
    }
    let result = element;
    let foundNumber = 0;
    while(result = result.parentNode) {
        if(result.nodeName.toLowerCase() == parentType.toLowerCase()) {
            foundNumber++;
            if(foundNumber == number) {
                return result;
            }
        }
    }
}