Unlock Hath Perks

Unlock Hath Perks and add other helpers

От 23.10.2017. Виж последната версия.

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