Index: /trunk/BookmarkApp.pm
===================================================================
--- /trunk/BookmarkApp.pm	(revision 19)
+++ /trunk/BookmarkApp.pm	(revision 20)
@@ -69,13 +69,18 @@
     my $format = $q->param('format') || 'html';
     my $tag = $q->param('tag');
+    my @tags = $q->param('tag');
+    # special case: handle the empty tag
+    if (@tags == 1 && $tags[0] eq '') {
+        @tags = ();
+    }
     my $limit = $q->param('limit');
     my $offset = $q->param('offset');
     my @resources = $bookmarks->get_resources({
-        tag    => $tag,
+        tag    => \@tags,
         limit  => $limit,
         offset => $offset,
     });
     my @all_tags = $bookmarks->get_tags({ selected => $tag });
-    my @cotags = $bookmarks->get_cotags({ tag => $tag });
+    my @cotags = $bookmarks->get_cotags({ tag => \@tags });
 
     if ($format eq 'json') {
@@ -136,5 +141,6 @@
                 base_url     => $base_url,
                 selected_tag => $tag,
-                tags         => \@all_tags,
+                search_tags  => \@tags,
+                all_tags     => \@all_tags,
                 cotags       => \@cotags,
                 resources    => \@resources,
Index: /trunk/Bookmarks.pm
===================================================================
--- /trunk/Bookmarks.pm	(revision 19)
+++ /trunk/Bookmarks.pm	(revision 20)
@@ -53,16 +53,31 @@
     my $self = shift;
     my $params = shift;
-    my $tag = $params->{tag};
+    my $tags = $params->{tag} || [];
     my $limit = $params->{limit};
     my $offset = $params->{offset};
 
-    my ($sql, @bind) = sql_interp(
-        'select * from resources join bookmarks on resources.uri = bookmarks.uri',
-        ($tag   ? ('join tags on resources.uri = tags.uri where tags.tag =', \$tag) : ''),
-        'order by ctime desc',
-        ($limit ? ('limit', \$limit) : ()),
-        # an offset is only allowed if we have a limit clause
-        ($limit && $offset ? ('offset', \$offset) : ()),
-    );
+    # build the query
+    my @sql;
+
+    if (!ref $tags) {
+        $tags = [ $tags ];
+    }
+    if (@$tags) {
+        my $intersect = 0;
+        for my $tag (@{ $tags }) {
+            push @sql, 'intersect' if $intersect;
+            push @sql, 'select resources.*, bookmarks.* from resources join bookmarks on resources.uri = bookmarks.uri';
+            push @sql, 'join tags on resources.uri = tags.uri where tags.tag =', \$tag;
+            $intersect++;
+        }
+    } else {
+        push @sql, 'select * from resources join bookmarks on resources.uri = bookmarks.uri';
+    }
+    push @sql, 'order by ctime desc';
+    push @sql, ('limit', \$limit) if $limit;
+    # an offset is only allowed if we have a limit clause
+    push @sql, ('offset', \$offset) if $limit && $offset;
+
+    my ($sql, @bind) = sql_interp(@sql);
 
     my $sth_resource = $self->dbh->prepare($sql);
@@ -95,7 +110,26 @@
     my $self = shift;
     my $params = shift;
-    my $tag = $params->{tag};
-    my $sth = $self->dbh->prepare('select tag, count(tag) as count from tags where tag != ? and uri in (select uri from tags where tag = ?) group by tag order by tag');
-    $sth->execute($tag, $tag);
+    my $tags = $params->{tag} || [];
+    if (!ref $tags) {
+        $tags = [ $tags ];
+    }
+    my @sql;
+
+    push @sql, 'select tag, count(tag) as count from tags';
+    if (@$tags) {
+        push @sql, 'where uri in (';
+        my $intersect = 0;
+        for my $tag (@{ $tags }) {
+            push @sql, 'intersect' if $intersect;
+            push @sql, 'select uri from tags where tag = ', \$tag;
+            $intersect++;
+        }
+        push @sql, ') and tag not in ', $tags, '';
+    }
+    push @sql, 'group by tag order by tag';
+
+    my ($sql, @bind) = sql_interp(@sql);
+    my $sth = $self->dbh->prepare($sql);
+    $sth->execute(@bind);
     return @{ $sth->fetchall_arrayref({}) };
 }
Index: /trunk/bkmk
===================================================================
--- /trunk/bkmk	(revision 19)
+++ /trunk/bkmk	(revision 20)
@@ -8,4 +8,5 @@
 GetOptions(
     'file|f=s' => \my $DBNAME,
+    'tag=s' => \my @TAGS,
 );
 
@@ -32,5 +33,7 @@
     },
     list => sub {
-        my @resources = $bookmarks->get_resources();
+        my @resources = $bookmarks->get_resources({
+            tag => \@TAGS
+        });
         # TODO: list by tags, date, etc.
         # TODO: coordinate this commandline script with the CGI app
Index: /trunk/list.tt
===================================================================
--- /trunk/list.tt	(revision 19)
+++ /trunk/list.tt	(revision 20)
@@ -20,10 +20,21 @@
 }
 ul {
-    height: 75%;
-    overflow-y: scroll;
     margin: 0;
     padding: 0;
+}
+ul.main {
+    height: 60%;
     border-top: 6px solid #eee;
     border-bottom: 6px solid #eee;
+}
+.refine {
+    height: 15%;
+}
+.cotags {
+    height: 100%;
+    border: 1px dotted #999;
+}
+ul.main, ul.cotags {
+    overflow-y: scroll !important;
 }
 li {
@@ -62,5 +73,5 @@
       <select name="tag" onchange="document.forms[0].submit()">
 	<option value="">All bookmarks</option>
-	[% FOREACH tag IN tags %]
+	[% FOREACH tag IN all_tags %]
 	  <option value="[% tag.tag %]" [% IF tag.selected %]selected="selected"[% END %]>[% tag.tag %] ([% tag.count %])</option>
 	[% END %]
@@ -68,12 +79,5 @@
       <input type="submit" value="Go"/>
     </form>
-    <!--
-    <select>
-    [% FOREACH tag IN cotags %]
-      <option>[% tag.tag %] ([% tag.count %])</option>
-    [% END %]
-    </select>
-    -->
-    <ul style="">
+    <ul class="main">
       [%  FOREACH resource IN resources %]
         <li>
@@ -90,18 +94,45 @@
       [%  END %]
     </ul>
-    [% IF selected_tag %]
-      <p>
-	<a href="?tag=[% selected_tag %]">[% selected_tag %] links</a> &middot;
-	<a href="feed?tag=[% selected_tag %]" type="application/atom+xml">Atom Feed</a>
-      </p>
-    [% END %]
-    <form method="get" action="">
-      [% IF selected_tag %]
-	<input type="hidden" name="tag" value="[% selected_tag %]"/>
-      [% END %]
+    <form method="get" action="" id="reload">
       <div>
-	<input type="submit" value="Reload"/>
+        [% IF selected_tag %]
+          <a href="?tag=[% selected_tag %]">[% selected_tag %] links</a> &middot;
+          <a href="feed?tag=[% selected_tag %]" type="application/atom+xml">Atom Feed</a>
+          <input type="hidden" name="tag" value="[% selected_tag %]"/>
+        [% END %]
+        <input type="submit" value="Reload"/>
       </div>
     </form>
+    <div class="refine">
+      <form method="get" action="">
+        <div class="taglists">
+          <ul class="searchtags">
+            [% FOREACH tag IN search_tags %]
+              <li>
+                <label>
+                  <input type="checkbox" name="tag" value="[% tag %]" checked="checked"/>
+                  [% tag %]
+                </label>
+              </li>
+            [% END %]
+          </ul>
+          [% IF cotags.size %]
+            <ul class="cotags">
+              [% FOREACH tag IN cotags %]
+                <li>
+                  <label>
+                    <input type="checkbox" name="tag" value="[% tag.tag %]"/>
+                    [% tag.tag %] ([% tag.count %])
+                  </label>
+                </li>
+              [% END %]
+            </ul>
+          [% END %]
+        </div>
+        <div>
+          <input type="submit" value="Refine"/>
+        </div>
+      </form>
+    </div>
   </body>
 </html>
