radiant-forum-extension 2.0.1 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.1
1
+ 2.0.2
@@ -4,6 +4,7 @@ class ForumBaseController < ReaderActionController
4
4
  radiant_layout { |c| Radiant::Config['forum.layout'] || Radiant::Config['reader.layout'] }
5
5
  before_filter :require_login_unless_public
6
6
  before_filter :establish_context
7
+ after_filter :clear_site_cache, :only => [:create, :update, :destroy]
7
8
  helper :forum, :reader
8
9
 
9
10
  protected
@@ -54,7 +55,10 @@ protected
54
55
 
55
56
  def render_page_or_feed(template_name = action_name)
56
57
  respond_to do |format|
57
- format.html { render :action => template_name }
58
+ format.html {
59
+ expires_in SiteController.cache_timeout, :public => true, :private => false
60
+ render :action => template_name
61
+ }
58
62
  format.rss { render :action => template_name, :layout => 'feed' }
59
63
  format.js { render :action => template_name, :layout => false }
60
64
  end
@@ -75,4 +79,8 @@ protected
75
79
  false
76
80
  end
77
81
 
82
+ def clear_site_cache
83
+ Radiant::Cache.clear if defined?(Radiant::Cache)
84
+ end
85
+
78
86
  end
@@ -0,0 +1,13 @@
1
+ class PostAttachmentsController < ForumBaseController
2
+
3
+ # this simple controller exists for two reasons: to use sendfile for attachments (but it doesn't, yet)
4
+ # and to allow other extensions (ie group_forum) to restrict access to post attachments
5
+
6
+ def show
7
+ @attachment = PostAttachment.find(params[:id])
8
+ size = params[:size] || 'original'
9
+ expires_in SiteController.cache_timeout, :public => true, :private => false
10
+ send_file @attachment.file.path(size.to_sym), :type => @attachment.file_content_type, :disposition => 'inline'
11
+ end
12
+
13
+ end
@@ -37,7 +37,7 @@ class PostAttachment < ActiveRecord::Base
37
37
  },
38
38
  :whiny_thumbnails => false,
39
39
  :url => "/:class/:id/:basename:no_original_style.:extension",
40
- :path => ":rails_root/public/:class/:id/:basename:no_original_style.:extension"
40
+ :path => ":rails_root/:class/:id/:basename:no_original_style.:extension" # attachments can only be accessed through the PostAttachments controller, in case file security is required
41
41
 
42
42
  attr_protected :file_file_name, :file_content_type, :file_file_size
43
43
  validates_attachment_presence :file, :message => t('error.no_file')
@@ -1,4 +1,4 @@
1
- - add_reader_css '/stylesheets/reader.css'
1
+ - add_reader_css '/stylesheets/forum.css'
2
2
  - if current_reader
3
3
  - if Radiant::Config['forum.toolbar?']
4
4
  - add_reader_js '/punymce/puny_mce_src.js'
@@ -8,7 +8,7 @@
8
8
  - add_reader_js '/punymce/plugins/editsource/editsource.js'
9
9
  - add_reader_js '/punymce/plugins/paste.js'
10
10
  - add_reader_js '/javascripts/forum.js'
11
-
11
+ - add_reader_js '/javascripts/gallery.js'
12
12
 
13
13
  - content_for :section_navigation do
14
14
  = link_to t('navigation.forum'), topics_url, :class => 'section'
@@ -18,6 +18,7 @@
18
18
  = link_to t('navigation.new_topic'), new_post_url
19
19
  - if current_reader
20
20
  = link_to t('navigation.your_account'), reader_account_url
21
+ = link_to t('log_out'), reader_logout_url
21
22
  - else
22
23
  = link_to t('log_in'), reader_login_url
23
24
  - if Radiant::Config['forum.help_url']
@@ -47,3 +48,4 @@
47
48
  - content_for :newtopic do
48
49
  .newmessage
49
50
  = link_to t('new_topic'), new_post_url
51
+
@@ -0,0 +1,26 @@
1
+ %h2
2
+ = t('busy_topics')
3
+ %ul
4
+ - Topic.most_commented(10).each do |topic|
5
+ %li
6
+ = link_to topic.name, forum_topic_url(topic.forum, topic), :class => 'title'
7
+ = t('started_by')
8
+ = link_to(topic.reader.name, reader_url(topic.reader))
9
+ %br
10
+ = topic.posts.count
11
+ = t('posts') + ','
12
+ = t('most_recently')
13
+ = friendly_date(topic.replied_at) + '.'
14
+
15
+ %h2
16
+ = t('busy_readers')
17
+ %ul
18
+ - Reader.most_commenting(10).each do |reader|
19
+ %li
20
+ = link_to reader.name, reader_url(reader), :class => 'title'
21
+ = reader.posts.count
22
+ = t('posts') + ','
23
+ %br
24
+ = t('last_seen')
25
+ = time_ago_in_words(reader.posts.first.created_at)
26
+ = t('ago')
@@ -1,9 +1,9 @@
1
1
  - af ||= false
2
2
  %li
3
3
  - if attachment.image?
4
- = link_to image_tag(attachment.thumbnail), attachment.file.url
4
+ = link_to image_tag(post_attachment_url(attachment, :size => :thumbnail)), post_attachment_url(attachment), :class => 'thumbnail'
5
5
  - else
6
- = link_to attachment.filename, attachment.file.url, :class => "attachment #{attachment.extension}"
6
+ = link_to attachment.filename, post_attachment_url(attachment), :class => "attachment #{attachment.extension}"
7
7
  - if af
8
8
  = af.check_box :_destroy, :class => 'checkbox'
9
9
 
@@ -4,10 +4,10 @@
4
4
  = t('attached') + ':'
5
5
  - post.attachments.images.each do |att|
6
6
  - display_size = Radiant::Config['forum.image_zoom_size'] || :original
7
- = link_to image_tag(att.thumbnail), att.file.url(display_size), :class => :thumbnail
7
+ = link_to image_tag(post_attachment_url(att, :size => :thumbnail)), post_attachment_url(att, :size => display_size), :class => :thumbnail, :rel => post_attachment_url(att, :size => :original)
8
8
  - if post.attachments.non_images.any?
9
9
  %ul.attachments
10
10
  - post.attachments.non_images.each do |att|
11
11
  %li
12
- = link_to att.filename, att.file.url, :class => "attachment #{att.extension}"
12
+ = link_to att.filename, post_attachment_url(att), :class => "attachment #{att.extension}"
13
13
 
@@ -9,6 +9,7 @@
9
9
 
10
10
  - content_for :sidebar do
11
11
  = render :partial => 'forums/latest'
12
+ = render :partial => 'forums/statistics'
12
13
 
13
14
  - content_for :pagination do
14
15
  = pagination_and_summary_for(@topics, t('topics'))
@@ -10,6 +10,8 @@ en:
10
10
  attach_file: "attach a file"
11
11
  author: "author"
12
12
  begun_on: "begun on %{date}"
13
+ busy_topics: "Biggest topics"
14
+ busy_readers: "Biggest talkers"
13
15
  by: "by"
14
16
  by_content_and_author: "by content, author or category."
15
17
  comment: "comment"
@@ -68,6 +70,7 @@ en:
68
70
  name: "Discussion title"
69
71
  sticky: "Sticky"
70
72
  locked: "Locked"
73
+ last_seen: "last here"
71
74
  latest_activity: "latest activity"
72
75
  latest_discussion: "Latest topics"
73
76
  latest_posts: "Latest comments"
data/config/routes.rb CHANGED
@@ -3,6 +3,7 @@ ActionController::Routing::Routes.draw do |map|
3
3
  forum.resources :forums, :only => [:index, :show], :has_many => [:topics]
4
4
  forum.resources :topics, :only => [:index, :show], :has_many => [:posts]
5
5
  forum.resources :posts, :member => { :remove => :get }
6
+ forum.resources :post_attachments, :only => [:show]
6
7
  end
7
8
 
8
9
  map.namespace :admin, :member => { :remove => :get }, :path_prefix => 'admin/forum' do |admin|
data/forum_extension.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require_dependency 'application_controller'
2
2
 
3
3
  class ForumExtension < Radiant::Extension
4
- version "2.0.1"
4
+ version "2.0.2"
5
5
  description "Nice clean forums and page comments for inclusion in your radiant site. Requires the reader extension and share_layouts."
6
6
  url "http://spanner.org/radiant/forum"
7
7
 
@@ -1,6 +1,17 @@
1
1
  module CommentableModel # for inclusion into ActiveRecord::Base
2
2
  def self.included(base)
3
3
  base.extend ClassMethods
4
+ base.class_eval do
5
+ named_scope :most_commented, lambda { |count|
6
+ {
7
+ :select => "topics.*, count(posts.id) AS post_count",
8
+ :joins => "INNER JOIN posts ON posts.topic_id = topics.id",
9
+ :group => "topics.id",
10
+ :order => "post_count DESC",
11
+ :limit => count
12
+ }
13
+ }
14
+ end
4
15
  end
5
16
 
6
17
  module ClassMethods
data/lib/forum_reader.rb CHANGED
@@ -4,6 +4,16 @@ module ForumReader
4
4
  base.class_eval {
5
5
  has_many :topics, :dependent => :nullify
6
6
  has_many :posts, :order => 'posts.created_at desc', :dependent => :nullify
7
+
8
+ named_scope :most_commenting, lambda { |count|
9
+ {
10
+ :select => "readers.*, count(posts.id) AS post_count",
11
+ :joins => "INNER JOIN posts ON posts.reader_id = readers.id",
12
+ :group => "readers.id",
13
+ :order => "post_count DESC",
14
+ :limit => count
15
+ }
16
+ }
7
17
  }
8
18
  end
9
19
  end
@@ -1,9 +1,11 @@
1
1
  /*
2
2
  Sample jquery-based forum scripts.
3
3
 
4
- Two functions are handled here:
5
4
  * inline editing of posts by authors and administrators.
6
5
  * attachment and upload of files.
6
+ * simple show and hide for first post
7
+ * toolbar hookup
8
+ * submit-button protection
7
9
  */
8
10
 
9
11
  (function($) {
@@ -153,21 +155,14 @@
153
155
  });
154
156
  }
155
157
 
156
- $.tools.post = {
157
- conf: {
158
-
159
- }
160
- };
161
-
162
158
  $.fn.editable_post = function(conf) {
163
- conf = $.extend({}, $.tools.post.conf, conf);
164
159
  this.each(function() {
165
160
  new EditablePost($(this), conf);
166
161
  });
167
162
  return this;
168
163
  };
169
164
 
170
- function FirstPost(container, conf) {
165
+ function HideablePost(container, conf) {
171
166
  var self = this;
172
167
  $.extend(self, {
173
168
  head: container.find('.post_header'),
@@ -175,11 +170,11 @@
175
170
  shower: null,
176
171
  show: function () {
177
172
  self.body.slideDown();
178
- self.shower.text('Hide first post');
173
+ self.shower.text('Hide');
179
174
  },
180
175
  hide: function () {
181
176
  self.body.slideUp();
182
- self.shower.text('Show first post');
177
+ self.shower.text('Show');
183
178
  },
184
179
  toggle: function (event) {
185
180
  squash(event);
@@ -188,14 +183,14 @@
188
183
  }
189
184
  });
190
185
 
191
- self.shower = $('<a href="#" class="shower">Hide first post</a>').appendTo(self.head.find('p.context'));
186
+ self.shower = $('<a href="#" class="shower">Hide</a>').appendTo(self.head.find('p.context'));
192
187
  self.shower.click(self.toggle);
193
188
  if ($('a.prev_page').length > 0) self.hide();
194
189
  }
195
190
 
196
- $.fn.first_post = function() {
191
+ $.fn.hideable_post = function() {
197
192
  this.each(function() {
198
- new FirstPost($(this));
193
+ new HideablePost($(this));
199
194
  });
200
195
  return this;
201
196
  };
@@ -215,7 +210,7 @@
215
210
  addUpload: function(event) {
216
211
  squash(event);
217
212
  var upload_field = self.file_field.clone();
218
- var nest_id = self.uploadCount() + 1;
213
+ var nest_id = self.attachmentCount() + self.uploadCount(); // nb. starts at zero so this total is +1
219
214
  var container = $('<li class="attachment">' + upload_field.val() + '</li>');
220
215
  upload_field.attr("id", upload_field.attr('id').replace(/\d+/, nest_id));
221
216
  upload_field.attr("name", upload_field.attr('name').replace(/\d+/, nest_id));
@@ -245,15 +240,8 @@
245
240
  self.attachments_list.find('li').add_remover();
246
241
  self.file_field.change(self.addUpload);
247
242
  }
248
-
249
- $.tools.stack = {
250
- conf: {
251
-
252
- }
253
- };
254
243
 
255
244
  $.fn.upload_stack = function(conf) {
256
- conf = $.extend({}, $.tools.stack.conf, conf);
257
245
  this.each(function() {
258
246
  el = new UploadStack($(this), conf);
259
247
  });
@@ -442,7 +430,7 @@
442
430
 
443
431
  $(function() {
444
432
  $(".post").editable_post({});
445
- $(".post.first").first_post({});
433
+ $(".post.first").hideable_post({});
446
434
  $(".upload_stack").upload_stack({});
447
435
  $(".toolbarred").add_editor({});
448
436
  $("input:submit").live('click', function (event) {
@@ -0,0 +1,249 @@
1
+ (function($) {
2
+
3
+ /*
4
+ * Some easing functions borrowed from the effects library to save loading the whole lot.
5
+ * Glide is really quartic out. Boing is back out. Bounce is bounce out.
6
+ */
7
+
8
+ $.easing.glide = function (x, t, b, c, d) {
9
+ return -c * ((t=t/d-1)*t*t*t - 1) + b;
10
+ }
11
+
12
+ $.easing.boing = function (x, t, b, c, d, s) {
13
+ if (s == undefined) s = 1.70158;
14
+ return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
15
+ };
16
+
17
+ function Gallery() {
18
+ var self = this;
19
+ var container = $('<div class="gallery"><img class="preview" /><p class="caption" /><div class="controls"><a href="#" class="previous" /><a href="#" class="download" /><a href="#" class="next" /></a></div><div class="closer"><a href="#" /></div></div>').hide().appendTo($('body'));
20
+ $.extend(self, {
21
+ container: container,
22
+ zoomer: new Zoomer(),
23
+ image: container.find('img.preview'),
24
+ caption: container.find('p.caption'),
25
+ controls: container.find('div.controls'),
26
+ closer: container.find('div.closer'),
27
+ stack: [],
28
+ item: null,
29
+ add: function (a) {
30
+ self.stack.push(new GalleryItem(a));
31
+ },
32
+ display: function (item) {
33
+ self.item = item;
34
+ if (self.visible()) self.crossfade();
35
+ else self.zoomUp();
36
+ },
37
+
38
+ mimic: function () {
39
+ self.zoomer.setImage(self.item.src);
40
+ self.image.attr('src', self.item.src);
41
+ if (self.item.caption.html()) self.caption.html(self.item.caption.html()).show();
42
+ else self.caption.hide();
43
+ },
44
+ zoomUp: function () {
45
+ self.hide();
46
+ self.mimic();
47
+ self.zoomer.zoomUp(self.item, self.show);
48
+ },
49
+ zoomDown: function () {
50
+ self.zoomer.zoomDown(self.item);
51
+ self.hide();
52
+ },
53
+ crossfade: function () {
54
+ self.fadeDown(self.fadeUp);
55
+ },
56
+ fadeDown: function (onFade) {
57
+ self.caption.fadeTo('fast', 0.2);
58
+ self.image.fadeTo('fast', 0.2, onFade);
59
+ },
60
+ fadeUp: function (onFade) {
61
+ self.mimic();
62
+ self.resize();
63
+ self.caption.fadeTo('fast', 1);
64
+ self.image.fadeTo('fast', 1, onFade);
65
+ },
66
+
67
+ current: function () {
68
+ return self.stack.indexOf(self.item);
69
+ },
70
+ next: function (e) {
71
+ e.preventDefault();
72
+ var at = self.current();
73
+ var next = (at == self.stack.length-1) ? 0 : at + 1;
74
+ self.display(self.stack[next]);
75
+ },
76
+ previous: function (e) {
77
+ e.preventDefault();
78
+ var at = self.current();
79
+ var previous = (at == 0) ? self.stack.length-1 : at - 1;
80
+ self.display(self.stack[previous]);
81
+ },
82
+ close: function (e) {
83
+ e.preventDefault();
84
+ self.zoomDown();
85
+ },
86
+ show: function () {
87
+ self.zoomer.hide();
88
+ self.container.show();
89
+ },
90
+ hide: function () {
91
+ self.container.hide();
92
+ },
93
+ visible: function () {
94
+ return self.container.is(':visible');
95
+ },
96
+ showControls: function (e) {
97
+ self.controls.fadeIn("fast");
98
+ self.closer.fadeIn("fast");
99
+ self.controls.find('a.download').attr('href', self.item.download_url());
100
+ },
101
+ hideControls: function (e) {
102
+ self.controls.fadeOut("fast");
103
+ self.closer.fadeOut("fast");
104
+ },
105
+
106
+ resize: function (item) {
107
+ if (!item) item = self.item;
108
+ var w = $(window);
109
+ var d = item.imageSize();
110
+ var p = self.container.offset();
111
+ self.image.animate(d, 'fast');
112
+ self.container.animate({
113
+ left: p.left + (self.image.innerWidth() - d.width)/2,
114
+ top: p.top + (self.image.innerHeight() - d.height)/2
115
+ }, 'fast');
116
+ self.controls.css({left: (d.width - 96)/2});
117
+ },
118
+ reposition: function (item) {
119
+ if (!item) item = self.item;
120
+ var w = $(window);
121
+ var d = item.imageSize();
122
+ var p = {
123
+ top: w.scrollTop() + (w.height() - d.height)/2,
124
+ left: w.scrollLeft() + (w.width() - d.width)/2
125
+ };
126
+ if (self.visible) {
127
+ self.image.animate(d, 'fast');
128
+ self.container.animate(p, 'fast');
129
+ } else {
130
+ self.image.css(d);
131
+ self.container.css(p);
132
+ }
133
+ self.controls.css({left: (d.width - 96)/2});
134
+ return $.extend(d,p);
135
+ },
136
+ currentPosition: function () {
137
+ var p = self.container.offset();
138
+ return {
139
+ left: p.left,
140
+ top: p.top,
141
+ width: self.image.innerWidth(),
142
+ height: self.image.innerHeight()
143
+ };
144
+ }
145
+ });
146
+
147
+ self.closer.find('a').click(self.close);
148
+ self.controls.find('a.previous').click(self.previous);
149
+ self.controls.find('a.next').click(self.next);
150
+ self.container.hover(self.showControls, self.hideControls);
151
+ };
152
+
153
+ function Zoomer() {
154
+ var self = this;
155
+ var sprite = $('<img class="grower" />').hide().appendTo($('body'));
156
+ $.extend(self, {
157
+ sprite: sprite,
158
+ defaultUpState: {position: 'absolute', opacity: 1, borderLeftWidth: 20, borderRightWidth: 20, borderTopWidth: 20, borderBottomWidth: 20},
159
+ defaultDownState: {position: 'absolute', opacity: 0, borderLeftWidth: 4, borderRightWidth: 4, borderTopWidth: 4, borderBottomWidth: 4},
160
+ zoomDuration: 'slow',
161
+ setImage: function (src) {
162
+ self.sprite.attr('src', src);
163
+ },
164
+ zoomUp: function (item, onZoom) {
165
+ if (!onZoom) onZoom = self.hide;
166
+ self.sprite.css($.extend(self.defaultDownState, item.position()));
167
+ self.show();
168
+ self.sprite.animate($.extend({}, self.defaultUpState, $.gallery.reposition()), self.zoomDuration, onZoom);
169
+ },
170
+ zoomDown: function (item, onZoom) {
171
+ if (!onZoom) onZoom = self.hide;
172
+ self.show();
173
+ self.sprite.css($.extend(self.defaultUpState, $.gallery.currentPosition()));
174
+ self.sprite.animate($.extend({}, self.defaultDownState, item.position()), self.zoomDuration, onZoom);
175
+ },
176
+ show: function () {
177
+ self.sprite.show();
178
+ },
179
+ hide: function () {
180
+ self.sprite.hide();
181
+ }
182
+ });
183
+ }
184
+
185
+
186
+ function GalleryItem(a) {
187
+ var self = this;
188
+ var link = $(a);
189
+ $.extend(self, {
190
+ link: link,
191
+ thumb: link.find('img'),
192
+ image: $('<img class="preloader" />').css({visibility: 'hidden'}).appendTo($('body')),
193
+ caption: link.next('.caption'),
194
+ src: link.attr('href'),
195
+ deactivate: function (event) {
196
+ self.thumb.fadeTo('slow', 0.3);
197
+ self.thumb.css('cursor', 'text');
198
+ self.image.bind("load", self.activate);
199
+ self.image.attr('src', self.src);
200
+ },
201
+ activate: function () {
202
+ self.thumb.fadeTo('slow', 1);
203
+ self.thumb.css('cursor', 'pointer');
204
+ self.link.click(function (e) {
205
+ if (e) {
206
+ e.preventDefault();
207
+ e.stopPropagation();
208
+ }
209
+ $.gallery.display(self);
210
+ });
211
+ },
212
+ position: function () {
213
+ var p = self.thumb.offset();
214
+ return {
215
+ left: p.left,
216
+ top: p.top,
217
+ width: self.thumb.outerWidth(),
218
+ height: self.thumb.outerHeight()
219
+ };
220
+ },
221
+ imageSize: function () {
222
+ return {
223
+ width: self.image.innerWidth(),
224
+ height: self.image.innerHeight()
225
+ };
226
+ },
227
+ download_url: function () {
228
+ return self.link.attr('rel');
229
+ }
230
+ });
231
+ if (!self.image.complete) {
232
+ self.deactivate();
233
+ } else {
234
+ self.activate();
235
+ }
236
+ }
237
+
238
+ $.fn.galleried = function() {
239
+ this.each(function() {
240
+ if (!$.gallery) $.gallery = new Gallery();
241
+ $.gallery.add(this);
242
+ });
243
+ };
244
+
245
+ })(jQuery);
246
+
247
+ $(function() {
248
+ $("a.thumbnail").galleried();
249
+ });