Unlock Hath Perks

Unlock Hath Perks and add other helpers

As of 21/10/2017. See the latest version.

  1. // ==UserScript==
  2. // @name Unlock Hath Perks
  3. // @name:zh 解鎖 Hath Perks
  4. // @description Unlock Hath Perks and add other helpers
  5. // @description:zh 解鎖 Hath Perks 及增加一些小工具
  6. // @namespace https://github.com/FlandreDaisuki
  7. // @version 1.0.1
  8. // @match *://e-hentai.org/*
  9. // @match *://exhentai.org/*
  10. // @icon https://i.imgur.com/JsU0vTd.png
  11. // @run-at document-start
  12. // @grant GM_setValue
  13. // @grant GM_getValue
  14. // @noframes
  15. //
  16. // Addition metas
  17. //
  18. // @supportURL https://github.com/FlandreDaisuki/My-Browser-Extensions/issues
  19. // @homepageURL https://github.com/FlandreDaisuki/My-Browser-Extensions/blob/master/userscripts/UnlockHathPerks.md
  20. // @author FlandreDaisuki
  21. // @license MPLv2
  22. // @compatible firefox 52+
  23. // @compatible chrome 55+
  24. // @incompatible any not support async/await, CSS-grid browsers
  25. // ==/UserScript==
  26.  
  27. 'use strict';
  28.  
  29. /************************************/
  30. /***** Before DOM Ready *****/
  31. /************************************/
  32.  
  33. Set.prototype.difference = function(setB) {
  34. const difference = new Set(this);
  35. for(const elem of setB) {
  36. difference.delete(elem);
  37. }
  38. return difference;
  39. };
  40.  
  41. function $find(el, selector, cb = () => {}) {
  42. const found = el.querySelector(selector);
  43. cb(found);
  44. return found;
  45. }
  46.  
  47. function $$find(el, selector, cb = () => {}) {
  48. const found = Array.from(el.querySelectorAll(selector));
  49. cb(found);
  50. return found;
  51. }
  52.  
  53. function $(selector) {
  54. return $find(document, selector);
  55. }
  56.  
  57. function $$(selector) {
  58. return $$find(document, selector);
  59. }
  60.  
  61. function $el(name, attr = {}, cb = () => {}) {
  62. const el = document.createElement(name);
  63. Object.assign(el, attr);
  64. cb(el);
  65. return el;
  66. }
  67.  
  68. function $style(textContent) {
  69. $el('style', {textContent}, el => document.head.appendChild(el));
  70. }
  71.  
  72. // sessionStorage namespace:
  73. // in tab && in domain
  74. function $scrollYTo(n) {
  75. n = parseFloat(n | 0);
  76. const id = setInterval(() => {
  77. scrollTo(scrollX, n);
  78. if (scrollY >= n) {
  79. clearInterval(id);
  80. }
  81. }, 100);
  82. }
  83.  
  84. class API {
  85. // ref: https://github.com/tommy351/ehreader-android/wiki/E-Hentai-JSON-API
  86.  
  87. static gInfo(href) {
  88. // pathname = '/g/{gallery_id}/{gallery_token}/'
  89. const a = $el('a', {href});
  90. const path = a.pathname.split('/').filter(x => x);
  91. if (path[0] !== 'g') {
  92. return null;
  93. }
  94. // [{gallery_id}, {gallery_token}]
  95. return path.slice(1);
  96. }
  97.  
  98. static async gData(gInfos) {
  99. const queue = [];
  100. const result = [];
  101.  
  102. while(gInfos.length) {
  103. const toQ = gInfos.slice(0, 25);
  104. gInfos.splice(0, 25);
  105. queue.push(toQ);
  106. }
  107.  
  108. for(const glist of queue) {
  109. const r = await fetch('/api.php', {
  110. method: 'POST',
  111. credentials: 'same-origin',
  112. body: JSON.stringify({
  113. method: 'gdata',
  114. gidlist: glist,
  115. }),
  116. });
  117.  
  118. const json = await r.json();
  119.  
  120. if (json.error) {
  121. console.error('API.gdata(): glist', glist);
  122. throw new Error(json.error);
  123. } else {
  124. result.push(...json.gmetadata);
  125. }
  126. }
  127.  
  128. return result;
  129. }
  130.  
  131. static async sPage(href) {
  132. const r = await fetch(href, {credentials: 'same-origin'});
  133. const html = await r.text();
  134. const imgsrc = html.replace(/[\s\S]*id="img" src="([^"]+)"[\s\S]*/g, '$1');
  135. return {
  136. imgsrc,
  137. };
  138. }
  139. }
  140.  
  141. const uhpConfig = {
  142. abg: true,
  143. mt: true,
  144. tf: false,
  145. pe: true,
  146. mpv: false,
  147. fw: false,
  148. rth: false,
  149. sr: false,
  150. pi: false,
  151. tpf: false,
  152. flaggingTags: {
  153. red: {
  154. hide: false,
  155. tags:[],
  156. },
  157. green: {
  158. hide: false,
  159. tags:[],
  160. },
  161. brown: {
  162. hide: false,
  163. tags:[],
  164. },
  165. blue: {
  166. hide: false,
  167. tags:[],
  168. },
  169. yellow: {
  170. hide: false,
  171. tags:[],
  172. },
  173. purple: {
  174. hide: false,
  175. tags:[],
  176. },
  177. },
  178. };
  179.  
  180. function uhpSaveConfig() {
  181. GM_setValue('uhp', uhpConfig);
  182. }
  183.  
  184. function uhpLoadConfig() {
  185. return GM_getValue('uhp', uhpConfig);
  186. }
  187.  
  188. Object.assign(uhpConfig, uhpLoadConfig());
  189. uhpSaveConfig();
  190.  
  191. if (uhpConfig.abg) {
  192. Object.defineProperty(window, 'adsbyjuicy', {
  193. enumerable: false,
  194. configurable: false,
  195. writable: false,
  196. value: null,
  197. });
  198. }
  199.  
  200. document.onreadystatechange = function() {
  201. if (document.readyState === 'interactive') {
  202. main();
  203. $style(cssText);
  204. $style(materialCSS);
  205. $el('link', {
  206. href: 'https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css',
  207. rel: 'stylesheet',
  208. integrity: 'sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN',
  209. crossOrigin: 'anonymous',
  210. }, el => document.head.appendChild(el));
  211. }
  212. };
  213.  
  214. /*****************************/
  215. /***** DOM Ready *****/
  216. /*****************************/
  217.  
  218. function main() {
  219. if (!location.pathname.startsWith('/s/')) {
  220. /* Make nav button */
  221. const nb = $('#nb');
  222. const mr = $el('img', {src: '//ehgt.org/g/mr.gif'});
  223. const uhpBtnEl =
  224. $el('a', {
  225. textContent: 'Unlock Hath Perks',
  226. id: 'uhp-btn',
  227. }, el => {
  228. el.addEventListener('click', () => {
  229. $('#uhp-panel-container').classList.remove('hidden');
  230. });
  231. });
  232. nb.appendChild(mr);
  233. nb.appendChild(document.createTextNode(' '));
  234. nb.appendChild(uhpBtnEl);
  235.  
  236. /* Setup UHP Panel */
  237. const uhpPanelContainerEl = $el('div', {
  238. className: 'hidden',
  239. id: 'uhp-panel-container',
  240. }, el => {
  241. el.addEventListener('click', () => {
  242. if($$('#uhp-panel input[pattern]').every(el=>el.validity.valid)) {
  243. el.classList.add('hidden');
  244. }
  245. });
  246. });
  247. document.body.appendChild(uhpPanelContainerEl);
  248.  
  249. const uhpPanelEl = $el('div', {
  250. id: 'uhp-panel',
  251. }, el => {
  252. if(location.host === 'exhentai.org') {
  253. el.classList.add('dark');
  254. }
  255. el.addEventListener('click', event => { event.stopPropagation(); });
  256. });
  257. uhpPanelContainerEl.appendChild(uhpPanelEl);
  258.  
  259. /* Setup UHP Configs */
  260. uhpPanelEl.innerHTML = uhpPanelElHTML + uhpTagFlaggingHTML;
  261.  
  262. $$('#uhp-panel input[id^="uhp-conf-"]').forEach(el => {
  263. const abbr = el.id.replace('uhp-conf-', '');
  264. el.checked = uhpConfig[abbr];
  265. el.addEventListener('change', () => {
  266. uhpConfig[abbr] = el.checked;
  267. uhpSaveConfig();
  268. });
  269. });
  270.  
  271. $$('#uhp-panel input[pattern]').forEach(el => {
  272. // tag color
  273. const tc = el.id.replace('uhp-tf-', '');
  274. el.addEventListener('change', () => {
  275. const newTags = el.value.split(',').map(x => x.trim()).filter(x => x);
  276. const oldTags = uhpConfig.flaggingTags[tc].tags;
  277. const allTags = Object.values(uhpConfig.flaggingTags).reduce((acc, val) => acc.concat(val.tags), []);
  278. const newAllSet = new Set(allTags).difference(oldTags);
  279. const newSet = new Set(newTags).difference(newAllSet);
  280.  
  281. el.value = [...newSet].join(', ');
  282. uhpConfig.flaggingTags[tc].tags = [...newSet];
  283. uhpSaveConfig();
  284. });
  285. });
  286.  
  287. $$('.uhp-tf-options input[type="checkbox"]').forEach(el => {
  288. // tag color
  289. const tc = el.id.replace(/uhp-tf-(\w+)-hide/, '$1');
  290. el.checked = uhpConfig.flaggingTags[tc].hide;
  291. el.addEventListener('change', () => {
  292. uhpConfig.flaggingTags[tc].hide = el.checked;
  293. uhpSaveConfig();
  294. });
  295. });
  296.  
  297. /* Setup Reactable UHP Configs */
  298. $('#uhp-conf-fw').addEventListener('change', event => {
  299. const fwc = $('#uhp-full-width-container');
  300. if(fwc) {
  301. if(event.target.checked) {
  302. fwc.classList.add('fullwidth');
  303. } else {
  304. fwc.classList.remove('fullwidth');
  305. }
  306. }
  307. });
  308.  
  309. $('#uhp-conf-tf').addEventListener('change', event => {
  310. const tfops = $$('.uhp-tf-options');
  311. if(event.target.checked) {
  312. tfops.forEach(el => el.classList.remove('hidden'));
  313. } else {
  314. tfops.forEach(el => el.classList.add('hidden'));
  315. }
  316. });
  317. }
  318.  
  319. if ($('#searchbox')) {
  320. const ido = $('div.ido');
  321. ido.id = 'uhp-full-width-container';
  322. if (uhpConfig.fw) {
  323. ido.classList.add('fullwidth');
  324. }
  325. }
  326.  
  327. /* Main Functions by Configs */
  328.  
  329. /**************/
  330. /* Ad-Be-Gone */
  331. /**************/
  332. if (uhpConfig.abg) {
  333. // if "No hits found", there is no mode
  334. if ($('#searchbox') && $('#dmi>span')) {
  335. const mode = $('#dmi>span').textContent === 'Thumbnails' ? 't' : 'l';
  336. if (mode === 'l') {
  337. $$('table.itg tr:nth-of-type(n+2)')
  338. .forEach(el => {
  339. if (!el.className) {
  340. el.remove();
  341. }
  342. });
  343. }
  344. }
  345.  
  346. $$('script[async]').forEach(el => el.remove());
  347. $$('iframe').forEach(el => el.remove());
  348. }
  349.  
  350. /**********************/
  351. /* Paging Enlargement */
  352. /**********************/
  353. async function getNextPage(nextURL, mode) {
  354. const selector = mode === 't' ? 'div.id1' : 'table.itg tr:nth-of-type(n+2)';
  355.  
  356. const result = {
  357. mode,
  358. elements: [],
  359. nextURL: null,
  360. };
  361.  
  362. console.assert(nextURL, `nextURL should be string but is ${nextURL}`);
  363.  
  364. if (!nextURL) {
  365. console.warn('nextURL:', nextURL);
  366. return result;
  367. }
  368.  
  369. const response = await fetch(nextURL, {
  370. credentials: 'same-origin',
  371. });
  372. if (response.ok) {
  373. const html = await response.text();
  374. const doc = new DOMParser().parseFromString(html, 'text/html');
  375. result.elements = Array.from($$find(doc, selector));
  376. if (uhpConfig.abg) {
  377. result.elements = result.elements.filter(el => el.className);
  378. }
  379. result.elements =
  380. result.elements
  381. .filter(el => {
  382. if(uhpConfig.rth) {
  383. if (mode === 't') {
  384. return !$find(el, '.id3 img').src.endsWith('blank.gif');
  385. } else {
  386. return !$find(el, '.it5 > a').onmouseover;
  387. }
  388. }
  389. return true;
  390. })
  391. .map(el => {
  392. el.removeAttribute('style');
  393. return el;
  394. });
  395.  
  396. const nextEl = $find(doc, '.ptb td:last-child > a');
  397. result.nextURL = nextEl ? nextEl.href : null;
  398. }
  399. return result;
  400. }
  401.  
  402. async function addTagFlags(page) {
  403. const selector = page.mode === 't' ? '.id3 > a' : '.it5 > a';
  404. const gLinks = page.elements.map(el => $find(el, selector).href);
  405. const gInfos = gLinks.map(a => API.gInfo(a));
  406. const gData = await API.gData(gInfos);
  407. const tagsMap = {};
  408. for(const i in gLinks) {
  409. const gLink = gLinks[i];
  410. // tag1;tag2;tag3
  411. tagsMap[gLink] = gData[i].tags.join(';');
  412. }
  413.  
  414. for(const pageEl of page.elements) {
  415. const parent = (page.mode === 't') ?
  416. $find(pageEl, '.id44') :
  417. $el('div', {className: 'it4t'}, el => {
  418. if($find(pageEl, '.it4t')) {
  419. $find(pageEl, '.it4t').replaceWith(el);
  420. } else {
  421. $find(pageEl, '.it4').appendChild(el);
  422. }
  423. });
  424.  
  425. const aLink = $find(pageEl, selector);
  426. // remove exists
  427. $$find(parent, `.tf${page.mode}`).forEach(el => el.remove());
  428.  
  429. for (const c in uhpConfig.flaggingTags) {
  430. const tags = uhpConfig.flaggingTags[c].tags;
  431. const matchs = tags.filter(t => tagsMap[aLink.href].includes(t));
  432. if (matchs.length) {
  433. const flagEl = $el('div', {title: matchs.join(', '), className:`tf${page.mode} ${c}`});
  434. parent.appendChild(flagEl);
  435. if (uhpConfig.flaggingTags[c].hide) {
  436. if (page.mode === 't') {
  437. $find(aLink, 'img').src = '//ehgt.org/g/blank.gif';
  438. } else {
  439. aLink.removeAttribute('onmouseover');
  440. aLink.removeAttribute('onmouseout');
  441. }
  442. }
  443. }
  444. }
  445. }
  446. console.info('before', page.elements);
  447. page.elements = page.elements.filter(el => {
  448. if(uhpConfig.rth) {
  449. if (page.mode === 't') {
  450. return !$find(el, '.id3 img').src.endsWith('blank.gif');
  451. } else {
  452. console.log($find(el, '.it5 > a'), $find(el, '.it5 > a').getAttribute('onmouseover'));
  453. return $find(el, '.it5 > a').getAttribute('onmouseover');
  454. }
  455. }
  456. return true;
  457. });
  458. console.info('after', page.elements);
  459. }
  460.  
  461. // if "No hits found", there is no mode
  462. if ($('#searchbox') && $('#dmi>span')) {
  463. (async() => {
  464. const nextEl = $('.ptb td:last-child > a');
  465. let nextURL = nextEl ? nextEl.href : null;
  466. const mode = $('#dmi>span').textContent === 'Thumbnails' ? 't' : 'l';
  467. const parent = mode === 't' ? $('div.itg') : $('table.itg tbody');
  468. const status = $el('h1', {
  469. textContent: 'Loading...',
  470. id: 'uhp-status',
  471. });
  472.  
  473. const urlSet = new Set();
  474.  
  475. if (mode === 'l') {
  476. if (location.hostname.startsWith('exh')) {
  477. parent.classList.add('uhp-list-parent-exh');
  478. } else {
  479. parent.classList.add('uhp-list-parent-eh');
  480. }
  481. } else {
  482. parent.style.borderBottom = 'none';
  483. $$('div.id1').forEach(el => el.removeAttribute('style'));
  484. }
  485.  
  486. // this page
  487. const thisPage = await getNextPage(location.href, mode);
  488. if(uhpConfig.tf) {
  489. await addTagFlags(thisPage);
  490. }
  491. while (parent.firstChild) {
  492. parent.firstChild.remove();
  493. }
  494. thisPage.elements.forEach(el => parent.appendChild(el));
  495. nextURL = thisPage.nextURL;
  496. if (!nextURL) {
  497. status.textContent = 'End';
  498. }
  499.  
  500. // next page
  501. if (uhpConfig.pe) {
  502. $('table.ptb').replaceWith(status);
  503.  
  504. // remove popular section
  505. $$('div.c, #pt, #pp').forEach(el => el.remove());
  506.  
  507. document.addEventListener('scroll', async() => {
  508. const anchorTop = status.getBoundingClientRect().top;
  509. const windowHeight = window.innerHeight;
  510.  
  511. if (anchorTop < windowHeight * 2 && nextURL && !urlSet.has(nextURL)) {
  512. urlSet.add(nextURL);
  513. const nextPage = await getNextPage(nextURL, mode);
  514. if(uhpConfig.tf) {
  515. await addTagFlags(nextPage);
  516. }
  517. console.log(nextPage);
  518.  
  519. //// work around first ////
  520. if(uhpConfig.pi) {
  521. if (mode === 'l') {
  522. parent.appendChild($el('tr', {
  523. className: 'uhp-open-in-new-page',
  524. }, el => {
  525. el.innerHTML = `<td colspan="4" style="font-size: 4rem;">
  526. <a href="${nextURL}" style="text-decoration: none; display: inline-flex; align-items: flex-end;">
  527. P${~~nextURL.replace(/.*(?:page=(\d+)|\/(\d+)$).*/g, '$1$2') + 1}
  528. </a>
  529. </td>`;
  530. }));
  531. } else {
  532. parent.appendChild($el('div', {
  533. className: 'uhp-open-in-new-page',
  534. style: 'grid-column: 1; display: flex; align-items: center; justify-content: center;',
  535. }, el => {
  536. el.innerHTML = `<div style="position: sticky;top: 0;font-size: 4rem;">
  537. <a href="${nextURL}" style="text-decoration: none; display: inline-flex; align-items: flex-end;">
  538. P${~~nextURL.replace(/.*(?:page=(\d+)|\/(\d+)$).*/g, '$1$2') + 1}
  539. </a>
  540. </div>`;
  541. }));
  542. }
  543. }
  544.  
  545. if(uhpConfig.tpf) {
  546. parent.classList.add('uhp-tpf-dense');
  547. }
  548. //// work around first ////
  549.  
  550.  
  551. nextPage.elements.forEach(el => parent.appendChild(el));
  552. nextURL = nextPage.nextURL;
  553. if (!nextURL) {
  554. status.textContent = 'End';
  555. }
  556. }
  557. });
  558. }
  559. })();
  560. }
  561.  
  562.  
  563. /***************/
  564. /* More Thumbs */
  565. /***************/
  566. async function getNextGallaryPage(nextURL) {
  567. const result = {
  568. elements: [],
  569. nextURL: null,
  570. };
  571. if (!nextURL) {
  572. return result;
  573. }
  574. const response = await fetch(nextURL, {
  575. credentials: 'same-origin',
  576. });
  577. if (response.ok) {
  578. const html = await response.text();
  579. const doc = new DOMParser().parseFromString(html, 'text/html');
  580. result.elements = $$find(doc, '#gdt > div');
  581. const nextEl = $$find(doc, '.ptb td:last-child > a');
  582. result.nextURL = nextEl ? nextEl.href : null;
  583. }
  584. return result;
  585. }
  586.  
  587. if (uhpConfig.mt && location.pathname.startsWith('/g/')) {
  588. (async() => {
  589. $('#gdo1').style.display = 'none';
  590. const nextEl = $('.ptb td:last-child > a');
  591. let nextURL = nextEl ? nextEl.href : null;
  592. const parent = $('#gdt');
  593. parent.classList.add('uhp-page-parent');
  594. const urlSet = new Set();
  595.  
  596. // this page
  597. const thisPage = await getNextGallaryPage(location.href);
  598. while (parent.firstChild) {
  599. parent.firstChild.remove();
  600. }
  601. thisPage.elements.forEach(el => parent.appendChild(el));
  602.  
  603. // next page
  604. document.addEventListener('scroll', async() => {
  605. const anchorTop = $('#cdiv').getBoundingClientRect().top;
  606. const windowHeight = window.innerHeight;
  607.  
  608. if (anchorTop < windowHeight * 2 && !urlSet.has(nextURL)) {
  609. urlSet.add(nextURL);
  610. const nextPage = await getNextGallaryPage(nextURL);
  611. console.log(nextPage);
  612. nextPage.elements.forEach(el => parent.appendChild(el));
  613. nextURL = nextPage.nextURL;
  614. }
  615. });
  616. })();
  617. }
  618.  
  619. /**********************/
  620. /* Scroll Restoration */
  621. /**********************/
  622. if(uhpConfig.sr) {
  623. history.scrollRestoration = 'manual';
  624.  
  625. window.addEventListener('beforeunload', () => {
  626. history.replaceState(scrollY, null);
  627. });
  628.  
  629. window.addEventListener('load', () => {
  630. if (history.state) {
  631. $scrollYTo(history.state);
  632. }
  633. });
  634. }
  635. }
  636.  
  637. var uhpPanelElHTML = `
  638. <h1>Hath Perks</h1>
  639. <div class="option-grid">
  640. <div class="material-switch">
  641. <input id="uhp-conf-abg" type="checkbox">
  642. <label for="uhp-conf-abg"></label>
  643. </div>
  644. <span id="uhp-conf-abg-title">Ads-Be-Gone</span>
  645. <span id="uhp-conf-abg-desc">Make ad scripts won't work before request.</span>
  646.  
  647. <div class="material-switch">
  648. <input id="uhp-conf-tf" type="checkbox">
  649. <label for="uhp-conf-tf"></label>
  650. </div>
  651. <span id="uhp-conf-tf-title">Tag Flagging</span>
  652. <span id="uhp-conf-tf-desc">Can flag 6 color for tags.<br/>
  653. Hide thumbnail of search results when the switch turn on.<br/>
  654. Conflict with official "Tag Flagging".
  655. </span>
  656.  
  657. <div class="material-switch">
  658. <input id="uhp-conf-mpv" type="checkbox" disabled>
  659. <label for="uhp-conf-mpv"></label>
  660. </div>
  661. <span id="uhp-conf-mpv-title">Multi-Page Viewer</span>
  662. <span id="uhp-conf-mpv-desc">Work in Progress</span>
  663.  
  664. <div class="material-switch">
  665. <input id="uhp-conf-mt" type="checkbox">
  666. <label for="uhp-conf-mt"></label>
  667. </div>
  668. <span id="uhp-conf-mt-title">More Thumbs</span>
  669. <span id="uhp-conf-mt-desc">Make thumbnails in book page infinitely scroll.</span>
  670.  
  671. <div class="material-switch">
  672. <input id="uhp-conf-pe" type="checkbox">
  673. <label for="uhp-conf-pe"></label>
  674. </div>
  675. <span id="uhp-conf-pe-title">Paging Enlargement</span>
  676. <span id="uhp-conf-pe-desc">Make search results page infinitely scroll.<br/>Popular section will be removed.</span>
  677. </div>
  678.  
  679. <h1>Others</h1>
  680. <div class="option-grid">
  681. <div class="material-switch">
  682. <input id="uhp-conf-fw" type="checkbox">
  683. <label for="uhp-conf-fw"></label>
  684. </div>
  685. <span id="uhp-conf-fw-title">Full Width</span>
  686. <span id="uhp-conf-fw-desc">Make search results fitting browser width.<br/>Only affect on thumb display mode.</span>
  687.  
  688. <div class="material-switch">
  689. <input id="uhp-conf-rth" type="checkbox">
  690. <label for="uhp-conf-rth"></label>
  691. </div>
  692. <span id="uhp-conf-rth-title">Remove Tag Hidden</span>
  693. <span id="uhp-conf-rth-desc">Remove search results which tagged with hidden when "Tag Flagging" work.</span>
  694.  
  695. <div class="material-switch">
  696. <input id="uhp-conf-sr" type="checkbox">
  697. <label for="uhp-conf-sr"></label>
  698. </div>
  699. <span id="uhp-conf-sr-title">Scroll Restoration</span>
  700. <span id="uhp-conf-sr-desc">Scroll last position you seen in last page when "Paging Enlargement" work.</span>
  701.  
  702. <div class="material-switch">
  703. <input id="uhp-conf-pi" type="checkbox">
  704. <label for="uhp-conf-pi"></label>
  705. </div>
  706. <span id="uhp-conf-pi-title">Page Indicator</span>
  707. <span id="uhp-conf-pi-desc">Add page indicator link to prevent "Scroll Restoration" work too hard.</span>
  708.  
  709. <div class="material-switch">
  710. <input id="uhp-conf-tpf" type="checkbox">
  711. <label for="uhp-conf-tpf"></label>
  712. </div>
  713. <span id="uhp-conf-tpf-title">Thumb Page Flow</span>
  714. <span id="uhp-conf-tpf-desc">Make dense flow when "Page Indicator" work.<br/>Only affect on thumb display mode.</span>
  715. </div>
  716. `;
  717.  
  718. var uhpTagFlaggingHTML = `
  719. <h1 class="uhp-tf-options ${uhpConfig.tf ? '' : 'hidden'}">Tag Flagging</h1>
  720. <div class="uhp-tf-options tf-option-grid ${uhpConfig.tf ? '' : 'hidden'}">
  721. <div class="tfl red"></div>
  722. <input id="uhp-tf-red" pattern="(\\w(?:[^:]|[\\w\\s])+)(?:,\\s*\\1)*" value="${uhpConfig.flaggingTags.red.tags.join(', ')}" placeholder="e.g. touhou, flandre scarlet"/>
  723. <div class="material-switch">
  724. <input id="uhp-tf-red-hide" type="checkbox">
  725. <label for="uhp-tf-red-hide"></label>
  726. </div>
  727.  
  728. <div class="tfl green"></div>
  729. <input id="uhp-tf-green" pattern="(\\w(?:[^:]|[\\w\\s])+)(?:,\\s*\\1)*" value="${uhpConfig.flaggingTags.green.tags.join(', ')}"/>
  730. <div class="material-switch">
  731. <input id="uhp-tf-green-hide" type="checkbox">
  732. <label for="uhp-tf-green-hide"></label>
  733. </div>
  734.  
  735. <div class="tfl brown"></div>
  736. <input id="uhp-tf-brown" pattern="(\\w(?:[^:]|[\\w\\s])+)(?:,\\s*\\1)*" value="${uhpConfig.flaggingTags.brown.tags.join(', ')}"/>
  737. <div class="material-switch">
  738. <input id="uhp-tf-brown-hide" type="checkbox">
  739. <label for="uhp-tf-brown-hide"></label>
  740. </div>
  741.  
  742. <div class="tfl blue"></div>
  743. <input id="uhp-tf-blue" pattern="(\\w(?:[^:]|[\\w\\s])+)(?:,\\s*\\1)*" value="${uhpConfig.flaggingTags.blue.tags.join(', ')}"/>
  744. <div class="material-switch">
  745. <input id="uhp-tf-blue-hide" type="checkbox">
  746. <label for="uhp-tf-blue-hide"></label>
  747. </div>
  748.  
  749. <div class="tfl yellow"></div>
  750. <input id="uhp-tf-yellow" pattern="(\\w(?:[^:]|[\\w\\s])+)(?:,\\s*\\1)*" value="${uhpConfig.flaggingTags.yellow.tags.join(', ')}"/>
  751. <div class="material-switch">
  752. <input id="uhp-tf-yellow-hide" type="checkbox">
  753. <label for="uhp-tf-yellow-hide"></label>
  754. </div>
  755.  
  756. <div class="tfl purple"></div>
  757. <input id="uhp-tf-purple" pattern="(\\w(?:[^:]|[\\w\\s])+)(?:,\\s*\\1)*" value="${uhpConfig.flaggingTags.purple.tags.join(', ')}"/>
  758. <div class="material-switch">
  759. <input id="uhp-tf-purple-hide" type="checkbox">
  760. <label for="uhp-tf-purple-hide"></label>
  761. </div>
  762. </div>
  763. `;
  764.  
  765. var cssText = `
  766. #uhp-btn {
  767. cursor: pointer;
  768. }
  769. #uhp-panel-container {
  770. position:fixed;
  771. top: 0;
  772. height: 100vh;
  773. width: 100vw;
  774. background-color: rgba(200, 200, 200, 0.7);
  775. z-index: 2;
  776. display: flex;
  777. align-items: center;
  778. justify-content: center;
  779. }
  780. #uhp-panel-container.hidden {
  781. visibility: hidden;
  782. opacity: 0;
  783. }
  784. #uhp-panel {
  785. padding: 1.2rem;
  786. background-color: floralwhite;
  787. border-radius: 1rem;
  788. font-size: 1rem;
  789. color: darkred;
  790. max-width: 650px;
  791. }
  792. #uhp-panel.dark {
  793. background-color: dimgray;
  794. color: ghostwhite;
  795. }
  796. #uhp-panel > .option-grid {
  797. display: grid;
  798. grid-template-columns: max-content max-content 1fr;
  799. grid-gap: 0.5rem 1rem;
  800. }
  801. #uhp-panel > .tf-option-grid {
  802. display: grid;
  803. grid-template-columns: 20px 1fr max-content;
  804. grid-gap: 0.5rem 1rem;
  805. }
  806. #uhp-panel > .option-grid > *,
  807. #uhp-panel > .tf-option-grid > * {
  808. display: flex;
  809. justify-content: center;
  810. align-items: center;
  811. }
  812. #uhp-panel > .tf-option-grid > .tfl {
  813. margin: auto;
  814. }
  815. #uhp-panel > .uhp-tf-options.hidden {
  816. display: none;
  817. }
  818. #uhp-full-width-container.fullwidth,
  819. #uhp-full-width-container.fullwidth div.itg {
  820. max-width: none;
  821. }
  822. #uhp-full-width-container div.itg {
  823. display: grid;
  824. grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  825. grid-gap: 2px;
  826. }
  827. #uhp-full-width-container div.itg.uhp-tpf-dense {
  828. grid-auto-flow: dense;
  829. }
  830. #uhp-full-width-container div.id1 {
  831. height: 345px;
  832. float: none;
  833. display: flex;
  834. flex-direction: column;
  835. margin: 3px auto;
  836. padding: 4px 0;
  837. }
  838. #uhp-full-width-container div.id2 {
  839. overflow: visible;
  840. height: initial;
  841. margin: 4px auto;
  842. }
  843. #uhp-full-width-container div.id3 {
  844. flex: 1;
  845. display: flex;
  846. justify-content: center;
  847. align-items: center;
  848. }
  849. .uhp-list-parent-eh tr:nth-of-type(2n+1){
  850. background-color: #EDEBDF;
  851. }
  852. .uhp-list-parent-eh tr:nth-of-type(2n+2){
  853. background-color: #F2F0E4;
  854. }
  855. .uhp-list-parent-exh tr:nth-of-type(2n+1) {
  856. background-color: #363940;
  857. }
  858. .uhp-list-parent-exh tr:nth-of-type(2n+2){
  859. background-color: #4F535B;
  860. }
  861. #uhp-status {
  862. text-align: center;
  863. font-size: 3rem;
  864. clear: both;
  865. padding: 2rem 0;
  866. }
  867. /* replace */
  868. div#pp,
  869. div#gdt.uhp-page-parent {
  870. display: flex;
  871. flex-wrap: wrap;
  872. }
  873. div#gdt.uhp-page-parent>div{
  874. float: initial;
  875. }
  876. div.it4t {
  877. width: 102px;
  878. }
  879. div.tfl.red,
  880. div.tft.red {
  881. background-position: 0 -1px;
  882. }
  883. div.tfl.green,
  884. div.tft.green {
  885. background-position: 0px -52px;
  886. }
  887. div.tfl.brown,
  888. div.tft.brown {
  889. background-position: 0px -18px;
  890. }
  891. div.tfl.blue,
  892. div.tft.blue {
  893. background-position: 0px -69px;
  894. }
  895. div.tfl.yellow,
  896. div.tft.yellow {
  897. background-position: 0px -35px;
  898. }
  899. div.tfl.purple,
  900. div.tft.purple {
  901. background-position: 0px -86px;
  902. }`;
  903.  
  904. /* https://bootsnipp.com/snippets/featured/material-design-switch */
  905. var materialCSS = `
  906. .material-switch {
  907. display: inline-block;
  908. }
  909.  
  910. .material-switch > input[type="checkbox"] {
  911. display: none;
  912. }
  913.  
  914. .material-switch > input[type="checkbox"] + label {
  915. display: inline-block;
  916. position: relative;
  917. margin: 6px;
  918. border-radius: 8px;
  919. width: 40px;
  920. height: 16px;
  921. opacity: 0.3;
  922. background-color: rgb(0, 0, 0);
  923. box-shadow: inset 0px 0px 10px rgba(0, 0, 0, 0.5);
  924. transition: all 0.4s ease-in-out;
  925. }
  926.  
  927. .material-switch > input[type="checkbox"] + label::after {
  928. position: absolute;
  929. top: -4px;
  930. left: -4px;
  931. border-radius: 16px;
  932. width: 24px;
  933. height: 24px;
  934. content: "";
  935. background-color: rgb(255, 255, 255);
  936. box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.3);
  937. transition: all 0.3s ease-in-out;
  938. }
  939.  
  940. .material-switch > input[type="checkbox"]:checked + label {
  941. background-color: #0e0;
  942. opacity: 0.7;
  943. }
  944.  
  945. .material-switch > input[type="checkbox"]:checked + label::after {
  946. background-color: inherit;
  947. left: 20px;
  948. }
  949. .material-switch > input[type="checkbox"]:disabled + label::after {
  950. content: "\\f023";
  951. line-height: 24px;
  952. font-size: 0.8em;
  953. font-family: FontAwesome;
  954. color: initial;
  955. }`;