bumble 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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