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
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,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"> </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
|
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
|
+
});
|