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.
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