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); } };