Guest User

JS to make AO3 downloads nicer to read

a guest
Feb 26th, 2026
116
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. let save_scroll = 0;
  2. let timer_id = null
  3. let last_scroll_time = 0
  4. let num_chapters = 0
  5. const scale = 1000000000
  6.  
  7. function init_book()
  8. {
  9.     let body = document.querySelector('body')
  10.     let mw = `${Math.round(3*body.clientWidth/4)}px`
  11.     let img = document.querySelectorAll('img')
  12.     img.forEach(e => {e.style.maxWidth = mw})
  13.     make_chapters()
  14.     set_chapter()
  15.     window.addEventListener('scroll', scroll_updater)
  16.     window.addEventListener('resize', scroll_updater)
  17.     window.addEventListener('visibilitychange', visibility_changed)
  18. }
  19.  
  20. //{ level 1
  21.  
  22. function make_chapters()
  23. {
  24.     let chnum = 0
  25.  
  26.     document.querySelectorAll('#chapters > div').forEach(e => {
  27.         e.classList.add('chapter')
  28.         if (e.classList.contains('group')) {
  29.             ++chnum
  30.             e.classList.add(`ch_${chnum}_top`)
  31.             e.id = `ch_${chnum}`
  32.         }
  33.         e.classList.add(`ch_${chnum}`)
  34.     })
  35.     num_chapters = chnum
  36.     if (num_chapters > 0) {
  37.         document.querySelector('#preface').classList.add('chapter')
  38.         document.querySelector('#preface').classList.add('ch_1')
  39.         document.querySelector('#afterword').classList.add('chapter')
  40.         document.querySelector('#afterword').classList.add(`ch_${chnum}`)
  41.  
  42.  
  43.     }
  44.  
  45.     add_chapter_nav(chnum)
  46. }
  47.  
  48. function set_chapter()
  49. {
  50.     var url = new URL(window.location.href);
  51.     var c = url.searchParams.get("c");
  52.     c = (c !== null) ? parseInt(c) : 1
  53.     show_chapter(c)
  54. }
  55.  
  56. function visibility_changed(e)
  57. {
  58.     if (document.visibilityState === 'visible') {
  59.         restore_position();
  60.     }
  61. }
  62.  
  63. function scroll_updater(e)
  64. {
  65.     if (timer_id === null) {
  66.         timer_id = setInterval(check_scroll, 1000)
  67.         timer_running = true
  68.     }
  69.     last_scroll_time = Date.now()
  70. }
  71.  
  72. //}{ level 2
  73.  
  74. function add_chapter_nav(n)
  75. {
  76.     let b = document.querySelector('body')
  77.  
  78.     let nd = make_nav_div(n, 'top', 0)
  79.     b.prepend(nd)
  80.  
  81.     nd = make_nav_div(n, 'bottom', 0)
  82.     b.append(nd)
  83.  
  84. }
  85.  
  86. function show_chapter(n)
  87. {
  88.     if (num_chapters > 1) {
  89.         let chclass = `ch_${n}`
  90.         document.querySelectorAll('.chapter').forEach(e => {
  91.             e.classList.add('ch_off')
  92.             e.classList.remove('ch_on')
  93.         })
  94.         document.querySelectorAll(`.ch_${n}`).forEach(e => {
  95.             e.classList.add('ch_on')
  96.             e.classList.remove('ch_off')
  97.         })
  98.  
  99.         document.querySelectorAll('.setch').forEach(e => {
  100.             e.value = n
  101.         })
  102.  
  103.         document.querySelectorAll('.prev_button').forEach(e => {
  104.             e.classList.remove('bt_off')
  105.         })
  106.         document.querySelectorAll('.next_button').forEach(e => {
  107.             e.classList.remove('bt_off')
  108.         })
  109.         if (n === 1) {
  110.             document.querySelectorAll('.prev_button').forEach(e => {
  111.                 e.classList.add('bt_off')
  112.             })
  113.         } else if (n === num_chapters) {
  114.             document.querySelectorAll('.next_button').forEach(e => {
  115.                 e.classList.add('bt_off')
  116.             })
  117.         }
  118.         add_param('c', n)
  119.     }
  120.     restore_position()
  121. }
  122.  
  123. function restore_position()
  124. {
  125.     var url = new URL(window.location.href);
  126.     var m = url.searchParams.get("m")
  127.     if (m === null) {
  128.         m = localStorage.getItem("mode")
  129.     }
  130.     set_night_mode(m === null ? 0 : parseInt(m))
  131.  
  132.     var s = url.searchParams.get("s");
  133.     if (s !== null) {
  134.         window.requestAnimationFrame(() => {
  135.             window.requestAnimationFrame(() => {
  136.                 scroll_to_pct(parseInt(s));
  137.             });
  138.         });
  139.         save_scroll = s
  140.         last_scroll_time = Date.now()
  141.     }
  142. }
  143.  
  144. function check_scroll()
  145. {
  146.     let now = Date.now()
  147.     if (now - last_scroll_time < 1000)
  148.         return
  149.  
  150.     let pct = get_scroll_pos()
  151.     if (pct != save_scroll) {
  152.         save_scroll = pct;
  153.         add_param("s", pct);
  154.     }
  155.     clearInterval(timer_id)
  156.     timer_id = null
  157. }
  158.  
  159. //}{ level 3
  160.  
  161. function set_night_mode(m)
  162. {
  163.     if (m === 0) {
  164.         document.querySelector('body').classList.remove('night')
  165.         add_param('m', 0)
  166.         localStorage.setItem('mode', 0)
  167.     } else {
  168.         document.querySelector('body').classList.add('night')
  169.         add_param('m', 1)
  170.         localStorage.setItem('mode', 1)
  171.     }
  172.  
  173.     document.querySelectorAll('.mode_button').forEach(e => {
  174.         e.value = m === 0 ? 'Night Mode' : 'Day Mode'
  175.     })
  176. }
  177.  
  178. function make_nav_div(n, sid, verbose)
  179. {
  180.     let navdiv = make_element('div', {'class':'nav_div'})
  181.     if (n > 0) {
  182.         let s = make_element('span', {'class':'nav_group'})
  183.  
  184.         let e = make_element('input', {'type':'button',
  185.             'name':'prev', 'value':'previous', 'onclick':'move_ch(-1)',
  186.             'class':'prev_button'})
  187.         s.append(e)
  188.  
  189.         let sel = make_element('select', {'onchange':`jump_ch('${sid}')`,
  190.             'id':sid, 'class':'setch'})
  191.         s.append(sel)
  192.  
  193.         for (let i = 1; i <= n; ++i) {
  194.             e = make_element('option',{'value':i})
  195.             let title = get_chapter_title(i)
  196.             title = `${i}: ${title}`
  197.             e.innerText = title
  198.             if (verbose) {
  199.                 console.log(`Found chapter ${title}`)
  200.             }
  201.             sel.append(e)
  202.         }
  203.  
  204.         e = make_element('input', {'type':'button',
  205.             'name':'next', 'value':'next', 'onclick':'move_ch(1)',
  206.             'class':'next_button'})
  207.         s.append(e)
  208.  
  209.         navdiv.append(s)
  210.     }
  211.  
  212.     let nm = make_element('input', {'type':'button', 'class':'mode_button',
  213.         'name':'mode', 'value':'mode', 'onclick':'change_mode()'})
  214.     navdiv.append(nm)
  215.  
  216.     return navdiv
  217. }
  218.  
  219. function add_param(name, value)
  220. {
  221.     let url = new URL(window.location.href);
  222.     url.searchParams.set(name, value);
  223.     window.history.replaceState(null, "", url.pathname + url.search);
  224. }
  225.  
  226. function scroll_to_pct(s)
  227. {
  228.     let h_vis = document.documentElement.clientHeight;
  229.     let h_all = document.documentElement.scrollHeight;
  230.     let span = h_all - h_vis;
  231.     let y = Math.round(span * s / scale);
  232.     //console.log(`s=${s}, all=${h_all}, span=${span}, vis=${h_vis}, new_pos=${y}`)
  233.     document.documentElement.scrollTo({top: y});
  234. }
  235.  
  236. //}{ level 4
  237.  
  238. function make_element(tag, attrs)
  239. {
  240.     let e = document.createElement(tag)
  241.     for (let key in attrs) {
  242.         e.setAttribute(key, attrs[key])
  243.     }
  244.     return e
  245. }
  246.  
  247. function get_scroll_pos()
  248. {
  249.     let h_vis = document.documentElement.clientHeight;
  250.     let h_scroll = document.documentElement.scrollTop;
  251.     let h_all = document.documentElement.scrollHeight;
  252.     let span = h_all - h_vis;
  253.     let s = span > 0 ? Math.round(scale * h_scroll / span) : 0;
  254.     //console.log(`s=${s}, all=${h_all}, span=${span}, vis=${h_vis}, pos=${h_scroll}`);
  255.     return s
  256. }
  257.  
  258. function get_chapter_title(n)
  259. {
  260.     return document.querySelector(`div.ch_${n}_top h2`).innerText
  261. }
  262.  
  263. function jump_ch(sid)
  264. {
  265.     let v = document.querySelector(`#${sid}`).value
  266.     add_param('s', 0)
  267.     show_chapter(parseInt(v))
  268.     document.activeElement.blur()
  269. }
  270.  
  271. function move_ch(dir)
  272. {
  273.     let e = document.querySelector('.ch_on')
  274.     let n = 1
  275.     while (n < 1000) {
  276.         if (e.classList.contains(`ch_${n}`)) { break }
  277.         ++n
  278.     }
  279.     let new_ch = n + dir
  280.     if (new_ch < 1) { new_ch = 1 }
  281.     if (new_ch > num_chapters) { new_ch = num_chapters }
  282.     if (new_ch !== n) {
  283.         add_param('s', 0)
  284.         show_chapter(new_ch)
  285.     }
  286. }
  287.  
  288. function change_mode()
  289. {
  290.     let onq = document.querySelector('body.night')
  291.     set_night_mode(onq ? 0 : 1)
  292. }
  293.  
  294. //}
  295.  
  296. window.addEventListener('load', init_book)
  297.  
Add Comment
Please, Sign In to add comment