radiant-forum-extension 0.6.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/Rakefile +1 -3
  2. data/VERSION +1 -1
  3. data/app/controllers/forums_controller.rb +13 -5
  4. data/app/controllers/posts_controller.rb +9 -3
  5. data/app/controllers/topics_controller.rb +10 -3
  6. data/app/helpers/forum_helper.rb +105 -0
  7. data/app/models/post_attachment.rb +20 -12
  8. data/app/models/topic.rb +1 -1
  9. data/app/views/admin/forums/index.html.haml +63 -65
  10. data/app/views/admin/reader_configuration/_edit_forum.html.haml +6 -0
  11. data/app/views/admin/reader_configuration/_forum.html.haml +6 -0
  12. data/app/views/forums/_forum.html.haml +1 -2
  13. data/app/views/forums/index.html.haml +27 -25
  14. data/app/views/forums/show.html.haml +31 -19
  15. data/app/views/posts/_attachment.html.haml +5 -2
  16. data/app/views/posts/_form.html.haml +3 -5
  17. data/app/views/posts/_post.html.haml +32 -29
  18. data/app/views/posts/_uploader.html.haml +2 -3
  19. data/app/views/posts/index.html.haml +27 -11
  20. data/app/views/topics/_form.html.haml +3 -3
  21. data/app/views/topics/_topic.html.haml +10 -11
  22. data/app/views/topics/index.html.haml +28 -22
  23. data/app/views/topics/new.html.haml +32 -27
  24. data/app/views/topics/show.html.haml +25 -10
  25. data/config/locales/en.yml +58 -0
  26. data/config/routes.rb +5 -5
  27. data/forum_extension.rb +18 -11
  28. data/lib/forum_readers_controller.rb +1 -0
  29. data/lib/tasks/radiant_forum_extension_tasks.rake +54 -31
  30. data/public/images/{forum → furniture}/attachment.png +0 -0
  31. data/public/images/{forum → furniture}/attachment_link.png +0 -0
  32. data/public/images/{forum → furniture}/attachment_over.png +0 -0
  33. data/public/images/{forum → furniture}/chk_off.png +0 -0
  34. data/public/images/{forum → furniture}/chk_on.png +0 -0
  35. data/public/images/{forum → furniture}/feed_14.png +0 -0
  36. data/public/images/{forum → furniture}/feed_28.png +0 -0
  37. data/public/images/furniture/post.png +0 -0
  38. data/public/images/furniture/post_over.png +0 -0
  39. data/public/images/{forum → furniture}/rdo_off.png +0 -0
  40. data/public/images/{forum → furniture}/rdo_on.png +0 -0
  41. data/public/images/{forum → furniture}/wait_16_grey.gif +0 -0
  42. data/public/javascripts/forum.js +198 -0
  43. data/public/stylesheets/sass/forum.sass +247 -0
  44. data/radiant-forum-extension.gemspec +21 -28
  45. metadata +26 -53
  46. data/lib/forum_helper.rb +0 -109
  47. data/public/images/forum/post_14.png +0 -0
  48. data/public/images/forum/post_14_over.png +0 -0
  49. data/public/javascripts/platform/forum.js +0 -175
  50. data/public/javascripts/platform/remotecontent.js +0 -89
  51. data/public/stylesheets/admin/forum.css +0 -87
  52. data/public/stylesheets/platform/forum.css +0 -70
@@ -1,40 +1,45 @@
1
- = render :partial => 'readers/flasher'
1
+ - content_for :signals do
2
+ = render :partial => 'readers/flasher'
2
3
 
3
- #forum
4
- #new_topic
5
- - form_for :topic, :url => @forum ? forum_topics_path(@forum) : topics_path, :html => {:class => 'friendly', :multipart => true} do |f|
6
- = render :partial => "form", :object => f
7
- %p.buttons
8
- = submit_tag 'Start Discussion'
9
- or
10
- - if @forum
11
- = link_to('cancel', forum_path(@forum))
12
- - else
13
- = link_to('cancel', :back)
14
-
15
- - content_for :pagetitle do
16
- Start a new discussion
4
+ - content_for :form do
5
+ - form_for :topic, :url => @forum ? forum_topics_path(@forum) : topics_path, :html => {:class => 'friendly', :multipart => true} do |f|
6
+ = render :partial => "form", :object => f
7
+ %p.buttons
8
+ = submit_tag t('new_topic_button')
9
+ or
10
+ - if @forum
11
+ = link_to t('cancel'), forum_path(@forum)
12
+ - else
13
+ = link_to t('cancel'), :back
14
+
15
+ - content_for :title do
16
+ = t('new_topic_heading')
17
17
 
18
18
  - content_for :breadhead do
19
+ = link_to t('forum'), forum_home_url
20
+ = t('separator')
19
21
  - if @forum
20
- = link_to @forum.name, forum_url(@forum), :class => 'breadhead'
21
- - else
22
- = link_to "Forum", topics_url, :class => 'breadhead'
22
+ = link_to @forum.name, forum_url(@forum)
23
23
 
24
24
  - content_for :controls do
25
25
  = render :partial => 'readers/controls'
26
26
 
27
27
  - content_for :credits do
28
- %p.context
29
- You are logged in as
30
- = link_to "#{current_reader.name}.", reader_url(current_reader)
31
- If that's not you, please
32
- = link_to "log out.", reader_logout_url
28
+ %p
29
+ = t('logged_in_as', :name => current_reader.name)
30
+ = t('if_not_you')
31
+ = link_to t('log_out'), reader_logout_url
33
32
 
34
33
  - content_for :breadcrumbs do
35
- = link_to 'Forum', topics_url
36
- \>
34
+ = link_to t('forum'), topics_url
35
+ = t('separator')
37
36
  - if @forum
38
37
  = link_to @forum.name, forum_url(@forum)
39
- \>
40
- New Discussion
38
+ = t('separator')
39
+ = t('new_topic')
40
+
41
+
42
+ #forum
43
+ = yield :credits
44
+ = yield :form
45
+
@@ -1,20 +1,24 @@
1
- = render :partial => 'readers/flasher'
1
+ - content_for :signals do
2
+ = render :partial => 'readers/flasher'
2
3
 
3
- #forum
4
- - if @posts.previous_page
5
- = paginate_and_summarise @posts, 'comments'
4
+ - content_for :messages do
5
+ = render :partial => 'posts/post', :collection => @posts, :locals => { :with_context => false }
6
6
 
7
- = render :partial => 'posts/post', :collection => @posts, :locals => { :with_context => false, :first_post => @topic.posts.first }
7
+ - content_for :new_topic do
8
+ .newmessage
9
+ = link_to t('new_topic_here'), new_forum_topic_url(@forum)
8
10
 
9
- - if @posts.next_page
10
- = paginate_and_summarise @posts, 'comments'
11
- - else
11
+ - content_for :reply_form do
12
+ - unless @posts.next_page
12
13
  - if @topic.page
13
14
  = link_to "Post a reply", new_topic_post_url(@topic), :class => 'inviting remote_content'
14
15
  - else
15
16
  = render :partial => 'posts/reply'
16
17
 
17
- - content_for :pagetitle do
18
+ - content_for :pagination do
19
+ = pagination_and_summary_for(@posts, t('post'))
20
+
21
+ - content_for :title do
18
22
  = @topic.name
19
23
 
20
24
  - content_for :credits do
@@ -35,7 +39,9 @@
35
39
  = render :partial => 'readers/controls'
36
40
 
37
41
  - content_for :breadhead do
38
- = link_to @forum.name, forum_url(@forum), :class => 'breadhead'
42
+ = link_to t('forum'), forum_home_url
43
+ = t('separator')
44
+ = link_to @forum.name, forum_url(@forum)
39
45
 
40
46
  - content_for :breadcrumbs do
41
47
  = link_to 'Forum', topics_url
@@ -43,3 +49,12 @@
43
49
  = link_to @forum.name, forum_url(@forum)
44
50
  \>
45
51
  = @topic.name
52
+
53
+ #forum
54
+ = yield :pagination if @posts.previous_page
55
+ = yield :messages
56
+ = yield :pagination if @posts.next_page
57
+ = yield :reply_form
58
+
59
+
60
+
@@ -0,0 +1,58 @@
1
+ en:
2
+ all: "all"
3
+ by: "by"
4
+ config:
5
+ forum:
6
+ allow_page_comments?: "Page comments allowed"
7
+ comments_have_attachments?: "Attachments allowed"
8
+ editable_period: "Editable period"
9
+ layout: "Forum layout"
10
+ public?: "Forum public?"
11
+ date_this_year: "on %B %e at %l:%M%p"
12
+ edit_your_post: "edit your post"
13
+ forum: "Forum"
14
+ forums: "Discussion categories"
15
+ forums_introduction: "These are the broad filing areas we use to organise the forum. You can also see a list of "
16
+ if_not_you: "If that's not you, please"
17
+ latest_discussion: "Latest topics"
18
+ latest_posts: "Latest comments"
19
+ latest_topics: "all the latest topics"
20
+ logged_in_as: "You are logged in as %{name}."
21
+ new_topic: "new topic"
22
+ new_topic_button: "Submit topic"
23
+ new_topic_heading: "Start a new topic"
24
+ new_topic_here: "start a new<br />topic here"
25
+ no_forums: "No discussion categories defined here yet."
26
+ no_posts: "The forum is empty."
27
+ no_topics: "No talk here yet."
28
+ of: "of"
29
+ on: "on"
30
+ post: "comment"
31
+ post_count_from:
32
+ zero: ""
33
+ one: "One comment, from"
34
+ other: "%{count} comments, most recently from"
35
+ post_removed: "Post removed"
36
+ posted_and_updated_on: "posted %{posted} and updated %{updated}"
37
+ posted_by: "Posted by"
38
+ posted_on: "posted %{date}"
39
+ posts_introduction: "This is a list of all forum posts by date."
40
+ really_remove_post: "Are you sure you want to delete this comment?"
41
+ remove_post: "delete comment"
42
+ replied_on: "replied on %{date}"
43
+ rss_feed: "RSS feed"
44
+ separator: '&raquo;'
45
+ showing: "showing"
46
+ standard_date: "on %B %e, %Y"
47
+ started_topic_in: "started a new topic under"
48
+ started_topic_on: "started a new topic on %{date}"
49
+ time_recently: "on %A at %l:%M%p"
50
+ time_remaining_to_edit: "You have %{time} left to "
51
+ time_today: "today at %l:%M%p"
52
+ time_yesterday: "yesterday at %l:%M%p"
53
+ to: "to"
54
+ topic: "topic"
55
+ topic_empty: "Topic empty!"
56
+ topics: "topics"
57
+ topics_introduction: "This is a list of all the discussions going on here, with the most recently updated first. You can also see a more organised list of"
58
+ unknown_date: "unknown date"
data/config/routes.rb CHANGED
@@ -6,11 +6,11 @@ ActionController::Routing::Routes.draw do |map|
6
6
  forum.resources :pages, :has_many => [:posts], :has_one => [:topic]
7
7
  end
8
8
 
9
- # forum admin is nested under readers to save interface clutter
10
- # some time soon I'll add proper moderation of topics and posts
11
- map.namespace :admin, :member => { :remove => :get }, :path_prefix => 'admin/readers' do |admin|
9
+ map.namespace :admin, :member => { :remove => :get }, :path_prefix => 'admin/forum' do |admin|
12
10
  admin.resources :forums
13
- # admin.resources :topics
14
- # admin.resources :posts
11
+ admin.resources :topics
12
+ admin.resources :posts
15
13
  end
14
+
15
+ map.forum_home "/forum.:format", :controller => 'topics', :action => 'index'
16
16
  end
data/forum_extension.rb CHANGED
@@ -1,10 +1,14 @@
1
1
  require_dependency 'application_controller'
2
2
 
3
3
  class ForumExtension < Radiant::Extension
4
- version "0.6.1"
4
+ version "1.1.0"
5
5
  description "Nice clean forums and page comments for inclusion in your radiant site. Derived long ago from beast. Requires the reader extension and share_layouts."
6
6
  url "http://spanner.org/radiant/forum"
7
7
 
8
+ extension_config do |config|
9
+ config.gem "paperclip"
10
+ end
11
+
8
12
  def activate
9
13
  Reader.send :include, ForumReader
10
14
  ReaderNotifier.send :include, ForumReaderNotifier
@@ -18,12 +22,14 @@ class ForumExtension < Radiant::Extension
18
22
  unless defined? admin.forum # UI is a singleton
19
23
  Radiant::AdminUI.send :include, ForumAdminUI
20
24
  admin.forum = Radiant::AdminUI.load_default_forum_regions
21
- # admin.pages.edit.add :parts_bottom, "edit_commentability", :after => "edit_layout_and_type"
22
- admin.reader_configuration.show.add :settings, "forum", :after => "sender"
23
- admin.reader_configuration.edit.add :form, "edit_forum", :after => "edit_sender"
24
- if defined? Site && admin.sites
25
- Site.send :include, ForumSite
26
- end
25
+ end
26
+
27
+ # admin.pages.edit.add :parts_bottom, "edit_commentability", :after => "edit_layout_and_type"
28
+ admin.reader_configuration.show.add :settings, "forum", :after => "sender"
29
+ admin.reader_configuration.edit.add :form, "edit_forum", :after => "edit_sender"
30
+
31
+ if defined? Site && admin.sites
32
+ Site.send :include, ForumSite
27
33
  end
28
34
 
29
35
  if defined? RedCloth::DEFAULT_RULES # identifies redcloth 3
@@ -32,11 +38,12 @@ class ForumExtension < Radiant::Extension
32
38
  else
33
39
  RedCloth::TextileDoc.send :include, ForumRedCloth4
34
40
  end
35
-
36
- ApplicationHelper.send :include, ForumHelper
37
41
 
38
- tab("Readers") do
39
- add_item 'Forum', '/admin/readers/forums', :before => 'Settings'
42
+ tab("Forum") do
43
+ add_item 'Categories', '/admin/forum/forums'
44
+ add_item 'Topics', '/admin/forum/topics'
45
+ add_item 'Posts', '/admin/forum/posts'
46
+ add_item 'Settings', '/admin/forum/settings'
40
47
  end
41
48
  end
42
49
 
@@ -2,6 +2,7 @@ module ForumReadersController
2
2
 
3
3
  def self.included(base)
4
4
  base.class_eval do
5
+ helper :forum
5
6
  alias_method_chain :show, :forum
6
7
  end
7
8
  end
@@ -28,15 +28,25 @@ namespace :radiant do
28
28
  desc "Quick and dirty import from Vanilla."
29
29
  task :vanilla => :environment do
30
30
  require 'dbi'
31
-
31
+
32
+ clear = ENV['clear'] || false
33
+ if clear == 'true'
34
+ p "*** deleting all forum data"
35
+ Forum.delete_all
36
+ Topic.delete_all
37
+ Post.delete_all
38
+ end
39
+
32
40
  database = ENV['database'] || 'vanilla'
33
41
  user = ENV['user'] || 'forum'
34
42
  password = ENV['password'] || ''
43
+ host = ENV['host'] || '127.0.0.1'
35
44
  Page.current_site = Site.find_by_id(ENV['site']) if ENV['site'] && defined? Site
36
45
 
37
- dbh = DBI.connect("DBI:Mysql:#{database}", user, password)
46
+ p "*** connecting to #{database} database at #{host}"
47
+ dbh = DBI.connect("DBI:Mysql:#{database}:#{host}", user, password)
38
48
 
39
- dbh.select_all('select * from LUM_User') do | row |
49
+ dbh.select_all('select * from LUM_User') do |row|
40
50
  begin
41
51
  reader = Reader.find_or_create_by_email(row['Email'])
42
52
  if reader.new_record?
@@ -72,62 +82,75 @@ namespace :radiant do
72
82
  :description => row['Description'],
73
83
  :position => row['Priority']
74
84
  )
75
- p "Imported forum #{forum.name}"
85
+ p "Imported category #{forum.name}"
76
86
  end
77
87
  end
78
88
 
79
89
  posts = {}
80
90
  topic_posts = {}
81
- dbh.select_all('select * from LUM_Comment ORDER BY DateCreated ASC') do | row |
82
- posts[row['CommentID']] = row
83
- topic_posts[row['DiscussionID']] ||= []
84
- topic_posts[row['DiscussionID']].push(row)
91
+
92
+ dbh.select_all('select * from LUM_Comment ORDER BY DateCreated ASC') do |row|
93
+ hash = row.to_h
94
+ posts[row['CommentID'].to_i] = hash
95
+ topic_posts[row['DiscussionID'].to_i] ||= []
96
+ topic_posts[row['DiscussionID'].to_i].push(hash)
85
97
  end
86
98
 
87
99
  p "*** importing topics"
88
-
89
- dbh.select_all('select * from LUM_Discussion') do | row |
90
- first_post = posts[row['FirstCommentID']] || topic_posts[row['DiscussionID']].shift
91
- forum = Forum.find_by_old_id(row['CategoryID'])
92
- raise "no forum for old id #{row['CategoryID']}. Go fix." unless forum
100
+ dbh.select_all('select * from LUM_Discussion ORDER BY DiscussionID ASC') do |row|
101
+ old_id = row['DiscussionID'].to_i
102
+ cat_id = row['CategoryID'].to_i
103
+ fp_id = row['FirstCommentID'].to_i
104
+
105
+ unless first_post = posts[fp_id]
106
+ if topic_posts[old_id]
107
+ first_post = topic_posts[old_id].shift
108
+ p "! first post missing: shifted #{first_post['CommentID']} from stack."
109
+ end
110
+ end
111
+ forum = Forum.find_by_old_id(cat_id)
112
+ raise RuntimeError, "no forum for category id #{cat_id}. Go fix." unless forum
113
+
93
114
  if forum && first_post
94
115
  topic = forum.topics.build(
95
- :reader => Reader.find_by_old_id(row['AuthUserID']),
116
+ :reader => Reader.find_by_old_id(row['AuthUserID'].to_i),
96
117
  :name => row['Name'],
97
118
  :body => first_post['Body'],
98
119
  :created_at => row['DateCreated'],
99
- :replied_at => topic_posts[row['DiscussionID']].last['DateCreated'],
100
120
  :sticky => row['Sticky'],
101
121
  :locked => row['Closed'],
102
- :replied_by => Reader.find_by_old_id(row['LastUserID']),
103
122
  :old_id => row['DiscussionID']
104
123
  )
105
124
 
106
125
  begin
107
126
  if topic.save!
108
- p "Imported topic #{topic.name}"
109
-
110
- topic_posts[row['DiscussionID']].each do |post|
111
- unless post['CommentID'] == row['FirstCommentID'] # first post is created in a pre-validation filter, so we can't check for its old_id
112
- post = topic.posts.build(
113
- :forum => forum,
114
- :reader => Reader.find_by_old_id(post['AuthUserID']),
115
- :created_at => post['DateCreated'],
116
- :updated_at => post['DateEdited'],
117
- :body => post['Body'],
118
- :old_id => post['CommentID']
119
- )
120
- post.save
127
+ p "Imported topic #{old_id}: #{topic.name}. posts to import: #{topic_posts[old_id].length}"
128
+ topic_posts[old_id].each do |post|
129
+ begin
130
+ unless post['CommentID'] == row['FirstCommentID'] # first_post is created in a pre-validation filter, so we can't check for its old_id
131
+ topic.posts.build(
132
+ :forum => forum,
133
+ :reader => Reader.find_by_old_id(post['AuthUserID'].to_i),
134
+ :created_at => post['DateCreated'],
135
+ :updated_at => post['DateEdited'],
136
+ :body => post['Body'],
137
+ :old_id => post['CommentID']
138
+ ).save!
139
+ end
140
+ rescue ActiveRecord::RecordInvalid => e
141
+ p "!!! failed to import post #{post['CommentID']}: #{e.inspect}"
121
142
  end
122
143
  end
123
144
  p "... with #{topic.posts.count} post" + (topic.posts.count == 1 ? '' : 's')
124
145
  end
125
146
  rescue ActiveRecord::RecordInvalid => e
126
- p "!!! failed to import topic #{row['DiscussionID']}: #{e.inspect}"
147
+ p "!!! failed to import topic #{old_id}: #{e.inspect}"
148
+ p " (reader is #{topic.reader.inspect})"
149
+ p " (body is #{topic.body.inspect})"
127
150
  end
128
151
 
129
152
  else
130
- p "skipping topic #{row['Name']}: no post"
153
+ p "skipping topic #{row['Name']} (#{old_id}): no post"
131
154
  end
132
155
  end
133
156
  end
File without changes
File without changes
File without changes
File without changes
File without changes
Binary file
File without changes
File without changes
File without changes
@@ -0,0 +1,198 @@
1
+ /*
2
+ Sample jquery-based forum scripts.
3
+
4
+ Two functions are handled here:
5
+ * inline editing of posts by authors and administrators.
6
+ * attachment and upload of files.
7
+ */
8
+
9
+ (function($) {
10
+
11
+ $.ajaxSetup({
12
+ 'beforeSend': function(xhr) {
13
+ xhr.setRequestHeader("Accept", "text/javascript");
14
+ }
15
+ });
16
+
17
+ function EditablePost(container, conf) {
18
+ var self = this;
19
+ $.extend(self, {
20
+ container: container,
21
+ edit_links: $(container.find('a.edit_post')),
22
+ edit_url: null,
23
+ form: null,
24
+ stumbit: null,
25
+ textarea: null,
26
+ showing: false,
27
+ header: null,
28
+ wrapper: null,
29
+ body_holder: null,
30
+ form_holder: null,
31
+ form_waiter: null,
32
+ uploader: null,
33
+
34
+ initEdit: function(event) {
35
+ squash(event);
36
+ self.edit_links.addClass('waiting');
37
+ if (self.showing) self.cancelEdit(); // toggle form off again
38
+ else if (self.form_holder) self.showForm(); // show previously-loaded form
39
+ else { // load form
40
+ self.header = $(container.find('.post_header'));
41
+ self.wrapper = $(container.find('.post_wrapper'));
42
+ self.body_holder = $(container.find('.post_body'));
43
+ self.edit_links = $(container.find('a.edit_post'));
44
+ self.getForm();
45
+ }
46
+ },
47
+
48
+ getForm: function () {
49
+ if (self.edit_url) self.reusableFormHolder().load(self.edit_url, self.captureForm);
50
+ },
51
+
52
+ // lazy-load a container div that is then held in memory instead of
53
+ // going back to the server for another edit form
54
+ reusableFormHolder: function () {
55
+ if (self.form_holder) return self.form_holder;
56
+ self.form_holder = $('<div class="post_form" />');
57
+ self.wrapper.prepend(self.form_holder);
58
+ return self.form_holder;
59
+ },
60
+
61
+ captureForm: function () {
62
+ self.form = self.form_holder.find('form');
63
+ self.textarea = self.form.find('textarea');
64
+ self.form_holder.find('a.cancel').click(self.cancelEdit);
65
+ self.uploader = new UploadStack(self.form_holder.find('div.upload_stack'));
66
+ self.stumbit = self.form_holder.find('div.buttons');
67
+ self.form.submit(self.sendForm);
68
+ self.showForm();
69
+ },
70
+
71
+ showForm: function () {
72
+ self.edit_links.removeClass('waiting');
73
+ self.body_holder.hide();
74
+ self.reusableFormHolder().show();
75
+ self.showing = true;
76
+ },
77
+
78
+ hideForm: function () {
79
+ self.reusableFormHolder().hide();
80
+ self.body_holder.show();
81
+ self.showing = false;
82
+ },
83
+
84
+ sendForm: function (event) {
85
+ self.form_waiter = $('<p class="waiting">Please wait</p>');
86
+ self.stumbit.hide();
87
+ self.form_waiter.after(self.stumbit);
88
+
89
+ console.log("uploader:", self.uploader, self.uploader.hasUploads());
90
+
91
+ if (self.uploader && self.uploader.hasUploads()) {
92
+ console.log("yes uploads");
93
+ return true; // can't send uploads over xmlhttp so we allow the event to pass through
94
+ } else {
95
+ console.log("no uploads");
96
+ squash(event);
97
+ $.post(self.form.attr('action'), self.form.serialize(), self.finishEdit);
98
+ }
99
+ },
100
+
101
+ cancelEdit: function (event) {
102
+ squash(event);
103
+ self.edit_links.removeClass('waiting');
104
+ self.hideForm();
105
+ },
106
+
107
+ finishEdit: function (results) {
108
+ self.form_holder.remove();
109
+ self.form_holder = null;
110
+ self.container.html(results);
111
+ self.body_holder.animate( { backgroundColor: 'pink' }, 200).animate( { backgroundColor: 'white' }, 1000);
112
+ self.container.editable_post();
113
+ }
114
+ });
115
+
116
+ self.edit_url = self.edit_links.attr('href');
117
+ self.edit_links.click(self.initEdit);
118
+ }
119
+
120
+ $.tools.post = {
121
+ conf: {
122
+
123
+ }
124
+ };
125
+
126
+ $.fn.editable_post = function(conf) {
127
+ conf = $.extend({}, $.tools.post.conf, conf);
128
+ this.each(function() {
129
+ el = new EditablePost($(this), conf);
130
+ });
131
+ return this;
132
+ };
133
+
134
+ function UploadStack(container) {
135
+ var self = this;
136
+ $.extend(self, {
137
+ container: container,
138
+ attachments_list: container.find('ul.attachments'),
139
+ uploads_list: container.find('ul.uploads'),
140
+ selector: container.find('div.selector'),
141
+ file_field: container.find('div.selector').find('input'),
142
+
143
+ addUpload: function(event) {
144
+ squash(event);
145
+ var upload_field = self.file_field.clone();
146
+ var container = $('<li class="attachment">' + upload_field.val() + '</li>');
147
+ container.append(upload_field);
148
+ container.add_remover();
149
+ container.appendTo(self.uploads_list).slideDown('slow');
150
+ self.file_field.val(null);
151
+ self.selector.find('a').text = 'attach another file';
152
+ },
153
+
154
+ hasAttachments: function () {
155
+ return self.attachments_list.find('li').length > 0;
156
+ },
157
+
158
+ hasUploads: function () {
159
+ return self.uploads_list.find('li').length > 0;
160
+ }
161
+ });
162
+ self.attachments_list.find('li').add_remover();
163
+ self.file_field.change(self.addUpload);
164
+ }
165
+
166
+ $.tools.stack = {
167
+ conf: {
168
+
169
+ }
170
+ };
171
+
172
+ $.fn.upload_stack = function(conf) {
173
+ conf = $.extend({}, $.tools.stack.conf, conf);
174
+ this.each(function() {
175
+ el = new UploadStack($(this), conf);
176
+ });
177
+ return this;
178
+ };
179
+
180
+ $.fn.add_remover = function() {
181
+ this.each(function() {
182
+ var self = $(this);
183
+ var remover = $('<a href="#" class="remove">remove</a>');
184
+ remover.click(function (event) {
185
+ squash(event);
186
+ self.slideUp('500', function() { self.remove(); });
187
+ });
188
+ self.append(remover);
189
+ });
190
+ return self;
191
+ };
192
+
193
+ })(jQuery);
194
+
195
+ $(function() {
196
+ $(".post").editable_post({});
197
+ $(".upload_stack").upload_stack({});
198
+ });