commit 621e7ed686544aff7753350a0f7cb96e40d1ab94
Author: dsorber <david.sorber@gmail.com>
Date:   Wed Apr 15 17:50:23 2015 -0400

    Finished up the tag adding UI and update backend. Woohoo, progress!

diff --git a/software/bookmark_library/bmklib/web/controllers/update_bookmark.py b/software/bookmark_library/bmklib/web/controllers/update_bookmark.py
index 998ae2f..382c492 100644
--- a/software/bookmark_library/bmklib/web/controllers/update_bookmark.py
+++ b/software/bookmark_library/bmklib/web/controllers/update_bookmark.py
@@ -4,6 +4,7 @@ import os.path
 import sqlite3
 
 from bmklib.lib.bookmark import Bookmark, BookmarkNotFoundError
+from bmklib.lib.tag import Tag
 from bmklib.web.base_controller import BaseHTMLController, DB_PATH
 
 class UpdateBookmarkController(BaseHTMLController):
@@ -58,10 +59,32 @@ class UpdateBookmarkController(BaseHTMLController):
                 bookmark.description = escape(post_vars['description'][0])
             if 'deleted' in post_vars:
                 bookmark.deleted = bool(escape(post_vars['deleted'][0]))
-            bookmark.save()     
+            bookmark.save()
+            
+            # Update category tags
+            if 'category_tags' in post_vars:
+                category_tags = escape(post_vars['category_tags'][0])
+                category_tags = category_tags.split(';')
+                bookmark.category_tags.clear()
+                for tag in category_tags:
+                    if tag:
+                        bookmark.category_tags.add(Tag(conn, value=tag))
+            
+            # Update description tags
+            if 'description_tags' in post_vars:
+                description_tags = escape(post_vars['description_tags'][0])
+                description_tags = description_tags.split(';')
+                bookmark.description_tags.clear()
+                for tag in description_tags:
+                    if tag:
+                        bookmark.description_tags.add(Tag(conn, value=tag))
             
             self.content['invalid'] = False
             self.content['bookmark'] = bookmark
+            
+            # Save changes to bookmark
+            bookmark.save()
+            
         except BookmarkNotFoundError:
             self.content['invalid'] = True
             self.content['error'] = '{:d} is not a valid bookmark ID'.format(bmk_id)
diff --git a/software/bookmark_library/bmklib/web/htdocs/css/bmk_form.css b/software/bookmark_library/bmklib/web/htdocs/css/bmk_form.css
index fda85c2..18ca01c 100644
--- a/software/bookmark_library/bmklib/web/htdocs/css/bmk_form.css
+++ b/software/bookmark_library/bmklib/web/htdocs/css/bmk_form.css
@@ -45,15 +45,21 @@
         margin-top: 1.5em;
     }
 
-    li.tag {
+    div#category_tag_list, div#description_tag_list {
+        padding: 1.2em;
+    }
+
+    span.tag {
+        display: inline-block;
+        padding: 0.4em 1em;
+        margin-right: 0.5em;
+        margin-top: 0.2em;
+        white-space: pre-wrap;
         font-size: 12px;
         font-weight: bold;
         background-color: #003399;
         color: #FFF;
-        padding: 0.4em 1em;
-        display: inline;
         border-radius: 8px;
-        margin-right: 0.5em;
     }
 
     div.left {
@@ -64,3 +70,21 @@
         float: right;
         display: inline-block;
     }
+
+    input.tag_box {
+        width: 60%;
+        font-size: 16px;
+    }
+
+    input.tag_button {
+        width: auto;
+        font-size: 9pt;
+        margin-left: 0.8em;
+    }
+
+    span#no_tags {
+        font-style: italic;
+        font-size: 9pt;
+        font-weight: bold;
+        color: #666;
+    }
diff --git a/software/bookmark_library/bmklib/web/htdocs/css/bmk_main.css b/software/bookmark_library/bmklib/web/htdocs/css/bmk_main.css
index cf40055..54382f2 100644
--- a/software/bookmark_library/bmklib/web/htdocs/css/bmk_main.css
+++ b/software/bookmark_library/bmklib/web/htdocs/css/bmk_main.css
@@ -15,11 +15,11 @@
     
     div#nav_bar {
         width: 100%;
-        background-color: #CC0000;
+        background-color: #FF6600;
         padding: 0.2em 0em;
         margin-bottom: 1em;
-        border-top: 1px solid #666666;
-        border-bottom: 1px solid #666666;
+        border-top: 1.5px solid #666666;
+        border-bottom: 1.5px solid #666666;
     }
 
     div#nav_bar a {
@@ -27,7 +27,7 @@
         display: block-inline;
         text-decoration: none;
         margin-left: 1em;
-        color: #FFFFFF;
+        color: #000000;
         font-size: 9pt;
         font-family: sans-serif;
         font-weight: bold;
@@ -37,7 +37,7 @@
         display: block-inline;
         text-decoration: none;
         margin-left: 1em;
-        color: #000;
+        color: #333333;
         font-size: 9pt;
         font-family: sans-serif;
         font-weight: bold;
diff --git a/software/bookmark_library/bmklib/web/htdocs/js/add_tags.js b/software/bookmark_library/bmklib/web/htdocs/js/add_tags.js
index 5ddc7b7..e44443d 100644
--- a/software/bookmark_library/bmklib/web/htdocs/js/add_tags.js
+++ b/software/bookmark_library/bmklib/web/htdocs/js/add_tags.js
@@ -1,11 +1,12 @@
 $(document).ready(function() {
 
+    // Handle the "Cancel" button by making it act like "back" button
     $("input[type='button']#back_button").click(function() {
         history.back(); 
         return false;
     });
 
-    // Function to add new tags
+    // Function to add new category tags
     $("input[name='add_category_tag']").click(function() {
         // Get list of existing category tags
         var category_tags = $("input[name='category_tags']").val().split(";");
@@ -26,22 +27,25 @@ $(document).ready(function() {
         // Check if the category tag already exists in the list
         for (var idx = 0; idx < category_tags.length; idx++) {
             if (category_tags[idx] == new_category_tag) {
-                alert("already exists");
+                alert("tag already exists");
                 return true;
             }
         }
 
+        // Remove the none placeholder if it exists
+        $("div#category_tag_list span#no_tags").remove();
+
         // Add new tag to list and update hidden input list of categor tags
-        var new_tag = $("<li class='tag'>" + new_category_tag + "</li>")
-        $("ul#category_tag_list").append(new_tag);
+        var new_tag = $("<span class='tag'>" + new_category_tag + "</span>")
+        $("div#category_tag_list").append(new_tag);
         category_tags.push(new_category_tag);
         var tags_serialized = category_tags.join(";");
         $("input[name='category_tags']").val(tags_serialized);
         $("input[name='new_category_tag']").val("");
     });
 
-    // Function to remove tags if you right click on them
-    $(document).on("contextmenu", "ul#category_tag_list li", function() {
+    // Function to remove category tags if you right click on them
+    $(document).on("contextmenu", "div#category_tag_list span.tag", function() {
         // Grab value of clicked tag
         var tag_value = $(this).text();
         
@@ -63,9 +67,88 @@ $(document).ready(function() {
 
             // Remove the clicked on tag UI element
             $(this).remove();
+
+            // Check if all the tags have been removed
+            var count = $("div#category_tag_list").children("span").length;
+            if (count <= 0) {
+                var none_placeholder = $('<span id="no_tags">None</span>');
+                $("div#category_tag_list").append(none_placeholder);
+            }
+            return false;
         }
-        return false;        
+        return true;
     });
 
+    // Function to add new description tags
+    $("input[name='add_description_tag']").click(function() {
+        // Get list of existing description tags
+        var description_tags = $("input[name='description_tags']").val().split(";");
+
+        // Get the new description tag
+        var new_description_tag = $("input[name='new_description_tag']").val();
+        if (new_description_tag == "") {
+            alert("No value specified");
+            return true;
+        }
+
+        // Validate the new description tag; make sure it does not contain ";"
+        if (new_description_tag.indexOf(";") != -1) {
+            alert("\"" + new_description_tag + "\" is not a valid tag!");
+            return true;
+        }
+        
+        // Check if the description tag already exists in the list
+        for (var idx = 0; idx < description_tags.length; idx++) {
+            if (description_tags[idx] == new_description_tag) {
+                alert("tag already exists");
+                return true;
+            }
+        }
+
+        // Remove the none placeholder if it exists
+        $("div#description_tag_list span#no_tags").remove();
+
+        // Add new tag to list and update hidden input list of description tags
+        var new_tag = $("<span class='tag'>" + new_description_tag + "</span>")
+        $("div#description_tag_list").append(new_tag);
+        description_tags.push(new_description_tag);
+        var tags_serialized = description_tags.join(";");
+        $("input[name='description_tags']").val(tags_serialized);
+        $("input[name='new_description_tag']").val("");
+    });
 
+    // Function to remove description tags if you right click on them
+    $(document).on("contextmenu", "div#description_tag_list span.tag", function() {
+        // Grab value of clicked tag
+        var tag_value = $(this).text();
+        
+        // Ask for confirmation before removing tag
+        var r = confirm("Are you sure you want to remove description tag " + tag_value + " ?");
+        if (r == true) {
+            // Update hidden input list of category tags
+            var description_tags = $("input[name='description_tags']").val().split(";");
+            var updated_description_tags = new Array();
+            for (idx = 0; idx < description_tags.length; idx++) {
+                if (description_tags[idx] != tag_value) {
+                    updated_description_tags.push(description_tags[idx]);
+                }
+            }
+
+            // Rewrite the updated description tags list
+            var tags_serialized = updated_description_tags.join(";");
+            $("input[name='description_tags']").val(tags_serialized);
+
+            // Remove the clicked on tag UI element
+            $(this).remove();
+
+            // Check if all the tags have been removed
+            var count = $("div#description_tag_list").children("span").length;
+            if (count <= 0) {
+                var none_placeholder = $('<span id="no_tags">None</span>');
+                $("div#description_tag_list").append(none_placeholder);
+            }
+            return false;
+        }
+        return true;
+    });
 });
diff --git a/software/bookmark_library/bmklib/web/templates/bookmark.html b/software/bookmark_library/bmklib/web/templates/bookmark.html
index c43b8af..47d73a5 100644
--- a/software/bookmark_library/bmklib/web/templates/bookmark.html
+++ b/software/bookmark_library/bmklib/web/templates/bookmark.html
@@ -4,7 +4,7 @@
       xmlns:py="http://genshi.edgewall.org/">
 <xi:include href="layout.html" />
 <head>
-<title>Bookmark Library</title>
+<title>Bookmark ${bookmark.id}</title>
 <link rel="stylesheet" type="text/css" href="/htdocs/css/bmk_form.css"></link>
 <link rel="stylesheet" type="text/css" href="/htdocs/css/bmk_main.css"></link>
 <script src="/htdocs/js/jquery-2.1.3.min.js"></script>
@@ -47,22 +47,40 @@
                 <textarea name="description">${bookmark.description}</textarea>
             </div>
 
+            <!-- Category tags -->
             <div class="not_first">
                 <input type="hidden" name="category_tags" value="${';'.join([tag.value for tag in bookmark.category_tags])}" />
                 <span class="form_label">Category Tags:</span>
-                <ul id="category_tag_list">
-                    <li py:for="cat_tag in bookmark.category_tags" class="tag">${cat_tag.value}</li>
-                </ul>
-                <input type="text" name="new_category_tag" /><input type="button" name="add_category_tag" value="Add tag"/>
+                <div id="category_tag_list">
+                    <py:choose test="not bookmark.category_tags">
+                        <py:when test="True"><span id="no_tags">None</span></py:when>
+                        <py:when test="False">
+                            <span py:for="cat_tag in bookmark.category_tags" class="tag">${cat_tag.value}</span>
+                        </py:when>
+                    </py:choose>
+                </div>
+                <input class="tag_box" type="text" name="new_category_tag" /><input class="tag_button" type="button" name="add_category_tag" value="Add tag"/>
             </div>
+
+            <!-- Description tags -->
             <div class="not_first">
+                <input type="hidden" name="description_tags" value="${';'.join([tag.value for tag in bookmark.description_tags])}" />
                 <span class="form_label">Description Tags:</span>
-                <ul>
-                    <li py:for="desc_tag in bookmark.description_tags">${desc_tag.value}</li>
-                </ul>               
+                <div id="description_tag_list">
+                    <py:choose test="not bookmark.description_tags">
+                        <py:when test="True"><span id="no_tags">None</span></py:when>
+                        <py:when test="False">
+                            <span py:for="desc_tag in bookmark.description_tags" class="tag">${desc_tag.value}</span>
+                        </py:when>
+                    </py:choose>
+                </div>
+                <input class="tag_box" type="text" name="new_description_tag" /><input class="tag_button" type="button" name="add_description_tag" value="Add tag"/>           
+            </div>
+
+            <div class="not_first">
+                <input id="back_button" class="button" type="button" value="Cancel" />
+                <input class="button" type="submit" value="Update" />
             </div>
-            <input id="back_button" class="button" type="button" value="Cancel" />
-            <input class="button" type="submit" value="Update" />
         </div>
     </form>
   </py:when>
diff --git a/software/bookmark_library/bmklib/web/templates/layout.html b/software/bookmark_library/bmklib/web/templates/layout.html
index 8c5b051..0070e24 100644
--- a/software/bookmark_library/bmklib/web/templates/layout.html
+++ b/software/bookmark_library/bmklib/web/templates/layout.html
@@ -5,7 +5,7 @@
   <py:match path="head" once="true">
     <head py:attrs="select('@*')">
       <title py:with="title = list(select('title/text()'))">
-        Geddit<py:if test="title">: ${title}</py:if>
+        Bookmark Library: <py:if test="title">${title}</py:if>
       </title>
     <!--
       <link rel="stylesheet" href="${url('/media/layout.css')}" type="text/css" />
@@ -19,7 +19,7 @@
 
   <py:match path="body" once="true">
     <body py:attrs="select('@*')">
-      <h1>Bookmarks</h1>
+      <h1>Bookmark Library</h1>
       <div id="nav_bar">
         <a href="/bmk/">Home</a>
         <a href="/bmk/bookmarks">All bookmarks</a>
