nhentai Navigation Improvements

Clone search page navigation on top for mobile and options for faster tag filtering in top bar

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        nhentai Navigation Improvements
// @description Clone search page navigation on top for mobile and options for faster tag filtering in top bar
// @namespace   xspeed.net
// @license     MIT
// @version     4
// @icon        https://nhentai.net/favicon.ico
// @match       *://nhentai.net/*
// @grant       none
// ==/UserScript==

"use strict";

function showTextPrompt(msg) {
    const text = document.createElement("p");
    text.innerText = msg;

    const input = document.createElement("input");
    input.type = "search";

    const link = document.createElement("i");
    link.className = "fa fa-paste";

    const button = document.createElement("button");
    button.className = "btn btn-primary btn-square";
    button.type = "submit";
    button.appendChild(link);

    const form = document.createElement("form");
    form.className = "search";
    form.role = "search";
    form.method = "dialog";
    form.appendChild(input);
    form.appendChild(button);

    const dialog = document.createElement("dialog");
    dialog.appendChild(text);
    dialog.appendChild(form);

    const style = getComputedStyle(document.body);
    dialog.style.backgroundColor = style.getPropertyValue("background-color");
    dialog.style.color = style.getPropertyValue("color");

    document.body.appendChild(dialog);
    dialog.showModal();

    return new Promise((resolve, reject) => form.addEventListener('submit', () => {
        resolve(input.value);
        dialog.remove();
    }, { once: true }));
}

function stopEvent(event) {
    event.stopImmediatePropagation();
    event.preventDefault();
}

async function onChangeTags(event, tags) {
    stopEvent(event);

    const input = document.querySelector("input[type=search]");
    const str = tags ?? await showTextPrompt("Specify tags, separate by commas");

    if (str) {
        str.split(',').map(x => x.trim()).filter(x => x).forEach(x => adjustSearch("tag", x, false));

        document.querySelector("button[type=submit]").click();
    }
}

function adjustSearch(type, item, add) {
    const input = document.querySelector("input[type=search]");
    item = wrap(item);
    const act = " " + (add ? item : `-${type}:${item}`);
    const inv = " " + (add ? `-${type}:${item}` : item);

    if (input.value.indexOf(inv) != -1) input.value = input.value.replace(inv, act);
    else if (input.value.indexOf(act) == -1) input.value += act;

    input.value = input.value.replaceAll('  ', ' ');
    sessionStorage.setItem("lastSearch", input.value);
}

function wrap(txt) {
    return txt.indexOf(" ") == -1 ? txt : `"${txt}"`;
}

function clearSearch(event) {
    stopEvent(event);

    document.querySelector("input[type=search]").value = "";
    sessionStorage.removeItem("lastSearch");
}

function setupBtn(elem) {
    if (!elem) return;

    let item = elem.firstChild.cloneNode(true);

    let link = item.firstChild;
    link.href = "#";
    link.innerText = "Block default";
    link.addEventListener("click", e => onChangeTags(e, "bbm,netorare,vore,scat,guro"));

    elem.insertBefore(item, elem.firstChild);

    item = elem.firstChild.cloneNode(true);

    link = item.firstChild;
    link.href = "#";
    link.innerText = "Block tags";
    link.addEventListener("click", onChangeTags);

    elem.insertBefore(item, elem.firstChild);

    item = elem.firstChild.cloneNode(true);

    link = item.firstChild;
    link.href = "#";
    link.innerText = "Clear search";
    link.addEventListener("click", e => clearSearch(e));

    elem.insertBefore(item, elem.firstChild);
}

function onTagClick(event, elem, add) {
    stopEvent(event);

    const data = elem.pathname.split("/").filter(x => x);
    adjustSearch(data[0], data[1].replaceAll("-", " "), add);
}

function setupTag(elem) {
    if (!elem.querySelector(".count")) return document.createTextNode("");

    const container = document.createElement("span");
    container.addEventListener("click", e => stopEvent(e));
    container.className = "name";

    const add = document.createElement("a");
    add.href = "#";
    add.addEventListener("click", e => onTagClick(e, elem, true));
    add.className = "fa fa-plus";
    add.style.padding = "0.25em 0em";
    add.style.marginRight = "0.4em";

    const del = document.createElement("a");
    del.href = "#";
    del.addEventListener("click", e => onTagClick(e, elem));
    del.className = "fa fa-minus";
    del.style.padding = "0.25em 0em";

    container.appendChild(add);
    container.appendChild(del);
    return container;
}

(function() {
    const content = document.getElementById("content");
    const pagination = document.querySelector("section.pagination");
    const input = document.querySelector("input[type=search]");

    if (content && pagination) {
        const clone = pagination.cloneNode(true);

        const spacer = clone.querySelector(".ios-mobile-webkit-bottom-spacing");
        if (spacer) spacer.remove();

        content.insertBefore(clone, content.firstChild);
    }

    if (!input.value) {
        input.value = sessionStorage.getItem("lastSearch") ?? "english ";
    }
    else if (input.value.trim() != "english") {
        sessionStorage.setItem("lastSearch", input.value);
    }

    setupBtn(document.querySelector("ul.menu"));
    setupBtn(document.querySelector("ul.dropdown-menu"));

    document.querySelectorAll(".tags>.tag").forEach(x => x.appendChild(setupTag(x)));
})();