Image generation: fix small image on mobile, novelai.net

Improves the mobile layout with full screen UX

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        Image generation: fix small image on mobile, novelai.net
// @namespace   Violentmonkey Scripts
// @match       https://novelai.net/*
// @grant       none
// @version     1.3
// @author      zackline
// @description Improves the mobile layout with full screen UX
// @license MIT
// ==/UserScript==

const PROMPT_INPUT_ID = "prompt-input-0";

function setNativeValue(element, value) {
  const valueSetter = Object.getOwnPropertyDescriptor(element, "value").set;
  const prototype = Object.getPrototypeOf(element);
  const prototypeValueSetter = Object.getOwnPropertyDescriptor(
    prototype,
    "value"
  ).set;

  if (valueSetter && valueSetter !== prototypeValueSetter) {
    prototypeValueSetter.call(element, value);
  } else {
    valueSetter.call(element, value);
  }

  element.dispatchEvent(new Event("input", { bubbles: true }));
}

function createNewInput() {
  const input = document.getElementById(PROMPT_INPUT_ID);
  const inputCopy = input.cloneNode(true);

  // On change of input copy, copy value to original input via change event
  inputCopy.onchange = function () {
    setNativeValue(input, inputCopy.value);
  };
  return inputCopy;
}

function createNewGenerateButton() {
  const generateButton = Array.from(
    document.getElementsByTagName("button")
  ).filter((button) => button.innerText.includes("Generate"))[0];
  const generateButtonCopy = generateButton.cloneNode(true);
  generateButtonCopy.id = "copied-generate-button";

  // On click trigger click on generate button
  generateButtonCopy.addEventListener("click", () => {
    // Replace text with loading spinner
    generateButtonCopy.children[0].innerText = "...";
    generateButton.click();
  });
  return generateButtonCopy;
}

function insertNewElements(originalImage) {
  // Check if there is no element with id 'copied-image'
  if (!document.getElementById("copied-image")) {
    const newImg = document.createElement("img");
    newImg.id = "copied-image";
    newImg.src = originalImage.src;
    newImg.alt = originalImage.alt;
    // 100vw minus scrollbar width to avoid horizontal scroll
    newImg.style.width = "calc(100vw - (100vw - 100%))";
    document.getElementById("__next").before(newImg);
    originalImage.style.display = "none";

    const inputCopy = createNewInput();
    inputCopy.style.margin = "4px";
    inputCopy.style.width = "calc(100vw - (100vw - 100%) - 8px)";
    newImg.after(inputCopy);

    const buttonsContainer = document.createElement("div");
    buttonsContainer.style.display = "flex";
    buttonsContainer.style.gap = "8px";
    buttonsContainer.style.margin = "4px 4px 64px 4px";

    inputCopy.after(buttonsContainer);
    const generateButtonCopy = createNewGenerateButton();
    buttonsContainer.appendChild(generateButtonCopy);

    const seedButton =
      originalImage.nextElementSibling.nextElementSibling.children[0];
    seedButton.querySelector("button").id = "original-seed-button";
    const seedButtonCopy = seedButton.cloneNode(true);
    seedButtonCopy.addEventListener("click", () => {
      seedButton.querySelector("button").click();
    });
    seedButtonCopy.querySelector("button").id = "copied-seed-button";

    buttonsContainer.appendChild(seedButtonCopy);
    const saveButtonsContainer = seedButton.nextElementSibling;
    const saveButtonsContainerCopy = saveButtonsContainer.cloneNode(true);
    saveButtonsContainerCopy.id = "copied-save-buttons-container";
    buttonsContainer.appendChild(saveButtonsContainerCopy);
    // Fix save buttons click handlers
    const originalSaveButtons = Array.from(
      saveButtonsContainer.querySelectorAll("button")
    );
    document
      .getElementById("copied-save-buttons-container")
      .querySelectorAll("button")
      .forEach((button, i) => {
        // Click original button
        button.addEventListener("click", () => {
          originalSaveButtons[i].click();
        });
      });
  }
}

function findOriginalImage() {
  // Find img with src starting with 'blob'
  const imgs = Array.from(document.getElementsByTagName("img")).filter(
    (img) => img.src.startsWith("blob") && img.alt.length > 0
  );
  if (imgs.length > 0) {
    return imgs.length > 1 ? imgs[1] : imgs[0];
  }
}

let scriptInfoPlaced = false;

const placeScriptInfo = () => {
  // Find path that starts with 'M58'
  const paths = Array.from(document.getElementsByTagName("path")).filter(
    (path) => path.getAttribute("d").startsWith("M58")
  );
  if (paths.length > 0) {
    const placeholderImage = paths[0].parentElement;
    if (placeholderImage.tagName === "svg") {
      // Insert span with script info
      const scriptInfo = document.createElement("span");
      scriptInfo.innerText = `Size fix v${GM_info?.script?.version}`;
      placeholderImage.replaceWith(scriptInfo);
    }
  }
};

const interval = setInterval(function () {
  if (document.getElementById(PROMPT_INPUT_ID)) {
    if (!scriptInfoPlaced) {
      placeScriptInfo();
      scriptInfoPlaced = true;
    }

    const img = findOriginalImage();
    if (img) {
      if (!document.getElementById("copied-image")) {
        insertNewElements(img);
      } else {
        // Update new image with the latest original image source
        if (document.getElementById("copied-image").src !== img.src) {
          document.getElementById("copied-image").src = img.src;
          const copiedGenerateButton = document.getElementById(
            "copied-generate-button"
          );
          if (copiedGenerateButton) {
            copiedGenerateButton.children[0].innerText = "Generate";
          }

          // Update copied seed button value
          const copiedSeedButton =
            document.getElementById("copied-seed-button");
          if (copiedSeedButton) {
            copiedSeedButton.innerText = document.getElementById(
              "original-seed-button"
            ).innerText;
          }
        }
      }
    }
  }
}, 500);