[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