- // ==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 (/^>>[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">>Greentext</span><br>
- <span class="quote2"><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();