Advertisement
Guest User

Chapter counter for Map and Next Chapter choice

a guest
Apr 5th, 2023
111
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name         Chapter counter for Map and Next Chapter choice
  3. // @version      3.0.0
  4. // @description  Counts and displays how many chapters each branch has inside on the map screen and when choosing your next chapter
  5. // @author       sllypper
  6. // @namespace    https://greasyfork.org/en/users/55535-sllypper
  7. // @match        *://chyoa.com/story/*
  8. // @match        *://chyoa.com/chapter/*
  9. // @icon         https://chyoa.com/favicon.png
  10. // @grant        GM.getValue
  11. // @grant        GM.setValue
  12. // @grant        GM.deleteValue
  13. // @grant        GM.listValues
  14. // @grant        GM.registerMenuCommand
  15. // ==/UserScript==
  16.  
  17.  
  18. /* Load the entire map by moving to the bottom, then click the Regenerate button. */
  19.  
  20. // runs automatically when opening a new map
  21. let runAutomatically = true
  22.  
  23. // shows the floating buttons on the bottom-right of the page
  24. let showFloatingButton = true
  25. let showTogglerButton = true
  26.  
  27. // Style for the counter on the chapter pages. Use one of the below between ""
  28. // alternative | simple | crazy | default
  29. const cssStyle = "alternative"
  30.  
  31. /************************/
  32.  
  33. let chapterDataArray;
  34. let chapterData;
  35. let storyStorageId;
  36. let pageType = getPageType()
  37. console.log('The current page is a ' + pageType);
  38.  
  39. /************************/
  40. (async() => {
  41.   if (pageType == "map") {
  42.     GM.registerMenuCommand("Regenerate map data", generateDraw, "r");
  43.     GM.registerMenuCommand("Redraw map counters", drawChildrenCounter, "d");
  44.     GM.registerMenuCommand("Fold read chapters", foldTopmostReadChapters, "f");
  45.     GM.registerMenuCommand("Fold unread chapters", foldTopmostUnreadChapters, "u");
  46.  
  47.     await loadMapData()
  48.  
  49.     if (showFloatingButton) { document.body.appendChild(btnLoad()) }
  50.  
  51.     addStyleHead(".title-wrapper > .children { padding-right: 8px; }")
  52.  
  53.     if (showTogglerButton) { collapsibator() }
  54.   }
  55.  
  56.   // /chapter/* and /story/* pages
  57.   if (pageType == "chapter") {
  58.     await showChapterCountOnChapterChoices()
  59.     showChapterDate()
  60.  
  61.     // previous chapters map tree on clicking the link
  62.     await prevMapInit()
  63.   }
  64.  
  65.   GM.registerMenuCommand("Delete this from cache", deleteThisFromCache, "c");
  66.   GM.registerMenuCommand("Delete entire cache", deleteEntireCache, "a");  
  67. })();
  68. return
  69. /************************/
  70.  
  71. async function loadMapData() {
  72.   if (!await attemptLoadChapterArrayFromCache() && runAutomatically) {
  73.     // no map data on cache
  74.     generateMap()
  75.     drawChildrenCounter()
  76.   } else {
  77.     // got map data from cache
  78.     drawChildrenCounter()
  79.   }
  80. }
  81.  
  82. function generateDraw() {
  83.     generateMap()
  84.     drawChildrenCounter()
  85. }
  86.  
  87. function createChildrenElement(chapterCount) {
  88.     var child = document.createElement('span')
  89.     child.setAttribute('class', 'control-item children')
  90.     let icon = document.createElement('i')
  91.     icon.setAttribute('class', 'btb bt-folder')
  92.     child.appendChild(icon);
  93.     let count = document.createTextNode(" "+chapterCount)
  94.     child.appendChild(count)
  95.     return child
  96. }
  97.  
  98. function addStyleHead(css) {
  99.     var style = document.createElement('style');
  100.     document.head.appendChild(style);
  101.     style.textContent = css;
  102. };
  103.  
  104. async function attemptLoadChapterArrayFromCache() {
  105.   if (!storyStorageId) loadStoryPathName()
  106.   if (!await storyIsInTheCache(storyStorageId)) {
  107.     console.log('story not found in cache', GM.listValues(), storyStorageId);
  108.     return false
  109.   }
  110.     var value = await GM.getValue(storyStorageId);
  111.   chapterDataArray = JSON.parse(value);
  112.   console.log("!! got map from cache");
  113.   unsafeWindow.chapterDataArray = chapterDataArray
  114.   console.log("Map available as a javascript array on `chapterDataArray`")
  115.  
  116.   return true
  117. }
  118.  
  119. function deleteThisFromCache() {
  120.     if (pageType == "map") {
  121.         GM.deleteValue(getStoryStorageId())
  122.     }
  123.     let storyStorageId = getStoryStorageId()
  124.     GM.deleteValue(storyStorageId)
  125. }
  126.  
  127. function deleteEntireCache() {
  128.     (GM.listValues()).forEach((value) => {
  129.         GM.deleteValue(value)
  130.     })
  131. }
  132.  
  133. async function readOrGenerateMap() {
  134.     if (! await attemptLoadChapterArrayFromCache()) generateMap()
  135. }
  136.  
  137. function generateMap() {
  138.     console.log("!! generating map");
  139.     chapterDataArray = createStoryMap()
  140.     console.log("Chapter Count = ", chapterDataArray.length);
  141.  
  142.     unsafeWindow.chapterDataArray = chapterDataArray
  143.     console.log("Map available as a javascript array on `chapterDataArray`");
  144.  
  145.     console.log("!! assembling hierarchy tree");
  146.     countMapChapters();
  147.     console.log("!! done counting the children");
  148.  
  149.     // cache it
  150.     GM.setValue(getStoryStorageId(), JSON.stringify(chapterDataArray))
  151. }
  152.  
  153. /**
  154. * counts and assigns the children count of all elements
  155. */
  156. function countMapChapters() {
  157.     for (const chapter of chapterDataArray) {
  158.         // find the leaves of the tree
  159.         if (chapter.children.length !== 0) continue;
  160.         chapter.chapterCount = 0;
  161.  
  162.         let next = chapter.parent != null ? chapter.parent : -1;
  163.  
  164.         // rise through the branch until it can't
  165.         while (next != -1) {
  166.             //console.log("from", chapter.id, "to", next);
  167.             next = countChildChaptersOf(next);
  168.         }
  169.         // then continue to the next chapter childless chapter
  170.     }
  171.     // done
  172. }
  173.  
  174. /**
  175. * Counts and assigns the chapterCount of the node at that index
  176. * Aborts returning -1 if it already has been counted
  177. * or can't because one of its children hasn't been counted yet
  178. *
  179. * @param {number} currIndex - The index of the node
  180. * @returns {number} The index of the next node. -1 to abort
  181. */
  182. function countChildChaptersOf(currIndex) {
  183.     let nextIndex = -1;
  184.     // if (currIndex > chapterDataArray.length) console.log('currIndex > chapterDataArray.length', currIndex, chapterDataArray.length)
  185.     const currentNode = chapterDataArray[currIndex];
  186.     if (currentNode.chapterCount != undefined) {
  187.         // this node was already been processed
  188.         // abort
  189.         return nextIndex;
  190.     }
  191.     // sum the counts of its children
  192.     const chapterCountSum = sumChildrenChaptersCount(currentNode);
  193.     if (chapterCountSum === -1) {
  194.         // one or more children haven't been processed yet
  195.         // abort
  196.         return nextIndex;
  197.     }
  198.  
  199.     // on successful sum
  200.     currentNode.chapterCount = chapterCountSum + currentNode.children.length;
  201.     nextIndex = currentNode.parent !== null ? currentNode.parent : -1;
  202.  
  203.     return nextIndex;
  204. }
  205.  
  206. /**
  207. * Sums the chapterCount of all children of the attribute node
  208. */
  209. function sumChildrenChaptersCount(chapterObj) {
  210.     return chapterObj.children.reduce((acc, curr) => {
  211.         const currentNode = chapterDataArray[curr];
  212.         if (currentNode.chapterCount == undefined) {
  213.             return -1;
  214.         }
  215.         return acc !== -1 ? acc + currentNode.chapterCount : acc;
  216.     }, 0);
  217. }
  218.  
  219. function drawChildrenCounter() {
  220.     let list = document.querySelectorAll(".title-wrapper");
  221.     if (list.length !== chapterDataArray.length) {
  222.       console.log('Outdated data. Please regenerate the map');
  223.       if (list.length >= chapterDataArray.length) { return }
  224.     }
  225.  
  226.     list.forEach((elem, i) => {
  227.         let existingChildren = elem.querySelector('.children')
  228.         if (existingChildren) {
  229.             // redraw
  230.             existingChildren.remove()
  231.         }
  232.  
  233.         let child = createChildrenElement(chapterDataArray[i].chapterCount)
  234.         let page = elem.querySelector('.page')
  235.  
  236.         elem.insertBefore(child, page)
  237.     })
  238. }
  239.  
  240. // receives html dom element
  241. // returns parsed element as an object
  242. function createChapterObj(el) {
  243.     const pel = {};
  244.  
  245.     let tempEl = el.querySelector(".title");
  246.  
  247.     pel.title = tempEl.textContent;
  248.     pel.url = tempEl.href;
  249.  
  250.     // sometimes the author is empty and there's no <a> inside it
  251.     tempEl = el.querySelector(".username > a");
  252.     pel.author = tempEl ? tempEl.textContent : "";
  253.  
  254.     // page is completely unreliable for chapters loaded afterwards. It resets on loading
  255.     // pel.page = el.querySelector(".page").textContent;
  256.  
  257.     // Sometimes the date is empty, but there's no issue here
  258.     // console.log(el)
  259.     pel.date = el.querySelector(".date").textContent;
  260.  
  261.     pel.parent = null;
  262.     pel.children = [];
  263.     // pel.linksTo = null;
  264.  
  265.     pel.margin = parseInt(el.style["margin-left"]);
  266.  
  267.     // link chapters don't have views, likes, or comments
  268.     // so find out if the chapter is a link chapter or not
  269.  
  270.     const viewsEl = el.querySelector(".views");
  271.     if (viewsEl == null) {
  272.         pel.views = null;
  273.         pel.likes = null;
  274.         pel.comments = null;
  275.         pel.isLinkChapter = 1;
  276.  
  277.         return pel;
  278.     }
  279.  
  280.     pel.views = parseInt(viewsEl.textContent.split(",").join("")) || 0;
  281.     pel.likes = parseInt(el.querySelector(".likes").textContent) || 0;
  282.     pel.comments = parseInt(el.querySelector(".comments").textContent) || 0;
  283.     pel.isLinkChapter = 0;
  284.  
  285.     return pel;
  286. }
  287.  
  288. // final list like [ chapterObj, (...) ]
  289. // where every element has its parent and children noted
  290. function createStoryMap() {
  291.     // temporary list, to get the DOM element from the page
  292.     // let list = document.getElementsByClassName("story-map-content");
  293.     // if (list == null || !list) return;
  294.     // list = Array.from(list[0].children);
  295.     let chapterElementArray = Array.from(document.querySelectorAll(".story-map-chapter"))
  296.  
  297.     let prevParentI = -1;
  298.     const finalList = [];
  299.  
  300.     chapterElementArray.forEach((el, i) => {
  301.     // for (const i in chapterElementArray) {
  302.         // console.log("- Processing Chapter", i);
  303.         // const el = chapterElementArray[i];
  304.  
  305.         // parse el and add it to the final list
  306.         const chapterObj = createChapterObj(el);
  307.         finalList[i] = chapterObj;
  308.         // console.log(chapterObj)
  309.  
  310.         // now we find the parent of el
  311.  
  312.         // before checking margin
  313.         // check if it's the first element of the list
  314.         if (i == 0) {
  315.             prevParentI = 0;
  316.             // continue; // when using a for loop
  317.             return;
  318.         }
  319.  
  320.         // check margins
  321.  
  322.         const currElMargin = chapterObj.margin
  323.         const prevElMargin = finalList[i-1].margin
  324.  
  325.         // check if el is child of prev el
  326.         if (prevElMargin < currElMargin) {
  327.             // prev el is parent
  328.             chapterObj.parent = parseInt(i - 1);
  329.             // add this el as child of prev element
  330.             finalList[i - 1].children.push(parseInt(i));
  331.             // set prev parent to prev element
  332.             prevParentI = i - 1;
  333.             // continue; // when using a for loop
  334.             return;
  335.         }
  336.  
  337.         // check if el is sibling of prev el
  338.         if (prevElMargin == currElMargin) {
  339.             // they share the same parent
  340.  
  341.             // prevParent is parent
  342.             chapterObj.parent = prevParentI;
  343.             // add this el as child of prevParent
  344.             finalList[prevParentI].children.push(i);
  345.             // continue; // when using a for loop
  346.             return;
  347.         }
  348.  
  349.         // then el must be the "uncle" of prev el
  350.         // prevElMargin > currElMargin
  351.  
  352.         // use a loop go back through the parents from the previous node
  353.         // to find the first element with margin smaller than self
  354.         const selfMargin = chapterObj.margin;
  355.         for (let j = i - 1; j >= 0; j = finalList[j].parent) {
  356.             if (finalList[j].margin < selfMargin) {
  357.                 // found the parent: j
  358.                 const actualParentI = j;
  359.                 chapterObj.parent = actualParentI;
  360.                 // add this el as child of actual parent
  361.                 // finalList[actualParentI].children.push(chapterObj.id);
  362.                 finalList[actualParentI].children.push(i);
  363.                 // set prev parent to actual parent
  364.                 prevParentI = actualParentI;
  365.                 break;
  366.             }
  367.         }
  368.     // } // when using a for loop
  369.     })
  370.  
  371.     return finalList;
  372. }
  373.  
  374. // button stuff
  375.  
  376. function createButton(text, action, styleStr) {
  377.     let button = document.createElement('button');
  378.     button.textContent = text;
  379.     button.onclick = action;
  380.     button.setAttribute('style', styleStr || '');
  381.     return button;
  382. };
  383. function toStyleStr(obj, selector) {
  384.     let stack = [],
  385.         key;
  386.     for (key in obj) {
  387.         if (obj.hasOwnProperty(key)) {
  388.             stack.push(key + ':' + obj[key]);
  389.         }
  390.     }
  391.     if (selector) {
  392.         return selector + '{' + stack.join(';') + '}';
  393.     }
  394.     return stack.join(';');
  395. };
  396. function btnLoadCss() {
  397.     return toStyleStr({
  398.         'position': 'fixed',
  399.         'bottom': 0,
  400.         'right': 0,
  401.         'padding': '2px',
  402.         'margin': '0 10px 10px 0',
  403.         'color': '#333',
  404.         'background-color': 'rgb(246, 245, 244)',
  405.         'z-index': '9999999999'
  406.     })
  407. }
  408. function btnLoad() {
  409.     return createButton('Regenerate', function() {
  410.         generateDraw()
  411.         // this.remove();
  412.     }, btnLoadCss());
  413. }
  414.  
  415. /* Depth Toggler */
  416.  
  417. function collapsibator() {
  418.     const input = document.createElement('input')
  419.     input.defaultValue = 1
  420.     input.setAttribute('id', 'toggler-input')
  421.     input.setAttribute('style', toStyleStr({
  422.         // 'padding': '2px',
  423.         'color': '#333',
  424.         'width': '30px'
  425.         // 'display': 'inline-block'
  426.     }))
  427.  
  428.     const button = document.createElement('button')
  429.     button.setAttribute('id', 'toggler-btn')
  430.     button.textContent = 'Toggle'
  431.     button.setAttribute('style', toStyleStr({
  432.         'padding': '2px',
  433.         'color': '#333',
  434.         'background-color': 'rgb(246, 245, 244)',
  435.         'display': 'inline-block',
  436.         'font-size': '12px',
  437.         'margin-left': '4px',
  438.     }))
  439.     button.onclick = () => {
  440.         const level = document.getElementById('toggler-input').value
  441.         toggleCollapsibleLevel(level)
  442.     }
  443.  
  444.     const cont = document.createElement('div')
  445.     cont.setAttribute('id', 'toggler-container')
  446.     cont.setAttribute('style', toStyleStr({
  447.         'position': 'fixed',
  448.         'bottom': '50px',
  449.         'right': '10px',
  450.         'padding': '2px',
  451.         'z-index': '9999999999'
  452.     }))
  453.     cont.appendChild(input)
  454.     cont.appendChild(button)
  455.     document.body.appendChild(cont)
  456. }
  457.  
  458. // toggle all collapsibles from depht level "level"
  459. function toggleCollapsibleLevel(level) {
  460.     const chapters = Array.from(document.getElementsByClassName("story-map-chapter"));
  461.     if (chapters == null) return;
  462.     // if (!chapterDataArray.length) { console.error('chapterDataArray is undefined'); return; }
  463.     if (!chapterDataArray) { console.error('chapterDataArray is undefined'); return; }
  464.  
  465.     const firstMargin = parseInt(chapters[0].style['margin-left'])
  466.     const marginGap = parseInt(chapters[1].style['margin-left']) - firstMargin;
  467.  
  468.     for (let i = 0; i < chapterDataArray.length; i++) {
  469.         // if (st.margin == (level*marginGap + firstMargin)) {
  470.         if (chapterDataArray[i].margin == (level*marginGap + firstMargin)) {
  471.             // toggle it
  472.             const btn = chapters[i].querySelector('.btn.btn-link.collapsable.js-collapsable')
  473.             if (btn) btn.click()
  474.  
  475.             // maybe will use this in the future for better performance???
  476.             // wasn't able to figure it out
  477.  
  478.             // expand
  479.             // let clazz = Array.from(chapters[i].getAttribute('class')).split(' ').filter(c=>c!="hidden")
  480.             // chapters[i].setAttribute('class', clazz)
  481.             // clazz = Array.from(chapters[i].querySelector(".js-collapsable > i").getAttribute('class')).split(' ').filter(c=>c!="bt-minus"&&c!="bt-plus")
  482.             // clazz.push('bt-minus')
  483.             // chapters[i].querySelector(".js-collapsable > i").setAttribute('class', clazz)
  484.  
  485.             // collapse
  486.  
  487.             // if (chapterDataArray[i].margin == (level*marginGap + firstMargin)) {
  488.             //     let el = chapters[i].querySelector(".js-collapsable > i")
  489.             //     let clazz = el.getAttribute('class').split(' ').filter(c=>c!="bt-minus").join(' ') + " bt-plus"
  490.             //     el.setAttribute('class', clazz)
  491.             // if (chapterDataArray[i].margin > (level*marginGap + firstMargin)) {
  492.             //     let el = chapters[i]
  493.             //     let clazz = el.getAttribute('class') + " hidden"
  494.             //     el.setAttribute('class', clazz)
  495.         }
  496.     }
  497.     // })
  498. }
  499.  
  500.  
  501. function getPageType() {
  502.     let url = window.location.pathname
  503.     // // console.log(url)
  504.     if (url.search(/\/story\/.*\/map/) >= 0) {
  505.         return "map"
  506.     }
  507.     if (url.search(/\/chapter\//) >= 0) {
  508.         return "chapter"
  509.     }
  510.     if (url.search(/\/story\/.*\.[0-9]+$\/?/) >= 0) {
  511.         // first chapter of story
  512.         return "chapter"
  513.     }
  514. }
  515.  
  516. async function storyIsInTheCache(storyStorageId) {
  517.   var values = await GM.listValues();
  518.   return (values.indexOf(storyStorageId) >= 0)
  519. }
  520.  
  521. // get story url
  522. // find it on cache
  523. // get chapter url
  524. // compare with chapters listed
  525.  
  526. async function showChapterCountOnChapterChoices() {
  527.   let storyStorageId = getStoryStorageId()
  528.   // check if chapterData is undefined
  529.   // if it is, try fetching it
  530.   if (!chapterData) {
  531.     //fetch story data array first
  532.     if (!chapterDataArray) if (!await attemptLoadChapterArrayFromCache()) return false;
  533.  
  534.     // fetch story data
  535.     chapterData = await getChapterData(storyStorageId)
  536.     if (!chapterData) return console.error('Story found but Chapter not found. Please regenerate the map.');
  537.   }
  538.  
  539.   let nextChapterIndexList = chapterData.children
  540.  
  541.   // get chapter choices
  542.   let chapterChoices = Array.from(document.querySelectorAll(".question-content a"))
  543.   // Remove "Add a new chapter" link from the list (if the story is not private)
  544.   // its link ends with "/new"
  545.   if (chapterChoices[chapterChoices.length-1].href.search(/\/new$/) > -1) {
  546.     chapterChoices.pop()
  547.   }
  548.  
  549.   // prepare Count Number css style
  550.   if (cssStyle == "crazy") { applyCrazyCSS() } else
  551.     if (cssStyle == "alternative") { applyAlternativeCSS() }
  552.  
  553.   chapterChoices.forEach((el, i) => {
  554.     let mapIndex = nextChapterIndexList[i]
  555.     if (mapIndex == undefined) { return console.log('Chapter "'+el.textContent+'" not found. Please regenerate the story map.'); }
  556.     drawLinkChapterCount(el, chapterDataArray[mapIndex], cssStyle)
  557.   })
  558. }
  559.  
  560. async function getChapterData() {
  561.   if (chapterData) return chapterData;
  562.   if (!chapterDataArray) chapterDataArray = await attemptLoadChapterArrayFromCache()
  563.   if (!chapterDataArray) return false;
  564.  
  565.   // story is on cache as chapterDataArray
  566.   // let chapterDataArray = JSON.parse(await GM.getValue(storyStorageId))
  567.  
  568.   // getting the chapter url if we're in /story/
  569.   let chapterUrl = window.location.href
  570.   if (chapterUrl.search(/\/story\//) > -1) {
  571.     let els = document.querySelectorAll('.controls-left a')
  572.     let chapterNum = els[els.length-1].href.match(/\d+\/?$/)[0]
  573.     chapterUrl = "https://chyoa.com/chapter/Introduction."+chapterNum;
  574.   }
  575.  
  576.   // console.log(chapterDataArray, chapterDataArray[1].url, chapterUrl)
  577.   return chapterDataArray.find(c=>c.url==chapterUrl)
  578. }
  579.  
  580. // Show chapter date under its Title
  581. // assuming pageType == "chapter"
  582. function showChapterDate() {
  583.   if (!chapterData) return false;
  584.  
  585.   let date = document.createElement('div')
  586.   date.textContent = chapterData.date
  587.   document.querySelector('.meta').append(date)
  588. }
  589.  
  590. // crazy css
  591. function applyCrazyCSS() {
  592.     let style = toStyleStr({
  593.         'display': 'flex',
  594.         'width': '55px',
  595.         'justify-content': 'space-between',
  596.         'align-items': 'center',
  597.         'margin': '0 8px 0 -55px !important',
  598.         'position': 'inherit !important',
  599.         'float': 'left',
  600.         'border-right': '1px solid',
  601.         'padding': '0 8px 0 0',
  602.         'font-family': 'monospace',
  603.         'font-size': '14px',
  604.         'line-height': '27px',
  605.     }, '.question-content .chapterCount')
  606.     addStyleHead(style)
  607. }
  608.  
  609. function applyAlternativeCSS() {
  610.     let style = toStyleStr({
  611.         'position': 'absolute',
  612.         'left': '-45px',
  613.         'text-align': 'right',
  614.         'width': '40px',
  615.         'padding': '11px 0',
  616.         'top': '0',
  617.     }, '.question-content .chapterCount')
  618.         + toStyleStr({
  619.         'position': 'relative',
  620.     }, '.question-content a')
  621.     addStyleHead(style)
  622. }
  623.  
  624. function drawLinkChapterCount(el, chapterObj, cssStyle = 'default') {
  625.  
  626.     const chapterCount = chapterObj.chapterCount
  627.     const isLinkChapter = chapterObj.isLinkChapter
  628.  
  629.     let span = document.createElement('SPAN')
  630.  
  631.     if (isLinkChapter) {
  632.         let icon = document.createElement('i')
  633.  
  634.         icon.setAttribute('class', 'btb bt-external-link')
  635.         el.insertAdjacentElement('afterbegin',icon)
  636.  
  637.         return
  638.     }
  639.  
  640.     span.setAttribute('class', 'chapterCount')
  641.  
  642.     if (cssStyle == 'simple') {
  643.         span.textContent = " ("+chapterCount+")"
  644.         el.append(span)
  645.         return
  646.     }
  647.  
  648.     if (cssStyle == 'alternative') {
  649.         let icon = document.createElement('i')
  650.  
  651.         icon.setAttribute('class', 'btb bt-folder')
  652.         span.textContent = Number(chapterCount).toString()
  653.         el.append(span)
  654.         el.insertAdjacentElement('afterbegin', icon)
  655.  
  656.         return
  657.     }
  658.  
  659.     // default & carzy markup
  660.  
  661.     let icon = document.createElement('i')
  662.     icon.setAttribute('class', 'btb bt-folder')
  663.     span.appendChild(icon);
  664.     let count = document.createTextNode(" "+chapterCount)
  665.     span.append(count)
  666.  
  667.     el.insertAdjacentElement('afterbegin',span)
  668.  
  669.     // skip styling if cssStyle is set to 'crazy'
  670.     if (cssStyle != 'crazy') {
  671.         let color = window.getComputedStyle(el).color
  672.         span.setAttribute('style', 'position: absolute; margin: 0 0 0 -60px; color: '+color+' !important;')
  673.         icon.setAttribute('style', 'margin: 0 2px 0 0;')
  674.     }
  675. }
  676.  
  677. function loadStoryPathName() {
  678.   storyStorageId = getStoryStorageId()
  679. }
  680.  
  681. function getStoryStorageId() {
  682.   if (storyStorageId) return storyStorageId;
  683.   if (pageType == "map") return window.location.pathname;
  684.   if (pageType != "chapter") return console.error("Unrecognizable page type");
  685.  
  686.   // pageType is chapter
  687.   let href = document.querySelectorAll('.controls-left a')
  688.   href = href[href.length-2].href
  689.   return href.slice(href.search(/\/story/)) + '/map';
  690. }
  691.  
  692. function foldTopmostReadChapters() {
  693.   let ignoreList = []
  694.   const els = Array.from(document.querySelectorAll(".story-map-chapter"))
  695.  
  696.   if (chapterDataArray.length != els.length) return false;
  697.  
  698.   // iterate all chapters
  699.   // fold all read chapters not in the ignore list
  700.   // put its children in the ignore list
  701.   // put every ignored chapter children in the ignored list
  702.   // console.log(chapterDataArray)
  703.  
  704.   for (const i in chapterDataArray) {
  705.     if (i == 0) continue
  706.     // console.log('in for')
  707.     // check if chapter read && i different from 0
  708.     if (els[i].classList.length == 1) {
  709.       // console.log('unread chapter found')
  710.       // if (binarySearch(i, ignoreList) == -1 && i != 0) {
  711.       if (binarySearch(i, ignoreList) == -1) {
  712.         console.log('success at i = ', i);
  713.         // console.log('first unread not in ignore list, fold')
  714.         els[i].querySelector('.btn').click()
  715.       }
  716.       // console.log('add children to ignored list')
  717.       ignoreList = ignoreList.concat(chapterDataArray[i].children)
  718.     }
  719.   }
  720. }
  721.  
  722. function foldTopmostUnreadChapters() {
  723.   let ignoreList = []
  724.   const els = Array.from(document.querySelectorAll(".story-map-chapter"))
  725.  
  726.   if (chapterDataArray.length != els.length) return false;
  727.  
  728.   for (const i in chapterDataArray) {
  729.     if (i == 0) continue
  730.     if (els[i].classList.length != 1) {
  731.       if (binarySearch(i, ignoreList) == -1) {
  732.         console.log('success at i = ', i);
  733.         els[i].querySelector('.btn').click()
  734.       }
  735.       ignoreList = ignoreList.concat(chapterDataArray[i].children)
  736.     }
  737.   }
  738.   console.log('ignoreList leng',ignoreList.length);
  739. }
  740.  
  741. function binarySearch(value, list) {
  742.   return list.find(a=>a==value) ? 1 : -1;
  743.   let first = 0; //left endpoint
  744.   let last = list.length - 1; //right endpoint
  745.   let position = -1;
  746.   let found = false;
  747.   let middle;
  748.  
  749.   if (value < 100) console.log('search', value, list)
  750.  
  751.   while (found === false && first <= last) {
  752.     middle = Math.floor((first + last)/2);
  753.     if (list[middle] == value) {
  754.       found = true;
  755.       position = middle;
  756.     } else if (list[middle] > value) { //if in lower half
  757.       last = middle - 1;
  758.     } else { //in in upper half
  759.       first = middle + 1;
  760.     }
  761.   }
  762.   return position;
  763. }
  764.  
  765. // TODO
  766. // index to remain in cache forever containing a story index with the number of chapters and other story stats
  767. //   update index if the generated data has more chapters than what's on the index
  768. // detect branches (chapters where child chapters have many chapters) and note them down
  769. //   on chapter pages, check if it belongs to any recognizable branch and print its name on the page
  770. // fold all topmost read chapters
  771.  
  772. // prev chapters map
  773. // let prevMapContainer;
  774. function prevMapShow() {
  775.   let container = document.querySelector('.prevMapContainer')
  776.   // if (container === null) {
  777.   //   container = prevMapRender()
  778.     // container = document.querySelector('.prevMapContainer')
  779.   // }
  780.   container.classList.toggle('show');
  781.   // prevMapContainer.classList.toggle('show');
  782. }
  783.  
  784.  
  785.  
  786. async function prevMapRender() {
  787.   let storyStorageId = getStoryStorageId()
  788.   // check if chapterData is undefined
  789.   // if it is, try fetching it
  790.   if (!chapterData) {
  791.     //fetch story data array first
  792.     // if (!chapterDataArray) if (!attemptLoadChapterArrayFromCache()) {
  793.     if (!chapterDataArray) {
  794.       if (!await attemptLoadChapterArrayFromCache()) {
  795.         document.querySelector('.question').insertAdjacentText('Map data not found. Please regenerate the map.')
  796.         return false;
  797.       }
  798.     }
  799.  
  800.     // fetch story data
  801.     chapterData = getChapterData(storyStorageId)
  802.     if (!chapterData) {
  803.       document.querySelector('.question').insertAdjacentText('Unable to find Chapter in the story data. Please regenerate the mapp.')
  804.       // return console.error('Unable to find Chapter in the story data. Please regenerate the map.');
  805.       return false;
  806.     }
  807.   }
  808.  
  809.   if (document.querySelector('.prevMapContainer') === null) {
  810.   // if (prevMapContainer === null) {
  811.     document.querySelector('.question').insertAdjacentHTML('beforeend', '<div class="prevMapContainer"></div>')
  812.     // prevMapContainer = document.querySelector('.prevMapContainer')
  813.   }
  814.  
  815.   // drawThisChapter()
  816.   drawParentTree()
  817. }
  818.  
  819. function drawThisChapter() {
  820.   let container = document.querySelector('.prevMapContainer')
  821.   container.insertAdjacentHTML('beforend', '<div class="prevMap_chapter prevMap_currentChapter"><a href="'+chapterData.url+'">'+chapterData.title+'</a></div>')
  822. }
  823.  
  824. function drawParentTree() {
  825.   let container = document.querySelector('.prevMapContainer')
  826.  
  827.   let chapter = chapterData
  828.   // let chapter = chapterDataArray[chapterData.parent]
  829.   while (chapter.parent != null) {
  830.     container.insertAdjacentHTML('afterbegin', '<div class="prevMap_chapter"><a href="'+chapter.url+'">'+chapter.title+'</a></div>')
  831.     chapter = chapterDataArray[chapter.parent]
  832.   }
  833. }
  834.  
  835. async function prevMapInit() {
  836.   document.querySelector('.controls-left').insertAdjacentHTML('beforeend','<br class="visible-xs-inline"> <a href="#" class="prevMap_button"><i class="btb bt-sitemap"></i>Prev Chapters</a>')
  837.   let prevMapButton = document.querySelector('.prevMap_button')
  838.   prevMapButton.addEventListener('click', function(e) {
  839.     e.preventDefault();
  840.     prevMapShow()
  841.   });
  842.  
  843.   await prevMapRender()
  844.  
  845.   let style = toStyleStr({
  846.     'display': 'none',
  847.   }, '.prevMapContainer')
  848.   + toStyleStr({
  849.     'display': 'block',
  850.   }, '.prevMapContainer.show')
  851.   addStyleHead(style)
  852. }
  853.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement