Unlock Hath Perks

Unlock Hath Perks and add other helpers

La data de 22-10-2017. Vezi ultima versiune.

  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.2
  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. if (!nextURL) {
  363. return result;
  364. }
  365.  
  366. const response = await fetch(nextURL, {
  367. credentials: 'same-origin',
  368. });
  369. if (response.ok) {
  370. const html = await response.text();
  371. const doc = new DOMParser().parseFromString(html, 'text/html');
  372. result.elements = Array.from($$find(doc, selector));
  373. if (uhpConfig.abg) {
  374. result.elements = result.elements.filter(el => el.className);
  375. }
  376. result.elements =
  377. result.elements
  378. .filter(el => {
  379. if(uhpConfig.rth) {
  380. if (mode === 't') {
  381. return !$find(el, '.id3 img').src.endsWith('blank.gif');
  382. } else {
  383. return $find(el, '.it5 > a').getAttribute('onmouseover');
  384. }
  385. }
  386. return true;
  387. })
  388. .map(el => {
  389. el.removeAttribute('style');
  390. return el;
  391. });
  392.  
  393. const nextEl = $find(doc, '.ptb td:last-child > a');
  394. result.nextURL = nextEl ? nextEl.href : null;
  395. }
  396. console.log(result);
  397. return result;
  398. }
  399.  
  400. async function addTagFlags(page) {
  401. const selector = page.mode === 't' ? '.id3 > a' : '.it5 > a';
  402. const gLinks = page.elements.map(el => $find(el, selector).href);
  403. const gInfos = gLinks.map(a => API.gInfo(a));
  404. const gData = await API.gData(gInfos);
  405. const tagsMap = {};
  406. for(const i in gLinks) {
  407. const gLink = gLinks[i];
  408. // tag1;tag2;tag3
  409. tagsMap[gLink] = gData[i].tags.join(';');
  410. }
  411.  
  412. for(const pageEl of page.elements) {
  413. const parent = (page.mode === 't') ?
  414. $find(pageEl, '.id44') :
  415. $el('div', {className: 'it4t'}, el => {
  416. if($find(pageEl, '.it4t')) {
  417. $find(pageEl, '.it4t').replaceWith(el);
  418. } else {
  419. $find(pageEl, '.it4').appendChild(el);
  420. }
  421. });
  422.  
  423. const aLink = $find(pageEl, selector);
  424. // remove exists
  425. $$find(parent, `.tf${page.mode}`).forEach(el => el.remove());
  426.  
  427. for (const c in uhpConfig.flaggingTags) {
  428. const tags = uhpConfig.flaggingTags[c].tags;
  429. const matchs = tags.filter(t => tagsMap[aLink.href].includes(t));
  430. if (matchs.length) {
  431. const flagEl = $el('div', {title: matchs.join(', '), className:`tf${page.mode} ${c}`});
  432. parent.appendChild(flagEl);
  433. if (uhpConfig.flaggingTags[c].hide) {
  434. if (page.mode === 't') {
  435. $find(aLink, 'img').src = '//ehgt.org/g/blank.gif';
  436. } else {
  437. aLink.removeAttribute('onmouseover');
  438. aLink.removeAttribute('onmouseout');
  439. }
  440. }
  441. }
  442. }
  443. }
  444. page.elements = page.elements.filter(el => {
  445. if(uhpConfig.rth) {
  446. if (page.mode === 't') {
  447. return !$find(el, '.id3 img').src.endsWith('blank.gif');
  448. } else {
  449. return $find(el, '.it5 > a').getAttribute('onmouseover');
  450. }
  451. }
  452. return true;
  453. });
  454. }
  455.  
  456. // if "No hits found", there is no mode
  457. if ($('#searchbox') && $('#dmi>span')) {
  458. (async() => {
  459. const nextEl = $('.ptb td:last-child > a');
  460. let nextURL = nextEl ? nextEl.href : null;
  461. const mode = $('#dmi>span').textContent === 'Thumbnails' ? 't' : 'l';
  462. const parent = mode === 't' ? $('div.itg') : $('table.itg tbody');
  463. const status = $el('h1', {
  464. textContent: 'Loading...',
  465. id: 'uhp-status',
  466. });
  467.  
  468. const urlSet = new Set();
  469.  
  470. if (mode === 'l') {
  471. if (location.hostname.startsWith('exh')) {
  472. parent.classList.add('uhp-list-parent-exh');
  473. } else {
  474. parent.classList.add('uhp-list-parent-eh');
  475. }
  476. } else {
  477. parent.style.borderBottom = 'none';
  478. $$('div.id1').forEach(el => el.removeAttribute('style'));
  479. }
  480.  
  481. // this page
  482. const thisPage = await getNextPage(location.href, mode);
  483. if(uhpConfig.tf) {
  484. await addTagFlags(thisPage);
  485. }
  486. while (parent.firstChild) {
  487. parent.firstChild.remove();
  488. }
  489.  
  490. thisPage.elements.forEach(el => parent.appendChild(el));
  491. nextURL = thisPage.nextURL;
  492. if (!nextURL) {
  493. status.textContent = 'End';
  494. }
  495.  
  496. // next page
  497. if (uhpConfig.pe) {
  498. $('table.ptb').replaceWith(status);
  499.  
  500. // remove popular section
  501. $$('div.c, #pt, #pp').forEach(el => el.remove());
  502.  
  503. document.addEventListener('scroll', async() => {
  504. const anchorTop = status.getBoundingClientRect().top;
  505. const windowHeight = window.innerHeight;
  506.  
  507. if (anchorTop < windowHeight * 2 && nextURL && !urlSet.has(nextURL)) {
  508. urlSet.add(nextURL);
  509. const nextPage = await getNextPage(nextURL, mode);
  510. if(uhpConfig.tf) {
  511. await addTagFlags(nextPage);
  512. }
  513.  
  514. //// work around first ////
  515. if(uhpConfig.pi) {
  516. if (mode === 'l') {
  517. parent.appendChild($el('tr', {
  518. className: 'uhp-open-in-new-page',
  519. }, el => {
  520. el.innerHTML = `<td colspan="4" style="font-size: 4rem;">
  521. <a href="${nextURL}" style="text-decoration: none; display: inline-flex; align-items: flex-end;">
  522. P${~~nextURL.replace(/.*(?:page=(\d+)|\/(\d+)$).*/g, '$1$2') + 1}
  523. </a>
  524. </td>`;
  525. }));
  526. } else {
  527. parent.appendChild($el('div', {
  528. className: 'uhp-open-in-new-page',
  529. style: 'grid-column: 1; display: flex; align-items: center; justify-content: center;',
  530. }, el => {
  531. el.innerHTML = `<div style="position: sticky;top: 0;font-size: 4rem;">
  532. <a href="${nextURL}" style="text-decoration: none; display: inline-flex; align-items: flex-end;">
  533. P${~~nextURL.replace(/.*(?:page=(\d+)|\/(\d+)$).*/g, '$1$2') + 1}
  534. </a>
  535. </div>`;
  536. }));
  537. }
  538. }
  539.  
  540. if(uhpConfig.tpf) {
  541. parent.classList.add('uhp-tpf-dense');
  542. }
  543. //// work around first ////
  544.  
  545.  
  546. nextPage.elements.forEach(el => parent.appendChild(el));
  547. nextURL = nextPage.nextURL;
  548. if (!nextURL) {
  549. status.textContent = 'End';
  550. }
  551. }
  552. });
  553. }
  554. })();
  555. }
  556.  
  557.  
  558. /***************/
  559. /* More Thumbs */
  560. /***************/
  561. async function getNextGallaryPage(nextURL) {
  562. const result = {
  563. elements: [],
  564. nextURL: null,
  565. };
  566. if (!nextURL) {
  567. return result;
  568. }
  569. const response = await fetch(nextURL, {
  570. credentials: 'same-origin',
  571. });
  572. if (response.ok) {
  573. const html = await response.text();
  574. const doc = new DOMParser().parseFromString(html, 'text/html');
  575. result.elements = $$find(doc, '#gdt > div');
  576. const nextEl = $find(doc, '.ptb td:last-child > a');
  577. result.nextURL = nextEl ? nextEl.href : null;
  578. }
  579. console.log(result);
  580. return result;
  581. }
  582.  
  583. if (uhpConfig.mt && location.pathname.startsWith('/g/')) {
  584. (async() => {
  585. $('#gdo1').style.display = 'none';
  586. const nextEl = $('.ptb td:last-child > a');
  587. let nextURL = nextEl ? nextEl.href : null;
  588. const parent = $('#gdt');
  589. parent.classList.add('uhp-page-parent');
  590. const urlSet = new Set();
  591.  
  592. // this page
  593. const thisPage = await getNextGallaryPage(location.href);
  594. while (parent.firstChild) {
  595. parent.firstChild.remove();
  596. }
  597. thisPage.elements.forEach(el => parent.appendChild(el));
  598.  
  599. // next page
  600. document.addEventListener('scroll', async() => {
  601. const anchorTop = $('#cdiv').getBoundingClientRect().top;
  602. const windowHeight = window.innerHeight;
  603.  
  604. if (anchorTop < windowHeight * 2 && !urlSet.has(nextURL)) {
  605. urlSet.add(nextURL);
  606. const nextPage = await getNextGallaryPage(nextURL);
  607. nextPage.elements.forEach(el => parent.appendChild(el));
  608. nextURL = nextPage.nextURL;
  609. }
  610. });
  611. })();
  612. }
  613.  
  614. /**********************/
  615. /* Scroll Restoration */
  616. /**********************/
  617. if(uhpConfig.sr) {
  618. history.scrollRestoration = 'manual';
  619.  
  620. window.addEventListener('beforeunload', () => {
  621. history.replaceState(scrollY, null);
  622. });
  623.  
  624. window.addEventListener('load', () => {
  625. if (history.state) {
  626. $scrollYTo(history.state);
  627. }
  628. });
  629. }
  630. }
  631.  
  632. var uhpPanelElHTML = `
  633. <h1>Hath Perks</h1>
  634. <div class="option-grid">
  635. <div class="material-switch">
  636. <input id="uhp-conf-abg" type="checkbox">
  637. <label for="uhp-conf-abg"></label>
  638. </div>
  639. <span id="uhp-conf-abg-title">Ads-Be-Gone</span>
  640. <span id="uhp-conf-abg-desc">Make ad scripts won't work before request.</span>
  641.  
  642. <div class="material-switch">
  643. <input id="uhp-conf-tf" type="checkbox">
  644. <label for="uhp-conf-tf"></label>
  645. </div>
  646. <span id="uhp-conf-tf-title">Tag Flagging</span>
  647. <span id="uhp-conf-tf-desc">Can flag 6 color for tags.<br/>
  648. Hide thumbnail of search results when the switch turn on.<br/>
  649. Conflict with official "Tag Flagging".
  650. </span>
  651.  
  652. <div class="material-switch">
  653. <input id="uhp-conf-mpv" type="checkbox" disabled>
  654. <label for="uhp-conf-mpv"></label>
  655. </div>
  656. <span id="uhp-conf-mpv-title">Multi-Page Viewer</span>
  657. <span id="uhp-conf-mpv-desc">Work in Progress</span>
  658.  
  659. <div class="material-switch">
  660. <input id="uhp-conf-mt" type="checkbox">
  661. <label for="uhp-conf-mt"></label>
  662. </div>
  663. <span id="uhp-conf-mt-title">More Thumbs</span>
  664. <span id="uhp-conf-mt-desc">Make thumbnails in book page infinitely scroll.</span>
  665.  
  666. <div class="material-switch">
  667. <input id="uhp-conf-pe" type="checkbox">
  668. <label for="uhp-conf-pe"></label>
  669. </div>
  670. <span id="uhp-conf-pe-title">Paging Enlargement</span>
  671. <span id="uhp-conf-pe-desc">Make search results page infinitely scroll.<br/>Popular section will be removed.</span>
  672. </div>
  673.  
  674. <h1>Others</h1>
  675. <div class="option-grid">
  676. <div class="material-switch">
  677. <input id="uhp-conf-fw" type="checkbox">
  678. <label for="uhp-conf-fw"></label>
  679. </div>
  680. <span id="uhp-conf-fw-title">Full Width</span>
  681. <span id="uhp-conf-fw-desc">Make search results fitting browser width.<br/>Only affect on thumb display mode.</span>
  682.  
  683. <div class="material-switch">
  684. <input id="uhp-conf-rth" type="checkbox">
  685. <label for="uhp-conf-rth"></label>
  686. </div>
  687. <span id="uhp-conf-rth-title">Remove Tag Hidden</span>
  688. <span id="uhp-conf-rth-desc">Remove search results which tagged with hidden when "Tag Flagging" work.</span>
  689.  
  690. <div class="material-switch">
  691. <input id="uhp-conf-sr" type="checkbox">
  692. <label for="uhp-conf-sr"></label>
  693. </div>
  694. <span id="uhp-conf-sr-title">Scroll Restoration</span>
  695. <span id="uhp-conf-sr-desc">Scroll last position you seen in last page when "Paging Enlargement" work.</span>
  696.  
  697. <div class="material-switch">
  698. <input id="uhp-conf-pi" type="checkbox">
  699. <label for="uhp-conf-pi"></label>
  700. </div>
  701. <span id="uhp-conf-pi-title">Page Indicator</span>
  702. <span id="uhp-conf-pi-desc">Add page indicator link to prevent "Scroll Restoration" work too hard.</span>
  703.  
  704. <div class="material-switch">
  705. <input id="uhp-conf-tpf" type="checkbox">
  706. <label for="uhp-conf-tpf"></label>
  707. </div>
  708. <span id="uhp-conf-tpf-title">Thumb Page Flow</span>
  709. <span id="uhp-conf-tpf-desc">Make dense flow when "Page Indicator" work.<br/>Only affect on thumb display mode.</span>
  710. </div>
  711. `;
  712.  
  713. var uhpTagFlaggingHTML = `
  714. <h1 class="uhp-tf-options ${uhpConfig.tf ? '' : 'hidden'}">Tag Flagging</h1>
  715. <div class="uhp-tf-options tf-option-grid ${uhpConfig.tf ? '' : 'hidden'}">
  716. <div class="tfl red"></div>
  717. <input id="uhp-tf-red" pattern="(\\w(?:[^:]|[\\w\\s])+)(?:,\\s*\\1)*" value="${uhpConfig.flaggingTags.red.tags.join(', ')}" placeholder="e.g. touhou, flandre scarlet"/>
  718. <div class="material-switch">
  719. <input id="uhp-tf-red-hide" type="checkbox">
  720. <label for="uhp-tf-red-hide"></label>
  721. </div>
  722.  
  723. <div class="tfl green"></div>
  724. <input id="uhp-tf-green" pattern="(\\w(?:[^:]|[\\w\\s])+)(?:,\\s*\\1)*" value="${uhpConfig.flaggingTags.green.tags.join(', ')}"/>
  725. <div class="material-switch">
  726. <input id="uhp-tf-green-hide" type="checkbox">
  727. <label for="uhp-tf-green-hide"></label>
  728. </div>
  729.  
  730. <div class="tfl brown"></div>
  731. <input id="uhp-tf-brown" pattern="(\\w(?:[^:]|[\\w\\s])+)(?:,\\s*\\1)*" value="${uhpConfig.flaggingTags.brown.tags.join(', ')}"/>
  732. <div class="material-switch">
  733. <input id="uhp-tf-brown-hide" type="checkbox">
  734. <label for="uhp-tf-brown-hide"></label>
  735. </div>
  736.  
  737. <div class="tfl blue"></div>
  738. <input id="uhp-tf-blue" pattern="(\\w(?:[^:]|[\\w\\s])+)(?:,\\s*\\1)*" value="${uhpConfig.flaggingTags.blue.tags.join(', ')}"/>
  739. <div class="material-switch">
  740. <input id="uhp-tf-blue-hide" type="checkbox">
  741. <label for="uhp-tf-blue-hide"></label>
  742. </div>
  743.  
  744. <div class="tfl yellow"></div>
  745. <input id="uhp-tf-yellow" pattern="(\\w(?:[^:]|[\\w\\s])+)(?:,\\s*\\1)*" value="${uhpConfig.flaggingTags.yellow.tags.join(', ')}"/>
  746. <div class="material-switch">
  747. <input id="uhp-tf-yellow-hide" type="checkbox">
  748. <label for="uhp-tf-yellow-hide"></label>
  749. </div>
  750.  
  751. <div class="tfl purple"></div>
  752. <input id="uhp-tf-purple" pattern="(\\w(?:[^:]|[\\w\\s])+)(?:,\\s*\\1)*" value="${uhpConfig.flaggingTags.purple.tags.join(', ')}"/>
  753. <div class="material-switch">
  754. <input id="uhp-tf-purple-hide" type="checkbox">
  755. <label for="uhp-tf-purple-hide"></label>
  756. </div>
  757. </div>
  758. `;
  759.  
  760. var cssText = `
  761. #uhp-btn {
  762. cursor: pointer;
  763. }
  764. #uhp-panel-container {
  765. position:fixed;
  766. top: 0;
  767. height: 100vh;
  768. width: 100vw;
  769. background-color: rgba(200, 200, 200, 0.7);
  770. z-index: 2;
  771. display: flex;
  772. align-items: center;
  773. justify-content: center;
  774. }
  775. #uhp-panel-container.hidden {
  776. visibility: hidden;
  777. opacity: 0;
  778. }
  779. #uhp-panel {
  780. padding: 1.2rem;
  781. background-color: floralwhite;
  782. border-radius: 1rem;
  783. font-size: 1rem;
  784. color: darkred;
  785. max-width: 650px;
  786. }
  787. #uhp-panel.dark {
  788. background-color: dimgray;
  789. color: ghostwhite;
  790. }
  791. #uhp-panel > .option-grid {
  792. display: grid;
  793. grid-template-columns: max-content max-content 1fr;
  794. grid-gap: 0.5rem 1rem;
  795. }
  796. #uhp-panel > .tf-option-grid {
  797. display: grid;
  798. grid-template-columns: 20px 1fr max-content;
  799. grid-gap: 0.5rem 1rem;
  800. }
  801. #uhp-panel > .option-grid > *,
  802. #uhp-panel > .tf-option-grid > * {
  803. display: flex;
  804. justify-content: center;
  805. align-items: center;
  806. }
  807. #uhp-panel > .tf-option-grid > .tfl {
  808. margin: auto;
  809. }
  810. #uhp-panel > .uhp-tf-options.hidden {
  811. display: none;
  812. }
  813. #uhp-full-width-container.fullwidth,
  814. #uhp-full-width-container.fullwidth div.itg {
  815. max-width: none;
  816. }
  817. #uhp-full-width-container div.itg {
  818. display: grid;
  819. grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  820. grid-gap: 2px;
  821. }
  822. #uhp-full-width-container div.itg.uhp-tpf-dense {
  823. grid-auto-flow: dense;
  824. }
  825. #uhp-full-width-container div.id1 {
  826. height: 345px;
  827. float: none;
  828. display: flex;
  829. flex-direction: column;
  830. margin: 3px auto;
  831. padding: 4px 0;
  832. }
  833. #uhp-full-width-container div.id2 {
  834. overflow: visible;
  835. height: initial;
  836. margin: 4px auto;
  837. }
  838. #uhp-full-width-container div.id3 {
  839. flex: 1;
  840. display: flex;
  841. justify-content: center;
  842. align-items: center;
  843. }
  844. .uhp-list-parent-eh tr:nth-of-type(2n+1){
  845. background-color: #EDEBDF;
  846. }
  847. .uhp-list-parent-eh tr:nth-of-type(2n+2){
  848. background-color: #F2F0E4;
  849. }
  850. .uhp-list-parent-exh tr:nth-of-type(2n+1) {
  851. background-color: #363940;
  852. }
  853. .uhp-list-parent-exh tr:nth-of-type(2n+2){
  854. background-color: #4F535B;
  855. }
  856. #uhp-status {
  857. text-align: center;
  858. font-size: 3rem;
  859. clear: both;
  860. padding: 2rem 0;
  861. }
  862. /* replace */
  863. div#pp,
  864. div#gdt.uhp-page-parent {
  865. display: flex;
  866. flex-wrap: wrap;
  867. }
  868. div#gdt.uhp-page-parent>div{
  869. float: initial;
  870. }
  871. div.it4t {
  872. width: 102px;
  873. }
  874. div.tfl.red,
  875. div.tft.red {
  876. background-position: 0 -1px;
  877. }
  878. div.tfl.green,
  879. div.tft.green {
  880. background-position: 0px -52px;
  881. }
  882. div.tfl.brown,
  883. div.tft.brown {
  884. background-position: 0px -18px;
  885. }
  886. div.tfl.blue,
  887. div.tft.blue {
  888. background-position: 0px -69px;
  889. }
  890. div.tfl.yellow,
  891. div.tft.yellow {
  892. background-position: 0px -35px;
  893. }
  894. div.tfl.purple,
  895. div.tft.purple {
  896. background-position: 0px -86px;
  897. }`;
  898.  
  899. /* https://bootsnipp.com/snippets/featured/material-design-switch */
  900. var materialCSS = `
  901. .material-switch {
  902. display: inline-block;
  903. }
  904.  
  905. .material-switch > input[type="checkbox"] {
  906. display: none;
  907. }
  908.  
  909. .material-switch > input[type="checkbox"] + label {
  910. display: inline-block;
  911. position: relative;
  912. margin: 6px;
  913. border-radius: 8px;
  914. width: 40px;
  915. height: 16px;
  916. opacity: 0.3;
  917. background-color: rgb(0, 0, 0);
  918. box-shadow: inset 0px 0px 10px rgba(0, 0, 0, 0.5);
  919. transition: all 0.4s ease-in-out;
  920. }
  921.  
  922. .material-switch > input[type="checkbox"] + label::after {
  923. position: absolute;
  924. top: -4px;
  925. left: -4px;
  926. border-radius: 16px;
  927. width: 24px;
  928. height: 24px;
  929. content: "";
  930. background-color: rgb(255, 255, 255);
  931. box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.3);
  932. transition: all 0.3s ease-in-out;
  933. }
  934.  
  935. .material-switch > input[type="checkbox"]:checked + label {
  936. background-color: #0e0;
  937. opacity: 0.7;
  938. }
  939.  
  940. .material-switch > input[type="checkbox"]:checked + label::after {
  941. background-color: inherit;
  942. left: 20px;
  943. }
  944. .material-switch > input[type="checkbox"]:disabled + label::after {
  945. content: "\\f023";
  946. line-height: 24px;
  947. font-size: 0.8em;
  948. font-family: FontAwesome;
  949. color: initial;
  950. }`;