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
data/config/routes.rb ADDED
@@ -0,0 +1,9 @@
1
+ ActionController::Routing::Routes.draw do |map|
2
+ map.page_schedule_draft_promotion 'admin/pages/schedule_draft_promotion/:id',
3
+ :controller => 'admin/pages', :action => 'schedule_draft_promotion'
4
+ map.snippet_schedule_draft_promotion 'admin/snippet/schedule_draft_promotion/:id',
5
+ :controller => 'admin/snippets', :action => 'schedule_draft_promotion'
6
+ map.layout_schedule_draft_promotion 'admin/layout/schedule_draft_promotion/:id',
7
+ :controller => 'admin/layouts', :action => 'schedule_draft_promotion'
8
+ map.page_unpublish 'admin/pages/unpublish/:id', :controller => 'admin/pages', :action => 'unpublish'
9
+ end
@@ -0,0 +1,29 @@
1
+ class UpdateSchemata < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :pages, :draft_promotion_scheduled_at, :datetime
4
+ add_column :pages, :draft_promoted_at, :datetime
5
+ add_column :page_parts, :draft_content, :text
6
+ add_column :snippets, :draft_promotion_scheduled_at, :datetime
7
+ add_column :snippets, :draft_promoted_at, :datetime
8
+ add_column :snippets, :draft_content, :text
9
+ add_column :layouts, :draft_promotion_scheduled_at, :datetime
10
+ add_column :layouts, :draft_promoted_at, :datetime
11
+ add_column :layouts, :draft_content, :text
12
+ Page.reset_column_information
13
+ PagePart.reset_column_information
14
+ Snippet.reset_column_information
15
+ Layout.reset_column_information
16
+ end
17
+
18
+ def self.down
19
+ remove_column :page_parts, :draft_content
20
+ remove_column :pages, :draft_promotion_scheduled_at
21
+ remove_column :pages,:draft_promoted_at
22
+ remove_column :snippets, :draft_content
23
+ remove_column :snippets, :draft_promotion_scheduled_at
24
+ remove_column :snippets, :draft_promoted_at
25
+ remove_column :layouts, :draft_content
26
+ remove_column :layouts, :draft_promotion_scheduled_at
27
+ remove_column :layouts, :draft_promoted_at
28
+ end
29
+ end
@@ -0,0 +1,10 @@
1
+ class CreateDraftPageElements < ActiveRecord::Migration
2
+ def self.up
3
+ PagePart.update_all("draft_content = content")
4
+ Snippet.update_all("draft_content = content")
5
+ Layout.update_all("draft_content = content")
6
+ end
7
+
8
+ def self.down
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ class AddPublisherRole < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :users, :publisher, :boolean, :default => false
4
+ end
5
+
6
+ def self.down
7
+ remove_column :users, :publisher
8
+ end
9
+ end
@@ -0,0 +1,52 @@
1
+ module ConcurrentDraft::AdminControllerExtensions
2
+ def self.included(base)
3
+ base.class_eval do
4
+ helper_method :model
5
+ helper_method :model_class
6
+ public :model, :model_class
7
+ only_allow_access_to :schedule_draft_promotion, :unpublish,
8
+ :when => [:publisher, :admin],
9
+ :denied_message => "You must have publisher privileges to execute this action.",
10
+ :denied_url => {:action => 'edit'}
11
+ after_filter :check_for_promote_now, :only => [:create, :update]
12
+ end
13
+ end
14
+
15
+ def authorized_user?
16
+ (current_user.publisher? || current_user.admin?)
17
+ end
18
+
19
+ def check_for_promote_now
20
+ if params[:promote] && authorized_user?
21
+ model.promote_draft!
22
+ flash[:notice] = "The existing draft #{model_class.to_s.downcase} has been saved and promoted, and is now live."
23
+ flash.keep
24
+ end
25
+ end
26
+
27
+ def schedule_draft_promotion
28
+ self.model = model_class.find(params[:id])
29
+ case params[:commit]
30
+ when model_class.promote_now_text
31
+ model.promote_draft!
32
+ flash[:notice] = "The existing draft #{model_class.to_s.downcase} has been promoted and is now live."
33
+ when model_class.schedule_promotion_text
34
+ if model.update_attributes(params[model.class.name.underscore.intern])
35
+ flash[:notice] = "The draft #{model_class.to_s.downcase} will be promoted on #{model.draft_promotion_scheduled_at.to_s(:long_civilian)}."
36
+ else
37
+ flash[:error] = model.errors.full_messages.to_sentence
38
+ end
39
+ when model_class.cancel_promotion_text
40
+ model.cancel_promotion!
41
+ flash[:notice] = "The scheduled draft #{model_class.to_s.downcase} promotion has been cancelled."
42
+ end
43
+ redirect_to :action => "edit"
44
+ end
45
+
46
+ def unpublish
47
+ self.model = model_class.find(params[:id])
48
+ model.unpublish!
49
+ flash[:notice] = "#{model_class} has been unpublished and reset to draft mode -- no draft promotion scheduled."
50
+ redirect_to :action => "edit"
51
+ end
52
+ end
@@ -0,0 +1,39 @@
1
+ module ConcurrentDraft::HelperExtensions
2
+ def updated_stamp(model)
3
+ unless model.new_record?
4
+ updated_by = (model.updated_by || model.created_by) if model.respond_to?(:updated_by)
5
+ login = updated_by ? updated_by.login : nil
6
+ time = (model.updated_at || model.created_at)
7
+ promoted_at = model.draft_promoted_at if model.respond_to?(:draft_promoted_at)
8
+ html = %{<p style="clear: left"><small>}
9
+ if login or time
10
+ html << 'Last updated '
11
+ html << %{by #{login} } if login
12
+ html << %{at #{ timestamp(time) }} if time
13
+ html << '. '
14
+ end
15
+ if promoted_at
16
+ html << %{Last promoted at #{ timestamp(promoted_at) }.}
17
+ end
18
+ html << %{</small></p>}
19
+ html
20
+ else
21
+ %{<p class="clear">&nbsp;</p>}
22
+ end
23
+ end
24
+
25
+ def save_model_button(_model)
26
+ label = _model.new_record? ? "Create" : "Save"
27
+ submit_tag "#{label} and Exit", :class => 'button'
28
+ end
29
+
30
+ def save_model_and_continue_editing_button(_model)
31
+ label = _model.new_record? ? "Create" : "Save"
32
+ submit_tag label, :name => 'continue', :class => 'button'
33
+ end
34
+
35
+ def save_model_and_promote_button(_model)
36
+ label = _model.new_record? ? "Create" : "Save"
37
+ submit_tag "#{label} and Promote Now", :name => 'promote', :class => 'button'
38
+ end
39
+ end
@@ -0,0 +1,77 @@
1
+ module ConcurrentDraft::ModelExtensions
2
+
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ base.class_eval do
6
+ validate :promotion_date_in_future
7
+ if instance_methods.include?('after_initialize')
8
+ alias_method_chain :after_initialize, :drafts
9
+ else
10
+ def after_initialize
11
+ promote_on_load
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ module ClassMethods
18
+ def promote_now_text; "Promote Now" end
19
+ def schedule_promotion_text; "Schedule for promotion on -->" end
20
+ def cancel_promotion_text; "Cancel scheduled promotion" end
21
+ end
22
+
23
+ def display_name
24
+ if self.class.to_s == 'Page'
25
+ self.title
26
+ else
27
+ self.name
28
+ end
29
+ end
30
+
31
+ def promotion_date_in_future
32
+ if respond_to?(:draft_promotion_scheduled_at) && !draft_promotion_scheduled_at.blank? && draft_promotion_scheduled_at < Time.now
33
+ errors.add :draft_promotion_scheduled_at, "may not be in the past"
34
+ end
35
+ end
36
+
37
+ def promote_on_load
38
+ promote_draft! if respond_to?(:draft_promotion_scheduled_at) && draft_should_be_promoted?
39
+ end
40
+
41
+ def after_initialize_with_drafts
42
+ promote_on_load
43
+ after_initialize_without_drafts
44
+ end
45
+
46
+ def has_draft_promotion_scheduled?
47
+ !draft_promotion_scheduled_at.blank?
48
+ end
49
+
50
+ def has_draft_promoted?
51
+ !draft_promoted_at.blank? && draft_promoted_at <= Time.now
52
+ end
53
+
54
+ def cancel_promotion!
55
+ update_attribute("draft_promotion_scheduled_at", nil)
56
+ end
57
+
58
+ def draft_should_be_promoted?
59
+ has_draft_promotion_scheduled? && draft_promotion_scheduled_at <= Time.now
60
+ end
61
+
62
+ def promote_draft!
63
+ update_attributes("content" => draft_content) if respond_to?(:content) && respond_to?(:draft_content)
64
+ update_attributes("draft_promotion_scheduled_at" => nil, "draft_promoted_at" => Time.now) if respond_to?(:draft_promoted_at)
65
+ Radiant::Cache.clear if defined?(Radiant::Cache)
66
+ end
67
+
68
+ def unpublish!
69
+ update_attributes("content" => nil) if respond_to?(:content) && respond_to?(:draft_content)
70
+ update_attributes("draft_promotion_scheduled_at" => nil, "draft_promoted_at" => nil) if respond_to?(:draft_promoted_at)
71
+ end
72
+
73
+ def publishable?
74
+ has_attribute?("published_at") && has_attribute?("status_id")
75
+ end
76
+
77
+ end
@@ -0,0 +1,28 @@
1
+ module ConcurrentDraft::PageExtensions
2
+
3
+ def self.included(base)
4
+ base.class_eval do
5
+ alias_method_chain :parse_object, :drafts
6
+ end
7
+ end
8
+
9
+ def promote_draft!
10
+ parts.reload.each(&:promote_draft!)
11
+ update_attribute('status_id', Status[:published].id)
12
+ super
13
+ end
14
+
15
+ def unpublish!
16
+ parts.each(&:unpublish!)
17
+ update_attributes('published_at' => nil, 'status_id' => Status[:draft].id)
18
+ super
19
+ end
20
+
21
+ private
22
+
23
+ def parse_object_with_drafts(object)
24
+ object.content = object.draft_content unless published?
25
+ parse_object_without_drafts(object)
26
+ end
27
+
28
+ end
@@ -0,0 +1,16 @@
1
+ module ConcurrentDraft::SiteControllerExtensions
2
+ def self.included(base)
3
+ base.class_eval do
4
+ before_filter :publish_if_scheduled, :only => :show_page
5
+ end
6
+ end
7
+
8
+ def publish_if_scheduled
9
+ url = Array === params[:url] ? params[:url].join('/') : params[:url]
10
+ page = Page.find_by_url(url, false)
11
+ if page && !page.published? && page.draft_should_be_promoted?
12
+ page.update_attribute('status_id', Status[:published].id)
13
+ end
14
+ true
15
+ end
16
+ end
@@ -0,0 +1,56 @@
1
+ module ConcurrentDraft::Tags
2
+ include Radiant::Taggable
3
+
4
+ def self.included(base)
5
+ base.class_eval do
6
+ %w{content snippet}.each do |t|
7
+ alias_method "tag:old_#{t}", "tag:#{t}"
8
+ alias_method "tag:#{t}", "tag:concurrent_draft_#{t}"
9
+ end
10
+ end
11
+ end
12
+
13
+ tag 'concurrent_draft_content' do |tag|
14
+ page = tag.locals.page
15
+ part_name = tag_part_name(tag)
16
+ boolean_attr = proc do |attribute_name, default|
17
+ attribute = (tag.attr[attribute_name] || default).to_s
18
+ raise TagError.new(%{`#{attribute_name}' attribute of `content' tag must be set to either "true" or "false"}) unless attribute =~ /true|false/i
19
+ (attribute.downcase == 'true') ? true : false
20
+ end
21
+ inherit = boolean_attr['inherit', false]
22
+ part_page = page
23
+ if inherit
24
+ while (part_page.part(part_name).nil? and (not part_page.parent.nil?)) do
25
+ part_page = part_page.parent
26
+ end
27
+ end
28
+ contextual = boolean_attr['contextual', true]
29
+ part = part_page.part(part_name)
30
+ ### CONCURRENT DRAFTS CHANGE ###
31
+ # Show the draft content on the dev site #
32
+ part.content = part.draft_content if part && dev?(tag.globals.page.request)
33
+ ### END CONCURRENT DRAFTS CHANGE ###
34
+ tag.locals.page = part_page unless contextual
35
+ tag.globals.page.render_snippet(part) unless part.nil?
36
+ end
37
+
38
+ tag 'concurrent_draft_snippet' do |tag|
39
+ if name = tag.attr['name']
40
+ if snippet = Snippet.find_by_name(name.strip)
41
+ tag.locals.yield = tag.expand if tag.double?
42
+ ### CONCURRENT DRAFTS CHANGE ###
43
+ # Show the draft content on the dev site #
44
+ # Promote the snippet if it needs to be #
45
+ snippet.promote_draft! if snippet.draft_should_be_promoted?
46
+ snippet.content = snippet.draft_content if dev?(tag.globals.page.request)
47
+ ### END CONCURRENT DRAFTS CHANGE ###
48
+ tag.globals.page.render_snippet(snippet)
49
+ else
50
+ raise StandardTags::TagError.new("snippet not found: #{name}")
51
+ end
52
+ else
53
+ raise StandardTags::TagError.new("`snippet' tag must contain `name' attribute")
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,46 @@
1
+ namespace :radiant do
2
+ namespace :extensions do
3
+ namespace :concurrent_draft do
4
+
5
+ desc "Runs the migration of the ConcurrentDraft extension"
6
+ task :migrate => :environment do
7
+ require 'radiant/extension_migrator'
8
+ if ENV["VERSION"]
9
+ ConcurrentDraftExtension.migrator.migrate(ENV["VERSION"].to_i)
10
+ else
11
+ ConcurrentDraftExtension.migrator.migrate
12
+ end
13
+ end
14
+
15
+ desc "Copies public assets of the Concurrent Draft to the instance public/ directory."
16
+ task :update => :environment do
17
+ is_svn_or_dir = proc {|path| path =~ /\.svn/ || File.directory?(path) }
18
+ Dir[ConcurrentDraftExtension.root + "/public/**/*"].reject(&is_svn_or_dir).each do |file|
19
+ path = file.sub(ConcurrentDraftExtension.root, '')
20
+ directory = File.dirname(path)
21
+ puts "Copying #{path}..."
22
+ mkdir_p RAILS_ROOT + directory
23
+ cp file, RAILS_ROOT + path
24
+ end
25
+ end
26
+
27
+ desc "Create drafts for all snippets"
28
+ task :create_draft_snippets => :environment do
29
+ print 'copying content to draft_content for all snippets...'
30
+ Snippet.update_all('draft_content = content')
31
+ puts 'done.'
32
+ end
33
+
34
+ desc "Promote all drafts of all assets"
35
+ task :promote_all => :environment do
36
+ [Page, Snippet, Layout].each do |asset|
37
+ print "Promoting all #{asset.to_s.pluralize}..."
38
+ @user = User.find(:first) ## Admin user
39
+ asset.find(:all).each(&:promote_draft!)
40
+ asset.update_all("updated_by_id = #{@user.id}")
41
+ puts "done."
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,72 @@
1
+ Event.addBehavior({'a.popup': Popup.TriggerBehavior()});
2
+
3
+ var Draft = {};
4
+
5
+ Draft.ControlBox = Behavior.create({
6
+ initialize: function(){
7
+ document.observe('click', Element.removeClassName.curry(this.element, 'active'));
8
+ },
9
+ onclick: function(e){
10
+ e.stop();
11
+ this.element.addClassName('active');
12
+ }
13
+ });
14
+
15
+ Draft.RevertLink = Behavior.create({
16
+ initialize: function(){
17
+ this.popup = $('revert-draft-popup');
18
+ },
19
+ onclick: function(e){
20
+ e.stop();
21
+ this.element.up('.active').removeClassName('active');
22
+ $$('input[name*="[content]"]').each(this.copyData, this);
23
+ center(this.popup);
24
+ this.popup.show();
25
+ },
26
+ copyData: function(input){
27
+ // var draft_id = input.name.gsub(/content/, 'draft_content').gsub(/[\[\]]+/, '_').sub(/\_*$/, '');
28
+ var draft_id = input.id.gsub(/content/, 'draft_content');
29
+ var draft = $(draft_id);
30
+ // The following was modified to accommodate templates extension
31
+ if (draft) {
32
+ switch(draft.type){
33
+ case 'checkbox':
34
+ draft.checked = (input.value == draft.value);
35
+ break;
36
+ default:
37
+ draft.value = input.value;
38
+ break;
39
+ }
40
+ draft.fire('draft:reverted');
41
+ } else {
42
+ // for true/false radio buttons in templates extension
43
+ var draft_true = document.getElementById(draft_id + '_true');
44
+ var draft_false = document.getElementById(draft_id + '_false');
45
+ if (draft_true && draft_false && draft_true.type == 'radio' && draft_false.type == 'radio') {
46
+ if (input.value == 'true') {
47
+ draft_true.checked = true;
48
+ } else if (input.value == 'false') {
49
+ draft_false.checked = true;
50
+ }
51
+ }
52
+ }
53
+ }
54
+ });
55
+
56
+ Draft.ScheduleLink = Behavior.create({
57
+ onclick: function(e){
58
+ e.stop();
59
+ this.element.up('.active').removeClassName('.active');
60
+ var element = $(this.element.href.split('#')[1]);
61
+ center(element);
62
+ element.show();
63
+ element.down('form').focus();
64
+ }
65
+ });
66
+
67
+
68
+ Event.addBehavior({
69
+ '#draft-controls': Draft.ControlBox,
70
+ '#draft-controls li.revert a': Draft.RevertLink,
71
+ '#draft-controls li.schedule_draft a': Draft.ScheduleLink
72
+ });