Wednesday, December 2, 2009

Salon Letters Beta — The Whole Enchilada

[(27/06/10) Salon has randomly changed the message header format on some letter pages causing the reformatting script to crash on those pages. I have written a patch that will fix this and incorporated it in the script posted below. To install, follow the instructions below for installing the script. Don't forget to uninstall the old version before installing the new; otherwise the new version won't be installed.]
It has now been about three weeks since Salon unveiled its “beta” pages, obviously designed by someone nostalgic for the red, white, and black color scheme so popular at the Nuremberg rallies. While the main pages at Salon are bad enough, the Letters pages were particularly hideous, with the previous more or less uncluttered format replaced with a page design where the letters are crammed into a space half the width of the page or less in order to make room for two additional columns, links were not marked in any way, and HTML tags that had previously been functional no longer worked.

I offered an update to the Salon Letter Filter that would work on the beta pages and also provided snippets of code that could be integrated into the Letter Filter script to relieve some of the clutter and the the poor design features that made trying to read the letters pages stressful as I developed them.

Now, by popular demand, a full Greasemonkey script that embodies both the Salon Letter Filter and the Salon Beta letter page reformatter is provided here. I have added some tweaks that let the script run on all Salon letters pages, including the letter posting page and individual author archive pages. The letter filter does not run on these pages as it is unneeded. The Letter Filter also will not run if the ‘killfile’ variable is not configured (i.e., it is empty or contains only the word “authors”). I have also added a switch to disable the Letter Filter even if the ‘killfile’ variable is configured. In short, if you do absolutely nothing after installing the script the Letter Filter will not operate — only the reformatting part of the script will run.

I have also added some bells and whistles that weren't part of the earlier scripts, including a “linkifier” that makes any properly formulated URL in the letter body into a clickable link. I have added commentary to the script that identifies the purpose of each block of code so that it will be easier for users to modify values or disable unwanted features. Here I give a brief summary of the features of the new script and how to manipulate it to suit the individual user's preferences.

First, however, and in keeping with the nature of this blog as a forum for language and grammar, a few remarks on the use of the term “comment” by programmers will be useful. All programming and scripting languages have a device that allows the compiler or interpreter to ignore lines in a program. In JavaScript (which is what Greasemonkey uses), the comment marker for an individual line is two slashes (‘//’). Programmers use comment (usually with “out”) as a verb meaning “to provide with comment markers” and any line with comment markers is said to be “commented out”. To uncomment a line is to remove the comment markers. I will be using these terms extensively in what follows.

  1. The Letter Filter

  2. The Letter Filter itself is essentially unchanged from the previous version. Instructions for configuring the ‘killfile’ variable are included in the script. I have added some tests so that the Filter does not run if the ‘killfile’ variable is not configured or if it is empty. I have also added a switch to disable the Letter Filter even if the ‘killfile’ variable is configured. The switch is found in the line //LetterFilterOn = "false";. To disable the Letter Filter, simply uncomment this line.

  3. Reformat the name of the author of the letter

  4. For each letter the author’s name is reformatted to make it a) visible and b) legible. The color is changed from gray to black and the font size is increased slightly. If the Letter Filter runs, this is done within the Filter. If not, it is done with a separate block of code. If you want to change the color or font size, the parameters are in <span style='font-size:110%;color:black'> and are self-explanatory.
    [Salon beta has now modified the format of authors’ names on letter pages. However, true to form, the name is now too large rather than too small. The size of the author’s name is now the same as the letter subject. To counter this, I have changed the earlier setting of 110% to 90%. The code is left in the script for those who may wish to further modify the format of the author’s name.]

  5. Ad Remover

  6. If you really want to see the ads, you can comment out this block of code. If you do, be sure to comment out the entire block, not just a line or two.

  7. Letters Help

  8. This section of the Salon letters page, located at the bottom of the second column, is actually useful and is worth saving before trashing the mostly useless second and third columns. This block of code saves the Letters Help and places it, very unobtrusively, at the beginning of the letter archive for the page.

  9. Remove columns two and three

  10. Now that the Letters Help has been salvaged, it is possible to remove column two containing the scoreboard (“Most Active Letters Threads”), telling which of Salon’s authors is ahead in the battle for clicks and column three containing innumerable shiny objects all begging to be clicked on (“Currently in Salon”). If you want to retain these columns, comment out this block of code. If you do, you won't be able to spread the text of the letters across the page. The letters have been crowded into the small space allotted to them in order to make room for these additional columns of click generators.

  11. Function to make global style changes

  12. Unless you are an experienced programmer, do not mess with this function. Even if you are an experienced programmer, don't mess with it. It falls into the “if it ain’t broke, don’t fix it” category.

  13. Change link colors

  14. One of the most disgusting things about the Salon beta design is that it does not identify links unless your pointing device hovers over them. From looking at the page there is no way to tell what is a link and what is normal text. Further, visited links are not marked so that there is no difference between visited links and any other links. Presumably, if you can’t see that you’ve already visited a link it’s more likely that you’ll click on it again. What is particularly egregious about this policy is that it means that you cannot tell which letter pages you have already read. You can see what page you’re on and presumably you know that pages on one side of the current page have been read and pages on the other haven’t. But if you have to close the browser or navigate away from the letter page, there is no way to tell when you return which pages of letters have been read and which not. The only way to tell is to write down what page you were on before you leave. This is not just poor web page design; it is outright bad manners and lack of consideration. [Salon beta has now apparently grudgingly changed the formatting so that letter pages that have been read are now marked. Other visited links, however, seem to be unaffected.]

    To rectify this, I have added three lines of code that provide colors for links:

    1. Links

    2. The first line of code changes the color of links so that they stand out from the default text color.

    3. Visited Links

    4. The second line of code sets the color of a link that has been visited. This allows you to see at a glance which pages you have already read.

    5. Hover Color

    6. The third line of code changes the color of a link when the mouse is held over it.

    The colors have been chosen on the basis of my own preferences and can be changed by the user to suit his or her own preferences. Colors can be defined in three ways: 1) by name; 2) by a hex number (00 to FF) indicating the Red, Green, and Blue components of the color; or 3) by the rgb() function, which specifies the RGB components as either a decimal number (0 to 255) or a percentage (0% to 100%). I have used each of these methods in one line of code but the user can change them ad libitum: color:blue, color:#0000FF, and color:rgb(0,0,255) all produce the same color. A list of named colors can be found here and you can quickly test your colors here if you have JavaScript enabled in your browser (or on the page if you use NoScript).

  15. Change width of letter components

  16. Now that the second and third columns are gone, it is possible to expand the text of the letters to utilize some of the empty space. The next block of code resets the width of each letter component except the signature to an absolute width of 650 pixels. This is the width that I find most comfortable for the browser width that I like to use. The user can change the numbers in these lines to suit his or her own preference. Remember, however, that it can be as difficult to read lines that are too long as it is to read those that are too short.

  17. Reformat the <li> HTML tag to display bullet points

  18. Salon letters beta still allows the <ul> (unordered list) and <li> (list item) HTML tags. However, there is no formatting associated with the tags so they have no effect other than allowing a single-spaced list. This block of code sets the parameters of the <li> element so that it displays bullet points and indents the text. Most posters probably will not use the <li> tag because a) they are unfamiliar with HTML and don't know what it is supposed to do and b) it doesn't work properly so even if they know what it is supposed to do, it doesn't do it. But with the Greasemonkey script working the <li> element will display correctly. There is no point in using the <ul> tag because Salon's HTML parser inserts a pair of <ul>/</ul> tags around every <li> element.

  19. Remove the huge navigation panels at the top and bottom of the page

  20. The next line of code removes the gigantic navigation panel (which essentially duplicates the one at the top of the page) at the bottom of the page. Since we know where the panel is and there is only one of it, there is no need to search for it and this task can be handled with a single line of code. The next two lines remove the large cumbersome navigation panel at the top of the page. If you want the navigation panels, simply comment out these lines of code (or turn off Greasemonkey by clicking on the icon in the status bar of your browser and then reload the page).

  21. Change the harsh red background color of the copyright notices at the bottom of the page

  22. I find the red used as background for the copyright notices at the bottom of the page to be both harsh and stressful. In addition, the black text of the notices is very difficult to read against the red background. Trying to do so gives me a headache. I have simply changed the background color to white to match the rest of the page. If you would like some other color, you can change the color in the line = 'white'; as described above in item 7. The method is slightly different in this case, however. The color name, hex number color code, or the rgb() function should replace the word ‘white’ inside the quotation marks.

  23. Reformat the letter posting page

  24. All of the foregoing will also run on the page that posts letters. Now that there is more space on that page it is possible to expand the area allotted to the composition of your letter. The following blocks of code accomplish this.

    1. Expand the space for the letter subject

    2. The first block of code in this section expands the size of the area provided for the subject of the letter. The part that does this is in the line newSize.setAttribute('size','70');. You can change the size by changing the number in this line. The remainder of the block is very important as it makes sure that the new element you are inserting has the same properties and content as the element you are replacing. This is important because the page has a verification script that looks for these properties and won't post the letter if they aren't there.

    3. Expand the space for the letter text

    4. The next block of code increases the size of the “textarea” element for the body of the letter. The numbers that do this are in the newTextarea.setAttribute('rows','20'); and newTextarea.setAttribute('cols','70'); lines. The “rows” number adjusts the height of the box and the “cols” adjusts the width. These numbers can be changed to suit the individual user. Again, the remainder of the block of code is very important for the same reasons as in i above and should not be changed.

    5. Remove the gray background for the letter preview

    6. There is a light gray background that is configured to the original size of the letter preview. This is distracting if the letter preview now extends outside this block. I found that the simplest solution to this was simply to remove the gray background rather than try to resize it. The final block of code in this section changes the gray background to white in the same manner as in item 11 above.

    These changes are, needless to say, specific to the letter posting page and do not affect other Salon letter pages. But what is more important is that they work. I have posted numerous letters using the reformatted pages and they can be used with confidence.

  25. Linkification

  26. The last block of code in this instantiation of the Letter Filter and Reformatter is a routine to convert any properly formulated URL in the body of a letter into a clickable link. There are Firefox add-ons that provide this capability (in fact I use one myself), but for those who are distrustful of add-ons I decided to add this to the Reformatter. This is by way of atonement for the fact that I haven't be able to find a way to sneak a link tag past Salon's HTML parser.

INSTRUCTIONS: General instructions are given in the script. To create the Greasemonky user script, copy everything below from //BEGIN CODE to //END CODE (you do not need to copy the BEGIN CODE and END CODE lines, but since they are comment lines it won't hurt anything if you do) into a new text file. Do not panic if it appears that the lines of code end in midair on the blog page. It is all there even if it doesn't display because of the Blogger page layout. Simply select the code, copy it, and paste it into a new text file.

IMPORTANT: DO NOT USE A WORD PROCESSOR TO CREATE THIS FILE. Word processors insert formatting codes into files. Scripts must contain nothing but text. If you do use a word processor (such as Wordpad), be sure to save the file as TEXT ONLY. It is recommended to use Notepad or some equivalent text editor (note: a text editor is fundamentally different from a word processor).

Once you have created the file, chose “Save As” and save the file with the extension .user.js. It doesn't matter what you name the file since Greasemonkey takes the name of the script from the “@name” element in the script itself, but it is essential that it have the extension .user.js otherwise Greasemonkey will not recognize it as a Greasemonkey user script.

When you have the file saved and Greasemonkey enabled on your browser, simply drag the file and drop it on the browser. Greasemonkey will recognize it as a user script and ask if you want to install it. Say yes.

IMPORTANT AGAIN: Once you have installed the script, you can no longer edit it from the original file. Well, you can edit it of course — it just won't have any effect on the script itself. When Greasemonkey installs the script it copies it to a directory far, far away, and that becomes the working copy of the script as far as Greasemonkey is concerned. You can edit this file with the “Edit” function in the “Manage User Scripts” dialog panel provided by Greasemonkey. If you want to edit the original script file, you can, but you will then have to uninstall the script and install it anew for your edits to have any effect.

var LetterFilterOn;
// To configure the letter filter, you must have a "killfile" variable configured.
// A template for this variable is given below. The "killfile" varable includes a
// comma-delimited string of the nyms of the authors that you wish to filter out.
// A sample "killfile variable configuration looks like:
// new String(killfile = 'author1,author2,author3');
// or
// new String(killfile = 'author');
// Simply replace the word "authors" in the template below with the nyms of the authors you
// wish to expunge, each one separated by a comma, and save the file.
// Entries are case insensitive. Since the "killfile" will become a regular expression,
// regular expression metacharacters (such as "." or "+") appearing in a nym should be
// preceded by two backslashes ("\\"; e.g., 'L\\.W\\.M\\.') in order to work properly.
// If you do not wish to use the Letter Filter function, no configuration is necessary,
// although reformatting parameters may be reconfigured by the user if desired.
new String(killfile = 'authors');

// Remove the "//" in front of the following line if you wish to disable the Letter
// Filter in all circumstances:
//LetterFilterOn = "false";
// --------------------------------------------------------------------
// This is a Greasemonkey user script.
// To install, you need Greasemonkey:
// After installing Greasemonkey, restart Firefox and revisit this script.
// Under Tools, there will be a new menu item to "Install User Script".
// Accept the default configuration and install.
// To uninstall, go to Tools/Manage User Scripts,
// select "Salon Letter Filter and Reformatter", and click Uninstall.
// --------------------------------------------------------------------
// ==UserScript==
// @name Salon Letter Filter and Reformatter
// @author Frankly, my dear, ...
// @namespace
// @description Filter and reformat letters from Salon. WARNING: manual configuration required. If you wish to use the Letter Filter you must edit the script and enter your "killfile" in the string variable near the top of the script. If you wish to use only the reformatting features, no configuration is necessary.
// @include*
// @version 0.0.1
// ==/UserScript==
// Shortcut function to evalute an XPath in the document
function xpath(query, sourceDoc) {
return document.evaluate(query, sourceDoc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);

if (! LetterFilterOn) LetterFilterOn = true;
if (! document.getElementById('letter_source_container') && ! document.getElementById('letters_send_msg') && LetterFilterOn != 'false' && killfile.length != 0 &&^authors$/) == -1) LetterFilterOn = true;
else LetterFilterOn = false;
if (LetterFilterOn) {
myregexp = new RegExp(killfile.replace(/, */g, "|"), "i");
var allLets, thisLet, allAuthors, thisAuthor, allFooters, thisFooter, allHeads, thisHead;
allHeads = xpath("//div[@class='posts']/div[@class='letter']/h2[@class='headline md'] | //div[@class='posts']/div[@class='letter']/h3[@class='headline md']",document);
allLets = xpath("//div[@class='posts']/div[@class='letter']/div[@class='deck md']",document);
allAuthors = xpath("//div[@class='posts']/div[@class='letter']/div[@class='byline'] | //div[@class='posts']/div[@class='letter']/div[@class='byline premium']",document);
allFooters = xpath("//div[@class='letter_entry_footer']",document);
for (var i = 0; i < allLets.snapshotLength; i++) {
thisHead = allHeads.snapshotItem(i);
thisLet = allLets.snapshotItem(i);
thisAuthor = allAuthors.snapshotItem(i);
thisFooter = allFooters.snapshotItem(i);
var author = thisAuthor.innerHTML;
thisAuthor.innerHTML = "<span style='font-size:90%;color:black'>" + thisAuthor.innerHTML + "</span>";
if (myregexp.test(author)) {
mymatch = myregexp.exec(author);
thisLet.innerHTML = "<h2><b>Letter from " + mymatch + " deleted</h2></b>" + thisFooter.innerHTML;
thisHead.innerHTML = "";
thisFooter.innerHTML = "";
thisAuthor.innerHTML = "";
//thisLet.innerHTML = "";

// change color and increase size of author's name if Letter Filter didn't run
if (! LetterFilterOn) {
allAuthors = xpath("//div[@class='posts']/div[@class='letter']/div[@class='byline'] | //div[@class='posts']/div[@class='letter']/div[@class='byline premium']",document);
for (var i = 0; i < allAuthors.snapshotLength; i++) {
thisAuthor = allAuthors.snapshotItem(i);
thisAuthor.innerHTML = "<span style='font-size:90%;color:black'>" + thisAuthor.innerHTML + "</span>";

// ad remover
var allAds, thisAd;
allAds = xpath("//div[@class='ad_content'] | //div[@id='ad_Top']",document);
for (var j = 0; j < allAds.snapshotLength; j++) {
thisAd = allAds.snapshotItem(j);
thisAd.innerHTML = "";

// save the letters help section and place it before the letters
var allBoxes, helplist,helpbox, letters;
allBoxes = xpath("//div[@class='box']",document);
helplist = allBoxes.snapshotItem(1).innerHTML;
helpbox = document.createElement('div');
helpbox.class = "box";
helpbox.innerHTML = helplist
letters = document.getElementById('letters_archive');
if (letters) {
letters.parentNode.insertBefore(helpbox, letters);

// remove the irrelevant second and third columns
var allCols, thisCol;
allCols = xpath("//div[@id='col2'] | //div[@id='col3']",document);
for (var k = 0; k < allCols.snapshotLength; k++) {
thisCol = allCols.snapshotItem(k);
thisCol.innerHTML = "";

// Function to make global changes in style
function addGlobalStyle(css) {
var head, style;
head = document.getElementsByTagName('head')[0];
if (!head) { return; }
style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = css;

// change link colors
addGlobalStyle('a:link {color:#0088FF ! important; }');
addGlobalStyle('a:visited {color:orange ! important; }');
addGlobalStyle('a:hover {color:rgb(0,0,255) ! important; }');

// change width of letter components
addGlobalStyle('p {width:650px ! important; }');
addGlobalStyle('blockquote {width:650px ! important; }');
addGlobalStyle('h2 {width:650px ! important; }');
addGlobalStyle('h3 {width:650px ! important; }');
addGlobalStyle('li {width:650px ! important; }');

// format the <li> HTML tag so it gives bullet points instead of garbage
var thisList, allLists;
allLists = xpath("//div[@class='posts preview']/div[@class='letter clearfix']/div[@class='deck md']/ul/li | //div[@class='posts']/div[@class='letter']/div[@class='deck md']/ul/li",document);
if (allLists.snapshotItem(0)) {
for (var m = 0; m < allLists.snapshotLength; m++) {
thisList = allLists.snapshotItem(m); = "25px"; = "disc"; = "outside";

// remove huge navigation panel at top and bottom of page
xpath("//div[@class='sitemap_wrap']",document).snapshotItem(1).innerHTML = "";

var header = document.getElementById('header');
header.innerHTML = "";

// remove the harsh red background from the copyright section at the bottom of the page
var foot = document.getElementById('footer_inner'); = 'white';

// *Reformat the letter posting page
// expand the space for the letter subject
var theSize, newSize;
theSize = document.getElementById('letter_subject');
if (theSize) {
newSize = document.createElement("input");
newSize.type = 'text';
newSize.value = theSize.value; = 'letter_subject'; = 'subject';
theSize.parentNode.replaceChild(newSize, theSize);

// expand the area for the letter body
var theTextarea, newTextarea;
theTextarea = document.getElementById('letter_body');
if (theTextarea) {
newTextarea = document.createElement("textarea");
newTextarea.value = theTextarea.value; = 'letter_body'; = 'body';
theTextarea.parentNode.replaceChild(newTextarea, theTextarea);

// remove the gray background from the letter preview
pre = document.getElementById('letters_archive');
if(pre) = 'white';

// make all URLs in letter body clickable links
var isURL = new RegExp("[a-z]+://[a-z0-9_-]+\\.[a-z0-9_#%&\?\/.,;:!'~=+-]+", "ig");
var isLink = new RegExp("http://static\\.open\\.salon\\.com", "i");
var allTexts, thisText, p;
allTexts = xpath("//div[@class='posts']/div[@class='letter']/div[@class='deck md']/* | //div[@class='posts preview']/div[@class='letter clearfix']/div[@class='deck md']/*",document);
if (allTexts.snapshotItem(0)) {
for (var n = 0; n < allTexts.snapshotLength; n++) {
thisText = allTexts.snapshotItem(n);
if (isURL.test(thisText.innerHTML)) {
myURLs = thisText.innerHTML.match(isURL);
for (p = 0; p < myURLs.length; p++) {
myURL = new String(myURLs[p]);
if (isLink.test(myURL)) break;
myURL = myURL.replace(/(\&gt;.?|[.,:;'!]+)$/,"");
newURL = myURL.replace(/([.?])/g,"\\$1");
var oldURL = new RegExp(newURL,"i");
thisText.innerHTML = thisText.innerHTML.replace(oldURL,'<a href="' + myURL + '">' + myURL + '</a>');