radiant-concurrent_draft-extension 1.0.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.
- 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
|