k3cms_blog 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 (51) hide show
  1. data/.gitignore +3 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +1 -0
  4. data/Gemfile.lock +136 -0
  5. data/License.txt +165 -0
  6. data/Rakefile +20 -0
  7. data/Readme.rdoc +13 -0
  8. data/app/cells/k3cms/blog/blog_posts/index.html.haml +46 -0
  9. data/app/cells/k3cms/blog/blog_posts/index.html.haml.haml +0 -0
  10. data/app/cells/k3cms/blog/blog_posts/metadata_drawer.html.haml +38 -0
  11. data/app/cells/k3cms/blog/blog_posts/published_status.html.haml +4 -0
  12. data/app/cells/k3cms/blog/blog_posts_cell.rb +36 -0
  13. data/app/controllers/k3cms/blog/base_controller.rb +29 -0
  14. data/app/controllers/k3cms/blog/blog_posts_controller.rb +82 -0
  15. data/app/models/k3cms/blog/ability.rb +37 -0
  16. data/app/models/k3cms/blog/blog_post.rb +75 -0
  17. data/app/models/user_decorator.rb +3 -0
  18. data/app/views/k3cms/blog/blog_posts/_form.html.erb +28 -0
  19. data/app/views/k3cms/blog/blog_posts/edit.html.erb +3 -0
  20. data/app/views/k3cms/blog/blog_posts/index.html.haml +3 -0
  21. data/app/views/k3cms/blog/blog_posts/new.html.erb +5 -0
  22. data/app/views/k3cms/blog/blog_posts/show.html.haml +41 -0
  23. data/app/views/k3cms/blog/init.html.haml +34 -0
  24. data/config/authorization.rb +37 -0
  25. data/config/locales/validates_timeliness.en.yml +16 -0
  26. data/config/routes.rb +3 -0
  27. data/db/migrate/20110113015852_create_k3_blog_posts.rb +17 -0
  28. data/db/migrate/20110118015034_create_slugs.rb +18 -0
  29. data/db/migrate/20110118023300_add_cached_slug_to_blog_posts.rb +10 -0
  30. data/db/migrate/20110120172202_add_meta_description_to_blog_posts.rb +15 -0
  31. data/db/migrate/20110415180204_rename_to_k3cms_blog_posts.rb +9 -0
  32. data/image_source/famfamfam_silk_icons/information.png +0 -0
  33. data/image_source/famfamfam_silk_icons/page_add.png +0 -0
  34. data/image_source/famfamfam_silk_icons/page_delete.png +0 -0
  35. data/image_source/famfamfam_silk_icons/page_white_add.png +0 -0
  36. data/image_source/famfamfam_silk_icons/text_list_bullets.png +0 -0
  37. data/image_source/icons.xcf +0 -0
  38. data/k3cms_blog.gemspec +37 -0
  39. data/lib/form_tag_helper.rb +3 -0
  40. data/lib/k3cms/blog/railtie.rb +67 -0
  41. data/lib/k3cms/blog/version.rb +5 -0
  42. data/lib/k3cms_blog.rb +8 -0
  43. data/lib/tasks/tasks.rake +17 -0
  44. data/public/images/k3cms/blog/icons.png +0 -0
  45. data/public/images/k3cms/blog/new.png +0 -0
  46. data/public/javascripts/k3cms/blog.js +13 -0
  47. data/public/stylesheets/k3cms/blog.css +53 -0
  48. data/spec/connection_and_schema.rb +12 -0
  49. data/spec/models/blog_post_spec.rb +254 -0
  50. data/spec/spec_helper.rb +49 -0
  51. metadata +340 -0
@@ -0,0 +1,29 @@
1
+ module K3cms
2
+ module Blog
3
+ class BaseController < ApplicationController
4
+
5
+ include CanCan::ControllerAdditions
6
+ helper K3cms::InlineEditor::InlineEditorHelper
7
+
8
+ def current_ability
9
+ @current_ability ||= K3cms::Blog::Ability.new(k3cms_user)
10
+ end
11
+
12
+ rescue_from CanCan::AccessDenied do |exception|
13
+ k3cms_authorization_required(exception)
14
+ end
15
+
16
+ protected
17
+ # Unfortunately, FriendlyId raises an error for some things instead of just adding a validation error to the errors array.
18
+ # Let's try to respond the same as we would for a validation error though.
19
+ rescue_from ::FriendlyId::BlankError, :with => :rescue_friendly_id_blank_error
20
+
21
+ def rescue_friendly_id_blank_error
22
+ respond_to do |format|
23
+ format.json { render :json => {:error => 'Cannot be blank'} }
24
+ end
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,82 @@
1
+ module K3cms
2
+ module Blog
3
+ class BlogPostsController < K3cms::Blog::BaseController
4
+ load_and_authorize_resource :blog_post, :class => 'K3cms::Blog::BlogPost'
5
+
6
+ def index
7
+ @blog_posts = @blog_posts.order('id desc')
8
+ respond_to do |format|
9
+ format.html # index.html.erb
10
+ format.xml { render :xml => @blog_posts }
11
+ format.json { render :json => @blog_posts }
12
+ end
13
+ end
14
+
15
+ def show
16
+ respond_to do |format|
17
+ format.html # show.html.erb
18
+ format.xml { render :xml => @blog_post }
19
+ format.json { render :json => @blog_post }
20
+ end
21
+ end
22
+
23
+ def new
24
+ respond_to do |format|
25
+ format.html # new.html.erb
26
+ format.xml { render :xml => @blog_post }
27
+ format.json { render :json => @blog_post }
28
+ end
29
+ end
30
+
31
+ def create
32
+ @blog_post.attributes = params[:k3cms_blog_blog_post]
33
+ @blog_post.author = current_user
34
+
35
+ respond_to do |format|
36
+ if @blog_post.save
37
+ format.html do
38
+ #redirect_to(k3cms_blog_blog_post_url(@blog_post),
39
+
40
+ redirect_to(k3cms_blog_blog_posts_url(:focus => "##{dom_id(@blog_post)} .editable[data-attribute=title]"),
41
+ :notice => 'Blog post was successfully created.')
42
+ end
43
+ format.xml { render :xml => @blog_post, :status => :created, :location => @blog_post }
44
+ format.json { render :nothing => true }
45
+ else
46
+ format.html { render :action => "new" }
47
+ format.xml { render :xml => @blog_post.errors, :status => :unprocessable_entity }
48
+ format.json { render :nothing => true }
49
+ end
50
+ end
51
+ end
52
+
53
+ def edit
54
+ end
55
+
56
+ def update
57
+ respond_to do |format|
58
+ if @blog_post.update_attributes(params[:k3cms_blog_blog_post])
59
+ format.html { redirect_to(k3cms_blog_blog_post_url(@blog_post), :notice => 'Blog post was successfully updated.') }
60
+ format.xml { head :ok }
61
+ format.json { render :json => {} }
62
+ else
63
+ format.html { render :action => "edit" }
64
+ format.xml { render :xml => @blog_post.errors, :status => :unprocessable_entity }
65
+ format.json { render :json => {:error => @blog_post.errors.full_messages.join('<br/>')} }
66
+ end
67
+ end
68
+ end
69
+
70
+ def destroy
71
+ @blog_post.destroy
72
+ respond_to do |format|
73
+ #format.html { redirect_to(k3cms_blog_blog_posts_url) }
74
+ format.html { redirect_to k3cms_blog_blog_posts_url }
75
+ format.xml { head :ok }
76
+ format.json { render :nothing => true }
77
+ end
78
+ end
79
+
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,37 @@
1
+ module K3cms
2
+ module Blog
3
+ class Ability
4
+ include CanCan::Ability
5
+
6
+ def initialize(user)
7
+ if user.k3cms_permitted?(:view_blog_post)
8
+ can :read, K3cms::Blog::BlogPost, [] do |blog_post|
9
+ blog_post.published?
10
+ end
11
+ end
12
+
13
+ if user.k3cms_permitted?(:edit_blog_post)
14
+ can :read, K3cms::Blog::BlogPost
15
+ can :update, K3cms::Blog::BlogPost
16
+ end
17
+
18
+ if user.k3cms_permitted?(:edit_own_blog_post)
19
+ can :read, K3cms::Blog::BlogPost
20
+ can :update, K3cms::Blog::BlogPost, :author_id => user.id
21
+ end
22
+
23
+ if user.k3cms_permitted?(:create_blog_post)
24
+ can :create, K3cms::Blog::BlogPost
25
+ end
26
+
27
+ if user.k3cms_permitted?(:delete_blog_post)
28
+ can :destroy, K3cms::Blog::BlogPost
29
+ end
30
+
31
+ if user.k3cms_permitted?(:delete_own_blog_post)
32
+ can :destroy, K3cms::Blog::BlogPost, :author_id => user.id
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,75 @@
1
+
2
+ module K3cms
3
+ module Blog
4
+ class BlogPost < ActiveRecord::Base
5
+ self.include_root_in_json = false
6
+ set_table_name 'k3cms_blog_blog_posts'
7
+
8
+ # There are multiple fields/methods involved for the slug/friendly_id. Don't be confused:
9
+ # * The cached_slug attribute is used when we do BlogPost.find('my-slug')
10
+ # * The url attribute is never (!!!) "used", except as input to friendly_id, which will then process it and *convert* it to a valid url, as necessary. The result of that, of course, is available in cached_slug.
11
+ # * The url= setter is left intact, but the url getter is overridden to return cached_slug, because we essentially never want to accidentally use a user-supplied value (read_attribute(:url)) instead of the processed value.
12
+ # The url attribute will remain nil unless it is ever manually set by the user.
13
+ # As long as custom_url? is false (the url attribute is nil), it will automatically create a slug based on title any time the title is updated.
14
+ # As soon as you set the url manually, however, it will stop doing that.
15
+ has_friendly_id :title_or_custom_url, :use_slug => true
16
+
17
+ belongs_to :author, :class_name => 'User'
18
+
19
+ normalize_attributes :title, :summary, :body, :with => [:strip, :blank]
20
+
21
+ validates :title, :presence => true
22
+ validates :date, :timeliness => {:type => :date}
23
+ #validates :url, :uniqueness => true, :allow_nil => true, :allow_blank => true
24
+
25
+ after_initialize :set_defaults
26
+ def set_defaults
27
+ # Copy summary to body
28
+ default_summary = '<p>Summary description goes here</p>'
29
+ self.body = self.attributes['summary'] if self.attributes['body'].nil? && self.attributes['summary'].present? && self.attributes['summary'] != default_summary
30
+
31
+ if new_record?
32
+ self.title = 'New Post' if self.attributes['title'].nil?
33
+ self.summary = default_summary if self.attributes['summary'].nil?
34
+ self.date = Date.tomorrow if self.attributes['date'].nil?
35
+ end
36
+ end
37
+
38
+ def to_s
39
+ title
40
+ end
41
+
42
+ #---------------------------------------------------------------------------------------------
43
+ # url/slug/friendly_id
44
+ private
45
+ def title_or_custom_url
46
+ custom_url? ? read_attribute(:url) : title
47
+ end
48
+ public
49
+ def normalize_friendly_id(text)
50
+ # This uses stringex
51
+ text.to_url
52
+ end
53
+ def url
54
+ cached_slug
55
+ end
56
+ def url=(new)
57
+ if new
58
+ new = normalize_friendly_id(new)
59
+ end
60
+ # By checking if new != url, we solve the problem where a user might tab from the title field to the url field, and then when they tab out of the url field, it will try to save the current url as a custom url instead of realizing that this is still an automatic url from the title.
61
+ if new != url # NOT read_attribute(:url) -- we want to compare to the cached_slug based on the title if that's what is currently being used for the slug
62
+ write_attribute(:url, new)
63
+ end
64
+ end
65
+ def custom_url?
66
+ read_attribute(:url).present?
67
+ end
68
+
69
+ #---------------------------------------------------------------------------------------------
70
+ def published?
71
+ date and Time.zone.now >= date.beginning_of_day
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,3 @@
1
+ User.class_eval do
2
+ has_many :k3cms_blog_posts
3
+ end
@@ -0,0 +1,28 @@
1
+ <%= form_for(@blog_post) do |f| %>
2
+ <% if @blog_post.errors.any? %>
3
+ <div id="errorExplanation">
4
+ <h2><%= pluralize(@blog_post.errors.count, "error") %> prevented this blog post from being saved:</h2>
5
+ <ul>
6
+ <% @blog_post.errors.full_messages.each do |msg| %>
7
+ <li><%= msg %></li>
8
+ <% end %>
9
+ </ul>
10
+ </div>
11
+ <% end %>
12
+
13
+ <div class="field">
14
+ <%= f.label :title %><br />
15
+ <%= f.text_field :title %>
16
+ </div>
17
+ <div class="field">
18
+ <%= f.label :body %><br />
19
+ <%= f.text_field :body %>
20
+ </div>
21
+ <div class="field">
22
+ <%= f.label :summary %><br />
23
+ <%= f.text_field :summary %>
24
+ </div>
25
+ <div class="actions">
26
+ <%= f.submit %>
27
+ </div>
28
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <h1>Editing blog post</h1>
2
+
3
+ <%= render 'form' %>
@@ -0,0 +1,3 @@
1
+ - content_for :title do
2
+ News
3
+ = render_cell 'k3cms/blog/blog_posts', :index
@@ -0,0 +1,5 @@
1
+ <h1>New blog_post</h1>
2
+
3
+ <%= render 'form' %>
4
+
5
+ <%= link_to 'Back', k3cms_blog_blog_posts_path %>
@@ -0,0 +1,41 @@
1
+ .k3cms_blog_blog_posts_show
2
+ - content_for :title do
3
+ = strip_tags @blog_post.title
4
+
5
+ - if edit_mode? and can?(:edit, K3cms::Blog::BlogPost)
6
+ :javascript
7
+ $(function() {
8
+ $('#k3cms_ribbon').k3cms_ribbon('enable', '.k3cms_blog .blog_post_metadata.button');
9
+ })
10
+
11
+ - k3cms_ribbon_add_drawer :k3cms_blog_blog_post_metadata do
12
+ = render_cell 'k3cms/blog/blog_posts', :metadata_drawer, :blog_post => @blog_post
13
+
14
+ %div{:class => dom_class(@blog_post), :id => dom_id(@blog_post)}
15
+ = render_cell 'k3cms/blog/blog_posts', :published_status, :blog_post => @blog_post
16
+ %h2.title
17
+ - if edit_mode? and can?(:edit, K3cms::Blog::BlogPost)
18
+ = inline_editable('span', @blog_post, :title) do
19
+ = raw @blog_post.title
20
+ - else
21
+ = raw @blog_post.title
22
+
23
+ .date
24
+ - if edit_mode? and can?(:edit, K3cms::Blog::BlogPost)
25
+ = inline_editable('span', @blog_post, :date) do
26
+ = raw @blog_post.date
27
+ - else
28
+ = @blog_post.date && @blog_post.date.to_s(:long)
29
+
30
+ .body
31
+ - if edit_mode? and can?(:edit, K3cms::Blog::BlogPost)
32
+ = inline_editable('div', @blog_post, :body) do
33
+ = raw @blog_post.body
34
+ - else
35
+ = raw @blog_post.body
36
+
37
+ .post_bottom
38
+ -# Comments, etc. go here
39
+
40
+ :javascript
41
+ #{inline_editor_update_page_from_object(@blog_post)}
@@ -0,0 +1,34 @@
1
+ - if edit_mode?
2
+ :javascript
3
+ $(function() {
4
+ $('#k3cms_ribbon').k3cms_ribbon({tabs: [
5
+ new K3cms_Ribbon.Tab('k3cms_blog', {label: 'Blog Post', sections: [
6
+ new K3cms_Ribbon.Section('k3cms_blog', {items: [
7
+ new K3cms_Ribbon.Button({
8
+ element: $('<li/>', { 'class': "icon button blog_post_metadata" }).
9
+ append($('<a/>', {title: "Post Information", href: "javascript:;", html: '&nbsp;'})),
10
+ onClick: function() { $(this).k3cms_ribbon('isEnabled') && $(this).trigger('invoke'); },
11
+ onInvoke: function() { $('.k3cms_blog_blog_post_metadata.drawer').slideToggle(); $('.k3cms_blog_blog_post_metadata.drawer .editable:visible:eq(0)').focus() }
12
+ }),
13
+ new K3cms_Ribbon.Button({
14
+ element: $('<li/>', { 'class': "icon button list_blog_posts" }).
15
+ append($('<a/>', {title: "List Blog Posts", href: "#{k3cms_blog_blog_posts_path}", html: '&nbsp;'})),
16
+ onMousedown: function(event) {
17
+ var a = $(this).find('a').eq(0);
18
+ if ($(this).k3cms_ribbon('isEnabled')) {
19
+ a.attr('href', a.data('href'));
20
+ } else {
21
+ a.data('href', a.attr('href'));
22
+ a.attr('href', 'javascript:;');
23
+ }
24
+ },
25
+ }),
26
+ ]})
27
+ ]})
28
+ ]})
29
+ $('#k3cms_ribbon').k3cms_ribbon({
30
+ always_enabled: [
31
+ '.k3cms_blog .list_blog_posts.button',
32
+ ]
33
+ });
34
+ })
@@ -0,0 +1,37 @@
1
+ K3cms::Blog::Railtie.authorization.draw do
2
+ # First define and describe some suggested permission sets.
3
+ suggested_permission_set :default, 'Allows managers to create & edit all blog_posts, and delete their own blog_posts'
4
+ suggested_permission_set :user_creation, 'Allows users to create and manage their own blog_posts'
5
+
6
+ # Context makes all abilities defined within to be prefixed with the
7
+ # singularized version of the given string. Contexts can be nested.
8
+ context :blog_posts do
9
+ ability :view, 'Can view a blog_post' # Creates :view_blog_post ability
10
+ ability :edit, 'Can edit a blog_post'
11
+ ability :edit_own, 'Can edit only my blog_posts'
12
+ ability :create, 'Can create a new blog_post'
13
+ ability :delete, 'Can delete a blog_post'
14
+ ability :delete_own, 'Can delete only blog_posts created by me'
15
+
16
+ # This defines the abilities for the default suggested permission set
17
+ # in terms of the four default roles (guest, user, manager, admin)
18
+ extend_suggested_permission_set :default do
19
+ guest :has => :view
20
+ # NOTE: :includes_role without first defining the role to be included
21
+ # will cause an error. The included abilities are limited to
22
+ # this extend_suggested_permission_set block.
23
+ user :includes_role => :guest
24
+ manager :has => [:create, :edit_own, :delete_own], :includes_role => :user
25
+ # :all only applies to the abilities in this context (:blog_posts)
26
+ admin :has => :all
27
+ end
28
+
29
+ # Define abilities for the suggested permission set.
30
+ extend_suggested_permission_set :user_creation do
31
+ guest :has => :view
32
+ user :has => [:create, :edit_own, :delete_own], :includes_role => :guest
33
+ manager :has => :all
34
+ admin :has => :all
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,16 @@
1
+ en:
2
+ errors:
3
+ messages:
4
+ invalid_date: "is not a valid date"
5
+ invalid_time: "is not a valid time"
6
+ invalid_datetime: "is not a valid datetime"
7
+ is_at: "must be at %{restriction}"
8
+ before: "must be before %{restriction}"
9
+ on_or_before: "must be on or before %{restriction}"
10
+ after: "must be after %{restriction}"
11
+ on_or_after: "must be on or after %{restriction}"
12
+ validates_timeliness:
13
+ error_value_formats:
14
+ date: '%Y-%m-%d'
15
+ time: '%H:%M:%S'
16
+ datetime: '%Y-%m-%d %H:%M:%S'
@@ -0,0 +1,3 @@
1
+ Rails.application.routes.draw do
2
+ resources :k3cms_blog_blog_posts, :path => 'news', :controller => 'k3cms/blog/blog_posts'
3
+ end
@@ -0,0 +1,17 @@
1
+ class CreateK3BlogPosts < ActiveRecord::Migration
2
+ def self.up
3
+ create_table 'k3_blog_blog_posts' do |t|
4
+ t.string 'title'
5
+ t.string 'url'
6
+ t.text 'summary'
7
+ t.text 'body'
8
+ t.date 'date'
9
+ t.integer 'author_id'
10
+ t.timestamps
11
+ end
12
+ end
13
+
14
+ def self.down
15
+ drop_table 'k3_blog_blog_posts'
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ class CreateSlugs < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :slugs do |t|
4
+ t.string :name
5
+ t.integer :sluggable_id
6
+ t.integer :sequence, :null => false, :default => 1
7
+ t.string :sluggable_type, :limit => 40
8
+ t.string :scope
9
+ t.datetime :created_at
10
+ end
11
+ add_index :slugs, :sluggable_id
12
+ add_index :slugs, [:name, :sluggable_type, :sequence, :scope], :name => "index_slugs_on_n_s_s_and_s", :unique => true
13
+ end
14
+
15
+ def self.down
16
+ drop_table :slugs
17
+ end
18
+ end