VcSaJen / Sluggy Freelance - give back navigation buttons!

// ==UserScript==
// @name        Sluggy Freelance - give back navigation buttons!
// @namespace   http://vcsajen.com/sluggynavbtns
// @description Returns navigation buttons for Sluggy Freelance web comic. And calendar widget. And subchapter navigation list.
// @copyright   2018, VcSaJen
// @license     MIT
// @include     http://sluggy.com/*
// @include     http://www.sluggy.com/*
// @include     http://archives.sluggy.com/*
// @include     https://sluggy.com/*
// @include     https://www.sluggy.com/*
// @include     https://archives.sluggy.com/*
// @version     1.0
// @grant       none
// ==/UserScript==
/*eslint-disable no-unused-vars*/

(function () {
  'use strict';
  var isComicPage = document.querySelector(".comic_list") !== null;
  if (!isComicPage) return;
  
  var domain = window.location.hostname;
  var chapterElem = null;
  var currentPage = "";
  var currentChapterNumber = 0;
  var isFirstChapterInBook = false;
  var dateRegEx = /^#((\d{4})-(\d\d)-(\d\d)(?:-(\d{1,4}))?)(?:,(\w+))?$/;
  var urlPrev = "", urlNext = "", urlCur = "";
  var fullComicDatesLoaded = false;
  var comicDates = new Set();
  var comicSubchapterCache = {};
  
  if (domain === "sluggy.com" || domain === "www.sluggy.com") {
    let comicWrapper = document.querySelector(".comic_wrapper");
    comicWrapper.id = "main_comic_wrapper";
    comicWrapper.insertBefore(getNavPanel(), comicWrapper.querySelector(".comic_links"));
    if (comicWrapper.querySelector(".comic_popup") === null) {
      let comicPopup = document.createElement("div");
      comicPopup.className = "comic_popup";
      comicPopup.style.display = "none";
      comicWrapper.insertBefore(comicPopup, document.getElementById("comic_navigation"));
    }
    currentPage = comicWrapper.parentElement.dataset.comic_date;
    let chapterNum, chapterName, subchapterName;
    
    [].some.call(comicWrapper.parentElement.querySelector(".story_info").childNodes, function(infoNode) {
      if (infoNode.nodeType == Node.TEXT_NODE) {
        let chapterMatch = /^\s*Chapter (\d+): (.+?) - (.+?)\s*$/.exec(infoNode.nodeValue);
        if (chapterMatch !== null) {
          [, chapterNum, chapterName, subchapterName] = chapterMatch;
          return true;//break
        }
      }
      return false;//continue
    });
    
    let selEl = document.getElementById('nav_sel_subchapter');
    
    selEl.add(new Option("Comics Not Yet in Books"));
    selEl.add(new Option("\u00a0\u00a0Chapter "+chapterNum+": "+chapterName));
    selEl.add(new Option("\u00a0\u00a0\u00a0\u00a0"+subchapterName));
    selEl.selectedIndex = 2;
    generateCalendar();
    
    //var chapterNum = /Chapter ([0-9]+): /.exec(comicWrapper.parentElement.querySelector(".story_info").textContent)[1];
    currentChapterNumber = chapterNum;
    urlCur = "//archives.sluggy.com/book.php?chapter="+chapterNum;
    disableButtons(false);
  }
  
  if (domain === "archives.sluggy.com") {
    //alert("Archives Page!");
    var dateMatch = dateRegEx.exec(window.location.hash);
    if (dateMatch !== null && document.getElementById(dateMatch[1]) !== null) {
      currentPage = dateMatch[1];
      urlCur = window.location.href;
      chapterElem = document.getElementById('page').cloneNode(true);
      processPage(chapterElem);
      
      var date = new Date(dateMatch[2], dateMatch[3]-1, dateMatch[4]);
      //alert(date);
      var headerElem = document.getElementById('book-header');
      if (headerElem !== null) headerElem.parentElement.removeChild(headerElem);
      var comicsContainerElem = document.getElementById('innerLeft');
      var currentComicElem = document.getElementById(currentPage).parentElement;
      while (comicsContainerElem.firstChild) {
        comicsContainerElem.removeChild(comicsContainerElem.firstChild);
      }
      comicsContainerElem.appendChild(currentComicElem);
      let comicWrapper = document.querySelector(".comic_wrapper");
      comicWrapper.id = "main_comic_wrapper";
      comicWrapper.appendChild(getNavPanel());
      var linksElem = comicWrapper.querySelector(".comic_links");
      comicWrapper.removeChild(linksElem);
      comicWrapper.appendChild(linksElem);
      if (comicWrapper.querySelector(".comic_popup") === null) {
        let comicPopup = document.createElement("div");
        comicPopup.className = "comic_popup";
        comicPopup.style.display = "none";
        comicWrapper.insertBefore(comicPopup, document.getElementById("comic_navigation"));
      }
      if (comicWrapper.querySelector(".comic_popup") === null) {
        let comicPopup = document.createElement("div");
        comicPopup.className = "comic_popup";
        comicPopup.style.display = "none";
        comicWrapper.insertBefore(comicPopup, document.getElementById("comic_navigation"));
      }
      var chapNav = currentComicElem.querySelector(".chapter_nav.comic_spacer");
      if (chapNav !== null) chapNav.parentElement.removeChild(chapNav);
      postprocessPage();
      
      //Incomplete scroll while images load workaround
      let viewportH = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
      var spaceFillerElem = document.createElement("div");
      spaceFillerElem.style.minHeight = (viewportH-230)+"px";
      comicsContainerElem.appendChild(spaceFillerElem);
      
      //Fixing pagespeed shenanigans
      let realBGStyle = document.querySelector("#parallax-group + style");
      realBGStyle.id = "parallax-styles";
      let fakeBGStyle = document.querySelector("body > div:not([class]):not([id]) > style:only-child");
      if (fakeBGStyle !== null) {
        fakeBGStyle.parentElement.removeChild(fakeBGStyle);
        document.body.appendChild(realBGStyle);
      }
      
      //Handling commands sent by main page redirect
      if (dateMatch[6]=='next' || dateMatch[6]=='prev') {
        jumpToStrip(dateMatch[6]);
      } else {
        comicWrapper.scrollIntoView();
        let selEl = document.getElementById('nav_sel_subchapter');
        selEl.selectedIndex = +selEl.dataset.subchapterIndexOffset + +comicSubchapterCache[currentPage].selIndex;
        generateCalendar();
        if (dateMatch[6]=='loadcalendar') {
          loadDates();
        }
      }
      
      updateURL(true);
      
      disableButtons(false);
    }
  }
  window.addEventListener('popstate', windowOnPopState);
  
  //Adding my style
  let myStyleElem = document.createElement("style");
  myStyleElem.id = "btnnavstyle";
  myStyleElem.textContent = getMyCSSText();
  document.body.appendChild(myStyleElem);
  
  /**
  * @param {Element} elem
  */
  function processPage(elem) {
    var garbages = elem.querySelectorAll("#innerLeft > :not(.comic_list)"); 
    [].forEach.call(garbages, function(garbage) {
      if (garbage.parentNode) garbage.parentNode.removeChild(garbage);
    });
    var uselessFirst = elem.querySelector("#begin");
    if (uselessFirst !== null) uselessFirst.parentElement.parentElement.removeChild(uselessFirst.parentElement);
    urlPrev = "";
    urlNext = "";
    var aPrevChapterElem = elem.querySelector(".chapter_nav > .previous > a");
    if (aPrevChapterElem!==null) urlPrev = aPrevChapterElem.href;
    var aNextChapterElem = elem.querySelector(".chapter_nav > .next > a");
    if (aNextChapterElem!==null) urlNext = aNextChapterElem.href;
  }
  
  function postprocessPage() {
    var selEl = document.getElementById('nav_sel_subchapter');
    while (selEl.length > 0) selEl.remove(0);
    //selEl.add(new Option("Subchapter name", "URL", false, false));
    var bookTitle = chapterElem.querySelector("#book-header-title").textContent;
    var bookMatch = /^Book (\d+): (.+)$/.exec(bookTitle);
    var bookOpt = new Option( bookTitle, (bookMatch !== null) ? bookMatch[1] : 0, false, false);
    bookOpt.dataset.type = "book";
    selEl.add(bookOpt);
    
    var comicElems = chapterElem.querySelectorAll("#innerLeft > .comic_list"); 
    if (!fullComicDatesLoaded)
      comicDates = new Set();
    comicSubchapterCache = {};
    var lastSubchapterName = "";
    var subchapters = [];
    var chapter;
    [].forEach.call(comicElems, function(comicElem) { //subchapters
      var comicSpacerElem = comicElem.querySelector(".comic_spacer");
      var comicDate = comicSpacerElem.id;
      if (!fullComicDatesLoaded && /^((\d{4})-(\d\d)-(\d\d)?)$/.test(comicDate)) {
        comicDates.add(comicDate);
      }
      var storyInfoElem = comicElem.querySelector(".story_info");
      var infoNodes = storyInfoElem.childNodes;
      [].some.call(infoNodes, function(infoNode) {
        if (infoNode.nodeType == Node.TEXT_NODE) {
          var chapterRegEx = /^\s*Chapter (\d+): (.+?) - (.+?)\s*$/;
          var chapterValue = infoNode.nodeValue;
          var chapterMatch = chapterRegEx.exec(chapterValue);
          if (chapterMatch !== null) {
            chapter = chapterMatch[1];
            var chapterName = chapterMatch[2];
            var subchapterName = chapterMatch[3];
            if (lastSubchapterName !== subchapterName) {
              lastSubchapterName = subchapterName;
              subchapters.push({"subchName": subchapterName, "subchDate": comicDate});
            }
            comicSubchapterCache[comicDate] = {};
            comicSubchapterCache[comicDate].selIndex = subchapters.length-1;
            comicSubchapterCache[comicDate].name = subchapterName;
            /*if (comicDate === currentPage) {
              selEl[selEl.length-1].defaultSelected = true;
              selEl[selEl.length-1].selected = true;
            }*/
            return true;//break
          }
        }
        return false;//continue
      });
    });
    
    currentChapterNumber = chapter;
    isFirstChapterInBook = false;
    
    var chapterLinkElems = chapterElem.querySelectorAll("#book-header-chapters > .book-header-chapter");
    [].forEach.call(chapterLinkElems, function(chapterLinkElem, index) { //chapters
      var chapterNumString = chapterLinkElem.querySelector(".book-header-chapter-number").textContent;
      var chapterNumMatch = /^\s*Chapter (\d+)\s*$/.exec(chapterNumString);
      if (chapterNumMatch === null) return;
      var chapterNum = chapterNumMatch[1];
      var chapterName = chapterLinkElem.querySelector(".book-header-chapter-name").textContent;
      var chapterOpt = new Option( "\u00a0\u00a0Chapter "+chapterNum+": "+chapterName, chapterNum, false, false);
      chapterOpt.dataset.type = "chapter";
      selEl.add(chapterOpt);
      if (index === 0 && chapterNum === chapter) {
        isFirstChapterInBook = true;
      }
      if (chapterNum === chapter) {
        selEl.dataset.subchapterIndexOffset = selEl.length;
        subchapters.forEach(function(subchapter) {
          var opt = new Option("\u00a0\u00a0\u00a0\u00a0"+subchapter.subchName, subchapter.subchDate, false, false);
          opt.dataset.type = "subchapter";
          selEl.add(opt);
        });
      }
    });
  }
  
  /**
  * @param {number} direction - 1 - next, -1 - prev
  * @returns {?Element}
  */
  function getSiblingComicElem(direction) {
    if (chapterElem===null) return null;
    var elem = chapterElem.querySelector("div[id='"+currentPage+"']");
    if (elem===null) return null;
    elem = elem.parentElement;
    if (elem===null) return null;
    if (direction == 1) return elem.nextElementSibling;
    if (direction == -1) return elem.previousElementSibling;
    if (direction == 0) return elem;
    return null;
  }
  
  /**
  * @param {MouseEvent} event
  */
  function onNavBtnClick(event) {
    var btn = event.target;
    if (chapterElem===null) {
      window.location = urlCur+"#"+currentPage+","+((btn.id === "nav_prev") ? "prev" : "next");
      //loadChapter("http://archives.sluggy.com/daily.php?date="+currentPage, (btn.id === "nav_prev") ? "prev" : "next");
      return;
    }
    var elem = chapterElem.querySelector("div[id='"+currentPage+"']");
    if (elem===null) return;
    elem = elem.parentElement;
    if (elem===null) return;
    
    if (btn.id === "nav_prev") {
      elem=elem.previousElementSibling;
    } else if (btn.id === "nav_next") {
      elem=elem.nextElementSibling;
    } else return;
    if (elem===null) {
      //Jump to prev/next chapter
      disableButtons(true);
      var curComicContent = document.querySelector("#main_comic_wrapper .comic_content");
      if (curComicContent !== null) curComicContent.style.opacity="0.5";
      if (btn.id === "nav_prev") {
        loadChapter(urlPrev, "last");
      } else {
        loadChapter(urlNext, "first");
      }
    } else {
      //Normal
      jumpToStrip((btn.id === "nav_prev") ? "prev" : "next");
      updateURL();
    }
    
  }
  
  /**
  * @param {boolean} disable - true if disable, false if undisable
  */
  function disableButtons(disable) {
    var btnNext = document.getElementById('nav_next');
    var btnPrev = document.getElementById('nav_prev');
    var btnFirst = document.getElementById('nav_first');
    var btnLast = document.getElementById('nav_last');
    var btnGo = document.getElementById('nav_goto_subchapter');
    var tblCalendar = document.getElementById('nav_calendar');
    if (btnNext === null || btnPrev === null) return;
    if (disable) {
      btnFirst.disabled = true;
      btnNext.disabled = true;
      btnPrev.disabled = true;
      btnGo.disabled = true;
      tblCalendar.dataset.disabled = "";
    } else {
      if (chapterElem === null) {
        btnNext.disabled = true;
        btnPrev.disabled = false;
        btnGo.disabled = true;
      } else {
        btnNext.disabled = (getSiblingComicElem(1) === null) && (urlNext === "");
        btnPrev.disabled = (getSiblingComicElem(-1) === null) && (urlPrev === "");
        btnGo.disabled = false;
      }
      btnFirst.disabled = btnPrev.disabled;
      btnLast.disabled = btnNext.disabled;
      if (tblCalendar.dataset.disabled !== undefined) delete tblCalendar.dataset.disabled;
    }
  }
  
  function fixImgSrcs(elem) {
    var imgs = elem.querySelectorAll("img[data-src]");
    [].forEach.call(imgs, function(img) {
      img.src = img.dataset.src;
      delete img.dataset.src;
    });
  }
  
  /**
  * @param {PopStateEvent} event
  */
  function windowOnPopState(event) {
    var newUrl = new URL(location.href);
    var newComicPage = newUrl.searchParams.get('date');
    if (newComicPage === null) return;
    
    jumpToArbitraryStrip(newUrl, newComicPage, false);
  }
  
  /**
  * @param {boolean} [replace=false] - true = replace state, false = push state
  */
  function updateURL(replace) {
    if (replace === undefined) replace = false;
    //var data = {};
    //data.url = urlCur;
    //data.comicPage = currentPage;
    var result = dateRegEx.exec("#"+currentPage);
    if (result !== null)
      document.title = result[3]+"/"+result[4]+"/"+result[2]+(result[5] ? " (recap #"+result[5]+")" : "")+" - Sluggy Freelance Archives";
    if (replace) {
      history.replaceState(/*data*/ null, null, "daily.php?date="+currentPage);
    } else {
      history.pushState(/*data*/ null, null, "daily.php?date="+currentPage); 
    }
  }
  
  function jumpToArbitraryStrip(url, strip, updateUrl) {
    if (updateUrl === undefined) updateUrl=true;
    if (strip === currentPage) return;
    if (url==null) url="//archives.sluggy.com/daily.php?date="+strip;
    
    if (!jumpToStrip(strip)) {
      disableButtons(true);
      var el = document.querySelector("#main_comic_wrapper > .comic_content");
      if (el !== null) el.style.opacity = "0.5";
      loadChapter(url, strip, updateUrl);
    } else {
      if (updateUrl) updateURL();
    }
  }
  
  /**
  * @param {string} strip - 'first', 'prev', 'next', 'last' (in chapter), or ID (YYYY-MM-DD) of strip
  * @returns {boolean} success
  */
  function jumpToStrip(strip) {
    if (chapterElem === null) return false;
    var container = chapterElem.querySelector("#innerLeft");
    var stripElem = null;
    var currentStrip = container.querySelector("div[id='"+currentPage+"']");//only for next and prev!!!
    switch (strip) {
      case 'first':
        stripElem = container.querySelector(".comic_list");
        break;
      case 'last':
        stripElem = container.querySelector(".comic_list:last-of-type");
        break;
      case 'next':
        if (currentStrip !== null)
          stripElem = currentStrip.parentElement.nextElementSibling;
        break;
      case 'prev':
        if (currentStrip !== null)
          stripElem = currentStrip.parentElement.previousElementSibling;
        break;
      default:
        if (dateRegEx.test("#"+strip)) {
          stripElem = container.querySelector("div[id='"+strip+"']");
          if (stripElem !== null) stripElem = stripElem.parentElement;
        }
    }

    if (stripElem === null) {
      console.log("FAIL stripElem is null");
      return false;
    }
    
    var newComicWrapper = stripElem.querySelector(".comic_wrapper");
    var realComicWrapper = document.getElementById('main_comic_wrapper');
    if (newComicWrapper === null || realComicWrapper === null) return false;
    newComicWrapper = newComicWrapper.cloneNode(true);
    realComicWrapper.style.backgroundColor = newComicWrapper.style.backgroundColor;
    var newDate = stripElem.querySelector(".comic_spacer").id;
    var newComicContent = newComicWrapper.querySelector(".comic_content");
    fixImgSrcs(newComicContent);
    realComicWrapper.replaceChild(newComicContent, realComicWrapper.querySelector(".comic_content"));
    var newComicPopup = newComicWrapper.querySelector(".comic_popup");
    if (newComicPopup !== null) {
      realComicWrapper.replaceChild(newComicWrapper.querySelector(".comic_popup"), realComicWrapper.querySelector(".comic_popup"));
    } else {
      realComicWrapper.querySelector(".comic_popup").style.display = "none";
    }
    realComicWrapper.replaceChild(newComicWrapper.querySelector(".comic_links"), realComicWrapper.querySelector(".comic_links"));
    
    var newStoryInfo = stripElem.querySelector(".story_info").cloneNode(true);
    var realStoryInfo = realComicWrapper.parentElement.querySelector(".story_info");
    realComicWrapper.parentElement.replaceChild(newStoryInfo, realStoryInfo);
    
    currentPage = newDate;
    disableButtons(false);
    var selElem = document.getElementById('nav_sel_subchapter');
    if (comicSubchapterCache[newDate])
      selElem.selectedIndex = +selElem.dataset.subchapterIndexOffset + +comicSubchapterCache[newDate].selIndex;
    generateCalendar();
    realComicWrapper.scrollIntoView();
    return true;
  }
  
  /**
  * @param {string} url - chapter url in archive
  * @param {string} jumpToStrip - 'first', 'prev', 'next', 'last' (in chapter)
  * @param {boolean} [updateUrl=true] 
  */
  function loadChapter(url, strip, updateUrl) {
    if (updateUrl === undefined) updateUrl=true;
    var xhr = new XMLHttpRequest();
    xhr.onload = function() {
      chapterElem = xhr.response.getElementById('page');
      processPage(chapterElem);
      postprocessPage();
      urlCur = url;
      updateChapterBackground();
      jumpToStrip(strip);
      generateCalendar(true);
      if (updateUrl) updateURL();
      //console.log(this.responseXML.title);
    };
    xhr.onerror = function(e) {
      console.log("Error: "+e.target.status);
    };
    xhr.open("GET", url);
    xhr.responseType = "document";
    xhr.send();
  }
  
  function updateChapterBackground() {
    var newParallaxGroup = chapterElem.querySelector("#parallax-group");
    var newBGStyle = chapterElem.querySelector("#parallax-group + style");
    var realParallaxGroup = document.querySelector("#parallax-group");
    var realBGStyle = document.querySelector("#parallax-styles");
    
    if (newParallaxGroup === null || newBGStyle === null || realParallaxGroup === null || realBGStyle === null)
      return;
    newParallaxGroup = newParallaxGroup.cloneNode(true);
    newBGStyle = newBGStyle.cloneNode(true);
    newBGStyle.id = "parallax-styles";

    realParallaxGroup.parentElement.replaceChild(newParallaxGroup, realParallaxGroup);
    realBGStyle.parentElement.replaceChild(newBGStyle, realBGStyle);
  }
  
  function loadDates() {
    var url = "http://archives.sluggy.com/calendar.php";
    var xhr = new XMLHttpRequest();
    xhr.onload = function() {
      var scriptText = xhr.response.getElementById('innerLeft').querySelector("#calendar + script").textContent;
      var startMarker = "var dates_available=['";
      var endMarker = "',];var monthsWide";
      var startIndex = scriptText.indexOf(startMarker)+startMarker.length;
      var endIndex = scriptText.lastIndexOf(endMarker);
      scriptText = scriptText.substring(startIndex, endIndex);
      let dateArr = scriptText.split("','");
      comicDates = new Set();
      let len = dateArr.length;
      for(let i = 0; i<len; i++) {
        comicDates.add(dateArr[i]);
      }
      fullComicDatesLoaded = true;
      document.getElementById('nav_calendar_overlay').remove();
      generateCalendar(true);
    };
    xhr.onerror = function(e) {
      console.log("Error while loading calendar page: "+e.target.status);
    };
    xhr.open("GET", url);
    xhr.responseType = "document";
    xhr.send();
  }
  
  /**
  * @param {MouseEvent} event
  */
  function onTblCalendarMonthNavClick(event) {
    var tblCalendar = document.getElementById('nav_calendar');
    if (event.target.classList.contains('nav_prev_month') === event.target.classList.contains('nav_next_month')) return;
    event.preventDefault();
    event.stopPropagation();
    var offset = event.target.classList.contains('nav_prev_month') ? -1 : 1;
    var year, month;
    [year, month] = tblCalendar.dataset.month.split('-').map(Number);
    month-=1;
    var date = new Date(Date.UTC(year, month+offset));
    generateCalendar(false, date.getUTCFullYear(), date.getUTCMonth());
  }
  
  /**
  * @param {MouseEvent} event
  */
  function onTblCalendarCellClick(event) {
    var tblCalendar = document.getElementById('nav_calendar');
    if (tblCalendar.dataset.disabled !== undefined) return;
    var cell = event.target;
    if (cell.classList.contains("nav_calendar_clickable")) {
      var strip = cell.dataset.date;
      jumpToArbitraryStrip("http://archives.sluggy.com/daily.php?date="+strip, strip);
    }
  }
  
  /**
  * Updates calendar to reflect month
  * When year and month parameters are not present, month for current comic is used.
  * @param [boolean] force - force full update
  * @param [number] year - year, for example, 2013
  * @param [number] month - month, 0 for January, 11 for December
  */
  function generateCalendar(force, year, month) {
    if (force === undefined) force = false;
    if (month === undefined && year === undefined) {
      [year, month] = currentPage.split("-").map(Number);
      month -= 1;
    }
    if (month === undefined) month = 0;
    var tblCalendar = document.getElementById('nav_calendar');
    var monthStart = new Date(Date.UTC(year, month));
    if (!force && tblCalendar.dataset.month === monthStart.toISOString().substring(0, 7)) {
      tblCalendar.querySelectorAll(".nav_calendar_current_date").forEach(function (curDateElem) {
        curDateElem.classList.remove("nav_calendar_current_date");
      });
      let elem = tblCalendar.querySelector('[data-date="'+currentPage+'"]');
      if (elem!==null) elem.classList.add("nav_calendar_current_date");
      return;
    } 
    tblCalendar.dataset.month = monthStart.toISOString().substring(0, 7);
    var curMonthElem = tblCalendar.querySelector(".nav_cur_month");
    var prevMonthElem = tblCalendar.querySelector(".nav_prev_month");
    var nextMonthElem = tblCalendar.querySelector(".nav_next_month");
    var locale = "en-us";
    curMonthElem.textContent = monthStart.toLocaleString(locale, {timeZone: "UTC", year: "numeric", month: "short"});
    prevMonthElem.textContent = "["+(new Date(Date.UTC(year, month-1))).toLocaleString(locale, {timeZone: "UTC", month: "short"})+"]";
    nextMonthElem.textContent = "["+(new Date(Date.UTC(year, month+1))).toLocaleString(locale, {timeZone: "UTC", month: "short"})+"]";
    var dateOffset = -monthStart.getUTCDay();
    var rows = tblCalendar.tBodies[0].rows;
    for (let i=0; i<6; i++) {
      let cells = rows[i+1].cells; //i+1 because first row with th's is skipped
      let curMonthDaysNum = 0;//in current week
      for (let j=0; j<7; j++) {
        let cell = cells[j];
        let date = new Date(Date.UTC(year, month, dateOffset+i*7+j+1));
        let stringDate = date.toISOString().split("T")[0];
        cell.textContent = date.getUTCDate();
        cell.classList.toggle("nav_calendar_curmonth_date", date.getUTCMonth() === month);
        cell.classList.toggle("nav_calendar_current_date", stringDate === currentPage);
        cell.dataset.date = stringDate;
        cell.classList.toggle("nav_calendar_clickable", comicDates.has(stringDate));
 
        if (date.getUTCMonth() === month) curMonthDaysNum++;
      }
      rows[i+1].style.display = (curMonthDaysNum===0) ? "none" : null;
    }
    //enable/disable overlay
    if (!fullComicDatesLoaded && chapterElem !== null) {
      let y,m,d;
      [y,m,d] = chapterElem.querySelector(".comic_list:first-child").querySelector(".comic_spacer").id.split("-").map(Number);
      var chapterStartDate = new Date(y,m-1,d);
      [y,m,d] = chapterElem.querySelector(".comic_list:last-child").querySelector(".comic_spacer").id.split("-").map(Number);
      var chapterEndDate = new Date(y,m-1,d);
      var calendarStartDate = new Date(Date.UTC(year, month, dateOffset));
      var monthEnd = new Date(Date.UTC(year, month+1));
      monthEnd = new Date(Date.UTC(monthEnd.getUTCFullYear(), monthEnd.getUTCMonth(), monthEnd.getUTCDate()-1));
      var endOffset = 6-monthEnd.getUTCDay();
      var calendarEndDate = new Date(Date.UTC(monthEnd.getUTCFullYear(), monthEnd.getUTCMonth(), monthEnd.getUTCDate()+endOffset));
      var overlayElem = document.getElementById('nav_calendar_overlay');
      if (chapterStartDate>calendarStartDate || chapterEndDate<monthEnd) {
        overlayElem.style.display = null;
      } else {
        overlayElem.style.display = "none";
      }
    }
  }
  
  /**
  * @returns {Element}
  */
  function getNavPanel() {
    var navPanel = document.createElement("nav");
    navPanel.id = 'comic_navigation';
    var btnFirst = document.createElement("button");
    btnFirst.id = 'nav_first';
    btnFirst.classList.add("comic_nav_btn");
    btnFirst.textContent = "First";
    btnFirst.addEventListener('click', function(event) {
      if (chapterElem == null) {
        window.location = "//archives.sluggy.com/daily.php?date=1997-08-25"; 
      } else {
        jumpToArbitraryStrip("http://archives.sluggy.com/book.php?chapter=1", "1997-08-25");
      }
    });
    var btnPrev = document.createElement("button");
    btnPrev.id = 'nav_prev';
    btnPrev.classList.add("comic_nav_btn");
    btnPrev.textContent = "Prev.";
    btnPrev.addEventListener('click', onNavBtnClick);
    var btnNext = document.createElement("button");
    btnNext.id = 'nav_next';
    btnNext.classList.add("comic_nav_btn");
    btnNext.textContent = "Next";
    btnNext.addEventListener('click', onNavBtnClick);
    var btnLast = document.createElement("button");
    btnLast.id = 'nav_last';
    btnLast.classList.add("comic_nav_btn");
    btnLast.textContent = "Last";
    btnLast.addEventListener('click', function(event) {
      window.location = "//sluggy.com/";
    });
    var btnChapter = document.createElement("button");
    btnChapter.id = 'nav_chapter';
    btnChapter.classList.add("comic_nav_btn");
    btnChapter.textContent = "Chapter";
    btnChapter.title = "View comic chapter by chapter";
    btnChapter.addEventListener('click', function(event) {
      window.location = urlCur.replace(/#.+$/, "");
    });
    var cbSubchapterNav = document.createElement("select");
    cbSubchapterNav.id = 'nav_sel_subchapter';
    /*cbSubchapterNav.add(new Option("First option"));
    cbSubchapterNav.add(new Option("Second option"));
    cbSubchapterNav.add(new Option("Third option"));*/
    var btnGoToSubchapter = document.createElement("button");
    btnGoToSubchapter.id = 'nav_goto_subchapter';
    btnGoToSubchapter.classList.add("comic_nav_btn");
    btnGoToSubchapter.textContent = "Go";
    if (chapterElem == null) {
      btnGoToSubchapter.disabled = true;
    }
    btnGoToSubchapter.addEventListener('click', function(event) {
      if (cbSubchapterNav.selectedIndex >= 0) {
        var el = document.querySelector("#main_comic_wrapper > .comic_content");
        var opt = cbSubchapterNav[cbSubchapterNav.selectedIndex];
        switch (opt.dataset.type) {
          case ("subchapter"):
            jumpToStrip(opt.value);
            updateURL();
            break;
          case ("chapter"):
            if (currentChapterNumber !== opt.value) {
              disableButtons(true);
              if (el !== null) el.style.opacity = "0.5";
              loadChapter("http://archives.sluggy.com/book.php?chapter="+opt.value, "first");
            } else { 
              jumpToStrip("first");
              updateURL();
            }
            break;
          case ("book"):
            if (isFirstChapterInBook) {
              jumpToStrip("first");
              updateURL();
            } else { 
              disableButtons(true);
              if (el !== null) el.style.opacity = "0.5";
              loadChapter("http://archives.sluggy.com/book.php?book="+opt.value, "first"); 
            }
            break;
        }
      }
    });
    var tblCalendarWrapper = document.createElement("div");
    tblCalendarWrapper.classList.add("nav_calendar_wrapper");
    var tblCalendar = document.createElement("table");
    tblCalendar.id = 'nav_calendar';
    var tHead = tblCalendar.createTHead();
    let tHeadRow = tHead.insertRow();
    let tHeadCell = document.createElement("th");
    tHeadCell.colSpan = 7;
    tHeadRow.appendChild(tHeadCell);
    
    let prevMonth = document.createElement("span");
    prevMonth.textContent = "[prev]"; //TODO: Убрать
    prevMonth.classList.add("nav_prev_month");
    prevMonth.classList.add("nav_calendar_clickable");
    prevMonth.addEventListener('click', onTblCalendarMonthNavClick, true);
    let curMonth = document.createElement("span");
    curMonth.textContent = "[cur]"; //TODO: Убрать
    curMonth.classList.add("nav_cur_month");
    let nextMonth = document.createElement("span");
    nextMonth.textContent = "[next]"; //TODO: Убрать
    nextMonth.classList.add("nav_next_month");
    nextMonth.classList.add("nav_calendar_clickable");
    nextMonth.addEventListener('click', onTblCalendarMonthNavClick, true);
    tHeadCell.appendChild(prevMonth);
    tHeadCell.appendChild(curMonth);
    tHeadCell.appendChild(nextMonth);
    var tBody = tblCalendar.appendChild(document.createElement("tbody"));
    let wdays = ["Su", "M", "Tu", "W", "Th", "F", "Sa"];
    for (let i=0;i<7;i++) {
      let row = tBody.insertRow();
      for (let j=0;j<7;j++) {
        let span = document.createElement("span");
        let cell;
        if (i===0) {
          cell = document.createElement("th");
          span.textContent = wdays[j];
          cell.appendChild(span);
          row.appendChild(cell);
        } else {
          cell = row.insertCell();
          span.textContent = "0"; //TODO: Убрать
          cell.appendChild(span);
          cell.addEventListener('click', onTblCalendarCellClick);
        }
      }
    }
    var tblCalendarOverlay = document.createElement("div");
    tblCalendarOverlay.id = "nav_calendar_overlay";
    tblCalendarOverlay.classList.add("nav_calendar_overlay");
    tblCalendarOverlay.textContent = "Click to load";
    tblCalendarOverlay.addEventListener('click', function (event) {
      if (chapterElem === null) {
        window.location = urlCur+"#"+currentPage+",loadcalendar";
        return;
      }
      if (event.target.dataset.disabled===undefined) {
        event.target.dataset.disabled="";
        event.target.textContent="Loading...";
        loadDates();
      }
    });
    tblCalendarWrapper.appendChild(tblCalendar);
    tblCalendarWrapper.appendChild(tblCalendarOverlay);
    
    navPanel.appendChild(btnFirst);
    navPanel.appendChild(btnPrev);
    navPanel.appendChild(btnNext);
    navPanel.appendChild(btnLast);
    navPanel.appendChild(btnChapter);
    navPanel.appendChild(cbSubchapterNav);
    navPanel.appendChild(btnGoToSubchapter);
    navPanel.appendChild(tblCalendarWrapper);
    
    document.addEventListener('keydown', function(event) {
      switch (event.key) {
        case "ArrowRight":
          btnNext.click();
          break;
        case "ArrowLeft":
          btnPrev.click();
          break;
      }
    });
    
    return navPanel;
  }
    
  function getMyCSSText() {
    return String.raw`/*My CSS*/
      #comic_navigation {
        padding: 0 25px 0 25px;
        margin-top: 10px;
      }
      #comic_navigation > * {
        float: left;
      }
      #nav_sel_subchapter {
        border: 1px solid #cccccc;
        font-size: 10px;
        margin: 0px 4px 0px 0px;
        padding: .4em .2em;
        border-radius: 4px;
        height: 16px;
        box-sizing: content-box;
        background: #f6f6f6 url(//ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/themes/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x;
      }
      .comic_nav_btn {
        height: 16px;
        box-sizing: content-box;
        border: 1px solid #cccccc;
        background: #f6f6f6 url(//ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/themes/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x;
        font-weight: bold;
        color: #1c94c4;
        font-size: 10px;
        margin: 0 4px 0 0;
        padding: .4em 1em;
        border-radius: 4px;
      }
      .comic_nav_btn:hover {
        border: 1px solid #fbcb09;
        background: #fdf5ce url(http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/themes/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png) 50% 50% repeat-x;
        color: #c77405;
      }
      .comic_nav_btn:active {
        filter: brightness(0.9) saturate(150%);
      }
      .comic_nav_btn:disabled:active {
        filter: brightness(1) saturate(1);
      }
      .comic_nav_btn:disabled {
        opacity: 0.35;
      }
      .comic_nav_btn:disabled:hover {
        opacity: 0.4;
      }
      #nav_first:before, #nav_prev:before, #nav_next:after, #nav_last:after, #nav_chapter:before {
        content: " ";
        display: inline-block;
        height: 16px;
        width: 16px;
        line-height: 16px;
        margin: 0px 0px -5px 0px;
        position: relative;
        top: -1px;
        left: 0px;
        background: url("http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/themes/ui-lightness/images/ui-icons_ef8c08_256x240.png") no-repeat left center transparent;
      }
      #nav_first:before {
        background-position: -80px -160px;
        left: -4px;
      }
      #nav_prev:before {
        background-position: -48px -160px;
        left: -4px;
      }
      #nav_chapter:before {
        background-position: -224px -96px;
        left: -4px;
      }
      #nav_next:after {
        background-position: -32px -160px;
        left: 4px;
      }
      #nav_last:after {
        background-position: -64px -160px;
        left: 4px;
      }

      #main_comic_wrapper > .comic_popup {
        margin-bottom: 0px;
      }

      .nav_calendar_wrapper {
        position: relative;
        width: 175px;
      }
      #nav_calendar {
        font-size: 10px;
        background-color: #cccccc;
        table-layout: fixed;
        width: 100%;
        font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
        border-spacing: 2px;
        border-collapse: separate;
        border-radius: 4px;
        border: 1px solid #dddddd;
        background: #eeeeee url(//ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/themes/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png) 50% top repeat-x;
      }
      #nav_calendar > thead > tr > th {
        position: relative;
        z-index: 1;
        border: 1px solid #e78f08;
        background: #f6a828 url(//ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/themes/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png) 50% 50% repeat-x;
        color: #ffffff;
        font-weight: bold;
        border-radius: 4px;
      }
      #nav_calendar th {
        font-weight: 800;
      }
      #nav_calendar th, #nav_calendar td {
        text-align: center;
      }
      .nav_prev_month {
        float: left;
      }
      .nav_next_month {
        float: right;
      }
      .nav_calendar_overlay {
        border-radius: 4px;
        position: absolute;
        top: 0;
        bottom: 0;
        background-color: rgba(255, 255, 255, 0.75);
        width: 100%;
        height: 100%;
        display: flex;
        flex-direction: row;
        justify-content: center;
        align-items: center;
        cursor: pointer;
      }
      .nav_calendar_overlay.nav_loading {
        cursor: default;
      }
      .nav_calendar_curmonth_date {
        border: 1px solid #cccccc;
        background: #f6f6f6 url(//ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/themes/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x;
      }
      #nav_calendar td:not(.nav_calendar_curmonth_date):not(.nav_calendar_clickable) {
        color: #9b9b9b;
      }
      #nav_calendar td.nav_calendar_curmonth_date {
        text-align: right;
      }
      #nav_calendar .nav_calendar_curmonth_date:not(.nav_calendar_clickable) {
        opacity: .35;
      }
      .nav_calendar_curmonth_date.nav_calendar_current_date {
        background: #ffffff url(//ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/themes/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x;
        border: 1px solid #fbd850;
        font-weight: bold;
      }
      .nav_calendar_clickable {
        color: #1c94c4;
        cursor: pointer;
      }
      td.nav_calendar_clickable:hover {
        border: 1px solid #fbcb09;
        background: #fdf5ce url(//ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/themes/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png) 50% 50% repeat-x;
        color: #c77405;
      }
      #nav_calendar[data-disabled] td.nav_calendar_clickable {
        color: #9b9b9b;
        cursor: default;
        opacity: .5;
      }
      `;
  }
})();