Group/Sort Tabs 2.0

27. December 2009

Hooray! Group/Sort Tabs v2.0 is public. Go grab it up! I'll just reproduce the text here because I'm lazy. :) Some more suitable information and pictures later perhaps. Here's the code repository, and here's the support page. This update from 1.1.4 is huge and took a good 6 months of work. I can say this extension is now up to my standards, it's as I envisioned. I'm quite satisfied. :D

group menuregrouped tab context menumoved tab context menutab groups

  • Group your tabs by hostname, browse session (tabs browsed within 15 minutes of each other), browse day, and original opening tab (IE8 style).
  • Sort your tabs by time opened, browsed, hostname and title.
  • Choose between sorting around groups (groups are freely movable), or ordering them along with the tabs for a true "one side to the other" sort.
  • Distinguish tab groups by color and separator. Colors rotate between 4 pleasing hues.
  • Tabs are movable and re-groupable, even when sorted. "Moved" tabs are designated with a black upper border and may be returned via the context menu. Tabs dropped in another group may be immediately sent to their sorted position within the group by holding shift before the drop.
  • Context menu for quick group/sort switching and closing groups.
  • Sorting is intelligently handled with the opening/restoration of multiple tabs.

If you're really taken with my extension you can send me a dollar. Send one with a nice little message in PayPal's comments for recipient box and you will absolutely make my day. Or write me a review, even dissenting! (Psst, you can hide that pesky little Donate $1 :-) link too... lookin' at you with those eyes.)

options groups unmovedoptions groups moved

Short URL Rewriting BlogEngine.NET

4. December 2009

Since beginning this blog one of my goals was to eventually rewrite the URLs. I've recently been successful and now you're going to learn how (for version 1.5). ;) When patching borrowed software I strive for minimal changes. This is also my first foray into this subject, so as usual "it works, but it may not be right".

This particular method uses the IIS7 URL Rewrite 1.1 module. If you're hosted on an earlier version of IIS and appreciate problem solving, request to be transferred to an IIS7 server if offered, or find another method. Thanks to IIS7's web application pool isolation this is possible even with shared hosting.

Assuming your host/you have successfully installed the URL Rewrite module, the rest will be easy (though figuring it out wasn't). Here is the <rewrite> rule section to add to your site's root Web.Config:

<system.webServer>
    <rewrite>
        <rules>
            <rule name="post redirect">
                <match url="^post/(.*)\.aspx$" />
                <action type="Redirect" url="{R:1}" redirectType="Found"/>
            </rule>
            <rule name="post rewrite">
                <match url="^([0-9]{4}/[0-9]{2}/[0-9]{2})/(.*)$" />
                <action type="Rewrite" url="post.aspx?date={R:1}&amp;slug={R:2}" />
            </rule>
            
            <rule name="blog redirect">
                <match url="^blog/(.*)$" />
                <conditions>
                    <add input="{REQUEST_URI}" matchType="Pattern" pattern="^/blog/admin/.*$" negate="true" />
                    <add input="{REQUEST_URI}" matchType="Pattern" pattern="^/blog/User controls/.*$" negate="true" />
                </conditions>
                <action type="Redirect" url="{R:1}" redirectType="Found"/>
            </rule>
            <rule name="blog rewrite">
                <match url="^(.*)$" />
                <conditions>
                    <add input="{REQUEST_URI}" matchType="Pattern" pattern="^/blog/admin/.*$" negate="true" />
                    <add input="{REQUEST_URI}" matchType="Pattern" pattern="^/blog/User controls/.*$" negate="true" />
                </conditions>
                <action type="Rewrite" url="blog/{R:1}" />
            </rule>
        </rules>
    </rewrite></system.webServer>
  • The blog redirect & rewrite rules strip out the /blog web application folder from the URL. If your virtual directory is named otherwise, you will need to make that adjustment in the match, condition, and rewrite URLs. The redirect rule ensures that anyone visiting the old URL is instead taken to the new one, and the rewrite URL makes the new one actually function.

    You will note that the /admin and /User controls (extensions) folders have been excluded from this substitution, and that is because it's necessary or they will cease functioning. (Though probably fixable, I haven't yet bothered.)

  • The post rules perform a similar redirect and rewrite, stripping out the /post subdirectory, and trailing .aspx suffix allowing post links to be in the following form: http://codeoptimism.com/2009/12/04/Short-URL-Rewriting-BlogEngineNET
    (/blog would be in there without the other rule).

Here's where things get interesting. You may be wondering why the post rewrite rule isn't simply adding /post and .aspx in a simplistic and peaceful reflection of the post redirect rule. Ah, if only it were so easy.

BlogEngine performs its own fun rewriting in BlogEngine.Core\Web\HttpModules\UrlRewrite.cs, and the Blog-Post.aspx pages themselves are actually faked. The true URLs are in the form http://codeoptimism.com/blog/post.aspx?id=686a72df-15cb-48bb-8f56-b40ffddb6af5. So why didn't they simply drop the .aspx themselves, or why can't I modify it to do so? I believe the answer is that without the .aspx extension the server fails to direct traffic to the web app whatsoever, and that's beyond my familiarity/access.

So there still shouldn't be a problem, the .aspx would be added by the rewrite rule, right? Wrong. The rewrite is a rewrite, not a redirect, it only affects the URLs appearance and you'd be in for a nasty 404. (And before you say it, using a redirect is square one, precluding the formatting we seek.)

Since I lack the wisdom to correct this problem in any other manner, I simply patched BlogEngine.Web\post.aspx.cs to accept my own (.aspx included) rewritable format: post.aspx?date=2009/12/04&slug=Short-URL-Rewriting-BlogEngineNET.

2828                    Response.AppendHeader("location", post.RelativeLink.ToString());
2929                    Response.End();
3030                }
3131            }
3232        }
3333 
 34        string qDate = Request.QueryString["date"];
 35        string qSlug = Request.QueryString["slug"];
 36 
34         if (Request.QueryString["id"] != null && Request.QueryString["id"].Length == 36)
 37        bool fromId = (Request.QueryString["id"] != null && Request.QueryString["id"].Length == 36);
 38        if (fromId)
3539        {
3640            Guid id = new Guid(Request.QueryString["id"]);
3741            this.Post = Post.GetPost(id);
 42        } else if (qDate != null && qSlug != null)
 43        {
 44            DateTime date = DateTime.Parse(qDate);
 45            string slug = qSlug;
 46            this.Post = Post.Posts.Find(delegate(Post p)
 47            {
 48                if (date != DateTime.MinValue &&
 49                    (p.DateCreated.Year != date.Year || p.DateCreated.Month != date.Month))
 50                {
 51                    if (p.DateCreated.Day != 1 && p.DateCreated.Day != date.Day)
 52                        return false;
 53                }
 54 
 55                return slug.Equals(Utils.RemoveIllegalCharacters(p.Slug), StringComparison.OrdinalIgnoreCase);
 56            });
 57        }
3858 
3959        if (Post != null)
4060        {
4161            if (!this.Post.IsVisible && !Page.User.Identity.IsAuthenticated)
4262                Response.Redirect(Utils.RelativeWebRoot + "error404.aspx", true);
4363 
 
80100            base.AddGenericLink("application/rss+xml", "alternate", Server.HtmlEncode(Post.Title) + " (RSS)", postView.CommentFeed + "?format=ATOM");
81101            base.AddGenericLink("application/rss+xml", "alternate", Server.HtmlEncode(Post.Title) + " (ATOM)", postView.CommentFeed + "?format=ATOM");
82102 
83103            if (BlogSettings.Instance.EnablePingBackReceive)
84104                Response.AppendHeader("x-pingback", "http://" + Request.Url.Authority + Utils.RelativeWebRoot + "pingback.axd");
85105        }
86         }
87106        else
88107        {
89108            Response.Redirect(Utils.RelativeWebRoot + "error404.aspx", true);
90109        }
91110    }
92111 

You could stop there, the URLs will be right in the address bar, though not the links on the page. For those we need to tweak the RelativeLink property of the Post class in BlogEngine.Core\Post.cs and rebuild BlogEngine.Core.dll.

375375        /// Only for in-site use.
376376        /// </summary>
377377        public string RelativeLink
378378        {
379379            get
380380            {
381                 string slug = Utils.RemoveIllegalCharacters(Slug) + BlogSettings.Instance.FileExtension;
 381                string slug = Utils.RemoveIllegalCharacters(Slug);// + BlogSettings.Instance.FileExtension;
 382                string relRoot = "/";//Utils.RelativeWebRoot;
382383 
383384                if (BlogSettings.Instance.TimeStampPostLinks)
384                     return Utils.RelativeWebRoot + "post/" + DateCreated.ToString("yyyy/MM/dd/", CultureInfo.InvariantCulture) + slug;
 385                    return relRoot + DateCreated.ToString("yyyy/MM/dd/", CultureInfo.InvariantCulture) + slug;
385386 
386                 return Utils.RelativeWebRoot + "post/" + slug;
 387                return relRoot + slug;
387388            }
388389        }
389390 
390391        /// <summary>
391392        /// The absolute link to the post.
392393        /// </summary>

If you're not one to tweak the source code you may download an otherwise vanilla 1.5 copy of BlogEngine.Core.dll from me. Throw that in the /bin folder on your site. Lastly you may wish to replace instances of <%=Utils.AbsoluteWebRoot %> in your Theme files with http://yoursite.com. I only had to change the one on the logo in site.master myself.

If you're thinking, "Hm, I should patch AbsoluteWebRoot and RelativeWebRoot in BlogEngine.Core/Utils.cs!" Knock yourself out and comment here when you've both changes working. (Unfortunately more is required, so I left them be.)

Summary

Mouse gestures: too awesome for you... until now!

19. November 2009

Mouse gestures are awesome. They're incredibly convenient, powerful, simple, and intuitive. I personally would be no where near as efficient without them. It's when you hold down the right mouse button, and drag the mouse in a particular direction or draw a particular shape to perform an action.

Of course it's apparent that mouse gestures must be too awesome, because you're not using them, are you? In fact, no one seems to be. Sure, the web browser Opera makes fair use of them. There's more than one Firefox extension which does the job too, with pitiful download numbers.

You'd even be hard pressed to find a gesture program for Windows. Though you would find StrokeIt, an entertainingly named little application so awesome I found it invaluable even when it wasn't updated for 4 years and had Vista bugs - but all of that has changed! A version .9.6 is here to release us of such troubles, it's time to rekindle the gesture revolution!

I'm not sure why mouse gestures have received such a poor welcoming, though I have a few ideas. It could be that you think they're slow, requiring convoluted ridiculous wrist gyrations. Is that it? Perhaps certain wonderful applications are even guilty of such default configurations. It sort of makes a difference, you know?

Here's your solution. Install StrokeIt, delete all the default configurations and add the following Global Actions:

  • / Up, Max/Restore
  • / Down, Minimize
  • Right, Move to Next Monitor
  • Left, Move to Previous Monitor
  • MButton, Launch Explorer
  • Up, Close Window

There you go, you just became incredibly more productive. Now enjoy the rest of your life. :)

8664 gestures performed

Note: If you're worried about closing a window accidentally with the Up gesture, don't be. For at least a few years now I've used this gesture and triggered it on accident maybe once, and even then was able to cancel closing my program. Is accidentally triggering gestures one of your fears?

When messy is meticulous: cleaning clutter like Gmail.

11. November 2009

When it comes to cleaning a house, or a room, few are quick to consider the strategy of throwing everything together, or at least they don't consider it a strategy.

That's too bad, because frequently it's exactly the thing to do. Too often I think, we plod through the task of organization without a realistic regard for impact on time. Programmers can see things differently. Consider organization as a function of time spent vs. time saved.

A hundred labels on a hundred envelopes for files you'll only access a few times is a colossal waste of time, despite the deceptively sly title of "organizing". In precisely the same manner, tossing together and tucking out of sight odds and ends you seldom use is fantastically practical.

Go ahead and gather up all of the clutter in your workspace! In your home! At your office, and wherever else you spend your time. Stick it all together haphazardly, in clear containers perhaps. Yes you may need to hunt, but rarely - that's why it's clutter. Furthermore it's together, and your workspace is clear of all but items relevant to your recent goals, enhancing your focus.

Often we're at a loss with what to do with our stuff, it's not worthless or we'd throw it away, it's unique or miscellaneous or we'd know where to put it. It could be organized or filed but it's awaiting our input. Throw it together! Sweep it into a box! You need it, you want it, but you haven't gotten to it, you don't want to see it and it's not immediately relevant. Instead of agonizing over organizing, reallocate your time, spend it on searching and not on sorting. Though inefficient in the long run, there will be no long run, as you search for an item to deal with it.

Gmail envelope

Obligatory technology tie-in: Everything has parallels in technology (and everything else), consider GMail's "don't sort, search". User interfaces are again similar. Items of frequent use and relevance are close by, and commands shouldn't be buried in otherwise empty multi-tiered menus in an inconvenient obtuse attempt at organization.

The user interface of your life: you, where you are, and the items around you that help you reach your goals - could use metrics of its own. Without mental measurement, are you sure you're building a time saver, or wasting time? Are you sorting when you should be searching?