Nejire Refine

ねじれ天国のUIを使いやすくするスクリプトです。

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Nejire Refine
// @namespace    http://nejiten.halfmoon.jp/
// @version      1.0.0
// @description  ねじれ天国のUIを使いやすくするスクリプトです。
// @author       euro_s
// @match        https://nejiten.halfmoon.jp/index.cgi?vid=*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=halfmoon.jp
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function () {
  "use strict";

  ////////////////////////////////////////////////////////////////////////////////
  // 設定
  ////////////////////////////////////////////////////////////////////////////////

  // サイドバーの表示(デフォルト非表示)
  let sideBarDisplay = GM_getValue("sideBarDisplay", "none");
  // 表示の場合の値
  // let sideBarDisplay = 'table-cell';

  // ページリンクの表示(デフォルト表示)
  let alllogAnnounceDisplay = GM_getValue("alllogAnnounceDisplay", "block");
  // 非表示の場合の値
  // let alllogAnnounceDisplay = 'none';

  // テキストエリアのフォントサイズ(デフォルト1.5)
  let textareaFontSize = GM_getValue("textareaFontSize", 1.5);

  // メッセージの最大行数 これを超えると省略される(デフォルト20)
  let maxBrTags = GM_getValue("maxBrTags", 20);

  // メッセージの最大文字数 これを超えると省略される(デフォルト800)
  let maxTextLength = GM_getValue("maxTextLength", 800);

  // 発言に含まれる画像の最大数 これを超えると削除される(デフォルト10)
  let maxImgCount = GM_getValue("maxImgCount", 10);

  // 連投制限(デフォルトで省略=truncate, 削除=delete, 何もしない=none)
  let spamOption = GM_getValue("spamOption", "truncate");

  // ページ右上に移動ボタンを表示するか(デフォルト非表示)
  let showScrollButton = GM_getValue("showScrollButton", false);

  // ページ右下に検索ボタンを表示するか(デフォルト非表示)
  let showSearchButton = GM_getValue("showSearchButton", false);

  ////////////////////////////////////////////////////////////////////////////////
  // メタ情報
  ////////////////////////////////////////////////////////////////////////////////

  let vid = 0;
  let date = 0;
  let idAndNames = new Map();

  function getMetadata() {
    const url = location.href;
    const vidMatch = url.match(/vid=(\d+)/);
    if (vidMatch) {
      vid = vidMatch[1];
    }
    const dateMatch = url.match(/date=(\d+)/);
    if (dateMatch) {
      date = dateMatch[1];
    } else {
      const str = document.querySelector("span.today").innerText;
      const dateMatch = str.match(/(\d+)日目/);
      if (dateMatch) {
        date = dateMatch[1];
      }
    }
    const list = document.getElementById("list");
    const aTags = list.getElementsByTagName("a");
    for (let i = 0; i < aTags.length; i++) {
      const aTag = aTags[i];
      const href = aTag.href;
      const idMatch = href.match(/&id=(\d+)/);
      if (idMatch) {
        const id = idMatch[1];
        const name = aTag.innerText;
        idAndNames.set(id, name);
      }
    }
  }

  ////////////////////////////////////////////////////////////////////////////////
  // メイン
  ////////////////////////////////////////////////////////////////////////////////

  window.addEventListener("DOMContentLoaded", async (event) => {
    getMetadata();
    createTopButtonContainer();
    createBottomButtonContainer();
    disableDoubleSubmit();
    createSettingDialog();
    upDownButtons();
    new MutationObserver(processMessage).observe(
      document.querySelector("#content"),
      { childList: true }
    );
    processMessage();
    new MutationObserver(processAnnounce).observe(
      document.querySelector("#content"),
      { childList: true }
    );
    processAnnounce();
    await addCopyAnchorEvent();
    disableLeaveButtonAndAddCheckbox();
    pagination();
    setAnchorToggle();
    addSearchIcon();
    applySettings();
  });

  ////////////////////////////////////////////////////////////////////////////////
  // 各種処理
  ////////////////////////////////////////////////////////////////////////////////

  // 設定の反映
  function applySettings() {
    // サイドバーの表示
    const sideBars = document.querySelectorAll(
      "body > table > tbody > tr > td > table > tbody > tr > td > table > tbody > tr > td:nth-child(1)"
    );
    sideBars.forEach((sideBar) => {
      sideBar.style.display = sideBarDisplay;
    });

    // ページリンクの表示
    const alllogAnnounces = document.querySelectorAll(".alllog_announce");
    alllogAnnounces.forEach((alllogAnnounce) => {
      alllogAnnounce.style.display = alllogAnnounceDisplay;
    });

    // 発言欄のフォントサイズ
    const textarea = document.querySelectorAll("textarea");
    textarea.forEach((ta) => {
      ta.style.fontSize = `${textareaFontSize}rem`;
    });

    // 上下移動ボタンの表示
    const buttonUp = document.getElementById("scrollToTopButton");
    const buttonDown = document.getElementById("scrollToBottomButton");
    if (showScrollButton) {
      buttonUp.style.display = "block";
      buttonDown.style.display = "block";
    } else {
      buttonUp.style.display = "none";
      buttonDown.style.display = "none";
    }

    // 検索ボタンの表示
    const searchButton = document.getElementById("searchIcon");
    if (showSearchButton) {
      searchButton.style.display = "block";
    } else {
      searchButton.style.display = "none";
    }
  }

  // 設定ボタンを作成
  function createSettingDialog() {
    const btn = document.createElement("button");
    btn.type = "button";
    btn.title = "設定";
    btn.id = "settingButton";
    // 歯車アイコンのSVGをBase64でエンコードしたもの 白背景用
    btn.innerHTML = `
    <img src="data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI1cHgiIGhlaWdodD0iMjVweCIgdmlld0JveD0iMCAwIDEyODAuMDAwMDAwIDEyODAuMDAwMDAwIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCBtZWV0Ij4KPG1ldGFkYXRhPgpDcmVhdGVkIGJ5IHBvdHJhY2UgMS4xNSwgd3JpdHRlbiBieSBQZXRlciBTZWxpbmdlciAyMDAxLTIwMTcKPC9tZXRhZGF0YT4KPHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0id2hpdGUiLz4KPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMC4wMDAwMDAsMTI4MC4wMDAwMDApIHNjYWxlKDAuMTAwMDAwLC0wLjEwMDAwMCkiIGZpbGw9IiMwMDAwMDAiIHN0cm9rZT0ibm9uZSI+CjxwYXRoIGQ9Ik02MDMzIDEyNzkwIGMtNDA5IC0yNyAtOTEzIC0xMDQgLTk3MyAtMTQ5IC02MSAtNDQgLTYwIC0zNiAtNjAgLTUxOQpsLTEgLTQ0MyAtMjIgLTkgYy0xMiAtNCAtOTggLTMxIC0xOTIgLTYwIC0xOTAgLTU4IC0zODMgLTEyOCAtNTU1IC0yMDMgLTEzNgotNTggLTQyMCAtMTk5IC01MDUgLTI1MCBsLTYwIC0zNiAtMzEwIDMwOCBjLTE3MCAxNzAgLTMyMCAzMTQgLTMzMiAzMjAgLTMwCjE1IC05NyAxNCAtMTM2IC0zIC00MSAtMTcgLTI1NiAtMTcxIC00MTIgLTI5NSAtMzk0IC0zMTIgLTgxNyAtNzM1IC0xMTI2Ci0xMTI2IC0xODYgLTIzNSAtMjk3IC0zOTcgLTMwOSAtNDUwIC0xOCAtNzggLTEwIC04OCAzMjUgLTQyNSBsMzE0IC0zMTUgLTM2Ci02MCBjLTUxIC04NiAtMTkyIC0zNzAgLTI1MCAtNTA1IC03NSAtMTcyIC0xNDUgLTM2NSAtMjAzIC01NTUgLTI5IC05MyAtNTYKLTE4MCAtNjAgLTE5MiBsLTkgLTIyIC00NDMgLTEgYy00ODUgMCAtNDc0IDEgLTUyMSAtNjIgLTM0IC00NiAtOTggLTQyNiAtMTM0Ci03OTMgLTI0IC0yNTIgLTI0IC04MzggMCAtMTA5MCAzNiAtMzY3IDEwMCAtNzQ4IDEzNSAtNzk0IDQ2IC02MiAzNiAtNjEgNTIwCi02MSBsNDQzIC0xIDkgLTIyIGM0IC0xMiAzMSAtOTggNjAgLTE5MiA1OCAtMTkwIDEyOCAtMzgzIDIwMyAtNTU1IDU4IC0xMzYKMTk5IC00MjAgMjUwIC01MDUgbDM2IC02MCAtMzA4IC0zMTAgYy0xNzAgLTE3MCAtMzE0IC0zMjAgLTMyMCAtMzMyIC0xNSAtMzAKLTE0IC05NyAzIC0xMzYgMTcgLTQxIDE3MSAtMjU2IDI5NSAtNDEyIDMxMiAtMzk0IDczNSAtODE3IDExMjYgLTExMjYgMjM1Ci0xODYgMzk3IC0yOTcgNDUwIC0zMDkgNzggLTE4IDg4IC0xMCA0MjUgMzI1IGwzMTUgMzE0IDYwIC0zNiBjODYgLTUxIDM3MAotMTkyIDUwNSAtMjUwIDE3MiAtNzUgMzY1IC0xNDUgNTU1IC0yMDMgOTQgLTI5IDE4MCAtNTYgMTkyIC02MCBsMjIgLTkgMQotNDQzIGMwIC00ODUgLTEgLTQ3NCA2MiAtNTIxIDQ2IC0zNCA0MjYgLTk4IDc5MyAtMTM0IDI1MiAtMjQgODM4IC0yNCAxMDkwIDAKMzY3IDM2IDc0OCAxMDAgNzk0IDEzNSA2MiA0NiA2MSAzNiA2MSA1MjAgbDEgNDQzIDIyIDkgYzEyIDQgOTkgMzEgMTkyIDYwCjE5MCA1OCAzODMgMTI4IDU1NSAyMDMgMTM2IDU4IDQyMCAxOTkgNTA1IDI1MCBsNjAgMzYgMzEwIC0zMDggYzE3MSAtMTcwIDMyMAotMzE0IDMzMiAtMzIwIDMwIC0xNSA5NyAtMTQgMTM2IDMgNDEgMTcgMjU2IDE3MSA0MTIgMjk1IDM5NCAzMTIgODE3IDczNQoxMTI2IDExMjYgMTg2IDIzNSAyOTcgMzk3IDMwOSA0NTAgMTggNzggMTAgODggLTMyNSA0MjUgbC0zMTQgMzE1IDM2IDYwIGM1MQo4NiAxOTIgMzcwIDI1MCA1MDUgNzUgMTcyIDE0NSAzNjUgMjAzIDU1NSAyOSA5NCA1NiAxODAgNjAgMTkyIGw5IDIyIDQ0MyAxCmM0ODUgMCA0NzQgLTEgNTIxIDYyIDM0IDQ2IDk4IDQyNiAxMzQgNzkzIDI0IDI1MiAyNCA4MzggMCAxMDkwIC0zNiAzNjcgLTEwMAo3NDggLTEzNSA3OTQgLTQ2IDYyIC0zNiA2MSAtNTIwIDYxIGwtNDQzIDEgLTkgMjIgYy00IDEyIC0zMSA5OSAtNjAgMTkyIC01OAoxOTAgLTEyOCAzODMgLTIwMyA1NTUgLTU4IDEzNiAtMTk5IDQyMCAtMjUwIDUwNSBsLTM2IDYwIDMwOCAzMTAgYzE3MCAxNzEKMzE0IDMyMCAzMjAgMzMyIDE1IDMwIDE0IDk3IC0zIDEzNiAtMTcgNDEgLTE3MSAyNTYgLTI5NSA0MTIgLTMxMiAzOTQgLTczNQo4MTcgLTExMjYgMTEyNiAtMjM1IDE4NiAtMzk3IDI5NyAtNDUwIDMwOSAtNzggMTggLTg4IDEwIC00MjUgLTMyNSBsLTMxNQotMzE0IC02MCAzNiBjLTg2IDUxIC0zNzAgMTkyIC01MDUgMjUwIC0xNzIgNzUgLTM2NSAxNDUgLTU1NSAyMDMgLTkzIDI5IC0xODAKNTYgLTE5MiA2MCBsLTIyIDkgLTEgNDQzIGMwIDQ4NSAxIDQ3NCAtNjIgNTIxIC00NiAzNCAtNDMyIDk5IC03ODMgMTMyIC0yMDAKMTkgLTczMyAyNyAtOTIyIDE1eiBtNjYwIC0zNDQxIGMxMDE3IC05MiAxOTM1IC03MzcgMjM4MyAtMTY3NCAxNzAgLTM1NSAyNjEKLTcxNSAyODMgLTExMTggNDEgLTczOSAtMTk0IC0xNDU3IC02NjggLTIwMzcgLTQ3MSAtNTc4IC0xMTQ1IC05NTMgLTE5MDEKLTEwNjAgLTIwOCAtMjkgLTU3MiAtMjkgLTc4MCAwIC02NjAgOTMgLTEyNDIgMzgyIC0xNzA1IDg0NSAtNDYzIDQ2MyAtNzUyCjEwNDUgLTg0NSAxNzA1IC0yOSAyMDggLTI5IDU3MiAwIDc4MCAxMDcgNzU2IDQ4MiAxNDMwIDEwNjAgMTkwMSA2MTkgNTA1CjEzNjQgNzMxIDIxNzMgNjU4eiIvPgo8L2c+Cjwvc3ZnPgo="/>
    `;
    // ボタンのクリックイベントリスナーを設定
    btn.addEventListener("click", function () {
      // ボタンがクリックされたときにポップアップウィンドウを表示
      dialog.showModal();
    });

    // ボタンを挿入する位置
    const action_box = document.querySelector(".action_box");
    const entryForm = document.querySelector("form[name=entryForm]");
    let target;
    if (action_box && !entryForm) {
      target = action_box.querySelector(
        "td.action_body" // 発言欄の名前の右
      );
      // replace innerHTML
      target.innerHTML = target.innerHTML.replace('</span>', '</span></div>');
      target.innerHTML = `<div>${target.innerHTML}`;
      target.classList.add("container");
      const span = document.createElement("span");
      span.classList.add("right");
      btn.classList.add("with_action_box");
      // ボタンを追加
      span.appendChild(btn);
      target.appendChild(span);

      // hopeForm の位置を変更
      const hopeForm = document.querySelector("form[name=hopeForm]");
      const boxHope = document.querySelector("#box_hope");
      if (hopeForm && boxHope) {
        boxHope.parentElement.appendChild(hopeForm);
        boxHope.parentElement.removeChild(boxHope);
        hopeForm.appendChild(boxHope);
      }
    } else {
      // ログアウト中などで発言欄がない場合、または入村前の場合
      // aタグ、title=にゃおーんの要素を取得してそこに追加
      target = document.querySelector('a[title="にゃおーん"]');
      // 歯車アイコンのSVGをBase64でエンコードしたもの 黒背景用
      btn.innerHTML = `
      <img src="data:image/svg+xml;base64,ICAgIDxzdmcgdmVyc2lvbj0iMS4wIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgICB3aWR0aD0iMjVweCIgaGVpZ2h0PSIyNXB4IiB2aWV3Qm94PSIwIDAgMTI4MC4wMDAwMDAgMTI4MC4wMDAwMDAiCiAgICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCBtZWV0Ij4KICAgIDx0aXRsZT5TZXR0aW5nIEljb248L3RpdGxlPgogICAgPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMC4wMDAwMDAsMTI4MC4wMDAwMDApIHNjYWxlKDAuMTAwMDAwLC0wLjEwMDAwMCkiCiAgICBmaWxsPSIjRkZGRkZGIiBzdHJva2U9Im5vbmUiPgogICAgPHBhdGggZD0iTTYwMzMgMTI3OTAgYy00MDkgLTI3IC05MTMgLTEwNCAtOTczIC0xNDkgLTYxIC00NCAtNjAgLTM2IC02MCAtNTE5CiAgICBsLTEgLTQ0MyAtMjIgLTkgYy0xMiAtNCAtOTggLTMxIC0xOTIgLTYwIC0xOTAgLTU4IC0zODMgLTEyOCAtNTU1IC0yMDMgLTEzNgogICAgLTU4IC00MjAgLTE5OSAtNTA1IC0yNTAgbC02MCAtMzYgLTMxMCAzMDggYy0xNzAgMTcwIC0zMjAgMzE0IC0zMzIgMzIwIC0zMAogICAgMTUgLTk3IDE0IC0xMzYgLTMgLTQxIC0xNyAtMjU2IC0xNzEgLTQxMiAtMjk1IC0zOTQgLTMxMiAtODE3IC03MzUgLTExMjYKICAgIC0xMTI2IC0xODYgLTIzNSAtMjk3IC0zOTcgLTMwOSAtNDUwIC0xOCAtNzggLTEwIC04OCAzMjUgLTQyNSBsMzE0IC0zMTUgLTM2CiAgICAtNjAgYy01MSAtODYgLTE5MiAtMzcwIC0yNTAgLTUwNSAtNzUgLTE3MiAtMTQ1IC0zNjUgLTIwMyAtNTU1IC0yOSAtOTMgLTU2CiAgICAtMTgwIC02MCAtMTkyIGwtOSAtMjIgLTQ0MyAtMSBjLTQ4NSAwIC00NzQgMSAtNTIxIC02MiAtMzQgLTQ2IC05OCAtNDI2IC0xMzQKICAgIC03OTMgLTI0IC0yNTIgLTI0IC04MzggMCAtMTA5MCAzNiAtMzY3IDEwMCAtNzQ4IDEzNSAtNzk0IDQ2IC02MiAzNiAtNjEgNTIwCiAgICAtNjEgbDQ0MyAtMSA5IC0yMiBjNCAtMTIgMzEgLTk4IDYwIC0xOTIgNTggLTE5MCAxMjggLTM4MyAyMDMgLTU1NSA1OCAtMTM2CiAgICAxOTkgLTQyMCAyNTAgLTUwNSBsMzYgLTYwIC0zMDggLTMxMCBjLTE3MCAtMTcwIC0zMTQgLTMyMCAtMzIwIC0zMzIgLTE1IC0zMAogICAgLTE0IC05NyAzIC0xMzYgMTcgLTQxIDE3MSAtMjU2IDI5NSAtNDEyIDMxMiAtMzk0IDczNSAtODE3IDExMjYgLTExMjYgMjM1CiAgICAtMTg2IDM5NyAtMjk3IDQ1MCAtMzA5IDc4IC0xOCA4OCAtMTAgNDI1IDMyNSBsMzE1IDMxNCA2MCAtMzYgYzg2IC01MSAzNzAKICAgIC0xOTIgNTA1IC0yNTAgMTcyIC03NSAzNjUgLTE0NSA1NTUgLTIwMyA5NCAtMjkgMTgwIC01NiAxOTIgLTYwIGwyMiAtOSAxCiAgICAtNDQzIGMwIC00ODUgLTEgLTQ3NCA2MiAtNTIxIDQ2IC0zNCA0MjYgLTk4IDc5MyAtMTM0IDI1MiAtMjQgODM4IC0yNCAxMDkwIDAKICAgIDM2NyAzNiA3NDggMTAwIDc5NCAxMzUgNjIgNDYgNjEgMzYgNjEgNTIwIGwxIDQ0MyAyMiA5IGMxMiA0IDk5IDMxIDE5MiA2MAogICAgMTkwIDU4IDM4MyAxMjggNTU1IDIwMyAxMzYgNTggNDIwIDE5OSA1MDUgMjUwIGw2MCAzNiAzMTAgLTMwOCBjMTcxIC0xNzAgMzIwCiAgICAtMzE0IDMzMiAtMzIwIDMwIC0xNSA5NyAtMTQgMTM2IDMgNDEgMTcgMjU2IDE3MSA0MTIgMjk1IDM5NCAzMTIgODE3IDczNQogICAgMTEyNiAxMTI2IDE4NiAyMzUgMjk3IDM5NyAzMDkgNDUwIDE4IDc4IDEwIDg4IC0zMjUgNDI1IGwtMzE0IDMxNSAzNiA2MCBjNTEKICAgIDg2IDE5MiAzNzAgMjUwIDUwNSA3NSAxNzIgMTQ1IDM2NSAyMDMgNTU1IDI5IDk0IDU2IDE4MCA2MCAxOTIgbDkgMjIgNDQzIDEKICAgIGM0ODUgMCA0NzQgLTEgNTIxIDYyIDM0IDQ2IDk4IDQyNiAxMzQgNzkzIDI0IDI1MiAyNCA4MzggMCAxMDkwIC0zNiAzNjcgLTEwMAogICAgNzQ4IC0xMzUgNzk0IC00NiA2MiAtMzYgNjEgLTUyMCA2MSBsLTQ0MyAxIC05IDIyIGMtNCAxMiAtMzEgOTkgLTYwIDE5MiAtNTgKICAgIDE5MCAtMTI4IDM4MyAtMjAzIDU1NSAtNTggMTM2IC0xOTkgNDIwIC0yNTAgNTA1IGwtMzYgNjAgMzA4IDMxMCBjMTcwIDE3MQogICAgMzE0IDMyMCAzMjAgMzMyIDE1IDMwIDE0IDk3IC0zIDEzNiAtMTcgNDEgLTE3MSAyNTYgLTI5NSA0MTIgLTMxMiAzOTQgLTczNQogICAgODE3IC0xMTI2IDExMjYgLTIzNSAxODYgLTM5NyAyOTcgLTQ1MCAzMDkgLTc4IDE4IC04OCAxMCAtNDI1IC0zMjUgbC0zMTUKICAgIC0zMTQgLTYwIDM2IGMtODYgNTEgLTM3MCAxOTIgLTUwNSAyNTAgLTE3MiA3NSAtMzY1IDE0NSAtNTU1IDIwMyAtOTMgMjkgLTE4MAogICAgNTYgLTE5MiA2MCBsLTIyIDkgLTEgNDQzIGMwIDQ4NSAxIDQ3NCAtNjIgNTIxIC00NiAzNCAtNDMyIDk5IC03ODMgMTMyIC0yMDAKICAgIDE5IC03MzMgMjcgLTkyMiAxNXogbTY2MCAtMzQ0MSBjMTAxNyAtOTIgMTkzNSAtNzM3IDIzODMgLTE2NzQgMTcwIC0zNTUgMjYxCiAgICAtNzE1IDI4MyAtMTExOCA0MSAtNzM5IC0xOTQgLTE0NTcgLTY2OCAtMjAzNyAtNDcxIC01NzggLTExNDUgLTk1MyAtMTkwMQogICAgLTEwNjAgLTIwOCAtMjkgLTU3MiAtMjkgLTc4MCAwIC02NjAgOTMgLTEyNDIgMzgyIC0xNzA1IDg0NSAtNDYzIDQ2MyAtNzUyCiAgICAxMDQ1IC04NDUgMTcwNSAtMjkgMjA4IC0yOSA1NzIgMCA3ODAgMTA3IDc1NiA0ODIgMTQzMCAxMDYwIDE5MDEgNjE5IDUwNQogICAgMTM2NCA3MzEgMjE3MyA2NTh6Ii8+CiAgICA8L2c+CiAgIDwvc3ZnPg=="/>
      `;
      btn.classList.add("no_action_box");
      if (target) {
        target.insertAdjacentElement("afterend", btn);
      }
    }

    // 設定値の設定
    const sidebarChecked = sideBarDisplay == "table-cell" ? "checked" : "";
    const pageLinkChecked = alllogAnnounceDisplay == "block" ? "checked" : "";
    const showScrollButtonChecked = showScrollButton ? "checked" : "";
    const showSearchButtonChecked = showSearchButton ? "checked" : "";
    const truncateSelected = spamOption == "truncate" ? "selected" : "";
    const deleteSelected = spamOption == "delete" ? "selected" : "";
    const noneSelected = spamOption == "none" ? "selected" : "";

    // ポップアップウィンドウ(<dialog>)を作成
    const dialog = document.createElement("dialog");
    dialog.innerHTML = `
    <div class="dialog">
      <h2>Nejire Refine 設定</h2>
      <label>
        <input type="checkbox" id="sidebarCheckbox" ${sidebarChecked}>
        サイドバーを表示する
      </label>
      <br/>
      <label>
        <input type="checkbox" id="pageLinkCheckbox" ${pageLinkChecked}>
        ページリンクを表示する
      </label>
      <br/>
      <label>
        <input type="checkbox" id="showScrollButton" ${showScrollButtonChecked}>
        一番上/一番下に移動するボタンを表示する
      </label>
      <br/>
      <label>
        <input type="checkbox" id="showSearchButton" ${showSearchButtonChecked}>
        検索ボタンを表示する
      </label>
      <br/>
      <hr>
      <div>ここから下は保存したあと画面リロードで反映</div>
      <label for="spamControl">連投制御オプション:</label>
      <select id="spamControl">
        <option value="truncate" ${truncateSelected}>省略表示</option>
        <option value="delete" ${deleteSelected}>発言全体を削除</option>
        <option value="none" ${noneSelected}>何もしない</option>
      </select>
      <br/>
      <label>
        <input type="number" id="fontSizeInput" value="${textareaFontSize}">
        発言欄のフォントサイズ(rem) デフォルトは 1.5
      </label>
      <br/>
      <label>
        <input type="number" id="maxLinesInput" value="${maxBrTags}">
        発言の最大行数(超えると省略) デフォルトは 20
      </label>
      <br/>
      <label>
        <input type="number" id="maxCharsInput" value="${maxTextLength}">
        発言の最大文字数(超えると省略) デフォルトは 800
      </label>
      <br/>
      <label>
        <input type="number" id="maxImagesInput" value="${maxImgCount}">
        発言の最大画像数(タペストリー対策) デフォルトは 10
      </label>
      <br/>
      <br/>
    </div>
    `;
    document.body.appendChild(dialog);

    // 設定を保存し、ダイアログを閉じるためのボタンを作成
    let saveButton = document.createElement("button");
    saveButton.classList.add("primary");
    saveButton.type = "button";
    saveButton.innerText = "保存";
    saveButton.addEventListener("click", () => {
      // 設定値を変数に反映
      sideBarDisplay = document.querySelector("#sidebarCheckbox").checked
        ? "table-cell"
        : "none";
      alllogAnnounceDisplay = document.querySelector("#pageLinkCheckbox")
        .checked
        ? "block"
        : "none";
      spamOption = document.querySelector("#spamControl").value;
      textareaFontSize = document.querySelector("#fontSizeInput").value;
      maxBrTags = document.querySelector("#maxLinesInput").value;
      maxTextLength = document.querySelector("#maxCharsInput").value;
      maxImgCount = document.querySelector("#maxImagesInput").value;
      showScrollButton = document.querySelector("#showScrollButton").checked;
      showSearchButton = document.querySelector("#showSearchButton").checked;

      // 設定値を保存
      GM_setValue("sideBarDisplay", sideBarDisplay);
      GM_setValue("alllogAnnounceDisplay", alllogAnnounceDisplay);
      GM_setValue("spamOption", spamOption);
      GM_setValue("textareaFontSize", textareaFontSize);
      GM_setValue("maxBrTags", maxBrTags);
      GM_setValue("maxTextLength", maxTextLength);
      GM_setValue("maxImgCount", maxImgCount);
      GM_setValue("showScrollButton", showScrollButton);
      GM_setValue("showSearchButton", showSearchButton);

      // 変数からDOMに反映
      applySettings();
      dialog.close();
    });
    dialog.appendChild(saveButton);

    // 設定をキャンセルし、ダイアログを閉じるためのボタンを作成
    let cancelButton = document.createElement("button");
    cancelButton.classList.add("secondary");
    cancelButton.type = "button";
    cancelButton.innerText = "キャンセル";
    cancelButton.addEventListener("click", () => {
      // 設定を元に戻す
      document.querySelector("#sidebarCheckbox").checked =
        sideBarDisplay == "table-cell";
      document.querySelector("#pageLinkCheckbox").checked =
        alllogAnnounceDisplay == "block";
      document.querySelector("#spamControl").value = spamOption;
      document.querySelector("#fontSizeInput").value = textareaFontSize;
      document.querySelector("#maxLinesInput").value = maxBrTags;
      document.querySelector("#maxCharsInput").value = maxTextLength;
      document.querySelector("#maxImagesInput").value = maxImgCount;
      document.querySelector("#showScrollButton").checked = showScrollButton;
      document.querySelector("#showSearchButton").checked = showSearchButton;

      // ダイアログを閉じる
      dialog.close();
    });
    dialog.appendChild(cancelButton);
  }

  // 右上ボタン群のコンテナ作成
  function createTopButtonContainer() {
    if (document.querySelector("#topButtonContainer")) {
      return;
    }
    // Create a new div element for buttons
    const topButtonContainer = document.createElement("div");
    topButtonContainer.id = "topButtonContainer";

    // Append the new div element to the bottom of the page
    document.body.appendChild(topButtonContainer);
  }

  // 右下ボタン群のコンテナ作成
  function createBottomButtonContainer() {
    if (document.querySelector("#bottomButtonContainer")) {
      return;
    }
    // Create a new div element for buttons
    const bottomButtonContainer = document.createElement("div");
    bottomButtonContainer.id = "bottomButtonContainer";

    // Append the new div element to the bottom of the page
    document.body.appendChild(bottomButtonContainer);
  }

  // 上下スクロールボタンを作成
  function upDownButtons() {
    const topButtonContainer = document.querySelector("#topButtonContainer");

    // Create a new button element for scrolling to bottom
    const buttonDown = document.createElement("button");
    buttonDown.id = "scrollToBottomButton";
    buttonDown.innerHTML = `
    <img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMCIgaGVpZ2h0PSIzMCIgdmlld0JveD0iMCAwIDMwIDMwIj4KICAgIDxwYXRoIGQ9Ik0xNSAyMEw1IDEwaDIwbC0xMCAxMHoiIGZpbGw9ImJsYWNrIi8+Cjwvc3ZnPgo="/>
    `;

    // Create a new button element for scrolling to top
    const buttonUp = document.createElement("button");
    buttonUp.id = "scrollToTopButton";
    buttonUp.innerHTML = `
    <img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMCIgaGVpZ2h0PSIzMCIgdmlld0JveD0iMCAwIDMwIDMwIj4KICAgIDxwYXRoIGQ9Ik0xNSAxMEw1IDIwaDIwbC0xMCAtMTB6IiBmaWxsPSJibGFjayIvPgo8L3N2Zz4K"/>
    `;

    // Add the buttons to the document body
    topButtonContainer.append(buttonUp, buttonDown);

    // Attach an event listener to the buttons to handle clicks
    buttonDown.addEventListener("click", function () {
      window.scrollTo({
        top: document.body.scrollHeight, // Scroll to the bottom of the page
        behavior: "smooth", // Animate the scroll
      });
    });

    buttonUp.addEventListener("click", function () {
      window.scrollTo({
        top: 0, // Scroll to the top of the page
        behavior: "smooth", // Animate the scroll
      });
    });
  }

  // 発言ボタン2度押し防止
  function disableDoubleSubmit() {
    const submitButtons = document.querySelectorAll('input[type="submit"]');
    for (const submitButton of submitButtons) {
      submitButton.addEventListener("click", (event) => {
        setTimeout(() => (this.disabled = true), 0);
      });
    }
  }

  // メッセージの情報を取得する
  function getMessageInformationFromMessageTable(messageTable) {
    // mark processed message, add data-processed: true
    messageTable.setAttribute("data-processed", true);

    const meta = messageTable.querySelector("tbody > tr:nth-child(1)");
    const icon = meta
      .querySelector("td:nth-child(1) > img")
      .getAttribute("src");
    const number = meta.querySelector(
      "td:nth-child(2) > span.mes_number"
    )?.textContent;
    const nameATag = meta.querySelector("td:nth-child(2) > a:nth-child(3)");
    let id;
    let name;
    if (!nameATag) {
      // nameATagがないのは匿名ユーザー
      id = "anonymous";
      name = "汝はねじれなりや?";
    } else {
      name = nameATag.textContent;
      let match;
      // 参加中のユーザーは数値のidを持つ
      match = /&id=(\d+)/.exec(nameATag.href);
      if (match) {
        id = match[1];
      } else {
        // 観戦発言などはuidを持つ
        match = /&uid=(\w+)/.exec(nameATag.href);
        if (match) {
          id = match[1];
        }
      }
    }
    if (!id) {
      console.error("IDが取得できませんでした");
      console.error(nameATag.href);
    }
    const content = messageTable.querySelector("[class$=body1]");
    const original = content.innerHTML;
    return { id, name, number, icon, content, original };
  }

  // div.announceの処理
  function processAnnounce() {
    const announces = document.querySelectorAll("div.announce:not([data-processed])");
    for (const announce of announces) {
      // mark processed announce, add data-processed: true
      announce.setAttribute("data-processed", true);
      if (announce.classList.contains("testament")) {
        // 遺言
        let tempHTML = announce.innerHTML;
        const brTags = tempHTML.split("<br>");
        const text = announce.textContent;
        const originalContentHTML = announce.innerHTML;

        if (brTags && brTags.length > maxBrTags) {
          // brタグの数がmaxBrTagsを超えていたら、それ以降のHTMLを削除し、最後に...を追加したHTMLを作成する
          const tempBrTags = brTags.slice(0, maxBrTags);
          tempHTML =
            tempBrTags.join("<br>") +
            "<br><a class='ellipsis' title='省略されています'>...</a>";
        } else if (text.length > maxTextLength) {
          // textContent の長さがmaxTextLengthを超えていたら、まずtextをmaxTextLengthで切り取る
          announce.textContent = text.slice(0, maxTextLength);
          announce.innerHTML +=
            "<a class='ellipsis' title='省略されています'>...</a>";
          tempHTML = announce.innerHTML;
        }

        announce.innerHTML = tempHTML;

        // ...にはイベントリスナーを追加し、クリックされたら全文を表示する
        announce
          .querySelector("a.ellipsis")
          ?.addEventListener("click", (event) => {
            event.preventDefault();
            announce.innerHTML = originalContentHTML;
          });
      }
    }
  }

  // messageの処理
  function processMessage() {
    const messages = document.querySelectorAll(
      "table.message:not([data-processed])"
    );
    const originalContentHTML = [];
    const latestMessages = {};
    for (const [i, messageTable] of messages.entries()) {
      const message = getMessageInformationFromMessageTable(messageTable);

      // 発言者のIDでグループ化
      if (!latestMessages[message.id]) {
        latestMessages[message.id] = [];
      }
      latestMessages[message.id].push(message);

      // originalの本文を保存しておく
      originalContentHTML.push(message.content.innerHTML);

      // 連投対策。同じ発言者の連続した発言は、発言内容を省略する
      if (spamOption == "truncate" || spamOption == "delete") {
        if (i > 0) {
          if (latestMessages[message.id].length > 1) {
            // 連投していたら、最後の発言の内容を省略する
            const lastMessage =
              latestMessages[message.id][latestMessages[message.id].length - 2];
            if (lastMessage.original == message.original) {
              if (spamOption == "truncate") {
                message.content.innerHTML =
                  "<a class='ellipsis' title='省略されています'>...</a>";
              } else if (spamOption == "delete") {
                messageTable.parentElement.removeChild(messageTable);
              }
            }
          }
        }
      }

      // タペストリー対策。imgタグがmaxImgCountを超えたら削除する
      const imgs = message.content.querySelectorAll("img");
      if (imgs.length > maxImgCount) {
        // 画像を削除したことを通知するメッセージ要素を作成
        const messageElement = document.createElement("span");
        messageElement.classList.add("ellipsis");
        messageElement.innerText = `この発言の画像は省略されました。`;
        imgs[0].parentElement.appendChild(messageElement);
        imgs.forEach((img) => {
          img.parentElement.removeChild(img);
        });
      }

      // 長文対策
      const innerHTML = message.content.innerHTML;
      const text = message.content.textContent;
      let count = 0;
      const savedLinks = {};

      // { と } をエスケープ
      let tempHTML = innerHTML.replace(/({|})/g, "\\$1");

      // aタグをプレースホルダーに置換
      tempHTML = tempHTML.replace(/<a\b[^>]*>(.*?)<\/a>/gi, function (match) {
        const placeholder = `{{link${count}}}`;
        savedLinks[placeholder] = match;
        count++;
        return placeholder;
      });

      // 置換したHTMLを<br>で分割
      const brTags = tempHTML.split("<br>");

      if (brTags && brTags.length > maxBrTags) {
        // brタグの数がmaxBrTagsを超えていたら、それ以降のHTMLを削除し、最後に...を追加したHTMLを作成する
        const tempBrTags = brTags.slice(0, maxBrTags);
        tempHTML =
          tempBrTags.join("<br>") +
          "<br><a class='ellipsis' title='省略されています'>...</a>";
      } else if (text.length > maxTextLength) {
        // textContent の長さがmaxTextLengthを超えていたら、まずtextをmaxTextLengthで切り取る
        // この場合、リンクやアンカーなどのタグが消えてしまうが、今のところしょうがない
        message.content.textContent = text.slice(0, maxTextLength);
        message.content.innerHTML +=
          "<a class='ellipsis' title='省略されています'>...</a>";
        tempHTML = message.content.innerHTML;
      }

      // プレースホルダーを元のaタグに戻す
      for (let placeholder in savedLinks) {
        tempHTML = tempHTML.replace(placeholder, savedLinks[placeholder]);
      }

      // エスケープした { と } を元に戻す
      tempHTML = tempHTML.replace(/\\({|})/g, "$1");

      message.content.innerHTML = tempHTML;

      // ...にはイベントリスナーを追加し、クリックされたら全文を表示する
      message.content
        .querySelector("a.ellipsis")
        ?.addEventListener("click", (event) => {
          event.preventDefault();
          message.content.innerHTML = originalContentHTML[i];
        });
    }
  }

  // 過去ログのアンカーをコピーする処理の追加
  async function addCopyAnchorEvent() {
    const match = /&date=(\d+)/.exec(location.href);
    if (match) {
      const date = match[1];
      const mesNumbers = document.querySelectorAll("span.mes_number");
      for (const mesNumberElm of mesNumbers) {
        mesNumberElm.addEventListener("click", async (event) => {
          const mesNumber = mesNumberElm.textContent;
          const anchor = `>>${date}:${mesNumber}`;
          // コピーするテキストを一時的なテキストエリアにセット
          const tempTextArea = document.createElement("textarea");
          tempTextArea.style.position = "absolute";
          tempTextArea.style.left = "-9999px";
          tempTextArea.value = anchor;
          document.body.appendChild(tempTextArea);
          tempTextArea.select();
          // クリップボードにコピー
          document.execCommand("copy");
          document.body.removeChild(tempTextArea);
          // コピーしたことを通知
          const notification = document.createElement("div");
          notification.textContent = `${anchor}をコピーしました`;
          notification.style.position = "fixed";
          notification.style.top = `${event.clientY + 20}px`;
          notification.style.left = `${event.clientX + 20}px`;
          notification.style.zIndex = "9999";
          notification.style.backgroundColor = "black";
          notification.style.padding = "1rem";
          notification.style.border = "1px solid black";
          notification.style.borderRadius = "1rem";
          notification.style.opacity = "0";
          notification.style.transition = "opacity 0.5s";
          document.body.appendChild(notification);
          setTimeout(() => {
            notification.style.opacity = "1";
          }, 10);
          setTimeout(() => {
            notification.style.opacity = "0";
            setTimeout(() => {
              document.body.removeChild(notification);
            }, 500);
          }, 1000);
        });
      }
    }
  }

  // 村を出るボタンの無効化とチェックボックスの追加
  function disableLeaveButtonAndAddCheckbox() {
    const leaeveButton = document.querySelector("input[value='村を出る']");
    if (leaeveButton) {
      leaeveButton.disabled = true;
      leaeveButton.style.opacity = "0.5";

      const checkbox = document.createElement("input");
      checkbox.type = "checkbox";
      checkbox.id = "vil_leave_checkbox";
      checkbox.style.marginRight = "0.5rem";
      checkbox.style.verticalAlign = "middle";
      checkbox.addEventListener("change", (event) => {
        leaeveButton.disabled = !event.target.checked;
        leaeveButton.style.opacity = event.target.checked ? "1" : "0.5";
      });
      // add checkbox element before leave button
      leaeveButton.parentNode.insertBefore(checkbox, leaeveButton);
    }
  }

  // ページネーションの追加
  function pagination() {
    const announce = document.querySelector(".alllog_announce");
    if (!announce) {
      return;
    }
    const match = window.location.href.match(/part=(\d+)/);
    if (match) {
      const currentPart = parseInt(match[1]);
      const parts = Array.from(announce.querySelectorAll("a[href*='part']"))
        .map((a) => parseInt(a.innerText))
        .filter((n) => !isNaN(n));
      const minPart = 1;
      const maxPart = Math.max(parts[parts.length - 1], currentPart);

      const firstMessage = getFirstMessageOrAnnounce();
      const lastMessage = getLastMessageOrAnnounce();

      if (currentPart > minPart) {
        const prevButton = createPaginationButton(
          "▲前のページを読み込む",
          currentPart - 1,
          firstMessage,
          false,
          maxPart
        );
        firstMessage.parentNode.insertBefore(prevButton, firstMessage.previousSibling);
      }
      if (currentPart < maxPart) {
        const nextButton = createPaginationButton(
          "▼次のページを読み込む",
          currentPart + 1,
          lastMessage,
          true,
          maxPart
        );
        // insert next button after last message
        lastMessage.parentNode.insertBefore(nextButton, lastMessage.nextSibling);
      }
    }
  }

  function getFirstMessageOrAnnounce() {
    const content = document.querySelector("#content");
    const firstChild = content.firstElementChild;
    if (firstChild.classList.contains("alllog_announce")) {
      return firstChild.nextElementSibling;
    } else {
      return firstChild;
    }
  }

  function getLastMessageOrAnnounce() {
    const content = document.querySelector("#content");
    const lastChild = content.lastElementChild;
    if (lastChild.classList.contains("alllog_announce")) {
      return lastChild.previousSibling;
    } else {
      return lastChild;
    }
  }

  // ページネーションのボタンを作成
  function createPaginationButton(text, part, anchorMessage, isNext, maxPart) {
    const button = document.createElement("button");
    button.innerText = text;
    button.style.cursor = "pointer";
    button.style.width = "100%";
    button.style.margin = "10px 0";
    button.style.backgroundColor = "black";
    button.style.color = "#994";
    button.style.borderWidth = "0";
    button.setAttribute("data-part", part);

    button.addEventListener("click", async function () {
      const part = parseInt(this.getAttribute("data-part"));
      if ((part <= 0 && !isNext) || (part > maxPart && isNext)) {
        alert(isNext ? "最後のページです。" : "最初のページです");
        return;
      }

      const messages = await fetchPage(part, isNext);
      const anchorRectBefore = anchorMessage.getBoundingClientRect();

      messages.forEach((message) => {
        anchorMessage.parentElement.insertBefore(
          message,
          isNext ? anchorMessage.nextSibling : anchorMessage
        );
      });

      const anchorRectAfter = anchorMessage.getBoundingClientRect();
      const topDiff = anchorRectAfter.top - anchorRectBefore.top;
      window.scrollBy(0, topDiff);

      anchorMessage = messages[0];
      button.setAttribute("data-part", isNext ? part + 1 : part - 1);
    });

    return button;
  }

  // ページを取得
  async function fetchPage(part, isNext) {
    const url = window.location.href.replace(/part=\d+/, `part=${part}`);
    const response = await fetch(url);
    const arrayBuffer = await response.arrayBuffer();
    const html = new TextDecoder("euc-jp").decode(arrayBuffer);
    const parser = new DOMParser();
    const doc = parser.parseFromString(html, "text/html");
    const messages = Array.from(doc.querySelectorAll("#content > *")).filter(
      (e) => !e.classList.contains("alllog_announce")
    );
    return isNext ? messages.reverse() : messages;
  }

  // アンカーのAjaxを再設定
  function setAnchorToggle() {
    $(document).on("click touchstart touchend", ".say", function (event) {
      event.preventDefault();
      if ($(this).data("clicked")) {
        onSayToggleOff.call(this);
      } else {
        onSayClick.call(this);
      }
      $(this).data("clicked", !$(this).data("clicked"));
    });

    $(document).on("click", ".mes_res", function (event) {
      if ($(this).data("clicked")) {
        onMesResToggleOff.call(this);
      } else {
        onMesResClick.call(this);
      }
      $(this).data("clicked", !$(this).data("clicked"));
    });
  }

  // アンカークリック時の処理
  function onSayClick() {
    const ank = $(this);

    if (ank.text().startsWith(">>")) {
      if (!ank.attr("onmouseover")) {
        if (
          confirm(
            "このアンカーの遷移先が見つかりません。それでも移動しますか?"
          )
        ) {
          window.location.href = this.href;
        }
        return false;
      }

      const href = this.href.replace("all", "anc").replace("#", "&num=");
      $.get(href, function (data) {
        const mes = $(data).find(".anchor");
        mes.addClass("ajax");
        insertElementAfterParents(mes, ank, [".message", ".announce"]);
        mes.find(".mes_res").show();
        mes.hide().toggle("slide");
      });
    } else {
      window.open(this.href, "_blank");
    }

    return false;
  }

  // アンカー閉じる処理
  function onSayToggleOff() {
    toggleSlideAndRemove($(this), [".message", ".announce"]);
    return false;
  }

  // レスクリック時の処理
  function onMesResClick() {
    const ank = $(this);
    const text = markEscape(ank.attr("id"));
    const reg = new RegExp(text + "(?![\\d:])");

    const res = $("<div>");
    $("#content")
      .children('.message, [class^="announce"]')
      .each(function () {
        const elementText = $(this).find(".say").text();
        if (elementText.search(reg) === -1) return;

        const cloned = $(this).clone().show();
        res.append(cloned).addClass("ajax");
      });

    insertElementAfterParents(res, ank, [".message", ".announce"]);
    res.hide().toggle("slide");
  }

  // レス閉じる処理
  function onMesResToggleOff() {
    toggleSlideAndRemove($(this), [".message", ".announce"]);
    return false;
  }

  // 要素を削除
  function toggleSlideAndRemove(element, parentSelectors) {
    parentSelectors.forEach((selector) => {
      const parent = element.parents(selector);
      parent.nextAll(".ajax").toggle("slide", function () {
        parent.nextAll(".ajax").remove();
      });
    });
  }

  // 親要素の後ろに要素を挿入する
  function insertElementAfterParents(element, reference, parentSelectors) {
    parentSelectors.forEach((selector) => {
      const parent = reference.parents(selector);
      parent.after(element);
    });
  }

  // 検索ボタンを追加
  function addSearchIcon() {
    const searchIcon = `
      <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMzAiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDwhLS0g6Jmr55y86Y+h44Gu5YaG6YOo5YiGIC0tPgogICAgPGNpcmNsZSBjeD0iMTEiIGN5PSIxMSIgcj0iNyIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLXdpZHRoPSIyIi8+CiAgICA8IS0tIOiZq+ecvOmPoeOBruODj+ODs+ODieODq+mDqOWIhiAtLT4KICAgIDxsaW5lIHgxPSIxNi42NSIgeTE9IjE2LjY1IiB4Mj0iMjIiIHkyPSIyMiIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLXdpZHRoPSIyIi8+Cjwvc3ZnPgo=">
    `;

    const $searchBoxContainer = $("<div>", { id: "searchBoxContainer" })
      .html(
        `
      <div class="container">
        <input id="searchBox" type="text" placeholder="Search...">
        <input id="searchButton" type="button" value="検索" class="submit2" onclick="window.open('?vid=${vid}&amp;date=${date}&amp;hash=' + encodeURIComponent(metaEscape(document.querySelector('#searchBox').value)));">
      </div>
      <hr>
      <div class="container">
        <select id="filter">
        </select>
        <input id="filterButton" type="button" value="抽出" class="submit2" onclick="window.open('?vid=${vid}&amp;date=${date}&amp;id=' + encodeURIComponent(document.querySelector('#filter').value));">
      </div>
      `
      )
      .hide();

    // idAndNamesの各要素を<select>ボックスに追加します
    idAndNames.forEach((name, id) => {
      const $option = $("<option>", {
        value: id,
        text: name,
      });
      $("#filter", $searchBoxContainer).append($option);
    });

    const $searchIcon = $("<button>", { id: "searchIcon" }).html(searchIcon);

    $("#bottomButtonContainer").append($searchBoxContainer).append($searchIcon);

    $searchIcon.on("click", function (event) {
      $searchBoxContainer.toggle();
      event.stopPropagation();
    });
    $searchBoxContainer.on("click", function (event) {
      event.stopPropagation();
    });
    $("#searchButton, #filterButton", $searchBoxContainer).on("click", function () {
      $searchBoxContainer.hide();
    });
    $(document).on("click", function () {
      $searchBoxContainer.hide();
    });

    $("#searchBox").on("keydown", function (event) {
      if (event.which === 13 || event.keyCode === 13) {
        // 13 is the key code for Enter
        $("#searchButton").click(); // Trigger the click event on the button
        event.preventDefault(); // Prevent the default behavior of Enter key
      }
    });
  }

  ////////////////////////////////////////////////////////////////////////////////
  // ねじれスクリプト置換
  ////////////////////////////////////////////////////////////////////////////////

  unsafeWindow.getElementsByClass = getElementsByClass;
  unsafeWindow.setAjaxEvent = setAjaxEvent;

  function getElementsByClass(searchClass) {
    // もしsearchClassが"announce"の場合、そのクラス名を含むすべての要素を取得
    if (searchClass === "announce") {
      return Array.from(document.querySelectorAll(`[class*="${searchClass}"]`));
    }
    // それ以外の場合、クラス名が正確にマッチする要素のみを取得
    return Array.from(document.getElementsByClassName(searchClass));
  }

  // ねじれスクリプトのsetAjaxEvent関数を置換して無効化
  function setAjaxEvent(target) {
    return false;
  }

  ////////////////////////////////////////////////////////////////////////////////
  // CSS
  ////////////////////////////////////////////////////////////////////////////////
  GM_addStyle(`
  div {
    overflow-wrap: anywhere;
    line-break: anywhere;
  }

  table.main {
    width: 100%;
  }

  body > table > tbody > tr > td > table > tbody > tr > td > table > tbody > tr > td:nth-child(1) {
    display: ${sideBarDisplay};
  }

  .vil_main {
    margin: auto;
    width: 552px;
  }

  .alllog_announce {
    display: ${alllogAnnounceDisplay};
  }

  a.ellipsis,
  span.ellipsis {
    font-weight: bold;
  }

  a.ellipsis {
    color: blue !important;
    font-size: 1.2rem;
    cursor: pointer;
  }

  span.ellipsis {
    font-size: 0.8rem;
  }

  textarea {
    font-size: ${textareaFontSize}rem !important;
  }

  .dialog {
    text-align: left;
    margin: 0 auto;
  }

  input[type=number] {
    width: 3rem;
    text-align: right;
  }

  button.primary, button.secondary, button.no_action_box {
    border: none;
    color: white;
    text-align: center;
    text-decoration: none;
    font-size: 1rem;
    cursor: pointer;
  }

  button.primary, button.secondary {
    padding: 0.5rem 1rem;
    display: inline-block;
    margin: 0.5rem 0.5rem;
  }

  button.primary {
    background-color: #4CAF50;
  }

  button.secondary {
    background-color: #008CBA;
  }

  button.with_action_box {
    border: none;
    cursor: pointer;
    background-color: white;
    padding: 0;
  }

  button.no_action_box {
    background-color: #000000;
    padding: 0rem 1rem;
  }

  .container {
    display: flex;
    position: relative;
    justify-content: space-between;
  }

  span.right {
    margin-left: auto;
  }

  #scrollToBottomButton, #scrollToTopButton, #searchIcon {
    padding: 5px;
    cursor: pointer;
    background: #ddd;
    border: none;
    border-radius: 5px;
    transition: background 0.2s;
    margin-bottom: 10px;
  }

  #scrollToTopButton:hover, #scrollToBottomButton:hover, #searchIcon:hover {
    background: #bbb;
  }

  #scrollToTopButton {
    order: 1;
  }

  #scrollToBottomButton {
    order: 2;
  }

  #topButtonContainer, #bottomButtonContainer {
    display: flex;
    flex-direction: column;
    position: fixed;
    right: 20px;
    z-index: 1000;
  }

  #topButtonContainer {
    top: 20px;
  }

  #bottomButtonContainer {
    bottom: 20px;
  }

  #searchBoxContainer {
    order: 1;
    align-items: center;
    padding: 5px;
    position: absolute;
    right: 55px;
    bottom: 0;
    display: none;
    background: #fff;
  }

  #searchBox, select#filter {
    width: 200px;
    height: 30px;
    border-radius: 5px;
    border: 1px solid #aaa;
  }

  input#searchBox, select#filter {
    margin-right: 10px;
  }

  hr {
    border: none;
    border-top: 1px solid #e0e0e0;
    margin: 5px 0;
  }

`);
})();