[bknr-cvs] hans changed trunk/projects/quickhoney/

BKNR Commits bknr at bknr.net
Sun Sep 7 14:49:19 UTC 2008


Revision: 3830
Author: hans
URL: http://bknr.net/trac/changeset/3830

News permalink and cms functionality updates.  Always scale images uploaded
down to the right width.

U   trunk/projects/quickhoney/src/handlers.lisp
U   trunk/projects/quickhoney/src/webserver.lisp
U   trunk/projects/quickhoney/upgrade-stuff/import.lisp
U   trunk/projects/quickhoney/website/static/index.css
U   trunk/projects/quickhoney/website/static/javascript.js
U   trunk/projects/quickhoney/website/templates/index.xml

Modified: trunk/projects/quickhoney/src/handlers.lisp
===================================================================
--- trunk/projects/quickhoney/src/handlers.lisp	2008-09-07 10:24:06 UTC (rev 3829)
+++ trunk/projects/quickhoney/src/handlers.lisp	2008-09-07 14:49:19 UTC (rev 3830)
@@ -38,25 +38,18 @@
       (blob-to-stream (quickhoney-animation-image-animation animation)
                       (send-headers)))))
 
-(defclass json-image-query-handler (object-handler quickhoney-image-handler)
-  ())
+(defclass json-image-info-handler (object-handler quickhoney-image-handler)
+  ()
+  (:default-initargs :query-function #'store-image-with-name))
 
-(defparameter *editable-keywords* '(:explicit :buy-file :buy-print :buy-t-shirt)
-  "List of keywords that are image keywords which can be edited through the CMS")
-
-(defun images-in-category-sorted-by-time (cat-sub)
-  (sort (copy-list (images-in-category cat-sub))
-        #'> :key #'blob-timestamp))
-
-(defmethod object-handler-get-object ((handler json-image-query-handler))
-  (images-in-category-sorted-by-time (mapcar #'make-keyword-from-string (decoded-handler-path handler))))
-
 (defmethod image-to-json ((image quickhoney-image))
   (with-json-object ()
+    (encode-object-element "class" (string-downcase (cl-ppcre:regex-replace "^QUICKHONEY-" (symbol-name (class-name (class-of image))) "")))
     (encode-object-element "name" (store-image-name image))
-    (encode-object-element "category" (quickhoney-image-category image))
-    (when (quickhoney-image-subcategory image)
-      (encode-object-element "subcategory" (quickhoney-image-subcategory image)))
+    (when (quickhoney-image-category image)
+      (encode-object-element "category" (quickhoney-image-category image))
+      (when (quickhoney-image-subcategory image)
+        (encode-object-element "subcategory" (quickhoney-image-subcategory image))))
     (encode-object-element "id" (store-object-id image))
     (encode-object-element "type" (image-content-type (blob-mime-type image)))
     (encode-object-element "width" (store-image-width image))
@@ -71,6 +64,24 @@
         (dolist (keyword (intersection *editable-keywords* (store-image-keywords image)))
           (encode-object-element (string-downcase (symbol-name keyword)) t))))))
 
+(defmethod handle-object ((handler json-image-info-handler) image)
+  (with-json-response ()
+    (with-object-element ("image")
+      (image-to-json image))))
+
+(defclass json-image-query-handler (object-handler quickhoney-image-handler)
+  ())
+
+(defparameter *editable-keywords* '(:explicit :buy-file :buy-print :buy-t-shirt)
+  "List of keywords that are image keywords which can be edited through the CMS")
+
+(defun images-in-category-sorted-by-time (cat-sub)
+  (sort (copy-list (images-in-category cat-sub))
+        #'> :key #'blob-timestamp))
+
+(defmethod object-handler-get-object ((handler json-image-query-handler))
+  (images-in-category-sorted-by-time (mapcar #'make-keyword-from-string (decoded-handler-path handler))))
+
 (defmethod layout-to-json ((layout layout))
   (with-json-array ()
     (dolist (page (layout-pages layout))
@@ -252,6 +263,17 @@
 (defclass upload-image-handler (admin-only-handler prefix-handler)
   ())
 
+(defun count-colors-used (&optional (image cl-gd:*default-image*))
+  (let ((color-table (make-hash-table :test #'eql)))
+    (cl-gd:do-pixels (image)
+      (setf (gethash (cl-gd:raw-pixel) color-table) t))
+    (hash-table-count color-table)))
+
+(defun maybe-convert-to-palette (&optional (image cl-gd:*default-image*))
+  (when (and (cl-gd:true-color-p image)
+             (<= (count-colors-used image) 256))
+    (cl-gd:true-color-to-palette :image image)))
+
 (defmethod handle ((handler upload-image-handler))
   (with-query-params (client spider-keywords)
     (let ((uploaded-file (request-uploaded-file "image-file")))
@@ -260,15 +282,10 @@
 	    (unless uploaded-file
 	      (error "no file uploaded"))
 	    (with-image-from-upload* (uploaded-file)
-	      (let* ((color-table (make-hash-table :test #'eql))
-		     (width (cl-gd:image-width))
+	      (let* ((width (cl-gd:image-width))
 		     (height (cl-gd:image-height))
 		     (ratio (/ 1 (max (/ width 300) (/ height 200)))))
-		(cl-gd:do-pixels ()
-		  (incf (gethash (cl-gd:raw-pixel) color-table 0)))
-		(when (and (cl-gd:true-color-p)
-			   (<= (hash-table-count color-table) 256))
-		  (cl-gd:true-color-to-palette))
+                (maybe-convert-to-palette)
 		(let* ((image (make-store-image :name (pathname-name (upload-original-filename uploaded-file))
 						:class-name 'quickhoney-image
 						:keywords (cons :upload (image-keywords-from-request-parameters))
@@ -283,8 +300,7 @@
 			      ((:script :type "text/javascript" :language "JavaScript")
 			       "function done() { window.opener.do_query(); window.close(); }"))
 			     (:body
-			      (:p "Image " (:princ-safe (store-image-name image)) " with "
-                                  (:princ-safe (hash-table-count color-table)) " colors uploaded")
+			      (:p "Image " (:princ-safe (store-image-name image)) " uploaded")
 			      (:p ((:img :src (format nil "/image/~D" (store-object-id image))
 					 :width (round (* ratio width)) :height (round (* ratio height)))))
 			      (:p ((:a :href "javascript:done()") "ok")))))))))))
@@ -303,6 +319,12 @@
 (defclass upload-news-handler (admin-only-handler page-handler)
   ())
 
+(defun normalize-news-title (title)
+  (string-downcase (cl-ppcre:regex-replace-all "(?i)[^a-z0-9_]+" title "_")))
+
+(defconstant +news-image-width+ 486
+  "Width of a news image.  Uploaded images that are wider are scaled down.")
+
 (defmethod handle ((handler upload-news-handler))
   (with-query-params (title text)
     (let ((uploaded-file (request-uploaded-file "image-file")))
@@ -310,35 +332,39 @@
 	  (progn
 	    (unless uploaded-file
 	      (error "no file uploaded"))
-	    (with-image-from-upload* (uploaded-file)
-	      (let* ((color-table (make-hash-table :test #'eql))
-		     (width (cl-gd:image-width))
-		     (height (cl-gd:image-height))
-		     (ratio (/ 1 (max (/ width 300) (/ height 200)))))
-		(cl-gd:do-pixels ()
-		  (incf (gethash (cl-gd:raw-pixel) color-table 0)))
-		(when (and (cl-gd:true-color-p)
-			   (<= (hash-table-count color-table) 256))
-		  (cl-gd:true-color-to-palette))
-		(let* ((image (make-store-image :name (pathname-name (upload-original-filename uploaded-file))
-						:class-name 'quickhoney-news-item
-						:keywords (list :upload)
-						:initargs (list :cat-sub (list :news)
-                                                                :title title
-                                                                :text text))))
-		  (with-http-response ()
-		    (with-http-body ()
-		      (html (:html
-			     (:head
-			      (:title "Upload successful")
-			      ((:script :type "text/javascript" :language "JavaScript")
-			       "function done() { window.opener.do_query(); window.close(); }"))
-			     (:body
-			      (:p "Image " (:princ-safe (store-image-name image)) " with "
-                                  (:princ-safe (hash-table-count color-table)) " colors uploaded")
-			      (:p ((:img :src (format nil "/image/~D" (store-object-id image))
-					 :width (round (* ratio width)) :height (round (* ratio height)))))
-			      (:p ((:a :href "javascript:done()") "ok")))))))))))
+	    (with-image-from-upload (uploaded-image uploaded-file)
+              (maybe-convert-to-palette uploaded-image)
+              (when (> (cl-gd:image-width uploaded-image) +news-image-width+)
+                (let* ((scaled-height (floor (* (/ +news-image-width+ (cl-gd:image-width uploaded-image))
+                                                (cl-gd:image-height uploaded-image))))
+                       (scaled-image (cl-gd:create-image +news-image-width+ scaled-height (cl-gd:true-color-p uploaded-image))))
+                  (cl-gd:copy-image uploaded-image scaled-image
+                                    0 0 0 0
+                                    (cl-gd:image-width uploaded-image) (cl-gd:image-height uploaded-image)
+                                    :resample t :resize t
+                                    :dest-width +news-image-width+ :dest-height scaled-height)
+                  (cl-gd:destroy-image uploaded-image)
+                  (setf uploaded-image scaled-image)))
+              (let ((item (make-store-image :name (normalize-news-title title)
+                                            :image uploaded-image
+                                            :type (if (cl-gd:true-color-p uploaded-image) :jpg :png)
+                                            :class-name 'quickhoney-news-item
+                                            :keywords (list :upload)
+                                            :initargs (list :cat-sub (list :news)
+                                                            :title title
+                                                            :text text
+                                                            :owner (bknr-session-user)))))
+                (declare (ignore item)) ; for now
+                (with-http-response ()
+                  (with-http-body ()
+                    (html (:html
+                           (:head
+                            (:title "News article created")
+                            ((:script :type "text/javascript" :language "JavaScript")
+                             "function done() { window.opener.reload_news(); window.close(); }"))
+                           (:body
+                            (:p "News article created")
+                            (:p ((:a :href "javascript:done()") "ok"))))))))))
 	(error (e)
 	  (with-http-response ()
 	    (with-http-body ()
@@ -449,17 +475,18 @@
 		      (:p (:princ-safe (apply #'format nil (simple-condition-format-control e) (simple-condition-format-arguments e))))
 		      (:p ((:a :href "javascript:window.close()") "ok"))))))))))))
 
-(defclass rss-channel-handler (object-handler)
+(defclass json-news-handler (object-handler)
   ()
-  (:default-initargs :object-class 'rss-channel :query-function #'find-rss-channel))
+  (:default-initargs  :query-function (lambda (string) (or (find-rss-channel string)
+                                                           (store-image-with-name string)
+                                                           (find-store-object string)))))
 
-(defclass json-news-handler (rss-channel-handler)
-  ())
-
 (defgeneric json-encode-news-item (item)
   (:method ((item t))
     ; do nothing
     )
+  (:method :before ((item store-object))
+    (encode-object-element "id" (store-object-id item)))
   (:method :before ((image quickhoney-image))
     (when (owned-object-owner image)
       (encode-object-element "owner" (user-login (owned-object-owner image))))
@@ -480,17 +507,24 @@
     (encode-object-element "width" (store-image-width item))
     (encode-object-element "height" (store-image-height item))))
 
-(defmethod handle-object ((handler json-news-handler) (channel rss-channel))
+(defun json-encode-news-items (items)
   (with-json-response ()
     (with-object-element ("items")
       (with-json-array ()
-        (dolist (item (rss-channel-items channel))
+        (dolist (item items)
           (with-json-object ()
             (json-encode-news-item item)))))))
 
-(defclass json-news-archive-handler (rss-channel-handler)
-  ())
+(defmethod handle-object ((handler json-news-handler) (channel rss-channel))
+  (json-encode-news-items (rss-channel-items channel)))
 
+(defmethod handle-object ((handler json-news-handler) (item quickhoney-news-item))
+  (json-encode-news-items (list item)))
+
+(defclass json-news-archive-handler (object-handler)
+  ()
+  (:default-initargs :object-class 'rss-channel :query-function #'find-rss-channel))
+
 (defmethod handle-object ((handler json-news-archive-handler) (channel rss-channel))
   (with-json-response ()
     (with-object-element ("months")

Modified: trunk/projects/quickhoney/src/webserver.lisp
===================================================================
--- trunk/projects/quickhoney/src/webserver.lisp	2008-09-07 10:24:06 UTC (rev 3829)
+++ trunk/projects/quickhoney/src/webserver.lisp	2008-09-07 14:49:19 UTC (rev 3830)
@@ -22,6 +22,7 @@
 		 :handler-definitions `(("/random-image" random-image-handler)
 					("/animation" animation-handler)
 					("/json-image-query" json-image-query-handler)
+					("/json-image-info" json-image-info-handler)
 					("/json-login" json-login-handler)
 					("/json-logout" json-logout-handler)
 					("/json-clients" json-clients-handler)

Modified: trunk/projects/quickhoney/upgrade-stuff/import.lisp
===================================================================
--- trunk/projects/quickhoney/upgrade-stuff/import.lisp	2008-09-07 10:24:06 UTC (rev 3829)
+++ trunk/projects/quickhoney/upgrade-stuff/import.lisp	2008-09-07 14:49:19 UTC (rev 3830)
@@ -68,9 +68,7 @@
 (with-transaction (:initialize-news)
   (setf (slot-value (find-rss-channel "quickhoney") 'bknr.rss::items)
         (sort (remove-if (lambda (image)
-                           (or (member :explicit (store-image-keywords image))
-                               (not (and (quickhoney-image-category image)
-                                         (quickhoney-image-subcategory image)))))
+                           (member :explicit (store-image-keywords image)))
                          (class-instances 'quickhoney-image))
               #'>
               :key #'blob-timestamp)))

Modified: trunk/projects/quickhoney/website/static/index.css
===================================================================
--- trunk/projects/quickhoney/website/static/index.css	2008-09-07 10:24:06 UTC (rev 3829)
+++ trunk/projects/quickhoney/website/static/index.css	2008-09-07 14:49:19 UTC (rev 3830)
@@ -359,12 +359,20 @@
 }
 
 .newsentry h1 {
-	margin: 5px 0px 2px 0px;
+	margin: 10px 0px 5px 0px;
 	font-size: 120%;
 	font-weight: normal;
         color: #30be01
 }
 
+.newsentry .delete-button {
+	margin: 10px 0px 5px 0px;
+}
+
+.newsentry .item-text {
+	margin-top: 1em;
+}
+
 .autonews {
 	height: 106px;
         color: #000; border-width: 1px; border-style: solid

Modified: trunk/projects/quickhoney/website/static/javascript.js
===================================================================
--- trunk/projects/quickhoney/website/static/javascript.js	2008-09-07 10:24:06 UTC (rev 3829)
+++ trunk/projects/quickhoney/website/static/javascript.js	2008-09-07 14:49:19 UTC (rev 3830)
@@ -247,6 +247,18 @@
 
 function make_news_item(item, revealer)
 {
+    var delete_button = '';
+
+    if (logged_in) {
+        delete_button = BUTTON({ 'class': 'delete-button' }, "Delete");
+        delete_button.onclick = function () {
+            if (confirm('Delete this news article?')) {
+                loadJSONDoc('/json-edit-image/' + item.id + '?action=delete')
+                    .addCallbacks(reload_news, alert);
+            }
+            return false;
+        }
+    }
     return DIV({ 'class': 'newsentry' },
                revealer.IMG({ src: "/image/" + encodeURI(item.name) + '/news-article-cutout',
                               style: 'visibility: hidden',
@@ -254,9 +266,10 @@
                DIV(null,
                    H1(null, item.title),
                    item.date, ' by ', item.owner, ' | ',
-                   A({ href: '#' }, 'permalink'),
+                   A({ href: '#news/' + item.name }, 'permalink'),
                    BR(),
-                   item.text));
+                   DIV({ 'class': 'item-text' }, item.text),
+                   delete_button));
 }
 
 function load_news(data)
@@ -272,18 +285,21 @@
                                            DIV({ 'class': 'news_sep' }) ];
                               }, data.items));
         $('archive-navigation').style.visibility = 'inherit';
+
+        display_cms_window();
     }
     catch (e) {
         log('error displaying news: ' + e);
     }
 }
 
-function news(subpath) {
-    var args = subpath.split('/');
-    var year = args[0];
-    var month = args[1];
+function reload_news()
+{
+    $('edit_news_form').reset();
+    news('');
+}
 
-    log('news year ' + year + ' month ' + month);
+function show_news_archive_navigation(year, month) {
 
     map(function (element) {
             ((element.year == year && (month || element.month)) ? addElementClass : removeElementClass)
@@ -293,12 +309,33 @@
     for (i = 1; i <= 12; i++) {
         ((month == i) ? addElementClass : removeElementClass)('archive-navigation', 'm' + i);
     }
+}
 
-    if (month || !year) {
-        show_cue('loading news');
+function news(subpath) {
+
+    if (!subpath.match(/^[0-9\/]*$/)) {
+
+        show_cue('loading news item');
         $('newsentries').style.visibility = 'hidden';
-        loadJSONDoc('/json-news/quickhoney' + (month ? ('?month=' + subpath) : ''))
+        loadJSONDoc('/json-news/' + subpath)
             .addCallbacks(load_news, alert);
+
+    } else {
+    
+        var args = subpath.split('/');
+        var year = args[0];
+        var month = args[1];
+
+        log('news year ' + year + ' month ' + month);
+
+        show_news_archive_navigation(year, month);
+
+        if (month || !year) {
+            show_cue('loading news');
+            $('newsentries').style.visibility = 'hidden';
+            loadJSONDoc('/json-news/quickhoney' + (month ? ('?month=' + subpath) : ''))
+                .addCallbacks(load_news, alert);
+        }
     }
 }
 
@@ -476,7 +513,7 @@
     if (logged_in) {
 	if (current_directory == "home") {
         } else if (current_directory == "news") {
-            show_cms_window("edit_news_form");
+            show_cms_window("edit_news");
 	} else if (current_directory && current_subdirectory) {
 	    if (current_image) {
 		show_cms_window('edit_form');
@@ -1160,7 +1197,7 @@
     var url_path = (document.location.href + "#").split("#")[1];
     if (url_path && (url_path != document.current_path)) {
         try {
-            pageTracker._trackPageview(document.location.href);
+            pageTracker._trackPageview(document.location.href.replace("#", "/"));
         }
         catch (e) {
             log("tracking failed: " + e);

Modified: trunk/projects/quickhoney/website/templates/index.xml
===================================================================
--- trunk/projects/quickhoney/website/templates/index.xml	2008-09-07 10:24:06 UTC (rev 3829)
+++ trunk/projects/quickhoney/website/templates/index.xml	2008-09-07 14:49:19 UTC (rev 3830)
@@ -295,9 +295,9 @@
         </form>
       </div>
       
-      <div id="edit_news_form" class="cms_form">
+      <div id="edit_news" class="cms_form">
         <div class="cms_title">Create news entry</div>
-        <form action="/upload-news" method="post"
+        <form action="/upload-news" method="post" id="edit_news_form"
               enctype="multipart/form-data" target="upload_result" onsubmit="do_upload(this.target);">
           <p class="cms">
             <table>
@@ -321,10 +321,6 @@
             <input type="submit" name="action" value="upload" />
           </p>
         </form>
-        <div class="cms_title">Delete this news entry</div>
-        <form id="delete_newsentry_form_element" action="/edit-news-js" target="edit_iframe" method="post">
-          <input type="submit" name="action" value="delete" onclick="return confirm('Really delete this news entry?');" />
-        </form>
       </div>
       
       <div id="saving_edits_form" class="cms_form">




More information about the Bknr-cvs mailing list