radiant-concurrent_draft-extension 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/HELP.rdoc +32 -0
  2. data/README.textile +52 -0
  3. data/Rakefile +136 -0
  4. data/VERSION +1 -0
  5. data/app/views/admin/_draft_controls.html.haml +85 -0
  6. data/app/views/admin/_edit_buttons.html.haml +10 -0
  7. data/app/views/admin/layouts/_edit_content.html.haml +5 -0
  8. data/app/views/admin/page_parts/_page_part.html.haml +20 -0
  9. data/app/views/admin/pages/_edit_layout_and_type.html.haml +11 -0
  10. data/app/views/admin/pages/_published_meta.html.haml +4 -0
  11. data/app/views/admin/snippets/_edit_content.html.haml +5 -0
  12. data/app/views/admin/users/_edit_roles.html.haml +26 -0
  13. data/concurrent_draft_extension.rb +30 -0
  14. data/config/initializers/radiant_config.rb +3 -0
  15. data/config/locales/en.yml +11 -0
  16. data/config/routes.rb +9 -0
  17. data/db/migrate/001_update_schemata.rb +29 -0
  18. data/db/migrate/002_create_draft_page_elements.rb +10 -0
  19. data/db/migrate/003_add_publisher_role.rb +9 -0
  20. data/lib/concurrent_draft/admin_controller_extensions.rb +52 -0
  21. data/lib/concurrent_draft/helper_extensions.rb +39 -0
  22. data/lib/concurrent_draft/model_extensions.rb +77 -0
  23. data/lib/concurrent_draft/page_extensions.rb +28 -0
  24. data/lib/concurrent_draft/site_controller_extensions.rb +16 -0
  25. data/lib/concurrent_draft/tags.rb +56 -0
  26. data/lib/tasks/concurrent_draft_extension_tasks.rake +46 -0
  27. data/public/images/admin/cancel.png +0 -0
  28. data/public/images/admin/clock.png +0 -0
  29. data/public/images/admin/page_delete.png +0 -0
  30. data/public/images/admin/page_edit.png +0 -0
  31. data/public/images/admin/page_refresh.png +0 -0
  32. data/public/images/admin/tick.png +0 -0
  33. data/public/javascripts/admin/concurrent_draft.js +72 -0
  34. data/public/stylesheets/admin/concurrent_draft.css +74 -0
  35. data/spec/controllers/admin_controller_extensions_spec.rb +184 -0
  36. data/spec/controllers/site_controller_extensions_spec.rb +31 -0
  37. data/spec/matchers/concurrent_draft_matcher.rb +11 -0
  38. data/spec/models/model_extensions_spec.rb +114 -0
  39. data/spec/models/page_extensions_spec.rb +56 -0
  40. data/spec/models/tags_spec.rb +43 -0
  41. data/spec/spec.opts +6 -0
  42. data/spec/spec_helper.rb +38 -0
  43. data/test/helpers/caching_test_helper.rb +42 -0
  44. data/test/helpers/page_part_test_helper.rb +49 -0
  45. data/test/helpers/page_test_helper.rb +77 -0
  46. data/vendor/plugins/12_hour_time/CHANGELOG +2 -0
  47. data/vendor/plugins/12_hour_time/README +16 -0
  48. data/vendor/plugins/12_hour_time/Rakefile +22 -0
  49. data/vendor/plugins/12_hour_time/init.rb +1 -0
  50. data/vendor/plugins/12_hour_time/lib/12_hour_time.rb +78 -0
  51. data/vendor/plugins/12_hour_time/test/12_hour_time_test.rb +52 -0
  52. data/vendor/plugins/12_hour_time/test/test_helper.rb +3 -0
  53. 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,11 @@
1
+ def have_draft
2
+ be_has_draft
3
+ end
4
+
5
+ def have_draft_promoted
6
+ be_has_draft_promoted
7
+ end
8
+
9
+ def have_draft_promotion_scheduled
10
+ be_has_draft_promotion_scheduled
11
+ 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