<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-3271789858112054722</id><updated>2012-02-14T02:00:32.098-08:00</updated><category term='tag:Blogger'/><category term='tag:Development'/><category term='tag:Chrome'/><category term='tag:#Troubleshooting'/><category term='tag:Portable Apps'/><category term='tag:Gmail'/><category term='tag:Keyboard'/><category term='tag:#Bookmarklet'/><category term='tag:Live Mesh'/><category term='tag:#How To'/><category term='tag:Evernote'/><category term='tag:iPad'/><category term='tag:iPhone'/><category term='tag:#Utility'/><category term='tag:#Tip'/><category term='tag:#How Things Work'/><title type='text'>Senseful Solutions</title><subtitle type='html'>A blog about tech solutions that, well, just make sense. &lt;small&gt;&lt;i&gt;(to me at least)&lt;/i&gt;&lt;/small&gt;</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Senseful</name><uri>http://www.blogger.com/profile/17245419786727860080</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>36</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-6313246443883524362</id><published>2012-01-16T01:59:00.000-08:00</published><updated>2012-01-20T23:25:11.830-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:#Utility'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:Evernote'/><title type='text'>Evernote Character Sort Order Finder</title><content type='html'>As I was updating my old post, &lt;a href="/2010/07/evernote-tag-sort-order.html"&gt;Evernote Tag Sort Order&lt;/a&gt;, I realized that there are many variations between the clients and figuring out the best set of symbols to use is not an easy task. Therefore, I decided to create a tool which will help you choose which symbols you should use for sorting purposes (e.g. to make it &lt;a href="/2010/12/evernote-tip-make-notes-easier-to.html"&gt;easier to access them&lt;/a&gt;). Simply check all the clients and lists you care about, and it will output several recommendations for you. See the end of this post for more details.

&lt;a name='more'&gt;&lt;/a&gt;

&lt;div id="fieldsets"&gt;
    &lt;div&gt;
    &lt;fieldset id="all"&gt;
&lt;legend&gt;All clients&lt;/legend&gt;
&lt;/fieldset&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class="clear"&gt;&lt;/div&gt;
Filter: &lt;input id="reqChars" type="text" value="" class="fixed-width large-text" /&gt;&lt;br /&gt;
&lt;textarea id="myTextArea" class="fixed-width large-text"&gt;&lt;/textarea&gt;

&lt;h4&gt;Documentation&lt;/h4&gt;
&lt;h5&gt;Definitions:&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;Notes:&lt;ul&gt;

   &lt;li&gt;List view: When you view notes. Some clients let you explicitly choose list view.&lt;/li&gt;
   &lt;li&gt;Snippet view: Some clients let you choose snippet view, which shows more information about each note.&lt;/li&gt;
   &lt;li&gt;(Mac) Full-screen view: On the Mac OS X Lion client, you can view the notes in full screen.&lt;/li&gt;
   &lt;li&gt;(iPad) Side list: when you click in to a notebook or tag, it shows you notes on the left side and a preview on the right side. If you switch to portrait mode, the list appears on top.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;Notebooks:&lt;ul&gt;

   &lt;li&gt;Main list: The left panel on desktop/web clients, or the main list you get to from the home screen on iOS clients.&lt;/li&gt;
   &lt;li&gt;Favorites bar: The favorites bar on desktop clients.&lt;/li&gt;
   &lt;li&gt;Note's notebook selection: When you edit a note, it lets you select a notebook.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;Tags:&lt;ul&gt;

   &lt;li&gt;Main list: The left panel on desktop/web clients, or the main list you get to from the home screen on iOS clients.&lt;/li&gt;
   &lt;li&gt;Favorites bar: The favorites bar on desktop clients.&lt;/li&gt;
   &lt;li&gt;Snippet view: When you look at a note in snippet view, in some clients it shows a note's tags after the date.&lt;/li&gt;
   &lt;li&gt;Note details: When you open a note's and view the tags it is assigned.&lt;/li&gt;
   &lt;li&gt;Auto-complete: When you edit a note and begin typing a tag's name, it auto-completes it for you.&lt;/li&gt;
   &lt;li&gt;(PC) Tag column: On the PC version, you can see the tag list in the Tags column of the list view.&lt;/li&gt;
   &lt;li&gt;(PC) Assign tags: The PC version has an assign tags screen which is accessed by right-clicking a note and choosing "Tag note..."&lt;/li&gt;
   &lt;li&gt;(iOS) Tag selection: when you edit a note and click tags, it shows you a list of tags you can select.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;Saved Searches:&lt;ul&gt;

   &lt;li&gt;For Mac and PC, it appears on the left panel. iOS has its own dedicated view for this. On the web, it appears when you click in the search box.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;Example use cases:&lt;/h5&gt;
&lt;ul&gt;
   &lt;li&gt;Aaron uses the web client only, and is only concerned with the sorting of notebook names. He checks the "Notebooks" option under "Web" and sees that it outputs the following: &lt;code&gt;&amp;nbsp;!"#$%&amp;'()*+,-./123:;&amp;lt;=&amp;gt;?@[\]^_`AbC{|}~&lt;/code&gt;. This tells him that if he were to create the notebooks &lt;code&gt;$Test&lt;/code&gt; and &lt;code&gt;@Test&lt;/code&gt;, the one with the dollar sign will appear before the one with the at-sign (since the dollar sign appears before the at sign in the output).&lt;/li&gt;
   &lt;li&gt;Bill uses Evernote on his PC only. He wants to use the symbols for sorting in all the lists available, and doesn't want to remember how symbols specifically sort in each of the 4 categories (i.e. notes, notebooks, tags, saved searches). That is, he wants a consistent sorting experience for each of the lists. So he checks all of the 4 categories under Windows and views the available options. One of the symbols he wants to use the equals sign (&lt;code&gt;=&lt;/code&gt;), so he types it in the filter box. He then chooses to use the top row in the result list, since that will maximize the amount of consistently-sorted characters he can use.&lt;/li&gt;
   &lt;li&gt;Jane uses Evernote on her Mac, iPhone, and iPad. She's mainly concerned with ensuring that tag sorting is consistent everywhere, so she checks all tag options available for her 3 clients. She realizes that on the iPad she won't be able to have a consistent experience when it comes to auto-completion, but she is okay with that. She then examines the top possibilites and notices that several of them have &lt;b&gt;either&lt;/b&gt; &lt;code&gt;Ab&lt;/code&gt; or &lt;code&gt;AC&lt;/code&gt;. This means that at least one of the clients has a case sensitive sort somewhere. Therefore, it would be best for her to stick with only capital or only lowercase letters. She decides she will use only capital letters, so she enters in &lt;code&gt;AC&lt;/code&gt; in the filter box. She's then presented with several choices of the same length, but upon examining them, notices that some have the digits (&lt;code&gt;123&lt;/code&gt;) while others don't. If she were to include the digits, that gives her another 7 characters she can use (only 3 of the 10 digits are represented here). She decides that it's important to her and enters in &lt;code&gt;1&lt;/code&gt; into the filter as well. She's now left with 4 choices of the same length. However, as she looks at the rest of the list, she notices that if she gives up one character, she can get symbols that appear more distinct than the symbols in the top 4 results, so she ends up choosing that one instead. In other words, even though &lt;code&gt;&amp;nbsp;!"()*/123AC&lt;/code&gt; contains more symbols (12 symbols) than &lt;code&gt;&amp;nbsp;!"#%+123AC&lt;/code&gt; (11 symbols), the latter is probably a better choice since the symbols &lt;code&gt;#%+&lt;/code&gt; stick out more than &lt;code&gt;()*/&lt;/code&gt;.&lt;/li&gt;&lt;/ul&gt;

&lt;h5&gt;Other notes:&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;There are many useful tips in my &lt;a href="/2010/07/evernote-tag-sort-order.html"&gt;original post&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Tag lists will always have sort orders that are different than other lists in the same client since they cannot contain the &lt;code&gt;,&lt;/code&gt; character.&lt;/li&gt;
  &lt;li&gt;Each &lt;a href="/2010/01/color-generator-hue-saturation.html"&gt;auto-generated color&lt;/a&gt; is used to represent a different sort order.&lt;/li&gt;
&lt;/ul&gt;

&lt;!--********************* Custom CSS  *********************--&gt;
&lt;style type="text/css"&gt;
#fieldsets &gt; div {
    float: left;
}
&lt;/style&gt;

&lt;!--****************** Custom JavaScript  *****************--&gt;
&lt;script type="text/javascript"&gt;

var clients = [
    {
    name: "Mac",
    version: "3.0.5",
    date: "1/3/12",
    options: [
        {
        name: "Notes",
        sortOrder: "0-9a-nq-vowpxyz_",
        desc: "List view, full-screen view.",
        group: "N"},
    {
        name: "Notebooks",
        sortOrder: "0-9a-nq-vowpxyz_",
        desc: "Main list, favorites bar, note's notebook selection.",
        group: "B"},
    {
        name: "Tags",
        sortOrder: "0-9abd-nq-vowpxyz_",
        desc: "Main list, snippet view, note details.",
        group: "T"},
    {
        name: "Tags (favorites bar)",
        sortOrder: "0udih1me7289qsxznafr635vtbjkly_4gowp",
        group: "T"},
    {
        name: "Tags (auto-complete*)",
        sortOrder: "0-9abd-z_",
        desc: "*Note: It first sorts by the tag's usage count, and only then by this order.",
        group: "T"},
    {
        name: "Saved searches",
        sortOrder: "0-9a-nq-vowpxyz_",
        group: "S"}
    ]},
{
    name: "Windows",
    version: "4.5.2",
    date: "1/3/12",
    options: [
        {
        name: "Notes",
        sortOrder: "0123456789abcdefghijklmnqrstuvowpxyz_",
        desc: "List view.",
        group: "N"},
    {
        name: "Notebooks",
        sortOrder: "012345689acefhimnqrstu7dvxyz_bjklgowp",
        desc: "Main list, favorites bar, note's notebook selection.",
        group: "B"},
    {
        name: "Tags",
        sortOrder: "012345689aefhimnqrstu7dvxyz_bjklgowp",
        desc: "Main list, favorites bar, tag column, note details, auto-complete, assign tags.",
        group: "T"},
    {
        name: "Saved searches",
        sortOrder: "012345689acefhimnqrstu7dvxyz_bjklgowp",
        group: "S"}
    ]},
{
    name: "Web",
    version: "?",
    date: "1/4/12",
    options: [
        {
        name: "Notes",
        sortOrder: "0-9a-nq-vowpxyz_",
        desc: "List view, snippet view.",
        group: "N"},
    {
        name: "Notebooks",
        sortOrder: "0-9a-nq-vowpxyz_",
        desc: "Main list, note's notebook selection.",
        group: "B"},
    {
        name: "Tags",
        sortOrder: "0-9abd-nq-vowpxyz_",
        desc: "Main list, auto-complete. (Snippet view doesn't show tags.)",
        group: "T"},
    {
        name: "Tags (note details)",
        sortOrder: "0-9abd-z_",
        group: "T"},
    {
        name: "Saved searches",
        sortOrder: "0-9a-nq-vowpxyz_",
        group: "S"}
    ]},
{
    name: "iPhone",
    version: "4.1.17",
    date: "1/3/12",
    options: [
        {
        name: "Notes",
        sortOrder: "0udcih1me7289qsxznafr635vtbjkly_4gowp",
        desc: "Snippet view. (There is no list view.)",
        group: "N"},
    {
        name: "Notebooks",
        sortOrder: "0udcih1me7289qsxznafr635vtbjkly_4gowp",
        desc: "Main list, note's notebook selection.",
        group: "B"},
    {
        name: "Tags",
        sortOrder: "0udih1me7289qsxznafr635vtbjkly_4gowp",
        desc: "Main list, tag selection.",
        group: "T"},
    {
        name: "Tags (note details)",
        sortOrder: "0-9abd-nq-vowpxyz_",
        group: "T"},
    {
        name: "Saved searches",
        sortOrder: "0udcih1me7289qsxznafr635vtbjkly_4gowp",
        group: "S"}
    ]},
{
    name: "iPad",
    version: "4.1.17",
    date: "1/3/12",
    options: [
        {
        name: "Notes",
        sortOrder: "0udcih1me7289qsxznafr635vtbjkly_4gowp",
        desc: "List view, side list.",
        group: "N"},
    {
        name: "Notebooks",
        sortOrder: "0udcih1me7289qsxznafr635vtbjkly_4gowp",
        desc: "Main list, note's notebook selection",
        group: "B"},
    {
        name: "Tags",
        sortOrder: "0udih1me7289qsxznafr635vtbjkly_4gowp",
        desc: "Main list, tag selection.",
        group: "T"},
    {
        name: "Tags (note details)",
        sortOrder: "0-9abd-nq-vowpxyz_",
        group: "T"},
    {
        name: "Tags (auto-complete*)",
        sortOrder: "",
        desc: "*Note: This list does not appear to be sorted.",
        group: "T"},
    {
        name: "Saved searches",
        sortOrder: "0udcih1me7289qsxznafr635vtbjkly_4gowp",
        group: "S"}
    ]}
];

var groupCodes = ["N", "B", "T", "S"];
var groupHeaders = []; // hash: string to $input elements
var groupCheckboxes = []; // hash: string to child $input elements
var $checkboxes = $(); // all checkboxes besides the "all clients" ones
var grayColor = "#BBB";
buildGroup("N", "Notes");
buildGroup("B", "Notebooks");
buildGroup("T", "Tags");
buildGroup("S", "Saved searches");

function buildGroup(code, name) {
    var $all = $('#all');
    var $checkbox = createCheckbox('chkAll' + code, name, $all);
    $checkbox.data('group', code);
    groupHeaders[code] = $checkbox;
    groupCheckboxes[code] = [];

    $checkbox.click(function() {
        var group = $(this).data('group');
        var checked = $(this).prop('checked');
        var childCheckboxes = groupCheckboxes[group];
        childCheckboxes.forEach(function($checkbox) {
            $checkbox.prop('checked', checked);
        });
        updateResults();
    });
}

function createCheckbox(id, text, $appendTo) {
    var $p = $('&lt;p&gt;');
    var $input = $('&lt;input&gt;').attr('type', 'checkbox').attr('id', id);
    var $label = $('&lt;label&gt;').text(text).attr('for', id);
    $p.append($input);
    $p.append($label);
    $p.appendTo($appendTo);
    return $input;
}

// bulid the options
var firstInstanceOfSortType = [];
var checkboxesPerMultiInstanceSortType = []; // hash: [sortType] to $input elements
var multiInstanceSortTypes = []; // string list of all sort types available
var maxFieldSetHeight = 0;
for (var i = 0; i &lt; clients.length; i++) {
    var client = clients[i];
    var clientName = client.name;
    var clientVersion = client.version;
    var clientDate = client.date;
    var safeClientName = clientName.replace(/[^a-z]/gi, "");

    // create new fieldset
    var $fieldset = $('&lt;fieldset&gt;');
    var $legend = $('&lt;legend&gt;').text(clientName).appendTo($fieldset);
    var $div = $('&lt;div&gt;').append($fieldset); // add div elements so that when we set the heights, the fieldsets can still look small
    $div.appendTo('#fieldsets');

    var clientOptions = client.options;
    var counter = 0;
    for (var j = 0; j &lt; clientOptions.length; j++) {
        var option = clientOptions[j];
        var optionName = option.name;
        var optionSortOrder = getRealSortOrder(option.sortOrder);
        var optionDesc = option.desc || "";
        var optionGroup = option.group;
        
        if (optionDesc != "") {
            optionDesc = "\n" + optionDesc;
        }

        var $checkbox = createCheckbox('chk' + safeClientName + counter++, optionName, $fieldset);
        var $tooltipText = $('&lt;div&gt;').addClass("info-text");
        $('&lt;div&gt;').addClass('fixed-width').text((optionSortOrder == "" ? "(Not sorted by title.)" : optionSortOrder)).appendTo($tooltipText);
        $('&lt;div&gt;').text(optionDesc).appendTo($tooltipText);
        $('&lt;div&gt;').text("Version: " + clientVersion + ', tested on ' + clientDate + '.').appendTo($tooltipText);
        $checkbox.parent().append($tooltipText);
        $checkbox.data('group', optionGroup);
        $checkbox.data('sortOrder', optionSortOrder);
        $checkboxes = $checkboxes.add($checkbox);

/*if (typeof firstInstanceOfSortType[optionSortOrder] === 'undefined') {
            // 1st instance of a sort type found
            firstInstanceOfSortType[optionSortOrder] = $checkbox;
        } else */
        if (optionSortOrder == "") {
            // invalid sort order
            $checkbox.attr('disabled', true);
            $checkbox.parent().css('background-color', grayColor);
        } else {
            if (typeof checkboxesPerMultiInstanceSortType[optionSortOrder] === 'undefined') {
                // 2nd instance of a sort type found
                //            checkboxesPerMultiInstanceSortType[optionSortOrder] = [firstInstanceOfSortType[optionSortOrder], $checkbox];
                checkboxesPerMultiInstanceSortType[optionSortOrder] = [$checkbox];
                multiInstanceSortTypes.push(optionSortOrder);
            } else {
                // 3rd+ instance of a sort type found
                checkboxesPerMultiInstanceSortType[optionSortOrder].push($checkbox);
            }
    
            groupCheckboxes[optionGroup].push($checkbox);
    
            $checkbox.click(function() {
                // check to see if the group checkbox needs to be checked/unchecked
                var group = $(this).data('group');
                var $header = groupHeaders[group];
                var checkboxes = groupCheckboxes[group];
                $header.prop('checked', checkboxes.every(function($cb) {
                    return $cb.prop('checked');
                }));
                updateResults();
            });
        }
    }
    if (maxFieldSetHeight &lt; $div.height()) {
        maxFieldSetHeight = $div.height();
    }
}
$('#fieldsets &gt; div').height(maxFieldSetHeight); // set to same height so that they all organize nicely.

// now that we know which sort patterns repeat, we can generate colors for them
var numberOfSortTypes = multiInstanceSortTypes.length;
var rgb = [246, 176, 188];
var colors = sfu.clr.getVaryingHues(rgb, numberOfSortTypes);
for (var i = 0; i &lt; multiInstanceSortTypes.length; i++) {
    var sortPattern = multiInstanceSortTypes[i];
    var checkboxes = checkboxesPerMultiInstanceSortType[sortPattern];
    var color = sfu.clr.rgbToHex(colors[i]);
    checkboxes.forEach(function($checkbox) {
        var $p = $checkbox.parent();
        $p.css('background-color', color);
    });
}

// create tooltips
sfb.tooltipify($checkboxes.parent());

var currentSequences = [];
var lastFilter = $('#reqChars').val();

$('#reqChars').keyup(function() {
    var newFilter = $('#reqChars').val();
    if (lastFilter == newFilter) return;
    updateFilter();
    lastFilter = newFilter;
});

function getRealSortOrder(encodedSortOrder) {
    if (encodedSortOrder == "") {
        return "";
    }
    
    var from = "012"  + "3456789abcdefghijklmnopqr"  + "stuvwxyz_";
    var to =   " !\"" + "#$%&amp;'()*+,-./1:;&lt;=&gt;?@AC[\\" + "]^_`b{|}~";
    
    
    // if there is a '-', then compression was used    
    while (true) {
        var dashIndex = encodedSortOrder.indexOf('-');
        if (dashIndex == -1) {
            break;
        }
        
        var prevLetter = encodedSortOrder[dashIndex - 1];
        var nextLetter = encodedSortOrder[dashIndex + 1];
        var fromIndex = from.indexOf(prevLetter) + 1;
        var toIndex = from.indexOf(nextLetter);
        var textToInsert = from.slice(fromIndex, toIndex);
        encodedSortOrder = encodedSortOrder.substr(0, dashIndex) + textToInsert 
            + encodedSortOrder.substr(dashIndex + 1);
    }
    
    var upperAIndex = null;
    var upperCIndex = null;
    var lowerbIndex = null;
    var lettersSeen = [];
    var result = "";
    for (var i = 0; i &lt; encodedSortOrder.length; i++) {
        var fromIndex = from.indexOf(encodedSortOrder[i]);
        if (fromIndex == -1) {
            alert('Error: char not found: ' + encodedSortOrder[i]);
            return null;
        }
        var newChar = to[fromIndex];
        if (newChar == "A") {
            upperAIndex = i;
        } else if (newChar == "C") {
            upperCIndex = i;
        } else if (newChar == "b") {
            lowerbIndex = i;
        }
                
        result += newChar;
        if (typeof lettersSeen[newChar] !== 'undefined') {
            alert('duplicate char: ' + encodedSortOrder[i]);
            return null;
        }
        lettersSeen[newChar] = true;
    }
    if (upperAIndex == null || upperCIndex == null || lowerbIndex == null) {
        alert('Error: all 3 letters must exist');
        return null;
    }
    var caseSensitiveSort = null;
    if (upperCIndex == upperAIndex + 1) {
        // okay, ex: @AC_b
        // either C needs to come after A
        caseSensitiveSort = true;
    } else if (lowerbIndex == upperAIndex + 1 &amp;&amp; upperCIndex == lowerbIndex + 1) {
        // okay, ex: @AbC
        // or b can come after A, but C must come after b
        caseSensitiveSort = false;
    } else {
        // invalid, ex: 
        // sorted backwards: @CA_b
        // letters not sorted together: b@A_C (probably unsorted list)
        alert('incorrectly sorted list.');
        return null;
    }
    
    // add more chars and numbers
    result = result.replace("1", "123"); // 4 nums and 10 chars gives about the same ratio as 10:26. use an even number for chars so that lower and uppercase is represented equally
    // don't alternate letters since then the algorithm produces basically duplicate results: Abdfhj, ACdfhj, ACEfhj, ACEGhj, ACEGIj, etc.
    /*if (caseSensitiveSort) {
        // ACDEF and bghij
        result = result.replace("C", "CEGI");
        result = result.replace("b", "bdfhj");
    } else {
        // AbCdEfGhIj
        result = result.replace("C", "CdEfGhIj"); // 10 total chars representing the letters
    }*/
    
    return result;
}

function output(s) {
    $('#myTextArea').text(s);
}

function updateResults() {
    var sortOrders = [];
    $checkboxes.filter(':checked').each(function() {
        var sortOrder = $(this).data('sortOrder');
        if (sortOrders.indexOf(sortOrder) == -1) {
            sortOrders.push(sortOrder);
        }
    });

    var pairs = getCommonPairs(sortOrders);
    currentSequences = getSequences(pairs);
    updateFilter();
}

function updateFilter() {
    var validSeqs = getMatchingSequences(currentSequences, $('#reqChars').val());
    //validSeqs = validSeqs.slice(0, 10);
    output(validSeqs.join('\n'));
}

function getMatchingSequences(sequences, requiredChars) {
    return sequences.filter(function(val, index, array) {
        for (var i = 0; i &lt; requiredChars.length; i++) {
            if (val.indexOf(requiredChars[i]) == -1) return false;
        }
        return true;
    });
}

function getSequences(pairs) {
    var subsequenceStartChars = ""; // the beginning vertecies which have no edges pointing towards them
    var startingLetters = []; // array of chars
    // make it easy to access letters which follow any given letter
    var letterMap = []; // e.g. if AB and AC are pairs, then letterMap["A"] == "BC"
    for (var i = 0; i &lt; pairs.length; i++) {
        var c1 = pairs[i][0];
        if (typeof letterMap[c1] === 'undefined') {
            // is a new first letter
            letterMap[c1] = "";
            startingLetters.push(c1);
            subsequenceStartChars += c1;
        }
        letterMap[c1] += pairs[i][1];
    }

    // trim the graph, leaving only the longest paths
    for (var i = 0; i &lt; startingLetters.length; i++) {
        var c1 = startingLetters[i];
        var children = getCharChildren(c1);
        for (var j = 0; j &lt; children.length; j++) {
            var c2 = children[j];
            if (hasLongerPath(c1 + c2)) {
                letterMap[c1] = removeChars(letterMap[c1], c2);
            }
        }
    }

    // now that we have the minimum graph edges, find the starting verticies (by removing the invalid starting chars) (i.e. those which have nodes pointing to them)
    var charsVisited = "";
    var charsToVisit = [];
    for (var i = 0; i &lt; startingLetters.length; i++) {
        var c = startingLetters[i];
        charsVisited += c;
        charsToVisit.push(c);
    }
    while (charsToVisit.length &gt; 0) {
        var c1 = charsToVisit.shift();
        var children = getCharChildren(c1);
        for (var i = 0; i &lt; children.length; i++) {
            var c2 = children[i];
            subsequenceStartChars = removeChars(subsequenceStartChars, c2);
        }
        children = removeChars(children, charsVisited);
        charsVisited += children;
        for (var i = 0; i &lt; children.length; i++) {
            var c2 = children[i];
            charsToVisit.push(c2);
        }
    }

    // finally, we can generate the subsequences
    wordsToProcess = [];
    for (var i = 0; i &lt; subsequenceStartChars.length; i++) {
        wordsToProcess.push(subsequenceStartChars[i]);
    }
    var result = [];
    while (wordsToProcess.length &gt; 0) {
        var word = wordsToProcess.pop();
        var lastLetter = word[word.length - 1];
        var nextChars = getCharChildren(lastLetter);
        var addedAnotherWord = false;

        for (var i = 0; i &lt; nextChars.length; i++) {
            var c = nextChars[i];
            wordsToProcess.push(word + c);
            addedAnotherWord = true;
        }

        if (!addedAnotherWord) {
            // no chars were able to be added, so we reached an edge
            result.push(word);
        }
    }

    // sort by length
    result.sort(function(a, b) {
        return b.length - a.length;
    });

    return result;

    // helper functions follow...

    function getCharChildren(char, charsToRemove) {
        charsToRemove = (typeof charsToRemove == 'undefined') ? '' : charsToRemove;

        var result = letterMap[char];
        if (typeof result === 'undefined') return "";

        return removeChars(result, charsToRemove);
    }

    function hasLongerPath(path) {
        // e.g. path = "AC", if AB and AC are nodes, then it should return true
        //   since the path A -&gt; B -&gt; C is longer than A -&gt; C
        var startChar = path[0];
        var endChar = path[1];
        var queuedChars = ""; // list of chars that have been, or will be, processed
        var charsToProcess = [];

        var nextChars = getCharChildren(startChar, endChar);

        queuedChars += nextChars;
        for (var i = 0; i &lt; nextChars.length; i++) {
            charsToProcess.push(nextChars[i]);
        }

        while (charsToProcess.length &gt; 0) {
            var c = charsToProcess.shift();
            if (c == endChar) return true;
            else {
                var children = getCharChildren(c, queuedChars);
                queuedChars += children;
                for (var i = 0; i &lt; children.length; i++) {
                    charsToProcess.push(children[i]);
                }
            }
        }
        return false;
    }
}

function removeChars(s, chars) {
    // chars: string of characters to remove from &lt;s&gt;
    for (var i = 0; i &lt; chars.length; i++) {
        s = s.replace(chars[i], "");
    }
    return s;
}

function getCommonPairs(sets) {
    // sets: array of strings to analyze
    var pairs = [];
    var finalPairs = [];
    var numberOfSets = sets.length;
    for (var i = 0; i &lt; sets.length; i++) {
        var s = sets[i];
        var lastSet = (i == sets.length - 1);

        while (s.length &gt; 1) {
            var c1 = s[0];
            s = s.substr(1);
            for (var j = 0; j &lt; s.length; j++) {
                var c2 = s[j];
                if (typeof pairs[c1 + c2] === 'undefined') {
                    pairs[c1 + c2] = 1;
                } else {
                    pairs[c1 + c2]++;
                }
                if (lastSet &amp;&amp; pairs[c1 + c2] == numberOfSets) {
                    // exists in all the sets
                    finalPairs.push(c1 + c2);
                }
            }
        }
    }
    return finalPairs;
}

function getPrintablePairsForString(s) {
    // debugging function
    // e.g. getPrintablePairsForString("ZIANCOTB")
    var output = "";
    var indentation = "";

    while (s.length &gt; 1) {
        var c = s[0];
        s = s.substr(1);
        output += indentation;
        for (var i = 0; i &lt; s.length; i++) {
            if (i &gt; 0) output += ", ";
            output += c + s[i];
        }
        output += "\n";
        indentation += "    ";
    }
    return output;
}
&lt;/script&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-6313246443883524362?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/6313246443883524362/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2012/01/evernote-tag-sort-order-utility.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/6313246443883524362'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/6313246443883524362'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2012/01/evernote-tag-sort-order-utility.html' title='Evernote Character Sort Order Finder'/><author><name>Senseful</name><uri>http://www.blogger.com/profile/17245419786727860080</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-1936377303169845958</id><published>2012-01-11T00:00:00.000-08:00</published><updated>2012-01-11T00:00:28.146-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:#How To'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:Blogger'/><title type='text'>How to perform a union label search on Blogger?</title><content type='html'>While Blogger allows you to easily search for all posts tagged with a specific label, there seems to be no way to search for a combination of a set of labels. This led me to coming up with my own solution which&amp;nbsp;requires&amp;nbsp;a bit more effort, but works!&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;The trick is that I name each label with the same prefix. In the case of this blog, I use &lt;code&gt;tag:&lt;/code&gt;. Once each label has a prefix, I can use Google to search for articles with any set of labels. For example, if I wanted to search for all posts tagged with &lt;code&gt;iPhone&lt;/code&gt; and &lt;code&gt;Tip&lt;/code&gt;, I would enter the following terms into a Google search: &lt;code&gt;"tag iPhone" "tag Tip"&lt;/code&gt;. Had there not been a prefix, my query would have needed to be &lt;code&gt;iPhone Tip&lt;/code&gt;, which would find many articles that only mention any of these words and aren't necessarily tagged with them. In fact, in the case of my blog, I have both words listed on the right side of each page, meaning that every single one of my posts would show up in the search results.&lt;br /&gt;
&lt;br /&gt;
This brings me to my next point. I like the "Labels" sidebar gadget, but if I were to enable it, it would mess up my search results for the reason I just mentioned. In order to have a list of all my labels on the right side of each page and still have the union label search work, I needed to manually create the labels gadget. I chose to add an "HTML/JavaScript" gadget, and then created a really simple unordered list, linking to each of my labels (e.g. &lt;code&gt;/search/label/tag:MyLabel&lt;/code&gt;). Unfortunately, this means that the list needs to be updated every time I create a new label.&lt;br /&gt;
&lt;br /&gt;
The final tip I'll leave you with is that when you create a link to the Google search, I would recommend adding &lt;code&gt;site:example.com&lt;/code&gt; and &lt;code&gt;inurl:html&lt;/code&gt;. The first part is obvious: it makes sure the results are on your own website. The second part ensures that the results will only include actual articles rather than pages which show snippets of articles (e.g. the home page or label search page).&lt;br /&gt;
&lt;br /&gt;
You can see how all of this works by clicking on &lt;a href="http://www.google.com/search?q=%22tag:iPhone%22+%22tag:Tip%22+inurl:html+site:sensefulsolutions.com"&gt;this link&lt;/a&gt; which will show you all my posts that are tagged with &lt;code&gt;iPhone&lt;/code&gt; and &lt;code&gt;Tip&lt;/code&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-1936377303169845958?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/1936377303169845958/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2012/01/how-to-perform-union-label-search-on.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/1936377303169845958'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/1936377303169845958'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2012/01/how-to-perform-union-label-search-on.html' title='How to perform a union label search on Blogger?'/><author><name>Senseful</name><uri>http://www.blogger.com/profile/17245419786727860080</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-282552020978061692</id><published>2012-01-08T21:24:00.000-08:00</published><updated>2012-01-27T23:36:35.755-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:#How To'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:Chrome'/><title type='text'>Viewing Chrome cache (the easy way)</title><content type='html'>&lt;a href="http://www.google.com/chrome"&gt;Chrome&lt;/a&gt;&amp;nbsp;is a great browser, however it could use improvement is in its cache viewer. While the cache viewer can be used to recover a file, it's&amp;nbsp;unnecessarily&amp;nbsp;complex. I decided to create a solution which makes it easy to recover a file in the cache. I got the idea from reading a post about &lt;a href="http://www.frozax.com/blog/2011/05/recover-file-google-chrome-cache-gzipped/"&gt;how to do the same thing in PHP&lt;/a&gt;. The problem was that I didn't have PHP set up, and, besides, a lot of people don't know how to use PHP. I wanted to create a solution that would be as easy as possible for anyone to use; a solution which wouldn't require you to download, install, or setup anything.&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;

There are three methods you can use to view your cached data. They are sorted in three tabs from the easiest-to-use method on the left (copy/paste) to the most difficult-to-use one on the right (console).

&lt;div id="tabs"&gt;
    &lt;ul&gt;
        &lt;li&gt;&lt;a href="#tabs-1"&gt;Via copy/paste&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href="#tabs-2"&gt;Via saved cache file&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href="#tabs-3"&gt;Via Console&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
    &lt;div id="tabs-1"&gt;
        &lt;h5&gt;Instructions:&lt;/h5&gt;
        &lt;ol&gt;
            &lt;li&gt;In Chrome, open a new tab and navigate to &lt;a href="chrome://cache/" target="_blank"&gt;chrome://cache/&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;Click on whichever file you want to view.&lt;br /&gt;You should then see a page with a bunch of text and numbers.&lt;/li&gt;
            &lt;li&gt;Copy all the text on that page.&lt;/li&gt;
            &lt;li&gt;Paste it in the text box below.&lt;/li&gt;
            &lt;li&gt;Press "Go".&lt;/li&gt;
            &lt;li&gt;The cached data will appear in the &lt;i&gt;Results&lt;/i&gt; section below.&lt;/li&gt;
        &lt;/ol&gt;
        Note: the file contents are not uploaded anywhere. The entire processing is done client side.
        &lt;h4&gt;Paste your content here:&lt;/h4&gt;
        &lt;textarea id="pasteInput" class="large-text fixed-width"&gt;&lt;/textarea&gt;
        &lt;button id="btnCopyPaste"&gt;Go&lt;/button&gt;
        &lt;h4&gt;Results:&lt;/h4&gt;
        &lt;div id="pasteOutput"&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;div id="tabs-2"&gt;
        &lt;h5&gt;Instructions:&lt;/h5&gt;
        &lt;ol&gt;
            &lt;li&gt;In Chrome, open a new tab and navigate to &lt;a href="chrome://cache/" target="_blank"&gt;chrome://cache/&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;Click on whichever file you want to view.&lt;br /&gt;You should then see a page with a bunch of text and numbers.&lt;/li&gt;
            &lt;li&gt;Right-click the page and save it as an HTML file (choose "HTML only", not "Complete").&lt;/li&gt;
            &lt;li&gt;On this page, press &lt;i&gt;Choose File&lt;/i&gt; and choose the file which you just saved.&lt;/li&gt;
            &lt;li&gt;The cached data will appear in the &lt;i&gt;Results&lt;/i&gt; section below.&lt;/li&gt;
        &lt;/ol&gt;
        Note: the file is not uploaded anywhere. The entire processing is done client side.
        &lt;h4&gt;Select your file here:&lt;/h4&gt;
        &lt;input id="userFile" type="file" onchange="fileSelected();" /&gt;
        &lt;h4&gt;Results:&lt;/h4&gt;
        &lt;div id="loadOutput"&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;div id="tabs-3"&gt;
&lt;h5&gt;How to view a file from the cache:&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;Copy the code at the bottom of this post.&lt;/li&gt;
&lt;li&gt;In Chrome, navigate to &lt;a href="chrome://cache/" target="_blank"&gt;chrome://cache/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Click on whichever file you want to view.&lt;br /&gt;You should then see a page with a bunch of text and numbers.&lt;/li&gt;
&lt;li&gt;In Chrome, go to &lt;i&gt;Settings&lt;/i&gt; &amp;gt; &lt;i&gt;Tools&lt;/i&gt; &amp;gt; &lt;i&gt;JavaScript Console&lt;/i&gt;.&lt;/li&gt;
&lt;li&gt;Near the &lt;code&gt;&amp;gt;&lt;/code&gt; character, paste the copied code.&lt;/li&gt;
&lt;li&gt;Press &lt;kbd&gt;Enter&lt;/kbd&gt; on your keyboard.&lt;br /&gt;You should then see a link which says &lt;i&gt;Download cached file&lt;/i&gt; on the top.&lt;/li&gt;
&lt;/ol&gt;

This has been tested on Chrome 17.0 on Mac OS X. In case you're wondering why it's not a one-click bookmarklet, it's because Chrome prevents bookmarklets from running on the cache pages.

&lt;h4&gt;Here's the code you will need to copy:&lt;/h4&gt;
&lt;pre class="javascript" name="code"&gt;(function() {
    var preTags = document.getElementsByTagName('pre');
    var preWithHeaderInfo = preTags[0];
    var preWithContent = preTags[2];

    var lines = preWithContent.textContent.split('\n');
    var text = '';
    for (var i = 0; i &lt; lines.length; i++) {
        var line = lines[i];
        var firstIndex = 11; // first index of the chars to match
        var indexJump = 4;
        var totalCharsPerLine = 16;
        index = firstIndex;
        for (var j = 0; j &lt; totalCharsPerLine; j++) {
            var hexValAsStr = line.substr(index, 2);
            if (hexValAsStr == '  ') {
                // no more chars
                break;
            }

            var asciiVal = parseInt(hexValAsStr, 16);
            text += String.fromCharCode(asciiVal);

            index += indexJump;
        }
    }

    var headerText = preWithHeaderInfo.textContent;
    var elToInsertBefore = document.body.childNodes[0];
    var insertedDiv = document.createElement("div");
    document.body.insertBefore(insertedDiv, elToInsertBefore);

    // find the filename
    var nodes = [document.body];
    var filepath = '';
    while (true) {
        var node = nodes.pop();
        if (node.hasChildNodes()) {
            var children = node.childNodes;
            for (var i = children.length - 1; i &gt;= 0; i--) {
                nodes.push(children[i]);
            }
        }

        if (node.nodeType === Node.TEXT_NODE &amp;&amp; /\S/.test(node.nodeValue)) {
            // 1st depth-first text node (with non-whitespace chars) found
            filepath = node.nodeValue;
            break;
        }
    }
    
    outputResults(insertedDiv, convertToBase64(text), filepath, headerText);

    insertedDiv.appendChild(document.createElement('hr'));

    function outputResults(parentElement, fileContents, fileUrl, headerText) {
        // last updated 1/27/12
        var rgx = /.+\/([^\/]+)/;
        var filename = rgx.exec(fileUrl)[1];

        // get the content type
        rgx = /content-type: (.+)/i;
        var match = rgx.exec(headerText);
        var contentTypeFound = match != null;
        var contentType = "text/plain";
        if (contentTypeFound) {
            contentType = match[1];
        }

        var dataUri = "data:" + contentType + ";base64," + fileContents;

        // check for gzipped file
        var gZipRgx = /content-encoding: gzip/i;
        if (gZipRgx.test(headerText)) {
            filename += '.gz';
        }
        
        // check for image
        var imageRgx = /image/i;
        var isImage = imageRgx.test(contentType);
            
        // create link
        var aTag = document.createElement('a');
        aTag.textContent = "Left-click to download the cached file";
        aTag.setAttribute('href', dataUri);
        aTag.setAttribute('download', filename);
        parentElement.appendChild(aTag);
        parentElement.appendChild(document.createElement('br'));
    
        // create image
        if (isImage) {
            var imgTag = document.createElement('img');
            imgTag.setAttribute("src", dataUri);
            parentElement.appendChild(imgTag);
            parentElement.appendChild(document.createElement('br'));
        }
    
        // create warning
        if (!contentTypeFound) {
            var pTag = document.createElement('p');
            pTag.textContent = "WARNING: the type of file was not found in the headers... defaulting to text file.";
            parentElement.appendChild(pTag);
        }
    }

    function getBase64Char(base64Value) {
        if (base64Value &lt; 0) {
            throw "Invalid number: " + base64Value;
        } else if (base64Value &lt;= 25) {
            // A-Z
            return String.fromCharCode(base64Value + "A".charCodeAt(0));
        } else if (base64Value &lt;= 51) {
            // a-z
            base64Value -= 26; // a
            return String.fromCharCode(base64Value + "a".charCodeAt(0));
        } else if (base64Value &lt;= 61) {
            // 0-9
            base64Value -= 52; // 0
            return String.fromCharCode(base64Value + "0".charCodeAt(0));
        } else if (base64Value &lt;= 62) {
            return '+';
        } else if (base64Value &lt;= 63) {
            return '/';
        } else {
            throw "Invalid number: " + base64Value;
        }
    }

    function convertToBase64(input) {
        // http://en.wikipedia.org/wiki/Base64#Example
        var remainingBits;
        var result = "";
        var additionalCharsNeeded = 0;

        var charIndex = -1;
        var charAsciiValue;
        var advanceToNextChar = function() {
            charIndex++;
            charAsciiValue = input.charCodeAt(charIndex);
            return charIndex &lt; input.length;
        };

        while (true) {
            var base64Char;

            // handle 1st char
            if (!advanceToNextChar()) break;
            base64Char = charAsciiValue &gt;&gt;&gt; 2;
            remainingBits = charAsciiValue &amp; 3; // 0000 0011
            result += getBase64Char(base64Char); // 1st char
            additionalCharsNeeded = 3;

            // handle 2nd char
            if (!advanceToNextChar()) break;
            base64Char = (remainingBits &lt;&lt; 4) | (charAsciiValue &gt;&gt;&gt; 4);
            remainingBits = charAsciiValue &amp; 15; // 0000 1111
            result += getBase64Char(base64Char); // 2nd char
            additionalCharsNeeded = 2;

            // handle 3rd char
            if (!advanceToNextChar()) break;
            base64Char = (remainingBits &lt;&lt; 2) | (charAsciiValue &gt;&gt;&gt; 6);
            result += getBase64Char(base64Char); // 3rd char
            remainingBits = charAsciiValue &amp; 63; // 0011 1111
            result += getBase64Char(remainingBits); // 4th char
            additionalCharsNeeded = 0;
        }

        // there may be an additional 2-3 chars that need to be added
        if (additionalCharsNeeded == 2) {
            remainingBits = remainingBits &lt;&lt; 2; // 4 extra bits
            result += getBase64Char(remainingBits) + "=";
        } else if (additionalCharsNeeded == 3) {
            remainingBits = remainingBits &lt;&lt; 4; // 2 extra bits
            result += getBase64Char(remainingBits) + "==";
        } else if (additionalCharsNeeded != 0) {
            throw "Unhandled number of additional chars needed: " + additionalCharsNeeded;
        }

        return result;
    }
})()&lt;/pre&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div id="version-history"&gt;  
  &lt;ul&gt;  
    &lt;li&gt;1/27/12:  
      &lt;ul&gt;  
      &lt;li&gt;Added 2 new methods of viewing the cache (copy/paste and save/load).&lt;/li&gt;
      &lt;li&gt;Improved code.&lt;/li&gt;  
      &lt;/ul&gt;
    &lt;/li&gt;  
    &lt;li&gt;1/16/12:  
      &lt;ul&gt;  
      &lt;li&gt;Uses the original filename when downloading the file, and adds a '.gz' extension if necessary.&lt;/li&gt;  
      &lt;/ul&gt;  
    &lt;/li&gt;  
  &lt;/ul&gt;  
&lt;/div&gt;

&lt;!--****************** Custom JavaScript  *****************--&gt;
&lt;script type="text/javascript"&gt;
$(function() {
    $("#tabs").tabs();

    $("#btnCopyPaste").click(function() {
        processInput($("#pasteInput").val(), document.getElementById("pasteOutput"));
    });
});

var oFReader = new FileReader();

oFReader.onload = function (oFREvent) {  
    var inputText = oFREvent.target.result;
    var rgx = /(&lt;[^&gt;]+&gt;)+/g;
    inputText = inputText.replace(rgx, "\n");
    processInput(inputText, document.getElementById("loadOutput"));
};

function fileSelected() {
    if (document.getElementById("userFile").files.length === 0) { 
        return; 
    }

    var oFile = document.getElementById("userFile").files[0];  
    oFReader.readAsText(oFile);
}

function processInput(input, parentElement) {
    var rgx = /^0{8}:/gm;
    var matches = sfu.getAllRegexMatches(rgx, input);
    if (matches.length != 2) {
        sfu.error("Unrecognized format... please ensure you copy/pasted correctly.");
        return;
    }
    
    
    // get properties about the url
    var urlRgx = /^\S+/mg;
    var urlMatch = urlRgx.exec(input);
    var url = urlMatch[0];
    var urlEndIndex = urlRgx.lastIndex;
    
    // get properties about the header
    var nextMatch = urlRgx.exec(input); // exploit the fact that the next match can be used to find the beginning of the next line
    var headerStartIndex = nextMatch.index;
    var headerBlobStartIndex = matches[0].index;
    var headers = input.slice(headerStartIndex, headerBlobStartIndex);
    
    // get properties about the content blob
    var contentBlobStartIndex = matches[1].index;
    var contentBlob = input.substr(contentBlobStartIndex);
    
    var file = "";
    var rgxLine = /^[0-9a-f]{8}:  ([0-9a-f ]{2})  ([0-9a-f ]{2})  ([0-9a-f ]{2})  ([0-9a-f ]{2})  ([0-9a-f ]{2})  ([0-9a-f ]{2})  ([0-9a-f ]{2})  ([0-9a-f ]{2})  ([0-9a-f ]{2})  ([0-9a-f ]{2})  ([0-9a-f ]{2})  ([0-9a-f ]{2})  ([0-9a-f ]{2})  ([0-9a-f ]{2})  ([0-9a-f ]{2})  ([0-9a-f ]{2})/gm;
    while ((match = rgxLine.exec(contentBlob)) != null) {
        for (var i = 1; i &lt; match.length; i++) {
            var hexValAsStr = match[i];
            if (hexValAsStr == '  ') {
                // no more chars
                break;
            }
            
            var asciiVal = parseInt(hexValAsStr, 16);
            file += String.fromCharCode(asciiVal);
        }
    }
    
    $(parentElement).empty();
    outputResults(parentElement, sfu.toBase64(file), url, headers);
}

function outputResults(parentElement, fileContents, fileUrl, headerText) {
    // last updated 1/27/12
    var rgx = /.+\/([^\/]+)/;
    var filename = rgx.exec(fileUrl)[1];

    // get the content type
    rgx = /content-type: (.+)/i;
    var match = rgx.exec(headerText);
    var contentTypeFound = match != null;
    var contentType = "text/plain";
    if (contentTypeFound) {
        contentType = match[1];
    }

    var dataUri = "data:" + contentType + ";base64," + fileContents;

    // check for gzipped file
    var gZipRgx = /content-encoding: gzip/i;
    if (gZipRgx.test(headerText)) {
        filename += '.gz';
    }
    
    // check for image
    var imageRgx = /image/i;
    var isImage = imageRgx.test(contentType);
        
    // create link
    var aTag = document.createElement('a');
    aTag.textContent = "Left-click to download the cached file";
    aTag.setAttribute('href', dataUri);
    aTag.setAttribute('download', filename);
    parentElement.appendChild(aTag);
    parentElement.appendChild(document.createElement('br'));

    // create image
    if (isImage) {
        var imgTag = document.createElement('img');
        imgTag.setAttribute("src", dataUri);
        parentElement.appendChild(imgTag);
        parentElement.appendChild(document.createElement('br'));
    }

    // create warning
    if (!contentTypeFound) {
        var pTag = document.createElement('p');
        pTag.textContent = "WARNING: the type of file was not found in the headers... defaulting to text file.";
        parentElement.appendChild(pTag);
    }
}
&lt;/script&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-282552020978061692?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/282552020978061692/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2012/01/viewing-chrome-cache-easy-way.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/282552020978061692'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/282552020978061692'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2012/01/viewing-chrome-cache-easy-way.html' title='Viewing Chrome cache (the easy way)'/><author><name>Senseful</name><uri>http://www.blogger.com/profile/17245419786727860080</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-7454746476017634115</id><published>2012-01-03T21:54:00.000-08:00</published><updated>2012-01-07T14:51:32.206-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:#How To'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:Evernote'/><title type='text'>How to copy tags from one note to another in Evernote (PC)</title><content type='html'>The option for duplicating a note and also copying along the tags is hidden inside the "Notebooks..." menu item. Since I didn't know about it at the time, I had to look for another way to copy the tags from one note to another. I found an easy method...&lt;a name='more'&gt;&lt;/a&gt;&lt;h5&gt;How to copy a note's tag to another note in Evernote (PC):&lt;/h5&gt;&lt;ol&gt;&lt;li&gt;Select both notes in the note list.&lt;/li&gt;&lt;li&gt;Right-click and choose &lt;code&gt;Tag Notes...&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Check &lt;code&gt;Hide unassigned tags&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Press &lt;code&gt;Select All&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Press &lt;code&gt;OK&lt;/code&gt;&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;That's it! Now both notes will contain the same exact tags (i.e. a union between both sets of tags).&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-7454746476017634115?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/7454746476017634115/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2012/01/how-to-copy-tags-from-one-note-to.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/7454746476017634115'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/7454746476017634115'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2012/01/how-to-copy-tags-from-one-note-to.html' title='How to copy tags from one note to another in Evernote (PC)'/><author><name>Senseful</name><uri>http://www.blogger.com/profile/17245419786727860080</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-9106021019562100131</id><published>2010-12-30T19:15:00.000-08:00</published><updated>2012-01-16T21:13:10.496-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:#Utility'/><title type='text'>JavaScript Client Side Diff Tool</title><content type='html'>Here's a simple utility for comparing text which uses the JavaScript library &lt;a href="http://snowtide.com/jsdifflib"&gt;jsdifflib&lt;/a&gt; created by Chas Emerick. This utility only uses client side JavaScript, which means that none of your data is transmitted over the Internet.

&lt;a name='more'&gt;&lt;/a&gt;

&lt;br /&gt;
 &lt;strong&gt;Diff View Type:&lt;/strong&gt; 
 &lt;input type="radio" name="_viewtype" checked="checked" id="sidebyside"/&gt; Side by Side
 &amp;#160;&amp;#160;
 &lt;input type="radio" name="_viewtype" id="inline"/&gt; Inline&lt;br /&gt;
&lt;br /&gt;
 &lt;strong&gt;Base Text:&lt;/strong&gt;&lt;br /&gt;
 &lt;textarea id="baseText" class="large-text"&gt;abcd
efgh
ijkl
mnop
qrst
uvwx
yz.

If your text is on one line and in sentence form, you should try using sentences mode. Instead of splitting on new lines, sentences mode splits on a period, exclamation point, or question mark. Go ahead and try it out by selecting sentences on the bottom to see if you can spot the differences in this long line.&lt;/textarea&gt;&lt;br/&gt; 
 
 &lt;strong&gt;New Text:&lt;/strong&gt;&lt;br /&gt;
 &lt;textarea id="newText" class="large-text"&gt;abcd
efgh
ijkl
maop
qrst
uvwx
yz.

If your text is on one line and in sentence form, you should try using sentences mode. Instead of splitting on new lines, sentences mode splits on a period, exclamation point, or question mark. This should make it easier to spot the differences. Go ahead and try it out by selecting sentences on the bottom to see if you can spot the differences in this long line.&lt;/textarea&gt;&lt;br/&gt; 
Split type:
&lt;select id="split-type"&gt;
  &lt;option value="lines"&gt;Lines&lt;/option&gt;
  &lt;option value="sent"&gt;Sentences&lt;/option&gt;
&lt;/select&gt;
 &lt;input type="button" value="Submit" onclick="javascript:diffUsingJS();"/&gt;
&lt;input id="hide-same-lines" type="checkbox" /&gt;&lt;label for="hide-same-lines"&gt;Hide lines that are the same&lt;/label&gt;&lt;input id="ignoreWhitespace" type="checkbox" /&gt;&lt;label for="ignoreWhitespace"&gt;Ignore whitespace&lt;/label&gt;

&lt;br/&gt;&lt;br/&gt; 
 &lt;a name="diff"&gt; &lt;/a&gt; 
 &lt;div id="diffoutput" style="width:100%" overflow:"scroll"&gt; &lt;/div&gt; 

&lt;div id="version-history"&gt;
&lt;ul&gt;
&lt;li&gt;1/16/11:
&lt;ul&gt;
&lt;li&gt;Ignore whitespace: strips all whitespace before the comparison is done.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;

&lt;!-- custom styles --&gt;
&lt;style type="text/css"&gt;
.fixed-width-table {
    width: 100%;
    table-layout: fixed;
}
table.diff td {
    overflow-x: auto;
}
table.diff tbody th.fix-vert-numbers {
  padding: .3em .5em 0 0 !important; /* fixes digits in numbers such as "10" from appearing one on top of the other */
}
&lt;/style&gt;

&lt;script type="text/javascript"&gt;
function hideSameLinesRefresh() {
  var $sameLines = $('table.diff tbody tr').not(':has(td.replace, td.insert, td.delete)');
  if ($('#hide-same-lines:checked').val())
    $sameLines.hide();
  else
    $sameLines.show();
}

$(function() {
  $('#hide-same-lines').change(function() {
    hideSameLinesRefresh();
  });
});

function diffUsingJS () {
  var baseText = $("#baseText").val();
  var newText = $("#newText").val();
  if ($('#split-type').val() == "sent") {
    baseText = baseText.replace(/[\r\n]+/g, ' ');
    newText = newText.replace(/[\r\n]+/g, ' ');
    baseText = baseText.replace(/[\.!?]\s/g, '$&amp;\n');
    newText = newText.replace(/[\.!?]\s/g, '$&amp;\n');
  }

 var base = difflib.stringAsLines(baseText);
 var newtxt = difflib.stringAsLines(newText);

  if ($('#ignoreWhitespace').prop('checked')) {
    $.each(base, function(i, v) {
      base[i] = sfu.trim(v);
    });
    $.each(newtxt, function(i, v) {
      newtxt[i] = sfu.trim(v);
    });
  }

 var sm = new difflib.SequenceMatcher(base, newtxt);
 var opcodes = sm.get_opcodes();
 var $diffoutputdiv = $("#diffoutput");
        $diffoutputdiv.empty();
 $diffoutputdiv.append(diffview.buildView({ baseTextLines:base,
               newTextLines:newtxt,
               opcodes:opcodes,
               baseTextName:"Base Text",
               newTextName:"New Text",
               contextSize:null,
               viewType: $("#inline").is(':checked') ? 1 : 0 }));
 //window.location = url + "#diff";

 // in case of really long columns, hard code the widths before setting it to a fixed tale layout.

 // select the 1st and 3rd column headers
 var $numCols = $('table.diff &gt; thead &gt; tr:first &gt; th:not(.texttitle)');
 var numColWidth = $numCols.width();
 $numCols.css('width', numColWidth);
 $('table.diff').addClass('fixed-width-table');

 // fix vertical numbers -- only at end so that it doesn't affect initial width
 $('table.diff tbody th').addClass('fix-vert-numbers');

 // hide lines if needed
 hideSameLinesRefresh();
}
&lt;/script&gt;

&lt;script type="text/javascript"&gt;
/***
This is part of jsdifflib v1.0. &lt;http://snowtide.com/jsdifflib&gt;

Copyright (c) 2007, Snowtide Informatics Systems, Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

 * Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.
 * Neither the name of the Snowtide Informatics Systems nor the names of its
  contributors may be used to endorse or promote products derived from this
  software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
***/
/* Author: Chas Emerick &lt;cemerick@snowtide.com&gt; */
__whitespace = {" ":true, "\t":true, "\n":true, "\f":true, "\r":true};

difflib = {
 defaultJunkFunction: function (c) {
  return c in __whitespace;
 },
 
 stripLinebreaks: function (str) { return str.replace(/^[\n\r]*|[\n\r]*$/g, ""); },
 
 stringAsLines: function (str) {
  var lfpos = str.indexOf("\n");
  var crpos = str.indexOf("\r");
  var linebreak = ((lfpos &gt; -1 &amp;&amp; crpos &gt; -1) || crpos &lt; 0) ? "\n" : "\r";
  
  var lines = str.split(linebreak);
  for (var i = 0; i &lt; lines.length; i++) {
   lines[i] = difflib.stripLinebreaks(lines[i]);
  }
  
  return lines;
 },
 
 // iteration-based reduce implementation
 __reduce: function (func, list, initial) {
  if (initial != null) {
   var value = initial;
   var idx = 0;
  } else if (list) {
   var value = list[0];
   var idx = 1;
  } else {
   return null;
  }
  
  for (; idx &lt; list.length; idx++) {
   value = func(value, list[idx]);
  }
  
  return value;
 },
 
 // comparison function for sorting lists of numeric tuples
 __ntuplecomp: function (a, b) {
  var mlen = Math.max(a.length, b.length);
  for (var i = 0; i &lt; mlen; i++) {
   if (a[i] &lt; b[i]) return -1;
   if (a[i] &gt; b[i]) return 1;
  }
  
  return a.length == b.length ? 0 : (a.length &lt; b.length ? -1 : 1);
 },
 
 __calculate_ratio: function (matches, length) {
  return length ? 2.0 * matches / length : 1.0;
 },
 
 // returns a function that returns true if a key passed to the returned function
 // is in the dict (js object) provided to this function; replaces being able to
 // carry around dict.has_key in python...
 __isindict: function (dict) {
  return function (key) { return key in dict; };
 },
 
 // replacement for python's dict.get function -- need easy default values
 __dictget: function (dict, key, defaultValue) {
  return key in dict ? dict[key] : defaultValue;
 }, 
 
 SequenceMatcher: function (a, b, isjunk) {
  this.set_seqs = function (a, b) {
   this.set_seq1(a);
   this.set_seq2(b);
  }
  
  this.set_seq1 = function (a) {
   if (a == this.a) return;
   this.a = a;
   this.matching_blocks = this.opcodes = null;
  }
  
  this.set_seq2 = function (b) {
   if (b == this.b) return;
   this.b = b;
   this.matching_blocks = this.opcodes = this.fullbcount = null;
   this.__chain_b();
  }
  
  this.__chain_b = function () {
   var b = this.b;
   var n = b.length;
   var b2j = this.b2j = {};
   var populardict = {};
   for (var i = 0; i &lt; b.length; i++) {
    var elt = b[i];
    if (elt in b2j) {
     var indices = b2j[elt];
     if (n &gt;= 200 &amp;&amp; indices.length * 100 &gt; n) {
      populardict[elt] = 1;
      delete b2j[elt];
     } else {
      indices.push(i);
     }
    } else {
     b2j[elt] = [i];
    }
   }
 
   for (var elt in populardict)
    delete b2j[elt];
   
   var isjunk = this.isjunk;
   var junkdict = {};
   if (isjunk) {
    for (var elt in populardict) {
     if (isjunk(elt)) {
      junkdict[elt] = 1;
      delete populardict[elt];
     }
    }
    for (var elt in b2j) {
     if (isjunk(elt)) {
      junkdict[elt] = 1;
      delete b2j[elt];
     }
    }
   }
 
   this.isbjunk = difflib.__isindict(junkdict);
   this.isbpopular = difflib.__isindict(populardict);
  }
  
  this.find_longest_match = function (alo, ahi, blo, bhi) {
   var a = this.a;
   var b = this.b;
   var b2j = this.b2j;
   var isbjunk = this.isbjunk;
   var besti = alo;
   var bestj = blo;
   var bestsize = 0;
   var j = null;
 
   var j2len = {};
   var nothing = [];
   for (var i = alo; i &lt; ahi; i++) {
    var newj2len = {};
    var jdict = difflib.__dictget(b2j, a[i], nothing);
    for (var jkey in jdict) {
     j = jdict[jkey];
     if (j &lt; blo) continue;
     if (j &gt;= bhi) break;
     newj2len[j] = k = difflib.__dictget(j2len, j - 1, 0) + 1;
     if (k &gt; bestsize) {
      besti = i - k + 1;
      bestj = j - k + 1;
      bestsize = k;
     }
    }
    j2len = newj2len;
   }
 
   while (besti &gt; alo &amp;&amp; bestj &gt; blo &amp;&amp; !isbjunk(b[bestj - 1]) &amp;&amp; a[besti - 1] == b[bestj - 1]) {
    besti--;
    bestj--;
    bestsize++;
   }
    
   while (besti + bestsize &lt; ahi &amp;&amp; bestj + bestsize &lt; bhi &amp;&amp;
     !isbjunk(b[bestj + bestsize]) &amp;&amp;
     a[besti + bestsize] == b[bestj + bestsize]) {
    bestsize++;
   }
 
   while (besti &gt; alo &amp;&amp; bestj &gt; blo &amp;&amp; isbjunk(b[bestj - 1]) &amp;&amp; a[besti - 1] == b[bestj - 1]) {
    besti--;
    bestj--;
    bestsize++;
   }
   
   while (besti + bestsize &lt; ahi &amp;&amp; bestj + bestsize &lt; bhi &amp;&amp; isbjunk(b[bestj + bestsize]) &amp;&amp;
      a[besti + bestsize] == b[bestj + bestsize]) {
    bestsize++;
   }
 
   return [besti, bestj, bestsize];
  }
  
  this.get_matching_blocks = function () {
   if (this.matching_blocks != null) return this.matching_blocks;
   var la = this.a.length;
   var lb = this.b.length;
 
   var queue = [[0, la, 0, lb]];
   var matching_blocks = [];
   var alo, ahi, blo, bhi, qi, i, j, k, x;
   while (queue.length) {
    qi = queue.pop();
    alo = qi[0];
    ahi = qi[1];
    blo = qi[2];
    bhi = qi[3];
    x = this.find_longest_match(alo, ahi, blo, bhi);
    i = x[0];
    j = x[1];
    k = x[2];
 
    if (k) {
     matching_blocks.push(x);
     if (alo &lt; i &amp;&amp; blo &lt; j)
      queue.push([alo, i, blo, j]);
     if (i+k &lt; ahi &amp;&amp; j+k &lt; bhi)
      queue.push([i + k, ahi, j + k, bhi]);
    }
   }
   
   matching_blocks.sort(difflib.__ntuplecomp);
 
   var i1 = j1 = k1 = block = 0;
   var non_adjacent = [];
   for (var idx in matching_blocks) {
    block = matching_blocks[idx];
    i2 = block[0];
    j2 = block[1];
    k2 = block[2];
    if (i1 + k1 == i2 &amp;&amp; j1 + k1 == j2) {
     k1 += k2;
    } else {
     if (k1) non_adjacent.push([i1, j1, k1]);
     i1 = i2;
     j1 = j2;
     k1 = k2;
    }
   }
   
   if (k1) non_adjacent.push([i1, j1, k1]);
 
   non_adjacent.push([la, lb, 0]);
   this.matching_blocks = non_adjacent;
   return this.matching_blocks;
  }
  
  this.get_opcodes = function () {
   if (this.opcodes != null) return this.opcodes;
   var i = 0;
   var j = 0;
   var answer = [];
   this.opcodes = answer;
   var block, ai, bj, size, tag;
   var blocks = this.get_matching_blocks();
   for (var idx in blocks) {
    block = blocks[idx];
    ai = block[0];
    bj = block[1];
    size = block[2];
    tag = '';
    if (i &lt; ai &amp;&amp; j &lt; bj) {
     tag = 'replace';
    } else if (i &lt; ai) {
     tag = 'delete';
    } else if (j &lt; bj) {
     tag = 'insert';
    }
    if (tag) answer.push([tag, i, ai, j, bj]);
    i = ai + size;
    j = bj + size;
    
    if (size) answer.push(['equal', ai, i, bj, j]);
   }
   
   return answer;
  }
  
  // this is a generator function in the python lib, which of course is not supported in javascript
  // the reimplementation builds up the grouped opcodes into a list in their entirety and returns that.
  this.get_grouped_opcodes = function (n) {
   if (!n) n = 3;
   var codes = this.get_opcodes();
   if (!codes) codes = [["equal", 0, 1, 0, 1]];
   var code, tag, i1, i2, j1, j2;
   if (codes[0][0] == 'equal') {
    code = codes[0];
    tag = code[0];
    i1 = code[1];
    i2 = code[2];
    j1 = code[3];
    j2 = code[4];
    codes[0] = [tag, Math.max(i1, i2 - n), i2, Math.max(j1, j2 - n), j2];
   }
   if (codes[codes.length - 1][0] == 'equal') {
    code = codes[codes.length - 1];
    tag = code[0];
    i1 = code[1];
    i2 = code[2];
    j1 = code[3];
    j2 = code[4];
    codes[codes.length - 1] = [tag, i1, Math.min(i2, i1 + n), j1, Math.min(j2, j1 + n)];
   }
 
   var nn = n + n;
   var groups = [];
   for (var idx in codes) {
    code = codes[idx];
    tag = code[0];
    i1 = code[1];
    i2 = code[2];
    j1 = code[3];
    j2 = code[4];
    if (tag == 'equal' &amp;&amp; i2 - i1 &gt; nn) {
     groups.push([tag, i1, Math.min(i2, i1 + n), j1, Math.min(j2, j1 + n)]);
     i1 = Math.max(i1, i2-n);
     j1 = Math.max(j1, j2-n);
    }
    
    groups.push([tag, i1, i2, j1, j2]);
   }
   
   if (groups &amp;&amp; groups[groups.length - 1][0] == 'equal') groups.pop();
   
   return groups;
  }
  
  this.ratio = function () {
   matches = difflib.__reduce(
       function (sum, triple) { return sum + triple[triple.length - 1]; },
       this.get_matching_blocks(), 0);
   return difflib.__calculate_ratio(matches, this.a.length + this.b.length);
  }
  
  this.quick_ratio = function () {
   var fullbcount, elt;
   if (this.fullbcount == null) {
    this.fullbcount = fullbcount = {};
    for (var i = 0; i &lt; this.b.length; i++) {
     elt = this.b[i];
     fullbcount[elt] = difflib.__dictget(fullbcount, elt, 0) + 1;
    }
   }
   fullbcount = this.fullbcount;
 
   var avail = {};
   var availhas = difflib.__isindict(avail);
   var matches = numb = 0;
   for (var i = 0; i &lt; this.a.length; i++) {
    elt = this.a[i];
    if (availhas(elt)) {
     numb = avail[elt];
    } else {
     numb = difflib.__dictget(fullbcount, elt, 0);
    }
    avail[elt] = numb - 1;
    if (numb &gt; 0) matches++;
   }
   
   return difflib.__calculate_ratio(matches, this.a.length + this.b.length);
  }
  
  this.real_quick_ratio = function () {
   var la = this.a.length;
   var lb = this.b.length;
   return _calculate_ratio(Math.min(la, lb), la + lb);
  }
  
  this.isjunk = isjunk ? isjunk : difflib.defaultJunkFunction;
  this.a = this.b = null;
  this.set_seqs(a, b);
 }
}

/***
This is part of jsdifflib v1.0. &lt;http://snowtide.com/jsdifflib&gt;

Copyright (c) 2007, Snowtide Informatics Systems, Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

 * Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.
 * Neither the name of the Snowtide Informatics Systems nor the names of its
  contributors may be used to endorse or promote products derived from this
  software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
***/
/* Author: Chas Emerick &lt;cemerick@snowtide.com&gt; */
diffview = {
 /**
  * Builds and returns a visual diff view.  The single parameter, `params', should contain
  * the following values:
  *
  * - baseTextLines: the array of strings that was used as the base text input to SequenceMatcher
  * - newTextLines: the array of strings that was used as the new text input to SequenceMatcher
  * - opcodes: the array of arrays returned by SequenceMatcher.get_opcodes()
  * - baseTextName: the title to be displayed above the base text listing in the diff view; defaults
  *    to "Base Text"
  * - newTextName: the title to be displayed above the new text listing in the diff view; defaults
  *    to "New Text"
  * - contextSize: the number of lines of context to show around differences; by default, all lines
  *    are shown
  * - viewType: if 0, a side-by-side diff view is generated (default); if 1, an inline diff view is
  *    generated
  */
 buildView: function (params) {
  var baseTextLines = params.baseTextLines;
  var newTextLines = params.newTextLines;
  var opcodes = params.opcodes;
  var baseTextName = params.baseTextName ? params.baseTextName : "Base Text";
  var newTextName = params.newTextName ? params.newTextName : "New Text";
  var contextSize = params.contextSize;
  var inline = (params.viewType == 0 || params.viewType == 1) ? params.viewType : 0;

  if (baseTextLines == null)
   throw "Cannot build diff view; baseTextLines is not defined.";
  if (newTextLines == null)
   throw "Cannot build diff view; newTextLines is not defined.";
  if (!opcodes)
   throw "Canno build diff view; opcodes is not defined.";
  
  function celt (name, clazz) {
   var e = document.createElement(name);
   e.className = clazz;
   return e;
  }
  
  function telt (name, text) {
   var e = document.createElement(name);
   e.appendChild(document.createTextNode(text));
   return e;
  }
  
  function ctelt (name, clazz, text) {
   var e = document.createElement(name);
   e.className = clazz;
   e.appendChild(document.createTextNode(text));
   return e;
  }
 
  var tdata = document.createElement("thead");
  var node = document.createElement("tr");
  tdata.appendChild(node);
  if (inline) {
   node.appendChild(document.createElement("th"));
   node.appendChild(document.createElement("th"));
   node.appendChild(ctelt("th", "texttitle", baseTextName + " vs. " + newTextName));
  } else {
   node.appendChild(document.createElement("th"));
   node.appendChild(ctelt("th", "texttitle", baseTextName));
   node.appendChild(document.createElement("th"));
   node.appendChild(ctelt("th", "texttitle", newTextName));
  }
  tdata = [tdata];
  
  var rows = [];
  var node2;
  
  /**
   * Adds two cells to the given row; if the given row corresponds to a real
   * line number (based on the line index tidx and the endpoint of the 
   * range in question tend), then the cells will contain the line number
   * and the line of text from textLines at position tidx (with the class of
   * the second cell set to the name of the change represented), and tidx + 1 will
   * be returned.  Otherwise, tidx is returned, and two empty cells are added
   * to the given row.
   */
  function addCells (row, tidx, tend, textLines, change) {
   if (tidx &lt; tend) {
    row.appendChild(telt("th", (tidx + 1).toString()));
    row.appendChild(ctelt("td", change, textLines[tidx].replace(/\t/g, "\u00a0\u00a0\u00a0\u00a0")));
    return tidx + 1;
   } else {
    row.appendChild(document.createElement("th"));
    row.appendChild(celt("td", "empty"));
    return tidx;
   }
  }
  
  function addCellsInline (row, tidx, tidx2, textLines, change) {
   row.appendChild(telt("th", tidx == null ? "" : (tidx + 1).toString()));
   row.appendChild(telt("th", tidx2 == null ? "" : (tidx2 + 1).toString()));
   row.appendChild(ctelt("td", change, textLines[tidx != null ? tidx : tidx2].replace(/\t/g, "\u00a0\u00a0\u00a0\u00a0")));
  }
  
  for (var idx = 0; idx &lt; opcodes.length; idx++) {
   code = opcodes[idx];
   change = code[0];
   var b = code[1];
   var be = code[2];
   var n = code[3];
   var ne = code[4];
   var rowcnt = Math.max(be - b, ne - n);
   var toprows = [];
   var botrows = [];
   for (var i = 0; i &lt; rowcnt; i++) {
    // jump ahead if we've alredy provided leading context or if this is the first range
    if (contextSize &amp;&amp; opcodes.length &gt; 1 &amp;&amp; ((idx &gt; 0 &amp;&amp; i == contextSize) || (idx == 0 &amp;&amp; i == 0)) &amp;&amp; change=="equal") {
     var jump = rowcnt - ((idx == 0 ? 1 : 2) * contextSize);
     if (jump &gt; 1) {
      toprows.push(node = document.createElement("tr"));
      
      b += jump;
      n += jump;
      i += jump - 1;
      node.appendChild(telt("th", "..."));
      if (!inline) node.appendChild(ctelt("td", "skip", ""));
      node.appendChild(telt("th", "..."));
      node.appendChild(ctelt("td", "skip", ""));
      
      // skip last lines if they're all equal
      if (idx + 1 == opcodes.length) {
       break;
      } else {
       continue;
      }
     }
    }
    
    toprows.push(node = document.createElement("tr"));
    if (inline) {
     if (change == "insert") {
      addCellsInline(node, null, n++, newTextLines, change);
     } else if (change == "replace") {
      botrows.push(node2 = document.createElement("tr"));
      if (b &lt; be) addCellsInline(node, b++, null, baseTextLines, "delete");
      if (n &lt; ne) addCellsInline(node2, null, n++, newTextLines, "insert");
     } else if (change == "delete") {
      addCellsInline(node, b++, null, baseTextLines, change);
     } else {
      // equal
      addCellsInline(node, b++, n++, baseTextLines, change);
     }
    } else {
     b = addCells(node, b, be, baseTextLines, change);
     n = addCells(node, n, ne, newTextLines, change);
    }
   }

   for (var i = 0; i &lt; toprows.length; i++) rows.push(toprows[i]);
   for (var i = 0; i &lt; botrows.length; i++) rows.push(botrows[i]);
  }
  
  rows.push(node = ctelt("th", "author", "diff view generated by "));
  node.setAttribute("colspan", inline ? 3 : 4);
  node.appendChild(node2 = telt("a", "jsdifflib"));
  node2.setAttribute("href", "http://snowtide.com/jsdifflib");
  
  tdata.push(node = document.createElement("tbody"));
  for (var idx in rows) node.appendChild(rows[idx]);
  
  node = celt("table", "diff" + (inline ? " inlinediff" : ""));
  for (var idx in tdata) node.appendChild(tdata[idx]);
  return node;
 }
}
&lt;/script&gt;
&lt;style type="text/css"&gt;
/***
This is part of jsdifflib v1.0. &lt;http://snowtide.com/jsdifflib&gt;

Copyright (c) 2007, Snowtide Informatics Systems, Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

 * Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.
 * Neither the name of the Snowtide Informatics Systems nor the names of its
  contributors may be used to endorse or promote products derived from this
  software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
***/
/* Author: Chas Emerick &lt;cemerick@snowtide.com&gt; */
table.diff {
 border-collapse:collapse;
 border:1px solid darkgray
}
table.diff tbody { 
 font-family:Courier, monospace
}
table.diff tbody th {
 font-family:verdana,arial,'Bitstream Vera Sans',helvetica,sans-serif;
 background:#EED;
 font-size:11px;
 font-weight:normal;
 border:1px solid #BBC;
 color:#886;
 padding:.3em .5em .1em 2em;
 text-align:right;
 vertical-align:top
}
table.diff thead {
 border-bottom:1px solid #BBC;
 background:#EFEFEF;
 font-family:Verdana
}
table.diff thead th.texttitle {
 text-align:left
}
table.diff tbody td {
 padding:0px .4em;
 padding-top:.4em;
 vertical-align:top;
}
table.diff .empty {
 background-color:#DDD;
}
table.diff .replace {
 background-color:#FD8
}
table.diff .delete {
 background-color:#E99;
}
table.diff .skip {
 background-color:#EFEFEF;
 border:1px solid #AAA;
 border-right:1px solid #BBC;
}
table.diff .insert {
 background-color:#9E9
}
table.diff th.author {
 text-align:right;
 border-top:1px solid #BBC;
 background:#EFEFEF
}
&lt;/style&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-9106021019562100131?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/9106021019562100131/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/12/javascript-client-side-diff-tool.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/9106021019562100131'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/9106021019562100131'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/12/javascript-client-side-diff-tool.html' title='JavaScript Client Side Diff Tool'/><author><name>Senseful</name><uri>http://www.blogger.com/profile/17245419786727860080</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-8919077598952698066</id><published>2010-12-24T11:10:00.000-08:00</published><updated>2012-01-16T02:03:13.741-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:#Tip'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:Evernote'/><title type='text'>Evernote Tip: Make notes easier to access</title><content type='html'>While using Evernote, you may notice that some notes are referenced a lot. The two main reasons are either because the note contains too much information that can't be memorized, or its information is constantly updated (i.e. you replace your gym's schedule every month). Since these notes are the ones that are accessed most frequently, they should also be the easiest ones to access. Read on for a few ways to do this.&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
Here are a several ways to make notes easy to access:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Use a notebook&lt;/b&gt;: Create a notebook specifically designed for notes that are frequently referenced. Notebooks are often the easiest to filter on in Evernote clients. The only downside is that notes can't be in more than one notebook, so it might not fit your organizational structure. If you minimize the amount of notebooks you use, however, this could be very useful. Another trick you could do is use the new notebook folders feature to create the following folders: [Notebook], [Notebook/Frequently Referenced], and [Notebook/Main]. And instead of using the query [Main], use [Notebook]. This way the frequently referenced notes still appear under the same notebook, but they can also easily be accessed. To access these notes, you would simply click on the notebook.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Use a tag&lt;/b&gt;: This is fairly simple. You would access the note by typing something like [tag:a] in your query. You should make the tag as short as possible. You don't need to worry about it being unique since it doesn't have the same problem that a unique string would have. When you clip a note, even if it does contain this tag name in it's body, since you are filtering with the tag prefix it won't show up in your search. You could use a symbol as the tag name as well (e.g. [tag:-]). To access these notes, you would type the following into the search: tag:a.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Use a saved search&lt;/b&gt;: You would use this in conjunction with creating a tag. This time your tag should be more descriptive (e.g. [tag:Frequently Accessed]), and simply create a saved search which filters for this tag. It may seem odd to create a saved search for a single tag, but many of the clients make it very easy to access a saved search, often times it's even easier than getting to a tag. To access these notes, you would click the saved search.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Use a unique string&lt;/b&gt;: In this method, you would add a string to a note so that it shows up when you filter on it. A string such as "abzc" probably doesn't exist in any of your notes (you should run a query to make sure it doesn't before you decide on which string to use). Better yet, search for it in Google to see if any pages on the Internet use the string. This will make it even more likely that when you tag notes in the future, it won't accidentally be added to a note. They key is to find a good balance between something that is easy to type (few characters), but is unique enough to not exist in a note you clip. Some clients (e.g. Mac) will automatically perform a "starts with search" when you type something in the search box, so you should try to find a string that isn't used in the begging of any words either. The last thing to consider is that the image recognition algorithm may find the string incorrectly in an image somewhere. The longer your string, the less likely this will happen. If you don't want to write this in the note somewhere, you could use it as tag since Evernote automatically searches tags as well. To access these notes, you would run a search on your unique string: abzc.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
Another thing to consider when using a tag, saved search, or notebook, is to make the notes appear near the top so that they are easier to access. You can do this by &lt;a href="/2012/01/evernote-tag-sort-order-utility.html"&gt;prefixing them with a symbol&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
I personally like using the saved search option. I named the tag and the saved search with a symbol prefix so that they both appear on the top of their lists. This way if one of the lists is not easily accessible, the other can still be used.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-8919077598952698066?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/8919077598952698066/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/12/evernote-tip-make-notes-easier-to.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/8919077598952698066'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/8919077598952698066'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/12/evernote-tip-make-notes-easier-to.html' title='Evernote Tip: Make notes easier to access'/><author><name>Senseful</name><uri>http://www.blogger.com/profile/17245419786727860080</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-7102028748210286791</id><published>2010-12-17T18:58:00.000-08:00</published><updated>2011-06-24T23:25:26.000-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:#Utility'/><title type='text'>Schedule Creator</title><content type='html'>Here's a utility which will let you find schedules that work together. You input the name of the classes you want to take and all of the times each class is offered. This utility will then show you all the possible schedules that can be made. The best way to understand how to use it is by looking at the pre-populated sample text.

Disclaimer: This isn't a very robust application, and it might cause unpredictable behavior if no schedule can be created. I'll try to improve this in the future whenever I have more free time.

&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;style type="text/css"&gt;
#outputSchedule {
  border-width: 0px;
  border-collapse: collapse;
  border-spacing: ;
}
#outputSchedule th, #outputSchedule td {
  border: 1px solid black;
}
#outputSchedule tr td:first-child {
  text-align: right;
  width:40px;
}
.instructions {
  color: gray;
  font-size: smaller;
}
&lt;/style&gt;
&lt;script type="text/javascript"&gt;
var daysInWeek = 7;
var dayLetters = ['Sun', 'M', 'T', 'W', 'Th', 'F', 'Sat'];
var dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
var AUTO_SELECT_SECTION_VAL = -1;
var optionRequiredVal = -1; // must be greater than optionOptionalVal
var optionOptionalVal = -2;
var optionGroups = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'];
var noLocationsEntered = true;
var commuteTimesBetweenLocations = [];
var defaultCommuteTimeBetweenLocations = 10;
var commuteColor = '#DDDDDD';

// classes

function createCommuteTimeBetweenLocations(fromLocation, toLocation, mins) {
    return {
        fromLocation: fromLocation,
        toLocation: toLocation,
        mins: mins
    }
}

function getCommuteTimeBetweenLocations(fromLocation, toLocation) {
    if (noLocationsEntered) {
        return 0; // if no location information entered, then disregard this feature
    }
    for (var i = 0; i &lt; commuteTimesBetweenLocations.length; i++) {
        var c = commuteTimesBetweenLocations[i];
        if (c.fromLocation == fromLocation &amp;&amp; c.toLocation == toLocation) // TODO: could use a more efficient algorithm
            return c.mins;
    }
    return defaultCommuteTimeBetweenLocations;
}

function createClass(name) {
    return {
        name: name,
        location: null,
        sections: [],
        color: null,
        $select: null,
        $optionCombobox: null,
        addSectionAndTime: function(sectionName, day, startTime, endTime) {
            var section = this.getSectionByName(sectionName);
            if (!section) {
                // if a section does not already exist, create it
                section = createSection(sectionName, this);
                this.sections.push(section);
            }
            section.addTime(day, startTime, endTime);
            return section;
        },
        getSectionByName: function(sectionName) {
            for (var i = 0; i &lt; this.sections.length; i++) {
                if (this.sections[i].name == sectionName) return this.sections[i];
            }
            return null;
        },
        hasLocation: function() {
            return this.location != null;
        },
        isRequired: function() {
            return this.$optionCombobox.val() == optionRequiredVal;
        },
        isOptional: function() {
            return this.$optionCombobox.val() == optionOptionalVal;
        },
        isInGroup: function() {
            return this.$optionCombobox.val() &gt; optionRequiredVal;
        },
        getGroup: function() {
            return this.$optionCombobox.val();
        },
        getPreferredSection: function() {
            return this.sections[this.$select.val()];
        },
        hasPreferredSection: function() {
            return this.$select.val() &gt; AUTO_SELECT_SECTION_VAL;
        }
    }
}

function createSection(name, aClass) {
    return {
        name: name,
        times: [], // array of Time objects
        aClass: aClass,
        addTime: function(day, startTime, endTime) {
            // TODO: make sure this isn't a repeat
            var time = createTime(day, startTime, endTime);
            this.times.push(time);
        },
        conflictsWithSection: function(section) {
            var commuteTimeFromThisToSection = getCommuteTimeBetweenLocations(this.aClass.location, section.aClass.location);
            var commuteTimeFromSectionToThis = getCommuteTimeBetweenLocations(section.aClass.location, this.aClass.location);
            for (var i = 0; i &lt; this.times.length; i++) {
                for (var j = 0; j &lt; section.times.length; j++) {
                    if (this.times[i].conflictsWithTime(section.times[j], commuteTimeFromThisToSection, commuteTimeFromSectionToThis)) {
                        return true;
                    }
                }
            }
            return false;
        },
        getTimesAsStr: function() {
            var result = '';
            for (var i = 0; i &lt; daysInWeek; i++) {
                for (var j = 0; j &lt; this.times.length; j++) {
                    var time = this.times[j];
                    if (time.day == i) {
                        // TODO: order the times
                        // sample: M: 8:00 am - 1:00 pm; W 8:00 am - 1:00 pm;
                        result += '; ' + dayLetters[time.day] + ': ' + myTimeToTimeStr(time.startTime) + ' - ' + myTimeToTimeStr(time.endTime);
                        // don't break since there may be multiple times for the same day
                    }
                }
            }
            result = result.substring(2); // remove leading semicolon
            return result;
/* // get the days
      var days = '';
      for (var i = 0; i &lt; daysInWeek; i++) {
        for (var j = 0; j &lt; this.times.length; j++) {
          var time = this.times[j];
          if (time.day == i) {
            days += dayLetters[time.day];
            break;
          }
        }
      }
      
      // get the times
      var times = '';
      var timesAdded = [];
      for (var i = 0; i &lt; this.times.length; i++) {
        var time = this.times[i];
        if (!timesAdded[time.startTime]) {
          timesAdded[time.startTime] = true;
          times += ', ' + myTimeToTimeStr(time.startTime);
        }
      }
      times = times.substring(2); // remove leading comma*/

            return days; // + ' ' + times;
        }
    };
}

function createTime(day, startTime, endTime) {
    return {
        day: day,
        startTime: startTime, // stored as integer in format 1730 (which is '5:30')
        endTime: endTime, // stored as integer in format 1730 (which is '5:30')
        conflictsWithTime: function(time, commuteTimeFromThisToSection, commuteTimeFromSectionToThis) {
            if (this.day != time.day) return false;

            if (incrementMyTime(this.endTime, commuteTimeFromThisToSection) &lt;= time.startTime) return false; // this occurs before time
            if (incrementMyTime(time.endTime, commuteTimeFromSectionToThis) &lt;= this.startTime) return false; // time occurs before this
            return true; // conflict in time
        }
    };
}

function createSchedule() {
    return {
        sections: [],
        minDay: null,
        maxDay: null,
        minHour: null,
        maxHour: null,
        days: [], // array of days of week, where if it exists, will have a 1 in its value
        classes: [], // unsorted array of class names
        finishedAddingSections: false,
        avgWaitTime: null,
        conflictsWithSection: function(candidateSection) {
            for (var i = 0; i &lt; this.sections.length; i++) {
                var section = this.sections[i];
                if (section.conflictsWithSection(candidateSection)) return true;
            }
            return false;
        },
        getCompatibleSections: function(sections) {
            var result = [];
            for (var i = 0; i &lt; sections.length; i++) {
                var section = sections[i];
                if (!this.conflictsWithSection(section)) result.push(section);
            }
            if (result.length == 0) result = null;
            return result;
        },
        getCompatibleClassSections: function(aClass) {
            return this.getCompatibleSections(aClass.sections);
        },
        fork: function(sectionToAdd) {
            var newSchedule = createSchedule();
            newSchedule.sections = this.sections.slice(0); // use slice to copy the array
            newSchedule.minDay = this.minDay;
            newSchedule.maxDay = this.maxDay;
            newSchedule.minHour = this.minHour;
            newSchedule.maxHour = this.maxHour;
            newSchedule.days = this.days.slice(0); // use slice to copy the array
            newSchedule.classes = this.classes.slice(0); // use slice to copy the array
            newSchedule.finishedAddingSections = this.finishedAddingSections;
            newSchedule.avgWaitTime = this.avgWaitTime;
            newSchedule.addSection(sectionToAdd);
            return newSchedule;
        },
        getForksByAddingSections: function(sectionsToAdd) {
            var compatibleSections = this.getCompatibleSections(sectionsToAdd);
            if (!compatibleSections) return null; // can't fork
            var forkedSchedules = [];
            for (var i = 0; i &lt; compatibleSections.length; i++) {
                var forkedSchedule = this.fork(compatibleSections[i]);
                forkedSchedules.push(forkedSchedule);
            }
            return forkedSchedules;
        },
        addSection: function(section) {
            // no need to check if it's valid, since that was alread done.
            this.sections.push(section);

            var noDataExists = this.minDay === null;
            for (var i = 0; i &lt; section.times.length; i++) {
                var time = section.times[i];
                if (noDataExists || time.day &lt; this.minDay) this.minDay = time.day;
                if (noDataExists || this.maxDay &lt; time.day) this.maxDay = time.day;

                function getHour(myTime) {
                    myTime = myTime + "";
                    if (myTime.length == 3) myTime = "0" + myTime;
                    return parseInt(myTime.substring(0, 2), 10);
                }

                if (noDataExists || getHour(time.startTime) &lt; this.minHour) this.minHour = getHour(time.startTime);
                if (noDataExists || this.maxHour &lt; getHour(time.endTime)) this.maxHour = getHour(time.endTime);

                noDataExists = false;
               
                // update stastics
                this.days[time.day] = 1;
            }
            
            // update stastics
            if (this.classes.indexOf(section.aClass.name) == -1) {
                this.classes.push(section.aClass.name);
            }
        },
        getDayCount: function() {
            var result = 0;
            for (var i = 0; i &lt; this.days.length; i++) {
                if (this.days[i] == 1) {
                    result++;
                }
            }
            return result;
        },
        getDisplayName: function() {
            // eg '5 classes, MTWTh, 3 hour average wait time'
            var result = '';
            result += this.classes.length + ' classes, ';

            var daysStr = '';
            for (var i = 0; i &lt; this.days.length; i++) {
                if (this.days[i] == 1) {
                    daysStr += dayLetters[i];
                }
            }
            result += daysStr + ', ';
            
            result += this.avgWaitTime + ' min average wait time';
            
            return result;
        },
        doneAddingSections: function() {
            // TODO: assert(!finishedAddingSections)
            finishedAddingSections = true;
            
            // sort the class names
            this.classes.sort();
            
            // calculate the wait times
            var times = [];
            for (var i = 0; i &lt; this.sections.length; i++) {
                var section = this.sections[i];
                for (var j = 0; j &lt; section.times.length; j++) {
                    times.push(section.times[j]);
                }
            }
            times.sort(function(a, b) {
                var result = 0;
                result = a.day - b.day;
                if (result == 0) {
                    result = a.startTime - b.startTime;
                }
                return result;
            });
            var maxWaitTime = 0; // in mins
            var allWaitTimes = 0; // in mins
            var numOfWaitTimes = 0;
            if (times.length &gt; 1) {
                for (var i = 1; i &lt; times.length; i++) {
                    var lastTime = times[i-1];
                    var curTime = times[i];
                    if (lastTime.day != curTime.day) continue; // diff day, so there is no wait time
                    var waitTime = myTimeToMinutes(curTime.startTime) - myTimeToMinutes(lastTime.endTime);
                    allWaitTimes += waitTime;
                    if (maxWaitTime &lt; waitTime) maxWaitTime = waitTime;
                    numOfWaitTimes++;
                }
            }
            var avgWaitTime = 0;
            if (numOfWaitTimes &gt; 0) avgWaitTime = allWaitTimes / numOfWaitTimes;
            this.avgWaitTime = avgWaitTime;
        }
    }
}

function parseTime(timeStr) {
    // parseTime('5:30 pm') = 1730
    var groups = timeStr.match(/(\d+):(\d+)\s*(?:am|(pm))?/i);
    var result = parseInt(groups[1], 10);
    if (groups[3] &amp;&amp; result != 12) // pm was matched, so add 12
    result += 12;

    return (result * 100) + parseInt(groups[2], 10);
}

function parseDays(days) {
    var validDays = ['Sun', 'M', 'T', 'W', 'Th', 'F', 'Sat'];
    var result = [];
    while (days != '') {
        var matchedIndex = -1;
        var matchedLen = 0;
        // when the string is 'Th', make sure to match Thursday (Th) and not Tuesday (T).
        for (var i = 0; i &lt; validDays.length; i++) {
            if (days.indexOf(validDays[i]) == 0 &amp;&amp; matchedLen &lt; validDays[i].length) {
                matchedLen = validDays[i].length;
                matchedIndex = i;
            }
        }
        if (matchedIndex == -1) return showError('Unable to parse day: ' + days);

        result.push(matchedIndex);
        days = days.substring(matchedLen);
    }
    return result;
}

function myTimeToMinutes(myTime) {
    // myTimeToMinutes(130) = 90 // since 130 represents '1:30'
    var myTime = myTime + "";
    if (myTime.length == 3) myTime = "0" + myTime;

    var hours = parseInt(myTime.substring(0, 2), 10);
    var minutes = parseInt(myTime.substring(2, 4), 10);
    return (hours * 60) + minutes;
}

function minsToMyTime(mins) {
    // minsToMyTime(90) = 130 // since 130 represents '1:30'
    var m = mins % 60;
    var h = Math.floor(mins / 60);

    return (h * 100) + m;
}

function incrementMyTime(myTime, minsToAdd) {
    if (minsToAdd == 0)
        return myTime;
    return minsToMyTime(myTimeToMinutes(myTime) + minsToAdd);
}

function myTimeToTimeStr(myTime) {
    var myTime = myTime + "";
    if (myTime.length == 3) myTime = "0" + myTime;

    var ampm = "am";
    var hours = parseInt(myTime.substring(0, 2), 10); // remove leading 0
    if (hours == 12) ampm = "pm";
    if (12 &lt; hours) {
        hours -= 12;
        ampm = "pm";
    }
    var minutes = myTime.substring(2, 4);
    return hours + ':' + minutes + ' ' + ampm;
}

function minsToTimeStr(mins) {
    // TODO: can be improved
    return myTimeToTimeStr(minsToMyTime(mins));
}

function showError(msg) {
    window.alert(msg);
}

function hourToShortTime(hour) {
    var ampm = 'am';
    if (hour == 0) {
        hour = 12;
    } else if (hour == 12) {
        ampm = 'pm';
    } else if (12 &lt; hour) {
        ampm = 'pm';
        hour -= 12;
    }
    return hour + ':00' + ampm;
}

function hideStep(stepNum, showNotHide) {
    showNotHide = showNotHide || false; // hide by default
    hide = !showNotHide;
    show = !hide;

    if (stepNum == 3 &amp;&amp; hide) hideStep(4, false);

    if (stepNum == 2 &amp;&amp; hide) // if hiding step 2, make sure to hide step 3 as well
    hideStep(3, false);

    if (show) {
        $('#step' + stepNum).hide();
        $('#step' + stepNum).fadeIn();
    } else $('#step' + stepNum).hide();
}

$(function() {
    hideStep(2);

    var classes = [];
    var schedules = [];
    var colorPallete = ['rgb(191,223,225)', 'rgb(255,191,191)', 'rgb(223,255,191)', 'rgb(255,223,191)', 'rgb(255,255,191)', 'rgb(223,191,255)', 'rgb(255,191,255)'];

    // document is ready
    $('#myButton').click(function() {
        // user entered in raw data, now parse the classes and sections
        classes = [];
        commuteTimesBetweenLocations = [];
        noLocationsEntered = true;

        function getClass(className) {
            for (var i = 0; i &lt; classes.length; i++) {
                if (classes[i].name == className) return classes[i];
            }
            var result = createClass(className);
            result.color = colorPallete[classes.length % (colorPallete.length - 1)];
            classes.push(result);
            return result;
        }
        var sectionLines = $('#input-sections').val().split(/[\r\n]+/g);

        // create the data structures to hold the data
        for (var i = 0; i &lt; sectionLines.length; i++) {
            var sectionLine = sectionLines[i];
            var sectionLineData = sectionLine.split(/\t/);
            if (sectionLineData.length == 1 &amp;&amp; sectionLine[1] == '&gt;') {
                // this is a commute time line
                var rgx = /^([A-Z])&gt;([A-Z])=(\d+)$/;
                var match = rgx.exec(sectionLineData);
                if (match == null) {
                    alert('Invalid data, commute lines must be in the format A&gt;B=60. Bad row:\n\n' + sectionLineData);
                    return;
                }
                var fromLocation = match[1];
                var toLocation = match[2];
                var mins = parseInt(match[3], 10);
                commuteTimesBetweenLocations.push(createCommuteTimeBetweenLocations(fromLocation, toLocation, mins));
                continue;
            }
            if (sectionLineData.length &lt; 3) {
                alert('Invalid data, make sure that there are at least 4 fields of data. Bad row:\n\n' + sectionLines[i]);
                return false;
            }
            var curClass = sectionLineData[0];
            var curSection = sectionLineData[1];
            var curDays = sectionLineData[2];
            var curTimes = sectionLineData[3];
            var curLocation = null; 
            if (sectionLineData.length &gt; 4) {
                curLocation = sectionLineData[4];
                noLocationsEntered = false;
            }
            //var curSeats = sectionLineData[5];
            var times = curTimes.split(/\s*-\s*/);
            var startTime = parseTime(times[0]);
            var endTime = parseTime(times[1]);
            var days = parseDays(curDays);

            var aClass = getClass(curClass);
            for (var j = 0; j &lt; days.length; j++) {
                aClass.addSectionAndTime(curSection, days[j], startTime, endTime);
            }
            if (curLocation != null &amp;&amp; !aClass.hasLocation())
                aClass.location = curLocation;
        }

        // clear the table
        var $classOutputTable = $('#output-classes');
        $classOutputTable.find('tr:not(:first-child)').remove();

        // create the visual components for those data structures
        for (var i = 0; i &lt; classes.length; i++) {
            var aClass = classes[i];
            var $row = $('&lt;tr&gt;').css('backgroundColor', aClass.color);

            // class column
            $row.append($('&lt;td&gt;').text(aClass.name));

            // time column
            var $select = $('&lt;select&gt;').append($('&lt;option&gt;').text('&lt;auto select&gt;').val(AUTO_SELECT_SECTION_VAL));
            for (var j = 0; j &lt; aClass.sections.length; j++) {
                var section = aClass.sections[j];
                $select.append($('&lt;option&gt;').text(section.name /* + ' (' +  + ')'*/ ).val(j));
            }
            $row.append($('&lt;td&gt;').append($select));
            $select.change(function() {
                hideStep(3);
            });
            aClass.$select = $select;

            // option column
            var $optionCombobox = $('&lt;select&gt;');
            $optionCombobox.append($('&lt;option&gt;').text('Required').val(optionRequiredVal));
            $optionCombobox.append($('&lt;option&gt;').text('Optional').val(optionOptionalVal));
            for (var j = 0; j &lt; optionGroups.length; j++) {
                $optionCombobox.append($('&lt;option&gt;').text(optionGroups[j]).val(j));
            }
            $row.append($('&lt;td&gt;').css('textAlign', 'center').append($optionCombobox));
            $optionCombobox.change(function() {
                hideStep(3);
            });
            aClass.$optionCombobox = $optionCombobox;

            $classOutputTable.append($row);
        }
        hideStep(2, true);
        hideStep(3);
    });

    $('#showSchedules').click(function() {
        // classes chosen, now display valid schedules
        var requiredSections = [];
        var requiredClasses = [];
        var optionalSections = [];
        var optionalClasses = [];
        var groupedSections = [];
        for (var i = 0; i &lt; classes.length; i++) {
            var aClass = classes[i];
            if (aClass.isOptional()) {
                if (aClass.hasPreferredSection()) {
                    optionalSections.push(aClass.getPreferredSection());
                } else {
                    optionalClasses.push(aClass);
                }
            } else if (aClass.isRequired()) {
                if (aClass.hasPreferredSection()) {
                    requiredSections.push(aClass.getPreferredSection());
                } else {
                    requiredClasses.push(aClass);
                }
            } else {
                // belongs to a group
                if (!groupedSections[aClass.getGroup()]) {
                   groupedSections[aClass.getGroup()] = [];
                }

                if (aClass.hasPreferredSection()) {
                    groupedSections[aClass.getGroup()].push(aClass.getPreferredSection());
                } else {
                    var sections = aClass.sections;
                    for (var j = 0; j &lt; sections.length; j++) {
                        groupedSections[aClass.getGroup()].push(sections[j]);
                    }
                }
            }
        }

        var schedule = createSchedule();
        schedules = [schedule];

        function forkSchedules(sectionsToAdd, optional) {
            optional = optional || false;

            var forkedSchedules = [];
            for (var i = 0; i &lt; schedules.length; i++) {
                var scheduleToFork = schedules[i];

                var compatibleSections = scheduleToFork.getCompatibleSections(sectionsToAdd);
                if (compatibleSections) {
                    for (var j = 0; j &lt; compatibleSections.length; j++) {
                        forkedSchedules.push(scheduleToFork.fork(compatibleSections[j]));
                    }
                } else {
                    if (optional) forkedSchedules.push(scheduleToFork); // the unmodified schedule is valid, since this section isn't required
                    else continue; // can't fork
                }
            }
            if (!optional &amp;&amp; forkedSchedules.length == 0) {
                showError('Unable to add one of the required sections: ' + sectionsToAdd);
                return false;
            }
            schedules = forkedSchedules;
            return true;
        }
        
        // add the groupedSections
        for (var i = 0; i &lt; optionGroups.length; i++) {
            var sections = groupedSections[i];
            if (!sections || sections.length == 0) {
                continue;
            }
            if (!forkSchedules(sections, false)) return false;
        }

        // add the requiredSections
        for (var i = 0; i &lt; requiredSections.length; i++) {
            var requiredSection = requiredSections[i];
            if (!forkSchedules([requiredSection], false)) return false;
/*if (schedule.conflictsWithSection(requiredSection)) {
        return showError('Two required sections conflict with eachother');
      }
      schedule.addSection(requiredSection);*/
        }

        // add the requiredClasses
        for (var i = 0; i &lt; requiredClasses.length; i++) {
            var requiredClass = requiredClasses[i];
            if (!forkSchedules(requiredClass.sections, false)) return false;
/*var schedulesToFork = schedules;
      var forkedSchedules = [];
      for (var j = 0, scheduleToFork; scheduleToFork = schedulesToFork[j]; j++) {
        // this class must be added to each schedule at least once
        var compatibleSections = scheduleToFork.getCompatibleClassSections(requiredClass);
        if (!compatibleSections) {
          // There are no instances of this class that would work with this section. For that reason, this section must be deleted.
          continue;
        } else {
          for (var k = 0; k &lt; compatibleSections.length; k++) {
            forkedSchedules.push(scheduleToFork.fork(compatibleSections[k]));
          }
        }
      }
      if (forkedSchedules.length == 0) {
        return showError('Unable to add class "' + requiredClass.name + '". There are no schedules that are compatible with it.');
      }
      schedules = forkedSchedules;*/
        }

        // add the optionalSections
        for (var i = 0; i &lt; optionalSections.length; i++) {
            if (!forkSchedules([optionalSections[i]], true)) return false;
        }

        // add the optionalClasses
        for (var i = 0; i &lt; optionalClasses.length; i++) {
            if (!forkSchedules(optionalClasses[i].sections, true)) return false;
        }

        // finalize each schedule
        for (var i = 0; i &lt; schedules.length; i++) {
            schedules[i].doneAddingSections();
        }
        
        // TODO1: sort schedules
        schedules.sort(function(a, b) {
            var result = 0;
            result = b.classes.length - a.classes.length;
            
            if (result == 0)
                result = a.getDayCount() - b.getDayCount();
            
            if (result == 0)
                result = a.avgWaitTime - b.avgWaitTime;
            
            return result;
        });

        // TODO: Analyze each schedule and show them in order of most favorable
        // output each schedule
        $outputSchedulesSelect = $('#outputSchedules');
        $outputSchedulesSelect.children().remove();
        for (var i = 0; i &lt; schedules.length; i++) {
            var schedule = schedules[i];
            $outputSchedulesSelect.append($('&lt;option&gt;').text((i + 1) + ': ' + schedule.getDisplayName()).val(i));
        }

        hideStep(3, true);
        hideStep(4);
    });

    $('#outputSchedules').change(function() {
        // a schedule was chosen, present it in the grid
        var value = $('#outputSchedules').val();
        if (value == -1) {
            hideStep(4);
            return;
        }
        hideStep(4, true); // show here since it requires the width
        var schedule = schedules[value];

        // create table times
        var startTime = schedule.minHour;
        var endTime = schedule.maxHour;
        var startDay = schedule.minDay;
        var endDay = schedule.maxDay;
        var $outputSchedule = $('#outputSchedule');
        var $outputScheduleSections = $('#outputScheduleSections');

        // clear old data
        $outputSchedule.children().remove();
        $outputScheduleSections.find('tr:not(:first-child)').remove();

        // create the header row
        var $trHeader = $('&lt;tr&gt;');
        var widthPercentage = (100 / (endDay - startDay + 1)) + "%";
        $trHeader.append($('&lt;th&gt;')); // time column
        for (var j = startDay; j &lt;= endDay; j++) {
            $trHeader.append($('&lt;th&gt;').text(dayNames[j]).css('width', widthPercentage));
        }
        $outputSchedule.append($trHeader);

        // create the next row which houses the spanned columns (and the 1st hr)
        var $tr = $('&lt;tr&gt;');
        var $firstCell = null;
        var rowspan = (endTime - startTime) + 1;
        $tr.append($('&lt;td&gt;').text(hourToShortTime(startTime)));
        for (var j = startDay; j &lt;= endDay; j++) {
            var $td = $('&lt;td rowspan="' + rowspan + '"&gt;');
            $td.append($('&lt;div id="day' + j + '"&gt;').css('position', 'relative').css('width', '100%'));
            if (!$firstCell) $firstCell = $td;
            $tr.append($td);
        }
        $outputSchedule.append($tr);

        // create rows for the rest of the hours
        for (var i = startTime + 1; i &lt;= endTime; i++) {
            var $tr = $('&lt;tr&gt;');
            $tr.append($('&lt;td&gt;').text(hourToShortTime(i)));
            $outputSchedule.append($tr);
        }

        // only after all rows have been created can you go and set the height property of each div element;
        var cellHeight = $firstCell.height();
        $outputSchedule.find('div').css('height', cellHeight - 2 + 'px'); // TODO: 100% doesn't work -- $td.height()-2 + '
        var startMinute = startTime * 60;
        var minutesRepresented = ((endTime - startTime) + 1) * 60;
        var pixelsPerMinute = cellHeight / minutesRepresented;

        // sort the times in the schedule so that commute can be calculated
        var sortedTimesPerDay = []; // sortedTimeDays[day] = [{section, time}, ...]
        for (var i = 0; i &lt; daysInWeek; i++) {
            sortedTimesPerDay.push([]);
        }
        for (var i = 0; i &lt; schedule.sections.length; i++) {
            var section = schedule.sections[i];
            for (var j = 0; j &lt; section.times.length; j++) {
                var time = section.times[j];
                sortedTimesPerDay[time.day].push({section: section, time: time});
            }
        }
        for (var i = startDay; i &lt;= endDay; i++) {
            var sortedTimes = sortedTimesPerDay[i];
            sortedTimes.sort(function(a, b) {
                return a.time.startTime - b.time.startTime;
            });
            
            // now that they are sorted, we can create the div elements which show the times inside the weekly table
            var $day = $('#day' + i);
            var lastLocation = null;
            for (var j = 0; j &lt; sortedTimes.length; j++) {
                var time = sortedTimes[j].time;
                var section = sortedTimes[j].section;
                var location = section.aClass.location;
                if (!noLocationsEntered) {
                    if (location != lastLocation &amp;&amp; j != 0) {
                        // new location, so output commute
                        var commuteTime = getCommuteTimeBetweenLocations(lastLocation, location);
                        var startTime = myTimeToMinutes(time.startTime) - commuteTime;
                        var endTime = startTime + commuteTime;
                        var $div = $('&lt;div&gt;');
                        $div.css('backgroundColor', commuteColor);
                        $div.css('position', 'absolute');
                        $div.css('top', (startTime - startMinute) * pixelsPerMinute + 'px');
                        $div.css('height', (endTime - startTime) * pixelsPerMinute + 'px');
                        $div.css('width', '100%');
                        $div.attr('title', 'Commute time: ' + minsToTimeStr(startTime) + ' - ' + minsToTimeStr(endTime) + ' (' + (endTime-startTime) + ' mins)');
                        $day.append($div);                        
                    }
                }
                
                var $div = $('&lt;div&gt;');
                $div.css('backgroundColor', section.aClass.color);
                $div.css('position', 'absolute');
                $div.css('top', (myTimeToMinutes(time.startTime) - startMinute) * pixelsPerMinute + 'px');
                $div.css('height', (myTimeToMinutes(time.endTime) - myTimeToMinutes(time.startTime)) * pixelsPerMinute + 'px');
                $div.css('width', '100%');
                $div.attr('title', section.aClass.name + ' - ' + section.name + ': ' + myTimeToTimeStr(time.startTime) + ' - ' + myTimeToTimeStr(time.endTime));
                $day.append($div);
                
                lastLocation = location;
            }
        }
        
        // output data in text schedule list
        for (var i = 0; i &lt; schedule.sections.length; i++) {
            var section = schedule.sections[i];
            var $tr = $('&lt;tr&gt;').css('backgroundColor', section.aClass.color);;
            $tr.append($('&lt;td&gt;').text(section.aClass.name));
            $tr.append($('&lt;td&gt;').text(section.name));
            $tr.append($('&lt;td&gt;').text(section.getTimesAsStr()));
            $outputScheduleSections.append($tr);
        }
    });

    // enable tab character
    $('#input-sections').keydown(function(e) {
        // http://stackoverflow.com/questions/1738808/keypress-in-jquery-press-tab-inside-textarea-when-editing-an-existing-text/1738888#1738888
        if (e.keyCode == 9) {
            var myValue = "\t";
            var startPos = this.selectionStart;
            var endPos = this.selectionEnd;
            var scrollTop = this.scrollTop;
            this.value = this.value.substring(0, startPos) + myValue + this.value.substring(endPos, this.value.length);
            this.focus();
            this.selectionStart = startPos + myValue.length;
            this.selectionEnd = startPos + myValue.length;
            this.scrollTop = scrollTop;

            e.preventDefault();
        }
    });
});&lt;/script&gt;
&lt;br /&gt;
&lt;h4&gt;
Step 1: Enter in your classes&lt;/h4&gt;
&lt;textarea class="inp large-text" id="input-sections"&gt;ENGL101&amp;#9;5412&amp;#9;MW&amp;#9;10:00 am - 12:30 pm&amp;#9;A
ENGL101&amp;#9;5413&amp;#9;TTh&amp;#9;10:30 am - 12:30 pm
ENGL101&amp;#9;5414&amp;#9;MW&amp;#9;6:00 pm - 8:00 pm
MATH101&amp;#9;6125&amp;#9;MW&amp;#9;7:30 am - 9:00 am&amp;#9;B
MATH101&amp;#9;6126&amp;#9;T&amp;#9;7:30 am - 9:00 am
MATH101&amp;#9;6126&amp;#9;W&amp;#9;8:30 am - 10:00 am
HIST101&amp;#9;7546&amp;#9;TTh&amp;#9;12:00 pm - 1:30 pm&amp;#9;A
HIST101&amp;#9;7546&amp;#9;TTh&amp;#9;2:00 pm - 2:30 pm
A&gt;B=30
B&gt;A=30&lt;/textarea&gt;&lt;br /&gt;
&lt;input id="myButton" type="button" value="Parse classes!" /&gt;

&lt;br /&gt;
&lt;div class="instructions"&gt;
Instructions: Enter in a tab delimited set of data consisting of: class name, instance id, days, times. For classes that have complicated day/times, enter multiple lines for the class using the same ID (see examples 6126 and 7546). Tip: You can use a spreadsheet application such as Excel to format your data into this format, then copy and paste into the above text area. Note: Days must be one of the following: Sun, M, T, W, Th, F, Sat, and times must be in the range of 1-12. -- You can also add a letter at the end which specifies the location of the class. Furthermore, you can then enter in the amount of time it takes to commute from one area to another in the format "A&gt;B=60", meaning that it takes 1 hour to go from A to B.
&lt;/div&gt;
&lt;div id="step2"&gt;
&lt;h4&gt;
Step 2: Select your classes&lt;/h4&gt;
&lt;table class="full-len" id="output-classes"&gt;
  &lt;tbody&gt;
&lt;tr&gt;
    &lt;th&gt;Class&lt;/th&gt;
    &lt;th&gt;Time&lt;/th&gt;
    &lt;th&gt;Options&lt;/th&gt;
  &lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;input id="showSchedules" type="button" value="Show schedules!" /&gt;

&lt;br /&gt;
&lt;div class="instructions"&gt;
Instructions: Choose which class instance you want to use. Most of the time you should leave all of them as "Auto select". Choose "optional" for those classes which aren't mandatory and should only be added to the schedule if there is an available space for them. Choose "required" if the class is mandatory. And choose a letter if one or more classes can be used to satisfy the same requirement (e.g. if MATH101 and MATH115 are interchangeable, you can set them both to "A"). 
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="step3"&gt;
&lt;h4&gt;
Step 3: Choose your schedule&lt;/h4&gt;
&lt;select class="full-len" id="outputSchedules" multiple="multiple"&gt;
&lt;/select&gt;
&lt;/div&gt;
&lt;div id="step4"&gt;
&lt;table class="full-len" id="outputSchedule"&gt;
&lt;/table&gt;
&lt;div class="instructions"&gt;
Tip: Place your mouse over one of the colored blocks in the week view to see the name of the class and its exact times.
&lt;/div&gt;
&lt;table class="full-len" id="outputScheduleSections"&gt;
  &lt;tbody&gt;
&lt;tr&gt;
    &lt;th&gt;Class&lt;/th&gt;
    &lt;th&gt;ID&lt;/th&gt;
    &lt;th&gt;Times&lt;/th&gt;
   &lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-7102028748210286791?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/7102028748210286791/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/12/schedule-creator.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/7102028748210286791'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/7102028748210286791'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/12/schedule-creator.html' title='Schedule Creator'/><author><name>Senseful</name><uri>http://www.blogger.com/profile/17245419786727860080</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-1998855792177744638</id><published>2010-12-03T01:09:00.001-08:00</published><updated>2010-12-03T01:09:03.935-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:iPhone'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:#How To'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:iPad'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:Gmail'/><title type='text'>How to set up a single Gmail inbox on iOS with the ability to send from multiple addresses</title><content type='html'>&lt;p&gt;One of the nice features in Gmail is the ability to only need a single account which gets mail via forwarding or by reading other accounts via POP3. Used in conjunction with Gmail's &amp;quot;send as&amp;quot; capability, you will never have to log in to more than one email account again.&lt;/p&gt;  &lt;p&gt;Setting this up to work in Gmail's online interface is easy. Getting the same functionality on your iOS device (iPhone, iPad, or iPod Touch), on the other hand, is a bit trickier to set up…&lt;/p&gt; &lt;a name='more'&gt;&lt;/a&gt;  &lt;p&gt;First, let's explain the email account setup…&lt;/p&gt;  &lt;p&gt;There's only one main email account which receives all of your email (e.g. main@example.com). This is the only email account you need to log in to to check for mail. The other accounts you used should forward their mail to the main account (e.g. work@example.com, blog@example.com, etc. all forward to main@example.com). You could also use other tricks such as plus addressing and nicknames (a Google Apps feature) to get email to the main account.&lt;/p&gt;  &lt;p&gt;Here are the requirements when sending mail from one of your other accounts (i.e. not the main account):&lt;/p&gt;  &lt;ol&gt;   &lt;li id="req-1"&gt;When sending mail from a non-main account, my main account's address should never be revealed to the recipient. &lt;/li&gt;    &lt;li id="req-2"&gt;A copy of the sent mail should be stored in the main account. &lt;/li&gt; &lt;/ol&gt;  &lt;table border="0" cellspacing="0" cellpadding="2" width="441"&gt;&lt;tbody&gt;     &lt;tr&gt;       &lt;td valign="top" width="98"&gt;&amp;#160;&lt;/td&gt;        &lt;td valign="top" width="96"&gt;Type&lt;/td&gt;        &lt;td valign="top" width="103"&gt;Credentials&lt;/td&gt;        &lt;td valign="top" width="153"&gt;Notes&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="98"&gt;&lt;a href="#sec-mainacc"&gt;Main Account&lt;/a&gt; (main@example.com)&lt;/td&gt;        &lt;td valign="top" width="96"&gt;Exchange&lt;/td&gt;        &lt;td valign="top" width="103"&gt;Self&lt;/td&gt;        &lt;td valign="top" width="153"&gt;&amp;#160;&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="98"&gt;&lt;a href="#sec-plusaddr"&gt;Plus Address&lt;/a&gt; of Main Account           &lt;br /&gt;(main+foo@example.com)&lt;/td&gt;        &lt;td valign="top" width="96"&gt;IMAP&lt;/td&gt;        &lt;td valign="top" width="103"&gt;Main Account&lt;/td&gt;        &lt;td valign="top" width="153"&gt;&amp;#160;&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="98"&gt;&lt;a href="#sec-plusaddr"&gt;Nickname&lt;/a&gt; of Main Account           &lt;br /&gt;(mn@example.com)&lt;/td&gt;        &lt;td valign="top" width="96"&gt;IMAP&lt;/td&gt;        &lt;td valign="top" width="103"&gt;Main Account&lt;/td&gt;        &lt;td valign="top" width="153"&gt;&amp;#160;&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="98"&gt;&lt;a href="#sec-fwdacc"&gt;Forwarding Account&lt;/a&gt;           &lt;br /&gt;(work@example.com)&lt;/td&gt;        &lt;td valign="top" width="96"&gt;Exchange&lt;/td&gt;        &lt;td valign="top" width="103"&gt;Self&lt;/td&gt;        &lt;td valign="top" width="153"&gt;Requires &lt;a href="#def-pop3reading"&gt;POP3 reading&lt;/a&gt; to meet &lt;a href="#req-2"&gt;requirement #2&lt;/a&gt;.&lt;/td&gt;     &lt;/tr&gt;   &lt;/tbody&gt;&lt;/table&gt;  &lt;p&gt;Some definitions:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;strong id="def-pop3reading"&gt;POP3 reading:&lt;/strong&gt; setting up the main account to read mail from the forwarding account via POP3 in Gmail's web interface. &lt;/li&gt; &lt;/ul&gt;  &lt;h4 id="sec-mainacc"&gt;Setting up the Main Account:&lt;/h4&gt;  &lt;p&gt;You should set up your account as an Exchange account for the best experience on on your iOS device. This is pretty simple if you follow Google's article: &lt;a href="http://www.google.com/support/mobile/bin/answer.py?answer=138740&amp;amp;topic=14252"&gt;Google Sync: Set Up Your Apple Device for Google Sync&lt;/a&gt;.&lt;/p&gt;  &lt;h4 id="sec-plusaddr"&gt;Setting up a Plus Address or Nickname Account:&lt;/h4&gt;  &lt;p&gt;The only tricky part with setting up one of these to work properly is that you must remember to add the account in Gmail's interface as a &amp;quot;send as&amp;quot; address. Even if you will only be sending mail on your iOS device, you still need to add the email in Gmail's &amp;quot;send as&amp;quot; settings.&lt;/p&gt;  &lt;p&gt;Once you have done that, you can add the plus address or nickname's email address as an IMAP account on your iOS device (a POP3 account will work as well). When it asks for your credentials, you should use the credentials of your main account. I have seen other guides recommend to use junk data for the IMAP or POP3 server, that's fine, but when you press the refresh button on the main screen of the mail app, you will get a bunch of error messages.&lt;/p&gt;  &lt;p&gt;Setting up these type of accounts will hide your information automatically and doesn't require the workaround that a forwarding account requires.&lt;/p&gt;  &lt;h4 id="sec-fwdacc"&gt;Setting up a Forwarding Account:&lt;/h4&gt;  &lt;p&gt;The first step is to set up the forwarding from the forwarding address to the main account. This will ensure that you get the message immediately as it is received rather than relying on POP3 reading, which could take up to an hour until the message is received in the main account.&lt;/p&gt;  &lt;p&gt;Next, you need to set up the main account to read the forwarding account via POP3. This will ensure that there is a copy of mail that was sent on the forwarding account on the main account. Don't worry about duplicates being copied over, since Gmail is very aggressive in eliminating duplicates.&lt;/p&gt;  &lt;p&gt;Now you need to set up the account on your iOS device &lt;a href="http://www.google.com/support/mobile/bin/answer.py?answer=138740&amp;amp;topic=14252"&gt;as an exchange account&lt;/a&gt;. The only thing you should do at the end is to select to only sync Mail (not Contacts or Calendars).&lt;/p&gt;  &lt;p&gt;Here are some more modifications I would recommend:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Go into Settings &amp;gt; Mail, Contacts, Calendars. &lt;/li&gt;    &lt;li&gt;Press Fetch New Data. &lt;/li&gt;    &lt;li&gt;Go to Advanced. &lt;/li&gt;    &lt;li&gt;Tap the Exchange account that was just added. &lt;/li&gt;    &lt;li&gt;Change the schedule to Manual. &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;That's it. If you would like more information about this, keep reading.&lt;/p&gt;  &lt;p&gt;What doesn't work, and why:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;strong&gt;Using the forwarding address credentials (without POP3 reading):&lt;/strong&gt; Although you set up forwarding on the account, this will only forward incoming mail, not outgoing mail. Thus, you will not have a copy of your sent mail in the main account. This violates &lt;a href="#req-2"&gt;requirement #2&lt;/a&gt;. &lt;/li&gt;    &lt;li&gt;&lt;strong&gt;Setting up the account as POP3, IMAP, or Gmail on iOS (with POP3 reading)&lt;/strong&gt;: For some reason, Gmail will not allow you to download the message via POP3 from another account if it is set up as one of these. Even if you manually move the messages to your Inbox, they will not be read via POP3. The only thing that works is to set up the account as an Exchange account. This violates &lt;a href="#req-2"&gt;requirement #2&lt;/a&gt;. &lt;/li&gt;    &lt;li&gt;&lt;strong&gt;Using the main account's email address but using the SMTP credentials of the forwarding account&lt;/strong&gt;: What happens here is that although the from address is displayed correctly, the return-path header in the email will reveal your main account. Note: to get the same results, you will need to first approved the main account to be able to send as the forwarding account in Gmail, otherwise even the from address will show up as the main account. This violates &lt;a href="#req-1"&gt;requirement #1&lt;/a&gt;. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;What's going on:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;strong&gt;When sending a message via Gmail's interface:&lt;/strong&gt; The message is sent directly to the other recipient and a copy of the outgoing message is not stored in the forwarding account. &lt;/li&gt;    &lt;li&gt;&lt;strong&gt;When sending a message using another device (e.g. iPhone):&lt;/strong&gt; You're sending the message via the forwarding account's Exchange settings. This means that the main account will not get a copy. However, since you set up the main account to read the forwarding account via POP3, you will get a copy of the message in the main account in 1-60 minutes (depending on how often POP3 reading occurs on your account). &lt;/li&gt; &lt;/ul&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-1998855792177744638?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/1998855792177744638/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/12/how-to-set-up-single-gmail-inbox-on-ios.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/1998855792177744638'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/1998855792177744638'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/12/how-to-set-up-single-gmail-inbox-on-ios.html' title='How to set up a single Gmail inbox on iOS with the ability to send from multiple addresses'/><author><name>Senseful</name><uri>http://www.blogger.com/profile/17245419786727860080</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-17230828969803274</id><published>2010-11-18T16:01:00.001-08:00</published><updated>2010-11-18T21:47:37.125-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:iPhone'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:#Tip'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:Gmail'/><title type='text'>iPhone Tip: Use a Gmail Plus Address for Note Sync</title><content type='html'>&lt;p&gt;If you have your iPhone sync notes with a Gmail account, you may notice that it doesn’t delete old versions of notes. Instead, every time you save a note, it creates a new instance in your email account. This can easily fill up your email address with a bunch of junk messages which are hard to get rid of. My solution is to use a Gmail plus address for syncing notes, and then periodically manually running a filter which deletes all of your old notes.&lt;/p&gt; &lt;a name='more'&gt;&lt;/a&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;First, let’s go through what happens when you modify a note on your iPhone. For example, let's say that you are creating a note with the text &amp;quot;This is a sample message&amp;quot; and you press done after each word so that it saves the message 5 times. The first time you do this, you will end up with: &lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;code&gt;[Notes]&lt;/code&gt; This &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;(Note: The &lt;code&gt;[Notes]&lt;/code&gt; part in all of these examples represents the &lt;code&gt;[Notes]&lt;/code&gt; label that is automatically applied or removed from these messages. Also, if you are trying to reproduce this experiment, make sure you give the service enough time to sync between each save.)&lt;/p&gt;  &lt;p&gt;The second time, you will end up with:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;code&gt;[Notes]&lt;/code&gt; This is &lt;/li&gt;    &lt;li&gt;This &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;The third time:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;code&gt;[Notes]&lt;/code&gt; This is a &lt;/li&gt;    &lt;li&gt;This is &lt;/li&gt;    &lt;li&gt;This &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;The last time, you will end up with:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;code&gt;[Notes]&lt;/code&gt; This is a sample message &lt;/li&gt;    &lt;li&gt;This is a sample &lt;/li&gt;    &lt;li&gt;This is a &lt;/li&gt;    &lt;li&gt;This is &lt;/li&gt;    &lt;li&gt;This &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;This becomes a nightmare if you want to get rid of all these old notes. It’s a difficult task since there are no labels, or other unique characteristics, applied to these messages which can be used to select them from among the thousands of messages you already have in your account. The only unique characteristics which could be filtered on are that the message’s from address (the same one set up on the iPhone), and the to address (it’s sent to no one). The best query I can come up with is: &lt;code&gt;from:me -to:(com | net | org | edu) -label:notes&lt;/code&gt;, but even this query will match other messages besides your old notes(e.g. a message sent to user@example.co.uk will match incorrectly).&lt;/p&gt;  &lt;p&gt;My recommendation is to use a Gmail plus address to set up note syncing with your iPhone. This gives you an accurate way to find messages that are created via the iPhone Notes application. It’s actually fairly simple to set up:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;On your iPhone, go into the mail settings and add a new email account. &lt;/li&gt;    &lt;li&gt;For the type, choose &lt;strong&gt;Other&lt;/strong&gt;. &lt;/li&gt;    &lt;li&gt;Configure it &lt;a href="http://mail.google.com/support/bin/answer.py?hl=en&amp;amp;answer=78799"&gt;as a normal IMAP account&lt;/a&gt;, except use a plus address for the &lt;strong&gt;Address&lt;/strong&gt; field. For example, if your email address is &lt;code&gt;user@example.com&lt;/code&gt;, use something such as &lt;code&gt;user+iPhone.notes@example.com&lt;/code&gt; instead. (This is only required in the address field. You can use your normal email address for the IMAP and SMTP &lt;strong&gt;User Name&lt;/strong&gt; fields.) &lt;/li&gt;    &lt;li&gt;The last setting to change is at the end where it asks you what to sync (&lt;strong&gt;Mail &lt;/strong&gt;and/or &lt;strong&gt;Notes&lt;/strong&gt;). Since this is a dedicated email address just for notes, enable &lt;strong&gt;Notes &lt;/strong&gt;and disable &lt;strong&gt;Mail&lt;/strong&gt;. (Your phone should already be reading mail from this account via a separate iPhone mail account which &lt;a href="http://www.google.com/support/mobile/bin/answer.py?answer=138740&amp;amp;topic=14252"&gt;uses Microsoft Exchange&lt;/a&gt; as the type.) &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Okay, everything should now be set up properly on the iPhone, now you can go into Gmail (in your browser) and set up a filter:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Go to &lt;strong&gt;Settings &lt;/strong&gt;&amp;gt; &lt;strong&gt;Filters &lt;/strong&gt;&amp;gt; &lt;strong&gt;Create new filter&lt;/strong&gt;. &lt;/li&gt;    &lt;li&gt;In the &lt;strong&gt;From&lt;/strong&gt; field, enter in the plus address you chose (e.g. &lt;code&gt;user+iPhone.notes@example.com&lt;/code&gt;). &lt;/li&gt;    &lt;li&gt;In the &lt;strong&gt;Has the words&lt;/strong&gt; field, enter in &lt;code&gt;-label:Notes&lt;/code&gt;. &lt;/li&gt;    &lt;li&gt;Press &lt;strong&gt;Next Step&lt;/strong&gt;. You can safely ignore the message about the warning to not use &lt;code&gt;label:&lt;/code&gt; in a filter. &lt;/li&gt;    &lt;li&gt;Check the &lt;strong&gt;Delete it&lt;/strong&gt; box. &lt;/li&gt;    &lt;li&gt;Press &lt;strong&gt;Create Filter&lt;/strong&gt;. &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt; Make sure you typed &lt;code&gt;–label:Notes&lt;/code&gt; correctly! If you made a typo, it will delete all of the notes you actually want to save. Thankfully, though, they are sent to the trash and aren’t permanently deleted, so you can recover them if you accidentally did that.&lt;/p&gt;  &lt;p&gt;The purpose of creating the filter is not to have it delete the old messages automatically (there’s no way to do that in Gmail at the time). It’s simply to save the query and action needed to delete old messages. Whenever you want to delete old notes, you can go to this filter, edit it, then check the box that says &lt;strong&gt;Also apply filter to N conversations below&lt;/strong&gt; and save it again.&lt;/p&gt;  &lt;p&gt;While this solution it’s not automatic, it sure beats having to manually sift through the mail to delete the old notes.&lt;/p&gt;  &lt;p&gt;If you have been using Notes the old way and have accumulated a lot of old notes, you will need to use the first query from above, and then manually check it to make sure only old notes are deleted. Once you set up the plus address, any time you save your old notes, they will get a new From address, so you will no longer have to deal with manual deletion.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-17230828969803274?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/17230828969803274/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/11/iphone-tip-use-gmail-plus-address-for.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/17230828969803274'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/17230828969803274'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/11/iphone-tip-use-gmail-plus-address-for.html' title='iPhone Tip: Use a Gmail Plus Address for Note Sync'/><author><name>Senseful</name><uri>http://www.blogger.com/profile/17245419786727860080</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-5240639989396453948</id><published>2010-10-22T01:12:00.000-07:00</published><updated>2010-10-23T16:14:07.478-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:iPhone'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:#How To'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:Evernote'/><title type='text'>Import iPhone Notes to Evernote via Outlook</title><content type='html'>Here's a simple way to import your iPhone notes to Evernote. This solution requires Microsoft Outlook, so it will only work on the PC. Note this could just as easily be used for importing Outlook Notes into Evernote without an iPhone... just skip the first step! The main benefit of this method is that it will retain the creation date of all of your notes.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;ol&gt;
&lt;li&gt;Sync your iPhone notes with Outlook and make sure all your notes are in there.&lt;/li&gt;
&lt;li&gt;In Outlook, go to Tools &amp;gt; Macro &amp;gt; Visual Basic Editor (Alt+F11)&lt;br /&gt;You will see that Microsoft Visual Basic has opened.&lt;/li&gt;
&lt;li&gt;There should now be a blank document where you can type code. If you do not see this, expand the Project1 tree on the left (in the Project panel) until you get to ThisOutlookSession, and double click it.
&lt;li&gt;Paste the code at the bottom of this post after any existing code (you may or may not have code there), and save the document.&lt;/li&gt;
&lt;li&gt;Close Microsoft Visual Basic.&lt;/li&gt;
&lt;li&gt;Allow the macro to run by doing one of the following:&lt;/li&gt;
&lt;ul&gt;
&lt;li&gt;To temporarily allow all macros to run (easier), do the following:
&lt;ol&gt;
&lt;li&gt;Go to Tools &amp;gt; Macro &amp;gt; Security...&lt;/li&gt;
&lt;li&gt;Change the Security Level to Medium or Low.&lt;/li&gt;
&lt;li&gt;Don't forget to change it back to High after you are done with this!&lt;/li&gt;
&lt;/ol&gt;&lt;/li&gt;
&lt;li&gt;To create a certificate that will be used to sign your macro (safer), do the following:
&lt;ol&gt;
&lt;li&gt;Start &amp;gt; All Programs &amp;gt; Microsoft Office &amp;gt; Microsoft Office Tools &amp;gt; Digital Certificate for VBA Projects&lt;/li&gt;
&lt;li&gt;Follow the steps to create a certificate, and name it whatever you want.&lt;/li&gt;
&lt;li&gt;Open Microsoft Visual Basic again (Alt+F11) in Outlook.&lt;/li&gt;
&lt;li&gt;Tools &amp;gt; Digital Signature &amp;gt; Choose...&lt;/li&gt;
&lt;li&gt;Choose the certificate you just created.&lt;/li&gt;
&lt;li&gt;Press OK and close Microsoft Visual Basic.&lt;/li&gt;
&lt;li&gt;When prompted in the next steps if you want to run this macro, remember to allow it and to choose to trust this publisher.&lt;/li&gt;
&lt;/ol&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;li&gt;If you changed any security settings, you should restart Outlook. When prompted if you want to save changes, choose yes.&lt;/li&gt;
&lt;li&gt;Run the macro by doing the following:&lt;/li&gt;
&lt;ol&gt;
&lt;li&gt;Tools &amp;gt; Macro &amp;gt; Macros... (Alt+F8)&lt;/li&gt;
&lt;li&gt;Choose&amp;nbsp;&lt;b&gt;ThisOutlookSession.exportNotesToEvernote&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Press Run.&lt;/li&gt;
&lt;/ol&gt;
&lt;li&gt;You should now have a file in your My Documents folder called&amp;nbsp;OutlookNotes.enex.&lt;/li&gt;
&lt;li&gt;Open up the Evernote desktop client, and do the following:&lt;/li&gt;
&lt;ol&gt;
&lt;li&gt;Choose File &amp;gt; Import &amp;gt; Evernote Export Files...&lt;/li&gt;
&lt;li&gt;Choose the OutlookNotes.enex file located in My Documents.&lt;/li&gt;
&lt;/ol&gt;
&lt;li&gt;You should now have a new notebook with all of your notes.&lt;/li&gt;
&lt;li&gt;Tip: If you want to tag all of these new notes with the same tag, you can select this notebook, then select all the notes in it, and then drag them onto a tag.&lt;/li&gt;
&lt;/ol&gt;
&lt;h5&gt;Here's the code for Microsoft Visual Basic:&lt;/h5&gt;
&lt;pre class="vb" name="code"&gt;
Function pad(ByVal n, ByVal l) As String
  pad = Right(&amp;quot;000000000&amp;quot; &amp;amp; n, l)
End Function

Function enDateTime(ByVal d) As String
  ' Sample date: 20101021T205209Z
  enDateTime = Year(d) &amp;amp; pad(Month(d), 2) &amp;amp; pad(Day(d), 2) &amp;amp; &amp;quot;T&amp;quot; &amp;amp; pad(Hour(d), 2) &amp;amp; pad(Minute(d), 2) &amp;amp; pad(Second(d), 2) &amp;amp; &amp;quot;Z&amp;quot;
End Function

Function HTMLEncode(ByVal Text As String) As String
  ' Slightly modified version of http://www.devx.com/vb2themax/Tip/19162
    Dim i As Integer
    Dim acode As Integer
    Dim repl As String

    HTMLEncode = Text

    For i = Len(HTMLEncode) To 1 Step -1
        acode = AscW(Mid$(HTMLEncode, i, 1))
        Select Case acode
            Case 32
                ' repl = &amp;quot;&amp;amp;nbsp;&amp;quot;
            Case 34
                repl = &amp;quot;&amp;amp;quot;&amp;quot;
            Case 38
                repl = &amp;quot;&amp;amp;amp;&amp;quot;
            Case 60
                repl = &amp;quot;&amp;amp;lt;&amp;quot;
            Case 62
                repl = &amp;quot;&amp;amp;gt;&amp;quot;
            Case 32 To 127
                ' don't touch alphanumeric chars
            Case 13
              ' don't touch new lines
            Case 10
              ' don't touch new lines
            Case Else
                repl = &amp;quot;&amp;amp;#&amp;quot; &amp;amp; CStr(acode) &amp;amp; &amp;quot;;&amp;quot;
        End Select
        If Len(repl) Then
            HTMLEncode = Left$(HTMLEncode, i - 1) &amp;amp; repl &amp;amp; Mid$(HTMLEncode, _
                i + 1)
            repl = &amp;quot;&amp;quot;
        End If
    Next
End Function

Sub exportNotesToEvernote()
  ' http://www.sensefulsolutions.com/2010/10/import-iphone-notes-to-evernote-via.html
  FileName = Environ$(&amp;quot;USERPROFILE&amp;quot;) &amp;amp; &amp;quot;\My Documents\OutlookNotes.enex&amp;quot;
  File = FreeFile()
  Open FileName For Output As File
  
  Print #File, &amp;quot;&amp;lt;?xml version=&amp;quot;&amp;quot;1.0&amp;quot;&amp;quot; encoding=&amp;quot;&amp;quot;UTF-8&amp;quot;&amp;quot;?&amp;gt;&amp;quot; _
   &amp;amp; &amp;quot;&amp;lt;!DOCTYPE en-export SYSTEM &amp;quot;&amp;quot;http://xml.evernote.com/pub/evernote-export.dtd&amp;quot;&amp;quot;&amp;gt;&amp;quot; _
   &amp;amp; &amp;quot;&amp;lt;en-export export-date=&amp;quot;&amp;quot;&amp;quot; &amp;amp; enDateTime(Now) &amp;amp; &amp;quot;&amp;quot;&amp;quot; application=&amp;quot;&amp;quot;Evernote/Windows&amp;quot;&amp;quot; version=&amp;quot;&amp;quot;3.5&amp;quot;&amp;quot;&amp;gt;&amp;quot;
  
  Tag = &amp;quot;&amp;quot;
  ' Uncomment this line to set a tag for all notes and (replace the My Tag with the name of the tag you want)
  ' Tag = &amp;quot;&amp;lt;tag&amp;gt;&amp;quot; &amp;amp; xmlEscape(&amp;quot;iPhone Notes&amp;quot;) &amp;amp; &amp;quot;&amp;lt;/tag&amp;gt;&amp;quot;
  
  Set Notes = Application.GetNamespace(&amp;quot;MAPI&amp;quot;).GetDefaultFolder(olFolderNotes)
  For i = 1 To Notes.Items.Count
    Set note = Notes.Items(i)
    Subject = note.Subject
    Subject = Left(Subject, 64)
    Subject = HTMLEncode(Subject)
    Body = note.Body
    Body = HTMLEncode(Body)
    Body = Replace(Body, vbNewLine, &amp;quot;&amp;lt;&amp;quot; &amp;amp; &amp;quot;br/&amp;gt;&amp;quot; &amp;amp; vbLf)
    Print #File, &amp;quot;&amp;lt;note&amp;gt;&amp;lt;title&amp;gt;&amp;quot; &amp;amp; Subject &amp;amp; &amp;quot;&amp;lt;/title&amp;gt;&amp;lt;content&amp;gt;&amp;quot; _
      &amp;amp; &amp;quot;&amp;lt;![CDATA[&amp;lt;?xml version=&amp;quot;&amp;quot;1.0&amp;quot;&amp;quot; encoding=&amp;quot;&amp;quot;UTF-8&amp;quot;&amp;quot;?&amp;gt;&amp;quot; _
      &amp;amp; &amp;quot;&amp;lt;!DOCTYPE en-note SYSTEM &amp;quot;&amp;quot;http://xml.evernote.com/pub/enml2.dtd&amp;quot;&amp;quot;&amp;gt;&amp;quot; _
      &amp;amp; &amp;quot;&amp;lt;en-note&amp;gt;&amp;quot; &amp;amp; Body _
      &amp;amp; &amp;quot;&amp;lt;/en-note&amp;gt;]]&amp;gt;&amp;lt;/content&amp;gt;&amp;lt;created&amp;gt;&amp;quot; &amp;amp; enDateTime(note.CreationTime) &amp;amp; &amp;quot;&amp;lt;/created&amp;gt;&amp;lt;updated&amp;gt;&amp;quot; &amp;amp; enDateTime(note.CreationTime) &amp;amp; &amp;quot;&amp;lt;/updated&amp;gt;&amp;quot; &amp;amp; Tag &amp;amp; &amp;quot;&amp;lt;/note&amp;gt;&amp;quot;
    ' uses the note's creation time for both the modification and creation time in Evernote
  Next
  Print #File, &amp;quot;&amp;lt;/en-export&amp;gt;&amp;quot;
  Close #File
End Sub&lt;/pre&gt;
&lt;h5&gt;Understanding how the dates work:&lt;/h5&gt;
&lt;p&gt;
When you look at notes on the iPhone, you only see their modification date. Behind the scenes, Apple is also storing their creation date, though. When you import the notes to Outlook, it will set the creation date to the time that Apple recorded as the note being created, and it will set the modification date to the current time. Thus, the modification time in Outlook is useless, and there is no way (using Outlook) to get the same exact dates you see on the iPhone. For this reason, I chose to completely ignore Outlook's modification date and I use the creation date for both the created and modification dates in Evernote.
&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-5240639989396453948?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/5240639989396453948/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/10/import-iphone-notes-to-evernote-via.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/5240639989396453948'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/5240639989396453948'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/10/import-iphone-notes-to-evernote-via.html' title='Import iPhone Notes to Evernote via Outlook'/><author><name>Senseful</name><uri>http://www.blogger.com/profile/17245419786727860080</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-1011046882810051028</id><published>2010-10-15T15:38:00.000-07:00</published><updated>2012-01-14T02:07:45.405-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:#Utility'/><title type='text'>Format Text as a Table</title><content type='html'>Here's a simple tool that let's you format text as a table. Simply enter in tab-delimited text (e.g. by copying from Excel), then press "Create Table".

&lt;a name='more'&gt;&lt;/a&gt;

&lt;h4&gt;Input:&lt;/h4&gt;
&lt;textarea id="input" class="fixed-width large-text"&gt;
Col1&amp;#9;Col2&amp;#9;NumCol
Value 1&amp;#9;Value 2&amp;#9;123
This is a row with only one cell
This row is testing html entities&amp;#9;Te&lt;br /&gt;st&amp;#9;45
&lt;/textarea&gt;

&lt;table class="data-input"&gt;
    &lt;tr&gt;
        &lt;td&gt;
            Header style:&lt;/td&gt;
        &lt;td&gt;
&lt;select id="hdr-style"&gt;
    &lt;option value="none"&gt;None&lt;/option&gt;
    &lt;option value="top" selected="true"&gt;Top Row&lt;/option&gt;
    &lt;option value="ssheet"&gt;Spreadsheet&lt;/option&gt;
        &lt;/select&gt;&lt;/td&gt;
        &lt;td&gt;&lt;div class="info-icon" title="Spreadsheet: will add letters on top and numbers on the left side."&gt;&lt;/div&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt;
            &lt;input type="checkbox" id="auto-format" checked="true" /&gt; &lt;label for="auto-format"&gt;Auto Align&lt;/label&gt;&lt;/td&gt;
        &lt;td&gt;&lt;div class="info-icon" title="Number columns will be right-aligned and headers will be centered."&gt;&lt;/div&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;Style:&lt;/td&gt;
        &lt;td&gt;
&lt;select id="style"&gt;
    &lt;option value="0"&gt;MySQL&lt;/option&gt;
    &lt;option value="1"&gt;Unicode Art&lt;/option&gt;
    &lt;option value="html"&gt;HTML Table&lt;/option&gt;
            &lt;/select&gt;&lt;/td&gt;
        &lt;td&gt;&lt;div class="info-icon"&gt;&lt;div class="info-text"&gt;&lt;b&gt;MySQL&lt;/b&gt;: outputs using Ascii characters.&lt;br /&gt;&lt;b&gt;Unicode Art&lt;/b&gt;: uses unicode chars that makes it look more like a table.&lt;br /&gt;&lt;b&gt;HTML&lt;/b&gt;: outputs a normal html table.&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt;&lt;button onclick="createTable()"&gt;Create Table&lt;/button&gt;&lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
&lt;/table&gt;

&lt;h4&gt;Output:&lt;/h4&gt;
&lt;div id="outputText"&gt;
  &lt;textarea id="output" class="fixed-width large-text"&gt;&lt;/textarea&gt;
  &lt;button onclick="parseTableClick()"&gt;Parse Table&lt;/button&gt;&lt;div class="info-icon" title="Paste a text table into the 'output' textarea, and then press Parse Table. This will parse the data, tab delimit it, and place the result in the 'input' textarea."&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outputTbl"&gt;
&lt;/div&gt;
&lt;div id="version-history"&gt;
&lt;ul&gt;
    &lt;li&gt;1/13/12:
    &lt;ul&gt;&lt;li&gt;Parse table added.&lt;/li&gt;
        &lt;li&gt;Improved code and layout.&lt;/li&gt;
        &lt;/ul&gt;&lt;/li&gt;
    &lt;li&gt;1/1/11:
    &lt;ul&gt;&lt;li&gt;Auto Align: number columns become right-aligned, and headers are centered.&lt;/li&gt;
        &lt;li&gt;Spreadsheet option added: adds letters on top and numbers on the side.&lt;/li&gt;
        &lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;

&lt;!--********************* Custom CSS  *********************--&gt;
&lt;style type="text/css"&gt;
&lt;/style&gt;

&lt;!--****************** Custom JavaScript  *****************--&gt;
&lt;script type="text/javascript"&gt;
/* Format text as table */

$(function() {
    // allow tab key to be used in the text area
    $('#input').keydown(function(e) {
        // http://stackoverflow.com/questions/1738808/keypress-in-jquery-press-tab-inside-textarea-when-editing-an-existing-text/1738888#1738888
        if (e.keyCode == 9) {
            var myValue = "\t";
            var startPos = this.selectionStart;
            var endPos = this.selectionEnd;
            var scrollTop = this.scrollTop;
            this.value = this.value.substring(0, startPos) + myValue + this.value.substring(endPos, this.value.length);
            this.focus();
            this.selectionStart = startPos + myValue.length;
            this.selectionEnd = startPos + myValue.length;
            this.scrollTop = scrollTop;

            e.preventDefault();
        }
    });
});

function createTable() {
    // set up the style
    var cTL, cTM, cTR;
    var cML, cMM, cMR;
    var cBL, cBM, cBR;
    var cH, cV;

    var headerStyle = $('#hdr-style').val();
    var autoFormat = $('#auto-format').is(':checked');
    var hasHeaders = headerStyle == "top";
    var spreadSheetStyle = headerStyle == "ssheet";
    var input = $('#input').val();

    var rows = input.split(/[\r\n]+/);
    if (rows[rows.length - 1] == "") {
        // extraneous last row, so delete it
        rows.pop();
    }
    
    if (spreadSheetStyle) {
        hasHeaders = true;
        // add the row numbers
        for (var i = 0; i &lt; rows.length; i++) {
            rows[i] = (i+1) + "\t" + rows[i];
        }
    }    

    // calculate the max size of each column
    var colLengths = [];
    var isNumberCol = [];
    for (var i = 0; i &lt; rows.length; i++) {
        var cols = rows[i].split(/\t/);
        for (var j = 0; j &lt; cols.length; j++) {
            var data = cols[j];
            var isNewCol = colLengths[j] == undefined;
            if (isNewCol) {
                isNumberCol[j] = true;
            }
            // keep track of which columns are numbers only
            if (autoFormat) {
                if (hasHeaders &amp;&amp; i == 0 &amp;&amp; !spreadSheetStyle) {
                    ; // a header is allowed to not be a number (exclude spreadsheet because the header hasn't been added yet
                } else if (isNumberCol[j] &amp;&amp; !data.match(/^(\s*-?\d+\s*|\s*)$/)) {
                    isNumberCol[j] = false;
                }
            }
            if (isNewCol || colLengths[j] &lt; data.length) {
               colLengths[j] = data.length;
            }
        }
    }
    
    if (spreadSheetStyle) {    
        // now that we have the number of columns, add the letters
        var colCount = colLengths.length;
        var letterRow = " "; // initial column will have a space
        for (var i = 0; i &lt; colCount; i++) {
            var asciiVal = (65 + i);
            if (90 &lt; asciiVal) {
                asciiVal = 90; // Z is the max column
            }
            letterRow += "\t" + String.fromCharCode(asciiVal);
        }
        rows.splice(0, 0, letterRow); // add as first row
    }

    var style = $('#style').val();
    switch (style) {
    case "0":
        // mysql
        cTL = "+";
        cTM = "+";
        cTR = "+";
        cML = "+";
        cMM = "+";
        cMR = "+";
        cBL = "+";
        cBM = "+";
        cBR = "+";
        cH = "-";
        cV = "|";
        break;
    case "1":
        // unicode
        cTL = "\u2554";
        cTM = "\u2566";
        cTR = "\u2557";
        cML = "\u2560";
        cMM = "\u256C";
        cMR = "\u2563";
        cBL = "\u255A";
        cBM = "\u2569";
        cBR = "\u255D";
        cH = "\u2550";
        cV = "\u2551";
        break;
    case "html":
        outputAsNormalTable(rows, hasHeaders, colLengths);
        return;
    default:
        break;
    }

    // output the text
    var output = "";
    for (var i = 0; i &lt; rows.length; i++) {
        // output the top most row
        // Ex: +---+---+
        if (i == 0) {
            output += cTL;
            for (var j = 0; j &lt; colLengths.length; j++) {
                output += sfu.repeat(cH, colLengths[j] + 2);
                if (j &lt; colLengths.length - 1) {
                    output += cTM;
                }
                else output += cTR;
            }
            output += "\n";
        }

        // output the header separator row
        // Ex: +---+---+
        if (hasHeaders &amp;&amp; i == 1) {
            output += cML;
            for (var j = 0; j &lt; colLengths.length; j++) {
                output += sfu.repeat(cH, colLengths[j] + 2);
                if (j &lt; colLengths.length - 1) {
                    output += cMM;
                }
                else {
                    output += cMR;
                }
            }
            output += "\n";
        }

        // output the data
        output += cV;
        var cols = rows[i].split(/\t/);
        for (var j = 0; j &lt; colLengths.length; j++) {
            var data = cols[j] || "";
            var align = "l";
            if (autoFormat) {
                if (hasHeaders &amp;&amp; i == 0) {
                    align = "c";
                } else if (isNumberCol[j]) {
                    align = "r";
                }
            }
            data = sfu.pad(data, colLengths[j], " ", align);
            output += " " + data + " " + cV;
        }
        output += "\n";

        // output the bottom row
        // Ex: +---+---+
        if (i == rows.length - 1) {
            output += cBL;
            for (var j = 0; j &lt; colLengths.length; j++) {
                output += sfu.repeat(cH, colLengths[j] + 2);
                if (j &lt; colLengths.length - 1) output += cBM;
                else output += cBR;
            }
        }
    }

    $('#output').val(output);
    $('#outputText').show();
    $('#outputTbl').hide();
}

function outputAsNormalTable(rows, hasHeaders, colLengths) {
    var output = "";

    var $outputTable = $('&lt;table&gt;');
    for (var i = 0; i &lt; rows.length; i++) {
        var cols = rows[i].split(/\t/);
        var tag = (hasHeaders &amp;&amp; i == 0) ? "th" : "td";
        var $row = $('&lt;tr&gt;').appendTo($outputTable);
        for (var j = 0; j &lt; colLengths.length; j++) {
            var data = cols[j] || " ";
            var $cell = $('&lt;' + tag + '&gt;').text(data);
            $row.append($cell);
        }
    }
    var $outputDiv = $('#outputTbl');
    $outputDiv.empty();
    $outputDiv.append($outputTable);
    $('#outputText').hide();
    $('#outputTbl').show();
}

function parseTableClick() {
    var result = parseTable($('#output').val());
    $('#input').val(result);
}

function parseTable(table) {
    var lines = table.split('\n');
    
    // first find a seprator line
    var separatorLine = '';
    for (var i = 0; i &lt; lines.length; i++) {
        var line = lines[i];
        if (isSepratorLine(line)) {
            separatorLine = line;
            break;
        }
    }
    
    if (separatorLine == '') {
        alert('Error: make sure to include separator lines.');
        return;
    }
    
    // next, find all column indexes
    var colIndexes = [];
    var horizLineChar = separatorLine[1]; // 2nd char is always the repeating char
    for (var i = 0; i &lt; separatorLine.length; i++) {
        var char = separatorLine[i];
        if (char != horizLineChar) {
            colIndexes.push(i);
        }
    }
    
    // finally, loop over all items and extract the data
    var result = "";
    for (var i = 0; i &lt; lines.length; i++) {
        var line = lines[i];
        if (isSepratorLine(line)) {
            continue;
        }
        
        for (var j = 0; j &lt; colIndexes.length - 1; j++) {
            var fromCol = colIndexes[j] + 1;
            var toCol = colIndexes[j+1];
            var data = line.slice(fromCol, toCol);
            data = sfu.trim(data);
            result += data;
            
            if (j &lt; colIndexes.length - 2)
                result += '\t';
        }
                
        if (i &lt; lines.length - 1)
            result += '\n';
    }

    return result;
}

function isSepratorLine(line) {
    return line.indexOf(" ") == -1; // must not have spaces
}
&lt;/script&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-1011046882810051028?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/1011046882810051028/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/10/format-text-as-table.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/1011046882810051028'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/1011046882810051028'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/10/format-text-as-table.html' title='Format Text as a Table'/><author><name>Senseful</name><uri>http://www.blogger.com/profile/17245419786727860080</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-4607794162363286875</id><published>2010-10-02T16:03:00.000-07:00</published><updated>2010-10-02T16:06:51.484-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:#Tip'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:iPad'/><title type='text'>iBooks Tip: Reading a Book Out of Order</title><content type='html'>Sometimes you will be reading a book out of order (e.g. I am currently reading Apple's &lt;a href="http://developer.apple.com/library/mac/#documentation/cocoa/conceptual/CoreData/Articles/cdTroubleshooting.html"&gt;Core Data Programming Guide&lt;/a&gt;) and it becomes hard to keep track of which chapters you have read and which ones you haven't. With a little bit of work, you can use Apple's iBooks app to solve this problem.&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
This works best with a PDF that has a table of contents.&lt;br /&gt;
&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;Open the PDF in iBooks.&lt;/li&gt;
&lt;li&gt;Navigate to its table of contents.&lt;/li&gt;
&lt;li&gt;For each chapter, click it's title page to navigate there, then add a bookmark to that page.&lt;/li&gt;
&lt;li&gt;Once you are finished, switch to bookmark view, to see all the chapters bookmarked. This is your reading list.&lt;/li&gt;
&lt;li&gt;Once you are finished with a chapter, simply remove its bookmark.&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
This also works really well if the title is displayed in a large font on the page, since then you can easily see the title of each chapter when you are viewing all your bookmarks. This will help you decide which chapter you want to read next.&lt;br /&gt;
&lt;br /&gt;
If the PDF doesn't have a table of contents, you could still navigate to each chapter's page, and follow the same steps above. It will just require more initial work.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-4607794162363286875?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/4607794162363286875/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/10/ibooks-tip-reading-book-out-of-order.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/4607794162363286875'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/4607794162363286875'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/10/ibooks-tip-reading-book-out-of-order.html' title='iBooks Tip: Reading a Book Out of Order'/><author><name>Senseful</name><uri>http://www.blogger.com/profile/17245419786727860080</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-859095797073185192</id><published>2010-08-16T07:22:00.000-07:00</published><updated>2010-08-16T07:22:05.440-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:#How To'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:Gmail'/><title type='text'>Gmail: Robust tagging of forwarded email</title><content type='html'>&lt;p&gt;One of the nice features of reading mail via POP 3 in Gmail is how it can automatically tag the emails with any tag of your choice. For example, you could create a tag for each email account you read. Then, you could select one of these labels to view only that mailbox’s emails.&lt;/p&gt;

&lt;p&gt;The way it’s implemented in POP3 is very robust. Even if your name is not listed in any of the to fields (e.g. like in a BCC message), it will still tag the message correctly.&lt;/p&gt;

&lt;p&gt;One of the drawbacks of using POP3 is that it can sometimes take up to an hour before it reads your mailbox. One solution for this is to use forwarding instead of POP3 which will deliver the emails as they arrive. However, if you switch to forwarding, you won’t have the correct tagging capabilities of POP3. In this post, I will show you how you can combine the two methods and get the best of both worlds.&lt;/p&gt;
&lt;a name='more'&gt;&lt;/a&gt;

&lt;p&gt;Let’s say you have the following email accounts:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="mailto:sales@example.com"&gt;sales@example.com&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="mailto:support@example.com"&gt;support@example.com&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="mailto:main@example.com"&gt;main@example.com&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;… and now you want to set it up so that the email from the first two is forwarded to the main one. This way you only need to check a single mailbox. Furthermore, let’s say you want the following tags:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;sales &lt;/li&gt;

  &lt;li&gt;support &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;… which correspond to the first two email addresses, obviously. This is how you would set that up…&lt;/p&gt;

&lt;h5&gt;To set up robust tagging via forwarding in Gmail:&lt;/h5&gt;

&lt;ol&gt;
  &lt;li&gt;Forward the mail to the main accounts plus address. Use a different address per forward. 
    &lt;br /&gt;

    &lt;br /&gt;In the example above, you could set up &lt;a href="mailto:sales@example.com"&gt;sales@example.com&lt;/a&gt; to forward to &lt;a href="mailto:main+sales@example.com"&gt;main+sales@example.com&lt;/a&gt; and &lt;a href="mailto:support@example.com"&gt;support@example.com&lt;/a&gt; to &lt;a href="mailto:main+support@example.com"&gt;main+support@example.com&lt;/a&gt;. &lt;/li&gt;

  &lt;li&gt;On the main account, create a filter which checks for these plus addresses, and labels them, by doing the following: 
    &lt;ol&gt;
      &lt;li&gt;Create a new filter (in the main account). &lt;/li&gt;

      &lt;li&gt;For the &lt;code&gt;has the words&lt;/code&gt; field, enter: &lt;code&gt;deliveredto:main+sales@example.com&lt;/code&gt; &lt;/li&gt;

      &lt;li&gt;For the action, set it to apply a label of your choice (&lt;code&gt;sales&lt;/code&gt;, in this example). &lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;

  &lt;li&gt;Repeat the process for each account (besides the main account). Here’s what it looks like for the other account: 
    &lt;ol&gt;
      &lt;li&gt;Create a new filter (in the main account). &lt;/li&gt;

      &lt;li&gt;For the &lt;code&gt;has the words&lt;/code&gt; field, enter: &lt;code&gt;deliveredto:main+support@example.com&lt;/code&gt; &lt;/li&gt;

      &lt;li&gt;For the action, set it to apply a label of your choice (&lt;code&gt;support&lt;/code&gt;, in this example). &lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You now have all the benefits of POP3, with the instantaneous delivery of email forwarding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: one other benefit POP3 has is that it will keep your account from expiring by logging in on your behalf every hour or so. If you want this feature, you could set up POP3 in addition to the above method so that both are working simultaneously. Don't worry, it won't cause duplicate emails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; this is mainly written for reading from/to Google Apps or Gmail accounts, but should work with other providers as well.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-859095797073185192?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/859095797073185192/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/08/gmail-robust-tagging-of-forwarded-email.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/859095797073185192'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/859095797073185192'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/08/gmail-robust-tagging-of-forwarded-email.html' title='Gmail: Robust tagging of forwarded email'/><author><name>Senseful</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-7911021569811750746</id><published>2010-08-09T09:37:00.001-07:00</published><updated>2010-08-09T11:49:29.237-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:iPhone'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:#How Things Work'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:iPad'/><title type='text'>iPhone Email Image Sizes</title><content type='html'>&lt;p&gt;When you use the native &lt;strong&gt;Mail &lt;/strong&gt;app to send a picture, it will ask you to choose which size to use. It tells you the file size of the image, but it doesn’t mention what resolution is going to be used. I created a table which shows which resolutions will be used. &lt;/p&gt; &lt;a name='more'&gt;&lt;/a&gt;  &lt;h4&gt;iPhone 4 Image Email Sizes&lt;/h4&gt;  &lt;table style="width: 428px" border="0" cellspacing="0" cellpadding="2" width="462"&gt;&lt;thead&gt;     &lt;tr&gt;       &lt;td valign="top" width="94"&gt;Type&lt;/td&gt;        &lt;td valign="top" width="61"&gt;Size&lt;/td&gt;        &lt;td valign="top" width="83"&gt;Format&lt;/td&gt;        &lt;td valign="top" width="94"&gt;File size&lt;/td&gt;        &lt;td valign="top" width="128"&gt;Image size&lt;/td&gt;     &lt;/tr&gt;   &lt;/thead&gt;&lt;tbody&gt;     &lt;tr&gt;       &lt;td valign="top" width="94"&gt;Picture&lt;/td&gt;        &lt;td valign="top" width="61"&gt;Small&lt;/td&gt;        &lt;td valign="top" width="83"&gt;JPG (GPS)&lt;/td&gt;        &lt;td valign="top" width="94"&gt;30 KB&lt;/td&gt;        &lt;td valign="top" width="128"&gt;320 x 239&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="94"&gt;Picture&lt;/td&gt;        &lt;td valign="top" width="61"&gt;Medium&lt;/td&gt;        &lt;td valign="top" width="83"&gt;JPG (GPS)&lt;/td&gt;        &lt;td valign="top" width="94"&gt;90 KB&lt;/td&gt;        &lt;td valign="top" width="128"&gt;640 x 478&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="94"&gt;Picture&lt;/td&gt;        &lt;td valign="top" width="61"&gt;Large&lt;/td&gt;        &lt;td valign="top" width="83"&gt;JPG (GPS)&lt;/td&gt;        &lt;td valign="top" width="94"&gt;375 KB&lt;/td&gt;        &lt;td valign="top" width="128"&gt;1296 x 968&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="94"&gt;Picture&lt;/td&gt;        &lt;td valign="top" width="61"&gt;Actual&lt;/td&gt;        &lt;td valign="top" width="83"&gt;JPG (GPS)&lt;/td&gt;        &lt;td valign="top" width="94"&gt;2 MB&lt;/td&gt;        &lt;td valign="top" width="128"&gt;2592 x 1936&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="94"&gt;Front picture&lt;/td&gt;        &lt;td valign="top" width="61"&gt;-&lt;/td&gt;        &lt;td valign="top" width="83"&gt;JPG (GPS)&lt;/td&gt;        &lt;td valign="top" width="94"&gt;120 KB&lt;/td&gt;        &lt;td valign="top" width="128"&gt;480 x 640&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="94"&gt;Screenshot&lt;/td&gt;        &lt;td valign="top" width="61"&gt;Small&lt;/td&gt;        &lt;td valign="top" width="83"&gt;PNG&lt;/td&gt;        &lt;td valign="top" width="94"&gt;135 KB&lt;/td&gt;        &lt;td valign="top" width="128"&gt;213 x 320&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="94"&gt;Screenshot&lt;/td&gt;        &lt;td valign="top" width="61"&gt;Medium&lt;/td&gt;        &lt;td valign="top" width="83"&gt;PNG&lt;/td&gt;        &lt;td valign="top" width="94"&gt;522 KB&lt;/td&gt;        &lt;td valign="top" width="128"&gt;426 x 640&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="94"&gt;Screenshot&lt;/td&gt;        &lt;td valign="top" width="61"&gt;Actual&lt;/td&gt;        &lt;td valign="top" width="83"&gt;PNG&lt;/td&gt;        &lt;td valign="top" width="94"&gt;1 MB&lt;/td&gt;        &lt;td valign="top" width="128"&gt;640 x 960&lt;/td&gt;     &lt;/tr&gt;   &lt;/tbody&gt;&lt;/table&gt;  &lt;h4&gt;iPad Image Email Sizes&lt;/h4&gt;  &lt;table style="width: 428px" border="0" cellspacing="0" cellpadding="2" width="462"&gt;&lt;thead&gt;     &lt;tr&gt;       &lt;td valign="top" width="95"&gt;Type&lt;/td&gt;        &lt;td valign="top" width="70"&gt;Size&lt;/td&gt;        &lt;td valign="top" width="79"&gt;Format&lt;/td&gt;        &lt;td valign="top" width="93"&gt;File size&lt;/td&gt;        &lt;td valign="top" width="123"&gt;Image size&lt;/td&gt;     &lt;/tr&gt;   &lt;/thead&gt;&lt;tbody&gt;     &lt;tr&gt;       &lt;td valign="top" width="95"&gt;Screenshot&lt;/td&gt;        &lt;td valign="top" width="70"&gt;-&lt;/td&gt;        &lt;td valign="top" width="79"&gt;PNG&lt;/td&gt;        &lt;td valign="top" width="93"&gt;933 KB&lt;/td&gt;        &lt;td valign="top" width="123"&gt;768 x 1024&lt;/td&gt;     &lt;/tr&gt;   &lt;/tbody&gt;&lt;/table&gt;  &lt;h5&gt;Legend:&lt;/h5&gt;  &lt;ul&gt;   &lt;li&gt;&lt;strong&gt;Picture&lt;/strong&gt;: A picture taken using the rear facing camera. &lt;/li&gt;    &lt;li&gt;&lt;strong&gt;Front picture&lt;/strong&gt;: A picture taken using the front facing camera. &lt;/li&gt;    &lt;li&gt;&lt;strong&gt;Screenshot&lt;/strong&gt;: A screenshot taken by pressing the home and lock buttons at the same time. &lt;/li&gt;    &lt;li&gt;&lt;strong&gt;GPS&lt;/strong&gt;: These images store your current location as part of the image, if location services is enabled for the camera app. &lt;/li&gt;    &lt;li&gt;&lt;strong&gt;Size&lt;/strong&gt;: The size as it is listed in the resize prompt of the Mail app. The “-“ means that the mail app does not prompt for an image size, and the full resolution is used. &lt;/li&gt;    &lt;li&gt;&lt;strong&gt;File size&lt;/strong&gt;: These file sizes are approximations. &lt;/li&gt;    &lt;li&gt;&lt;strong&gt;Image size&lt;/strong&gt;: The image size, in pixels. They are all 72 pixels/inch.&lt;/li&gt; &lt;/ul&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-7911021569811750746?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/7911021569811750746/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/08/iphone-email-image-sizes.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/7911021569811750746'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/7911021569811750746'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/08/iphone-email-image-sizes.html' title='iPhone Email Image Sizes'/><author><name>Senseful</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-4166828005923396651</id><published>2010-08-06T16:48:00.000-07:00</published><updated>2010-08-16T00:53:02.862-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:#How To'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:Keyboard'/><title type='text'>How to fix keyboard shortcuts in KLC (e.g. Ctrl+S)</title><content type='html'>&lt;p&gt;I recently created my own keyboard layout using &lt;a href="http://msdn.microsoft.com/en-us/goglobal/bb964665.aspx"&gt;Microsoft Keyboard Layout Creator&lt;/a&gt;. Everything was working fine until I tried using a shortcut such as &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;S&lt;/kbd&gt;. Instead of saving the document, it used the underlying Qwerty keyboard key. In this case it was &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;;&lt;/kbd&gt;, which didn't do anything. A day or so later, I read &lt;a href="http://michaelcapewell.com/projects/keyboard/layout_capewell-dvorak.htm"&gt;this article&lt;/a&gt; by Michael Capewell, where he mentions that he was able to fix the keyboard shortcuts. I sent him an email asking how to do just that, and this is what I found out...&lt;/p&gt;

&lt;a name='more'&gt;&lt;/a&gt;

&lt;p&gt;&lt;h5&gt;To restore keyboard shortcuts in Microsoft KLC:&lt;/h5&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the keyboard as you normally would in KLC.&lt;/li&gt;
&lt;li&gt;Save the layout as a &lt;code&gt;.klc&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;Open the &lt;code&gt;.klc&lt;/code&gt; file in a text editor.&lt;/li&gt;
&lt;li&gt;For each key that was moved, you need to update the &lt;code&gt;VK_&lt;/code&gt; column to reflect this change as well, since KLC doesn't do this for you. See the &lt;a href="#which-keys"&gt;list at the end of this post&lt;/a&gt; to see the valid values.

&lt;p&gt;For example, if you are changing &lt;kbd&gt;L&lt;/kbd&gt; to &lt;kbd&gt;N&lt;/kbd&gt;, you would change line 3:&lt;/p&gt;

&lt;pre class="javascript" name="code"&gt;//SC    VK_             Cap     0       1       2
//--    ----            ----    ----    ----    ----
26      L               1       n       N       -1              // LATIN SMALL LETTER N, LATIN CAPITAL LETTER N, &amp;lt;none&amp;gt;
&lt;/pre&gt;

&lt;p&gt;to:&lt;/p&gt;

&lt;pre class="javascript" name="code"&gt;//SC    VK_             Cap     0       1       2
//--    ----            ----    ----    ----    ----
26      N               1       n       N       -1              // LATIN SMALL LETTER N, LATIN CAPITAL LETTER N, &amp;lt;none&amp;gt;
&lt;/pre&gt;

&lt;p&gt;... by simply replacing 1 letter, &lt;code&gt;L&lt;/code&gt; with &lt;code&gt;N&lt;/code&gt;, in the &lt;code&gt;VK_&lt;/code&gt; column.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You cannot repeat a &lt;code&gt;VK_&lt;/code&gt; value. If you do, it will only take the first one and the other key will not be assigned to anything. You will get warnings about this when you try to build your keyboard.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;Open the &lt;code&gt;.klc&lt;/code&gt; in Microsoft Keyboard Layout Creator.&lt;/li&gt;
&lt;li&gt;Build and install your keyboard as you normally would.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id="which-keys"&gt;Which values can I use in the VK_ column?&lt;/h3&gt;

&lt;p&gt;The default &lt;code&gt;VK_&lt;/code&gt; values that can be used are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;A&lt;/code&gt; ... &lt;code&gt;Z&lt;/code&gt; - The keys representing &lt;kbd&gt;A&lt;/kbd&gt; to &lt;kbd&gt;Z&lt;/kbd&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0&lt;/code&gt; ... &lt;code&gt;9&lt;/code&gt; - The keys at the top of your keyboard (not the Numpad) that represent &lt;kbd&gt;0&lt;/kbd&gt; to &lt;kbd&gt;9&lt;/kbd&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OEM_PLUS&lt;/code&gt; - &lt;kbd&gt;+&lt;/kbd&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OEM_COMMA&lt;/code&gt; - &lt;kbd&gt;,&lt;/kbd&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OEM_PERIOD&lt;/code&gt; - &lt;kbd&gt;.&lt;/kbd&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OEM_1&lt;/code&gt; - &lt;kbd&gt;;&lt;/kbd&gt; &lt;kbd&gt;:&lt;/kbd&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OEM_2&lt;/code&gt; - &lt;kbd&gt;/&lt;/kbd&gt; &lt;kbd&gt;?&lt;/kbd&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OEM_3&lt;/code&gt; - &lt;kbd&gt;`&lt;/kbd&gt; &lt;kbd&gt;~&lt;/kbd&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OEM_4&lt;/code&gt; - &lt;kbd&gt;[&lt;/kbd&gt; &lt;kbd&gt;{&lt;/kbd&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OEM_5&lt;/code&gt; - &lt;kbd&gt;\&lt;/kbd&gt; &lt;kbd&gt;|&lt;/kbd&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OEM_6&lt;/code&gt; - &lt;kbd&gt;]&lt;/kbd&gt; &lt;kbd&gt;}&lt;/kbd&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OEM_7&lt;/code&gt; - &lt;kbd&gt;'&lt;/kbd&gt; &lt;kbd&gt;"&lt;/kbd&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OEM_MINUS&lt;/code&gt; - &lt;kbd&gt;-&lt;/kbd&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SPACE&lt;/code&gt; - &lt;kbd&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/kbd&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DECIMAL&lt;/code&gt; - &lt;kbd&gt;.&lt;/kbd&gt; on Numpad&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See &lt;a href="http://msdn.microsoft.com/en-us/library/ms927178.aspx"&gt;this document for more information&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks to Michael Capewell for explaining the process.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-4166828005923396651?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/4166828005923396651/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/08/how-to-fix-keyboard-shortcuts-in-klc-eg.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/4166828005923396651'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/4166828005923396651'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/08/how-to-fix-keyboard-shortcuts-in-klc-eg.html' title='How to fix keyboard shortcuts in KLC (e.g. Ctrl+S)'/><author><name>Senseful</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-7279812943991862612</id><published>2010-08-05T14:49:00.000-07:00</published><updated>2010-08-09T09:46:10.267-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:iPhone'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:#How To'/><title type='text'>iPhone: How to play a YouTube video in portrait mode when orientation lock is enabled</title><content type='html'>One of my favorite features of iOS 4 is orientation lock (double tap home button, swipe right, first icon). I can't stand the wide screen keyboard and hate when applications force you to use landscape mode. This is especially annoying when you are lying down and trying to use the phone. This new feature solves the problem in most applications.&amp;nbsp;One application that doesn't handle it well, though, is YouTube.&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
YouTube allows you to view portrait videos, ironically though, for users that enable orientation lock (i.e. those that like portrait the most) it shows a landscape video. The problem lies in how Apple implemented orientation lock and in the fact that the YouTube app doesn't compensate for this. When orientation lock is enabled, it suppresses the events that are normally triggered when you rotate your device. This makes it much easier for apps to support this feature without having to write any logic. The problem with the YouTube app is that it starts every video in landscape, and because the event that tells it you are in portrait isn't triggered, it stays in landscape.&lt;br /&gt;
&lt;br /&gt;
The way to fix it is to navigate away from the YouTube app and then back to it. Here are some of the ways to do that...&lt;br /&gt;
&lt;br /&gt;
&lt;h5&gt;To force a YouTube video into portrait mode when orientation lock is enabled:&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;Launch the video&lt;/li&gt;
&lt;li&gt;When the video is on the screen, do &lt;b&gt;one &lt;/b&gt;of the following:&lt;/li&gt;
&lt;ul&gt;
&lt;li&gt;Double tap the home button (to show the running apps), then click on the video again.&lt;/li&gt;
&lt;li&gt;Lock the screen, then unlock the screen.&lt;/li&gt;
&lt;li&gt;Press the mail icon on the video, press cancel, and then delete draft.&lt;/li&gt;
&lt;li&gt;Press the home button (to exit YouTube), then launch YouTube again (your device must support background apps so that the video is still open when you return to YouTube).&lt;/li&gt;
&lt;/ul&gt;
&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-7279812943991862612?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/7279812943991862612/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/08/iphone-how-to-play-youtube-video-in.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/7279812943991862612'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/7279812943991862612'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/08/iphone-how-to-play-youtube-video-in.html' title='iPhone: How to play a YouTube video in portrait mode when orientation lock is enabled'/><author><name>Senseful</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-557786515623739133</id><published>2010-08-02T15:25:00.000-07:00</published><updated>2010-08-06T16:50:56.993-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:#Troubleshooting'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:Keyboard'/><title type='text'>Microsoft KLC keyboard doesn't uninstall</title><content type='html'>&lt;p&gt;I was trying out &lt;a href="http://msdn.microsoft.com/en-us/goglobal/bb964665.aspx"&gt;Microsoft's Keyboard Layout Creator&lt;/a&gt; this week. I ended up creating a keyboard and then wanted to correct it and reinstall it. When I tried reinstalling it, though, it said that there was already a keyboard with that name installed even though I removed it.&lt;/p&gt;

&lt;p&gt;I figured out the problem and posted the steps to resolve it here.&lt;/p&gt;

&lt;a name='more'&gt;&lt;/a&gt;

&lt;p&gt;&lt;strong&gt;How to uninstall a keyboard (the correct way)&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to add/remove programs&lt;/li&gt;
&lt;li&gt;Find the keyboard, it should be the same name as the &lt;strong&gt;Description&lt;/strong&gt; field of the project's properties in Keyboard Layout Creator.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;Modify&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Remove the keyboard layout&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This will also cause it to be removed from the add/remove program list. This method will even remove the keyboard if it is set up as your secondary keyboard. There's no need to remove it from the Regional settings prior to uninstalling it with this method.&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;The problem is that I uninstalled the keyboard incorrectly. These are the steps I did which caused it to uninstall incorrectly. (I'm including this here for reference, do not repeat these steps).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DO NOT repeat these steps&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to uninstall a keyboard (the INCORRECT way)&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to add/remove programs&lt;/li&gt;
&lt;li&gt;Press Support&lt;/li&gt;
&lt;li&gt;Again: Do not repeat these steps unless you purposely want a keyboard that you won't be able to uninstall correctly.&lt;/li&gt;
&lt;li&gt;Press Repair&lt;/li&gt;
&lt;li&gt;Now press uninstall.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You will now &lt;strong&gt;not&lt;/strong&gt; be able to uninstall the keyboard properly even if you reinstall it. The reason is that when you reinstall it, it will actually create a second entry in the registry rather than overwriting the first.&lt;/p&gt;

&lt;p&gt;If you did something similar to the steps above, read on for a way to manually uninstall it.&lt;/p&gt;

&lt;p&gt;Here's how I manually removed the keyboard, partially following &lt;a href="http://www.tomshardware.com/forum/57424-45-microsoft-keyboard-layout-creator"&gt;these instructions&lt;/a&gt;, and partially using Process Monitor to see what files and registry entires are created:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to manually remove a keyboard that was installed using Keyboard Layout Creator&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Remove the keyboard from the Language and Regional settings:
&lt;ol&gt;&lt;li&gt;Control Panel &amp;gt; Regional and Language Options &amp;gt; Languages &amp;gt; Details...&lt;/li&gt;
&lt;li&gt;If it exists as one of the installed services, you should remove it. You can do this by adding another keyboard (e.g. &lt;code&gt;US&lt;/code&gt;) and making it the default one, then removing the keyboard you are trying to get rid of.&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;
&lt;li&gt;Open &lt;strong&gt;regedit&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Warning: you should backup your registry in case you accidentally delete something important.&lt;/li&gt;
&lt;li&gt;Navigate to &lt;code&gt;HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts\&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Find the key that represents your keyboard: (referred to as "the key" in following steps)
&lt;ul&gt;&lt;li&gt;It should be in the form &lt;code&gt;XXXXXXXX&lt;/code&gt; (an 8 hex digit number).&lt;/li&gt;
&lt;li&gt;Each key should have values such as: Layout Display Name, Layout File, Layout Id, Layout Product Code, Layout Text.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Layout Text&lt;/code&gt; property should match the keyboard name that you chose.&lt;/li&gt;
&lt;li&gt;Mine was near the end and started with an &lt;code&gt;a&lt;/code&gt; instead of a &lt;code&gt;0&lt;/code&gt; like all the others.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Take a look at the Layout File which should be a &lt;code&gt;.dll&lt;/code&gt; name. I will refer to it as "the dll" in the following steps.&lt;/li&gt;
&lt;li&gt;Look for the dll in &lt;code&gt;C:\WINDOWS\system32\dllcache&lt;/code&gt; and delete it if it exists.&lt;/li&gt;
&lt;li&gt;Look for the dll in &lt;code&gt;C:\WINDOWS\system32&lt;/code&gt; and delete it if it exists.&lt;/li&gt;
&lt;li&gt;Go to &lt;code&gt;HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout\DosKeybCodes&lt;/code&gt; and see if it has a &lt;code&gt;Name&lt;/code&gt; which is equal to the key's name (e.g. &lt;code&gt;XXXXXXXX&lt;/code&gt;) and a value such as &lt;code&gt;en&lt;/code&gt;. If it does, delete it.&lt;/li&gt;
&lt;li&gt;Navigate back to the key (from step 5), and delete the entire key.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It should now no longer exist in the Regional Settings dialog, and you can recreate your keyboard!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-557786515623739133?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/557786515623739133/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/08/how-to-uninstall-klc-keyboard.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/557786515623739133'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/557786515623739133'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/08/how-to-uninstall-klc-keyboard.html' title='Microsoft KLC keyboard doesn&apos;t uninstall'/><author><name>Senseful</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-8139824644500989835</id><published>2010-08-02T00:18:00.000-07:00</published><updated>2010-10-29T00:49:50.066-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:iPhone'/><title type='text'>iPhone Folder Background Home Screen Icons</title><content type='html'>&lt;p&gt;Here's something similar to the &lt;a href="/2010/01/iphone-blank-home-screen-icon.html"&gt;iPhone Blank Home Screen Icons&lt;/a&gt; I made. This will work in iOS4, however it won't be 100% transparent, since as I mentioned in the other post, Apple doesn't allow transparent icons.&lt;/p&gt;
&lt;a name='more'&gt;&lt;/a&gt;
&lt;p&gt;
To install, click on one of the images below:
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img alt="1x1" height="64px" onclick="createIcon(this.alt, this.src);" src="http://2.bp.blogspot.com/_LFHelgrd5Rg/TFZdF_X_xGI/AAAAAAAAADA/ITW38wIchlE/s1600/1x1.png" width="64px" /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img alt="1x2" height="64px" onclick="createIcon(this.alt, this.src);" src="http://1.bp.blogspot.com/_LFHelgrd5Rg/TFZdGHhaYCI/AAAAAAAAADE/zVaIbI_28Ek/s1600/1x2.png" width="64px" /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img alt="1x3" height="64px" onclick="createIcon(this.alt, this.src);" src="http://4.bp.blogspot.com/_LFHelgrd5Rg/TFZdGR8FZQI/AAAAAAAAADI/HirX2DEOYyM/s1600/1x3.png" width="64px" /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img alt="1x4" height="64px" onclick="createIcon(this.alt, this.src);" src="http://1.bp.blogspot.com/_LFHelgrd5Rg/TFZdGkIylSI/AAAAAAAAADM/X0ImIeOTojs/s1600/1x4.png" width="64px" /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img alt="2x1" height="64px" onclick="createIcon(this.alt, this.src);" src="http://2.bp.blogspot.com/_LFHelgrd5Rg/TFZdG-Uc48I/AAAAAAAAADQ/FJ9Q5UQ5bKw/s1600/2x1.png" width="64px" /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img alt="2x2" height="64px" onclick="createIcon(this.alt, this.src);" src="http://4.bp.blogspot.com/_LFHelgrd5Rg/TFZdHDst2QI/AAAAAAAAADU/EOYFggRq5uk/s1600/2x2.png" width="64px" /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img alt="2x3" height="64px" onclick="createIcon(this.alt, this.src);" src="http://1.bp.blogspot.com/_LFHelgrd5Rg/TFZdHsyTEcI/AAAAAAAAADY/5wiv4_gATRw/s1600/2x3.png" width="64px" /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img alt="2x4" height="64px" onclick="createIcon(this.alt, this.src);" src="http://2.bp.blogspot.com/_LFHelgrd5Rg/TFZdHyGjmZI/AAAAAAAAADc/uxOKy-ZtPqw/s1600/2x4.png" width="64px" /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img alt="3x1" height="64px" onclick="createIcon(this.alt, this.src);" src="http://2.bp.blogspot.com/_LFHelgrd5Rg/TFZdH2Bl9YI/AAAAAAAAADg/Tfeh2GtbghA/s1600/3x1.png" width="64px" /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img alt="3x2" height="64px" onclick="createIcon(this.alt, this.src);" src="http://2.bp.blogspot.com/_LFHelgrd5Rg/TFZdIdYNpaI/AAAAAAAAADk/r3slitfbwQY/s1600/3x2.png" width="64px" /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img alt="3x3" height="64px" onclick="createIcon(this.alt, this.src);" src="http://3.bp.blogspot.com/_LFHelgrd5Rg/TFZdImxzVBI/AAAAAAAAADo/cIVlrQJ2S7I/s1600/3x3.png" width="64px" /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img alt="3x4" height="64px" onclick="createIcon(this.alt, this.src);" src="http://1.bp.blogspot.com/_LFHelgrd5Rg/TFZdFkUJ3KI/AAAAAAAAAC8/HtXrwqWzvaY/s1600/3x4.png" width="64px" /&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
(version 1.0)
&lt;/p&gt;
&lt;br /&gt;
  &lt;script type="text/javascript"&gt;
function createIcon(iconNum, iconURL) {
  var version = '1.0';
  
  // WebApp HTML
  var src = 'data:text/html;charset=utf-8,%3Chtml%3E%3Chead%3E%3Cmeta%20' +
    'name%3D%22viewport%22%20content%3D%22width%3Ddevice-width%2' +
    'Cuser-scalable%3Dno%22%20%2F%3E%3C%2Fhead%3E%3Cbody%3E%3Cp%' +
    '3EFolder%20Icon%20FICONNUM.%3C%2Fp%3E%3Cp%3E%3Ca%20href%3D%' +
    '22FURL%22%3EiPhone%20Folder%20Background%20Icon%3C%2Fa%3E%2' +
    '0FVERSION%3C%2Fp%3E%3C%2Fbody%3E%3C%2Fhtml%3E';
  src = src.replace('FICONNUM', iconNum);
  src = src.replace('FVERSION', version);

  // Safari HTML
  var newDoc = window.open(src).document;
  newDoc.open();
  newDoc.writeln('&lt;html&gt;&lt;head&gt;');
  newDoc.writeln('&lt;title&gt;&amp;zwj;&lt;/title&gt;');
  newDoc.writeln('&lt;meta name="apple-mobile-web-app-capable" content="yes" /&gt;');
  newDoc.writeln('&lt;meta name="viewport" content="width=device-width,user-scalable=no" /&gt;');
  newDoc.writeln('&lt;link rel="apple-touch-icon-precomposed" href="' + iconURL + '"/&gt;');
  newDoc.writeln('&lt;/head&gt;');
  newDoc.writeln('&lt;body&gt;');
  newDoc.writeln('Instructions:');
  newDoc.writeln('&lt;ol&gt;');
  newDoc.writeln('&lt;li&gt;Press the &lt;b&gt;plus (+)&lt;/b&gt; button.&lt;/li&gt;');
  newDoc.writeln('&lt;li&gt;Press &lt;b&gt;Add to Home Screen&lt;/b&gt;.&lt;/li&gt;');
  newDoc.writeln('&lt;li&gt;Press &lt;b&gt;Add&lt;/b&gt;.&lt;/li&gt;');
  newDoc.writeln('&lt;li&gt;&lt;a href="#" onclick="window.close(); return false;"&gt;Close this window.&lt;/a&gt;&lt;/li&gt;');
  newDoc.writeln('&lt;/ol&gt;');
  newDoc.writeln('&lt;/body&gt;&lt;/html&gt;');
  newDoc.close();
}
  &lt;/script&gt;
  &lt;style type="text/css"&gt;
    td img {
      cursor: pointer;
    }
  
&lt;/style&gt;&lt;br /&gt;
&lt;div&gt;
Here's an example of what it looks like:&lt;br /&gt;
&lt;div&gt;
&lt;a href="http://2.bp.blogspot.com/_LFHelgrd5Rg/TFZ8pX2fEhI/AAAAAAAAADs/5dbYYQXAeDs/s1600/column-organization.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="240" src="http://2.bp.blogspot.com/_LFHelgrd5Rg/TFZ8pX2fEhI/AAAAAAAAADs/5dbYYQXAeDs/s320/column-organization.png" width="160" /&gt;&lt;/a&gt;
&lt;a href="http://2.bp.blogspot.com/_LFHelgrd5Rg/TFZ8rJAEpLI/AAAAAAAAADw/h9ENoXw0lkA/s1600/empty-folder.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="240" src="http://2.bp.blogspot.com/_LFHelgrd5Rg/TFZ8rJAEpLI/AAAAAAAAADw/h9ENoXw0lkA/s320/empty-folder.png" width="160" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;&lt;/div&gt;

&lt;p&gt;
Features:
&lt;ul&gt;
&lt;li&gt;There are multiple icons since the background of folders is different for each icon position. If you want to get it the most transparent possible, you should place each icon in its corresponding position. They are numbered 1x1 (1st row, 1st col) to 3x4 (3rd row, 4th column). If you forget which place an icon belongs, you can simply open it up and it will display a number in that format.&lt;/li&gt;
&lt;li&gt;By default they will use a blank title.&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;

&lt;p&gt;
Uses:
&lt;ul&gt;
&lt;li&gt;I like having columns (or sometimes rows) of similar apps together.&lt;/li&gt;
&lt;li&gt;I also like having my more frequently used icons near the bottom so that they are easier to reach.&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-8139824644500989835?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/8139824644500989835/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/08/iphone-folder-background-home-screen.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/8139824644500989835'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/8139824644500989835'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/08/iphone-folder-background-home-screen.html' title='iPhone Folder Background Home Screen Icons'/><author><name>Senseful</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_LFHelgrd5Rg/TFZdF_X_xGI/AAAAAAAAADA/ITW38wIchlE/s72-c/1x1.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-729841632085425171</id><published>2010-08-01T16:44:00.001-07:00</published><updated>2010-08-01T17:23:24.398-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:#Tip'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:Evernote'/><title type='text'>Evernote Tip: Modify the “Date Updated” field to sort notes to the top</title><content type='html'>&lt;p&gt;I usually have my Evernote notes sorted by the &lt;strong&gt;Updated&lt;/strong&gt; property. This let’s me easily get to notes I am currently working on.&lt;/p&gt;  &lt;p&gt;Sometimes I’ll find a few more notes that I want near the top, but I don’t want to modify their contents to force them there. For example, I may just want them for reference. &lt;/p&gt;  &lt;p&gt;Here are a few ways to update the &lt;strong&gt;Updated&lt;/strong&gt; attribute without modifying the note’s content:&lt;/p&gt; &lt;a name='more'&gt;&lt;/a&gt;  &lt;ul&gt;   &lt;li&gt;PC:      &lt;br /&gt;      &lt;ul&gt;       &lt;li&gt;Type a space at the end of the title; &lt;/li&gt;        &lt;li&gt;or press the left align button on text that is already left aligned. &lt;/li&gt;     &lt;/ul&gt;   &lt;/li&gt;    &lt;li&gt;Mac:      &lt;br /&gt;      &lt;ul&gt;       &lt;li&gt;Press the left align button on text that is already left aligned. &lt;/li&gt;     &lt;/ul&gt;   &lt;/li&gt;    &lt;li&gt;iPad:      &lt;br /&gt;      &lt;ul&gt;       &lt;li&gt;Edit the note, type a space at the end of the title, Save. &lt;/li&gt;     &lt;/ul&gt;   &lt;/li&gt;    &lt;li&gt;iPhone:      &lt;br /&gt;      &lt;ul&gt;       &lt;li&gt;Edit the note, type a space at the end of the title, Save. &lt;/li&gt;     &lt;/ul&gt;   &lt;/li&gt;    &lt;li&gt;Web:      &lt;br /&gt;      &lt;ul&gt;       &lt;li&gt;Edit the note, type a space at the end of the title, Save and close. &lt;/li&gt;     &lt;/ul&gt;   &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;None of the above methods will change the note’s contents. Whitespace is simply stripped out from the title field when the note is saved.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-729841632085425171?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/729841632085425171/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/08/evernote-tip-modify-date-updated-field.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/729841632085425171'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/729841632085425171'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/08/evernote-tip-modify-date-updated-field.html' title='Evernote Tip: Modify the “Date Updated” field to sort notes to the top'/><author><name>Senseful</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-1377678628854313258</id><published>2010-08-01T01:14:00.000-07:00</published><updated>2010-08-01T01:19:54.883-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:#How Things Work'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:Gmail'/><title type='text'>How does email threading work in Gmail?</title><content type='html'>A really cool feature of Gmail is how it automatically threads email messages so that they appear as a single item rather than multiple items in your email list. This helps keep your inbox organized, especially if you have really long email threads. The problem is that sometimes I notice that it has false positives and something it has false negatives. I wanted to know exactly how email threading works in Gmail, so I tested it out, and here's what I found out....&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
The following rules must be met:
&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;The subject must be similar.&lt;/li&gt;
&lt;li&gt;The sender must be a part of the thread OR in-reply-to must be used.&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;
1) The subjects do not have to be exactly the same, but they must contain a pre-approved prefix (e.g. &lt;code&gt;test&lt;/code&gt; and &lt;code&gt;re: test&lt;/code&gt; will be in the same thread). I haven't tried all the prefixes, but some of the valid ones are: &lt;code&gt;RE:&lt;/code&gt;, &lt;code&gt;R:&lt;/code&gt;, and &lt;code&gt;FWD:&lt;/code&gt;. If you modify the subject in any other way, it will start a new thread (e.g. if you modify &lt;code&gt;test&lt;/code&gt; to &lt;code&gt;test 123&lt;/code&gt;).&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
2) The sender of the email message must be a part of the thread, or otherwise it will start a new thread. The one exception is if the in-reply-to header is supplied. You can get the in-reply-to header by replying to the original email through Gmail. For example, let's say you have a@example.com forward messages to b@example.com. Now let's say user@example.com sends you an email to a@example.com. The message will also be delivered to b@example.com. If you use the reply form in your email client (e.g. gmail) from the b@example.com, it will most likely automatically add the in-reply-to header for you. This way even though b@example.com was never a part of the thread that exists on user@example.com, it still goes into the same thread.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
One interesting thing to note is that if you send email messages from Gmail they will also be threaded. The rules are exactly the same as when you receive them, except for one minor detail. If you send the same exact message twice with no subject prefix&amp;nbsp;(e.g. subject is &lt;code&gt;test&lt;/code&gt; not &lt;code&gt;re: test&lt;/code&gt;) it does get threaded on the receiving end, but not on sending end. Conversely, if it does contain a prefix (e.g. &lt;code&gt;re: test&lt;/code&gt;) it will be threaded in both cases.&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
Note: there may be additional conditions which I didn't check for. &lt;a href="http://mail.google.com/support/bin/answer.py?answer=5900"&gt;Google's site says&lt;/a&gt; that there's a maximum of 100 messages per thread.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;
Now that I have a better understanding of how this feature works, I can try to limit the number of false positives and negatives.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-1377678628854313258?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/1377678628854313258/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/08/how-does-email-threading-work-in-gmail.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/1377678628854313258'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/1377678628854313258'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/08/how-does-email-threading-work-in-gmail.html' title='How does email threading work in Gmail?'/><author><name>Senseful</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-8055646706368894878</id><published>2010-07-20T15:42:00.001-07:00</published><updated>2012-01-20T23:27:53.600-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:#How Things Work'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:Evernote'/><title type='text'>Evernote Tag Sort Order</title><content type='html'>&lt;div class="alert"&gt;I created a &lt;a href="/2012/01/evernote-tag-sort-order-utility.html"&gt;new utility&lt;/a&gt; which supports more clients and makes finding which characters to use much easier.&lt;/div&gt;&lt;br /&gt;
Sometimes I like to use characters in front of tags (or saved searches) to have them sort to the top. The problem is that not every Evernote client sorts the tags in the same order. For reference, I have written down the sort order for several of the clients:&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;PC:&amp;nbsp;&lt;code&gt;-' !"#$%&amp;amp;()*./:;?@[\]^_`{|}~+&amp;lt;=&amp;gt;1aBcD&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Mac:&amp;nbsp;&lt;code&gt; !"#$%&amp;amp;'()*+-./1:;&amp;lt;=&amp;gt;?@[\]^_`aBcD{|}~&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Web:&amp;nbsp; (same as Mac)&lt;/li&gt;
&lt;li&gt;iPhone:&amp;nbsp; (coming soon)&lt;/li&gt;
&lt;li&gt;iPad:&lt;ul&gt;
  &lt;li&gt;Tags list: &lt;code&gt;&amp;nbsp;`^_-;:!?.'"()[]{}@*/\&amp;amp;#%+&amp;lt;=&amp;gt;|~$1aBcD&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Tag selection list: (same as Tags list)&lt;/li&gt;
  &lt;li&gt;Note info list: (same as Mac)&lt;/li&gt;
  &lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
Therefore, to maximize the amount of symbols you can use and still have them be consistently sorted (on the Mac and PC clients), I would recommend using the following symbols: &lt;code&gt;&amp;nbsp;!"#$%&amp;amp;()./:;?@[\]^_`A&lt;/code&gt;&lt;br /&gt;
... and avoiding the following symbols: &lt;code&gt;-'*{|}~+&amp;lt;=&amp;gt;;1&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
This also means that numbers (0-9) will not be consistently sorted on the Mac and PC, so you might want to avoid mixing them with symbols.&lt;br /&gt;
&lt;br /&gt;
The only way to consistently get something to the bottom is to prefix it with several &lt;code&gt;z&lt;/code&gt;'s. &lt;br /&gt;
&lt;br /&gt;
Notes:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;These orders are only for the Tags and Saved Searches. The Title sorting for all clients always uses the same order as the Mac version.&lt;/li&gt;
&lt;li&gt;The space symbol (&lt;code&gt;&amp;nbsp;&lt;/code&gt;) is okay to use. Look for it near the beginning of each string.&lt;/li&gt;
&lt;li&gt;It uses a &lt;b&gt;case-insensitive&lt;/b&gt; sort, this can be seen with the string &lt;code&gt;aBcD&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If you decide to use the open parenthesis symbol &lt;code&gt;(&lt;/code&gt;, it must have a matching close symbol &lt;code&gt;)&lt;/code&gt;, otherwise there is no way you can search for the tag. For example, &lt;code&gt;(testing)&lt;/code&gt; is fine, but &lt;code&gt;(testing&lt;/code&gt; is not.&lt;/li&gt;
&lt;li&gt;The reason I don't recommend &lt;code&gt;*&lt;/code&gt;, even though it would be sorted correctly, is because it will not allow you to use the &lt;code&gt;tag:&lt;/code&gt; search query to find it. The only way to search for it is to actually click on the tag in the tags list.&lt;/li&gt;
&lt;li&gt;The&amp;nbsp;&lt;code&gt;,&lt;/code&gt;&amp;nbsp;symbol is not allowed in tag names.&lt;/li&gt;
&lt;li&gt;I've had at least one occasion where the iPad was giving me trouble creating a tag with the &lt;code&gt;?&lt;/code&gt; or &lt;code&gt;*&lt;/code&gt; symbols. I had to use another client to create them.&lt;/li&gt;
&lt;li&gt;The iPad "Tags list" refers to the Tags option on the home page. The iPad "Tag selection list" refers to the list when you tap the blue arrow next to the tags, which allows you to select tags to apply to the active note. The iPad "Note info list" refers to the list that is shown when you tap the "Details" link on a note, to see which tags it currently has.
&lt;/ul&gt;

&lt;b&gt;Update (9/26/2010)&lt;/b&gt;: I just added the data for iPad sorting. As you can see, it's very different from the other two clients. For this reason, you may not want to optimize your symbol selection based on its criteria, since then you will be limited to far fewer symbols to choose from.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-8055646706368894878?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/8055646706368894878/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/07/evernote-tag-sort-order.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/8055646706368894878'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/8055646706368894878'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/07/evernote-tag-sort-order.html' title='Evernote Tag Sort Order'/><author><name>Senseful</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-3446567868214934385</id><published>2010-07-14T18:32:00.000-07:00</published><updated>2010-07-14T18:32:53.547-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:#How Things Work'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:Gmail'/><title type='text'>How Gmail Filter Email-Matching Works</title><content type='html'>&lt;p&gt;I was trying to create some complicated Gmail filters. However, there doesn't seem to be any documentation of how the &lt;code&gt;to&lt;/code&gt; and &lt;code&gt;from&lt;/code&gt; fields work exactly. So I tried figuring it out myself...&lt;/p&gt;

&lt;a name='more'&gt;&lt;/a&gt;

&lt;h3&gt;General Matching Guidelines&lt;/h3&gt;

&lt;p&gt;The matching criteria is similar to Google's search. There is no word stemming, so you must enter full words (e.g. &lt;code&gt;joh&lt;/code&gt; will not match &lt;code&gt;john.smith@gmail.com&lt;/code&gt;). Not even plural stemming, like what Google search has, is supported (e.g. &lt;code&gt;app&lt;/code&gt; will not match &lt;code&gt;apps@example.com&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Word order does not matter, unless the words are enclosed in quotes (e.g. &lt;code&gt;"smith john"&lt;/code&gt; will not match &lt;code&gt;john.smith@gmail.com&lt;/code&gt;). Generally, symbols are ignored (for more information see the next section).&lt;/p&gt;

&lt;p&gt;Words are split on everything &lt;em&gt;except&lt;/em&gt;: letters, numbers, and underscores. The most common symbols that split words are &lt;code&gt;+.@&lt;/code&gt;. This means that &lt;code&gt;foo&lt;/code&gt; will not match &lt;code&gt;foo_bar@example.com&lt;/code&gt; but will match &lt;code&gt;foo+bar@example.com&lt;/code&gt;. The &lt;code&gt;@&lt;/code&gt; character itself is not considered a word and can be skipped over (e.g. &lt;code&gt;"smith gmail"&lt;/code&gt; will match &lt;code&gt;john.smith@gmail.com&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;You can use the &lt;code&gt;OR&lt;/code&gt; operator in addition to grouping &lt;code&gt;()&lt;/code&gt; for some complex conditions.&lt;/p&gt;

&lt;h3&gt;Symbol Behavior&lt;/h3&gt;

&lt;p&gt;When you enter a symbol in the filter box, they usually behave differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Symbols that act as &lt;code&gt;x y&lt;/code&gt;: &lt;code&gt;~#$%^*+;",&amp;lt;&amp;gt;?&lt;/code&gt; and the grave character. For example, &lt;code&gt;smith~john&lt;/code&gt; becomes &lt;code&gt;smith john&lt;/code&gt;, which matches &lt;code&gt;john.smith@gmail.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Symbols that act as &lt;code&gt;"x y"&lt;/code&gt;: &lt;code&gt;-=\:'./&lt;/code&gt; -- For example, &lt;code&gt;john-smith&lt;/code&gt; becomes &lt;code&gt;"john smith"&lt;/code&gt;, which matches &lt;code&gt;john.smith@gmail.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Symbols that are treated literally: &lt;code&gt;&amp;amp;_&lt;/code&gt; -- For example, &lt;code&gt;john_smith&lt;/code&gt; will match &lt;code&gt;john_smith@gmail.com&lt;/code&gt;, but not &lt;code&gt;john.smith@gmail.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Special symbols: &lt;code&gt;!@()[]{}|&lt;/code&gt;
&lt;ul&gt;&lt;li&gt;&lt;code&gt;!&lt;/code&gt;: &lt;code&gt;john!smith&lt;/code&gt; becomes &lt;code&gt;john -smith&lt;/code&gt;, which matches &lt;code&gt;john.foo@gmail.com&lt;/code&gt; but not &lt;code&gt;john.smith@gmail.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@&lt;/code&gt;: 
&lt;ul&gt;&lt;li&gt;&lt;code&gt;@&lt;/code&gt; is stripped out at the end of a word. For example, &lt;code&gt;john@&lt;/code&gt; becomes &lt;code&gt;john&lt;/code&gt;, which matches &lt;code&gt;john.smith@gmail.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@&lt;/code&gt; is stripped out at the start of a word. For example, &lt;code&gt;@foo.com&lt;/code&gt; will become &lt;code&gt;foo.com&lt;/code&gt;, which matches &lt;code&gt;john+foo.com@gmail.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@&lt;/code&gt; in the middle of a word will generally require the full address for a successful match. For example, &lt;code&gt;john.smith@gmail&lt;/code&gt; will not match &lt;code&gt;john.smith@gmail.com&lt;/code&gt;. Additionally, symbols will be taken literally. For example, to match &lt;code&gt;john.smith@gmail.com&lt;/code&gt; you must use &lt;code&gt;john.smith@gmail.com&lt;/code&gt;... both &lt;code&gt;john-smith@gmail.com&lt;/code&gt; and &lt;code&gt;john~smith@gmail.com&lt;/code&gt; will no longer work.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@&lt;/code&gt; in a different location in the middle of a word has strange behavior. For example, when trying to match &lt;code&gt;john.smith@gmail.com&lt;/code&gt;:
&lt;ul&gt;&lt;li&gt;&lt;code&gt;john@smith@gmail@com&lt;/code&gt; does not match&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gmail@com&lt;/code&gt; does not match&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@gmail@com&lt;/code&gt; does not match&lt;/li&gt;
&lt;li&gt;&lt;code&gt;smith@gmail@com&lt;/code&gt; &lt;strong&gt;does&lt;/strong&gt; match&lt;/li&gt;
&lt;li&gt;&lt;code&gt;smith@gmail.com&lt;/code&gt; does not match&lt;/li&gt;
&lt;li&gt;&lt;code&gt;"john smith@gmail.com"&lt;/code&gt; does not match&lt;/li&gt;
&lt;li&gt;&lt;code&gt;"john.smith@gmail com"&lt;/code&gt; does not match&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;|&lt;/code&gt; acts as the &lt;code&gt;OR&lt;/code&gt; operator.&lt;/li&gt;
&lt;li&gt;Parenthesis act as grouping for &lt;code&gt;OR&lt;/code&gt; and &lt;code&gt;AND&lt;/code&gt; filters.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Other Matching Behaviors&lt;/h3&gt;

&lt;p&gt;The default account you use (e.g. &lt;code&gt;john.smith@gmail.com&lt;/code&gt;) will match all variations of your address. This includes dot notation, plus addressing, and using the googlemail.com domain.&lt;/p&gt;

&lt;p&gt;Here's a brief explanation of each:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Using dot notation&lt;/strong&gt;: You can enter as many non-consecutive dots in your email as you want. For example, if your email is &lt;code&gt;john.smith@gmail.com&lt;/code&gt;, mail sent to &lt;code&gt;j.o.h.n.s.mith@gmail.com&lt;/code&gt; will still arrive at your account.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Using plus addressing&lt;/strong&gt;: After your account name, you can enter the &lt;code&gt;+&lt;/code&gt; sign and whatever text you want afterwards followed by the Gmail domain. For example, mail sent to &lt;code&gt;john.smith+foo@gmail.com&lt;/code&gt; will arrive at &lt;code&gt;john.smith@gmail.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Using googlemail.com domain&lt;/strong&gt;: Any mail sent to your &lt;code&gt;&amp;lt;your-gmail-account&amp;gt;@googlemail.com&lt;/code&gt; will arrive at your &lt;code&gt;@gmail.com&lt;/code&gt; address. For example, mail sent to &lt;code&gt;john.smith@googlemail.com&lt;/code&gt; will arrive at &lt;code&gt;john.smith@gmail.com&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any of the above can be combined (e.g. &lt;code&gt;j.o.h.n.s.m.i.t.h+foo.bar@googlemail.com&lt;/code&gt; will still go to &lt;code&gt;john.smith@gmail.com&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;Interesting Consequences&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Can't match all dot versions of your Gmail address easily&lt;/strong&gt;: If you're in the habit of giving out the &lt;code&gt;.&lt;/code&gt; version of your email address to prevent spam (e.g. &lt;code&gt;j.ohn.smith@gmail.com&lt;/code&gt;), you cannot easily create a filter for all dot version of your address since these are split up into separate words (e.g. &lt;code&gt;j&lt;/code&gt; &lt;code&gt;ohn&lt;/code&gt; &lt;code&gt;smith&lt;/code&gt;). When you only use one variation of this, it's easy to create a filter and, for example, send it to spam. However, if you start using different variations (e.g. &lt;code&gt;jo.h.n.smi.th@gmail.com&lt;/code&gt;) it causes different words in the address (e.g. &lt;code&gt;jo&lt;/code&gt; &lt;code&gt;h&lt;/code&gt; &lt;code&gt;n&lt;/code&gt; &lt;code&gt;smi&lt;/code&gt; &lt;code&gt;th&lt;/code&gt;), forcing you to create a distinct condition for each variation you use.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The &lt;code&gt;+&lt;/code&gt; symbol is worse than the &lt;code&gt;""&lt;/code&gt; operator when matching plus addresses&lt;/strong&gt;: If you're trying to create a filter for a plus address, your best bet is to include the full address (e.g. &lt;code&gt;john.smith+foo@gmail.com&lt;/code&gt;). If for some reason you aren't using the full address, the &lt;code&gt;+&lt;/code&gt; operator is actually worse than the &lt;code&gt;""&lt;/code&gt; operator. For example, &lt;code&gt;john+foo&lt;/code&gt; is worse than using &lt;code&gt;"john foo"&lt;/code&gt;, since the former will match &lt;code&gt;foo@john.com&lt;/code&gt;. Keep in mind that the later is not bullet proof either, it will still match &lt;code&gt;foo@john.foo.com&lt;/code&gt;. It just guarantees that the order is correct. For clarity, you could use &lt;code&gt;"john+foo"&lt;/code&gt;, but realize that it's the same as &lt;code&gt;"john foo"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You must use negation to match all email sent to plus addresses&lt;/strong&gt;: To filter on all plus addresses (e.g. to send them to spam), you should use the query &lt;code&gt;john.smith@gmail.com -"john smith gmail com"&lt;/code&gt;. The first part of the query will match any plus addresses you have. The second will remove all those that don't have the words in the exact order. For example, &lt;code&gt;john.smith+foo@gmail.com&lt;/code&gt; will not match since it has the word &lt;code&gt;foo&lt;/code&gt; in between the other words. Note that there is one weird, and very unlikely, case where this won't work: &lt;code&gt;john.smith+john.smith.gmail.com@gmail.com&lt;/code&gt;, since it does have the words in the specified order.&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-3446567868214934385?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/3446567868214934385/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/07/how-gmail-filter-email-matching-works.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/3446567868214934385'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/3446567868214934385'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/07/how-gmail-filter-email-matching-works.html' title='How Gmail Filter Email-Matching Works'/><author><name>Senseful Solutions</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-2234635247714520939</id><published>2010-06-30T16:41:00.000-07:00</published><updated>2010-06-30T16:41:28.328-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:iPhone'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:#Tip'/><title type='text'>iPhone Tip: Use Disposable Email Addresses</title><content type='html'>Whenever you sign up for a new service, they usually ask for your email address. This is usually a problem if you hate getting spam. One solutions is to use disposable email addresses. Gmail has one way to do this using the + sign at the end (e.g. if your email is john@gmail.com, any mail sent to john+junk@gmail.com will be sent to you as well), however this has a few problems. The first problem being that someone can easily figure out your real email address, by removing everything after the plus sign. The second problem is that many websites don't let you use the plus sign in your email address.&lt;br /&gt;
&lt;br /&gt;
This is where Yahoo mail comes in. You will need a premium account, however if you are an AT&amp;amp;T customer (as most iPhone owners are in the US), you can get one for free.&lt;br /&gt;
&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="http://get.att.net/"&gt;get.att.net&lt;/a&gt;&amp;nbsp;and sign up for an @att.net account.&lt;/li&gt;
&lt;li&gt;Login at &lt;a href="http://att.net/"&gt;att.net&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Click on &lt;b&gt;mail&lt;/b&gt; on the left.&lt;/li&gt;
&lt;li&gt;Once you're at the mail interface, on the top right, select &lt;b&gt;Options&lt;/b&gt; &amp;gt; &lt;b&gt;More Options...&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Select &lt;b&gt;Disposable email address&lt;/b&gt; on the left and follow the instructions there.&lt;/li&gt;
&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-2234635247714520939?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/2234635247714520939/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/06/iphone-tip-use-disposable-email.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/2234635247714520939'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/2234635247714520939'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/06/iphone-tip-use-disposable-email.html' title='iPhone Tip: Use Disposable Email Addresses'/><author><name>Senseful Solutions</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-5820166907317374381</id><published>2010-06-30T14:18:00.000-07:00</published><updated>2010-10-22T14:41:18.340-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:iPhone'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:#How To'/><title type='text'>How to Export Tagged Songs from Shazam</title><content type='html'>I have over 700 songs tagged in Shazam, the scary thing is that this information is only stored on my phone (I rarely backup my phone). That means if anything happens to my phone, the data is gone. In fact, I've already lost my Shazam tags twice:&amp;nbsp;once when I upgraded Shazam and it would crash when opening it; and another time when my phone got bricked. Storing this much information in Shazam is a bad idea. As I've said before, I like cloud services. Had this data been stored in the cloud I wouldn't have this problem. Unfortunately, Shazam doesn't provide that option. The only way to export data in Shazam is the "share" link. Tapping the "share" link and sending an email 700 times didn't sound too appealing... so I tried finding a way to export all the data with minimal effort. If you follow these steps, you will&amp;nbsp;get the data as text (artist, title, version, and date/time) for each of your tagged songs.&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
These instructions are for Windows, however, you can probably make this work on a Mac too with simple modifications. For some of the steps, I'll include information for the Mac. This guide is for non-jailbroken phones. If you have a jailbroken phone, there are probably easier tutorials you can follow online.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;1. Backup the phone with iTunes&lt;/b&gt;&lt;br /&gt;
Backing up your iPhone is the only way to get Shazam's information on the computer. Make sure that you create an unencrypted backup.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;2. Find the *.mddata file where Shazam stores your information&lt;/b&gt;&lt;br /&gt;
First, locate the location where the backup is stored. From Apple's&amp;nbsp;&lt;a href="http://support.apple.com/kb/ht1766"&gt;iPhone and iPod touch: About backups&lt;/a&gt;:&lt;br /&gt;
&lt;blockquote&gt;
&lt;span class="Apple-style-span" style="color: #333333; font-family: 'Lucida Grande', Geneva, Arial, Verdana, sans-serif; font-size: 12px; line-height: 18px;"&gt;&lt;strong style="font-size: 1em; font-style: normal; font-weight: bold;"&gt;Mac&lt;/strong&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="color: #333333; font-family: 'Lucida Grande', Geneva, Arial, Verdana, sans-serif; font-size: 12px; line-height: 18px;"&gt;:&lt;/span&gt;&lt;span class="Apple-style-span" style="color: #333333; font-family: 'Lucida Grande', Geneva, Arial, Verdana, sans-serif; font-size: 12px; line-height: 18px;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="Apple-style-span" style="color: #333333; font-family: 'Lucida Grande', Geneva, Arial, Verdana, sans-serif; font-size: 12px; line-height: 18px;"&gt;&lt;tt&gt;~/Library/Application Support/MobileSync/Backup/&lt;/tt&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;span class="Apple-style-span" style="color: #333333; font-family: 'Lucida Grande', Geneva, Arial, Verdana, sans-serif; font-size: 12px; line-height: 18px;"&gt;&lt;strong style="font-size: 1em; font-style: normal; font-weight: bold;"&gt;Windows XP:&lt;/strong&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="color: #333333; font-family: 'Lucida Grande', Geneva, Arial, Verdana, sans-serif; font-size: 12px; line-height: 18px;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="Apple-style-span" style="color: #333333; font-family: 'Lucida Grande', Geneva, Arial, Verdana, sans-serif; font-size: 12px; line-height: 18px;"&gt;&lt;tt&gt;\Documents and Settings\(username)\Application Data\Apple Computer\MobileSync\Backup\&lt;/tt&gt;&lt;/span&gt;&amp;nbsp;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;span class="Apple-style-span" style="color: #333333; font-family: 'Lucida Grande', Geneva, Arial, Verdana, sans-serif; font-size: 12px; line-height: 18px;"&gt;&lt;strong style="font-size: 1em; font-style: normal; font-weight: bold;"&gt;Windows Vista and Windows 7:&lt;/strong&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="color: #333333; font-family: 'Lucida Grande', Geneva, Arial, Verdana, sans-serif; font-size: 12px; line-height: 18px;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="Apple-style-span" style="color: #333333; font-family: 'Lucida Grande', Geneva, Arial, Verdana, sans-serif; font-size: 12px; line-height: 18px;"&gt;&lt;tt&gt;\Users\(username)\AppData\Roaming\Apple Computer\MobileSync\Backup\&lt;/tt&gt;&lt;/span&gt;&lt;/blockquote&gt;
Inside of that folder you should see a file with lot's of numbers and letters. That's your backup folder, open it up. You will need this path in the next step.&lt;br /&gt;
&lt;br /&gt;
Now you need to open up a command prompt to that folder:&lt;br /&gt;
&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;&lt;b&gt;Start &lt;/b&gt;&amp;gt; &lt;b&gt;Run&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Launch: &lt;b&gt;cmd&amp;nbsp;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Navigate to the location of your backup (from the previous step) using the &lt;b&gt;cd&lt;/b&gt; command. For example:&lt;br /&gt;&lt;code&gt;cd&amp;nbsp;C:\Documents and Settings\[user]\Application Data\Apple Computer\MobileSync\Backup\[backup-id]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Now search for &lt;b&gt;Shazam&lt;/b&gt; using &lt;b&gt;findstr&lt;/b&gt;:&lt;br /&gt;&lt;code&gt;findstr "shazam" *.mdinfo&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;You should see it output something such with white bold letters in front with the unique ID of shazam. If you see more than one file, then you can open up the folder and examine the size of the corresponding *.mddata file and see which one actually has data in it. If this is too complicated, you can use a search query such as &lt;code&gt;findstr "madonna" *.mddata&lt;/code&gt; where "madonna" is a single word which should only be in Shazam (e.g. you don't have notes that contain "madonna"), otherwise you will get more than one result again.&lt;/li&gt;
&lt;li&gt;Now that you know the name of Shazam's unique ID, you should also see a corresponding *.mddata file which has the same name except that it ends with &lt;b&gt;.mddata&lt;/b&gt;&amp;nbsp;instead of &lt;b&gt;.mdinfo&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;
&lt;b&gt;3. Open the *.mddata in SQLite&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
&lt;ol&gt;
&lt;li&gt;Download &lt;a href="http://sqlitebrowser.sourceforge.net/"&gt;SQLite Database Explorer&lt;/a&gt;. You can get a binary from the&amp;nbsp;&lt;b&gt;SOURCEFORGE PROJECT PAGES&lt;/b&gt; link on the top right. I am using the beta version (sqlitebrowser_200_b1_win.zip). Note that there is also a Mac version available if you select &lt;b&gt;View All Files&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;Launch SQLite Database Explorer and open the &lt;b&gt;*.mddata&lt;/b&gt; from step 2.&lt;/li&gt;
&lt;li&gt;Go to the &lt;b&gt;Browse Data&lt;/b&gt; tab on top.&lt;/li&gt;
&lt;li&gt;Where it says &lt;b&gt;Table&lt;/b&gt;, select &lt;b&gt;recresult&lt;/b&gt;. You should see all of your songs here.&lt;/li&gt;
&lt;li&gt;Go to &lt;b&gt;File&lt;/b&gt; &amp;gt; &lt;b&gt;Export&lt;/b&gt; &amp;gt; &lt;b&gt;Table as CSV file&lt;/b&gt; choose &lt;b&gt;recresult&lt;/b&gt; then &lt;b&gt;Export&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;In the save dialog, be sure to include the &lt;code&gt;.csv&lt;/code&gt; at the end of the file's name (e.g. &lt;code&gt;Shazam Songs.csv&lt;/code&gt;).
&lt;li&gt;Use your favorite spreadsheet application (e.g. Excel) to open the file and extract only the data you need. The most important columns are &lt;b&gt;previewArtist&lt;/b&gt;, &lt;b&gt;previewTitle&lt;/b&gt;, and &lt;b&gt;previewSubtitle&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;
&lt;b&gt;4. Getting the Date/Time for each song (Optional)&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
For me I like to also know the date/time a song was tagged. If you noticed, this information is stored as a number in the &lt;b&gt;date&lt;/b&gt; field. I'm going to use Excel to convert this to a date time I can understand. The date is stored as the amount of seconds since 1/1/2001 UTC.&lt;/div&gt;
&lt;div&gt;
&lt;ol&gt;
&lt;li&gt;Create a new column immediately to the right of &lt;b&gt;date&lt;/b&gt; (i.e. it should be column C).&lt;/li&gt;
&lt;li&gt;Enter the formula: &lt;code&gt;=B2/60/60/24&lt;/code&gt; for the second row. This converts the seconds into the amount of days they represent. Extend this formula to the rest of the cells downwards.&lt;/li&gt;
&lt;li&gt;Add another column to this column's right (i.e. new one should be D), Enter the formula:&lt;br /&gt;&lt;code&gt;=DATE(YEAR(C19)+101, MONTH(C19), DAY(C19)+2) + MOD(C19, 1) - TIME(7, 0, 0)&lt;/code&gt;&lt;br /&gt;The important part here is the time zone offset from UTC (e.g. &lt;code&gt;TIME(7, 0, 0)&lt;/code&gt;, MST = -7 hours), you should change this to whatever time zone you want. Change the column's format to show the time. You can verify that this is the correct formula by comparing it to your iPhone's tagged song's date. Extend this formula downwards for the rest of the cells.&lt;/li&gt;
&lt;li&gt;You can now copy/paste this column along with the other three important columns and save them anywhere you like.&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;
That's it. I hope it helps.&lt;br /&gt;
&lt;br /&gt;
Note: if you have a really old backup and the files are stored as *.mdbackup, simply follow the above steps. The only thing you need to change is before you can open up the file in SQLite Browser, you need to modify the file a bit. Open it up in a hex editor such as &lt;a href="http://www.softpedia.com/get/Programming/File-Editors/HEdit.shtml"&gt;HEdit&lt;/a&gt;. Create a backup of the file (just in case something goes wrong), then remove the first few lines, so that it starts with &lt;code&gt;SQLite format 3&lt;/code&gt;, and then save it and you should be able to open it up in SQLite Browser.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Personally I'm saving the data in Evernote since it is much better suited to store this data. If you do end up pasting this information into Evernote, note that it will preserve the tab characters, you just won't see them. I'll probably end up processing the song names sometime in the future since it isn't terribly useful as a single note. At least I now have access to data that used to be locked up in Shazam though.&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
If you want more information about iTunes backup files, I would recommend reading &lt;a href="http://www.appleexaminer.com/iPhoneiPad/iPhoneBackup/iPhoneBackup.html"&gt;this article&lt;/a&gt;.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-5820166907317374381?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/5820166907317374381/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/06/how-to-export-tagged-songs-from-shazam.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/5820166907317374381'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/5820166907317374381'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/06/how-to-export-tagged-songs-from-shazam.html' title='How to Export Tagged Songs from Shazam'/><author><name>Senseful Solutions</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-539885420045785247</id><published>2010-06-26T19:27:00.000-07:00</published><updated>2010-06-26T19:33:31.006-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:#Tip'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:iPad'/><title type='text'>iPad Tip: Quickly reconnect to Wi-Fi via lock/unlock</title><content type='html'>For some reason I always lose my Wi-Fi connection every 30 minutes to an hour, it's probably a router issue I have. What's more annoying is that sometimes it takes up to a minute until my iPad reconnects to my Wi-Fi (even though it could have actually reconnected immediately after the signal was lost). The way I used to solve this problem is to exit out of whatever app I'm running, go to Settings, tap Wi-Fi, and then manually press on the name of my router to force it to connect immediately. Unfortunately even with this method it takes around 10 seconds to connect, not to mention that it requires you to quit your current app.&lt;br /&gt;
&lt;br /&gt;
I found that by far the quickest way to reconnect to Wi-Fi after you lost your signal is to simply &lt;b&gt;lock the iPad&lt;/b&gt;, and then &lt;b&gt;press the lock button again to see the "slide to unlock" screen&lt;/b&gt;. On the top left where it shows the Wi-Fi signal, you should &lt;b&gt;see it reconnect within a second or two&lt;/b&gt;. Now you can &lt;b&gt;unlock the screen&lt;/b&gt; and you'll have your connection back, all without ever leaving your app!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-539885420045785247?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/539885420045785247/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/06/ipad-tip-quickly-reconnect-to-wi-fi-via.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/539885420045785247'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/539885420045785247'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/06/ipad-tip-quickly-reconnect-to-wi-fi-via.html' title='iPad Tip: Quickly reconnect to Wi-Fi via lock/unlock'/><author><name>Senseful Solutions</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-8347900566465532092</id><published>2010-03-28T15:28:00.000-07:00</published><updated>2010-12-31T16:42:58.755-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:#How To'/><title type='text'>Launching Quix in Chrome via Address Bar (Installing it as a Search Engine)</title><content type='html'>&lt;p&gt;I just found out about &lt;a href="http://quixapp.com/"&gt;Quix &lt;/a&gt;recently, and it reminds me of &lt;a href="http://yubnub.org/"&gt;Yubnub&lt;/a&gt;. The thing I like about Quix is that you aren't competing with commands in a single namespace with every other developer as you do with Yubnub. You can define what each command does for you. So for example, &lt;a href="http://yubnub.org/kernel/man?args=gm"&gt;gm&lt;/a&gt; in Yubnub is defined as a Google maps search. You cannot define gm to mean anything else in Yubnub (e.g. Gmail). With Quix, however, you can.&lt;/p&gt;
&lt;a name='more'&gt;&lt;/a&gt;

&lt;p&gt;My browser of choice is Chrome, so I wanted to integrate Quix with it.
Unfortunately, the &lt;a href="http://quixapp.com/browsers/chrome/"&gt;recommended installation method&lt;/a&gt; requires an extension for a keyboard shortcut to work. This got me thinking... why not utilize the extensible address bar that is already built into Chrome?&lt;/p&gt;

&lt;p&gt;
I started messing around with the search engine feature of Chrome. I tried a simple Hello World application:

&lt;pre class="javascript" name="code"&gt;javascript:(function(){alert("%s")})()&lt;/pre&gt;

I quickly found out that the &lt;a href="http://code.google.com/p/chromium/issues/detail?id=2238"&gt;open curly brace character is not allowed&lt;/a&gt;. However, it does work if you encode it as %7B, so this now works:

&lt;pre class="javascript" name="code"&gt;javascript:(function()%7Balert("%s")})()&lt;/pre&gt;
&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Note:&lt;/b&gt; It seems like this &lt;a href="http://code.google.com/p/chromium/issues/detail?id=2238#c9"&gt;issue is about to get fixed&lt;/a&gt;. If you notice the OK button disabled when you try to save the search engine in later steps, you will need to replace all opening curly braces with %7B.&lt;/p&gt;

&lt;p&gt;
Now that I figured out that using this feature would be possible, I wanted to make it so that instead of popping up an input dialog, it uses whatever I type into the address bar. I used the &lt;a href="http://jsbeautifier.org/"&gt;JavaScript unpacker&lt;/a&gt; to format the bookmarklet's source code so that I can see what's going on. If you look at it yourself, you will notice this line:

&lt;pre class="javascript" name="code"&gt;  var c = window.prompt('Quix: Type `help` for a list of commands:');&lt;/pre&gt;

... if you simply replace it with this line:

&lt;pre class="javascript" name="code"&gt;  var c = "%s";&lt;/pre&gt;

... it will use whatever you type in the address bar and not popup a dialog. Here's the finished code:

&lt;pre class="javascript" name="code"&gt;javascript:Quix();function Quix()%7Bvar e=encodeURIComponent;var t=window.getSelection?window.getSelection():(document.getSelection?document.getSelection():(document.selection?document.selection.createRange().text:''));var c="%s";if(t!='')%7Bif(c)%7Bc+=' '+t}else%7Bc=''+t}}if(c)%7Bvar u='http://quixapp.com/go/?c='+e(c)+'&amp;amp;t='+(document.title?e(document.title):'')+'&amp;amp;s='+'&amp;amp;v=080'+'&amp;amp;u='+(document.location?e(document.location):'');d=''+document.location;if(d.substr(0,4)!='http')%7Bwindow.location=u+'&amp;amp;mode=direct'}else%7Bheads=document.getElementsByTagName('head');if(c.substring(0,1)==' ')%7Bvar w=window.open(u+'&amp;amp;mode=direct');w.focus()}else if(heads.length==0)%7Bwindow.location=u+'&amp;amp;mode=direct'}else%7Bq=document.getElementById('quix');if(q)%7Bq.parentNode.removeChild(q)}sc=document.createElement('script');sc.src=u;sc.id='quix';sc.type='text/javascript';void(heads[0].appendChild(sc))}}}}
&lt;/pre&gt;
&lt;/p&gt;

&lt;p&gt;
&lt;b&gt;Update:&lt;/b&gt; If you want to use it with your own commands, use this version:

&lt;pre class="javascript" name="code"&gt;javascript:Quix();function Quix()%7Bvar e=encodeURIComponent;var t=window.getSelection?window.getSelection():(document.getSelection?document.getSelection():(document.selection?document.selection.createRange().text:''));var c="%s";if(t!='')%7Bif(c)%7Bc+=' '+t}else%7Bc=''+t}}if(c)%7Bvar u='http://quixapp.com/go/?c='+e(c)+'&amp;amp;t='+(document.title?e(document.title):'')+'&amp;amp;s=YOUR_URL_GOES_HERE'+'&amp;amp;v=080'+'&amp;amp;u='+(document.location?e(document.location):'');d=''+document.location;if(d.substr(0,4)!='http')%7Bwindow.location=u+'&amp;amp;mode=direct'}else%7Bheads=document.getElementsByTagName('head');if(c.substring(0,1)==' ')%7Bvar w=window.open(u+'&amp;amp;mode=direct');w.focus()}else if(heads.length==0)%7Bwindow.location=u+'&amp;amp;mode=direct'}else%7Bq=document.getElementById('quix');if(q)%7Bq.parentNode.removeChild(q)}sc=document.createElement('script');sc.src=u;sc.id='quix';sc.type='text/javascript';void(heads[0].appendChild(sc))}}}}
&lt;/pre&gt;
... just replace "YOUR_URL_GOES_HERE". (Thanks open0source)
&lt;/p&gt;

&lt;p&gt;&lt;i&gt;Note: If you want to manually create your own version of the bookmarklet, after replacing the single line, the next step is to take the code and put it in a &lt;a href="http://dean.edwards.name/packer/"&gt;javascript packer&lt;/a&gt;. Next run a simple replace on "{" to "%7B". Then use the code you just created in the following steps for installation.&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;
Now that the code is ready, all that's left is to install it:&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;In Chrome, go to Tools &amp;gt; Options&lt;/li&gt;
&lt;li&gt;In the Basics tab, click Manage next to Default search&lt;/li&gt;
&lt;li&gt;Press Add...&lt;/li&gt;
&lt;li&gt;For the name and keyword, you can enter "quix"&lt;/li&gt;
&lt;li&gt;For the URL, paste the bookmarklet code from above&lt;/li&gt;
&lt;li&gt;Press OK &lt;br /&gt;
Note: if the OK button is disabled, make sure there are no open curly braces in the URL field. Also ensure that the name and keyword fields are filled in.&lt;/li&gt;
&lt;li&gt;Press Make Default in the Search Engines window&lt;br /&gt;
Making it the default search engine will make it so you don't need to type a keyword before using this search engine.&lt;/li&gt;
&lt;li&gt;Press Close in the Search Engines window&lt;/li&gt;
&lt;li&gt;Press Close in the Google Chrome Options window&lt;/li&gt;
&lt;li&gt;To try it out, press Ctrl+K&lt;/li&gt;
&lt;li&gt;Type a query, e.g.: w hello world&lt;/li&gt;
&lt;/ol&gt;
If everything was installed correctly, this should have taken you to Wikipedia's Hello World article.
&lt;/p&gt;

&lt;p&gt;To easily get to the address bar and use Quix, use Ctrl+K which will insert a leading "?" in the address bar. This leading "?" forces the address bar to use the Quix search engine you added (without the leading "?", if you type something that looks like a URL, it will navigate to that URL). However, since commands in Quix do not look like URLs, any command you type into the address bar will be resolved correctly even without the leading "?". Thus, you can also use the Alt+D shortcut to focus on the address bar and start typing immediately.&lt;/p&gt;

&lt;p&gt;I hope you found this post useful. I still haven't started customizing Quix, but so far it looks promising.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-8347900566465532092?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/8347900566465532092/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/03/launching-quix-in-chrome-via-address.html#comment-form' title='13 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/8347900566465532092'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/8347900566465532092'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/03/launching-quix-in-chrome-via-address.html' title='Launching Quix in Chrome via Address Bar (Installing it as a Search Engine)'/><author><name>Senseful Solutions</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>13</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-6965875835295477794</id><published>2010-03-18T19:53:00.000-07:00</published><updated>2010-03-18T22:24:17.134-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:#Troubleshooting'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:Portable Apps'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:Live Mesh'/><title type='text'>Portable Apps not syncing to Live Mesh</title><content type='html'>The PortableApps folder does not get synchronized with Live Mesh because it is marked as a System file. Live mesh does not synchronize system or hidden files.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;To remove the System attribute from the Portable App's folders:&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;Start &amp;gt; Run&lt;/li&gt;
&lt;li&gt;Type &lt;b&gt;cmd&lt;/b&gt;&amp;nbsp;and press OK.&lt;/li&gt;
&lt;li&gt;Navigate to your portable apps directory (i.e. the folder that contains both &lt;b&gt;Documents&lt;/b&gt;&amp;nbsp;and &lt;b&gt;PortableApps&lt;/b&gt;) (e.g. &lt;b&gt;CD d:&lt;/b&gt;)&lt;/li&gt;
&lt;li&gt;Type the following commands to remove the system attribute:&lt;ol&gt;&lt;li&gt;attrib -S Documents&lt;/li&gt;
&lt;li&gt;attrib -S PortableApps&lt;/li&gt;
&lt;li&gt;attrib -S Documents\Music&lt;/li&gt;
&lt;li&gt;attrib -S Documents\Pictures&lt;/li&gt;
&lt;li&gt;attrib -S Documents\Videos&lt;/li&gt;
&lt;/ol&gt;&lt;/li&gt;
&lt;li&gt;You are done. The folders should now sync automatically within the next few minutes.&lt;/li&gt;
&lt;/ol&gt;
&lt;b&gt;Note:&lt;/b&gt; This will get rid of the folder icons that the folders used to have, however, I think it's a small price to pay for having the folders sync to Live Mesh.&lt;br /&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
For more information about the attrib command, type &lt;b&gt;&lt;span class="Apple-style-span" style="background-color: #cfe2f3;"&gt;attrib /?&lt;/span&gt;&lt;/b&gt;&amp;nbsp;into the command prompt.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-6965875835295477794?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/6965875835295477794/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/03/portable-apps-not-syncing-to-live-mesh.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/6965875835295477794'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/6965875835295477794'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/03/portable-apps-not-syncing-to-live-mesh.html' title='Portable Apps not syncing to Live Mesh'/><author><name>Senseful Solutions</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-8332415630581545377</id><published>2010-03-18T00:35:00.000-07:00</published><updated>2010-07-20T16:13:46.149-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:#Tip'/><category scheme='http://www.blogger.com/atom/ns#' term='tag:Evernote'/><title type='text'>Finding all PDF documents in Evernote</title><content type='html'>Here's a quick Evernote tip:&lt;br /&gt;
&lt;br /&gt;
To find all notes that contain a PDF file within them, use the search query: &lt;code&gt;resource:application/pdf&lt;/code&gt;.&lt;br /&gt;
&lt;br /&gt;
This can be useful if you want to, for example, create a saved search of all your product manuals. Let's say you tag all your products with the tag called&amp;nbsp;&lt;code&gt;Products&lt;/code&gt;,&amp;nbsp;and that for many of them you find a digital version of their manual online (which is virtually always in PDF format) and attach it to the note. You can create a saved search consisting of the query: &lt;code&gt;Products resource:application/pdf&lt;/code&gt; and call it &lt;b&gt;Product Manuals&lt;/b&gt;.&lt;br /&gt;
&lt;br /&gt;
For more information about the resource filter, see the &lt;a href="http://www.evernote.com/about/developer/api/evernote-api.htm#_Toc200272599"&gt;Evernote API documentation&lt;/a&gt;. Application/pdf is the mime type for PDFs. For a list of other mime types, see this &lt;a href="http://www.w3schools.com/media/media_mimeref.asp"&gt;excellent reference page&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-8332415630581545377?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/8332415630581545377/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/03/finding-all-pdf-documents-in-evernote.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/8332415630581545377'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/8332415630581545377'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/03/finding-all-pdf-documents-in-evernote.html' title='Finding all PDF documents in Evernote'/><author><name>Senseful Solutions</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-3596367725658938229</id><published>2010-01-29T02:03:00.000-08:00</published><updated>2012-01-23T19:44:56.959-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:#Bookmarklet'/><title type='text'>Show File Type Bookmarklet</title><content type='html'>&lt;a name="more"&gt;&lt;/a&gt;
&lt;p&gt;
This bookmarklet will show you the file type of each link on a page by displaying an icon next to each one.
&lt;/p&gt;
&lt;p&gt;
&lt;a class="bookmarklet" href="javascript:/*http://bit.ly/dmv1O6#v0.1*/(function(){(function(u,c){var%20h=document.getElementsByTagName('head')[0];var%20s=document.createElement('script');s.src=u;var%20d=false;s.onload=s.onreadystatechange=function(){if(!d&amp;&amp;(!this.readyState||this.readyState=='loaded'||this.readyState=='complete')){d=true;jQuery.noConflict();c(jQuery);s.onload=s.onreadystatechange=null;h.removeChild(s);}};h.appendChild(s);})('http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js',function($){var%20i={};var%20u='http://s6.tinypic.com/';i['7z']=u+'1051elf_th.jpg';i['ai']=u+'2qb63d4_th.jpg';i['aiff']=u+'hs53l5_th.jpg';i['asc']=u+'24dk8hx_th.jpg';i['audio']=u+'eapdkx_th.jpg';i['bin']=u+'on2u1_th.jpg';i['bz2']=u+'w2osqs_th.jpg';i['c']=u+'28w31u_th.jpg';i['cfc']=u+'103h8cn_th.jpg';i['cfm']=u+'wtt0gg_th.jpg';i['chm']=u+'4vhi7k_th.jpg';i['class']=u+'mu93lt_th.jpg';i['conf']=u+'2m4q0zm_th.jpg';i['cpp']=u+'14kjl8z_th.jpg';i['cs']=u+'2dhz0xu_th.jpg';i['css']=u+'al29f8_th.jpg';i['csv']=u+'2vcsvgi_th.jpg';i['deb']=u+'30cbt3s_th.jpg';i['divx']=u+'35me2v8_th.jpg';i['doc']=u+'dls2yx_th.jpg';i['dot']=u+'2e4esdv_th.jpg';i['eml']=u+'25qxkjk_th.jpg';i['enc']=u+'fvcop2_th.jpg';i['file']=u+'f4n1fs_th.jpg';i['gif']=u+'121delc_th.jpg';i['gz']=u+'16jec6t_th.jpg';i['hlp']=u+'112eo8n_th.jpg';i['htm']=u+'nf5zzd_th.jpg';i['html']=u+'2v3mo_th.jpg';i['image']=u+'htwob5_th.jpg';i['iso']=u+'14uawz4_th.jpg';i['jar']=u+'11l60hv_th.jpg';i['java']=u+'27xl3jq_th.jpg';i['jpeg']=u+'2eans6e_th.jpg';i['jpg']=u+'21mxc0z_th.jpg';i['js']=u+'2ryga6q_th.jpg';i['lua']=u+'2jg0zv7_th.jpg';i['m']=u+'2ntkd1t_th.jpg';i['mm']=u+'14bqq84_th.jpg';i['mov']=u+'5djz3s_th.jpg';i['mp3']=u+'uv9qf_th.jpg';i['mpg']=u+'v7fx3b_th.jpg';i['odc']=u+'2rrmgt4_th.jpg';i['odf']=u+'2qwixpi_th.jpg';i['odg']=u+'15q7lma_th.jpg';i['odi']=u+'2d5dmv_th.jpg';i['odp']=u+'33o5chk_th.jpg';i['ods']=u+'347wkxz_th.jpg';i['odt']=u+'x5380p_th.jpg';i['ogg']=u+'b96o7c_th.jpg';i['pdf']=u+'29cm4oj_th.jpg';i['pgp']=u+'cqrh4_th.jpg';i['php']=u+'9hqbmx_th.jpg';i['pl']=u+'2vs5fkp_th.jpg';i['png']=u+'2dt19aq_th.jpg';i['ppt']=u+'15flic2_th.jpg';i['ps']=u+'23m2beg_th.jpg';i['py']=u+'119tjyx_th.jpg';i['ram']=u+'2ns5cuh_th.jpg';i['rar']=u+'3538ow8_th.jpg';i['rb']=u+'2v9ro88_th.jpg';i['rm']=u+'2eukk5x_th.jpg';i['rpm']=u+'2larosw_th.jpg';i['rtf']=u+'30u6zo2_th.jpg';i['sig']=u+'zlbxn5_th.jpg';i['sql']=u+'2hfujyq_th.jpg';i['swf']=u+'n2hhs6_th.jpg';i['sxc']=u+'2rgf4ft_th.jpg';i['sxd']=u+'eperfr_th.jpg';i['sxi']=u+'vdhcep_th.jpg';i['sxw']=u+'dh4x0_th.jpg';i['tar']=u+'20s92x5_th.jpg';i['tex']=u+'29gn76f_th.jpg';i['tgz']=u+'zwdumh_th.jpg';i['txt']=u+'112g900_th.jpg';i['vcf']=u+'2akie0y_th.jpg';i['video']=u+'fu8ep0_th.jpg';i['vsd']=u+'286qy2r_th.jpg';i['wav']=u+'2rmab60_th.jpg';i['wma']=u+'35klqth_th.jpg';i['wmv']=u+'1zmm92g_th.jpg';i['xls']=u+'14ac3k8_th.jpg';i['xml']=u+'13zpk4h_th.jpg';i['xpi']=u+'25g53qp_th.jpg';i['xvid']=u+'2qnuume_th.jpg';i['zip']=u+'6zaqs5_th.jpg';var%20ignoreExts=['htm','html'];var%20regex=/^(?:[^?#\r\n]+[^\/]\/|\/)?[^\/?%23\r\n:]+\.([\w\-+=~,!%@&amp;]+)(?=$|\?|%23)/;$('a').each(function(){var%20link=$(this).attr('href');if(!link)return;var%20match=regex.exec(link);if(match!=null){var%20ext=match[1].toLowerCase();var%20extU=ext.toUpperCase();if(ignoreExts.indexOf(ext)!=-1)return;var%20imgUrl=i[ext];if(imgUrl)$(this).append('%3Cimg%20alt=\''+extU+'\'%20src=\''+imgUrl+'\'%20title=\''+extU+'\'%20/%3E');else%20$(this).append('%20[**'+extU+'**]');}});})})();"&gt;Show File Type&lt;/a&gt;&lt;br /&gt;(version 0.1)
&lt;/p&gt;
&lt;a name='more'&gt;&lt;/a&gt;
&lt;h2&gt;Try it out&lt;/h2&gt;

&lt;p&gt; You can try it out right now on this page. Simply click it now, or add it to your browser and then click it. Watch what happens to these sample links:&lt;/p&gt;

&lt;p&gt;
  &lt;a href="http://example.com/test.doc"&gt;Word Document&lt;/a&gt;&lt;br /&gt;
  &lt;a href="http://example.com/test.jpg"&gt;JPG Document&lt;/a&gt;&lt;br /&gt;
  &lt;a href="http://example.com/test.pdf"&gt;Acrobat Document&lt;/a&gt;&lt;br /&gt;
  &lt;a href="http://example.com/test.swf"&gt;SWF Document&lt;/a&gt;&lt;br /&gt;
  &lt;a href="http://example.com/test.txt"&gt;Text Document&lt;/a&gt;&lt;br /&gt;
  &lt;a href="http://example.com/test.xls"&gt;Excel Document&lt;/a&gt;&lt;br /&gt;
  &lt;a href="http://example.com/test.xml"&gt;XML Document&lt;/a&gt;&lt;br /&gt;
  &lt;a href="http://example.com/test.zip"&gt;Zip Document&lt;/a&gt;&lt;br /&gt;
  &lt;a href="http://example.com/test.unknown"&gt;Unknown Document&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;For common file types, it will use an image from the &lt;a href="http://www.splitbrain.org/projects/file_icons"&gt;splitbrain.org file icons&lt;/a&gt; set. Otherwise, it will append the file extension to the end of the link. Common web page extensions (e.g. htm, html) are ignored. A link to this page and the version number are the first things embedded inside the bookmarklet.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-3596367725658938229?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/3596367725658938229/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/01/show-file-type-bookmarklet.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/3596367725658938229'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/3596367725658938229'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/01/show-file-type-bookmarklet.html' title='Show File Type Bookmarklet'/><author><name>Senseful Solutions</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-8458912507437408287</id><published>2010-01-14T12:40:00.000-08:00</published><updated>2012-01-16T00:53:21.253-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:#Utility'/><title type='text'>Color Generator (Hue, Saturation, Brightness variations)</title><content type='html'>Here's an online tool for generating colors. It modifies a color's hue, saturation, and brightness (HSB or HSV) to generate new colors. You can click on any color swatch to set that as the color used for generating. It uses &lt;a href="http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript"&gt;Michael Jackson's HSB JavaScript functions&lt;/a&gt;.&lt;a name='more'&gt;&lt;/a&gt;
&lt;h4&gt;Options:&lt;/h4&gt;
&lt;table class="data-input"&gt;
  &lt;tr&gt;&lt;td&gt;Red:&lt;/td&gt;&lt;td&gt;&lt;input id="red" type="text" value="255" size="5" /&gt;&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;Green:&lt;/td&gt;&lt;td&gt;&lt;input id="green" type="text" value="0" size="5" /&gt;&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;Blue:&lt;/td&gt;&lt;td&gt;&lt;input id="blue" type="text" value="0" size="5" /&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;# of colors:&lt;/td&gt;&lt;td&gt;&lt;input id="colors" type="text" value="12" size="5" /&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt; &lt;/td&gt;&lt;td&gt;&lt;input id="useHex" type="checkbox" /&gt;&lt;label for="useHex"&gt;Output as Hex&lt;/label&gt;&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt; &lt;/td&gt;&lt;td&gt;&lt;button onclick="generateColors()"&gt;Generate Colors&lt;/button&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;

&lt;h4&gt;Samples:&lt;/h4&gt;
&lt;div id="sample"&gt;
&lt;div style="background-color: rgb(246, 150, 121)" onclick="runSample(246, 150, 121)"&gt;&lt;/div&gt;
&lt;div style="background-color: rgb(242, 108, 79)" onclick="runSample(242, 108, 79)"&gt;&lt;/div&gt;
&lt;div style="background-color: rgb(237, 28, 36)" onclick="runSample(237, 28, 36)"&gt;&lt;/div&gt;
&lt;div style="background-color: rgb(157, 10, 14)" onclick="runSample(157, 10, 14)"&gt;&lt;/div&gt;
&lt;div style="background-color: rgb(121, 0, 0)" onclick="runSample(121, 0, 0)"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div style="clear:both"&gt;&lt;/div&gt;

&lt;h4&gt;Results:&lt;/h4&gt;
&lt;div class="tableholder"&gt;Hue:
&lt;table id="results" class="results"&gt;
  &lt;tr&gt;&lt;th width="16px"&gt;&lt;/th&gt;&lt;th&gt;RGB&lt;/th&gt;&lt;!--th&gt;HSB&lt;/th--&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class="tableholder"&gt;Saturation:
&lt;table id="results-sat" class="results"&gt;
  &lt;tr&gt;&lt;th width="16px"&gt;&lt;/th&gt;&lt;th&gt;RGB&lt;/th&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class="tableholder"&gt;Brightness:
&lt;table id="results-bri" class="results"&gt;
  &lt;tr&gt;&lt;th width="16px"&gt;&lt;/th&gt;&lt;th&gt;RGB&lt;/th&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div style="clear:both"&gt;&lt;/div&gt;
&lt;div id="version-history"&gt;
&lt;ul&gt;
    &lt;li&gt;1/16/12:
    &lt;ul&gt;&lt;li&gt;Hex output added.&lt;/li&gt;
        &lt;li&gt;Improved code and layout.&lt;/li&gt;
        &lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;

&lt;!--********************* Custom CSS  *********************--&gt;
&lt;style type="text/css"&gt;
div.tableholder {
  float:left;
}
table.results {
  margin-right: 20px;
 border-width: 0px;
 border-spacing: ;
 border-style: outset;
 border-color: gray;
 border-collapse: collapse;
 background-color: white;
}
table.results th {
 border-width: 1px;
 padding: 4px;
 border-style: inset;
 border-color: gray;
 background-color: white;
 -moz-border-radius: ;
}
table.results td {
 border-width: 1px;
 padding: 4px;
 border-style: inset;
 border-color: gray;
 background-color: white;
}
#sample div {
  font-weight: bold;
  width: 24px;
  height: 24px;
  float: left;
  margin: 5px;
}
&lt;/style&gt;

&lt;!--****************** Custom JavaScript  *****************--&gt;
&lt;script type="text/javascript"&gt;
$(function() {
    generateColors();
    $('#useHex').click(function() {
        generateColors();
        $('div.tableholder &gt; table tr:first-child &gt; th:nth-child(2)').text($(this).prop('checked') ? 'Hex' : 'RGB');
    });
});

function generateColors() {
    // clear results
    $('#results tr').not(":first-child").remove();
    $('#results-sat tr').not(":first-child").remove();
    $('#results-bri tr').not(":first-child").remove();

    var r = $('#red').val();
    var g = $('#green').val();
    var b = $('#blue').val();
    var rgb = [r, g, b];
    var colorCount = $('#colors').val();
    var colors;

    // generate Hue table
    colors = sfu.clr.getVaryingHues(rgb, colorCount);
    for (var i = 0; i &lt; colors.length; i++) {
        createSwatch(colors[i], $('#results'));
    }

    // generate Saturation table
    colors = sfu.clr.getVaryingSaturations(rgb, colorCount);
    for (var i = 0; i &lt; colors.length; i++) {
        createSwatch(colors[i], $('#results-sat'));
    }

    // generate Brightness table
    colors = sfu.clr.getVaryingBrightnesses(rgb, colorCount);
    for (var i = 0; i &lt; colors.length; i++) {
        createSwatch(colors[i], $('#results-bri'));
    }
}

function runSample(r, g, b) {
    $('#red').val(r);
    $('#green').val(g);
    $('#blue').val(b);
    generateColors();
}

function createSwatch(rgb, $parentTable) {
    var sRgb = sfu.clr.rgbToRgbStr(rgb);
    var $tr = $('&lt;tr&gt;');
    var $td = $('&lt;td&gt;');
    var td = $td.get(0);
    td.style.backgroundColor = "rgb(" + sRgb + ")";
    $td.click(function() {
        runSample(rgb[0], rgb[1], rgb[2]);
    });
    $tr.append($td);

    if ($('#useHex').prop('checked')) sRgb = sfu.clr.rgbToHex(rgb);
    $td = $('&lt;td&gt;').text(sRgb);
    $tr.append($td);

    $parentTable.append($tr);
}
&lt;/script&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-8458912507437408287?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/8458912507437408287/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/01/color-generator-hue-saturation.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/8458912507437408287'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/8458912507437408287'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/01/color-generator-hue-saturation.html' title='Color Generator (Hue, Saturation, Brightness variations)'/><author><name>Senseful Solutions</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-324324512885676480</id><published>2010-01-10T23:20:00.000-08:00</published><updated>2010-03-18T22:19:57.438-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:#Utility'/><title type='text'>iPhone Offline Web App Creator (Data URI Base64 Encoder)</title><content type='html'>Here's an online tool to quickly create iPhone Web Apps that are stored entirely in the URL so that they can be run offline. It encodes the entire web app in a base 64 encoded &lt;a href="http://en.wikipedia.org/wiki/Data_URI_scheme"&gt;data URI&lt;/a&gt;. Be warned, it might not work for very large Web Apps, it all depends on the iPhone's URL limit.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;

&lt;!-- *********************************************************************** --&gt;
&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"&gt;&lt;/script&gt;
&lt;script type="text/javascript"&gt;
function runEncode() {
  $('#div-results').css('display', 'inline');
  var result = $('#txt-prefix').val() + convertToBase64($('#txt-input').val());
  $('#a-result').attr('href', result);
  var name = $('#txt-name').val();
  if (name)
    result = '&lt;a href="' + result + '"&gt;' + name + '&lt;/a&gt;';
  $('#txt-results').val(result);
}
&lt;/script&gt;
Data to encode:&lt;br /&gt;
&lt;textarea id="txt-input" cols="50" rows="20" wrap="Off" onkeyup="runEncode()"&gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;Hello World!&amp;lt;/title&amp;gt;
    &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width&amp;quot; /&amp;gt;
    &amp;lt;meta name=&amp;quot;apple-mobile-web-app-capable&amp;quot; content=&amp;quot;yes&amp;quot; /&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
  Hello World!
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/textarea&gt;&lt;br /&gt;
Prefix: &lt;input id="txt-prefix" size="57" type="text" value="data:text/html;charset=utf-8;base64," onkeyup="runEncode()" /&gt;&lt;br /&gt;
Name (optional): &lt;input id="txt-name" size="45" type="text" onkeyup="runEncode()" /&gt;&lt;br /&gt;
&lt;input value="Encode" type="submit" onclick="runEncode()" /&gt;
&lt;br /&gt;&lt;br /&gt;
&lt;div id="div-results" style="display:none"&gt;
&lt;b&gt;Results:&lt;/b&gt; &lt;a id="a-result" href="" target="_blank"&gt;Click here to try it out in a new window!&lt;/a&gt;&lt;br /&gt;
&lt;textarea id="txt-results" cols="50" rows="20"&gt;
&lt;/textarea&gt;&lt;br /&gt;

&lt;/div&gt;
&lt;script type="text/javascript"&gt;
function getBase64Char(base64Value) {
  if (base64Value &lt; 0) {
    throw "Invalid number: " + base64Value;
  } else if (base64Value &lt;= 25) { 
    // A-Z
  return String.fromCharCode(base64Value + "A".charCodeAt(0));
 } else if (base64Value &lt;= 51) {
    // a-z
    base64Value -= 26; // a
  return String.fromCharCode(base64Value + "a".charCodeAt(0));
 } else if (base64Value &lt;= 61) {
    // 0-9
    base64Value -= 52; // 0
  return String.fromCharCode(base64Value + "0".charCodeAt(0));
 } else if (base64Value &lt;= 62) {
  return '+';
 } else if( base64Value &lt;= 63 ) {
  return '/';
 } else {
  throw "Invalid number: " + base64Value;
 }
}
function convertToBase64(input) {
  // http://en.wikipedia.org/wiki/Base64#Example
 var remainingBits;
 var result = "";
  var additionalCharsNeeded = 0;
  
  var charIndex = -1;
  var charAsciiValue;
  var advanceToNextChar = function(){ 
    charIndex++; 
    charAsciiValue = input.charCodeAt(charIndex);
    return charIndex &lt; input.length; 
  };
  
  while (true) {
    var base64Char;

    // handle 1st char
    if (!advanceToNextChar())
      break;
    base64Char = charAsciiValue &gt;&gt;&gt; 2;
    remainingBits = charAsciiValue &amp; 3; // 0000 0011
    result += getBase64Char(base64Char); // 1st char
    additionalCharsNeeded = 3;
    
    // handle 2nd char
    if (!advanceToNextChar())
      break;
    base64Char = (remainingBits &lt;&lt; 4) | (charAsciiValue &gt;&gt;&gt; 4);
    remainingBits = charAsciiValue &amp; 15; // 0000 1111
    result += getBase64Char(base64Char); // 2nd char
    additionalCharsNeeded = 2;      
    
    // handle 3rd char
    if (!advanceToNextChar())
      break;
    base64Char = (remainingBits &lt;&lt; 2) | (charAsciiValue &gt;&gt;&gt; 6);
    result += getBase64Char(base64Char); // 3rd char
    remainingBits = charAsciiValue &amp; 63; // 0011 1111
    result += getBase64Char(remainingBits); // 4th char
    additionalCharsNeeded = 0;
  }

  // there may be an additional 2-3 chars that need to be added
  if (additionalCharsNeeded == 2) {
    remainingBits = remainingBits &lt;&lt; 2; // 4 extra bits
    result += getBase64Char(remainingBits) + "=";
  } else if (additionalCharsNeeded == 3) {
    remainingBits = remainingBits &lt;&lt; 4; // 2 extra bits
    result += getBase64Char(remainingBits) + "==";
  } else if (additionalCharsNeeded != 0) {
    throw "Unhandled number of additional chars needed: " + additionalCharsNeeded;
  }
  
 return result;
}
&lt;/script&gt;
&lt;!-- *********************************************************************** --&gt;

Instructions:
&lt;ul&gt;
&lt;li&gt;You need JavaScript enabled for this to work.&lt;/li&gt;
&lt;li&gt;If you want it to automatically generate the link for you, enter a name that will be used as the link text.&lt;/li&gt;
&lt;li&gt;Once you encoded the Web App, simply set it as the href target of a link on your website so that people with iPhones can click on it (e.g. &amp;lt;a href="data:text/html;charset=utf-8;base64,dGVzdA=="&amp;gt;My Web App&amp;lt;/a&amp;gt;). Then once they opened up the data: url in Safari, they simply need to press the Add button in Safari and choose to add it to their home screen.&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-324324512885676480?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/324324512885676480/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/01/iphone-offline-web-app-creator-data-uri.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/324324512885676480'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/324324512885676480'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/01/iphone-offline-web-app-creator-data-uri.html' title='iPhone Offline Web App Creator (Data URI Base64 Encoder)'/><author><name>Senseful Solutions</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-8660452414056846902</id><published>2010-01-06T14:27:00.000-08:00</published><updated>2012-01-21T00:10:07.172-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:iPhone'/><title type='text'>iPhone Blank Home Screen Icon</title><content type='html'>&lt;div class="alert" alt="Update: "&gt; This doesn't work for iOS 4 with a non-black background. Once I get some free time, I'll try to find a solution. Although the image used is indeed transparent, it seems that iOS 4.0 doesn't allow this and forces a black background to be used. &lt;span class="important"&gt;If you are using a background other than black, you shouldn't use this.&lt;/span&gt; For iOS 4, I created something similar to this: &lt;a href="/2010/08/iphone-folder-background-home-screen.html"&gt;Folder Background Home Screen Icons&lt;/a&gt;.&lt;/div&gt;

&lt;span style="font-size: x-large;"&gt;&lt;span style="background-color: #cfe2f3;"&gt;&lt;span style="color: #0b5394;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;div class="bookmarklet"&gt;&lt;a href="data:text/html;charset=utf-8;base64,PGh0bWw%2BDQo8aGVhZD4NCiAgPHRpdGxlPiZ6d2o7PC90aXRsZT4NCiAgPG1ldGEgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCx1c2VyLXNjYWxhYmxlPW5vIiAvPg0KICA8bGluayByZWw9ImFwcGxlLXRvdWNoLWljb24tcHJlY29tcG9zZWQiIGhyZWY9Imh0dHA6Ly80LmJwLmJsb2dzcG90LmNvbS9fN1RHWmVFSjgwbDAvUzBaYWg3c290QUkvQUFBQUFBQUFBQlkvSldBWWZORVprRXMvczE2MDAvaXBob25lLXRyYW5zcGFyZW50LWljb24ucG5nIi8%2BDQogIDxtZXRhIG5hbWU9ImFwcGxlLW1vYmlsZS13ZWItYXBwLWNhcGFibGUiIGNvbnRlbnQ9InllcyIgLz4NCjwvaGVhZD4NCjxib2R5Pg0KPHNjcmlwdCB0eXBlPSJ0ZXh0L2phdmFzY3JpcHQiPg0KaWYgKCF3aW5kb3cubmF2aWdhdG9yLnN0YW5kYWxvbmUpIHsNCiAgZG9jdW1lbnQud3JpdGUoIkluc3RydWN0aW9uczo8b2w%2BPGxpPlByZXNzIHRoZSA8Yj5wbHVzPC9iPiAoPGI%2BKzwvYj4pIGJ1dHRvbi48L2xpPjxsaT5QcmVzcyA8Yj5BZGQgdG8gSG9tZSBTY3JlZW48L2I%2BLjwvbGk%2BPC9vbD4iKQ0KfQ0KPC9zY3JpcHQ%2BDQo8cD4NCjxhIGhyZWY9Imh0dHA6Ly93d3cuc2Vuc2VmdWxzb2x1dGlvbnMuY29tLzIwMTAvMDEvaXBob25lLWJsYW5rLWhvbWUtc2NyZWVuLWljb24uaHRtbCI%2BaVBob25lIEJsYW5rIEhvbWUgU2NyZWVuIEljb248L2E%2BIHYxLjINCjwvcD4NCjwvYm9keT4NCjwvaHRtbD4%3D"&gt;iPhone Blank Icon&lt;/a&gt;&lt;/div&gt;

&lt;br /&gt;
Version 1.2 (2010-10-29)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Examples:&lt;/b&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;a href="http://1.bp.blogspot.com/_7TGZeEJ80l0/S0ZrZUnHztI/AAAAAAAAABo/1nINATsmI_s/s1600-h/SettingsBottomRight.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/_7TGZeEJ80l0/S0ZrZUnHztI/AAAAAAAAABo/1nINATsmI_s/s200/SettingsBottomRight.jpg" /&gt;&lt;/a&gt;
&lt;a href="http://2.bp.blogspot.com/_7TGZeEJ80l0/S0ZirzO235I/AAAAAAAAABg/raq32J4OtHM/s1600-h/AndroidExample.png" imageanchor="1" style="float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_7TGZeEJ80l0/S0ZirzO235I/AAAAAAAAABg/raq32J4OtHM/s200/AndroidExample.png" /&gt;&lt;/a&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;b&gt;Features:&lt;/b&gt;&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Completely invisible icon (pure black icon, no text).&lt;/li&gt;
&lt;li&gt;Doesn't open up Safari if you accidentally click the black icon.&lt;/li&gt;
&lt;li&gt;Has a link which links back to this page in case you want to add more blank icons.&lt;/li&gt;
&lt;li&gt;Uses a data: url so that if you are in airplane mode, you can still see the details for the "web app" (e.g. name and version number).&lt;/li&gt;
&lt;li&gt;Doesn't require jail breaking your iPhone.&lt;/li&gt;
&lt;li&gt;Completely free, no ads.&lt;/li&gt;
&lt;/ul&gt;
&lt;b&gt;Instructions:&lt;/b&gt;&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;&lt;span class="important"&gt;Ensure you have a black background on your device.&lt;/span&gt; (For more information, see the comment at top of this post.)&lt;/li&gt;
&lt;li&gt;Open up this page in Safari on your iPhone. (You can simply type &lt;a href="http://tinyurl.com/blankicon"&gt;http://tinyurl.com/blankicon&lt;/a&gt;&amp;nbsp;instead of the full URL.)&lt;/li&gt;
&lt;li&gt;Click the&lt;b&gt; iPhone Blank Icon&lt;/b&gt;&amp;nbsp;link (in blue) at the top of this post.&lt;/li&gt;
&lt;li&gt;Press the &lt;b&gt;plus&lt;/b&gt; (&lt;b&gt;+&lt;/b&gt;) button.&lt;/li&gt;
&lt;li&gt;Press &lt;b&gt;Add to Home Screen&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;Press &lt;b&gt;Add&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;Press and hold any icon on your home screen to position the blank icon wherever you would like.&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;
&lt;b&gt;Tips:&lt;/b&gt;&lt;br /&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;ul&gt;
&lt;li&gt;Add as many blank icons as needed by repeating the steps above.&lt;/li&gt;
&lt;li&gt;When you no longer need a blank icon, simply remove it by pressing and holding the icons and clicking the x on top of the black icon.&lt;/li&gt;
&lt;li&gt;To quickly add additional place holder icons, tap an existing one on your home screen, and click the link contained inside. It will link you back to this page which has all the instructions.&lt;/li&gt;
&lt;li&gt;If you don't see&amp;nbsp;&lt;b&gt;Add to Home Screen&lt;/b&gt;&amp;nbsp;in step 4, it's because you have used up all the space for icons on your home screens. You must delete icons in order to add additional ones.&lt;/li&gt;
&lt;li&gt;If you would rather see the blank icons, you could give them a subtle name such as&amp;nbsp;"." or "•" right before step 5. By default it uses a space character so it won't show up on your home screen.&lt;/li&gt;
&lt;li&gt;After step 5, you won't see the icon, but it's there, just press and hold on any icon and you will see all blank icons as well.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;span style="font-weight: bold;"&gt;What can this be used for?&lt;/span&gt;&lt;br /&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Keep icons grouped by categories:&lt;/b&gt;&amp;nbsp;Let's say you have a page dedicated to utilities and you have 3 different calculators (e.g. standard calculator, graphing calculator, tipping calculator), if you want to keep these icons on the first row, you could simply add a blank icon at the end of the row which will cause all the rest of the icons to start at the second row.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Position icons precisely where you want them:&lt;/b&gt;&amp;nbsp;The iPhone does not allow you to place icons wherever you want (i.e. free form placement), as a workaround, you can use blank icons to position apps exactly where you want them. If most of the time you hold your phone with your right hand, it is easiest to reach icons on the bottom right of the home screen, so you could place your most used icons on the bottom right of every page, without needing to add additional apps on each page. You could, for example,&amp;nbsp;have your Settings application on the bottom right corner of your home screen.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Keep icons in the same place:&lt;/b&gt;&amp;nbsp;I remember reading somewhere (can't find the link) that people tend to remember where an icon is located and it's much faster than searching for the icon's image to find what you need. So let's say you have had an app in a certain location on a page for a few months, it's very likely that you know exactly where the icon is located even without looking at your home screen. Now let's say you want to delete an app that comes before it on the same page. If you were to delete this app, you will be modifying the location of up to 15 additional apps (assuming a full screen and you delete the first one). Instead of having to remember the new locations of all these icons, you could use this blank icon as a placeholder to keep all the apps where they originally were. Once you find a new app to install, you can replace this placeholder with the new app and everything will have remained in it's place.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Preventing a screen from collapsing:&lt;/b&gt; Say you want to have games on your 11th screen separated by all the other screens by several empty pages. If you try to do this by saving an empty page, the empty page will collapse after you finish positioning your icons. Empty pages can be achieved by placing a single blank icon on the page.&lt;/li&gt;
&lt;/ul&gt;

&lt;div id="version-history"&gt;  
  &lt;ul&gt;  
    &lt;li&gt;10/29/2010:
      &lt;ul&gt;  
      &lt;li&gt;Fixed a bug that didn't allow a blank title to be used, thanks @Omnifico.&lt;/li&gt;
      &lt;/ul&gt;  
    &lt;/li&gt;  
    &lt;li&gt;1/7/2010:
      &lt;ul&gt;  
      &lt;li&gt;Added instructions on the blank icon page.&lt;/li&gt;
      &lt;li&gt;Uses a transparent icon instead of a black icon in case the SDK changes to support these (looks exactly the same right now).&lt;/li&gt;
      &lt;/ul&gt;  
    &lt;/li&gt;  
  &lt;/ul&gt;  
&lt;/div&gt;  

&lt;/div&gt;
&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-8660452414056846902?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/8660452414056846902/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/01/iphone-blank-home-screen-icon.html#comment-form' title='10 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/8660452414056846902'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/8660452414056846902'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/01/iphone-blank-home-screen-icon.html' title='iPhone Blank Home Screen Icon'/><author><name>Senseful Solutions</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_7TGZeEJ80l0/S0ZrZUnHztI/AAAAAAAAABo/1nINATsmI_s/s72-c/SettingsBottomRight.jpg' height='72' width='72'/><thr:total>10</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-7837779361942178973</id><published>2010-01-02T16:22:00.000-08:00</published><updated>2010-03-18T22:19:57.438-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:#Utility'/><title type='text'>Show All Regex Matches</title><content type='html'>&lt;script type="text/javascript"&gt;
(function(u, c){
  var h = document.getElementsByTagName('head')[0];
  var s = document.createElement('script');
  s.src = u;
  var d = false;
  s.onload = s.onreadystatechange = function() {
    if(!d &amp;&amp; ( !this.readyState || this.readyState == 'loaded' || this.readyState == 'complete')) {
      d = true;
      // jQuery.noConflict();
      c(jQuery);
      s.onload = s.onreadystatechange = null;
      h.removeChild(s);
    }
  };
  h.appendChild(s);
})('http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js', function($) {
  var params = getUrlVars();
  $("#rgx").val(params["rgx"]);
})

function listMatches() {
  // TODO: show matching lines button
  var rgx = $('#rgx').val(); // TODO: fixed width font for regex
  var subject = $('#subject').val();
  var regex = new RegExp(rgx, "g"); // TODO: Modifiers
  var matches = subject.match(regex);
  if (!matches) 
    matches = [];
  var result = "";
  $('#matchCount').html(matches.length);
  for (var i = 0; i &lt; matches.length; i++)
    //result += matches[i].replace(/&amp;/g, "&amp;amp;").replace(/&lt;/g, "&amp;lt;").replace(/&gt;/g, "&amp;gt;") + "&lt;br /&gt;";
    result += matches[i] + "\n";
  $('#result').html(result);
}

// Read a page's GET URL variables and return them as an associative array.
function getUrlVars()
{
    var vars = [], hash;
    var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&amp;');
    for(var i = 0; i &lt; hashes.length; i++)
    {
        hash = hashes[i].split('=');
        vars.push(hash[0]);
        vars[hash[0]] = hash[1];
    }
    return vars;
}
&lt;/script&gt;

&lt;p&gt;
This is a simple online utility that will list all the matches of a regular expression. To see how this works, try this &lt;a href="?rgx=\bt\w*\b"&gt;sample regex&lt;/a&gt; which will list all the words that start with a "t".
&lt;/p&gt;

Regex:&lt;br /&gt;
&lt;input name="rgx" id="rgx" type="text" size="65" /&gt;&lt;br /&gt;
&lt;!--input id="caseInsens" type="checkbox" checked /&gt; Case Insensitive&lt;br /--&gt;
Subject:&lt;br /&gt;
&lt;textarea name="subject" id="subject" cols="50" rows="20" wrap="Off"&gt;This is where you should paste the text to match.&lt;/textarea&gt;
&lt;input value="List Matches" type="submit" onclick="listMatches()" /&gt;

&lt;h2&gt;Results: &lt;span id="matchCount"&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;textarea id="result" cols="50" rows="20" wrap="Off"&gt;
(press List Matches above.)
&lt;/textarea&gt;

&lt;p&gt;
Notes: The regex syntax used is JavaScript. JavaScript must be &lt;a href="https://www.google.com/adsense/support/bin/answer.py?answer=12654"&gt;enabled&lt;/a&gt; for this to work. For more information about regular expressions, you can visit &lt;a href="http://www.regular-expressions.info/"&gt;this site&lt;/a&gt;. Feel free to link other people to this website. You can even preload the Regex for them by using the "rgx" parameter &lt;a href="?rgx=example"&gt;like this&lt;/a&gt;.
&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-7837779361942178973?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/7837779361942178973/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2010/01/show-all-regex-matches.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/7837779361942178973'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/7837779361942178973'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2010/01/show-all-regex-matches.html' title='Show All Regex Matches'/><author><name>Senseful Solutions</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-8365336372881206529</id><published>2009-12-17T07:47:00.000-08:00</published><updated>2012-01-23T19:44:36.112-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:#Bookmarklet'/><title type='text'>Show Anchors Bookmarklet</title><content type='html'>&lt;a class="bookmarklet" href="javascript:(function(){/* v2.2 -- http://bit.ly/7akCur */function ls(u,c){var h=document.getElementsByTagName(&amp;quot;head&amp;quot;)[0];var s=document.createElement(&amp;quot;script&amp;quot;);s.src=u;var d=false;s.onload=s.onreadystatechange=function(){if(!d&amp;amp;&amp;amp;(!this.readyState||this.readyState==&amp;quot;loaded&amp;quot;||this.readyState==&amp;quot;complete&amp;quot;)){d=true;c();s.onload=s.onreadystatechange=null;h.removeChild(s);}};h.appendChild(s);}var $;var loc=location.href;var anchorPos=location.href.lastIndexOf('#');if(anchorPos&amp;gt;-1){loc=loc.substring(0,anchorPos);/* if already has an anchor, it needs to be replaced */}function a(e,n,t){$('&amp;lt;a href=&amp;quot;'+loc+'#'+n+'&amp;quot; title=&amp;quot;'+t+': '+n+'&amp;quot;&amp;gt;&amp;lt;img src=&amp;quot;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAGdSURBVDjLpVMxa8JAFL6rAQUHXQoZpLU/oUOnDtKtW/MDBFHHThUKTgrqICgOEtd2EVxb2qFkKTgVChbSCnZTiVBEMBRLiEmafleCDaWxDX3w8e7dve+7l3cv1LZt8h/jvA56vV7DNM20YRgE/jyRSOR+ytvwEgAxvVwui/BF+LTvCtjNwKvj/X8CbgXPOHMEZl559HsTu93uPQi7jBiNRgMEx8PR0GIxRB+y2eze2gqQeAXoSCaqqu5bpsWIdyzGvvRrBW7rdDo2I6ZSKeq7B8x0XV/bwJWAJEnHSMwBDUEQWq5GfsJthUJhlVuv11uckyiGgiH2RWK73RYRb2cymbG7gnK5vIX9USwWI1yAI/KjLGK7teEI8HN1TizrnZWdRxxsNps8vI3YLpVKbB2EWB6XkMHzgAlvriYRSW+app1Mpy/jSCRSRSyDUON5nuJGytaAHI/vVPv9p/FischivL96gEP2bGxorhVFqYXDYQFCScwBYa9EKU1OlAkB+QLEU2AGaJ7PWKlUDiF2BBw4P9Mt/KUoije+5uAv9gGcjD6Kg4wu3AAAAABJRU5ErkJggg%3D%3D&amp;quot; /&amp;gt;&amp;lt;/a&amp;gt;').insertBefore(e);}ls(&amp;quot;http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js&amp;quot;,function(){$=jQuery;$(&amp;quot;a[name]&amp;quot;).each(function(i){a(this,this.name,&amp;quot;NAME&amp;quot;);});$(&amp;quot;[id]:not(input[type='hidden'])&amp;quot;).each(function(i){a(this,this.id,&amp;quot;ID&amp;quot;);});});})()"&gt;Show Anchors&lt;/a&gt;

&lt;div&gt;
(version 2.2, updated 6/30/2010)
&lt;a name='more'&gt;&lt;/a&gt;
&lt;a href="http://www.famfamfam.com/about"&gt;&lt;/a&gt;&lt;/div&gt;
&lt;h5&gt;How to use this bookmarklet:&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;Install the bookmarklet in your browser (e.g. by dragging the "Show Anchors" link from above into your browser's bookmarks).&lt;/li&gt;
&lt;li&gt;Navigate to a page which has anchors (e.g. a Wikipedia article that contains a table of contents).&lt;/li&gt;
&lt;li&gt;Invoke the bookmarklet by clicking the bookmark you added from step 1.&lt;br /&gt;You will now see a lot of anchor icons located near the anchors on the webpage you are viewing.&lt;/li&gt;
&lt;li&gt;Do one of the following to copy a link to an anchor's location:&lt;/li&gt;
&lt;ul&gt;
&lt;li&gt;Click on one to navigate to that location, and then copy the location from the address bar.&lt;/li&gt;
&lt;li&gt;Right-click on an icon and copy its location.&lt;/li&gt;
&lt;/ul&gt;
&lt;/ol&gt;
&lt;br /&gt;
One thing that really annoys me on the internet is when you get to some really big page and find something useful in the middle of the page. You want to link to it, but you can't. If you are lucky, the site owner implemented anchors which allow you to jump to a specific section of the page which does allow you to link to that section. However there are many times I come across webpages that don't have links to the different sections. Some examples are forum posts (mainly non-standard forum engines) and comments on blogs and sites such as YouTube. The annoying part is that in the source code, there are elements which allow you to link to that specific part of the page, it's just that the site owners didn't provide a means to access it easily. So instead of having to spend a minute or two every time (i.e. viewing the source code, finding the anchor to use, etc), I thought that a bookmarklet would be an excellent solution.&lt;br /&gt;
&lt;br /&gt;
The first thing I did was try to look for any existing solution. The top 10 results in Google all weren't good. Many of them were outdated (read: don't work), didn't support linking to the ID attribute. On the &lt;a href="http://www.w3.org/TR/html401/struct/links.html#idx-fragment_identifier"&gt;W3 specification page for anchors&lt;/a&gt; you can see that it says that the ID attribute can be used as anchors:&lt;br /&gt;
&lt;blockquote&gt;
&lt;span style="font-family: sans-serif;"&gt;Destination anchors in HTML documents may be specified either by the &lt;a class="noxref" href="http://www.w3.org/TR/html401/struct/links.html#edef-A" style="-webkit-background-clip: initial; -webkit-background-origin: initial; background-attachment: initial; background-color: transparent; background-image: initial; background-position: initial initial; background-repeat: initial; color: #0000cc;"&gt;&lt;samp class="einst"&gt;A&lt;/samp&gt;&lt;/a&gt; element (naming it with the &lt;a class="noxref" href="http://www.w3.org/TR/html401/struct/links.html#adef-name-A" style="-webkit-background-clip: initial; -webkit-background-origin: initial; background-attachment: initial; background-color: transparent; background-image: initial; background-position: initial initial; background-repeat: initial; color: #0000cc;"&gt;&lt;samp class="ainst-A"&gt;name&lt;/samp&gt;&lt;/a&gt; attribute), or by any other element (naming with the &lt;a class="noxref" href="http://www.w3.org/TR/html401/struct/global.html#adef-id" style="-webkit-background-clip: initial; -webkit-background-origin: initial; background-attachment: initial; background-color: transparent; background-image: initial; background-position: initial initial; background-repeat: initial; color: #0000cc;"&gt;&lt;samp class="ainst"&gt;id&lt;/samp&gt;&lt;/a&gt; attribute).&lt;/span&gt;&lt;/blockquote&gt;
This makes many more anchor locations available on webpages... places that the site owner probably never intended to be linked to. In fact, I just used this very bookmarklet to find a nice place to link to in that document which takes you as close to the quoted text as possible. The only problem would be if they change the page thinking no one linked to those ID attributes, but that's a risk I'm willing to take.&lt;br /&gt;
&lt;br /&gt;
So since none of the existing solutions worked, I decided to make my own. There were a few requirements:&lt;br /&gt;
&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;Not hosted on a 3rd party server (i.e. the bookmarklet should not simply import a JavaScript file from a site that may or may not be up).&lt;/li&gt;
&lt;li&gt;Images should be used to show where the links are. This makes it much easier to spot than text. Btw, the image should not be hosted on a website either.&lt;/li&gt;
&lt;li&gt;And finally, it should work exactly as the specification states, where all A tags with name attributes and all elements with ID attributes (except for hidden elements) are linkable.&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
The first idea I had was to use &lt;a href="http://jquery.com/"&gt;JQuery&lt;/a&gt;. JQuery is a JavaScript library that makes programming in JavaScript fun. In fact, I was able to write most of the code needed in essentially 3 lines. This fit requirement #1 very well because it needs to be as small as possible if it's a non-hosted bookmarklet. While I am linking to the current version of JQuery, which adds a dependency, I would much rather prefer adding a dependency on a well known library such as JQuery than something else. It's more likely that the web server will be up than if I used a different hosted solution. The benefits (3 lines of code with cross compatibility across all browsers) far outweighed the drawback (dependency).&lt;br /&gt;
&lt;br /&gt;
To load JQuery, I used a modified version of the &lt;a href="http://stackoverflow.com/questions/756382/bookmarklet-wait-until-javascript-is-loaded/756526#756526"&gt;code here&lt;/a&gt;. The only difference is that I reduced the function and variable names to use up less characters. In case you want to make your own bookmarklet that uses JQuery, you can use this template:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="no-wrap-sh"&gt;
&lt;pre class="javascript" name="code"&gt;javascript:(function(){
  function ls(u, c) {
    var h = document.getElementsByTagName("head")[0];
    var s = document.createElement("script");
    s.src = u;
    var d = false;
    s.onload = s.onreadystatechange = function() {
      if(!d &amp;amp;&amp;amp; ( !this.readyState || this.readyState == "loaded" || this.readyState == "complete")) {
        d = true;
        c();
        s.onload = s.onreadystatechange = null;
        h.removeChild(s);
      }
    };
    h.appendChild(s);
  }

  var $;
  ls("http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js", function() {
    $ = jQuery;
    // do something
  });
})()&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Don't forget to use a site such as &lt;a href="http://subsimple.com/bookmarklets/jsbuilder.htm"&gt;Bookmarklet Builder&lt;/a&gt; to compress the whitespace even further.&lt;br /&gt;
&lt;br /&gt;
Now that I had JQuery loading correctly, and figuring out the code to write was simple. I just needed to get the image to use. I decided to use the anchor image from the &lt;a href="http://www.famfamfam.com/"&gt;famfamfam&lt;/a&gt; set of icons. In following the requirement to not have a hosted image, I used this &lt;a href="http://www.greywyvern.com/code/php/binary2base64"&gt;Base64 converter&lt;/a&gt; and converted it to base64 so it is essentially embedded into the bookmarklet. Another benefit of not using a hosted image is that it loads really quickly when compared to a hosted solution.&lt;br /&gt;
&lt;br /&gt;
I tested the bookmarklet on Safari (Mac) and Chrome (PC), so while I didn't test on all the 5 major browsers, it should work flawlessly on all of them because it is using JQuery.&lt;stackoverflow&gt; I find this bookmarklet very useful and a time saver. Heck, it even helped me writing this blog post! But there are other benefits as well such as not having to find the links on websites. Each website can have anchor links in different places. With this bookmarklet, you get the same icon in exactly the place where the anchor appears making it much easier to use.&lt;/stackoverflow&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;
The first line in the bookmarklet contains the version number, this way if I ever update it, you can know if you have the latest version or not. To install the bookmarklet, grab it from the top of this post. Most browsers let you drag and drop it onto your bookmarks. If you need more help installing it, search Google for how to install a bookmarklet in your browser.&lt;/div&gt;
&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;
I hope you find it as useful as I do!&lt;/div&gt;
&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-8365336372881206529?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/8365336372881206529/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2009/12/show-anchors-bookmarklet.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/8365336372881206529'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/8365336372881206529'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2009/12/show-anchors-bookmarklet.html' title='Show Anchors Bookmarklet'/><author><name>Senseful Solutions</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-2482339870344759479</id><published>2009-12-17T06:23:00.000-08:00</published><updated>2010-03-18T22:32:58.951-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:Blogger'/><title type='text'>Syntax Highlighting in Blogger with TicTac Theme</title><content type='html'>&lt;div&gt;At first I tried to get &lt;a href="http://code.google.com/p/google-code-prettify/"&gt;Prettifier&lt;/a&gt; to &lt;a href="http://sunday-lab.blogspot.com/2007/10/source-code-high-light-in-blogger.html"&gt;work on Blogger by following these steps&lt;/a&gt;. However, it then complained about a "--" string or something similar. So I removed the old HTML comments that were in the script tag (&amp;lt;!-- --&amp;gt;), and it fixed that problem. It then started complaining about something else though. I tried both the standard version and the small version downloads from their website, neither worked.
&lt;/div&gt;

&lt;div&gt;So instead, I decided to try out &lt;a href="http://code.google.com/p/syntaxhighlighter/"&gt;SyntaxHighlighter&lt;/a&gt; by following the &lt;a href="http://heisencoder.net/2009/01/adding-syntax-highlighting-to-blogger.html"&gt;directions here&lt;/a&gt;. This ended up working. The only problem was that orange bullets were being shown in the code on every line. This is what it looked like:


&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_7TGZeEJ80l0/SypHNp2lyLI/AAAAAAAAAAM/HEd58fJ9Pg8/s1600-h/Picture+2.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 56px;" src="http://4.bp.blogspot.com/_7TGZeEJ80l0/SypHNp2lyLI/AAAAAAAAAAM/HEd58fJ9Pg8/s320/Picture+2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5416219801765136562" /&gt;&lt;/a&gt;


I found out that the reason is because of this Tic Tac theme I'm using on blogger. To fix it, I added the following code inside the CSS:

&lt;pre name="code" class="css"&gt;/* Remove background image from the tictac theme */
.dp-highlighter li { background-image: none }
&lt;/pre&gt;

And, as you can see, there are no more orange bullets appearing. The only bad thing is I have to write the name of the language everytime unlike in Prettifier that does it for you, but oh well, I'm just happy this works.
&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-2482339870344759479?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/2482339870344759479/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2009/12/sample-post-w-code.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/2482339870344759479'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/2482339870344759479'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2009/12/sample-post-w-code.html' title='Syntax Highlighting in Blogger with TicTac Theme'/><author><name>Senseful Solutions</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_7TGZeEJ80l0/SypHNp2lyLI/AAAAAAAAAAM/HEd58fJ9Pg8/s72-c/Picture+2.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3271789858112054722.post-8478651592162464851</id><published>2009-11-10T02:13:00.000-08:00</published><updated>2010-03-18T22:21:42.784-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tag:Development'/><title type='text'>Using Live Mesh to Backup SVN</title><content type='html'>&lt;div&gt;I really like cloud storage. The whole not having to worry about your hard drive crashing or backing up makes it sound very appealing. The fact that you can access your files from anywhere isn't too bad either.&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;For notes, I use &lt;a href="http://evernote.com/"&gt;Evernote&lt;/a&gt; and I have a very deliberate system of tagging (which I'll probably share in later posts). For most of my files, I have &lt;a href="http://mesh.com/"&gt;Live Mesh&lt;/a&gt; installed on both my PC and Mac so my files are available on both. The only problem I noticed was that my development files (all stored in SVN repositories) were sitting locally on my hard drive without anything getting backed up. If my hard drive dies all my hard work is gone. Sure, I have revisions checked out on other computers so I would probably still have a relatively recent snapshot of my repository; but all the history of SVN, one of the most important features, will be gone.&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;I didn't want to deal with backing up to an external hard drive and having to create some script that runs every so often. I wanted a simple solution that involved the cloud. So I was thinking... why not store the repository in Live Mesh? &lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;At first I was thinking of all the ways Live Mesh and SVN could be combined. The first is to put a checkout folder on Live Mesh. The advantage of this would be that you could have multiple computers looking at the same checked out repository. You wouldn't need to commit/update every time you want to make a change on both computers and sync them. However, the benefits end there. There are major flaws with this implementation. The first being that you aren't storing the entire repository in Live Mesh, you're only storing a checked out snapshot. That means it can't be used for backup purposes. The second issue is that (potentially) large binary files will take up lot's of space on your Live Mesh. This is useless because you could just recreate the binary files by building the project. There is no way to tell Live Mesh which files to exclude based on filters. I find no use in syncing the same exact checkout folder between my two computers, and that's why I this method wouldn't work for me. The other use case of Live Mesh and SVN would be to add the repositories to Live Mesh. After thinking about it a bit, it seemed very intriguing. I would have the benefit of my entire repository being backed up in a small footprint. This doesn't take up a lot of space on live mesh since it only contains deltas of file differences, and it doesn't store all those huge binaries which you shouldn't be including in your repository anyways.&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;I went over to my repositories folder and saw that it's less than 5MB, no where near the 5GB limit available on Live Mesh. I looked through the files and they all seemed small enough and this started to look feasible. Before I take the plunge however, I usually try to think of all the things that could possibly go wrong. &lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;The first was what if the repository gets too big? For example, what if I accidentally commit a huge file to my repository it will forever remain in live mesh taking up a whole lot of space even if I end up deleting the file from the repository. I started looking online if there was a way to delete all traces of a file using SVN. Apparently, most version control systems have a command called obliterate which is supposed to help with just this issue. However, &lt;a href="http://blog.projectnibble.org/2008/03/01/subversion-obliterate-the-forgotten-feature/"&gt;SVN doesn't seem to have a function like that&lt;/a&gt;. I kept on reading and noticed that there is a workaround involving dumping the svn repository, filtering the repository, and then importing it again. They even documented this usage in the &lt;a href="http://svnbook.red-bean.com/en/1.5/svn-book.html#svn.reposadmin.maint.filtering"&gt;SVN Book&lt;/a&gt;. So I figure the chance of me making this mistake is small and even though it's not trivial to export/import the data, it's possible, so there's no need to worry about it now. &lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;The next issue I thought of is making sure my data doesn't accidentally get erased by live mesh. Live Mesh automatically updates my local PC every time the contents are changed. That means if I accidentally go online and press delete on my repository folder, all my data will be gone forever from both the cloud and my local PC. Or will it? I setup a little experiment to see what happens when I delete a file on my live mesh Live Desktop. After deleting the file, I verified, as the documentation says, that it simply gets sent to the recycle bin and isn't deleted right away. This seems good enough for me. The odds that the folder will get deleted are slim. The odds that it will get deleted and my recycle bin will be emptied are even slimmer, so I figure this is a non issue.&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;The only other issue I could think of is the security risk, which I'll leave for you to make the call, depending on how sensitive your code is; how much you trust Microsoft in securing your data; not accidentally revealing your password; making sure your virus protection is up to date... the list could go on and on, I'll just leave it at that. I'm sure there are people much more suitable to discuss the security risk than I am.&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;Okay, with those issues resolved, and me not being able to think of any other ones, I decided to give it a go. I'm using &lt;a href="http://www.visualsvn.com/server/"&gt;VisualSVN Server&lt;/a&gt; which let's me easily create repositories with a Windows GUI. I'll include the steps I did in case someone has the same Live Mesh/SVN/VisualSVN Server combination I have. I first created a top level folder in Live Mesh which will store my repositories. The name &lt;b&gt;Repositories&lt;/b&gt; seemed fitting. I like creating this as a top level folder cause it should only be synced with the Live Desktop and my PC that is hosting the SVN Server. My Macintosh doesn't need to know about this so it's better off in it's own top folder. Next, I "checked out the folder" on my PC which should now be empty. I then copied my repositories from where VisualSVN Server stored them to my newly synced folder. To find out where VisualSVN Server stores your Repositories, right-click the root node &lt;b&gt;VisualSVN Server (Local)&lt;/b&gt; and select properties. You'll see the &lt;b&gt;Repositories Root&lt;/b&gt; setting with a path on your local computer. I simply pointed this to the checked out repository folder that is synced with Live Mesh, and it all works flawlessly. &lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;I then made sure that I'd be able to use this data as a backup recovery if my hard drive ever were to crash. I synced a new version of my repository folder (only taking data from the cloud) and pointed VisualSVN Server to it, it worked perfectly. While Live Mesh does exclude some types of files from syncing, such as hidden files and those that have the extension .tmp , all the files that are stored in an SVN Repository get synced by the service, making the two compatible.&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;With that, it seems like I solved the final piece of the puzzle, and now all of my data is stored in cloud services and is backed up in case my local hard drives fail. I hope you found this post useful. And if you're new to this, welcome to the cloud. This seems like the future of data storage.&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-style: italic; "&gt;Disclaimer: While I don't foresee any problems with this method, I may just be overlooking them. Keeping your whole repository only backed up on Live Mesh should be done so at your own risk. You might want to combine this method with another form of backup just to be on the safe side.&lt;/span&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3271789858112054722-8478651592162464851?l=www.sensefulsolutions.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.sensefulsolutions.com/feeds/8478651592162464851/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.sensefulsolutions.com/2009/11/using-live-mesh-to-backup-svn.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/8478651592162464851'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3271789858112054722/posts/default/8478651592162464851'/><link rel='alternate' type='text/html' href='http://www.sensefulsolutions.com/2009/11/using-live-mesh-to-backup-svn.html' title='Using Live Mesh to Backup SVN'/><author><name>Senseful Solutions</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>
