您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Comment on tags via a popup modal, which even has some text formatting options!!
// ==UserScript== // @name AO3: [Wrangling] Comment on tags without leaving bins!!! // @description Comment on tags via a popup modal, which even has some text formatting options!! // @version 2.0.0 // @author owlwinter // @namespace N/A // @license MIT license // @match *://*.archiveofourown.org/tags/*/wrangle?* // @grant none // ==/UserScript== (function() { 'use strict'; //Important: If you use iconify, you'll need to set this to be true once installed!! const ICONIFY = false; //Checks if using dark mode const darkmode = window.getComputedStyle(document.body).backgroundColor == 'rgb(51, 51, 51)' //This will load FontAwesome so the icons will properly render var font_awesome_icons = document.createElement('script'); font_awesome_icons.setAttribute('src', 'https://use.fontawesome.com/ed555db3cc.js'); document.getElementsByTagName('head')[0].appendChild(font_awesome_icons); var fa_icons_css = document.createElement('style'); fa_icons_css.setAttribute('type', 'text/css'); fa_icons_css.innerHTML = ".comment-formatting, ul.actions { font-family: FontAwesome, Lucida Grande, Lucida Sans Unicode;}" document.getElementsByTagName('head')[0].appendChild(fa_icons_css); //If the user is in an empty bin, nothing will happen if (document.getElementById("wrangulator") == null) { return } //Grabbing the link connected to the edit button const actionsbuttons = document.getElementById("wrangulator").querySelectorAll("td > ul.actions") const array = a => Array.prototype.slice.call(a, 0) const get_url = function get_url(label) { // This will return the link if iconify is enabled const a = label.parentElement.parentElement.querySelector("ul.actions > li[title='Edit'] > a"); if (a) { return a.href; } // If there's no iconify, we'll stick with the default path const buttons = label.parentElement.parentElement.querySelectorAll("ul.actions > li > a"); return array(buttons).filter(b => b.innerText == "Edit")[0].href; } //Adding a comment button after the tag options for (const buttonset of actionsbuttons) { //UW Tag Snooze Buttons script support if (buttonset.querySelector('a').href == "") { continue } //And so begins our decent into madness //here be dragons, but I'll do my best to comment them all const newli = document.createElement("li") newli.title = "Add Comment" const button = document.createElement("a"); button.style.textAlign = "center" //If the user has iconify set to be true, we'll show a very cute comment+ icon //so they can keep using iconify if they so wish, but there's not two identical icons //you are welcome, iconify users button.textContent = ICONIFY ? "\u{f086} \u{f067}" : "Comment"; button.href = "#"; newli.appendChild(button) buttonset.appendChild(newli) //If you want the "Works" button to be last, replace that with the following line: //buttonset.insertBefore(newli, buttonset.children[buttonset.childElementCount -2]) //When any of the comment buttons have been clicked button.addEventListener("click", (e) => { e.preventDefault() //If there's already a comment box modal open, close out of it if (document.getElementById("commentbox_id") != null) { document.body.removeChild(document.getElementById("commentbox_id")) } //Creating the comment box modal const newdiv = document.createElement("div") newdiv.id = "commentbox_id" newdiv.style.position = "fixed" newdiv.style.top = "25%" newdiv.style.left = "25%" newdiv.style.width = "50%" if (darkmode) { //...heh newdiv.style.backgroundColor = "#696969" } else { newdiv.style.backgroundColor = "rgb(221, 221, 221)" } newdiv.style.border = "1px solid black" newdiv.style.padding = "5px" //the most important part of course ! ;) newdiv.style.borderRadius = "5px" //This chunk is for the text above the comment text box //the following set of divs and spans is SUCH a mess I KNOW I am so sorry i regret it too //But anyways I spent like three hours making this still be pretty when you make the webpage thinner or wider //so pls admire that at least once, just for me <3 const titlediv = document.createElement("div") titlediv.setAttribute("style", "margin-bottom: 5px;"); const newdivtitle = document.createTextNode("Comment on tag: ") const title = document.createElement("span") //Adding the tag's text and then becasue we are cool, making it a hyperlink const label = buttonset.parentElement.parentElement.firstElementChild.getElementsByTagName("label")[0] const tag_title = document.createElement("a") tag_title.target = "_blank" tag_title.innerText = label.innerText; tag_title.href = get_url(label) if (darkmode) { tag_title.style.color = "white" } else { tag_title.style.color = "cornflowerblue" } let pseud_id = null; title.appendChild(tag_title); title.style.fontStyle = "italic"; titlediv.appendChild(newdivtitle) titlediv.appendChild(title) //The html formatting options we're offering - bold, italics, underline etc //a lot of that part was based on the AO3: Comment Formatting Options script by dusty //https://gf.qytechs.cn/en/scripts/31400-ao3-comment-formatting-options //Feel free to customize the below to suit your wrangling needs!!! //The format is button_name: [["Tooltip", "Text on button or fontawesome icon number"], ["What shows up before selected text", "What shows up after selected text"]], //For example, try adding the following: //ffu: [["freeform for you", "FF"], ["Freeform for you: ", ""]] //Also add a comma after every line except for the last one! var commentFormatting = document.createElement("ul"); var commentFormattingOptions = { bold_text: [["Bold", "\u{f032}"], ["<strong>", "</strong>"]], italic_text: [["Italic", "\u{f033}"], ["<em>", "</em>"]], underline_text: [["Underline", "\u{f0cd}"], ["<u>", "</u>"]], strike_text: [["Strikethrough", "\u{f0cc}"], ["<s>", "</s>"]], insert_link: [["Insert Link", "\u{f0c1}"], ['<a href="">', "</a>"]], insert_image: [["Insert Image", "\u{f03e}"], ['<img src="">']], blockquote_text: [["Blockquote", "\u{f10d}"], ["<blockquote>", "</blockquote>"]] } commentFormatting.id = "comment_formatting" commentFormatting.setAttribute("class", "actions comment-formatting"); commentFormatting.setAttribute("style", "float: left; text-align: left; margin-bottom: 3px;"); //Setting up each button for the html options we are offering for (let key in commentFormattingOptions) { var commentFormattingOptionItem = document.createElement("li"); var commentFormattingOptionLink = document.createElement("a"); commentFormattingOptionItem.setAttribute("class", key); commentFormattingOptionItem.setAttribute("title", commentFormattingOptions[key][0][0]); commentFormattingOptionItem.style.paddingLeft = "0px" commentFormattingOptionItem.style.paddingRight = "2px" commentFormattingOptionItem.style.fontSize = "80%" commentFormattingOptionItem.style.margin = "0" commentFormattingOptionLink.textContent = commentFormattingOptions[key][0][1]; commentFormattingOptionLink.setAttribute("style", "margin: 1px;"); commentFormattingOptionItem.appendChild(commentFormattingOptionLink); commentFormatting.appendChild(commentFormattingOptionItem); //the actual magic when you click each html options button commentFormattingOptionLink.addEventListener("click", (e) => { e.preventDefault() //the beginning and the end of the text the user is highlighting, and the value of that text var caretPos = commentFormattingOptionLink.parentElement.parentElement.parentElement.querySelector("TextArea").selectionStart; var caretEnd = commentFormattingOptionLink.parentElement.parentElement.parentElement.querySelector("TextArea").selectionEnd; var textAreaTxt = commentFormattingOptionLink.parentElement.parentElement.parentElement.querySelector("TextArea").value; var formatToAdd var highlightingtext if (caretPos == caretEnd) { //if the user isn't highlighting any text (ie their cursor is just chilling) formatToAdd = commentFormattingOptions[key][1].join(""); highlightingtext = false } else { //if the user is highlighting text var textAreaHighlight = textAreaTxt.slice(caretPos, caretEnd); formatToAdd = commentFormattingOptions[key][1].join(textAreaHighlight); highlightingtext = true } //adding the html formatting!! commentFormattingOptionLink.parentElement.parentElement.parentElement.querySelector("TextArea").value = textAreaTxt.substring(0, caretPos) + formatToAdd + textAreaTxt.substring(caretEnd); commentFormattingOptionLink.parentElement.parentElement.parentElement.querySelector("TextArea").focus(); //this took a hot minute to figure out how to do if (highlightingtext) { //If the user is highlighting text (ie they want to bold the word 'thing'), the cursor will move to after the closing html tag //so they can just continue typing the next word commentFormattingOptionLink.parentElement.parentElement.parentElement.querySelector("TextArea").selectionStart = caretEnd + commentFormattingOptions[key][1][0].length + commentFormattingOptions[key][1][1].length commentFormattingOptionLink.parentElement.parentElement.parentElement.querySelector("TextArea").selectionEnd = caretEnd + commentFormattingOptions[key][1][0].length + commentFormattingOptions[key][1][1].length } else { //if the user is not highlighting text, we'll put the cursor in the middle of the html tags //so that they can type what it is they want bolded, italicized etc commentFormattingOptionLink.parentElement.parentElement.parentElement.querySelector("TextArea").selectionStart = caretPos + commentFormattingOptions[key][1][0].length commentFormattingOptionLink.parentElement.parentElement.parentElement.querySelector("TextArea").selectionEnd = caretPos + commentFormattingOptions[key][1][0].length } }); } //The textbox the user can type their comment in const textinput = document.createElement("textarea"); textinput.style.width = "98%" textinput.style.height = "250px" textinput.style.display = "block" textinput.style.resize = "none" textinput.style.marginTop = "5px" //again, the most important part ! ;) textinput.style.borderRadius = "3px" //the cancel/save button part const buttondiv = document.createElement("div") const savebutton = document.createElement("button"); savebutton.style.textAlign = "center" //OK SO THIS WAS!!! A PAIN!!! AND A HALF!!!! TO FIGURE OUT!!!!!!!!! //But!!!! The tag ID that we have immediate access to is NOT the same as the tag ID wanted in the POST request to actually send the comment!!!! //So we have to go grab the correct tag ID //BUT! that takes a small amount of time //SO! we make the 'comment' button say 'Loading' until that ID is figured out (and also disable that button) //then afterwords we change it to say "comment"! //the actual place we grab the correct tag ID is a bit later, just wanted to explain why it starts as 'Loading' here savebutton.textContent = "Loading..."; savebutton.disabled = true; //When the save button is clicked savebutton.addEventListener("click", (e) => { savebutton.disabled = true; //don't want empty comments if (textinput.value.length == 0) { alert("Brevity is the soul of wit, but we need your comment to have text in it.") //We re-enable the button after any error message shows up so that the user can edit their comment and attempt to do better savebutton.textContent = "Comment"; savebutton.disabled = false; return } //THIS WAS ANOTHER PAIN AND A HALF TO FIGURE OUT, MY GOD //So basically, when we submit the comment //The character count doesn't include the paragraph tags: <p> and </p> //So something that is one paragraph and the maximum of 10000 characters is actually 10007 characters and will make the surver angry at us //So what we do, is grab the number of paragraphs in the user's text //and multiply that by 7 (the character count of each '<p></p>' that is added) //then we add THAT to the length of the user text //and BOOM!!! the actual length of what we are submitting //so now we can accurately tell the user if their text is too long var paragraphhtmllen = textinput.value.replace(/\n$/gm, '').split(/\n/).length * 7; var textinputlengthactual = textinput.value.length + paragraphhtmllen if (textinputlengthactual >= 10000) { alert("Comment is too long; please restrict to 10000 characters or less, including <p></p> tags.") savebutton.textContent = "Comment"; savebutton.disabled = false; return } //what actually submits the comment!! const xhr2 = new XMLHttpRequest(); xhr2.onreadystatechange = function xhr_onreadystatechange() { if (xhr2.readyState == xhr2.DONE) { if (xhr2.status == 200) { //So we can get a 200 OK status but still have an error !!!!!!! //So we check if the response has an error in it //And if so, pass the error up to the user let error = xhr2.responseXML.documentElement.querySelector("#error") if (error) { alert(error.innerText); savebutton.textContent = "Comment"; savebutton.disabled = false; } else { //happy path!! //Change the button text to say 'commented' to show the user that their comment was submitted //Then remove the comment modal after half a second savebutton.textContent = "Commented!"; setTimeout(function(){ if (newdiv.parentElement != null) { document.body.removeChild(newdiv) } }, 500); } } else if (xhr2.status == 429) { // go to ao3 jail do not pass go do not collect $200 // honestly tho if anyone ever submits so many comments that they'd get rate limited // i'd just be impressed alert("Rate limited. Sorry :(") } else { // .....less happy path alert("Error - check console for details.") console.log(xhr2) } } } //grabbing everything that we need in order to post the comment //for exampe, what's in the textfield const fd = new FormData() fd.set("comment[comment_content]", textinput.value) fd.set("tag_id", buttonset.parentElement.parentElement.firstElementChild.getElementsByTagName("label")[0].innerText); fd.set("controller_name", "comments") fd.set("comment[pseud_id]", pseud_id) //Copy auth token from the current page fd.set("authenticity_token", document.getElementsByName("authenticity_token")[0].value) xhr2.open("POST", "/comments") xhr2.responseType = "document" //And off we go! xhr2.send(fd) savebutton.textContent = "Commenting..."; }) //The cancel button const cancelbutton = document.createElement("button"); cancelbutton.style.textAlign = "center" cancelbutton.textContent = "Cancel"; cancelbutton.style.marginRight = "5px" //When the user clicks 'cancel,' we close out of the comment box cancelbutton.addEventListener("click", (e) => { if (newdiv.parentElement != null) { document.body.removeChild(newdiv) } }) //Adding cancel/save buttons to the same div and right justifying them buttondiv.appendChild(cancelbutton) buttondiv.appendChild(savebutton) buttondiv.style.textAlign = "right" buttondiv.style.marginTop = "5px" //Adding everything to the comment popup!! newdiv.appendChild(titlediv) newdiv.appendChild(commentFormatting) newdiv.appendChild(textinput) newdiv.appendChild(buttondiv) //This is the bizzare thing we have to do in order to get the ACTUAL tag ID that we need //when submitting the comment - see comments above savebutton.textContent lines for more details const xhr = new XMLHttpRequest(); xhr.onreadystatechange = function xhr_onreadystatechange() { if (xhr.readyState == xhr.DONE ) { if (xhr.status == 200) { //THIS WAS AN ABSOLUTE PAIN //AN. ABSOLUTE. PAIN. //AN ABSOLUTE PAIN!!!!!!!! to figure out //But the page is actually different if the user commenting has a pseud: //If a user has pseuds, they'll see a dropdown menu (a "select" element) - if they don't, there is a hidden "input" element. // the * will catch them both! const pseud_id_elem = xhr.responseXML.documentElement.querySelector("*[name='comment[pseud_id]']") pseud_id = pseud_id_elem.value if (pseud_id_elem.tagName == "SELECT") { //Makes a dropdown menu that lets the user select which pseud to comment from const options = pseud_id_elem.options const select = document.createElement("select") array(options).forEach(o => { const option = document.createElement("option") option.value = o.value option.innerText = o.innerText select.prepend(option); }); select.value = pseud_id_elem.value; select.addEventListener("change", () => { pseud_id = select.value; }); commentFormatting.appendChild(select) } savebutton.textContent = "Comment"; savebutton.disabled = false; } else { alert("Something broke, sorry :( - check the console") console.log(xhr) } } } const comments_url = get_url(label).replace(/\/edit$/, "/comments") xhr.open("GET", comments_url) xhr.responseType = "document" xhr.send() document.body.appendChild(newdiv) //After the modal pops up, start with the textfield selected so you can type right away newdiv.querySelector("textarea").select() }) } // Your code here... })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址