spree_wordsmith 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +121 -0
- data/app/controllers/admin/pages_controller.rb +22 -0
- data/app/controllers/admin/posts_controller.rb +20 -0
- data/app/controllers/admin/wordsmith_settings_controller.rb +13 -0
- data/app/controllers/pages_controller.rb +19 -0
- data/app/controllers/posts_controller.rb +30 -0
- data/app/helpers/wordsmith_helper.rb +21 -0
- data/app/models/ability_decorator.rb +23 -0
- data/app/models/page.rb +4 -0
- data/app/models/post.rb +11 -0
- data/app/models/ws_item.rb +80 -0
- data/app/stylesheets/wordsmith.less +76 -0
- data/app/views/admin/pages/_form.html.erb +60 -0
- data/app/views/admin/pages/edit.html.erb +3 -0
- data/app/views/admin/pages/index.html.erb +25 -0
- data/app/views/admin/pages/new.html.erb +4 -0
- data/app/views/admin/posts/_form.html.erb +59 -0
- data/app/views/admin/posts/edit.html.erb +5 -0
- data/app/views/admin/posts/index.html.erb +25 -0
- data/app/views/admin/posts/new.html.erb +6 -0
- data/app/views/admin/posts/show.html.erb +27 -0
- data/app/views/admin/shared/_wordsmith_sub_menu.html.erb +8 -0
- data/app/views/admin/users/_display_name.html.erb +4 -0
- data/app/views/admin/wordsmith_settings/edit.html.erb +34 -0
- data/app/views/admin/wordsmith_settings/show.html.erb +37 -0
- data/app/views/content/show.html.erb +5 -0
- data/app/views/pages/index.html.erb +6 -0
- data/app/views/pages/show.html.erb +4 -0
- data/app/views/posts/_post.html.erb +11 -0
- data/app/views/posts/index.html.erb +13 -0
- data/app/views/posts/index.rss.builder +19 -0
- data/app/views/posts/show.html.erb +10 -0
- data/app/views/posts/tags.html.erb +18 -0
- data/app/views/shared/_recent_articles.html.erb +7 -0
- data/app/views/shared/_wordsmith_content_for.html.erb +7 -0
- data/lib/extensions/string.rb +20 -0
- data/lib/spree_wordsmith.rb +72 -0
- data/lib/spree_wordsmith_hooks.rb +8 -0
- data/lib/tasks/install.rake +33 -0
- data/lib/tasks/spree_wordsmith.rake +21 -0
- data/spec/controllers/admin/pages_controller_spec.rb +10 -0
- data/spec/controllers/admin/posts_controller_spec.rb +10 -0
- data/spec/controllers/posts_controller_spec.rb +27 -0
- data/spec/factories.rb +34 -0
- data/spec/models/posts_spec.rb +9 -0
- data/spec/spec_helper.rb +39 -0
- metadata +326 -0
data/README.markdown
ADDED
@@ -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,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
|
+
|
data/app/models/page.rb
ADDED
data/app/models/post.rb
ADDED
@@ -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
|
+
}
|