spree_wordsmith 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/README.markdown +121 -0
  2. data/app/controllers/admin/pages_controller.rb +22 -0
  3. data/app/controllers/admin/posts_controller.rb +20 -0
  4. data/app/controllers/admin/wordsmith_settings_controller.rb +13 -0
  5. data/app/controllers/pages_controller.rb +19 -0
  6. data/app/controllers/posts_controller.rb +30 -0
  7. data/app/helpers/wordsmith_helper.rb +21 -0
  8. data/app/models/ability_decorator.rb +23 -0
  9. data/app/models/page.rb +4 -0
  10. data/app/models/post.rb +11 -0
  11. data/app/models/ws_item.rb +80 -0
  12. data/app/stylesheets/wordsmith.less +76 -0
  13. data/app/views/admin/pages/_form.html.erb +60 -0
  14. data/app/views/admin/pages/edit.html.erb +3 -0
  15. data/app/views/admin/pages/index.html.erb +25 -0
  16. data/app/views/admin/pages/new.html.erb +4 -0
  17. data/app/views/admin/posts/_form.html.erb +59 -0
  18. data/app/views/admin/posts/edit.html.erb +5 -0
  19. data/app/views/admin/posts/index.html.erb +25 -0
  20. data/app/views/admin/posts/new.html.erb +6 -0
  21. data/app/views/admin/posts/show.html.erb +27 -0
  22. data/app/views/admin/shared/_wordsmith_sub_menu.html.erb +8 -0
  23. data/app/views/admin/users/_display_name.html.erb +4 -0
  24. data/app/views/admin/wordsmith_settings/edit.html.erb +34 -0
  25. data/app/views/admin/wordsmith_settings/show.html.erb +37 -0
  26. data/app/views/content/show.html.erb +5 -0
  27. data/app/views/pages/index.html.erb +6 -0
  28. data/app/views/pages/show.html.erb +4 -0
  29. data/app/views/posts/_post.html.erb +11 -0
  30. data/app/views/posts/index.html.erb +13 -0
  31. data/app/views/posts/index.rss.builder +19 -0
  32. data/app/views/posts/show.html.erb +10 -0
  33. data/app/views/posts/tags.html.erb +18 -0
  34. data/app/views/shared/_recent_articles.html.erb +7 -0
  35. data/app/views/shared/_wordsmith_content_for.html.erb +7 -0
  36. data/lib/extensions/string.rb +20 -0
  37. data/lib/spree_wordsmith.rb +72 -0
  38. data/lib/spree_wordsmith_hooks.rb +8 -0
  39. data/lib/tasks/install.rake +33 -0
  40. data/lib/tasks/spree_wordsmith.rake +21 -0
  41. data/spec/controllers/admin/pages_controller_spec.rb +10 -0
  42. data/spec/controllers/admin/posts_controller_spec.rb +10 -0
  43. data/spec/controllers/posts_controller_spec.rb +27 -0
  44. data/spec/factories.rb +34 -0
  45. data/spec/models/posts_spec.rb +9 -0
  46. data/spec/spec_helper.rb +39 -0
  47. metadata +326 -0
@@ -0,0 +1,121 @@
1
+ Wordsmith 2.0
2
+ =========
3
+
4
+ A blog/cms extension for spree. Admin users in Spree can create blog posts and static pages. The comment system has been outsourced using the Disqus system. Posts and pages can have commenting disabled allowing the site to work without the Disqus api enabled.
5
+
6
+ Wordsmith has provided the basic views and a stylesheet that can easily be overridden in your site extension. Also several helpers exist to allow tags, posts and pages to be accesible in your site views.
7
+
8
+ Wordsmith uses concepts from many projects and tries to integrate them nicely with Spree making it easy to use the extension in your own site.
9
+
10
+
11
+ Installation
12
+ ------------
13
+
14
+ 'script/extension install git://github.com/tonkapark/spree-wordsmith.git'
15
+
16
+ rake gems:install
17
+
18
+ rake db:migrate
19
+
20
+ All authors for the blog should be given a display name in the admin/users edit form. A new display_name field is added to the spree users table.
21
+
22
+ Edit preferences in wordsmith_extension.rb
23
+
24
+ script/server
25
+
26
+ Setup
27
+ ------
28
+
29
+ in wordsmith_extension.rb add your disqus account identifier if you plan to use comments.
30
+
31
+ You can edit the preferences in the extension.rb file or in the http://localhost:3000/admin/wordsmith_settings ui.
32
+
33
+ The blog home slug requires a server restart so it can only be edited in the extension file.
34
+
35
+ Wordsmith adds a new column to the users model, display_name. For each admin user that will be blogging a display_name should be given. This can be used for display as the post author. The display_name field is made available only on the admin user view by default but could easily be extended to the public user views.
36
+
37
+
38
+ Posts
39
+ -------
40
+
41
+ New Posts can be created in the admin section http://localhost:3000/admin/posts
42
+
43
+ Posts have unique permalinks created for all new post entries. The permalink can be modified after initial save.
44
+
45
+ RedCloth is used for post formatting.
46
+
47
+ is_taggable gem is used for adding tags to each post. A helper linked_tag_list is provided to display a list of the link tags to filter by.
48
+
49
+
50
+ Comments
51
+ --------
52
+
53
+ Comments can be opened or closed for a post or page. The comment form displays on the post page when enabled for the specific post.
54
+
55
+ Comments are now managed by Disqus via the disqus gem, http://github.com/norman/disqus/. Management of spam and comment moderation is done through the disqus ui.
56
+
57
+ Create disqus account at http://disqus.com/.
58
+
59
+
60
+ Blog
61
+ -----
62
+
63
+ View the blog at http://localhost:3000/blog or the defined slug from the preferences.
64
+
65
+ Use helper post_link_list(limit) to get a list of recent post links.
66
+
67
+ Views are in folder extension/app/views/posts
68
+
69
+
70
+ RSS
71
+ -----
72
+
73
+ http://localhost:3000/blog.rss - All Posts
74
+
75
+ Customize RSS feed in extension/app/views/posts/index.rss.builder
76
+
77
+
78
+ Pages
79
+ -----
80
+
81
+ Integrated portions of spree-static-content, http://peterberkenbosch.github.com/spree-static-content/
82
+
83
+ Under Admin section add pages for static-content.
84
+
85
+ Use helper page_link() to print a link to page, based on ID or Permalink.
86
+
87
+ Pages are displayed by the content route provided by Spree core. Customize the extension/app/views/content/show.html.erb file.
88
+
89
+
90
+
91
+ Tips
92
+ --------
93
+
94
+ You can add a blog link anywhere on your site or include the recent_articles partial.
95
+
96
+ One option is to add the Blog link to the store_menu partial
97
+
98
+ '<li><%= link_to t('blog'), posts_path %></li>'
99
+
100
+ Use the wordsmith_content_for partial to include the wordsmith css and recent articles partial in the sidebar.
101
+
102
+ '<%= render :partial => 'shared/wordsmith_content_for'%>'
103
+
104
+
105
+
106
+ TODO
107
+ -----
108
+
109
+ - improve permalinks for posts
110
+
111
+ - wysiwyg editor for blog posts and pages.
112
+
113
+ - tests
114
+
115
+ - store and save links (aka blogroll)
116
+
117
+
118
+ Credits
119
+ -------
120
+
121
+ created by [Matt Anderson](http://tonkapark.com/)
@@ -0,0 +1,22 @@
1
+ class Admin::PagesController < Admin::BaseController
2
+ resource_controller
3
+
4
+ def new
5
+ @page = Page.new(:user_id => current_user.id)
6
+ @page.is_active = Spree::Config[:wordsmith_page_status_default]
7
+ @page.commentable = Spree::Config[:wordsmith_page_comment_default]
8
+
9
+ respond_to do |format|
10
+ format.html
11
+ end
12
+ end
13
+
14
+ update.response do |wants|
15
+ wants.html { redirect_to collection_url }
16
+ end
17
+
18
+ create.response do |wants|
19
+ wants.html { redirect_to collection_url }
20
+ end
21
+
22
+ end
@@ -0,0 +1,20 @@
1
+ class Admin::PostsController < Admin::BaseController
2
+ resource_controller :except => [:show]
3
+
4
+ def new
5
+ @post = Post.new(:user_id => current_user.id)
6
+ @post.is_active = Spree::Config[:wordsmith_post_status_default]
7
+ @post.commentable = Spree::Config[:wordsmith_post_comment_default]
8
+
9
+ respond_to do |format|
10
+ format.html
11
+ end
12
+ end
13
+
14
+ def edit
15
+ @post = Post.find(params[:id])
16
+ end
17
+
18
+ create.wants.html {redirect_to admin_posts_path}
19
+ update.wants.html {redirect_to admin_posts_path}
20
+ end
@@ -0,0 +1,13 @@
1
+ class Admin::WordsmithSettingsController < Admin::BaseController
2
+
3
+ def update
4
+ Spree::Config.set(params[:preferences])
5
+
6
+ respond_to do |format|
7
+ format.html {
8
+ redirect_to admin_wordsmith_settings_path
9
+ }
10
+ end
11
+ end
12
+
13
+ end
@@ -0,0 +1,19 @@
1
+ class PagesController < Spree::BaseController
2
+
3
+ resource_controller
4
+ actions :show, :index
5
+
6
+ index.before do
7
+ @pages = Page.listed_pages
8
+ response do |wants|
9
+ wants.html
10
+ end
11
+ end
12
+
13
+ show.before do
14
+ @page = Page.find(params[:id])
15
+ response do |wants|
16
+ wants.html
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ class PostsController < Spree::BaseController
2
+
3
+ resource_controller
4
+ actions :show, :index
5
+
6
+ index.response do |wants|
7
+ wants.html
8
+ wants.rss
9
+ end
10
+
11
+ show.response do |wants|
12
+ wants.html
13
+ end
14
+
15
+ def tags
16
+ @posts = Post.publish.find_all_tagged_with(params[:tag_name]).paginate :page => params[:page]
17
+ end
18
+
19
+ private
20
+ def collection
21
+ @collection ||= end_of_association_chain.publish.paginate :page => params[:page]
22
+ end
23
+
24
+ def object
25
+ @object ||= end_of_association_chain.publish.find(param) unless param.nil?
26
+ @object
27
+ end
28
+
29
+
30
+ end
@@ -0,0 +1,21 @@
1
+ module WordsmithHelper
2
+
3
+ def linked_tag_list(tags)
4
+ tags.collect {|tag| link_to(tag.name, tag_posts_url(:tag_name => tag.name ))}.join(", ")
5
+ end
6
+
7
+ def post_link_list(limit = Spree::Config[:wordsmith_posts_recent])
8
+ link = Struct.new(:name,:url)
9
+ Post.publish.find(:all, :limit => limit).collect { |post| link.new(post.title, post_path(post)) }
10
+ end
11
+
12
+ def page_link(id)
13
+ if id.kind_of?(String)
14
+ page = Page.publish.find_by_permalink(id)
15
+ elsif id.kind_of?(Fixnum)
16
+ page = Page.publish.find(id)
17
+ end
18
+ link_to page.title, page.link unless page.nil?
19
+ end
20
+
21
+ end
@@ -0,0 +1,23 @@
1
+ class AbilityDecorator
2
+ include CanCan::Ability
3
+
4
+ def initialize(user)
5
+ can :index, Page
6
+ can :read, Page do |page|
7
+ page.published_at || user.has_role?(:admin) || (page.user == user)
8
+ end
9
+ can :update, Page do |page|
10
+ user.has_role?(:admin) || (page.user == user)
11
+ end
12
+ can :index, Post
13
+ can :read, Post do |post|
14
+ post.published_at || user.has_role?(:admin) || (post.user == user)
15
+ end
16
+ can :update, Post do |post|
17
+ user.has_role?(:admin) || (post.user == user)
18
+ end
19
+ end
20
+ end
21
+
22
+ Ability.register_ability(AbilityDecorator)
23
+
@@ -0,0 +1,4 @@
1
+ class Page < WsItem
2
+
3
+ scope :listed_pages, lambda { where('published_at is not null').where(:list_page => true).order('order_number') }
4
+ end
@@ -0,0 +1,11 @@
1
+ class Post < WsItem
2
+
3
+ validates_presence_of :body, :message => 'required'
4
+
5
+ cattr_reader :per_page
6
+ @@per_page = Spree::Config[:wordsmith_posts_per_page]
7
+
8
+ scope :recent_first, lambda { order('published_at DESC') }
9
+ scope :published_posts, lambda { recent_first.where('published_at is not null') }
10
+ scope :recent_posts, lambda { published_posts.limit(5) }
11
+ end
@@ -0,0 +1,80 @@
1
+ require 'is_taggable'
2
+
3
+ class WsItem < ActiveRecord::Base
4
+ attr_accessible :user_id, :title, :body_raw, :published_at, :is_active, :permalink, :tag_list, :excerpt, :commentable, :order_number, :list_page, :meta_keywords, :meta_description
5
+
6
+ is_taggable :tags
7
+
8
+ belongs_to :user
9
+
10
+ before_validation :format_markup
11
+ before_validation :generate_permalink
12
+ before_validation :published
13
+
14
+ validates_presence_of :title, :message => 'required'
15
+ validates_uniqueness_of :permalink
16
+
17
+ default_scope :order => 'published_at DESC'
18
+ scope :publish, -> { where('published_at < ? and is_active = ?', Time.zone.now, 1) }
19
+
20
+
21
+ #should be part of is_taggable
22
+ #pulled from http://github.com/gnugeek/is_taggable/commit/10b590865f0effeed20f00e3581a7aed8a6bd3b4
23
+ def self.find_all_tagged_with(tag_or_tags, conditions=[])
24
+ return [] if tag_or_tags.nil? || tag_or_tags.empty?
25
+ case tag_or_tags
26
+ when Array, IsTaggable::TagList
27
+ all(:include => ['tags', 'taggings'], :conditions => conditions ).select { |record| tag_or_tags.all? { |tag| record.tags.map(&:name).include?(tag) } } || []
28
+ else
29
+ all(:include => ['tags', 'taggings'], :conditions => conditions).select { |record| record.tags.map(&:name).include?(tag_or_tags) } || []
30
+ end
31
+ end
32
+
33
+ def format_markup
34
+ self.body = RedCloth.new(self.body_raw,[:sanitize_html, :filter_html]).to_html
35
+
36
+ #~ if self.excerpt.blank?
37
+ #~ self.excerpt = self.body.gsub(/\<[^\>]+\>/, '')[0...50] + "..."
38
+ #~ else
39
+ #~ self.excerpt = self.excerpt.gsub(/\<[^\>]+\>/, '')
40
+ #~ end
41
+
42
+ end
43
+
44
+ def generate_permalink
45
+ self.permalink = self.title.dup if self.permalink.blank?
46
+ self.permalink.linkify!
47
+ end
48
+
49
+ def link
50
+ ensure_slash_prefix permalink
51
+ end
52
+
53
+ def published
54
+ #self.published_at ||= Time.now unless self.is_active == 0
55
+ if self.is_active == 0
56
+ if !self.published_at.blank?
57
+ self.published_at = nil
58
+ end
59
+ else
60
+ if self.published_at.blank?
61
+ self.published_at = Time.now
62
+ end
63
+ end
64
+ end
65
+
66
+ def month
67
+ published_at.strftime('%B %Y')
68
+ end
69
+
70
+ def to_param
71
+ "#{id}-#{permalink}"
72
+ end
73
+
74
+ private
75
+
76
+ def ensure_slash_prefix(str)
77
+ str.index('/') == 0 ? str : '/' + str
78
+ end
79
+
80
+ end
@@ -0,0 +1,76 @@
1
+
2
+ .posts {
3
+ .post {
4
+ border-bottom: solid 1px #ccc;
5
+ }
6
+ }
7
+
8
+ .post {
9
+ padding-bottom: 10px;
10
+ margin-bottom: 20px;
11
+ font-size: 14px;
12
+
13
+ .post-content {
14
+ p {
15
+ padding: 0 0 .5em 0;
16
+ margin: .5em 0 0;
17
+ }
18
+ }
19
+ h2.post-title {
20
+ margin: 0;
21
+ padding-bottom: 10px;
22
+ line-height: 1em;
23
+ text-transform: none;
24
+ a, a:visited {
25
+ color: #333;
26
+ text-decoration: none;
27
+ font-weight: normal;
28
+ }
29
+ a:hover {
30
+ color: #333;
31
+ text-decoration: underline;
32
+ }
33
+ }
34
+ .post-date {
35
+ margin: 0 0 .5em 0;
36
+ font-size: 11px;
37
+ padding: 0;
38
+ color: #a5a5a5;
39
+ a {
40
+ text-decoration: none;
41
+ }
42
+ }
43
+ }
44
+
45
+ .post {
46
+ .post-meta {
47
+ margin: 3px 0 1.5em 0;
48
+ font-size: 12px;
49
+ a {
50
+ color: #999999;
51
+ font-weight: normal;
52
+ }
53
+ }
54
+ }
55
+
56
+
57
+ #comments {
58
+ border-top: solid 5px #999;
59
+ padding-top: 10px;
60
+ }
61
+
62
+ .comment {
63
+ padding-top: 5px;
64
+ margin-bottom: 20px;
65
+ border-top: solid 1px #ccc;
66
+ p {
67
+ padding: 5px 0;
68
+ }
69
+ .meta {
70
+ span {
71
+ padding-left: 5px;
72
+ font-size: 11px;
73
+ color: #999;
74
+ }
75
+ }
76
+ }