Sharty Fixes 2025

Fixes/Enhancements for the 'party

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name        Sharty Fixes 2025
// @namespace   soyjak.party
// @match       https://soyjak.party/*
// @match       https://soyjak.st/*
// @version     1.152
// @author      Xyl (Currently Maintained by Swedewin)
// @license     MIT
// @description Fixes/Enhancements for the 'party
// ==/UserScript==

const version = "v1.152";
console.log(`Sharty fixes ${version}`);

const namespace = "ShartyFixes.";
function setValue(key, value) {
  if (key == "hiddenthreads" || key == "hiddenimages") {
    if (typeof GM_setValue == "function") {
      GM_setValue(key, value);
    }
    localStorage.setItem(key, value);
  } else {
    if (typeof GM_setValue == "function") {
      GM_setValue(namespace + key, value);
    } else {
      localStorage.setItem(namespace + key, value);
    }
  }
}

function getValue(key) {
  if (key == "hiddenthreads" || key == "hiddenimages") {
    if (typeof GM_getValue == "function" && GM_getValue(key)) {
      localStorage.setItem(key, GM_getValue(key).toString());
    }
    return localStorage.getItem(key);
  }
  if (typeof GM_getValue == "function") {
    return GM_getValue(namespace + key);
  } else {
    return localStorage.getItem(namespace + key);
  }
}

function isEnabled(key) {
  let value = getValue(key);
  if (value == null) {
    value = optionsEntries[key][2];
    setValue(key, value);
  }
  return value.toString() == "true";
}

function getNumber(key) {
  let value = parseInt(getValue(key));
  if (Number.isNaN(value)) {
    value = 0;
  }
  return value;
}

function getJson(key) {
  let value = getValue(key);
  if (value == null) {
    value = "{}";
  }
  return JSON.parse(value);
}

function addToJson(key, jsonKey, value) {
  let json = getJson(key);
  let parent = json;
  jsonKey.split(".").forEach((e, index, array) => {
    if (index < array.length - 1) {
      if (!parent.hasOwnProperty(e)) {
        parent[e] = {};
      }
      parent = parent[e];
    } else {
      parent[e] = value;
    }
  });
  setValue(key, JSON.stringify(json));
  return json;
}

function removeFromJson(key, jsonKey) {
  let json = getJson(key);
  let parent = json;
  jsonKey.split(".").forEach((e, index, array) => {
    if (index < array.length - 1) {
      parent = parent[e];
    } else {
      delete parent[e];
    }
  });
  setValue(key, JSON.stringify(json));
  return json;
}

function customAlert(a) {
    document.body.insertAdjacentHTML("beforeend", `
<div id="alert_handler">
  <div id="alert_background" onclick="this.parentNode.remove()"></div>
  <div id="alert_div">
    <a id='alert_close' href="javascript:void(0)" onclick="this.parentNode.parentNode.remove()"><i class='fa fa-times'></i></a>
    <div id="alert_message">${a}</div>
    <button class="button alert_button" onclick="this.parentNode.parentNode.remove()">OK</button>
  </div>
</div>`);
}

const optionsEntries = {
  "show-quote-button": ["checkbox", "Show quick quote button", false],
  "mass-reply-quote": ["checkbox", "Enable mass reply and mass quote buttons", true],
  "anonymise": ["checkbox", "Anonymise name and tripfags", false],
  "hide-blotter": ["checkbox", "Always hide blotter", false],
  "truncate-long-posts": ["checkbox", "Truncate line spam", true],
  "disable-submit-on-cooldown": ["checkbox", "Disable submit button on cooldown", false],
  "force-exact-time": ["checkbox", "Show exact time", false],
  "hide-sage-images": ["checkbox", "Hide sage images by default (help mitigate gross spam)", false],
  "catalog-navigation": ["checkbox", "Board list links to catalogs when on catalog", true]
}
let options = Options.add_tab("sharty-fixes", "gear", "Sharty Fixes").content[0];
let optionsHTML = `<span style="display: block; text-align: center">${version}</span>`;
optionsHTML += `<a style="display: block; text-align: center" href="https://booru.soyjak.st/post/list/variant%3Aimpish_soyak_ears/">#Impishgang</a><br>`;
for ([optKey, optValue] of Object.entries(optionsEntries)) {
  optionsHTML += `<input type="${optValue[0]}" id="${optKey}" name="${optKey}"><label for="${optKey}">${optValue[1]}</label><br>`;
}
options.insertAdjacentHTML("beforeend", optionsHTML);

options.querySelectorAll("input[type=checkbox]").forEach(e => {
  e.checked = isEnabled(e.id);
  e.addEventListener("change", e => {
    setValue(e.target.id, e.target.checked);
  });
});

// redirect (for some reason breaks hidden threads if removed)
if (location.origin.match(/(http:|\/www)/g)) {
  location.replace(`https://soyjak.party${location.pathname}${location.hash}`);
}

const board = window.location.pathname.split("/")[1];

// post fixes
const weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

// Update observer for active thread
if (document.body.classList.contains("active-thread")) {
  const updateObserver = new MutationObserver(list => {
    const evt = new CustomEvent("post_update", { detail: list });
    document.dispatchEvent(evt);
  });
  updateObserver.observe(document.querySelector(".thread"), { childList: true });
}

let intervals = {};

// Fix individual post
function fixPost(post) {
  let timeElement = post.querySelector("[datetime]");
  let time = new Date(Date.parse(timeElement.getAttribute("datetime")));
  let postNumber = post.getElementsByClassName("post_no")[1];
  let postText = postNumber.textContent;

  // Hide images for sage posts
  if (email = post.querySelector("a.email")) {
    if (isEnabled("hide-sage-images") && !post.classList.contains("image-hide-processed") && email.href.match(/mailto:sage$/i)) {
      let localStorageBackup = localStorage.getItem("hiddenimages");
      let interval = setInterval(() => {
        if (document.querySelector(`[id*="${postText}"] .hide-image-link`)) {
          post.classList.add("image-hide-processed");
          clearInterval(interval);
          document.querySelector(`[id*="${postText}"] .files`)
            .querySelectorAll(".hide-image-link:not([style*='none'])")
            .forEach(e => e.click());
          localStorage.setItem("hiddenimages", localStorageBackup);
        }
      }, 50);
    }
  }

  // Check if post is own post
  let isOwnPost = false;
  try {
    isOwnPost = JSON.parse(localStorage.getItem("own_posts"))[board].includes(post.querySelector(".post_no[onclick*=cite]").innerText);
  } catch { }

  if (isOwnPost && getNumber("lastTime") < time.getTime()) {
    setValue("lastTime", time.getTime());
  }

  // Format time
  timeElement.outerHTML = `<span datetime=${timeElement.getAttribute("datetime")}>${timeElement.innerText}</span>`;
  post.querySelector(".intro").insertAdjacentHTML("beforeend", `<span class="quote-buttons"></span>`);

  // Add quote buttons if enabled
  if (isEnabled("show-quote-button")) {
    post.querySelector(".quote-buttons").insertAdjacentHTML("beforeend", `<a href="javascript:void(0);" class="quick-quote">[>]</a>`);
    post.querySelector(".quote-buttons").insertAdjacentHTML("beforeend", `<a href="javascript:void(0);" class="quick-orange">[<]</a>`);
  }

  // Mass reply/quote options for OP posts
  if (isEnabled("mass-reply-quote") && post.classList.contains("op") && post.closest(".active-thread")) {
    document.querySelector(".quote-buttons").insertAdjacentHTML("beforeend", `<a href="javascript:void(0);" id="mass-reply">[Mass Reply]</a><a href="javascript:void(0);" id="mass-quote">[Mass Quote]</a><a href="javascript:void(0);" id="mass-orange">[Mass Orange]</a>`);
  }

  // Handle post text formatting
  let body = post.querySelector(".body");
  body.childNodes.forEach(e => {
    if (e.nodeType === 3) {
      let span = document.createElement("span");
      span.innerText = e.textContent;
      e.parentNode.replaceChild(span, e);
    }
  });

  // Format post number and add click event for citations
  if (document.body.classList.contains("active-thread")) {
    postNumber.href = `#q${postNumber.textContent}`;
    postNumber.setAttribute("onclick", `$(window).trigger('cite', [${postNumber.textContent}, null]);`);
    postNumber.addEventListener("click", () => {
      let selection = window.getSelection().toString();
      document.querySelectorAll("textarea[name=body]").forEach(e => {
        e.value += `>>${postNumber.textContent}\n${selection !== "" ? selection.replace(/(\r\n|\r|\n|^)/g, "$1>") : ""}`;
      });
    });
  }

  // Anonymize post if enabled
  if (isEnabled("anonymise")) {
    post.querySelector(".name").textContent = "Chud";
    if (trip = post.querySelector(".trip")) {
      trip.remove();
    }
  }

  // DO NOT REMOVE THIS, IT WILL BREAK THE SCRIPT.
  undoFilter(post);
}

// Add expandos for truncating long posts
function addExpandos() {
  if (isEnabled("truncate-long-posts")) {
    document.querySelectorAll(".post").forEach(e => {
      let body = e.querySelector(".body");
      e.classList.add("sf-cutoff");

      if (body.scrollHeight > body.offsetHeight) {
        if (!e.querySelector(".sf-expander")) {
          body.insertAdjacentHTML("afterend", `<br><a href="javascript:void(0)" class="sf-expander"></a>`);
        }
        if (e.getAttribute("manual-cutoff") === "false" || (window.location.hash.includes(e.id.split("_")[1]) && !e.getAttribute("manual-cutoff"))) {
          e.classList.remove("sf-cutoff");
        }
      } else if (body.scrollHeight === body.offsetHeight) {
        if (expander = e.querySelector(".sf-expander")) {
          expander.remove();
        }
        e.classList.remove("sf-cutoff");
      }
    });
  }
}

window.addEventListener("resize", () => addExpandos());

// Function to modify the report form (File label, Urgent label, and Reason box)
function modifyReportForm(form) {
    // Skip modification if the form has already been modified
    if (form.dataset.modified === 'true') return;

    // Modify the "File" label and checkbox
    const fileLabel = form.querySelector('label[for^="delete_file_"]');
    if (fileLabel) {
        fileLabel.textContent = '[ File only ]';  // Modify the label text
        const fileCheckbox = form.querySelector('input[type="checkbox"][name="file"]');
        if (fileCheckbox) {
            // Move the checkbox inside the label
            fileLabel.insertBefore(fileCheckbox, fileLabel.firstChild);
        }
    }

    // Modify the "Urgent report" label and checkbox
    const urgentCheckbox = form.querySelector('#urgent-checkbox');
    if (urgentCheckbox) {
        const urgentLabel = form.querySelector('label[for="urgent-checkbox"]');
        if (urgentLabel) {
            urgentLabel.textContent = 'Urgent';  // Modify the label text
            // Move the checkbox inside the label
            urgentLabel.parentNode.insertBefore(urgentCheckbox, urgentLabel);
        }
    }

    // Set Reason input width equal to Password input width
    const passwordInput = form.querySelector('input[type="password"][name="password"]');
    const reasonInput = form.querySelector('input[type="text"][name="reason"]');
    if (passwordInput && reasonInput) {
        // Set the Reason input size equal to Password input's size
        reasonInput.setAttribute('size', passwordInput.getAttribute('size'));
    }

    // Mark the form as modified using a dataset attribute to prevent re-modification
    form.dataset.modified = 'true';
}

// Function to observe changes to the page (to handle dynamic form rendering)
function observeFormChanges() {
    // Find all forms on the page that are for reporting
    const forms = document.querySelectorAll('form.post-actions');

    forms.forEach(form => {
        modifyReportForm(form);  // Apply modification to the form
    });
}

// Initial run when the page loads
window.addEventListener('load', function() {
    // Apply changes to any forms already present on the page
    observeFormChanges();

    // Set up a MutationObserver to watch for changes in the DOM
    const observer = new MutationObserver((mutationsList) => {
        for (let mutation of mutationsList) {
            if (mutation.type === 'childList') {
                // Check for changes to form elements
                observeFormChanges();
            }
        }
    });

    // Observe the body of the page for changes
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
});

// Listen for any changes to checkboxes to ensure modifications are reapplied dynamically
document.addEventListener('change', function(event) {
    if (event.target.classList.contains('delete')) {
        // When the checkbox is clicked, check for changes in the form
        const form = event.target.closest('form.post-actions');
        if (form) {
            modifyReportForm(form);
        }
    }
});

// Custom CSS for .deadlink class
const style = `<style>
.deadlink {
  text-decoration: line-through !important;
  color: #789922;
}
</style>`;
document.head.innerHTML += style;

// Modify quote links to be styled as dead links
const quote = document.querySelectorAll('.quote');

quote.forEach(elem => {
  if (/^&gt;&gt;[1-9]+[0-9]*$/.test(elem.innerHTML)) {
    const postNum = elem.innerHTML;
    elem.outerHTML = `<span class="deadlink">${postNum}</span>`;
  }
});

// Fix post times (relative and exact)
function fixTime() {
  document.querySelectorAll(".post").forEach(e => {
    let timeElement = e.querySelector("[datetime]");
    let time = new Date(Date.parse(timeElement.getAttribute("datetime")));
    let exactTime = `${("0" + (time.getMonth() + 1)).slice(-2)}/${("0" + time.getDate()).slice(-2)}/${time.getYear().toString().slice(-2)} (${weekdays[time.getDay()]}) ${("0" + time.getHours()).slice(-2)}:${("0" + time.getMinutes()).slice(-2)}:${("0" + time.getSeconds()).slice(-2)}`;
    let relativeTime;
    let difference = (Date.now() - time.getTime()) / 1000;

    if (difference < 10) relativeTime = "Just now";
    else if (difference < 60) relativeTime = `${Math.floor(difference)} seconds ago`;
    else if (difference < 120) relativeTime = `1 minute ago`;
    else if (difference < 3600) relativeTime = `${Math.floor(difference / 60)} minutes ago`;
    else if (difference < 7200) relativeTime = `1 hour ago`;
    else if (difference < 86400) relativeTime = `${Math.floor(difference / 3600)} hours ago`;
    else if (difference < 172800) relativeTime = `1 day ago`;
    else if (difference < 2678400) relativeTime = `${Math.floor(difference / 86400)} days ago`;
    else if (difference < 5356800) relativeTime = `1 month ago`;
    else if (difference < 31536000) relativeTime = `${Math.floor(difference / 2678400)} months ago`;
    else if (difference < 63072000) relativeTime = `1 year ago`;
    else relativeTime = `${Math.floor(difference / 31536000)} years ago`;

    if (isEnabled("force-exact-time")) {
      timeElement.innerText = exactTime;
      timeElement.setAttribute("title", relativeTime);
    } else {
      timeElement.innerText = relativeTime;
      timeElement.setAttribute("title", exactTime);
    }
  });
}

// Initialize post fixes
function initFixes() {
  // Add formatting help to comment section
  document.querySelectorAll("form[name=post] th").forEach(e => {
    if (e.innerText === "Comment") {
      e.insertAdjacentHTML("beforeend", `<sup title="Formatting help" class="sf-formatting-help">?</sup><br><div class="comment-quotes"><a href="javascript:void(0);" class="comment-quote">[>]</a><a href="javascript:void(0);" class="comment-orange">[<]</a></div>`);
    }
  });

// Add the formatting help next to "Email" column header with a new class
document.querySelectorAll("form[name=post] th").forEach(e => {
  if (e.innerText === "Email") {
    // Insert the formatting help button next to the "Email" header with a new class
    e.insertAdjacentHTML("beforeend", `<sup title="Formatting help for Email" class="sf-formatting-help-email">?</sup>`);
  }
});

// Handle the click event for the new "sf-formatting-help-email" button
document.addEventListener('click', function(event) {
  let t = event.target;

  // Check if the clicked element is the "sf-formatting-help-email" button (for "Email")
  if (t.classList.contains("sf-formatting-help-email")) {
    // Trigger the site's existing popup system (if it exists, e.g., customAlert)
    customAlert(`
      <h1>Email Field</h1>
      <p>bump</p>
      <p>sage</p>
      <p>supersage</p>
      <p>anonymous</p>
      <p>anonymous sage</p>
      <p>flag</p>
      <p>flag sage</p>
    `);  // Show the custom message with multiple lines inside the existing popup
  }
});

  // Add file selection URL input if GM_xmlhttpRequest is available (i don't think this is needed anymore, removing or keeping it doesn't break anything though.)
  if (typeof GM_xmlhttpRequest === "function") {
    let fileSelectionInterval = setInterval(() => {
      if (select = document.querySelector("#upload_selection")) {
        select.childNodes[0].insertAdjacentHTML('afterend', ` / <a href="javascript:void(0)" id="sf-file-url"></a>`);
        clearInterval(fileSelectionInterval);
      }
    }, 100);
  }

  // Handle dynamic updates
  document.addEventListener("dyn_update", e => {
    e.detail.forEach(e => fixPost(e));
    fixTime();
    addExpandos();
  });

  // Handle post updates
  document.addEventListener("post_update", e => {
    e.detail.forEach(node => {
      if (node.addedNodes[0].nodeName === "DIV") {
        fixPost(node.addedNodes[0]);
      }
    });
    fixTime();
    addExpandos();
  });

  // Apply fixes to existing posts
  [...document.getElementsByClassName("post")].forEach(e => {
    fixPost(e);
  });
  fixTime();
  addExpandos();
}

// DONT REMOVE THIS PART, IT WILL BREAK THE SCRIPT
// undo filter
function undoFilter(post) {
  // if (isEnabled("restore-filtered")) {
  //   post.querySelectorAll(".body, .body *, .replies, .replies *").forEach(e => {
  //     e.childNodes.forEach(e => {
  //       if (e.nodeName == "#text") {
  //         e.nodeValue = e.nodeValue.replaceAll("im trans btw", "kuz");
  //       }
  //     });
  //   });
  // }
}


// Catalog Fixes: Adjust thread timestamps & undo filters
document.querySelectorAll("#Grid > div").forEach(e => {
  let threadTime = new Date(parseInt(e.getAttribute("data-time")) * 1000);
  e.getElementsByClassName("thread-image")[0].setAttribute("title", `${months[threadTime.getMonth()]} ${("0" + threadTime.getDate()).slice(-2)}` +
    ` ${("0" + threadTime.getHours()).slice(-2)}:${("0" + threadTime.getMinutes()).slice(-2)}`);
  undoFilter(e);
});

// Keyboard Shortcuts
window.addEventListener("keydown", e => {
  if (e.key == "Enter" && (e.ctrlKey || e.metaKey)) {
    if (form = e.target.closest("form[name=post]")) {
      form.querySelector("input[type=submit]").click();
    }
  }
});


// Autofocus Textarea on Certain Pages
if ((textarea = document.querySelector("textarea[name=body]")) && document.documentElement.classList.contains("desktop-style") && window.location.hash[1] != "q") {
  textarea.focus({
    preventScroll: true
  });
}

    // --- Character Counter Feature ---
    const observer = new MutationObserver(() => {
        const commentHeader = Array.from(document.querySelectorAll('th')).find(el => el.textContent.includes('Comment'));

        if (commentHeader) {
            // Stop observing once the Comment header is found
            observer.disconnect();

            // Create the character counter container under the "Comment" heading
            const counterContainer = document.createElement('div');
            counterContainer.style.marginTop = '10px';

            const counterText = document.createElement('span');
            counterText.setAttribute('id', 'char-count');
            counterText.textContent = '0 / 24000';

            counterContainer.appendChild(counterText);
            commentHeader.appendChild(counterContainer);

            // Find the textarea for the comment
            const textArea = document.querySelector('textarea[name="body"]');
            if (textArea) {
                // Update character count as the user types
                textArea.addEventListener('input', function() {
                    const currentLength = textArea.value.length;
                    const maxLength = 24000;

                    // Update the counter
                    counterText.textContent = `${currentLength} / ${maxLength}`;
                });
            }
        }
    });

    // Start observing the DOM for changes in the body element
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

// Password Box Toggle (Show/Hide Password)
if (passwordBox = document.querySelector("form[name=post] input[name=password]")) {
  passwordBox.setAttribute("type", "password");
  passwordBox.insertAdjacentHTML("afterend", `<input type="button" name="toggle-password" value="Show">`);
}


// Form Submit Cooldown
document.querySelectorAll("form[name=post] input[type=submit]").forEach(e => {
  e.setAttribute("og-value", e.getAttribute("value"));
});

setInterval(() => {
  let lastTime = getNumber("lastTime");
  let difference = 11 - Math.ceil((Date.now() - lastTime) / 1000);
  let buttons = document.querySelectorAll("form[name=post] input[type=submit]");

  if ([...buttons].find(e => e.value.includes("Post"))) {
    return;
  } else if (difference > 0) {
    let disableButton = isEnabled("disable-submit-on-cooldown");
    buttons.forEach(e => {
      e.value = `${e.getAttribute("og-value")} (${difference})`;
      if (disableButton) {
        e.setAttribute("disabled", "disabled");
      }
    });
  } else {
    buttons.forEach(e => {
      e.value = e.getAttribute("og-value");
      e.removeAttribute("disabled");
    });
  }
}, 100);


// Thread Hiding Logic
function areHiddenThreads() {
  let threadGrid = document.getElementById("Grid");
  if (document.querySelector(".catty-thread.hidden")) {
    if (!document.getElementById("toggle-hidden")) {
      document.querySelector(".desktop-style #image_size, .mobile-style header").insertAdjacentHTML("afterend", `<span id="toggle-hidden"></span>`);
    }
  } else if (toggleButton = document.getElementById("toggle-hidden")) {
    toggleButton.remove();
    document.body.classList.remove("showing-hidden");
  }
}


// Catalog Navigation and Search Form
if (document.body.classList.contains("active-catalog")) {
  if (isEnabled("catalog-navigation")) {
    document.querySelectorAll(".boardlist a[href*='index.html']").forEach(e => e.href = e.href.replace("index.html", "catalog.html"));
  }

  document.querySelector("#image_size").insertAdjacentHTML("afterend", `
    <form style="display: inline-block; margin-bottom: 0px; height: 10px;" action="/search.php">
      <p>
        <input type="text" name="search" placeholder="${board} search">
        <input type="hidden" name="board" value="${board}">
        <input type="submit" value="Search">
      </p>
    </form>
  `);

  let hiddenThreads = getJson("hiddenthreads");
  let hasThreads = hiddenThreads.hasOwnProperty(board);

  document.querySelectorAll(".mix").forEach(e => {
    e.classList.replace("mix", "catty-thread");
    if (hasThreads && hiddenThreads[board].hasOwnProperty(e.getAttribute("data-id"))) {
      e.classList.add("hidden");
      delete hiddenThreads[board][e.getAttribute("data-id")];
    }
    if (e.getAttribute("data-sticky") == "true") {
      e.parentNode.prepend(e);
    }
  });

  if (hasThreads) {
    Object.keys(hiddenThreads[board]).forEach(e => {
      removeFromJson("hiddenthreads", `${board}.${e}`);
    });
  }

  areHiddenThreads();
}


// Event Listeners for Clicks and Inputs
document.addEventListener("click", e => {
  let t = e.target;

  // Submit button in post form
  if (t.matches("form[name=post] input[type=submit]")) {
    t.value = t.getAttribute("og-value");

    // Bypass filter and modify text
    if (isEnabled("bypass-filter")) {
      let textbox = t.closest("tbody").querySelector("textarea[name=body]");
      textbox.value = textbox.value.replaceAll(/(discord)/ig, str => {
        let arr = [];
        while (!arr.includes("​")) {
          arr = [];
          [...str].forEach((c, i) => {
            if (Math.random() < 0.5 && i != 0) {
              arr.push("​");
            }
            arr.push(c);
            if (Math.random() > 0.5 && i != str.length - 1) {
              arr.push("​");
            }
          });
        }
        return arr.join("");
      });
    }
  }
  // Toggle password visibility
  else if (t.matches("input[name=toggle-password]")) {
    if (passwordBox.getAttribute("type") == "password") {
      passwordBox.setAttribute("type", "text");
      t.value = "Hide";
    } else {
      passwordBox.setAttribute("type", "password");
      t.value = "Show";
    }
  }

  // Mass reply functionality
  else if (t.id == "mass-reply") {
    let massReply = "";
    document.querySelectorAll("[href*='#q']").forEach(e => {
      massReply += `>>${e.textContent}\n`;
    });
    document.querySelectorAll("textarea[name=body]").forEach(e => {
      e.value += massReply;
      e.focus();
    });
  }

  // Mass quote or mass orange functionality
  else if (t.id == "mass-quote" || t.id == "mass-orange") {
    document.body.classList.add("hide-quote-buttons");
    let selection = window.getSelection();
    let range = document.createRange();
    range.selectNodeContents(document.body);
    selection.removeAllRanges();
    selection.addRange(range);

    let massQuote = window.getSelection().toString().replace(/(\r\n|\r|\n|^)/g, t.id == "mass-quote" ? "$1>" : "$1<") + "\n";
    selection.removeAllRanges();
    document.body.classList.remove("hide-quote-buttons");

    document.querySelectorAll("textarea[name=body]").forEach(e => {
      e.value += massQuote;
      e.focus();
    });
  }

  // Quick quote or quick orange functionality
  else if (t.classList.contains("quick-quote") || t.classList.contains("quick-orange")) {
    let quote = t.closest(".post").querySelector(".body").innerText.replace(/(\r\n|\r|\n|^)/g, t.classList.contains("quick-quote") ? "$1>" : "$1<") + "\n";
    document.querySelectorAll("textarea[name=body]").forEach(e => {
      e.value += quote;
      e.focus();
    });
  }

  // Comment quote or comment orange functionality
  else if (t.classList.contains("comment-quote") || t.classList.contains("comment-orange")) {
    document.querySelectorAll("textarea[name=body]").forEach(e => {
      e.value = e.value.replace(/(\r\n|\r|\n|^)/g, t.classList.contains("comment-quote") ? "$1>" : "$1<");
      e.focus();
    });
  }

  // Toggle visibility of threads
  else if ((e.shiftKey || (e.detail == 3 && (document.documentElement.matches(".mobile-style") || isEnabled("desktop-triple-click")))) &&
           t.matches(".active-catalog .catty-thread *, .active-catalog .catty-thread")) {
    e.preventDefault();
    let thread = t.closest(".catty-thread");
    thread.classList.toggle("hidden");

    if (thread.classList.contains("hidden")) {
      addToJson("hiddenthreads", `${board}.${thread.getAttribute("data-id")}`, Math.floor(Date.now() / 1000));
    } else {
      removeFromJson("hiddenthreads", `${board}.${thread.getAttribute("data-id")}`);
    }
    areHiddenThreads();
  }

  // Toggle hidden threads visibility
  else if (t.id == "toggle-hidden") {
    document.body.classList.toggle("showing-hidden");
  }

  // Hide/unhide thread link functionality
  else if (t.classList.contains("hide-thread-link") || t.classList.contains("unhide-thread-link")) {
    setValue("hiddenthreads", localStorage.getItem("hiddenthreads"));
  }

  // Hide blotter functionality
  else if (t.classList.contains("hide-blotter")) {
    setValue("hidden-blotter", document.querySelector(".blotter").innerText);
    document.body.classList.add("hidden-blotter");
  }

  // SF-expander button functionality (manual cutoff)
  else if (t.classList.contains("sf-expander")) {
    t.closest(".post").setAttribute("manual-cutoff", t.closest(".post").classList.toggle("sf-cutoff"));
  }

  // Formatting help popup
  else if (t.classList.contains("sf-formatting-help")) {
    let help = `
      <h1>Comment Field</h1>
      <span class="heading">Font Guide</span><br>
      <span class="spoiler">**Spoiler**</span><br>
      <em>''Italics''</em><br>
      <b>'''Bold'''</b><br>
      <code>\`\`\`Codetext\`\`\`</code><br>
      <u>__Underline__</u><br>
      <s>~~Strikethrough~~</s><br>
      <big>+=Bigtext=+</big><br>
      <span class="rotate">##Spintext##</span><small><sup> (disabled)</sup></small><br>
      <span class="quote">&gt;Greentext</span><br>
      <span class="quote2">&lt;Orangetext</span><br>
      <span class="quote3" style="color: #6577E6;">^Bluetext</span><br>
      <span class="heading">==Redtext==</span><br>
      <span class="heading2">--Bluetext--</span><br>
      <font color="FD3D98"><b>-~-Pinktext-~-</b></font><br>
      <span class="glow">%%Glowtext%%</span><br>
      <span style="text-shadow:0px 0px 40px #36d7f7, 0px 0px 2px #36d7f7">;;Blueglowtext;;</span><br>
      <span style="text-shadow:0px 0px 40px #fffb00, 0px 0px 2px #fffb00">::Yellowglowtext::</span><br>
      <span style="text-shadow:0px 0px 40px #ff0000, 0px 0px 2px #ff0000">!!Redglowtext!!</span><small><sup> (put after each word)</sup></small><br>
      <span style="background: linear-gradient(to left, red, orange , yellow, green, cyan, blue, violet);-webkit-background-clip: text;-webkit-text-fill-color: transparent;">~-~Rainbowtext~-~</span><br>
      <span class="glow"><span style="background: linear-gradient(to left, red, orange , yellow, green, cyan, blue, violet);-webkit-background-clip: text;-webkit-text-fill-color: transparent;">%%~-~Gemeraldtext~-~%%</span></span><br>
      <span style="background:#faf8f8;color:#3060a8">(((Zionisttext)))</span><br>
      <br>
      <span class="heading">Linking</span><br>
      Link to a post on the current board<br>
      >>1234<br>
      Link to another board<br>
      >>>/soy/<br>
      Link to a post on another board<br>
      >>>/soy/1234<br>
      <br>
      <span class="heading">Wordfilters</span><br>
      See <a href="https://wiki.soyjak.st/Wordfilter" target="_blank">https://wiki.soyjak.st/Wordfilter</a><br>
    `;
    customAlert(help);
  }
});

// Search Input Filter for Catalog Threads
document.addEventListener("input", e => {
  let t = e.target;
  if (t.matches("input[name=search]") && document.querySelector(".sf-catty, .active-catalog")) {
    document.querySelectorAll(".catty-thread").forEach(e => {
      if (e.innerText.toLowerCase().includes(t.value.toLowerCase())) {
        e.classList.remove("sf-filtered");
      } else {
        e.classList.add("sf-filtered");
      }
    });
  }
});


// Blotter Hide/Show Button
if (blotter = document.querySelector(".blotter")) {
  blotter.insertAdjacentHTML("beforebegin", `<a class="hide-blotter" href="javascript:void(0)">[–]</a>`);
  if (blotter.innerText == getValue("hidden-blotter") || isEnabled("hide-blotter")) {
    document.body.classList.add("hidden-blotter");
  }
}

document.head.insertAdjacentHTML("beforeend", `
  <style>
    /* Hide elements in specific conditions */
    .hide-blotter {
      float: left;
    }

    .hidden-blotter .blotter,
    .hidden-blotter .blotter + hr,
    .hidden-blotter .hide-blotter,
    .catty-thread.hidden,
    .showing-hidden .catty-thread,
    .mobile-style .g-recaptcha-bubble-arrow,
    .catty-thread.sf-filtered,
    .showing-hidden .catty-thread.hidden.sf-filtered,
    .hide-quote-buttons .quote-buttons {
      display: none !important;
    }

    /* Styling for expander button in replies */
    .reply .sf-expander {
      margin-left: 1.8em;
      padding-right: 3em;
      padding-bottom: 0.3em;
    }

    .sf-expander::after {
      content: "[Hide Full Text]";
    }

    .sf-cutoff .sf-expander::after {
      content: "[Show Full Text]";
    }

    /* File URL input styling */
    #sf-file-url::after {
      content: "URL";
    }

    #sf-file-url.sf-loading::after {
      content: "Loading...";
    }

    /* Hover styling for sharty */
    #sharty-hover {
      pointer-events: none;
      position: fixed;
      z-index: 500;
    }

    /* Display adjustments for threads */
    .catty-thread,
    .showing-hidden .catty-thread.hidden {
      display: inline-block !important;
    }

    /* Toggle hidden threads button styling */
    #toggle-hidden {
      text-decoration: underline;
      color: #34345C;
      cursor: pointer;
      user-select: none;
    }

    #toggle-hidden::before {
      content: "[Show Hidden]";
    }

    .showing-hidden #toggle-hidden::before {
      content: "[Hide Hidden]";
    }

    #image_size + #toggle-hidden {
      display: inline-block;
      padding-left: 5px;
    }

    header + #toggle-hidden {
      display: block;
      margin: 1em auto;
      width: fit-content;
    }

    /* Recaptcha bubble styling on mobile */
    .mobile-style .g-recaptcha-bubble-arrow + div {
      position: fixed !important;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
      -webkit-transform: translate(-50%, -50%);
    }

    /* Password input field adjustments */
    input[name=toggle-password] {
      margin-left: 2px;
    }

    /* Formatting help link styling */
    .sf-formatting-help {
      cursor: pointer;
    }

    /* Formatting email link styling */
    .sf-formatting-help-email {
      cursor: pointer;
    }

    /* Comment quote button styling */
    .comment-quotes {
      text-align: center;
    }

    /* Truncate long posts styling (conditionally enabled) */
    ${isEnabled("truncate-long-posts") ? `
      .sf-cutoff:not(.post-hover) .body {
        overflow: hidden;
        word-break: break-all;
        display: -webkit-box;
        min-width: min-content;
        -webkit-line-clamp: 20;
        -webkit-box-orient: vertical;
      }

      .sf-cutoff.post-hover .sf-expander {
        display: none !important;
      }

      div.post.reply.sf-cutoff div.body {
        margin-bottom: 0.3em;
        padding-bottom: unset;
      }
    ` : ""}
  </style>
`);

// Initialize additional fixes
initFixes();