28 June 2009

CSS inheritance and selector specificity for max-width

While finding it easier to read text using narrower columns using my custom CSS definition, I noticed that it didn't work on pages that use table rows for grouping sections, such as the following structure:

table
  tbody
    tr
      td
        <headings>
        p …

Here's the CSS definition I was using:


@namespace url(http://www.w3.org/1999/xhtml);

@-moz-document domain(java.sun.com)
{
  body {
    max-width : 35em;
  }
}

It appears that the max-width property defined in the <body> element property isn't inherited by the <p> elements within the <table>. My first fix was to select all the elements in the path between <body> and <p> and make them inherit the max-width property:


…
  table, tbody, tr, td {
    max-width : inherit;
  }

This isn't a nice solution because I would have to specify all the possible types (or elements) that could be in a table cell. It would be better the use the CSS universal selector, *, instead:


…
  * {
    max-width : inherit;
  }

Now the definition is even applied to some elements that we don't really want to limit in size. Elements that are naturally wider than max-width, such as images in <img> tags, are now squashed, while preformatted text in <pre> elements are displayed in boxes with horizontal scrollbars.

If the max-width property is set to none, then the width of an element is not limited. The solution is to have two rules, a universal selector and a type selector, and we have a CSS definition that limits the width of all elements except for <div>, <img> and <pre>:


@namespace url(http://www.w3.org/1999/xhtml);

@-moz-document domain(java.sun.com)
{
  body {
    max-width : 35em;
  }
  * {
    max-width : inherit;
  }
  div, img, pre {
    max-width : none;
  }
}

This definition works because the universal selector is a less specific than a type selector, so the rules for <div>, <img> and <pre> override the universal selector's rule.

As usual when hacking code, I now realise that there's no longer any need to use inherit and I can simplify my CSS definition by specifying the max-width of every type using the universal selector.


@namespace url(http://www.w3.org/1999/xhtml);

@-moz-document domain(java.sun.com)
{
  * {
    max-width : 35em;
  }
  div, img, pre {
    max-width : none;
  }
}

To see the effects of the different definitions, add the style to your Stylish add-in, visit this Sun Java page and observe what happens to the width of the navigation bar on the top of the page and the sequence diagram images.

24 June 2009

CSS max-width versus width properties for text columns

It's easier to read on a web page if the columns aren't too wide. This presentation Ideal line length for content suggests setting the values of the min-width and max-width CSS properties around 30 em. Columns of 30 em sounds very narrow for a PC-based browser, but I expect it makes sense for browsers on handheld devices.

Using the Stylish Firefox add-in, here's my Narrow Paragraph rule for sites that I read:

@-moz-document domain(<domain 1>)
  , domain(<domain 2>) {
  body {
    min-width : 25em;
    max-width : 30em;
  }
}

Earlier, I used the width CSS property but when the browser window is made narrower than the width's value, the width of the text column doesn't change and you have to scroll left and right to read. Setting both min-width and max-width gives the designer and reader some flexibility in layout and viewing, respectively.

21 June 2009

Installing and configuring Subversion for personal use

I decided (finally!) to migrate my version control system from CVS to Subversion so that I could have atomic transactions when committing multiple files. There's plenty of tutorials on using and setting up Subversion on the net, so here's the simplest way to get started on Windows …

  1. Install and run Subversion on Windows
  2. Create a repository.
  3. Import a project into the repository.
  4. Check-out a project from the repository.

Install and run Subversion on Windows

  1. Install TortoiseSVN Subversion GUI client.
  2. Install CollabNet Subversion Server and Client:
    • I chose the svnserve option, otherwise by default the installer will also install the Apache web server and I already had Apache installed.
    • The installer will create a Windows service using svnserve. While it is possible to use the file protocol for personal development (since there's only one developer, yourself), it's likely that I'd use Subversion in a team, so I decided I might as well get used to the svn protocol and have a server manage my repository.

Create a repository.

  1. Open a command prompt and create a repository with this command: svnadmin create <repository path>.
    • Note that the installer should have added the Subversion bin folder to your PATH environment variable.
    • Note 2: I think I'm missing a step: How does svnserve map svn://localhost to the repository path?
  2. Edit <repository path>\conf\svnserve.conf and uncomment the following lines:
    • password-db = passwd
    • realm = My First Repository
    Note that passwd refers to the passwd file in the conf folder.
  3. Edit <repository path>\conf\passwd file and either uncomment one of the sample user and password pairs, or add a new pair. You could have anonymous read and write operations, but again, since I was likely to use it in a team, I might as well create a username-password pair.
  4. Open the Windows services console and start the CollabNet Subversion svnserve service if it is not already running.
  5. Check that TortoiseSVN can connect to your Subversion server.
    1. Open Windows Explorer
    2. Highlight an arbitrary file or folder and select the context menu item TortoiseSVN/repo-browser.
    3. When TortoiseSVN prompts you for a URL, enter svn://localhost (the address of your local Subversion server) and press the OK button.
    4. TortoiseSVN should display its Repository Browser window. If the svnserve service isn't running, you would get this message: Can't connect to host 'localhost': No connection could be made because the target machine actively refused it. If you get this message, check that the service is running in the Windows Services console.
    5. Leave the Repository Browser window open because you can use it to check that you have imported your projects successfully.

Import a project into the repository

  1. Copy or rename an existing project folder to something like <project>_import.
  2. In Explorer, select that folder and use the context menu item TortoiseSVN/Import....
  3. TortoiseSVN displays the Import dialog.
    1. In the URL of Repository field, enter svn://localhost/<project>.
    2. In the Import Message field, enter Importing <project>.
    3. Press the OK button.
  4. Since this is the first time you've used TortoiseSVN to import a project, you'll be prompted to enter a username and password. Optionally, select the Save authentication checkbox so that TortoiseSVN remembers your credentials. When you are satisfied, press the OK button to submit your credentials.
  5. Check that your project has been imported successfully using the TortoiseSVN's Repository Browser. You may have to press F5 to refresh the list of folders in the repository.

Check-out a project from the repository

  1. In Windows Explorer, highlight the folder to store your project and select the context menu item SVN Checkout....
  2. TortoiseSVN should display the Checkout dialog, with ...
    • URL of Repository = svn://localhost/<project>
    • Checkout directory = <path of project>
  3. Press the OK button and your project should be checked out to the target folder.
  4. Refresh the Windows Explorer window and you should see a green tick overlaid on the <project>'s folder icon. This shows that TortoiseSVN recognises this folder as a Subversion folder.

Final words

That's pretty much all the steps required to set up a basic Subversion system for personal development on Windows.

See Also

17 June 2009

Escaping special characters in Windows International Keyboard

The Windows International Keyboard allows you to enter letters with accent marks. For example, to type é, just type Apostrophe e. But what if you just want to enter (or escape) an apostrophe (for example, when entering a string literal while programming)? The trick is to type a space after the apostrophe, as per this bullet point in the support page: If you press the space bar, the symbol (apostrophe, quotation mark, accent grave, tilde, accent circumflex or caret) is displayed by itself.

14 June 2009

Merging multiple blog feeds into Facebook Notes with Yahoo Pipes

I started a second blog, Vibogafi, for writing about media, and wanted to display updates from that and this blog using the Facebook Notes application. Notes only allows users to import one RSS feed, so I had to merge both my RSS feeds first.

One aggregation or mashup system that came to mind was Yahoo Pipes. If you have a Yahoo account, then you can create your personal pipes. I followed the video tutorial and got my pipe working the second time around. Some tips and minor gotchas:

  • When entering the Blogger feed URL in the Pipes Fetch Feed gadget, you cannot simply enter http://<myblog>.blogspot.com and expect the gadget to find the feed URL; instead, you have to enter the entire feed URL, which looks like http://<myblog>.blogspot.com/feeds/posts/default.
  • Check the Pipes Debugger output by selecting each gadget. If you do not get the expected output, try to fix the problem before proceeding.
  • If you just want to merge two or more feeds, just use one Fetch Feed gadget instead of multiple Fetch Feed and one Union gadget; the Fetch Feed gadget allows you to enter more than one URL.
  • To use your new Pipe in Facebook Notes, you have to use your pipe's RSS output, whose URL ends with &_render=rss, not just the pipe's basic address. (Upon hindsight, it is obvious, but I tripped over that problem, too.)

10 June 2009

Localize dates in Scientific American

Scientific American formats dates in its pages as m/d/yy, while I prefer d/m/yy. Like an earlier post about formatting dates in Blogger's Dashboard, this Greasemonkey script uses a regular expression to find date strings in a page, and switches the month and day values.

// ==UserScript==
// @name           Scientific American localize dates
// @namespace      kamhungsoh.com
// @description    Convert dates from mm/dd/yy to dd/mm/yy format
// @require        http://jqueryjs.googlecode.com/files/jquery-1.3.2.min.js
// @include        http://www.scientificamerican.com/*
// ==/UserScript==

$('span').each(function() {
  var s = $(this).text().replace(/(.*)(\d+)\/(\d+)\/(\d+)/, '$1$3/$2/$4');
  $(this).text(s);
});

The script only works for the home page and I haven't figured out why I can't construct an expression to locate date strings in articles. Another thing to note is that jQuery's .text() function returns all the text values in a node, so some of the formatting, such as strong tags, are lost when the script replaces the text.

07 June 2009

Reformatting post dates in Blogger Dashboard

In the Blogger Dashboard, the posting date of your entries are in mm/dd/yy format and Google doesn't provide a way to localize it or choose a date format. Here's how you can use a Greasemonkey script to convert dates in Dashboard to dd/mm/yy format.

The dates in Dashboard have this structure:

<td class="date">
  <span>3/6/09</span>
</td>

The following script uses jQuery to iterate through all span nodes of td elements with a date class attribute, then applies the Javascript string replace() function to swap the first two numbers in the span node. To change the output string, just modify the second argument of the replace() function.

// ==UserScript==
// @name           blogger.com localize dashboard date
// @namespace      kamhungsoh.com
// @description    Convert date format from m/d/yy to d/m/yy.
// @require        http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js
// @include        http://www.blogger.com/posts.g*
// ==/UserScript==

$("td.date > span").each(function() {
  var s = $(this).text().replace(/(\d+)\/(\d+)/, '$2\/$1');
  $(this).text(s);
});

The first argument of the replace() function is a regular expression where \d is a meta-character for a digit, + matches one or more of the preceding character (e.g. 4 or 23) and the parentheses group the characters to be memorised. The forward slash has to be escaped, \/, to allow us to match it in the input string. In the second argument, the $2/$1 represents the second and first memorised strings.

03 June 2009

Filtering busy mailing lists in Gmail

I subscribe to a busy mailing list and find that I only keep some of the messages and delete the rest. Then I realised it would be less work to automatically delete all messages from this list first and only keep the ones that interested me.

Here's how I set up my mailing list filter in Gmail:

  1. Specify the criteria (e.g. the mailing list name).
  2. Mark Skip the inbox (Archive it).
  3. Pick a label from the Apply the label field.
  4. Mark Delete it.

Using this filter, messages that match the criteria are automatically labelled and moved into the Bin (or Trash). I read messages from the mailing list in the Bin and if I want to keep one, I just uncheck the Bin label in the message's tool bar.