bumble 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. data/.gitignore +16 -0
  2. data/.rvmrc +1 -0
  3. data/Gemfile +4 -0
  4. data/Rakefile +1 -0
  5. data/Readme.mdown +8 -0
  6. data/app/controllers/bumble_controller.rb +45 -0
  7. data/app/controllers/comments_controller.rb +99 -0
  8. data/app/controllers/password_resets_controller.rb +34 -0
  9. data/app/controllers/posts_controller.rb +103 -0
  10. data/app/controllers/user_sessions_controller.rb +22 -0
  11. data/app/controllers/users_controller.rb +60 -0
  12. data/app/helpers/bumble_helper.rb +50 -0
  13. data/app/models/asset.rb +57 -0
  14. data/app/models/comment.rb +78 -0
  15. data/app/models/notifier.rb +35 -0
  16. data/app/models/post.rb +57 -0
  17. data/app/models/posts/blog.rb +3 -0
  18. data/app/models/posts/code.rb +3 -0
  19. data/app/models/posts/image.rb +3 -0
  20. data/app/models/posts/link.rb +7 -0
  21. data/app/models/posts/quote.rb +3 -0
  22. data/app/models/posts/video.rb +4 -0
  23. data/app/models/user.rb +35 -0
  24. data/app/models/user_session.rb +2 -0
  25. data/app/views/comments/_comment.html.haml +14 -0
  26. data/app/views/comments/_form.html.haml +9 -0
  27. data/app/views/comments/edit.html.haml +7 -0
  28. data/app/views/comments/index.atom.builder +15 -0
  29. data/app/views/comments/new.html.haml +7 -0
  30. data/app/views/layouts/_sidebar.html.haml +20 -0
  31. data/app/views/layouts/bumble.html.haml +35 -0
  32. data/app/views/notifier/activation_confirmation.erb +5 -0
  33. data/app/views/notifier/activation_instructions.erb +5 -0
  34. data/app/views/notifier/new_comment_alert.erb +8 -0
  35. data/app/views/notifier/password_reset_instructions.erb +8 -0
  36. data/app/views/password_resets/edit.html.haml +10 -0
  37. data/app/views/password_resets/new.html.haml +7 -0
  38. data/app/views/posts/_form.html.haml +10 -0
  39. data/app/views/posts/_post.html.haml +16 -0
  40. data/app/views/posts/_preview.html.haml +8 -0
  41. data/app/views/posts/edit.html.haml +10 -0
  42. data/app/views/posts/forms/_blog.html.haml +4 -0
  43. data/app/views/posts/forms/_code.html.haml +6 -0
  44. data/app/views/posts/forms/_image.html.haml +9 -0
  45. data/app/views/posts/forms/_link.html.haml +6 -0
  46. data/app/views/posts/forms/_quote.html.haml +4 -0
  47. data/app/views/posts/forms/_video.html.haml +6 -0
  48. data/app/views/posts/index.atom.builder +15 -0
  49. data/app/views/posts/index.html.haml +21 -0
  50. data/app/views/posts/new.html.haml +8 -0
  51. data/app/views/posts/search.html.haml +27 -0
  52. data/app/views/posts/show.html.haml +22 -0
  53. data/app/views/posts/types/_blog.html.haml +4 -0
  54. data/app/views/posts/types/_code.html.haml +7 -0
  55. data/app/views/posts/types/_image.html.haml +3 -0
  56. data/app/views/posts/types/_link.html.haml +3 -0
  57. data/app/views/posts/types/_quote.html.haml +2 -0
  58. data/app/views/posts/types/_video.html.haml +5 -0
  59. data/app/views/user_sessions/new.html.haml +10 -0
  60. data/app/views/users/_form.html.haml +12 -0
  61. data/app/views/users/_user.html.haml +3 -0
  62. data/app/views/users/delete.html.haml +5 -0
  63. data/app/views/users/edit.html.haml +8 -0
  64. data/app/views/users/index.html.haml +3 -0
  65. data/app/views/users/new.html.haml +7 -0
  66. data/app/views/users/show.html.haml +6 -0
  67. data/bumble.gemspec +31 -0
  68. data/config/initializers/bumble.rb +9 -0
  69. data/config/initializers/haml.rb +2 -0
  70. data/config/initializers/html5_forms.rb +163 -0
  71. data/config/initializers/inflections.rb +10 -0
  72. data/config/initializers/mime_types.rb +5 -0
  73. data/config/initializers/will_paginate.rb +3 -0
  74. data/config/routes.rb +29 -0
  75. data/lib/bumble.rb +18 -0
  76. data/lib/bumble/version.rb +3 -0
  77. data/lib/generators/bumble/assets_generator.rb +10 -0
  78. data/lib/generators/bumble/migrations_generator.rb +40 -0
  79. data/lib/generators/bumble/templates/assets/images/.gitignore +0 -0
  80. data/lib/generators/bumble/templates/assets/images/close.png +0 -0
  81. data/lib/generators/bumble/templates/assets/images/email.png +0 -0
  82. data/lib/generators/bumble/templates/assets/images/feed.png +0 -0
  83. data/lib/generators/bumble/templates/assets/images/image.png +0 -0
  84. data/lib/generators/bumble/templates/assets/images/loading.gif +0 -0
  85. data/lib/generators/bumble/templates/assets/images/preview.png +0 -0
  86. data/lib/generators/bumble/templates/assets/images/shortcut.png +0 -0
  87. data/lib/generators/bumble/templates/assets/images/youtube.png +0 -0
  88. data/lib/generators/bumble/templates/assets/javascripts/admin.js +95 -0
  89. data/lib/generators/bumble/templates/assets/javascripts/bumble.js +76 -0
  90. data/lib/generators/bumble/templates/assets/javascripts/iphone.js +7 -0
  91. data/lib/generators/bumble/templates/assets/javascripts/jquery.autogrow.js +37 -0
  92. data/lib/generators/bumble/templates/assets/javascripts/jquery.cookie.js +96 -0
  93. data/lib/generators/bumble/templates/assets/javascripts/jquery.form.js +665 -0
  94. data/lib/generators/bumble/templates/assets/javascripts/jquery.hint.js +44 -0
  95. data/lib/generators/bumble/templates/assets/javascripts/jquery.jgrow.js +85 -0
  96. data/lib/generators/bumble/templates/assets/javascripts/jquery.js +154 -0
  97. data/lib/generators/bumble/templates/assets/javascripts/jquery.ui.core.js +18 -0
  98. data/lib/generators/bumble/templates/assets/javascripts/jquery.ui.tabs.js +13 -0
  99. data/lib/generators/bumble/templates/assets/javascripts/jquery.ui.widget.js +18 -0
  100. data/lib/generators/bumble/templates/assets/javascripts/jquery.validate.js +16 -0
  101. data/lib/generators/bumble/templates/assets/javascripts/modernizr.js +13 -0
  102. data/lib/generators/bumble/templates/assets/javascripts/ui.core.js +289 -0
  103. data/lib/generators/bumble/templates/assets/javascripts/ui.tabs.js +593 -0
  104. data/lib/generators/bumble/templates/migrations/add_approved_to_comments.rb +15 -0
  105. data/lib/generators/bumble/templates/migrations/add_delta_to_posts.rb +9 -0
  106. data/lib/generators/bumble/templates/migrations/add_missing_indexes.rb +24 -0
  107. data/lib/generators/bumble/templates/migrations/add_url_to_users.rb +9 -0
  108. data/lib/generators/bumble/templates/migrations/create_assets.rb +18 -0
  109. data/lib/generators/bumble/templates/migrations/create_comments.rb +19 -0
  110. data/lib/generators/bumble/templates/migrations/create_posts.rb +25 -0
  111. data/lib/generators/bumble/templates/migrations/create_users.rb +28 -0
  112. data/lib/generators/bumble/templates/migrations/full_text_search.rb +13 -0
  113. data/public/stylesheets/sass/bumble.sass +423 -0
  114. data/public/stylesheets/sass/iphone.sass +79 -0
  115. data/public/stylesheets/sass/print.sass +2 -0
  116. data/public/stylesheets/sass/reset.sass +49 -0
  117. data/test/factories.rb +28 -0
  118. data/test/functional/comments_controller_test.rb +177 -0
  119. data/test/functional/password_resets_controller_test.rb +61 -0
  120. data/test/functional/posts_controller_test.rb +181 -0
  121. data/test/test_helper.rb +113 -0
  122. data/test/unit/comment_test.rb +18 -0
  123. data/test/unit/post_test.rb +18 -0
  124. data/test/unit/user_test.rb +37 -0
  125. metadata +403 -0
@@ -0,0 +1,16 @@
1
+ .autotest
2
+ .DS_Store
3
+ config/database.yml
4
+ config/*.sphinx.conf
5
+ coverage/
6
+ coverage.data
7
+ db/schema.rb
8
+ db/*.sqlite3
9
+ db/sphinx/*
10
+ log/*.log
11
+ log/*.pid
12
+ public/system/**/*
13
+ public/stylesheets/*.css
14
+ public/sitemap*
15
+ tmp/**/*
16
+ tmp/restart.txt
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.8.7
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in bumble.gemspec
4
+ gemspec
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,8 @@
1
+ # Bumble
2
+
3
+ Blogging plugin provided as a rails engine
4
+
5
+ ## Requirements
6
+
7
+ * A rails 3.0.x app
8
+ * Postgres database
@@ -0,0 +1,45 @@
1
+ class BumbleController < ActionController::Base
2
+ protect_from_forgery
3
+
4
+ helper_method :current_user_session, :current_user, :iphone?
5
+
6
+ private
7
+
8
+ def iphone?
9
+ request.env["HTTP_USER_AGENT"] && request.env["HTTP_USER_AGENT"][/iP[^a].+iPhone/]
10
+ end
11
+
12
+ def current_user_session
13
+ return @current_user_session if defined?(@current_user_session)
14
+ @current_user_session = UserSession.find
15
+ end
16
+
17
+ def current_user
18
+ return @current_user if defined?(@current_user)
19
+ @current_user = current_user_session && current_user_session.user
20
+ end
21
+
22
+ def require_user
23
+ access_denied('You must be logged in to do that!') unless current_user
24
+ end
25
+
26
+ def store_location
27
+ session[:return_to] = request.request_uri
28
+ end
29
+
30
+ def access_denied(message = 'Access Denied')
31
+ if request.xhr?
32
+ render :text => message, :status => :unauthorized
33
+ else
34
+ store_location
35
+ flash[:error] = message
36
+ redirect_to login_path
37
+ end
38
+ return false
39
+ end
40
+
41
+ def redirect_back_or_default(default = root_path)
42
+ redirect_to(session[:return_to] || default)
43
+ session[:return_to] = nil
44
+ end
45
+ end
@@ -0,0 +1,99 @@
1
+ class CommentsController < BumbleController
2
+
3
+ before_filter :require_user, :only => [:edit, :update, :destroy]
4
+
5
+ def create
6
+ @post = Post.find(params[:post_id])
7
+ @comment = Comment.new(params[:comment])
8
+ @comment.user = current_user
9
+ @comment.request = request
10
+ @comment.post = @post
11
+
12
+ if @comment.save
13
+ respond_to do |format|
14
+ format.html do
15
+ if @comment.approved?
16
+ flash[:notice] = 'Create successful!'
17
+ else
18
+ flash[:notice] = "Your comment looks like spam, it will show up once it's been approved."
19
+ end
20
+ redirect_to post_path(@post, :anchor => dom_id(@comment))
21
+ end
22
+ format.js do
23
+ if @comment.approved?
24
+ render @comment, :content_type => :html
25
+ else
26
+ render :text => "Your comment looks like spam, it will show up once it's been approved.", :status => 406, :content_type => :html
27
+ end
28
+ end
29
+ end
30
+ else
31
+ respond_to do |format|
32
+ format.html { render :action => "new" }
33
+ format.js { render :text => @comment.errors.full_messages.join(', ').capitalize, :status => 403, :content_type => :html }
34
+ end
35
+ end
36
+ end
37
+
38
+ def index
39
+ @post = Post.find(params[:post_id])
40
+ @comments = @post.comments
41
+ respond_to do |format|
42
+ format.html { redirect_to post_path(@post, :anchor => 'comments') }
43
+ format.atom {}
44
+ format.js { render @comments }
45
+ end
46
+ end
47
+
48
+ def destroy
49
+ @comment = Comment.find(params[:id])
50
+ @comment.destroy
51
+ respond_to do |format|
52
+ format.html do
53
+ flash[:notice] = 'Record deleted!'
54
+ redirect_to post_path(@comment.post)
55
+ end
56
+ format.js { render :nothing => true }
57
+ end
58
+ end
59
+
60
+ def update
61
+ @post = Post.find(params[:post_id])
62
+ @comment = Comment.find(params[:id])
63
+ if @comment.update_attributes(params[:comment])
64
+ respond_to do |format|
65
+ format.html do
66
+ flash[:notice] = 'Save successful!'
67
+ redirect_to post_path(@post, :anchor => dom_id(@comment))
68
+ end
69
+ end
70
+ else
71
+ render :action => 'edit'
72
+ end
73
+ end
74
+
75
+ def edit
76
+ @comment = Comment.find(params[:id])
77
+ end
78
+
79
+ def new
80
+ @comment = Comment.new
81
+ end
82
+
83
+ def show
84
+ @comment = Comment.find(params[:id])
85
+ redirect_to post_path(@comment.post, :anchor => dom_id(@comment))
86
+ end
87
+
88
+ def approve
89
+ @comment = Comment.find(params[:id])
90
+ @comment.mark_as_ham!
91
+ redirect_to post_path(@comment.post, :anchor => dom_id(@comment))
92
+ end
93
+
94
+ def reject
95
+ @comment = Comment.find(params[:id])
96
+ @comment.mark_as_spam!
97
+ redirect_to post_path(@comment.post, :anchor => dom_id(@comment))
98
+ end
99
+ end
@@ -0,0 +1,34 @@
1
+ class PasswordResetsController < BumbleController
2
+ before_filter :load_user_using_perishable_token, :only => [:edit, :update]
3
+
4
+ def create
5
+ if @user = User.find_by_email(params[:email])
6
+ @user.deliver_password_reset_instructions!
7
+ flash[:notice] = "Instructions to reset your password have been emailed to you. Please check your email."
8
+ redirect_to root_path
9
+ else
10
+ flash[:notice] = "No user was found with that email address"
11
+ render :action => :new
12
+ end
13
+ end
14
+
15
+ def update
16
+ @user.password = params[:user][:password]
17
+ @user.password_confirmation = params[:user][:password_confirmation]
18
+ if @user.save
19
+ flash[:notice] = "Password successfully updated"
20
+ redirect_to root_url
21
+ else
22
+ render :action => :edit
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def load_user_using_perishable_token
29
+ unless @user = User.find_using_perishable_token(params[:id])
30
+ flash[:notice] = "We're sorry, but we could not locate your account. "
31
+ redirect_to password_resets_path
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,103 @@
1
+ class PostsController < BumbleController
2
+ before_filter :require_user, :except => [:index, :show, :search, :sitemap]
3
+
4
+ def new
5
+ @post = build_object
6
+ end
7
+
8
+ def destroy
9
+ @post = Post.find(params[:id])
10
+ @post.destroy
11
+ respond_to do |format|
12
+ format.html do
13
+ flash[:notice] = "Record deleted!"
14
+ redirect_to posts_path
15
+ end
16
+ format.js { render :nothing => true }
17
+ end
18
+ end
19
+
20
+ def show
21
+ @post = current_model.find_by_permalink_or_id(params[:id])
22
+ fresh_when(:etag => [@post, @post.comments]) unless current_user
23
+ end
24
+
25
+ def index
26
+ respond_to do |format|
27
+ format.html do
28
+ @posts = current_model.paginate :page => params[:page],
29
+ :order => 'published_at DESC',
30
+ :per_page => per_page
31
+ fresh_when(:etag => [@posts, Comment.last]) unless current_user
32
+ end
33
+ format.atom do
34
+ @posts = current_model.find(:all,
35
+ :order => 'published_at DESC',
36
+ :limit => 50)
37
+ end
38
+ end
39
+ end
40
+
41
+ def create
42
+ build_object
43
+ @post.user = current_user
44
+ if params[:commit] == "Preview"
45
+ @post.valid?
46
+ respond_to do |format|
47
+ format.js { render :partial => 'preview', :locals => {:post => @post}, :content_type => :html }
48
+ format.html do
49
+ flash[:notice] = 'Create successful!'
50
+ redirect_to posts_path
51
+ end
52
+ end
53
+ else
54
+ if @post.save
55
+ respond_to do |format|
56
+ format.js { render :partial => 'post', :locals => {:post => @post}, :content_type => :html }
57
+ format.html do
58
+ flash[:notice] = 'Create successful!'
59
+ redirect_to post_path(@post)
60
+ end
61
+ end
62
+ else
63
+ respond_to do |format|
64
+ format.js { render :text => @post.errors.full_messages.join(', ').capitalize, :status => 403, :content_type => :html }
65
+ format.html { render :action => "new" }
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ def search
72
+ @posts = current_model.search(params[:query]).paginate :page => params[:page],
73
+ :order => 'published_at DESC',
74
+ :per_page => per_page
75
+ respond_to do |format|
76
+ format.html
77
+ format.atom { render :action => :index}
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ def build_object
84
+ Post.types.each do |t|
85
+ if params.include?(t.downcase)
86
+ @post = Object.const_get(t).new(params[t.downcase])
87
+ end
88
+ end
89
+ @post ||= Blog.new
90
+ end
91
+
92
+ def per_page
93
+ (iphone? ? 5 : 10)
94
+ end
95
+
96
+ def current_model_name
97
+ params.include?(Post.types.map(&:downcase)) || controller_name.singularize.camelize
98
+ end
99
+
100
+ def current_model
101
+ current_user ? Post.scoped : Post.published.all_public
102
+ end
103
+ end
@@ -0,0 +1,22 @@
1
+ class UserSessionsController < BumbleController
2
+
3
+ def new
4
+ @user_session = UserSession.new
5
+ end
6
+
7
+ def create
8
+ @user_session = UserSession.new
9
+ if @user_session.save
10
+ flash[:notice] = 'Logged in successfully'
11
+ redirect_back_or_default root_path
12
+ else
13
+ render :action => 'new'
14
+ end
15
+ end
16
+
17
+ def destroy
18
+ current_user_session.destroy
19
+ flash[:notice] = 'Logged out successfully'
20
+ redirect_to root_path
21
+ end
22
+ end
@@ -0,0 +1,60 @@
1
+ class UsersController < BumbleController
2
+ before_filter :require_user, :except => [:activate]
3
+
4
+ def new
5
+ @user = User.new
6
+ end
7
+
8
+ def edit
9
+ @user = User.find(params[:id])
10
+ end
11
+
12
+ def index
13
+ @users = User.paginate(:page => params[:page])
14
+ end
15
+
16
+ def destroy
17
+ @user = User.find(params[:id])
18
+ @user.destroy
19
+ respond_to do |format|
20
+ format.html do
21
+ flash[:notice] = "User Deleted"
22
+ redirect_to users_path
23
+ end
24
+ end
25
+ end
26
+
27
+ def update
28
+ @user = User.find(params[:id])
29
+ @user.update_attributes(params[:user])
30
+ if @user.save
31
+ flash[:notice] = 'User updated successfully'
32
+ redirect_to posts_path
33
+ else
34
+ render :action => 'edit'
35
+ end
36
+ end
37
+
38
+ def create
39
+ @user = User.new(params[:user])
40
+ if @user.save_without_session_maintenance
41
+ @user.deliver_activation_instructions!
42
+ flash[:notice] = "Your account has been created. Please check your e-mail for your account activation instructions!"
43
+ redirect_to login_path
44
+ else
45
+ render :action => :new
46
+ end
47
+ end
48
+
49
+ def activate
50
+ @user = User.find_using_perishable_token(params[:activation_code], 1.week) || (raise Exception)
51
+ if @user.activate!
52
+ @user.deliver_activation_confirmation!
53
+ flash[:notice] = "Your account has been activated."
54
+ UserSession.create(@user)
55
+ redirect_to root_path
56
+ else
57
+ render :action => :new
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,50 @@
1
+ module BumbleHelper
2
+ def markdown(text)
3
+ text.blank? ? "" : sanitize(RDiscount.new(text).to_html, :tags => %w(a p pre code b strong em i strike ul ol li blockquote br))
4
+ end
5
+
6
+ def render_post(post, title = true)
7
+ render :partial => "posts/types/#{post.class.to_s.downcase}.html.haml", :object => post, :locals => {:title => title}
8
+ end
9
+
10
+ def format_datetime_ago(time)
11
+ if time > 5.days.ago
12
+ formatted = time_ago_in_words(time).capitalize
13
+ formatted.insert(0, 'Live in ') if time > Time.now
14
+ formatted.insert(formatted.length, ' ago') if time < Time.now
15
+ end
16
+
17
+ content_tag :abbr, formatted || time.strftime('%d %b %y'), :title => time.iso8601, :class => 'published updated'
18
+ end
19
+
20
+ def page_title
21
+ escape_once(strip_tags([DOMAIN, @page_title].compact.reject(&:blank?).join(' | ')))
22
+ end
23
+
24
+ def page_description
25
+ escape_once((@page_description || 'TODO'))
26
+ end
27
+
28
+ def youtube_embed(link, width = 500, height = 300)
29
+ matches = link.match(/http:\/\/(www.)?youtube\.com\/watch\?v=([A-Za-z0-9._%-]*)(\&\S+)?/)
30
+ youtube_id = matches[2]
31
+ if youtube_id
32
+ content_tag :object, :width => width, :height => height, :data => "http://www.youtube.com/v/#{youtube_id}" do
33
+ tag(:param, :name => 'movie', :value => "http://www.youtube.com/v/#{youtube_id}") +
34
+ tag(:param, :name => 'allowFullScreen', :value => 'true') +
35
+ tag(:param, :name => 'allowscriptaccess', :value => 'always') +
36
+ tag(:embed, :src => "http://www.youtube.com/v/#{youtube_id}", :type => 'application/x-shockwave-flash', :allowscriptaccess => 'always', :allowfullscreen => 'true', :width => width, :height => height)
37
+ end
38
+ else
39
+ link_to link, link
40
+ end
41
+ end
42
+
43
+ def spam_link_for(comment)
44
+ if comment.approved?
45
+ link_to 'Spam', reject_comment_path(comment)
46
+ else
47
+ link_to 'Approve', approve_comment_path(comment)
48
+ end
49
+ end
50
+ end