Apps and Browser Extensions
We detect your platform to show the right downloads—no guesswork, just the apps you need.
const axios = require("axios");
const queryString = require("query-string");
const ui = require("./ui");
const $ = require("jquery");
const cookieJS = require("js-cookie");
const utils = require("../utils/common");
const parsedUA = require("ua-parser-js")();
const scrollToElement = require("scroll-to-element");
const html_escaper = require('html-escaper');
import DeviceDetector from "device-detector-js";
//@Rishi general comments:
// This file is > 1000 lines. this is a red flag for splitting it up into multiple files
// I highly recommend you make a file called "form.js" in the support folder and move your form logic code into there.
// Also, you have a lot of UI specific code that should end up in ui.js
const ERROR = -1;
const INFORM = 0;
const file_size_limit = 15000000;
let banner_number = 1;
let time_out = null;
/* init with UA string from browser */
console.log(parsedUA);
// Object that maps type (c/f/a) to id of current nav.
let state = {};
// Map of ids to names of (c/f/a)
let nameMap = {};
// List of objects containing data for breadcrumb buttons
// breadcrumb: {type: string (c/f/a), id: int, interactive: bool}
let breadcrumbs = [];
let idMap = {};
let order_sel = false;
let $search = $("#search");
let $noResults = $("#no-results");
let $breadcrumbHome = $("#breadcrumb-home");
// let $supportSubmit = $("#support_submit");
let revealer = document.getElementById("support-info-revealer");
let support_btn = document.getElementById("submit_form");
let dropArea = document.getElementById("drop-area");
["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => {
    dropArea.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
    e.preventDefault();
    e.stopPropagation();
}
["dragenter", "dragover"].forEach((eventName) => {
    dropArea.addEventListener(eventName, function() {
        dropArea.classList.add("highlight");
    }, false);
});
["dragleave", "drop"].forEach((eventName) => {
    dropArea.addEventListener(eventName, function() {
        dropArea.classList.remove("highlight");
    }, false);
});
let files_store = [];
dropArea.addEventListener("click", function() {
    console.log("drop area clicked");
    $("#attached_file").trigger("click");
});
//this is triggered when user drops on drop-area to add files
dropArea.ondrop = function(ev) {
    console.log("files dropped");
    let filenames = [];
    Array.from(ev.dataTransfer.files).forEach((file) => {
        filenames.push(file.name);
        files_store.push(file);
    });
    filenames.forEach(name => {
        let node = document.createElement('LI');
        node.id = `success_msg_${banner_number}`;
        node.classList.add('banner-msg');
        let textNode = document.createTextNode(`${name} `);
        let delButton = document.createElement('div');
        delButton.id = `${banner_number}`;
        let buttonText = document.createTextNode(`×`);
        delButton.appendChild(buttonText);
        delButton.classList.add('del_button');
        node.appendChild(textNode);
        node.appendChild(delButton);
        document.getElementById('banners').appendChild(node);
        banner_number += 1;
    });
    setTimeout(() => {
        document.getElementById('banners').onclick = function(event) {
            let target = getEventTarget(event);
            let del_id = target.id;
            console.log(del_id);
            let bannerToDel = document.getElementById(`success_msg_${del_id}`);
            let buttonToDel = document.getElementById(`${del_id}`);
            bannerToDel.parentNode.removeChild(bannerToDel);
            buttonToDel.parentNode.removeChild(buttonToDel);
            console.log(files_store);
            files_store[del_id - 1] = null;
        };
    }, 200);
    // banner_number += 1;
    ev.target.value = null;
    preventDefaults(ev);
};
function getEventTarget(event) {
    event = event || window.event;
    return event.target || event.srcElement;
}
$('input[type="file"]').change(function(ev) {
    if (ev.target.files.length != 0) {
        // files_store.push(ev.target.files);
        let filenames = [];
        Array.from(ev.target.files).forEach((file) => {
            filenames.push(file.name);
            files_store.push(file);
        });
        console.log(files_store);
        // let stringify_names = filenames.toString();
        filenames.forEach(name => {
            let node = document.createElement('LI');
            node.id = `success_msg_${banner_number}`;
            node.classList.add('banner-msg');
            let textNode = document.createTextNode(`${name} `);
            let delButton = document.createElement('div');
            delButton.id = `${banner_number}`;
            let buttonText = document.createTextNode(`×`);
            delButton.appendChild(buttonText);
            delButton.classList.add('del_button');
            node.appendChild(textNode);
            node.appendChild(delButton);
            document.getElementById('banners').appendChild(node);
            banner_number += 1;
        });
        setTimeout(() => {
            document.getElementById('banners').onclick = function(event) {
                let target = getEventTarget(event);
                let del_id = target.id;
                console.log(del_id);
                let bannerToDel = document.getElementById(`success_msg_${del_id}`);
                let buttonToDel = document.getElementById(`${del_id}`);
                bannerToDel.parentNode.removeChild(bannerToDel);
                buttonToDel.parentNode.removeChild(buttonToDel);
                console.log(files_store);
                files_store[del_id - 1] = null;
            };
        }, 200);
        //@Rishi pls link the stackoverflow/bug tracker/forum where you found the bug and the workaround for reference to future devs
        //apparently a nice little chrome bug
        //@Seva I don't remember the website I found it in :/ I'll search for it later...
        ev.target.value = null;
        preventDefaults(ev);
    }
});
function validate_form(
    name,
    email,
    subject,
    description,
    file_size,
    cat_sel,
    sub_cat_sel
) {
    if (
        name.validity.valid &&
        email.validity.valid &&
        utils.isEmail(email.value.trim()) &&
        subject.validity.valid &&
        description.validity.valid &&
        //@Rishi extract magic numbers
        file_size <= file_size_limit &&
        cat_sel &&
        sub_cat_sel
    ) {
        return true;
    }
    return false;
}
function displayFeedbackBanner(type, message) {
    let banner = document.getElementById("feedback-banner");
    if (type === ERROR) {
        banner.style.backgroundColor = " #ff0000";
    } else if (type === INFORM) {
        banner.style.backgroundColor = "#0085f5";
    } else { //is success
        banner.style.backgroundColor = "#0085f5";
    }
    banner.style.display = "block";
    banner.innerText = message;
}
function setBannerTimeout(type, message) {
    if (time_out !== null) {
        clearTimeout(time_out);
    }
    displayFeedbackBanner(type, message);
    time_out = setTimeout(() => {
        document.getElementById('feedback-banner').style.display = 'none';
    }, 7000);
}
function alert_missing_fields(
    name,
    email,
    subject,
    description,
    file_size,
    cat_sel,
    sub_cat_sel
) {
    if (!cat_sel) {
        setBannerTimeout(ERROR, "Select a category");
    } else if (!sub_cat_sel) {
        setBannerTimeout(ERROR, "Select a subcategory");
    } else if (!name.validity.valid) {
        setBannerTimeout(ERROR, "Enter a name");
    } else if (!email.validity.valid || !utils.isEmail(email.value.trim())) {
        setBannerTimeout(ERROR, "Enter a valid email");
    } else if (!subject.validity.valid) {
        setBannerTimeout(ERROR, "Enter a subject");
    } else if (!description.validity.valid) {
        setBannerTimeout(ERROR, "Enter a message");
    } else if (file_size >= file_size_limit) {
        setBannerTimeout(ERROR, "Attachment file size cannot exceed 15 MB");
    }
}
function formatNotes(userInfo, ord_num, parsedUA_data, name, email) {
    let formatted_info;
    console.log(userInfo.device);
    name = name || "Anonymous";
    if (email) {
        if (ord_num == null) {
            formatted_info = `
                
                     Contact - ${name} <${email}>
                    
                     Browser - ${parsedUA_data.browser.name} ${parsedUA_data.browser.version} 
                     Operating System - ${userInfo.os.name} ${userInfo.os.version}
                     Device - ${userInfo.device.brand} ${userInfo.device.model} ${userInfo.device.type}
                 
            `;
        } else {
            formatted_info = `
                
                      Order Number - ${ord_num} 
                    
                     Contact - ${name} <${email}>
                    
                     Browser - ${parsedUA_data.browser.name} ${parsedUA_data.browser.version}
                     Operating System - ${userInfo.os.name} ${userInfo.os.version}
                     Device - ${userInfo.device.brand} ${userInfo.device.model} ${userInfo.device.type}
                 
            `;
        }
    } else {
        if (ord_num == null) {
            formatted_info = `
                
                     Contact - ${name}
                    
                     Browser - ${parsedUA_data.browser.name} ${parsedUA_data.browser.version} 
                     Operating System - ${userInfo.os.name} ${userInfo.os.version}
                     Device - ${userInfo.device.brand} ${userInfo.device.model} ${userInfo.device.type}
                 
            `;
        } else {
            formatted_info = `
                
                      Order Number - ${ord_num} 
                    
                     Contact - ${name}
                    
                     Browser - ${parsedUA_data.browser.name} ${parsedUA_data.browser.version}
                     Operating System - ${userInfo.os.name} ${userInfo.os.version}
                     Device - ${userInfo.device.brand} ${userInfo.device.model} ${userInfo.device.type}
                 
            `;
        }
    }
    return formatted_info;
}
function validate_order_num() {
    let order_num = document.getElementById("order_number");
    if (order_num.validity.valid) {
        order_num = order_num.value.trim();
        return order_num;
    }
    return null;
}
function clearOrderNumber() {
    document.getElementById("ord-number").style.maxHeight = '0px';
    document.getElementById("ord-number").style.opacity = 0;
    document.getElementById("ord-number").style.transition = "max-height 350ms ease-out, opacity 200ms ease-in";
    setTimeout(() => {
        document.getElementById("ord-number").innerHTML = "";
    }, 350);
}
function request_order_num() {
    let ord_num = document.getElementById("ord-number");
    ord_num.innerHTML = `
`;
    setTimeout(() => {
        ord_num.style.maxHeight = '108px';
        ord_num.style.opacity = 1;
        ord_num.style.transition = "max-height 350ms ease-out, opacity .5s ease-in";
    }, 350);
}
function sliceTags(tag) {
    //brute force through these 3
    if (tag == "Everykey Two-Factor Authentication") {
        return "Everykey Two Factor";
    }
    if (tag == "Cancellations, Returns, and Exchanges") {
        return "Refunds Returns and Exchanges";
    }
    if (tag == "Product Updates and Special Offers") {
        return "Updates and Special Offers";
    }
    //handle rest
    let sliced_tag = tag.replace(/,/g, "");
    if (sliced_tag.length >= 32) {
        return sliced_tag.slice(0, 31);
    }
    return sliced_tag;
}
function getFileSize(files) {
    let f_size = 0;
    for (let i = 0, numFiles = files.length; i < numFiles; i++) {
        const file = files[i];
        f_size += file.size;
    }
    return f_size;
}
function displaySuccessPage() {
    $("#toScroll").addClass('hidden-content');
    $("#form_submitted").removeClass('hidden-content');
    $("#form_submitted").addClass('submit_page');
    window.scrollTo(0, 0);
}
// **************************************
// After submit button is clicked
// **************************************
support_btn.addEventListener("click", function() {
    let cat_selected = false;
    let sub_cat_sel = false;
    let type = $("#category").find("option:selected").val();
    let _tags;
    if (document.getElementById("category").selectedIndex != 0) {
        cat_selected = true;
        _tags = $(`#inner_${type}`).find("option:selected").val();
        //if inner_type is null, reset all the fields
        if (document.getElementById(`inner_${type}`).selectedIndex != 0) {
            sub_cat_sel = true;
        }
    }
    console.log(document.getElementsByClassName('files'));
    let name = document.getElementById("name");
    let email = document.getElementById("email");
    let subject = document.getElementById("subject");
    let description = document.getElementById("description");
    let attchd_files = files_store.filter(file => file !== null);
    let file_size = getFileSize(attchd_files);
    console.log(attchd_files);
    let ord_num = null;
    const deviceDetector = new DeviceDetector();
    if (
        validate_form(
            name,
            email,
            subject,
            description,
            file_size,
            cat_selected,
            sub_cat_sel
        )
    ) {
        support_btn.disabled = true;
        support_btn.style.display = 'none';
        document.getElementById('shop-loading').style.display = 'block';
        document.getElementById('feedback-banner').style.display = 'none';
        name = html_escaper.escape(name.value.trim());
        email = html_escaper.escape(email.value.trim());
        subject = html_escaper.escape(subject.value.trim());
        description = html_escaper.escape(description.value.trim());
        console.log(name, email, subject, description);
        let form = new FormData();
        form.append("name", name);
        form.append("email", email);
        form.append("description", description);
        form.append("subject", subject);
        let file_names = [];
        for (let i = 0, numFiles = attchd_files.length; i < numFiles; i++) {
            file_names.push(attchd_files[i].name);
            const file = attchd_files[i];
            console.log(file);
            form.append(`file${i}`, file);
        }
        if (order_sel) {
            ord_num = validate_order_num();
        }
        let deviceDetInfo = deviceDetector.parse(navigator.userAgent);
        let formattedInfo = formatNotes(deviceDetInfo, ord_num, parsedUA, name, email);
        console.log(formattedInfo);
        form.append("userInfo", formattedInfo);
        form.append("type", type);
        _tags = sliceTags(_tags);
        console.log("sliced tag", _tags);
        form.append("tags", _tags);
        return axios({
            method: 'post',
            url: '/api/support/submit-ticket',
            headers: {
                'Content-Type': 'multipart/form-data'
            },
            data: form,
        }).then(function(res) {
            console.log(res);
            if (res.data.status === 0) {
                displaySuccessPage();
            } else {
                setBannerTimeout(ERROR, res.data.text);
                document.getElementById('shop-loading').style.display = 'none';
                support_btn.disabled = false;
                support_btn.style.display = 'inline-block';
            }
            // if (res.status == 201) {
            //     displaySuccessPage();
            // } else {
            //     setBannerTimeout(ERROR, res.body);
            //     document.getElementById('shop-loading').style.display = 'none';
            //     support_btn.style.display = 'inline-block';
            //     console.log(res);
            // }
        }).catch((err) => {
            setBannerTimeout(ERROR, err);
            document.getElementById('shop-loading').style.display = 'none';
            support_btn.disabled = false;
            support_btn.style.display = 'inline-block';
            console.log(err);
        });
    } else {
        alert_missing_fields(
            name,
            email,
            subject,
            description,
            file_size,
            cat_selected,
            sub_cat_sel
        );
    }
});
//@Rishi does not need to be in an onready event trigger
$(function() {
    console.log($breadcrumbHome);
    $breadcrumbHome.click(function() {
        state = {};
        breadcrumbs = [];
        $search.val("");
        generateBreadcrumbsAndTitle();
        window.history.pushState(state, "", location.pathname);
        displayCategories();
    });
    // console.log(location);
    let processInputTimeout;
    $search.on("keyup", function() {
        clearTimeout(processInputTimeout);
        processInputTimeout = setTimeout(function() {
            if (state.q !== $search.val()) {
                delete state.c;
                delete state.f;
                delete state.a;
                queryHandler($search.val(), {
                    replaceState: true,
                    fade: !state.q,
                });
            }
        }, 250);
    });
    let info = document.getElementById("support-info");
    revealer.onclick = function() {
        //fade this out fade that in
        revealer.style.display = "none";
        info.style.display = "block";
        info.style.visibility = "visible";
        document.getElementById('contact-us-header').style.display = "block";
        document.getElementById('contact-us-mini-header').style.display = "block";
        scrollToElement("#contact-us-header", {
            offset: -60,
            duration: 500
        });
        if (document.getElementById("category").selectedIndex != 0) {
            let catgry = $("#category").find("option:selected").val();
            if (document.getElementById(`inner_${catgry}`) == null) {
                // $("#category").val(catgry).trigger("change");
                $("#category").val("Category");
            }
        }
        if (navigator.cookieEnabled) {
            cookieJS.set("clickedSupport", 1);
        }
        // fetchCategories(); 
    };
    // **************************************
    // Populate idMap which stores category names and their ids
    // **************************************
    let sub_cat_sel = document.getElementById("sub-category");
    function set_cat_animation() {
        sub_cat_sel.style.maxHeight = '108px';
        sub_cat_sel.style.opacity = 1;
        sub_cat_sel.style.transition = "max-height 350ms ease-out, opacity .5s ease-in";
    }
    function fill_folders(fldr_array, category) {
        getFolders(idMap[category]).then(function(res) {
            res.data.forEach((folder) => {
                fldr_array.push(folder.name);
            });
            return fldr_array;
        }).then((f_arr) => {
            sub_cat_sel.innerHTML = `
`
                );
            } else {
                document.getElementById(`inner_${category}`).insertAdjacentHTML(
                    "beforeend",
                    `
`
                );
            }
        }).then(() => {
            // addtnl_reqrmnts();
            set_cat_animation();
            if (category == "Orders") {
                document.getElementById(`inner_${category}`).addEventListener('change', function() {
                    let sub_cat = $(`#inner_${category}`).val();
                    $(`#inner_${category}`).trigger("change");
                    if (sub_cat == "Placing a New Order") {
                        order_sel = false;
                        clearOrderNumber();
                    } else {
                        order_sel = true;
                        request_order_num();
                    }
                });
            } else {
                order_sel = false;
                clearOrderNumber();
            }
        }).catch((err) => console.log(err));
    }
    // **************************************
    //This chunk of code is to dynamically display fill drop downs based on the categories selected
    // **************************************
    // **************************************
    // Fill in the folders when a category is selected
    // **************************************
    function fetchData() {
        // @Rishi Why are you using asyncCallback with only 1 function?
        //@seva I just copied the code from categoryhandler() below. Yeah it makes no sense lol. Will change
        axios.get("/api/support/categories").then(response => {
            console.log(response.data);
            response.data.forEach(function(category) {
                idMap[category.name] = category.id;
                console.log("contents of idMap");
                console.log(idMap);
            });
        }).catch(console.error);
    }
    $("#category").change(function() {
        //fetch for data and populate idMap field
        fetchData();
        console.log("entering change function");
        fill_folders([], $(this).find("option:selected").attr("id"));
    });
    if (location.search) {
        state = queryString.parse(location.search);
        console.log(state);
    }
    console.log("state: " + JSON.stringify(state));
    window.history.replaceState(state, "", location.pathname + location.search);
    navFromState();
});
function pushState(breadcrumb, options) {
    $noResults.hide();
    console.log("New Breadcrumb: " + JSON.stringify(breadcrumb));
    if (breadcrumb.text || nameMap[breadcrumb.id]) {
        breadcrumbs.push(breadcrumb);
    }
    console.log("Breadcrumbs: " + JSON.stringify(breadcrumbs));
    generateBreadcrumbsAndTitle();
    if (breadcrumb.id) {
        state[breadcrumb.type] = breadcrumb.id;
    } else {
        delete state[breadcrumb.type];
    }
    console.log("state: " + JSON.stringify(state));
    if (options.pushHistory) {
        window.history.pushState(
            state,
            "",
            location.pathname + "?" + queryString.stringify(state)
        );
    } else if (options.replaceState) {
        window.history.replaceState(
            state,
            "",
            location.pathname + "?" + queryString.stringify(state)
        );
    }
}
window.onpopstate = function(event) {
    console.log("Previous State: " + JSON.stringify(state));
    console.log("New State from onpopstate: " + JSON.stringify(event.state));
    console.log(nameMap);
    state = event.state || {};
    breadcrumbs = [];
    generateBreadcrumbsAndTitle();
    navFromState();
};
function generateBreadcrumbsAndTitle() {
    ui.clearBreadcrumbs();
    // We start at breadcrumbs.length - 2 because we know that the last
    // breadcrumb in the list will never be interactive.
    // Be default, breadcrumbClick is a noop
    let breadcrumbClick = function() {};
    for (let i = breadcrumbs.length - 2; i >= 0; i--) {
        if (!breadcrumbs[i].onclick) {
            if (breadcrumbs[i].interactive) {
                breadcrumbClick = function() {
                    console.log(
                        "Clicked Crumb #" +
                        i +
                        " " +
                        JSON.stringify(breadcrumbs[i])
                    );
                    // Pop off all crumbs that are after the clicked crumb
                    for (let j = breadcrumbs.length - 1; j > i; j--) {
                        delete state[breadcrumbs.pop().type];
                    }
                    // Pop off the clicked crumb as well, it will get
                    // added back in the navFromBreadcrumb function.
                    navFromBreadcrumb(breadcrumbs.pop());
                };
            }
            breadcrumbs[i].onclick = breadcrumbClick;
        }
    }
    for (let i = 0; i < breadcrumbs.length - 1; i++) {
        let breadcrumb = breadcrumbs[i];
        if (breadcrumb.interactive) {
            createBreadcrumb(breadcrumb);
        }
    }
    // Unique logic for last breadcrumb in list
    //if the last breadcrumb is an article don't set it's title
    if (breadcrumbs.length) {
        $breadcrumbHome.show();
        let lastCrumb = breadcrumbs[breadcrumbs.length - 1];
        console.log(lastCrumb.type);
        //this should be missing as well
        if (lastCrumb.type == "c" || lastCrumb.type == "f") {
            if (breadcrumbs.length > 1 || !state.a) {
                createBreadcrumb(lastCrumb);
            }
        }
        ui.setTitle(lastCrumb.text || nameMap[lastCrumb.id]);
    }
}
function createBreadcrumb(breadcrumb) {
    ui.createBreadcrumb(
        breadcrumb.text || nameMap[breadcrumb.id],
        breadcrumb.onclick
    );
}
function navFromBreadcrumb(breadcrumb) {
    return {
        c: categoryHandler,
        f: folderHandler,
        a: articleHandler,
        q: queryHandler,
    } [breadcrumb.type](breadcrumb.id, {
        pushHistory: true,
        fade: true,
    });
}
function navFromState() {
    let shouldQueryNames = false;
    ["c", "f", "a"].forEach(function(type) {
        let id = state[type];
        if (id && !nameMap[id]) {
            shouldQueryNames = true;
        }
    });
    if (shouldQueryNames) {
        queryNames().then(function(response) {
            if (response && response.data) {
                console.log(
                    "Got new names for state: " + JSON.stringify(response.data)
                );
                //support form prepop
                Object.keys(response.data).forEach(function(type) {
                    idMap[response.data[type]] = state[type];
                    nameMap[state[type]] = response.data[type];
                });
                //missed in merge
                let cat = null;
                let sub_cat = null;
                if (Object.keys(response.data).length >= 1) {
                    cat = response.data.c;
                    if (response.data.f) {
                        sub_cat = response.data.f;
                    }
                }
                function category_prepop() {
                    if (cat != null) {
                        $("#category").val(cat).trigger("change");
                    }
                    setTimeout(subcat_prepop, 1000);
                }
                function subcat_prepop() {
                    if (sub_cat != null) {
                        console.log(`inner_${cat}`);
                        console.log("inside subcat_prepop");
                        // document.getElementById(`inner_${cat}`).value = sub_cat;
                        $(`#inner_${cat}`).val(sub_cat).trigger("change");
                        if (cat == "Orders") {
                            if (sub_cat == "Placing a New Order") {
                                order_sel = false;
                                clearOrderNumber();
                            } else {
                                order_sel = true;
                                request_order_num();
                            }
                        }
                    }
                }
                category_prepop();
            }
            processNavFromState();
            //till here ^
        });
    } else {
        processNavFromState();
    }
}
function processNavFromState() {
    if (state.a) {
        if (state.q) {
            $search.val(state.q);
            pushStateFromQuery(state.q, {
                pushHistory: false,
            });
        } else {
            pushStateFromState("c");
            pushStateFromState("f");
        }
        articleHandler(state.a, {
            pushHistory: false,
        });
    } else if (state.q) {
        $search.val(state.q);
        queryHandler(state.q, {
            fade: true,
        });
    } else {
        $search.val("");
        if (state.f) {
            pushStateFromState("c");
            folderHandler(state.f, {
                pushHistory: false,
            });
        } else if (state.c) {
            categoryHandler(state.c, {
                pushHistory: false,
            });
        } else {
            displayCategories();
        }
    }
}
function pushStateFromState(type) {
    let id = state[type];
    if (id && nameMap[id]) {
        pushState({
            type: type,
            id: id,
            interactive: true,
        }, {
            pushHistory: false,
        });
    }
}
function pushStateFromQuery(query, options) {
    pushState({
        type: "q",
        id: query,
        text: 'Search Results for "' + query + '"',
        interactive: true,
    }, options);
}
function queryHandler(query, options) {
    if (query) {
        options = options || {};
        let asyncFunctions = [function(callback) {
            queryArticles(query).then(callback);
        }];
        if (options.fade) {
            asyncFunctions.push(function(callback) {
                ui.fade(0, callback);
            });
        }
        utils.asyncCallback(asyncFunctions, function(response) {
            breadcrumbs = [];
            pushStateFromQuery(query, {
                pushHistory: options.pushHistory || !state.q,
                replaceState: options.replaceState,
            });
            ui.clear();
            if (response && response.data && response.data.length) {
                response.data.forEach(function(article) {
                    processArticle(JSON.parse(article.json), false, {
                        pushHistory: true,
                    });
                });
            } else {
                $noResults.show();
                ui.fade(1);
            }
        });
    } // else do nothing
}
function displayCategories() {
    utils.asyncCallback([
        function(callback) {
            ui.fade(0, callback);
        },
        function(callback) {
            axios.get("/api/support/categories").then(callback).catch(console.error);
        },
    ], function(response) {
        console.log(response.data);
        $breadcrumbHome.hide();
        ui.hideBreadCrumbs();
        $noResults.hide();
        ui.clear();
        ui.setTitle("Everykey Support");
        response.data.forEach(function(category) {
            nameMap[category.id] = category.name;
            idMap[category.name] = category.id;
            console.log("contents of idMap");
            console.log(idMap);
            ui.renderBlock(category.name, function() {
                categoryClick(category.id);
            });
            window.scrollTo(window.scrollX, 0);
        });
        ui.fade(1);
    });
}
function categoryClick(id) {
    let catName = nameMap[id];
    $("#category").val(catName).trigger("change");
    //this doesnt merge as well
    categoryHandler(id, {
        pushHistory: true,
    });
}
function categoryHandler(id, options) {
    options = options || {};
    utils.asyncCallback([
        function(callback) {
            ui.fade(0, callback);
        },
        function(callback) {
            getFolders(id).then(callback);
        },
    ], function(response) {
        pushState({
            type: "c",
            id: id,
            interactive: true,
        }, options);
        ui.clear();
        response.data.forEach(function(folder) {
            nameMap[folder.id] = folder.name;
            idMap[folder.name] = folder.id;
            ui.renderBlock(folder.name, function() {
                folderClick(folder.id);
            });
            window.scrollTo(window.scrollX, 0);
        });
        ui.fade(1);
    });
}
function folderClick(id) {
    //this should show up as well
    let foldName = nameMap[id];
    console.log(foldName);
    console.log(state.c);
    console.log(state);
    let catN = nameMap[state.c];
    console.log(catN);
    //condense this code later
    $(`#inner_${catN}`).val(foldName).trigger("change");
    if (catN == "Orders") {
        if (foldName == "Placing a New Order") {
            order_sel = false;
            clearOrderNumber();
        } else {
            order_sel = true;
            request_order_num();
        }
        //^
    }
    folderHandler(id, {
        pushHistory: true,
    });
}
function folderHandler(id, options) {
    options = options || {};
    utils.asyncCallback([
        function(callback) {
            ui.fade(0, callback);
        },
        function(callback) {
            getArticles(id).then(callback);
        },
    ], function(response) {
        ui.clear();
        if (response.data.length === 1) {
            pushState({
                type: "f",
                id: id,
                //these must be true
                interactive: true,
            }, {
                pushHistory: true,
            });
            processArticle(response.data[0], true, options);
        } else {
            pushState({
                type: "f",
                id: id,
                interactive: true,
            }, options);
            response.data.forEach(function(article) {
                processArticle(article, false, options);
            });
        }
    });
}
function articleHandler(id, options) {
    utils.asyncCallback([function(callback) {
            ui.fade(0, callback);
        },
        function(callback) {
            getArticle(id).then(callback);
        },
    ], function(response) {
        ui.clear();
        processArticle(response.data, true, options);
    });
}
function processArticle(article, single, options) {
    options = options || {};
    console.log(article);
    nameMap[article.id] = article.title;
    // Sometimes JSON keys are flattened and contained in top level of article object
    let redirectData = (function(descriptionText) {
        try {
            return JSON.parse(descriptionText);
        } catch (exception) {
            return;
        }
    })(article.description_text || article.json.description_text);
    if (redirectData) {
        if (redirectData.a) {
            // redirect from one article to another, simplest case.
            // query for the other article and put its data into the
            // article view instead of the article we currently have.
            processSingle(article.title, single, function() {
                articleHandler(redirectData.a, options);
            });
        } else if (redirectData.f) {
            // redirect from article to folder (list of articles)
            // query the articles and we make a single list entry which
            // once clicked will act as if a folder was clicked rather
            // than opening a new article.
            processSingle(article.title, single, function() {
                folderHandler(redirectData.f, options);
            });
        } else if (redirectData.c) {
            // redirect from article to category (list of folders)
            // since folders can not redirect, we simply create a list
            // entry that once clicked will open the category view.
            // if the article is alone in the folder, immediately open
            // the category view
            processSingle(article.title, single, function() {
                categoryHandler(redirectData.c, options);
            });
        } else {
            console.error("Cannot Redirect to " + JSON.stringify(redirectData));
        }
    } else {
        // Not a redirect, proceed to default rendering
        let description = article.description || article.json.description;
        let tags = article.tags || article.json.tags;
        if (single) {
            pushState({
                type: "a",
                id: article.id,
                interactive: false,
            }, options);
            ui.renderArticle(description);
            window.scrollTo(window.scrollX, 0);
            ui.fade(1);
        } else if (tags.indexOf("hidden_article") === -1) {
            // Hidden articles can still be rendered when navigated to directly
            ui.renderList(article.title, function() {
                ui.fade(0, function() {
                    pushState({
                        type: "a",
                        id: article.id,
                        interactive: false,
                    }, options);
                    ui.clear();
                    ui.renderArticle(description);
                    window.scrollTo(window.scrollX, 0);
                    ui.fade(1);
                });
            });
            window.scrollTo(window.scrollX, 0);
            ui.fade(1);
        } else {
            console.log("HIDE ARTICLE FROM LIST!");
            console.log(article);
        }
    }
}
// Render a list view with given data unless single is true in
// which case do not render, just trigger callback immediately
function processSingle(title, single, callback) {
    if (single) {
        callback();
    } else {
        ui.renderList(title, function() {
            callback();
        });
        window.scrollTo(window.scrollX, 0);
    }
}
function getArticles(id) {
    return axios.get("/api/support/articles?id=" + id).catch(console.error);
}
function getArticle(id) {
    return axios.get("/api/support/article?id=" + id).catch(console.error);
}
function getFolders(id) {
    return axios.get("/api/support/folders?id=" + id).catch(console.error);
}
function queryArticles(query) {
    return axios.get("/api/support/search?q=" + query).catch(console.error);
}
function queryNames() {
    return axios.post("/api/support/names", {
        state: state,
    }).catch(console.error);
}
const $ = require('jquery');
const utils = require('../utils/common');
const $content = $('#content');
const $title = $("#title");
const breadcrumbs = document.getElementById("breadcrumbs");
const revealer = document.getElementById("support-info-revealer");
let currOpacity = $content.css("opacity");
module.exports = {
    clear: function() {
        $content.html("");
    },
    fade: function(opacity, callback) {
        console.log(currOpacity + " -> " + opacity);
        if (currOpacity != opacity) {
            currOpacity = opacity;
            $content.add($title).fadeTo(250, opacity, utils.callbackOnce(callback));
        } else {
            console.log("DONT FADE, ALREADY AT STATE");
            callback && callback();
        }
    },
    renderBlock: function(title, onclick) {
        $title.removeClass("small");
        //this isn't merged too
        revealer.innerText = "Contact Us";
        let button = document.createElement("button");
        button.setAttribute("class", "button category");
        button.innerText = title;
        button.addEventListener("click", onclick);
        $content.append(button);
    },
    renderList: function(title, onclick) {
        $title.removeClass("small");
        revealer.innerText = "Contact Us";
        let button = document.createElement("button");
        button.setAttribute("class", "button article");
        button.innerText = title;
        button.addEventListener("click", onclick);
        $content.append(button);
    },
    renderArticle: function(articleHTML) {
        $title.addClass("small");
        revealer.innerText = "Contact Us";
        $content.html(articleHTML);
    },
    hideBreadCrumbs: function() {
        breadcrumbs.style.display = "none";
    },
    clearBreadcrumbs: function() {
        breadcrumbs.innerHTML = "";
    },
    createBreadcrumb: function(text, onclick) {
        breadcrumbs.style.display = "inline";
        let a = document.createElement("a");
        a.innerText = text;
        a.onclick = onclick;
        let li = document.createElement("li");
        li.append(a);
        breadcrumbs.append(li);
    },
    setTitle: function(title) {
        $title.text(title);
    }
};