// ==UserScript== // @namespace http://mrob.com/time/scripts-beta // @name no-tinytext for OTT // @description Locate tiny and/or pale-colored text and make it readable // @author Robert Munafo (with help from azule and balthasar_s) // @version 48660.32 // @downloadURL http://mrob.com/time/scripts-beta/no-tinytext.user.js.txt // @grant GM_getValue // @grant GM_setValue // @include http://forums.xkcd.com/* // @include http://www.forums.xkcd.com/* // @include http://fora.xkcd.com/* // @include http://echochamber.me/* // @include http://1190.bicyclesonthemoon.info/ott/view* // @run-at document-end // ==/UserScript== // REVISION HISTORY: // // np5831.xx alpha // np5832.36 get it (mostly) working. // np5848.50 more sophisticated size mapping, and defeat nested constructs // (innermost size is the one that will take effect) // np5848.86 Add comment pointing to my sample post // np5949.74 Detect and fix very light font colors by changing the background // to black. // np5971.34 Add a button at the top of the page that toggles the "reveal // light text" functionality. // np5979.01 Change the button to a checkbox. // np5991.71 Refactor code to pass an object pointer into the action function // (which should make it easier to add more options) // np5993.10 Add second checkbox controlling the 'embiggen tinytext' action // np5993.54 Add third checkbox and azule's "red outline with titletext" // method for tinytext // np6011.51 Add an Azule-like highlighting option for light-colored text. // np6294.73 The smallest sizes are still a bit too small for my liking. // np6317.70 Start at size 8 instead of size 9 // np7611.06 Recognize colors 'white' and e.g. '#BFC' // np8162.47 Place checkboxes above the page-footer, rather than below the // pope's parent node // np8207.75 Add make_checkbox function; remove console.info calls // np10050.10 Convert newlines to spaces for alt-text. increase size limit from // 59 to 65. // np10051.18 Rename the opts variables. // np10068.40 Convert
to space plus \n, which makes better-looking alt // text (but probably only in some browsers) // np10197.50 Add getInnerText() // np10966.67: Work on the balthamirror (1190.bicyclesonthemoon.dnsd.info) // np13032.30: Work on www.forums.xkcd.com // np13095.60: Broaden the match patterns so it works in PMs // np14757.49 Remove 'dnsd' from bicyclesonthemoon hostname // np21083.61 Add @grant and @run-at requests // np27097.25 Fix syntax errors; guarantee run at least once. // np48660.32 Fix errors/warnings reported by TamperMonkey // A sample forum post containing a variety of sizes, including Vytron's // nested super-size hack, is here: // // fora.xkcd.com/viewtopic.php?p=3448128#p3448128 // // In the HTML served up by echochamber, tinytext appears within items like: // // // This would normally be way too small to read // // // I have trouble reading sizes below about 50%. // // Here is a post that has a series of lines (footnotes) in a small size: // // fora.xkcd.com/viewtopic.php?p=3342149#p3342149 // // It is useful for testing anything that deals with multiple lines inside a // single [size] tag, such as the getInnerText() function that creates // the title text. // // Here is another that has a spoiler inside white text. We currently do not // handle it properly, only part of the text is noticed and changed. // // fora.xkcd.com/viewtopic.php?p=3465549#p3465549 // var pat1 = new RegExp('^[0-9]\%$'); // Match '0%' through '9%' var pat2 = new RegExp('^[0-5][0-9]\%$'); // Match '00%' through '59%' var pat3 = new RegExp('^[0-9]+\%$'); // Match any number followed by '%' var pat4 = new RegExp('^[0-9]+'); // Match just a number at the beginning // A sample post containing font colours deliberately set to be as unreadable // as possible: // // fora.xkcd.com/viewtopic.php?p=3340789#p3340789 // // And another with a gradation from white to gray: // // fora.xkcd.com/viewtopic.php?p=3341004#p3341004 // // Regardless of the HTML, the color will appear to us as a string in // the syntax 'rgb(123, 234, 210)' or possibly '#80BFFF' // This matches e.g. 'rgb(123, 234, 210)' var pc1 = new RegExp('rgb\\([12][0-9][0-9], [12][0-9][0-9], [12][0-9][0-9]\\)'); // This matches the same thing but saves the three numbers, for use // with .match() var pc2 = new RegExp('rgb\\(([12][0-9][0-9]), ([12][0-9][0-9]), ([12][0-9][0-9])\\)'); // This matches e.g. '#BFFFFF' (best friends forever!) var pc3 = new RegExp('\#[A-F][0-9A-F][A-F][0-9A-F][A-F][0-9A-F]'); var pc4 = new RegExp('\#[A-F][A-F][A-F]'); var pc5 = new RegExp('white'); // Maximum brightness we'll accept var gthres = 190; var needrun = 1; var notinytext = { /*
and
are ignored in alt-text. This causes connecting * the last word in a line with the first word in the next one. We * don't want this, so we convert
to a space plus a newline. */ getInnerText: function(elem) { var text = elem.innerHTML .replace(//gi,"\n") //
,
,
.replace(/(<([^>]+)>)/gi, ""); // return text; /* Old version of getInnerText, using a more roundabout method but * not dependent on regexps. * * We temporarily change the contents (via its innerHTML property) * then get it in text format via the textContent property; then * we restore the original unaltered html. */ // console.info("e.ih == '" + elem.innerHTML + "'"); // ihtml = elem.innerHTML; // elem.innerHTML = ihtml.replace(//g, ' \n'); // var text = elem.textContent; // elem.innerHTML = ihtml; // return text; }, findAncestorById: function(elem, idName) { if (new RegExp('\\b'+idName+'\\b').test(elem.id)) { return elem; } else { if (elem != document.body) { return this.findAncestorById(elem.parentNode, idName); } return null; } }, // Set a checkbox to be on or off setChkVal: function(chk, val) { if (val === 0) { chk.checked=false; } else { chk.checked=true; } }, /* opt_action will set or clear an option. */ opt_action: function(chk, optobj, nam) { // If the option was just initialized, it will now become set. // This makes sense becuse if they've just installed the script // and click on the checkbox, they want to set the checkbox. if (optobj.val === 0) { optobj.val = 1; } else { optobj.val = 0; } this.setChkVal(chk, optobj.val); chk.disabled = false; /* Save the user's work in a way that will persist across page loads. * see http://wiki.greasespot.net/GM_setValue */ GM_setValue(nam, JSON.stringify(optobj.val)); }, make_checkbox: function(nam, title, optobj, pdiv) { // Make the checkbox for 'reveal light text' var chk = document.createElement('input'); chk.type = 'checkbox'; chk.value = 'nott-' + nam; chk.id = nam; var lbl = document.createElement('label'); lbl.htmlFor = nam; var lab_text = document.createTextNode(title); lbl.appendChild(lab_text); this.setChkVal(chk, optobj.val); chk.addEventListener('click', this.opt_action.bind(this, chk, optobj, nam)); pdiv.appendChild(chk); pdiv.appendChild(lbl); }, /* create_checkboxen will add a checkbox that changes an option */ create_checkboxen: function() { var container = document.createElement('div'); var preDiv = document.createElement('div'); preDiv.style.marginTop = '1px'; preDiv.style.fontSize = '1.0em'; preDiv.style.fontWeight = 'bold'; preDiv.style.color = '#0B7'; var optstitle = document.createTextNode("No-tinytext (or lighttext) by Mrob27:"); preDiv.appendChild(optstitle); var opts_div = document.createElement('div'); opts_div.style.textAlign = 'left'; // Make the checkbox for 'reveal light text' opts_div.appendChild(document.createTextNode("  ")); // CJK space, for indentation this.make_checkbox('o_reveal_light', 'Reveal Light Text', this.o_reveal_light, opts_div); // Make the checkbox for 'highlight light text' opts_div.appendChild(document.createTextNode(" ")); this.make_checkbox('o_hgl_light', 'Highlight Light Text', this.o_hgl_light, opts_div); // Make the checkbox for 'embiggen tiny text' opts_div.appendChild(document.createTextNode(" ")); this.make_checkbox('o_embiggen', 'Embiggen TinyText', this.o_embiggen, opts_div); // Make the checkbox for 'highlight tiny text' opts_div.appendChild(document.createTextNode(" ")); this.make_checkbox('o_hgl_tiny', 'Highlight TinyText', this.o_hgl_tiny, opts_div); container.appendChild(preDiv); container.appendChild(opts_div); return(container); }, convert: function() { var spans = document.getElementsByTagName('span'); var i; var so; var sz; if (needrun === 0) { return(0); } needrun = 0; // %%% 20171117: Editing the options is disabled for now, because // GM_xxxValue is useless. I can shim the GM_ functions with // localStorage, see newpix_converter to see how. if (0) { this.o_reveal_light = { val: JSON.parse(GM_getValue('o_reveal_light', '0')) }; if (typeof this.o_reveal_light == 'undefined') { this.o_reveal_light = { val: "0" }; this.o_reveal_light.val = JSON.parse(GM_getValue('o_reveal_light', '0')); } this.o_embiggen = { val: JSON.parse(GM_getValue('o_embiggen', '0')) }; if (typeof this.o_embiggen == 'undefined') { this.o_embiggen = { val: "0" }; this.o_embiggen.val = JSON.parse(GM_getValue('o_embiggen', '0')); } this.o_hgl_tiny = { val: JSON.parse(GM_getValue('o_hgl_tiny', '0')) }; if (typeof this.o_hgl_tiny == 'undefined') { this.o_hgl_tiny = { val: "0" }; this.o_hgl_tiny.val = JSON.parse(GM_getValue('o_hgl_tiny', '0')); } this.o_hgl_light = { val: JSON.parse(GM_getValue('o_hgl_light', '0')) }; if (typeof this.o_hgl_light == 'undefined') { this.o_hgl_light = { val: "0" }; this.o_hgl_light.val = JSON.parse(GM_getValue('o_hgl_light', '0')); } } else { this.o_reveal_light = { val: 1 }; this.o_embiggen = { val: 1 }; this.o_hgl_tiny = { val: 0 }; this.o_hgl_light = { val: 0 }; } // Create the options checkboxes. var footer = document.getElementById("page-footer"); var ft_par = footer.parentNode; ft_par.insertBefore(this.create_checkboxen(), footer); for(i=0 ; i8, 50->10, 100->12 sz = 8 + so/25; } else { // 100->12, 150->17, 200->22 sz = 2 + (so / 10); } spans[i].style.fontSize = sz + "px"; } // np5993.51: azule's method of revealing tinytext if (this.o_hgl_tiny.val) { if (so < 20) { spans[i].style.outline = "1px dotted red"; } // For some screens and eyes, size 65 and smaller is too small // to read, so we add titletext (also called "alt text" or // "hovertext") if (so <= 65) { spans[i].setAttribute('title', notinytext.getInnerText(spans[i])); } } } } if (this.o_reveal_light.val || this.o_hgl_light.val) { // Fix text that has a very light colour by changing the // background to black so = 0; if (spans[i].style.color === '') { } else if ( pc2.test(spans[i].style.color) ) { if ((RegExp.$1 > gthres) && (RegExp.$2 > gthres) && (RegExp.$3 > gthres)) { so = 1; } // Test color again using '#80BFFF' syntax } else if ( pc3.test(spans[i].style.color) ) { so = 1; } else if ( pc4.test(spans[i].style.color) ) { so = 1; } else if ( pc5.test(spans[i].style.color) ) { so = 1; } if (so) { if (this.o_reveal_light.val) { spans[i].style.backgroundColor = '#000'; } else { spans[i].style.outline = "1px dotted red"; } spans[i].setAttribute('title', notinytext.getInnerText(spans[i])); } } } } }; // 3 cases for cross-platform, cross-browser: not necessary for this // application but I want this code to be useful elsewhere too! if (window.addEventListener) { window.addEventListener('DOMContentLoaded', // was 'load', notinytext.convert.bind(notinytext), false); } else if (window.attachEvent) { window.attachEvent('onload', notinytext.convert.bind(notinytext)); } // but make sure it runs at east once notinytext.convert(notinytext);