miniblog 1.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +7 -0
  2. data/.autotest +5 -0
  3. data/.gitignore +17 -0
  4. data/.rspec +2 -0
  5. data/.simplecov +13 -0
  6. data/.travis.yml +17 -0
  7. data/CHANGELOG.md +7 -0
  8. data/Gemfile +20 -0
  9. data/Gemfile.4.1 +19 -0
  10. data/MIT-LICENSE +20 -0
  11. data/README.md +86 -0
  12. data/Rakefile +29 -0
  13. data/app/assets/images/crowdblog/.gitkeep +0 -0
  14. data/app/assets/javascripts/miniblog.js +7 -0
  15. data/app/assets/javascripts/miniblog/alerts.js +5 -0
  16. data/app/assets/javascripts/miniblog/form-errors.js +3 -0
  17. data/app/assets/stylesheets/application.css +3 -0
  18. data/app/assets/stylesheets/miniblog.css +5 -0
  19. data/app/assets/stylesheets/miniblog/bootstrap_and_overrides.css.scss +1 -0
  20. data/app/assets/stylesheets/miniblog/posts.css.scss +34 -0
  21. data/app/controllers/miniblog/admin/assets_controller.rb +20 -0
  22. data/app/controllers/miniblog/admin/base_controller.rb +7 -0
  23. data/app/controllers/miniblog/admin/posts_controller.rb +63 -0
  24. data/app/controllers/miniblog/admin/preview_controller.rb +8 -0
  25. data/app/controllers/miniblog/admin/states_controller.rb +11 -0
  26. data/app/controllers/miniblog/admin/transitions_controller.rb +22 -0
  27. data/app/controllers/miniblog/application_controller.rb +14 -0
  28. data/app/controllers/miniblog/posts_controller.rb +7 -0
  29. data/app/helpers/miniblog/admin/posts_helper.rb +18 -0
  30. data/app/helpers/miniblog/application_helper.rb +4 -0
  31. data/app/models/miniblog/asset.rb +7 -0
  32. data/app/models/miniblog/post.rb +135 -0
  33. data/app/models/miniblog/status_change_record.rb +6 -0
  34. data/app/models/miniblog/user.rb +32 -0
  35. data/app/presenters/miniblog/post_presenter.rb +13 -0
  36. data/app/uploaders/attachment_uploader.rb +48 -0
  37. data/app/views/layouts/miniblog/admin/base.html.erb +26 -0
  38. data/app/views/miniblog/admin/posts/_form.html.erb +10 -0
  39. data/app/views/miniblog/admin/posts/_form.html.slim +27 -0
  40. data/app/views/miniblog/admin/posts/_post.html.erb +14 -0
  41. data/app/views/miniblog/admin/posts/_post.html.slim +20 -0
  42. data/app/views/miniblog/admin/posts/edit.html.erb +19 -0
  43. data/app/views/miniblog/admin/posts/edit.html.slim +14 -0
  44. data/app/views/miniblog/admin/posts/index.html.erb +5 -0
  45. data/app/views/miniblog/admin/posts/new.html.erb +17 -0
  46. data/app/views/miniblog/admin/posts/new.html.slim +12 -0
  47. data/app/views/miniblog/application/_navbar.html.erb +19 -0
  48. data/app/views/miniblog/application/_notices.html.slim +6 -0
  49. data/app/views/miniblog/posts/_post.html.slim +4 -0
  50. data/app/views/miniblog/posts/index.html.slim +1 -0
  51. data/config/cucumber.yml +9 -0
  52. data/config/initializers/date_formats.rb +1 -0
  53. data/config/initializers/state_machine.rb +4 -0
  54. data/config/locales/devise.en.yml +57 -0
  55. data/config/routes.rb +26 -0
  56. data/db/migrate/20120217213920_create_miniblog_posts.rb +17 -0
  57. data/db/migrate/20120219071614_create_miniblog_assets.rb +10 -0
  58. data/lib/generators/miniblog/views_generator.rb +11 -0
  59. data/lib/miniblog.rb +18 -0
  60. data/lib/miniblog/devise/failure_app.rb +9 -0
  61. data/lib/miniblog/engine.rb +9 -0
  62. data/lib/miniblog/rspec.rb +1 -0
  63. data/lib/miniblog/rspec/miniblog_shared_examples.rb +77 -0
  64. data/lib/miniblog/version.rb +3 -0
  65. data/miniblog.gemspec +40 -0
  66. data/script/cucumber +10 -0
  67. data/script/rails +8 -0
  68. data/spec/dummy/README.rdoc +261 -0
  69. data/spec/dummy/Rakefile +7 -0
  70. data/spec/dummy/app/assets/javascripts/application.js +16 -0
  71. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  72. data/spec/dummy/app/controllers/application_controller.rb +2 -0
  73. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  74. data/spec/dummy/app/mailers/.gitkeep +0 -0
  75. data/spec/dummy/app/models/.gitkeep +0 -0
  76. data/spec/dummy/app/models/user.rb +3 -0
  77. data/spec/dummy/app/views/home/show.html.slim +4 -0
  78. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  79. data/spec/dummy/bin/bundle +3 -0
  80. data/spec/dummy/bin/rails +4 -0
  81. data/spec/dummy/bin/rake +4 -0
  82. data/spec/dummy/config.ru +4 -0
  83. data/spec/dummy/config/application.rb +28 -0
  84. data/spec/dummy/config/boot.rb +4 -0
  85. data/spec/dummy/config/database.yml +25 -0
  86. data/spec/dummy/config/environment.rb +5 -0
  87. data/spec/dummy/config/environments/development.rb +37 -0
  88. data/spec/dummy/config/environments/production.rb +82 -0
  89. data/spec/dummy/config/environments/test.rb +39 -0
  90. data/spec/dummy/config/initializers/assets.rb +8 -0
  91. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  92. data/spec/dummy/config/initializers/carrierwave.rb +18 -0
  93. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  94. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  95. data/spec/dummy/config/initializers/inflections.rb +16 -0
  96. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  97. data/spec/dummy/config/initializers/miniblog.rb +2 -0
  98. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  99. data/spec/dummy/config/initializers/session_store.rb +3 -0
  100. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  101. data/spec/dummy/config/locales/en.yml +23 -0
  102. data/spec/dummy/config/routes.rb +3 -0
  103. data/spec/dummy/config/secrets.yml +22 -0
  104. data/spec/dummy/db/schema.rb +50 -0
  105. data/spec/dummy/db/seed.rb +6 -0
  106. data/spec/dummy/lib/assets/.gitkeep +0 -0
  107. data/spec/dummy/lib/tasks/cucumber.rake +65 -0
  108. data/spec/dummy/public/404.html +26 -0
  109. data/spec/dummy/public/422.html +26 -0
  110. data/spec/dummy/public/500.html +25 -0
  111. data/spec/dummy/public/favicon.ico +0 -0
  112. data/spec/dummy/script/rails +6 -0
  113. data/spec/features/miniblog_spec.rb +5 -0
  114. data/spec/generators/miniblog/views_generator_spec.rb +16 -0
  115. data/spec/models/post_spec.rb +163 -0
  116. data/spec/spec_helper.rb +58 -0
  117. data/vendor/assets/javascripts/markdown.js +1470 -0
  118. data/vendor/assets/javascripts/uploader/jquery.html5uploader.js +148 -0
  119. metadata +464 -0
@@ -0,0 +1,22 @@
1
+ module Miniblog
2
+ module Admin
3
+ class TransitionsController < Miniblog::Admin::BaseController
4
+ before_filter :load_post, only: [:create]
5
+
6
+ def create
7
+ namespace = '_as_publisher' if current_user.is_publisher?
8
+ @post.send "#{params[:transition]}#{namespace}"
9
+ status = @post.status_change_records.build(user: current_user, state: params[:transition])
10
+ status.save
11
+ redirect_to admin_posts_path
12
+ end
13
+
14
+ private
15
+
16
+ def load_post
17
+ post = Post.scoped_for(current_user).find(params[:id])
18
+ @post = PostPresenter.new(post, current_user)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,14 @@
1
+ module Miniblog
2
+ class ApplicationController < ::ApplicationController
3
+ def method_missing(method_name)
4
+ if method_name == :current_user
5
+ Rails.logger.warn("current_user in Miniblog::ApplicationController should be overriden")
6
+ ::User.new
7
+ elsif method_name == :authenticate_user!
8
+ Rails.logger.warn("authenticate_user! in Miniblog::ApplicationController should be overriden")
9
+ end
10
+ end
11
+
12
+ helper_method :current_user
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ module Miniblog
2
+ class PostsController < ApplicationController
3
+ def index
4
+ @posts = Post.published_and_ordered
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,18 @@
1
+ module Miniblog::Admin::PostsHelper
2
+ def edit_post_button(post)
3
+ link_to 'Edit', miniblog.edit_admin_post_path(post),
4
+ class: "btn btn-default btn-block"
5
+ end
6
+
7
+ def delete_post_button(post)
8
+ link_to 'Delete', miniblog.admin_post_path(post),
9
+ class: "btn btn-default btn-block",
10
+ method: :delete, data: { confirm: "Are you sure?"}
11
+ end
12
+
13
+ def publish_post_button(post)
14
+ link_to 'Publish', miniblog.admin_post_state_path(post),
15
+ class: "btn btn-block #{post.published? ? "btn-success" : "btn-danger" }",
16
+ method: :put
17
+ end
18
+ end
@@ -0,0 +1,4 @@
1
+ module Miniblog
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,7 @@
1
+ module Miniblog
2
+ class Asset < ActiveRecord::Base
3
+ belongs_to :post
4
+
5
+ mount_uploader :attachment, AttachmentUploader
6
+ end
7
+ end
@@ -0,0 +1,135 @@
1
+ module Miniblog
2
+ class Post < ActiveRecord::Base
3
+ belongs_to :author, :class_name => Miniblog.author_user_class_name
4
+ belongs_to :publisher, :class_name => Miniblog.publisher_user_class_name
5
+ has_many :assets
6
+
7
+ delegate :name, to: :author, prefix: true, allow_nil: true
8
+ delegate :email, to: :author, prefix: true, allow_nil: true
9
+ delegate :gravatar_url, to: :author
10
+
11
+ delegate :year, to: :published_at
12
+
13
+ validates_presence_of :title
14
+
15
+ attr_accessor :transition
16
+
17
+ LEGACY_TITLE_REGEXP = /(\d+-\d+-\d+)-(.*)/
18
+
19
+ state_machine :state, initial: :drafted do
20
+ state :drafted
21
+ state :published
22
+
23
+ before_transition on: :publish do |post, transition|
24
+ post.published_at ||= Time.now
25
+ end
26
+
27
+ before_transition on: :draft do |post, transition|
28
+ post.published_at = nil
29
+ end
30
+
31
+ event :draft do
32
+ transition all => :drafted
33
+ end
34
+
35
+ event :publish do
36
+ transition all => :published
37
+ end
38
+ end
39
+
40
+ # CLASS METHODS
41
+ class << self
42
+ def all_posts_json
43
+ includes(:author).
44
+ order_by_publish_date.to_json only: [:id, :title, :state, :published_at, :ready_for_review],
45
+ methods: [:author_email, :published?]
46
+ end
47
+
48
+ def by_author(author_id)
49
+ published_and_ordered.where(author_id: author_id)
50
+ end
51
+
52
+ def last_published(number)
53
+ published_and_ordered.limit(number)
54
+ end
55
+
56
+ def order_by_publish_date
57
+ order('published_at DESC, created_at DESC, id DESC')
58
+ end
59
+
60
+ def published
61
+ where(state: 'published')
62
+ end
63
+
64
+ def published_and_ordered
65
+ published.order_by_publish_date
66
+ end
67
+
68
+ def scoped_for(user)
69
+ user.is_publisher? ? all : user.authored_posts
70
+ end
71
+
72
+ def for_admin_index
73
+ ordered_by_state.order_by_publish_date
74
+ end
75
+
76
+ def ordered_by_state
77
+ order(:state)
78
+ end
79
+ end
80
+
81
+ # Must be after Class methods (otherwise a missing method error will raise)
82
+ scope :for_index, -> { last_published(3) }
83
+ scope :for_history, -> { last_published(13) }
84
+ scope :all_for_feed, -> { last_published(15) }
85
+
86
+
87
+ # INSTANCE METHODS
88
+ def allowed_to_update_permalink?
89
+ !self.published?
90
+ end
91
+
92
+ def day
93
+ "%02d" % published_at.day
94
+ end
95
+
96
+ def formatted_published_date
97
+ published_at.strftime("%b %d, %Y")
98
+ end
99
+
100
+ def html_body
101
+ @@renderer ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML,
102
+ :autolink => true,
103
+ :fenced_code_blocks => true,
104
+ :space_after_headers => false)
105
+ @@renderer.render(self.body).html_safe
106
+ end
107
+
108
+ def month
109
+ "%02d" % published_at.month
110
+ end
111
+
112
+ def publish_if_allowed(transition, user)
113
+ if user.is_publisher?
114
+ self.publisher = user
115
+ self.send(transition)
116
+ end
117
+ end
118
+
119
+ def regenerate_permalink
120
+ self.permalink = title.parameterize
121
+ end
122
+
123
+ #
124
+ # Use this methods to generate the post url
125
+ # always use with the splat
126
+ # operator
127
+ #
128
+ # Example:
129
+ # post_url(*post.url_params)
130
+ #
131
+ def url_params
132
+ [self.year, self.month, self.day, self.permalink, 'html']
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,6 @@
1
+ module Miniblog
2
+ class StatusChangeRecord < ActiveRecord::Base
3
+ belongs_to :post
4
+ belongs_to :user, class_name: Miniblog.publisher_user_class_name
5
+ end
6
+ end
@@ -0,0 +1,32 @@
1
+ module Miniblog
2
+ module User
3
+ def self.included(base)
4
+ base.send(:has_many, :authored_posts, inverse_of: :author,
5
+ foreign_key: 'author_id', class_name: 'Miniblog::Post')
6
+ base.send(:has_many, :published_posts, -> {
7
+ where(state: 'published').
8
+ order('published_at DESC')
9
+ },
10
+ inverse_of: :author,
11
+ foreign_key: 'author_id', class_name: 'Miniblog::Post')
12
+ base.send(:has_one, :last_post, -> {
13
+ where(state: 'published').
14
+ order('published_at DESC, created_at DESC, id DESC')
15
+ },
16
+ class_name: 'Miniblog::Post',
17
+ foreign_key: :author_id)
18
+ end
19
+
20
+ def is_publisher?
21
+ true
22
+ end
23
+
24
+ def last_post_at
25
+ last_post.try(:published_at)
26
+ end
27
+
28
+ def last_published_at
29
+ published_posts.first ? published_posts.first.published_at : nil
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,13 @@
1
+ module Miniblog
2
+ class PostPresenter < SimpleDelegator
3
+ def initialize(object, user)
4
+ @user = user
5
+ super(object)
6
+ end
7
+
8
+ def publish_as_publisher
9
+ self.publisher = @user
10
+ super
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,48 @@
1
+ # encoding: utf-8
2
+
3
+ class AttachmentUploader < CarrierWave::Uploader::Base
4
+
5
+ # Include RMagick or MiniMagick support:
6
+ # include CarrierWave::RMagick
7
+ # include CarrierWave::MiniMagick
8
+
9
+ # Choose what kind of storage to use for this uploader:
10
+ # storage :file
11
+ #storage :fog
12
+
13
+ # Override the directory where uploaded files will be stored.
14
+ # This is a sensible default for uploaders that are meant to be mounted:
15
+ def store_dir
16
+ "#{model.class.to_s.underscore}/#{model.id}"
17
+ end
18
+
19
+ # Provide a default URL as a default if there hasn't been a file uploaded:
20
+ # def default_url
21
+ # "/images/fallback/" + [version_name, "default.png"].compact.join('_')
22
+ # end
23
+
24
+ # Process files as they are uploaded:
25
+ # process :scale => [200, 300]
26
+ #
27
+ # def scale(width, height)
28
+ # # do something
29
+ # end
30
+
31
+ # Create different versions of your uploaded files:
32
+ # version :thumb do
33
+ # process :scale => [50, 50]
34
+ # end
35
+
36
+ # Add a white list of extensions which are allowed to be uploaded.
37
+ # For images you might use something like this:
38
+ # def extension_white_list
39
+ # %w(jpg jpeg gif png)
40
+ # end
41
+
42
+ # Override the filename of the uploaded files:
43
+ # Avoid using model.id or version_name here, see uploader/store.rb for details.
44
+ # def filename
45
+ # "something.jpg" if original_filename
46
+ # end
47
+
48
+ end
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Crowd Blog</title>
5
+ <%= stylesheet_link_tag :miniblog, media: 'all' %>
6
+ <%= javascript_include_tag :miniblog %>
7
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
8
+ <meta charset="utf-8">
9
+ <meta name="viewport" content="width=device-width, initial-scale=1">
10
+ <%= csrf_meta_tags %>
11
+ <%= yield :scripts %>
12
+ </head>
13
+ <body>
14
+ <div class="container">
15
+ <div class="row">
16
+ <%= render '/miniblog/application/navbar' %>
17
+ </div>
18
+ <div class="row">
19
+ <%= render '/miniblog/application/notices' %>
20
+ </div>
21
+ </div>
22
+ <div class="container">
23
+ <%= yield %>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,10 @@
1
+ <div class="col-xs-10 col-xs-offset-1">
2
+ <div class="form-group">
3
+ <%= f.label :title, class: 'control-label' %>
4
+ <%= f.text_field :title, class: "form-control input-lg" %>
5
+ </div>
6
+ <div class="form-group">
7
+ <%= f.label :body %>
8
+ <%= f.text_area :body, class: "form-control", rows: 20 %>
9
+ </div>
10
+ </div>
@@ -0,0 +1,27 @@
1
+ .row
2
+ .span12
3
+ .control-group.title
4
+ = f.label :title, :class => 'control-label'
5
+ .controls
6
+ = f.text_field :title, :class => "input-xlarge span5"
7
+ span.help-inline.title
8
+
9
+ .row
10
+ .control-group.body
11
+ .span12
12
+ = label_tag 'post_body', 'Body', class: 'control-label'
13
+ .controls.span5
14
+ = f.text_area :body, :class => "span5", :rows => 20
15
+ span.help-inline= link_to 'Markdown syntax', 'http://daringfireball.net/projects/markdown/syntax', target: '_BLANK'
16
+
17
+ #post-preview.span7
18
+ .inner
19
+
20
+ .row.form-box
21
+ .span5
22
+ #uploader
23
+ .inner Drop images...
24
+
25
+ .span7#assets
26
+ - f.object.assets.each do |asset|
27
+ .asset= "![image alt](#{asset.attachment_url})"
@@ -0,0 +1,14 @@
1
+ <%= div_for post, class: "row" do %>
2
+ <div class="col-sm-4 col-sm-offset-1"><h4><%= post.title %></h4></div>
3
+ <div class="visible-xs col-xs-12">
4
+ <div class="col-xs-4"><%= edit_post_button(post) %></div>
5
+ <div class="col-xs-4"><%= publish_post_button(post) %></div>
6
+ <div class="col-xs-4"><%= delete_post_button(post) %></div>
7
+ </div>
8
+ <div class="hidden-xs">
9
+ <div class="col-sm-2"><%= edit_post_button(post) %></div>
10
+ <div class="col-sm-2"><%= publish_post_button(post) %></div>
11
+ <div class="col-sm-2"><%= delete_post_button(post) %></div>
12
+ </div>
13
+ <% end %>
14
+ <hr/>
@@ -0,0 +1,20 @@
1
+ = cache [:admin, post] do
2
+ = content_tag_for :tr, post, :"data-post-id" => post.id, :"data-state" => post.state, :"data-ready-for-review" => post.ready_for_review do
3
+ td= post.title
4
+ td= post.author_email
5
+ td.span2.published-at = post.published_at.try(:to_s, :miniblog_short)
6
+ td.span1
7
+ - if current_user.is_publisher?
8
+ = link_to 'Publish', '#', :class => "btn btn-small publish-btn btn-danger #{(post.published? ? 'draft' : 'publish')}"
9
+ td.span1
10
+ .btn-group.states
11
+ button.btn.btn-small.draft Drafted
12
+ button.btn.btn-small.finish Finished
13
+ - if !current_user.is_publisher?
14
+ button.btn.btn-small.disabled.publish-status
15
+ - if current_user.is_publisher?
16
+ button.btn.btn-small.review Reviewed
17
+ td.span1
18
+ = link_to 'Delete', miniblog.admin_post_path(post), :method => :delete, :data => { :confirm => 'Are you sure?' }, :class => "btn btn-small"
19
+ td.span1
20
+ = link_to 'Edit', miniblog.edit_admin_post_path(post), :class => "btn btn-small"
@@ -0,0 +1,19 @@
1
+ <div class="page-header">
2
+ <h1>Edit Post</h1>
3
+ </div>
4
+ <%= form_for [:admin, @post] do |f| %>
5
+ <div class="row">
6
+ <%= render 'form', f: f %>
7
+ </div>
8
+ <div class="row well">
9
+ <div class="col-xs-4 col-xs-offset-1 col-sm-2 col-sm-offset-3">
10
+ <%= f.submit "Update", class: "btn btn-primary btn-block" %>
11
+ </div>
12
+ <div class="col-xs-3 col-sm-2">
13
+ <%= link_to 'Preview', miniblog.admin_preview_path(@post), class: "btn btn-default btn-block" %>
14
+ </div>
15
+ <div class="col-xs-3 col-sm-2">
16
+ <%= link_to 'Cancel', miniblog.admin_posts_path, class: "btn btn-default btn-block" %>
17
+ </div>
18
+ </div>
19
+ <% end %>