radiant-concurrent_draft-extension 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/HELP.rdoc +32 -0
- data/README.textile +52 -0
- data/Rakefile +136 -0
- data/VERSION +1 -0
- data/app/views/admin/_draft_controls.html.haml +85 -0
- data/app/views/admin/_edit_buttons.html.haml +10 -0
- data/app/views/admin/layouts/_edit_content.html.haml +5 -0
- data/app/views/admin/page_parts/_page_part.html.haml +20 -0
- data/app/views/admin/pages/_edit_layout_and_type.html.haml +11 -0
- data/app/views/admin/pages/_published_meta.html.haml +4 -0
- data/app/views/admin/snippets/_edit_content.html.haml +5 -0
- data/app/views/admin/users/_edit_roles.html.haml +26 -0
- data/concurrent_draft_extension.rb +30 -0
- data/config/initializers/radiant_config.rb +3 -0
- data/config/locales/en.yml +11 -0
- data/config/routes.rb +9 -0
- data/db/migrate/001_update_schemata.rb +29 -0
- data/db/migrate/002_create_draft_page_elements.rb +10 -0
- data/db/migrate/003_add_publisher_role.rb +9 -0
- data/lib/concurrent_draft/admin_controller_extensions.rb +52 -0
- data/lib/concurrent_draft/helper_extensions.rb +39 -0
- data/lib/concurrent_draft/model_extensions.rb +77 -0
- data/lib/concurrent_draft/page_extensions.rb +28 -0
- data/lib/concurrent_draft/site_controller_extensions.rb +16 -0
- data/lib/concurrent_draft/tags.rb +56 -0
- data/lib/tasks/concurrent_draft_extension_tasks.rake +46 -0
- data/public/images/admin/cancel.png +0 -0
- data/public/images/admin/clock.png +0 -0
- data/public/images/admin/page_delete.png +0 -0
- data/public/images/admin/page_edit.png +0 -0
- data/public/images/admin/page_refresh.png +0 -0
- data/public/images/admin/tick.png +0 -0
- data/public/javascripts/admin/concurrent_draft.js +72 -0
- data/public/stylesheets/admin/concurrent_draft.css +74 -0
- data/spec/controllers/admin_controller_extensions_spec.rb +184 -0
- data/spec/controllers/site_controller_extensions_spec.rb +31 -0
- data/spec/matchers/concurrent_draft_matcher.rb +11 -0
- data/spec/models/model_extensions_spec.rb +114 -0
- data/spec/models/page_extensions_spec.rb +56 -0
- data/spec/models/tags_spec.rb +43 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +38 -0
- data/test/helpers/caching_test_helper.rb +42 -0
- data/test/helpers/page_part_test_helper.rb +49 -0
- data/test/helpers/page_test_helper.rb +77 -0
- data/vendor/plugins/12_hour_time/CHANGELOG +2 -0
- data/vendor/plugins/12_hour_time/README +16 -0
- data/vendor/plugins/12_hour_time/Rakefile +22 -0
- data/vendor/plugins/12_hour_time/init.rb +1 -0
- data/vendor/plugins/12_hour_time/lib/12_hour_time.rb +78 -0
- data/vendor/plugins/12_hour_time/test/12_hour_time_test.rb +52 -0
- data/vendor/plugins/12_hour_time/test/test_helper.rb +3 -0
- metadata +143 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
#draft-controls-container {
|
2
|
+
float: right;
|
3
|
+
margin: -12px 0 0 0;
|
4
|
+
}
|
5
|
+
|
6
|
+
#draft-controls {
|
7
|
+
position: relative;
|
8
|
+
border: 1px solid rgb(0, 92, 157);
|
9
|
+
background: white;
|
10
|
+
padding: 0px;
|
11
|
+
line-height: 20px;
|
12
|
+
width: 220px;
|
13
|
+
cursor: pointer;
|
14
|
+
-moz-border-radius: 3px;
|
15
|
+
-webkit-border-radius: 3px;
|
16
|
+
}
|
17
|
+
|
18
|
+
#draft-controls .status {
|
19
|
+
margin: 5px;
|
20
|
+
padding: 0 5px 0 20px;
|
21
|
+
background: url(/images/admin/page_edit.png) left center no-repeat;
|
22
|
+
font-size: 80%;
|
23
|
+
color: black;
|
24
|
+
}
|
25
|
+
|
26
|
+
#draft-controls ul.dropdown {
|
27
|
+
position: absolute;
|
28
|
+
margin: -1px 0 0 -1px;
|
29
|
+
width: 210px;
|
30
|
+
top: 28px;
|
31
|
+
left: -1px;
|
32
|
+
border-width: 0 1px 1px 1px;
|
33
|
+
border-style: solid;
|
34
|
+
border-color: rgb(0, 92, 157);
|
35
|
+
display: none;
|
36
|
+
background-color: #eef;
|
37
|
+
padding: 5px;
|
38
|
+
}
|
39
|
+
|
40
|
+
#draft-controls > ul.dropdown {
|
41
|
+
top: auto;
|
42
|
+
left: auto;
|
43
|
+
}
|
44
|
+
|
45
|
+
#draft-controls.active ul.dropdown {
|
46
|
+
display: block;
|
47
|
+
}
|
48
|
+
|
49
|
+
#draft-controls ul.dropdown li {
|
50
|
+
margin-left: 10px;
|
51
|
+
list-style: none;
|
52
|
+
margin-bottom: 3px;
|
53
|
+
padding-right: 10px;
|
54
|
+
white-space: nowrap;
|
55
|
+
}
|
56
|
+
|
57
|
+
#draft-controls ul.dropdown a {
|
58
|
+
color: black;
|
59
|
+
text-decoration: none;
|
60
|
+
outline: none;
|
61
|
+
padding-left: 20px;
|
62
|
+
background-position: left center;
|
63
|
+
background-repeat: no-repeat;
|
64
|
+
}
|
65
|
+
|
66
|
+
#draft-controls ul.dropdown a:hover {
|
67
|
+
text-decoration: underline;
|
68
|
+
}
|
69
|
+
|
70
|
+
#draft-controls .schedule_draft a { background-image: url(/images/admin/clock.png);}
|
71
|
+
#draft-controls .publish a { background-image: url(/images/admin/tick.png);}
|
72
|
+
#draft-controls .cancel a { background-image: url(/images/admin/cancel.png);}
|
73
|
+
#draft-controls .revert a { background-image: url(/images/admin/page_refresh.png);}
|
74
|
+
#draft-controls .unpublish a { background-image: url(/images/admin/page_delete.png);}
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
shared_examples_for 'controller with scheduled draft promotion' do
|
4
|
+
dataset :users
|
5
|
+
|
6
|
+
before :each do
|
7
|
+
create_user "Publisher", :publisher => true
|
8
|
+
# controller.cache.clear
|
9
|
+
@klass = controller.class.model_class
|
10
|
+
@model_symbol = @klass.name.symbolize
|
11
|
+
@object = mock_model(controller.class.model_class, :promote_draft! => nil, :save => true, :url => '')
|
12
|
+
@object.errors.stub!(:full_messages).and_return([])
|
13
|
+
@klass.stub!(:find).and_return(@object)
|
14
|
+
login_as :admin
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "common actions" do
|
18
|
+
|
19
|
+
def do_post(options={})
|
20
|
+
post(:schedule_draft_promotion, {:id => '1', :commit => @klass.promote_now_text}.merge(options))
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should load the model" do
|
24
|
+
@klass.should_receive(:find).with('1').and_return(@object)
|
25
|
+
login_as :admin
|
26
|
+
do_post
|
27
|
+
assigns[@model_symbol].should == @object
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should redirect back to the edit screen" do
|
31
|
+
do_post
|
32
|
+
response.should be_redirect
|
33
|
+
response.should redirect_to(:action => "edit")
|
34
|
+
end
|
35
|
+
|
36
|
+
[:admin, :publisher].each do |user|
|
37
|
+
describe "#{user} user" do
|
38
|
+
before :each do
|
39
|
+
login_as user
|
40
|
+
#request.session[:user_id] = user_id(user)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should allow #{user}" do
|
44
|
+
do_post
|
45
|
+
response.should be_redirect
|
46
|
+
response.should redirect_to(:action => "edit")
|
47
|
+
flash[:error].should be_blank
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
[:designer, :existing].each do |user|
|
53
|
+
before :each do
|
54
|
+
login_as user
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should deny #{user}" do
|
58
|
+
do_post
|
59
|
+
response.should be_redirect
|
60
|
+
response.should redirect_to(:action => "edit")
|
61
|
+
flash[:error].should == 'You must have publisher privileges to execute this action.'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "promoting/publishing now" do
|
67
|
+
|
68
|
+
def do_post
|
69
|
+
post :schedule_draft_promotion, :id => '1', :commit => @klass.promote_now_text
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should promote the draft" do
|
73
|
+
@object.should_receive(:promote_draft!)
|
74
|
+
do_post
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should set the flash message" do
|
78
|
+
do_post
|
79
|
+
flash[:notice].should match(/published|promoted/)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "scheduling draft promotion" do
|
84
|
+
def do_post
|
85
|
+
post :schedule_draft_promotion, :id => '1',
|
86
|
+
@model_symbol => @post_attrs,
|
87
|
+
:commit => @klass.schedule_promotion_text
|
88
|
+
end
|
89
|
+
|
90
|
+
before :each do
|
91
|
+
@post_attrs = {'draft_promotion_scheduled_at' => 3.days.from_now}
|
92
|
+
@object.stub!(:update_attributes)
|
93
|
+
@object.stub!(:draft_promotion_scheduled_at).and_return(3.days.from_now)
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should not promote the draft" do
|
97
|
+
@object.should_not_receive(:promote_draft!)
|
98
|
+
do_post
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should not cancel the draft" do
|
102
|
+
@object.should_not_receive(:cancel_draft!)
|
103
|
+
do_post
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should set the flash notice message when the scheduled time is valid" do
|
107
|
+
@object.should_receive(:update_attributes).with(@post_attrs).and_return(true)
|
108
|
+
do_post
|
109
|
+
flash[:notice].should match(/will be promoted/)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should set the flash error message when the scheduled time is invalid" do
|
113
|
+
@object.should_receive(:update_attributes).with(@post_attrs).and_return(false)
|
114
|
+
do_post
|
115
|
+
flash[:error].should be
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "cancelling promotion" do
|
120
|
+
def do_post
|
121
|
+
post :schedule_draft_promotion, :id => '1', :commit => @klass.cancel_promotion_text
|
122
|
+
end
|
123
|
+
|
124
|
+
before :each do
|
125
|
+
@object.stub!(:cancel_promotion!)
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should cancel the draft promotion" do
|
129
|
+
@object.should_receive(:cancel_promotion!)
|
130
|
+
do_post
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should set the flash message" do
|
134
|
+
do_post
|
135
|
+
flash[:notice].should match(/cancelled/)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe "promotion in conjunction with saving" do
|
140
|
+
before :each do
|
141
|
+
controller.class.skip_before_filter :filter_chain
|
142
|
+
@klass.stub!(:find_by_id).and_return(@object)
|
143
|
+
controller.stub!(:handle_new_or_edit_post_without_promotion).and_return(false)
|
144
|
+
end
|
145
|
+
|
146
|
+
def redirects_to_index
|
147
|
+
response.should be_redirect
|
148
|
+
response.should redirect_to(:action => :index)
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should promote the draft when the 'Save & Promote Now' button was pushed" do
|
152
|
+
@object.should_receive(:promote_draft!).once
|
153
|
+
@object.should_receive(:update_attributes!).once
|
154
|
+
@object.should_receive(:index).once
|
155
|
+
put :update, :id => 1, :promote => 'Save & Promote Now'
|
156
|
+
redirects_to_index
|
157
|
+
end
|
158
|
+
|
159
|
+
it "should not promote the draft when the 'Save & Promote Now' button was not pushed" do
|
160
|
+
@object.should_not_receive(:promote_draft!)
|
161
|
+
@object.should_receive(:update_attributes!).once
|
162
|
+
@object.should_receive(:index).once
|
163
|
+
put :update, :id => 1
|
164
|
+
redirects_to_index
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
after :each do
|
169
|
+
logout
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
describe Admin::PagesController, "with concurrent_draft functions" do
|
175
|
+
it_should_behave_like 'controller with scheduled draft promotion'
|
176
|
+
end
|
177
|
+
|
178
|
+
describe Admin::SnippetsController, "with concurrent_draft functions" do
|
179
|
+
it_should_behave_like 'controller with scheduled draft promotion'
|
180
|
+
end
|
181
|
+
|
182
|
+
describe Admin::LayoutsController, "with concurrent_draft functions" do
|
183
|
+
it_should_behave_like 'controller with scheduled draft promotion'
|
184
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
require 'site_controller'
|
3
|
+
SiteController.module_eval { def rescue_action(e); raise e; end }
|
4
|
+
|
5
|
+
describe SiteController, "(Extended) - concurrent draft changes" do
|
6
|
+
dataset :users_and_pages
|
7
|
+
|
8
|
+
before :each do
|
9
|
+
@page = mock_model(Page, :published? => false, :draft_should_be_promoted? => true, :process => nil, :update_attribute => nil)
|
10
|
+
Page.stub!(:find_by_url).and_return(@page)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should include the extension module" do
|
14
|
+
SiteController.included_modules.should include(ConcurrentDraft::SiteControllerExtensions)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should add the before filter" do
|
18
|
+
SiteController.before_filters.should be_any {|f| f == :publish_if_scheduled }
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should run the before filter" do
|
22
|
+
self.controller.should_receive(:publish_if_scheduled)
|
23
|
+
get :show_page, :url => '/'
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should set the status to published if the draft should be promoted" do
|
27
|
+
Page.should_receive(:find_by_url).at_least(:once).and_return(@page)
|
28
|
+
@page.should_receive(:update_attribute).with('status_id', Status[:published].id)
|
29
|
+
get :show_page, :url => '/'
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
share_examples_for "model with concurrent draft" do
|
4
|
+
it "should be invalid with a draft promotion schedule date in the past" do
|
5
|
+
@object.draft_promotion_scheduled_at = 2.days.ago
|
6
|
+
@object.should_not be_valid
|
7
|
+
@object.should have(1).error_on(:draft_promotion_scheduled_at)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should be valid with a draft promotion schedule date in the future" do
|
11
|
+
@object.draft_promotion_scheduled_at = 1.day.from_now
|
12
|
+
@object.should be_valid
|
13
|
+
@object.should have(0).errors_on(:draft_promotion_scheduled_at)
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "after draft promotion" do
|
17
|
+
before :each do
|
18
|
+
@object.promote_draft!
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should have copied the content from the draft content" do
|
22
|
+
if @object.respond_to?(:content)
|
23
|
+
@object.content.should == @object.draft_content
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should have set the promotion date/time" do
|
28
|
+
@object.draft_promoted_at.should be_close(Time.now, 10)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should have cleared the scheduled date" do
|
32
|
+
@object.draft_promotion_scheduled_at.should be_nil
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should have promoted the draft" do
|
36
|
+
@object.should have_draft_promoted
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should have the draft promotion scheduled if the date is in the future" do
|
41
|
+
@object.draft_promotion_scheduled_at = 1.day.from_now
|
42
|
+
@object.should have_draft_promotion_scheduled
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should cancel the scheduled promotion" do
|
46
|
+
@object.draft_promotion_scheduled_at = 1.day.from_now
|
47
|
+
@object.cancel_promotion!
|
48
|
+
@object.draft_promotion_scheduled_at.should be_nil
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should be promoted when the promotion time has past" do
|
52
|
+
@object.draft_promotion_scheduled_at = 1.minute.ago
|
53
|
+
@object.should be_draft_should_be_promoted
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should automatically promote the draft when necessary on load" do
|
57
|
+
@object.class.update_all({:draft_promotion_scheduled_at => 2.minutes.ago}, :id => @object.id)
|
58
|
+
new_object = @object.class.find(@object.id)
|
59
|
+
new_object.should have_draft_promoted
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe Snippet, "with concurrent draft" do
|
64
|
+
dataset :snippets
|
65
|
+
before :each do
|
66
|
+
@object = snippets(:first)
|
67
|
+
@object.update_attributes(:content => 'content test', :draft_content => 'draft content')
|
68
|
+
end
|
69
|
+
it "should not be publishable" do
|
70
|
+
@object.publishable?.should_not be_true
|
71
|
+
end
|
72
|
+
it_should_behave_like 'model with concurrent draft'
|
73
|
+
end
|
74
|
+
|
75
|
+
describe Layout, "with concurrent draft" do
|
76
|
+
dataset :layouts
|
77
|
+
before :each do
|
78
|
+
@object = layouts(:main)
|
79
|
+
@object.update_attributes(:content => 'content test', :draft_content => 'draft content')
|
80
|
+
end
|
81
|
+
it "should not be publishable" do
|
82
|
+
@object.publishable?.should_not be_true
|
83
|
+
end
|
84
|
+
it_should_behave_like 'model with concurrent draft'
|
85
|
+
end
|
86
|
+
|
87
|
+
describe PagePart, "with concurrent draft" do
|
88
|
+
dataset :pages
|
89
|
+
before :each do
|
90
|
+
@object = pages(:home).parts.first
|
91
|
+
@object.update_attributes(:content => 'content test', :draft_content => 'draft content')
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "after draft promotion" do
|
95
|
+
before :each do
|
96
|
+
@object.promote_draft!
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should have copied the content from the draft content" do
|
100
|
+
@object.content.should == @object.draft_content
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe Page, 'with concurrent draft' do
|
106
|
+
dataset :pages
|
107
|
+
before :each do
|
108
|
+
@object = pages(:home)
|
109
|
+
end
|
110
|
+
it "should be publishable" do
|
111
|
+
@object.publishable?.should be_true
|
112
|
+
end
|
113
|
+
it_should_behave_like 'model with concurrent draft'
|
114
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
describe Page, "with concurrent draft" do
|
4
|
+
dataset :pages_with_layouts
|
5
|
+
before :each do
|
6
|
+
@page = pages(:home)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should be publishable" do
|
10
|
+
@page.publishable?.should be_true
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "when promoting" do
|
14
|
+
|
15
|
+
it "should promote its page parts" do
|
16
|
+
@page.parts.should_receive(:reload).and_return(@page.parts)
|
17
|
+
@page.parts.each {|part| part.should_receive(:promote_draft!) }
|
18
|
+
@page.promote_draft!
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should update its status to published" do
|
22
|
+
@page.update_attribute('status_id', Status[:draft].id)
|
23
|
+
@page.promote_draft!
|
24
|
+
@page.reload.status.should == Status[:published]
|
25
|
+
@page.published?.should be_true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "when unpublishing" do
|
30
|
+
|
31
|
+
before(:each) do
|
32
|
+
@page.promote_draft!
|
33
|
+
@page.unpublish!
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should set the content of its page parts to nil" do
|
37
|
+
@page.parts.each {|part| part.content.should be_nil}
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should retain its draft content" do
|
41
|
+
@page.part("body").should_not be_nil
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should set published_at, draft_promoted_at and draft_promotion_scheduled_at dates to nil" do
|
45
|
+
@page.published_at.should be_nil
|
46
|
+
@page.draft_promoted_at.should be_nil
|
47
|
+
@page.draft_promotion_scheduled_at.should be_nil
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should update its status to draft" do
|
51
|
+
@page.reload.status.should == Status[:draft]
|
52
|
+
@page.published?.should be_false
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|