您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds an option to make text bold/italic?/underlined/colorful in the escgo! chat. I tried to keep it as ES5-friendly as possible.
// ==UserScript== // @name escgo! colors in webchat - legacy // @version 0.7.6 // @description Adds an option to make text bold/italic?/underlined/colorful in the escgo! chat. I tried to keep it as ES5-friendly as possible. // @author Andrei Felix // @match http://www.escgo.com/wp-content/uploads/euwebirc-master/static/qui.html // @match http://www.escgo.com/wp-content/uploads/euwebirc-master2/static/qui.html // @match http://webchat.euirc.net/ // @icon http://www.escgo.com/wp-content/uploads/2017/04/cropped-escgologolarge-32x32.png // @grant none // @run-at document-end // @namespace https://greasyfork.org/users/868721 // ==/UserScript== (function() { // this function waits until it can find the box you type stuff in // couldn't risk using querySelector, apparently it's a 2013 thing function getTextBox(fn) { var result = document.getElementsByClassName("keyboard-input")[0]; if (result === undefined) { window.setTimeout(function() { getTextBox(fn); }, 1000); return; } fn(result); } // this adds a style element containing everything needed for this script // TODO: check whether getComputedStyle works on older browsers // no template literals = :( function addCustomCss() { var ircStyle = getComputedStyle(document.getElementsByClassName("lines")[0]); var bgColor = ircStyle.backgroundColor; var fgColor = ircStyle.color; ircStyle = getComputedStyle(document.getElementsByClassName("bottomboundpanel")[0]); var bdColor = ircStyle.borderTopColor; ircStyle = getComputedStyle(document.getElementsByClassName("tab-selected")[0]); var btnColor = ircStyle.backgroundColor; var customCss = ".qwebirc-qui.bottomboundpanel form{padding-left:1.3em;padding-right:2px} " + ".qwebirc-qui .input input.keyboard-input{padding-left:2px} " + "#formatArea{position:absolute;width:1.2em;height:1.2em;left:0;top:0;bottom:0;margin:0;padding:0} " + "#formatBtn{position:relative;width:100%;height:100%;left:0;top:0;text-align:center;" + "background:" + btnColor + ";font-weight:bold;font-style:italic;text-decoration:underline;color:cyan;cursor:default} " + "#formatMenu{display:none;position:absolute;top:auto;left:0;bottom:100%;padding:0.1em 0 0.1em 0.3em;" + "border:1px " + bdColor + " solid;background:" + bgColor + ";white-space:nowrap;font-size:85%;text-align:left} " + "#formatMenu .colourline{display:inline-block;white-space:nowrap} " + "#formatArea:focus #formatMenu,#formatArea:focus-within #formatMenu,#formatArea:hover #formatMenu,#formatMenu.forceOpen{display:block} " + ".formatLabel{color:" + fgColor + ";margin-right:0.1em;margin-bottom:0.1em;line-height:1} " + ".formatStyleBtn{display:inline-block;opacity:0.9;background:#cccccc;color:black;font-size:95%;text-align:center;" + "margin-right:0.3em;margin-bottom:0.1em;padding:0.2em;width:1.2em;height:1.2em;border:1px #666666 solid;user-select:none;" + "vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;cursor:default} " + ".formatStyleBtn:hover{opacity:1;outline:2px " + fgColor + " solid;outline-offset:-1px} " + ".formatStyleBtn:active{opacity:0.65} " + ".colourline .formatStyleBtn{opacity:1} " + "#formatBoldBtn{font-weight:bold} " + "#formatItalicBtn{font-style:italic} " + "#formatUnderlineBtn{text-decoration:underline} " + "#formatMenu #formatColorAdvanced.colourline{display:none;padding-left:1.95em} " + "#formatColorPreview{padding:2px;width:8.87em;height:1.2em;border:1px #666666 solid;margin-right:0.3em;margin-bottom:0.1em;text-align:center} " + ".formatColorPicked{background:transparent;outline:2px " + fgColor + " none;outline-offset: -1px} " + "#formatMenu .Xc99,#formatMenu .XcDef.XbcDef.invertDef{color:" + fgColor + "} " + "#formatMenu .Xbc99{background:transparent}" + "#formatMenu .XcDef{color:" + bgColor + "} " + "#formatMenu .XbcDef,#formatMenu #formatColorFg.Xbc99{background:" + fgColor + "} " + "#formatMenu .XcDef.XbcDef.invertDef{background:" + bgColor + "}"; var cuCss = document.createElement("style"); cuCss.innerHTML = customCss; document.head.appendChild(cuCss); } // this creates labels in my little formatting menu function createFormatMenuLabel(text, inline) { var label = document.createElement("div"); label.className = "formatLabel"; if (inline) label.style.display = "inline-block"; label.textContent = text; return label; } // this is a template used for buttons that affect formatting in some way // can't use classList function createFormatStyleButton(textBox, text, tooltip, id, delim, param, classes, picker, pickerCompat) { var button = document.createElement("div"); button.className = "formatStyleBtn"; classes.forEach(function(cl) { button.className += " " + cl; }); if (id) button.id = id; if (tooltip) button.title = tooltip; button.addEventListener("click", function (e) { e.preventDefault(); e.stopPropagation(); if (!pickerCompat || !e.shiftKey) { // buttons that are incompatible with the "picker" will cancel it picker.disable(); var ss = textBox.selectionStart; var se = textBox.selectionEnd; var endTag = delim; if (ss == se) endTag = ""; var currValue = textBox.value; textBox.value = currValue.slice(0, ss) + delim + param + currValue.slice(ss, se) + endTag + currValue.slice(se); textBox.selectionStart = textBox.selectionEnd = se + delim.length + param.length + endTag.length; textBox.focus(); } else if (e.shiftKey) picker.setColor(parseInt(param)); }); button.textContent = text; return button; } // custom function because padStart is a fairly new addition to ECMAScript // I'm forcing double digits because they're more predictable, // especially when posting stuff that already begins with a number, // such as chat results // some linter told me "" + number was bad function compatiblePad2(number) { if (number < 10) return "0" + number; return String(number); } // advanced color selection UI ("picker") activated by holding Shift // this allows simultaneous entry of a foreground and a background color function advancedPicker(textBox, formatMenu) { var preview, okBtn, cancelBtn, fgShow, bgShow, that = this; var colorBoxClasses = "formatStyleBtn formatColorPicked"; var defaultColorClasses = "formatStyleBtn XcDef XbcDef"; // these properties describe the state of the "picker" this.selection = null; this.fg = null; this.bg = null; this.btn99 = null; // the following lines describe the UI for the "picker" this.DOM = document.createElement("div"); this.DOM.id = "formatColorAdvanced"; this.DOM.className = "colourline"; this.DOM.appendChild(createFormatMenuLabel("Advanced:", false)); okBtn = document.createElement("div"); okBtn.className = "formatStyleBtn"; okBtn.textContent = "OK"; okBtn.style.width = "3.39em"; this.DOM.appendChild(okBtn); cancelBtn = document.createElement("div"); cancelBtn.className = "formatStyleBtn"; cancelBtn.textContent = "Cancel"; cancelBtn.style.width = "5em"; this.DOM.appendChild(cancelBtn); preview = document.createElement("div"); preview.id = "formatColorPreview"; preview.textContent = "Preview"; this.DOM.appendChild(preview); this.DOM.appendChild(createFormatMenuLabel("Fore:", true)); fgShow = document.createElement("div"); fgShow.className = colorBoxClasses; fgShow.id = "formatColorFg"; this.DOM.appendChild(fgShow); this.DOM.appendChild(createFormatMenuLabel("Back:", true)); bgShow = document.createElement("div"); bgShow.className = colorBoxClasses; this.DOM.appendChild(bgShow); // the "picker" submits when releasing Shift // this is equivalent to clicking OK (while holding said Shift) // the "picker" can be canceled by clicking Cancel (as seen further down below) var shiftUpFn = function (e) { if (!e || e.key === "Shift" || e.which === 16) { var delim = "\x03"; var param = compatiblePad2(that.fg); if (that.bg !== null) param += "," + compatiblePad2(that.bg); var ss = textBox.selectionStart; var se = textBox.selectionEnd; var endTag = delim; if (ss == se) endTag = ""; var currValue = textBox.value; textBox.value = currValue.slice(0, ss) + delim + param + currValue.slice(ss, se) + endTag + currValue.slice(se); textBox.selectionStart = textBox.selectionEnd = se + delim.length + param.length + endTag.length; that.disable(); textBox.focus(); } } // the "picker" is canceled if the window loses focus var blurFn = function (e) { that.disable(); textBox.focus(); } this.set99 = function(button) { this.btn99 = button; } // this initializes the "picker" if it's not initialized this.enable = function() { if (this.selection === null) { this.DOM.style.display = "inline-block"; this.selection = 1; formatMenu.className = "forceOpen"; fgShow.style.outlineStyle = "solid"; this.btn99.className = defaultColorClasses; window.addEventListener("keyup", shiftUpFn); window.addEventListener("blur", blurFn); } } // this restores the "picker" to its original state this.disable = function() { this.DOM.style.display = ""; this.selection = this.fg = this.bg = null; formatMenu.className = ""; fgShow.style.outlineStyle = ""; fgShow.className = colorBoxClasses; bgShow.style.outlineStyle = ""; bgShow.className = colorBoxClasses; preview.className = ""; this.btn99.className = defaultColorClasses; window.removeEventListener("keyup", shiftUpFn); window.removeEventListener("blur", blurFn); } // normally, after choosing a foreground color, // the "picker" expects a background color, // but this allows users to set the focus back to the // foreground color if needed this.switch = function (boxIndex) { if (boxIndex == 1) { this.selection = 1; fgShow.style.outlineStyle = "solid"; bgShow.style.outlineStyle = ""; this.btn99.className = defaultColorClasses; } else { this.selection = 2; fgShow.style.outlineStyle = ""; bgShow.style.outlineStyle = "solid"; this.btn99.className = defaultColorClasses + " invertDef"; } textBox.focus(); } // this effectively registers a color selection this.setColor = function (color) { this.enable(); if (this.selection == 1) { this.fg = color; fgShow.className = colorBoxClasses + " Xbc" + color; this.switch(2); } else { this.bg = color; bgShow.className = colorBoxClasses + " Xbc" + color; this.switch(1); } preview.className = "Xc" + this.fg + " Xbc" + this.bg; } // this makes the "picker" buttons work okBtn.addEventListener("click", function() { shiftUpFn(); }); cancelBtn.addEventListener("click", function() { blurFn(); }); fgShow.addEventListener("click", function() { that.switch(1); }); bgShow.addEventListener("click", function() { that.switch(2); }); } // this creates the formatting menu; it appears when hovering over the A function constructFormatMenu(textBox) { var formatMenu = document.createElement("div"); formatMenu.id = "formatMenu"; formatMenu.className = "dropdownmenu"; var picker = new advancedPicker(textBox, formatMenu); // "B", "I", "U", "N" buttons; "N" negates every other tag // unlike the web chat, mIRC does recognize italic text // TODO: the status of "I" is subject to consideration, ask var fontStyleLabel = createFormatMenuLabel("Font style:", true); fontStyleLabel.style.width = "5.75em"; formatMenu.appendChild(fontStyleLabel); formatMenu.appendChild(createFormatStyleButton(textBox, "B", "bold", "formatBoldBtn", "\x02", "", [], picker, false)); formatMenu.appendChild(createFormatStyleButton(textBox, "I?", "italic?", "formatItalicBtn", "\x1D", "", [], picker, false)); formatMenu.appendChild(createFormatStyleButton(textBox, "U", "underline", "formatUnderlineBtn", "\x1F", "", [], picker, false)); formatMenu.appendChild(createFormatStyleButton(textBox, "N", "normal", undefined, "\x0F", "", [], picker, false)); formatMenu.appendChild(document.createElement("br")); // I added tooltips because colors are confusing // "dark" gray is darker in mIRC, but lighter in browsers // the numbers help with readability // 0 = color code is shown in white // 1 = color code is shown in black var colors = [ ["white", 1], ["black", 0], ["dark blue", 0], ["green", 0], ["red", 0], ["dark red", 0], ["purple", 0], ["orange", 1], ["yellow", 1], ["light green", 0], ["teal", 0], ["cyan", 1], ["blue", 0], ["fuchsia", 0], ["\"dark\" gray", 1], ["gray", 0] ]; // dummy element because I don't want to redefine the colors var dummyColourline = document.createElement("div"); dummyColourline.className = "colourline"; dummyColourline.appendChild(createFormatMenuLabel("Font colour:", false)); // this is where the color buttons are actually created colors.forEach(function (color, back) { var desc = color[0], fore = color[1]; if ((back != 0) && (back % 6 == 0)) dummyColourline.appendChild(document.createElement("br")); dummyColourline.appendChild(createFormatStyleButton(textBox, String(back), desc, undefined, "\x03", compatiblePad2(back), ["Xc" + fore, "Xbc" + back], picker, true)); }); // 2 extra buttons for default formatting + just the symbol var defaultStyleBtn = createFormatStyleButton(textBox, "99", "default", undefined, "\x03", "99", ["XcDef", "XbcDef"], picker, true); picker.set99(defaultStyleBtn); dummyColourline.appendChild(defaultStyleBtn); dummyColourline.appendChild(createFormatStyleButton(textBox, "\u2514", "reset/manual entry", undefined, "\x03", "", [], picker, false)); formatMenu.appendChild(dummyColourline); formatMenu.appendChild(picker.DOM); return formatMenu; } // this adds the formatting menu to the DOM once the textbox is found getTextBox(function(textBox) { addCustomCss(); var contForm = textBox.parentElement; var contDiv = contForm.parentElement; var formatArea = document.createElement("div"); formatArea.id = "formatArea"; var formatBtn = document.createElement("div"); formatBtn.id = "formatBtn"; formatBtn.textContent = "A"; formatArea.appendChild(formatBtn); formatArea.appendChild(constructFormatMenu(textBox)); contDiv.insertBefore(formatArea, contForm); }); })();