radiant-scheduler-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/README.md +18 -0
- data/Rakefile +109 -0
- data/app/views/admin/pages/_edit_scheduler_meta.html.haml +48 -0
- data/cucumber.yml +1 -0
- data/db/migrate/001_add_schedule_fields.rb +11 -0
- data/features/support/env.rb +16 -0
- data/features/support/paths.rb +14 -0
- data/lib/radiant-scheduler-extension.rb +8 -0
- data/lib/scheduler/controller_extensions.rb +10 -0
- data/lib/scheduler/page_extensions.rb +58 -0
- data/lib/tasks/scheduler_extension_tasks.rake +28 -0
- data/public/javascripts/date_selector.js +181 -0
- data/public/javascripts/lowpro.js +338 -0
- data/radiant-scheduler-extension.gemspec +29 -0
- data/scheduler_extension.rb +16 -0
- data/spec/ci/before_script +23 -0
- data/spec/ci/script +4 -0
- data/spec/controllers/controller_extensions_spec.rb +17 -0
- data/spec/datasets/pages_with_scheduling_dataset.rb +14 -0
- data/spec/models/page_extensions_spec.rb +123 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +36 -0
- metadata +97 -0
data/README.md
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# Radiant Scheduler Extension
|
2
|
+
|
3
|
+
[](http://travis-ci.org/radiant/radiant-scheduler-extension)
|
4
|
+
|
5
|
+
Created by: Sean Cribbs, September 2007
|
6
|
+
|
7
|
+
The Scheduler extension creates publish and expiration dates (or
|
8
|
+
appearance and disappearance) that are configurable by the content
|
9
|
+
editor. These may be set in the "meta" area of the page editing
|
10
|
+
screen, and include a calendar-style date picker, thanks to Dan Webb's
|
11
|
+
wonderful LowPro library (and his date_selector behavior). These
|
12
|
+
dates only affect what may be found from the 'live' site. All pages
|
13
|
+
are accessible when in 'dev' or 'preview' mode.
|
14
|
+
|
15
|
+
## Acknowledgments
|
16
|
+
|
17
|
+
Thanks to Digital Pulp, Inc. for funding the initial development of this
|
18
|
+
extension as part of the Redken.com project.
|
data/Rakefile
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
# Determine where the RSpec plugin is by loading the boot
|
2
|
+
unless defined? RADIANT_ROOT
|
3
|
+
ENV["RAILS_ENV"] = "test"
|
4
|
+
case
|
5
|
+
when ENV["RADIANT_ENV_FILE"]
|
6
|
+
require File.dirname(ENV["RADIANT_ENV_FILE"]) + "/boot"
|
7
|
+
when File.dirname(__FILE__) =~ %r{vendor/radiant/vendor/extensions}
|
8
|
+
require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../../")}/config/boot"
|
9
|
+
else
|
10
|
+
require "#{File.expand_path(File.dirname(__FILE__) + "/../../../")}/config/boot"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'rake'
|
15
|
+
require 'rdoc/task'
|
16
|
+
require 'rake/testtask'
|
17
|
+
|
18
|
+
rspec_base = File.expand_path(RADIANT_ROOT + '/vendor/plugins/rspec/lib')
|
19
|
+
$LOAD_PATH.unshift(rspec_base) if File.exist?(rspec_base)
|
20
|
+
require 'spec/rake/spectask'
|
21
|
+
require 'cucumber'
|
22
|
+
require 'cucumber/rake/task'
|
23
|
+
|
24
|
+
# Cleanup the RADIANT_ROOT constant so specs will load the environment
|
25
|
+
Object.send(:remove_const, :RADIANT_ROOT)
|
26
|
+
|
27
|
+
extension_root = File.expand_path(File.dirname(__FILE__))
|
28
|
+
|
29
|
+
task :default => [:spec, :features]
|
30
|
+
task :stats => "spec:statsetup"
|
31
|
+
|
32
|
+
desc "Run all specs in spec directory"
|
33
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
34
|
+
t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
|
35
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
36
|
+
end
|
37
|
+
|
38
|
+
task :features => 'spec:integration'
|
39
|
+
|
40
|
+
namespace :spec do
|
41
|
+
desc "Run all specs in spec directory with RCov"
|
42
|
+
Spec::Rake::SpecTask.new(:rcov) do |t|
|
43
|
+
t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
|
44
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
45
|
+
t.rcov = true
|
46
|
+
t.rcov_opts = ['--exclude', 'spec', '--rails']
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "Print Specdoc for all specs"
|
50
|
+
Spec::Rake::SpecTask.new(:doc) do |t|
|
51
|
+
t.spec_opts = ["--format", "specdoc", "--dry-run"]
|
52
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
53
|
+
end
|
54
|
+
|
55
|
+
[:models, :controllers, :views, :helpers].each do |sub|
|
56
|
+
desc "Run the specs under spec/#{sub}"
|
57
|
+
Spec::Rake::SpecTask.new(sub) do |t|
|
58
|
+
t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
|
59
|
+
t.spec_files = FileList["spec/#{sub}/**/*_spec.rb"]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
desc "Run the Cucumber features"
|
64
|
+
Cucumber::Rake::Task.new(:integration) do |t|
|
65
|
+
t.fork = true
|
66
|
+
t.cucumber_opts = ['--format', (ENV['CUCUMBER_FORMAT'] || 'pretty')]
|
67
|
+
# t.feature_pattern = "#{extension_root}/features/**/*.feature"
|
68
|
+
t.profile = "default"
|
69
|
+
end
|
70
|
+
|
71
|
+
# Setup specs for stats
|
72
|
+
task :statsetup do
|
73
|
+
require 'code_statistics'
|
74
|
+
::STATS_DIRECTORIES << %w(Model\ specs spec/models)
|
75
|
+
::STATS_DIRECTORIES << %w(View\ specs spec/views)
|
76
|
+
::STATS_DIRECTORIES << %w(Controller\ specs spec/controllers)
|
77
|
+
::STATS_DIRECTORIES << %w(Helper\ specs spec/views)
|
78
|
+
::CodeStatistics::TEST_TYPES << "Model specs"
|
79
|
+
::CodeStatistics::TEST_TYPES << "View specs"
|
80
|
+
::CodeStatistics::TEST_TYPES << "Controller specs"
|
81
|
+
::CodeStatistics::TEST_TYPES << "Helper specs"
|
82
|
+
::STATS_DIRECTORIES.delete_if {|a| a[0] =~ /test/}
|
83
|
+
end
|
84
|
+
|
85
|
+
namespace :db do
|
86
|
+
namespace :fixtures do
|
87
|
+
desc "Load fixtures (from spec/fixtures) into the current environment's database. Load specific fixtures using FIXTURES=x,y"
|
88
|
+
task :load => :environment do
|
89
|
+
require 'active_record/fixtures'
|
90
|
+
ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym)
|
91
|
+
(ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(RAILS_ROOT, 'spec', 'fixtures', '*.{yml,csv}'))).each do |fixture_file|
|
92
|
+
Fixtures.create_fixtures('spec/fixtures', File.basename(fixture_file, '.*'))
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
desc 'Generate documentation for the scheduler extension.'
|
100
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
101
|
+
rdoc.rdoc_dir = 'rdoc'
|
102
|
+
rdoc.title = 'SchedulerExtension'
|
103
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
104
|
+
rdoc.rdoc_files.include('README')
|
105
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
106
|
+
end
|
107
|
+
|
108
|
+
# Load any custom rakefiles for extension
|
109
|
+
Dir[File.dirname(__FILE__) + '/tasks/*.rake'].sort.each { |f| require f }
|
@@ -0,0 +1,48 @@
|
|
1
|
+
%tr
|
2
|
+
%td{:class=>"label"} Scheduler
|
3
|
+
%td{:class => "field", :style => "text-align: center"}
|
4
|
+
%label{:for => "page_appears_on", :style => "padding: 0 10px;"} Publish date
|
5
|
+
= text_field "page", "appears_on", :size => 15
|
6
|
+
%label{:for => "page_expires_on", :style => "padding: 0 10px;"} Expiration date
|
7
|
+
= text_field "page", "expires_on", :size => 15
|
8
|
+
- include_javascript 'lowpro'
|
9
|
+
- include_javascript 'date_selector'
|
10
|
+
- content_for :page_scripts do
|
11
|
+
:plain
|
12
|
+
Event.addBehavior({
|
13
|
+
'#page_appears_on, #page_expires_on': DateSelector
|
14
|
+
});
|
15
|
+
- content_for :page_css do
|
16
|
+
:sass
|
17
|
+
#page_expires_on, #page_appears_on
|
18
|
+
:position relative
|
19
|
+
table.calendar
|
20
|
+
:background white
|
21
|
+
:border 1px solid black
|
22
|
+
td
|
23
|
+
:padding 2px
|
24
|
+
:border 1px solid #eee
|
25
|
+
:background white
|
26
|
+
:text-align right
|
27
|
+
&.today
|
28
|
+
a
|
29
|
+
color: red
|
30
|
+
&.selected
|
31
|
+
:background #ddd
|
32
|
+
:font-weight bold
|
33
|
+
:border-color black
|
34
|
+
:border-width 2px
|
35
|
+
.back, .forward
|
36
|
+
:background #ddd
|
37
|
+
th
|
38
|
+
:background #eee
|
39
|
+
a
|
40
|
+
:text-decoration none
|
41
|
+
:color black
|
42
|
+
tbody
|
43
|
+
tr:last-child td
|
44
|
+
:border-bottom 1px solid black
|
45
|
+
td:first-child
|
46
|
+
:border-left 1px solid black
|
47
|
+
td:last-child
|
48
|
+
:border-right 1px solid black
|
data/cucumber.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
default: --format progress features --tags ~@proposed,~@in_progress
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Sets up the Rails environment for Cucumber
|
2
|
+
ENV["RAILS_ENV"] = "test"
|
3
|
+
# Extension root
|
4
|
+
extension_env = File.expand_path(File.dirname(__FILE__) + '/../../../../../config/environment')
|
5
|
+
require extension_env+'.rb'
|
6
|
+
|
7
|
+
Dir.glob(File.join(RADIANT_ROOT, "features", "**", "*.rb")).each {|step| require step}
|
8
|
+
|
9
|
+
Cucumber::Rails::World.class_eval do
|
10
|
+
include Dataset
|
11
|
+
datasets_directory "#{RADIANT_ROOT}/spec/datasets"
|
12
|
+
Dataset::Resolver.default = Dataset::DirectoryResolver.new("#{RADIANT_ROOT}/spec/datasets", File.dirname(__FILE__) + '/../../spec/datasets', File.dirname(__FILE__) + '/../datasets')
|
13
|
+
self.datasets_database_dump_path = "#{Rails.root}/tmp/dataset"
|
14
|
+
|
15
|
+
# dataset :scheduler
|
16
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
module RadiantSchedulerExtension
|
2
|
+
VERSION = "1.0.0"
|
3
|
+
SUMMARY = "Scheduler extension for Radiant CMS"
|
4
|
+
DESCRIPTION = "Allows setting of appearance and expiration dates for pages."
|
5
|
+
URL = "https://github.com/radiant/radiant-scheduler-extension"
|
6
|
+
AUTHORS = ["Sean Cribbs"]
|
7
|
+
EMAIL = ["sean@basho.com"]
|
8
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Scheduler::PageExtensions
|
2
|
+
include Radiant::Taggable
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
class << base
|
7
|
+
alias_method_chain :find_by_path, :scheduling
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def find_by_path_with_scheduling(url, live=true)
|
13
|
+
if live
|
14
|
+
self.with_published_only do
|
15
|
+
find_by_path_without_scheduling(url, live)
|
16
|
+
end
|
17
|
+
else
|
18
|
+
find_by_path_without_scheduling(url, live)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def with_published_only
|
23
|
+
if @with_published
|
24
|
+
yield
|
25
|
+
else
|
26
|
+
@with_published = true
|
27
|
+
result = with_scope(:find => {:conditions => ["(appears_on IS NULL OR appears_on <= ?) AND (expires_on IS NULL OR expires_on > ?)", Date.today, Date.today]}) do
|
28
|
+
yield
|
29
|
+
end
|
30
|
+
@with_published = false
|
31
|
+
result
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def visible?
|
37
|
+
published? && appeared? && !expired?
|
38
|
+
end
|
39
|
+
|
40
|
+
def appeared?
|
41
|
+
appears_on.blank? || appears_on <= Date.today
|
42
|
+
end
|
43
|
+
|
44
|
+
def expired?
|
45
|
+
!expires_on.blank? && self.expires_on < Date.today
|
46
|
+
end
|
47
|
+
|
48
|
+
tag 'children' do |tag|
|
49
|
+
tag.locals.children = tag.locals.page.children
|
50
|
+
if dev?(tag.globals.page.request)
|
51
|
+
tag.expand
|
52
|
+
else
|
53
|
+
Page.with_published_only do
|
54
|
+
tag.expand
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
namespace :radiant do
|
2
|
+
namespace :extensions do
|
3
|
+
namespace :scheduler do
|
4
|
+
|
5
|
+
desc "Runs the migration of the Scheduler extension"
|
6
|
+
task :migrate => :environment do
|
7
|
+
require 'radiant/extension_migrator'
|
8
|
+
if ENV["VERSION"]
|
9
|
+
SchedulerExtension.migrator.migrate(ENV["VERSION"].to_i)
|
10
|
+
else
|
11
|
+
SchedulerExtension.migrator.migrate
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "Copies public assets of the Scheduler to the instance public/ directory."
|
16
|
+
task :update => :environment do
|
17
|
+
is_svn_or_dir = proc {|path| path =~ /\.svn/ || File.directory?(path) }
|
18
|
+
puts "Copying assets from SchedulerExtension"
|
19
|
+
Dir[SchedulerExtension.root + "/public/**/*"].reject(&is_svn_or_dir).each do |file|
|
20
|
+
path = file.sub(SchedulerExtension.root, '')
|
21
|
+
directory = File.dirname(path)
|
22
|
+
mkdir_p RAILS_ROOT + directory, :verbose => false
|
23
|
+
cp file, RAILS_ROOT + path, :verbose => false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
|
2
|
+
/* http://svn.danwebb.net/external/lowpro/trunk/behaviours/date_selector.js */
|
3
|
+
DateSelector = Behavior.create({
|
4
|
+
initialize: function(options) {
|
5
|
+
this.element.addClassName('date_selector');
|
6
|
+
this.calendar = null;
|
7
|
+
this.options = Object.extend(DateSelector.DEFAULTS, options || {});
|
8
|
+
this.date = this.getDate();
|
9
|
+
this._createCalendar();
|
10
|
+
},
|
11
|
+
setDate : function(value) {
|
12
|
+
this.date = value;
|
13
|
+
this.element.value = this.options.setter(this.date);
|
14
|
+
|
15
|
+
if (this.calendar)
|
16
|
+
setTimeout(this.calendar.element.hide.bind(this.calendar.element), 500);
|
17
|
+
},
|
18
|
+
_createCalendar : function() {
|
19
|
+
var calendar = $div({ 'class' : 'date_selector' });
|
20
|
+
document.body.appendChild(calendar);
|
21
|
+
calendar.setStyle({
|
22
|
+
position : 'absolute',
|
23
|
+
zIndex : '500'
|
24
|
+
});
|
25
|
+
this.calendar = new Calendar(calendar, this);
|
26
|
+
},
|
27
|
+
onclick : function(e) {
|
28
|
+
this.calendar.element.setStyle({
|
29
|
+
top : Position.cumulativeOffset(this.element)[1] + this.element.getHeight() + 'px',
|
30
|
+
left : Position.cumulativeOffset(this.element)[0] + 'px'
|
31
|
+
});
|
32
|
+
this.calendar.show();
|
33
|
+
Event.stop(e);
|
34
|
+
},
|
35
|
+
onfocus : function(e) {
|
36
|
+
this.onclick(e);
|
37
|
+
},
|
38
|
+
getDate : function() {
|
39
|
+
return this.options.getter(this.element.value) || new Date;
|
40
|
+
}
|
41
|
+
});
|
42
|
+
|
43
|
+
Calendar = Behavior.create({
|
44
|
+
initialize : function(selector) {
|
45
|
+
this.selector = selector;
|
46
|
+
this.element.hide();
|
47
|
+
Event.observe(document, 'click', this.element.hide.bind(this.element));
|
48
|
+
},
|
49
|
+
show : function() {
|
50
|
+
Calendar.instances.invoke('hide');
|
51
|
+
this.date = this.selector.getDate();
|
52
|
+
this.redraw();
|
53
|
+
this.element.show();
|
54
|
+
this.active = true;
|
55
|
+
},
|
56
|
+
hide : function() {
|
57
|
+
this.element.hide();
|
58
|
+
this.active = false;
|
59
|
+
},
|
60
|
+
redraw : function() {
|
61
|
+
var html = '<table class="calendar">' +
|
62
|
+
' <thead>' +
|
63
|
+
' <tr><th class="back"><a href="#">←</a></th>' +
|
64
|
+
' <th colspan="5" class="month_label">' + this._label() + '</th>' +
|
65
|
+
' <th class="forward"><a href="#">→</a></th></tr>' +
|
66
|
+
' <tr class="day_header">' + this._dayRows() + '</tr>' +
|
67
|
+
' </thead>' +
|
68
|
+
' <tbody>';
|
69
|
+
html += this._buildDateCells();
|
70
|
+
html += '</tbody></table>';
|
71
|
+
this.element.innerHTML = html;
|
72
|
+
},
|
73
|
+
onclick : function(e) {
|
74
|
+
var source = Event.element(e);
|
75
|
+
Event.stop(e);
|
76
|
+
|
77
|
+
if ($(source.parentNode).hasClassName('day')) return this._setDate(source);
|
78
|
+
if ($(source.parentNode).hasClassName('back')) return this._backMonth();
|
79
|
+
if ($(source.parentNode).hasClassName('forward')) return this._forwardMonth();
|
80
|
+
},
|
81
|
+
_setDate : function(source) {
|
82
|
+
if (source.innerHTML.strip() != '') {
|
83
|
+
this.date.setDate(parseInt(source.innerHTML));
|
84
|
+
this.selector.setDate(this.date);
|
85
|
+
this.element.getElementsByClassName('selected').invoke('removeClassName', 'selected');
|
86
|
+
source.parentNode.addClassName('selected');
|
87
|
+
}
|
88
|
+
},
|
89
|
+
_backMonth : function() {
|
90
|
+
this.date.setMonth(this.date.getMonth() - 1);
|
91
|
+
this.redraw();
|
92
|
+
return false;
|
93
|
+
},
|
94
|
+
_forwardMonth : function() {
|
95
|
+
this.date.setMonth(this.date.getMonth() + 1);
|
96
|
+
this.redraw();
|
97
|
+
return false;
|
98
|
+
},
|
99
|
+
_getDateFromSelector : function() {
|
100
|
+
this.date = new Date(this.selector.date.getTime());
|
101
|
+
},
|
102
|
+
_firstDay : function(month, year) {
|
103
|
+
return new Date(year, month, 1).getDay();
|
104
|
+
},
|
105
|
+
_monthLength : function(month, year) {
|
106
|
+
var length = Calendar.MONTHS[month].days;
|
107
|
+
return (month == 1 && (year % 4 == 0) && (year % 100 != 0)) ? 29 : length;
|
108
|
+
},
|
109
|
+
_label : function() {
|
110
|
+
return Calendar.MONTHS[this.date.getMonth()].label + ' ' + this.date.getFullYear();
|
111
|
+
},
|
112
|
+
_dayRows : function() {
|
113
|
+
for (var i = 0, html='', day; day = Calendar.DAYS[i]; i++)
|
114
|
+
html += '<th>' + day + '</th>';
|
115
|
+
return html;
|
116
|
+
},
|
117
|
+
_buildDateCells : function() {
|
118
|
+
var month = this.date.getMonth(), year = this.date.getFullYear();
|
119
|
+
var day = 1, monthLength = this._monthLength(month, year), firstDay = this._firstDay(month, year);
|
120
|
+
|
121
|
+
for (var i = 0, html = '<tr>'; i < 9; i++) {
|
122
|
+
for (var j = 0; j <= 6; j++) {
|
123
|
+
|
124
|
+
if (day <= monthLength && (i > 0 || j >= firstDay)) {
|
125
|
+
var classes = ['day'];
|
126
|
+
|
127
|
+
if (this._compareDate(new Date, year, month, day)) classes.push('today');
|
128
|
+
if (this._compareDate(this.selector.date, year, month, day)) classes.push('selected');
|
129
|
+
|
130
|
+
html += '<td class="' + classes.join(' ') + '">' +
|
131
|
+
'<a href="#">' + day++ + '</a>' +
|
132
|
+
'</td>';
|
133
|
+
} else html += '<td></td>';
|
134
|
+
}
|
135
|
+
|
136
|
+
if (day > monthLength) break;
|
137
|
+
else html += '</tr><tr>';
|
138
|
+
}
|
139
|
+
|
140
|
+
return html + '</tr>';
|
141
|
+
},
|
142
|
+
_compareDate : function(date, year, month, day) {
|
143
|
+
return date.getFullYear() == year &&
|
144
|
+
date.getMonth() == month &&
|
145
|
+
date.getDate() == day;
|
146
|
+
}
|
147
|
+
});
|
148
|
+
|
149
|
+
DateSelector.DEFAULTS = {
|
150
|
+
setter: function(date) {
|
151
|
+
return [
|
152
|
+
date.getFullYear(),
|
153
|
+
date.getMonth() + 1,
|
154
|
+
date.getDate()
|
155
|
+
].join('/');
|
156
|
+
},
|
157
|
+
getter: function(value) {
|
158
|
+
var parsed = Date.parse(value);
|
159
|
+
|
160
|
+
if (!isNaN(parsed)) return new Date(parsed);
|
161
|
+
else return null;
|
162
|
+
}
|
163
|
+
}
|
164
|
+
|
165
|
+
Object.extend(Calendar, {
|
166
|
+
DAYS : $w('S M T W T F S'),
|
167
|
+
MONTHS : [
|
168
|
+
{ label : 'January', days : 31 },
|
169
|
+
{ label : 'February', days : 28 },
|
170
|
+
{ label : 'March', days : 31 },
|
171
|
+
{ label : 'April', days : 30 },
|
172
|
+
{ label : 'May', days : 31 },
|
173
|
+
{ label : 'June', days : 30 },
|
174
|
+
{ label : 'July', days : 31 },
|
175
|
+
{ label : 'August', days : 31 },
|
176
|
+
{ label : 'September', days : 30 },
|
177
|
+
{ label : 'October', days : 31 },
|
178
|
+
{ label : 'November', days : 30 },
|
179
|
+
{ label : 'December', days : 31 }
|
180
|
+
]
|
181
|
+
});
|
@@ -0,0 +1,338 @@
|
|
1
|
+
LowPro = {};
|
2
|
+
LowPro.Version = '0.5';
|
3
|
+
LowPro.CompatibleWithPrototype = '1.6';
|
4
|
+
|
5
|
+
if (Prototype.Version.indexOf(LowPro.CompatibleWithPrototype) != 0 && window.console && window.console.warn)
|
6
|
+
console.warn("This version of Low Pro is tested with Prototype " + LowPro.CompatibleWithPrototype +
|
7
|
+
" it may not work as expected with this version (" + Prototype.Version + ")");
|
8
|
+
|
9
|
+
if (!Element.addMethods)
|
10
|
+
Element.addMethods = function(o) { Object.extend(Element.Methods, o) };
|
11
|
+
|
12
|
+
// Simple utility methods for working with the DOM
|
13
|
+
DOM = {};
|
14
|
+
|
15
|
+
// DOMBuilder for prototype
|
16
|
+
DOM.Builder = {
|
17
|
+
tagFunc : function(tag) {
|
18
|
+
return function() {
|
19
|
+
var attrs, children;
|
20
|
+
if (arguments.length>0) {
|
21
|
+
if (arguments[0].constructor == Object) {
|
22
|
+
attrs = arguments[0];
|
23
|
+
children = Array.prototype.slice.call(arguments, 1);
|
24
|
+
} else {
|
25
|
+
children = arguments;
|
26
|
+
};
|
27
|
+
children = $A(children).flatten()
|
28
|
+
}
|
29
|
+
return DOM.Builder.create(tag, attrs, children);
|
30
|
+
};
|
31
|
+
},
|
32
|
+
create : function(tag, attrs, children) {
|
33
|
+
attrs = attrs || {}; children = children || []; tag = tag.toLowerCase();
|
34
|
+
var el = new Element(tag, attrs);
|
35
|
+
|
36
|
+
for (var i=0; i<children.length; i++) {
|
37
|
+
if (typeof children[i] == 'string')
|
38
|
+
children[i] = document.createTextNode(children[i]);
|
39
|
+
el.appendChild(children[i]);
|
40
|
+
}
|
41
|
+
return $(el);
|
42
|
+
}
|
43
|
+
};
|
44
|
+
|
45
|
+
// Automatically create node builders as $tagName.
|
46
|
+
(function() {
|
47
|
+
var els = ("p|div|span|strong|em|img|table|tr|td|th|thead|tbody|tfoot|pre|code|" +
|
48
|
+
"h1|h2|h3|h4|h5|h6|ul|ol|li|form|input|textarea|legend|fieldset|" +
|
49
|
+
"select|option|blockquote|cite|br|hr|dd|dl|dt|address|a|button|abbr|acronym|" +
|
50
|
+
"script|link|style|bdo|ins|del|object|param|col|colgroup|optgroup|caption|" +
|
51
|
+
"label|dfn|kbd|samp|var").split("|");
|
52
|
+
var el, i=0;
|
53
|
+
while (el = els[i++])
|
54
|
+
window['$' + el] = DOM.Builder.tagFunc(el);
|
55
|
+
})();
|
56
|
+
|
57
|
+
DOM.Builder.fromHTML = function(html) {
|
58
|
+
var root;
|
59
|
+
if (!(root = arguments.callee._root))
|
60
|
+
root = arguments.callee._root = document.createElement('div');
|
61
|
+
root.innerHTML = html;
|
62
|
+
return root.childNodes[0];
|
63
|
+
};
|
64
|
+
|
65
|
+
|
66
|
+
|
67
|
+
// Wraps the 1.6 contentloaded event for backwards compatibility
|
68
|
+
//
|
69
|
+
// Usage:
|
70
|
+
//
|
71
|
+
// Event.onReady(callbackFunction);
|
72
|
+
Object.extend(Event, {
|
73
|
+
onReady : function(f) {
|
74
|
+
if (document.body) f();
|
75
|
+
else document.observe('dom:loaded', f);
|
76
|
+
}
|
77
|
+
});
|
78
|
+
|
79
|
+
// Based on event:Selectors by Justin Palmer
|
80
|
+
// http://encytemedia.com/event-selectors/
|
81
|
+
//
|
82
|
+
// Usage:
|
83
|
+
//
|
84
|
+
// Event.addBehavior({
|
85
|
+
// "selector:event" : function(event) { /* event handler. this refers to the element. */ },
|
86
|
+
// "selector" : function() { /* runs function on dom ready. this refers to the element. */ }
|
87
|
+
// ...
|
88
|
+
// });
|
89
|
+
//
|
90
|
+
// Multiple calls will add to exisiting rules. Event.addBehavior.reassignAfterAjax and
|
91
|
+
// Event.addBehavior.autoTrigger can be adjusted to needs.
|
92
|
+
Event.addBehavior = function(rules) {
|
93
|
+
var ab = this.addBehavior;
|
94
|
+
Object.extend(ab.rules, rules);
|
95
|
+
|
96
|
+
if (!ab.responderApplied) {
|
97
|
+
Ajax.Responders.register({
|
98
|
+
onComplete : function() {
|
99
|
+
if (Event.addBehavior.reassignAfterAjax)
|
100
|
+
setTimeout(function() { ab.reload() }, 10);
|
101
|
+
}
|
102
|
+
});
|
103
|
+
ab.responderApplied = true;
|
104
|
+
}
|
105
|
+
|
106
|
+
if (ab.autoTrigger) {
|
107
|
+
this.onReady(ab.load.bind(ab, rules));
|
108
|
+
}
|
109
|
+
|
110
|
+
};
|
111
|
+
|
112
|
+
Event.delegate = function(rules) {
|
113
|
+
return function(e) {
|
114
|
+
var element = $(e.element());
|
115
|
+
for (var selector in rules)
|
116
|
+
if (element.match(selector)) return rules[selector].apply(this, $A(arguments));
|
117
|
+
}
|
118
|
+
}
|
119
|
+
|
120
|
+
Object.extend(Event.addBehavior, {
|
121
|
+
rules : {}, cache : [],
|
122
|
+
reassignAfterAjax : false,
|
123
|
+
autoTrigger : true,
|
124
|
+
|
125
|
+
load : function(rules) {
|
126
|
+
for (var selector in rules) {
|
127
|
+
var observer = rules[selector];
|
128
|
+
var sels = selector.split(',');
|
129
|
+
sels.each(function(sel) {
|
130
|
+
var parts = sel.split(/:(?=[a-z]+$)/), css = parts[0], event = parts[1];
|
131
|
+
$$(css).each(function(element) {
|
132
|
+
if (event) {
|
133
|
+
var wrappedObserver = Event.addBehavior._wrapObserver(observer);
|
134
|
+
$(element).observe(event, wrappedObserver);
|
135
|
+
Event.addBehavior.cache.push([element, event, wrappedObserver]);
|
136
|
+
} else {
|
137
|
+
if (!element.$$assigned || !element.$$assigned.include(observer)) {
|
138
|
+
if (observer.attach) observer.attach(element);
|
139
|
+
|
140
|
+
else observer.call($(element));
|
141
|
+
element.$$assigned = element.$$assigned || [];
|
142
|
+
element.$$assigned.push(observer);
|
143
|
+
}
|
144
|
+
}
|
145
|
+
});
|
146
|
+
});
|
147
|
+
}
|
148
|
+
},
|
149
|
+
|
150
|
+
unload : function() {
|
151
|
+
this.cache.each(function(c) {
|
152
|
+
Event.stopObserving.apply(Event, c);
|
153
|
+
});
|
154
|
+
this.cache = [];
|
155
|
+
},
|
156
|
+
|
157
|
+
reload: function() {
|
158
|
+
var ab = Event.addBehavior;
|
159
|
+
ab.unload();
|
160
|
+
ab.load(ab.rules);
|
161
|
+
},
|
162
|
+
|
163
|
+
_wrapObserver: function(observer) {
|
164
|
+
return function(event) {
|
165
|
+
if (observer.call(this, event) === false) event.stop();
|
166
|
+
}
|
167
|
+
}
|
168
|
+
|
169
|
+
});
|
170
|
+
|
171
|
+
Event.observe(window, 'unload', Event.addBehavior.unload.bind(Event.addBehavior));
|
172
|
+
|
173
|
+
// A silly Prototype style shortcut for the reckless
|
174
|
+
$$$ = Event.addBehavior.bind(Event);
|
175
|
+
|
176
|
+
// Behaviors can be bound to elements to provide an object orientated way of controlling elements
|
177
|
+
// and their behavior. Use Behavior.create() to make a new behavior class then use attach() to
|
178
|
+
// glue it to an element. Each element then gets it's own instance of the behavior and any
|
179
|
+
// methods called onxxx are bound to the relevent event.
|
180
|
+
//
|
181
|
+
// Usage:
|
182
|
+
//
|
183
|
+
// var MyBehavior = Behavior.create({
|
184
|
+
// onmouseover : function() { this.element.addClassName('bong') }
|
185
|
+
// });
|
186
|
+
//
|
187
|
+
// Event.addBehavior({ 'a.rollover' : MyBehavior });
|
188
|
+
//
|
189
|
+
// If you need to pass additional values to initialize use:
|
190
|
+
//
|
191
|
+
// Event.addBehavior({ 'a.rollover' : MyBehavior(10, { thing : 15 }) })
|
192
|
+
//
|
193
|
+
// You can also use the attach() method. If you specify extra arguments to attach they get passed to initialize.
|
194
|
+
//
|
195
|
+
// MyBehavior.attach(el, values, to, init);
|
196
|
+
//
|
197
|
+
// Finally, the rawest method is using the new constructor normally:
|
198
|
+
// var draggable = new Draggable(element, init, vals);
|
199
|
+
//
|
200
|
+
// Each behaviour has a collection of all its instances in Behavior.instances
|
201
|
+
//
|
202
|
+
var Behavior = {
|
203
|
+
create: function() {
|
204
|
+
var parent = null, properties = $A(arguments);
|
205
|
+
if (Object.isFunction(properties[0]))
|
206
|
+
parent = properties.shift();
|
207
|
+
|
208
|
+
var behavior = function() {
|
209
|
+
if (!this.initialize) {
|
210
|
+
var args = $A(arguments);
|
211
|
+
|
212
|
+
return function() {
|
213
|
+
var initArgs = [this].concat(args);
|
214
|
+
behavior.attach.apply(behavior, initArgs);
|
215
|
+
};
|
216
|
+
} else {
|
217
|
+
var args = (arguments.length == 2 && arguments[1] instanceof Array) ?
|
218
|
+
arguments[1] : Array.prototype.slice.call(arguments, 1);
|
219
|
+
|
220
|
+
this.element = $(arguments[0]);
|
221
|
+
this.initialize.apply(this, args);
|
222
|
+
behavior._bindEvents(this);
|
223
|
+
behavior.instances.push(this);
|
224
|
+
}
|
225
|
+
};
|
226
|
+
|
227
|
+
Object.extend(behavior, Class.Methods);
|
228
|
+
Object.extend(behavior, Behavior.Methods);
|
229
|
+
behavior.superclass = parent;
|
230
|
+
behavior.subclasses = [];
|
231
|
+
behavior.instances = [];
|
232
|
+
|
233
|
+
if (parent) {
|
234
|
+
var subclass = function() { };
|
235
|
+
subclass.prototype = parent.prototype;
|
236
|
+
behavior.prototype = new subclass;
|
237
|
+
parent.subclasses.push(behavior);
|
238
|
+
}
|
239
|
+
|
240
|
+
for (var i = 0; i < properties.length; i++)
|
241
|
+
behavior.addMethods(properties[i]);
|
242
|
+
|
243
|
+
if (!behavior.prototype.initialize)
|
244
|
+
behavior.prototype.initialize = Prototype.emptyFunction;
|
245
|
+
|
246
|
+
behavior.prototype.constructor = behavior;
|
247
|
+
|
248
|
+
return behavior;
|
249
|
+
},
|
250
|
+
Methods : {
|
251
|
+
attach : function(element) {
|
252
|
+
return new this(element, Array.prototype.slice.call(arguments, 1));
|
253
|
+
},
|
254
|
+
_bindEvents : function(bound) {
|
255
|
+
for (var member in bound) {
|
256
|
+
var matches = member.match(/^on(.+)/);
|
257
|
+
if (matches && typeof bound[member] == 'function')
|
258
|
+
bound.element.observe(matches[1], Event.addBehavior._wrapObserver(bound[member].bindAsEventListener(bound)));
|
259
|
+
}
|
260
|
+
}
|
261
|
+
}
|
262
|
+
};
|
263
|
+
|
264
|
+
|
265
|
+
|
266
|
+
Remote = Behavior.create({
|
267
|
+
initialize: function(options) {
|
268
|
+
if (this.element.nodeName == 'FORM') new Remote.Form(this.element, options);
|
269
|
+
else new Remote.Link(this.element, options);
|
270
|
+
}
|
271
|
+
});
|
272
|
+
|
273
|
+
Remote.Base = {
|
274
|
+
initialize : function(options) {
|
275
|
+
this.options = Object.extend({
|
276
|
+
evaluateScripts : true
|
277
|
+
}, options || {});
|
278
|
+
|
279
|
+
this._bindCallbacks();
|
280
|
+
},
|
281
|
+
_makeRequest : function(options) {
|
282
|
+
if (options.update) new Ajax.Updater(options.update, options.url, options);
|
283
|
+
else new Ajax.Request(options.url, options);
|
284
|
+
return false;
|
285
|
+
},
|
286
|
+
_bindCallbacks: function() {
|
287
|
+
$w('onCreate onComplete onException onFailure onInteractive onLoading onLoaded onSuccess').each(function(cb) {
|
288
|
+
if (Object.isFunction(this.options[cb]))
|
289
|
+
this.options[cb] = this.options[cb].bind(this);
|
290
|
+
}.bind(this));
|
291
|
+
}
|
292
|
+
}
|
293
|
+
|
294
|
+
Remote.Link = Behavior.create(Remote.Base, {
|
295
|
+
onclick : function() {
|
296
|
+
var options = Object.extend({ url : this.element.href, method : 'get' }, this.options);
|
297
|
+
return this._makeRequest(options);
|
298
|
+
}
|
299
|
+
});
|
300
|
+
|
301
|
+
|
302
|
+
Remote.Form = Behavior.create(Remote.Base, {
|
303
|
+
onclick : function(e) {
|
304
|
+
var sourceElement = e.element();
|
305
|
+
|
306
|
+
if (['input', 'button'].include(sourceElement.nodeName.toLowerCase()) &&
|
307
|
+
sourceElement.type == 'submit')
|
308
|
+
this._submitButton = sourceElement;
|
309
|
+
},
|
310
|
+
onsubmit : function() {
|
311
|
+
var options = Object.extend({
|
312
|
+
url : this.element.action,
|
313
|
+
method : this.element.method || 'get',
|
314
|
+
parameters : this.element.serialize({ submit: this._submitButton.name })
|
315
|
+
}, this.options);
|
316
|
+
this._submitButton = null;
|
317
|
+
return this._makeRequest(options);
|
318
|
+
}
|
319
|
+
});
|
320
|
+
|
321
|
+
Observed = Behavior.create({
|
322
|
+
initialize : function(callback, options) {
|
323
|
+
this.callback = callback.bind(this);
|
324
|
+
this.options = options || {};
|
325
|
+
this.observer = (this.element.nodeName == 'FORM') ? this._observeForm() : this._observeField();
|
326
|
+
},
|
327
|
+
stop: function() {
|
328
|
+
this.observer.stop();
|
329
|
+
},
|
330
|
+
_observeForm: function() {
|
331
|
+
return (this.options.frequency) ? new Form.Observer(this.element, this.options.frequency, this.callback) :
|
332
|
+
new Form.EventObserver(this.element, this.callback);
|
333
|
+
},
|
334
|
+
_observeField: function() {
|
335
|
+
return (this.options.frequency) ? new Form.Element.Observer(this.element, this.options.frequency, this.callback) :
|
336
|
+
new Form.Element.EventObserver(this.element, this.callback);
|
337
|
+
}
|
338
|
+
});
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "radiant-scheduler-extension"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "radiant-scheduler-extension"
|
7
|
+
s.version = RadiantSchedulerExtension::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = RadiantSchedulerExtension::AUTHORS
|
10
|
+
s.email = RadiantSchedulerExtension::EMAIL
|
11
|
+
s.homepage = RadiantSchedulerExtension::URL
|
12
|
+
s.summary = RadiantSchedulerExtension::SUMMARY
|
13
|
+
s.description = RadiantSchedulerExtension::DESCRIPTION
|
14
|
+
|
15
|
+
# Define gem dependencies here.
|
16
|
+
# Don't include a dependency on radiant itself: it causes problems when radiant is in vendor/radiant.
|
17
|
+
# s.add_dependency "something", "~> 1.0.0"
|
18
|
+
# s.add_dependency "radiant-some-extension", "~> 1.0.0"
|
19
|
+
|
20
|
+
ignores = if File.exist?('.gitignore')
|
21
|
+
File.read('.gitignore').split("\n").inject([]) {|a,p| a + Dir[p] }
|
22
|
+
else
|
23
|
+
[]
|
24
|
+
end
|
25
|
+
s.files = Dir['**/*'] - ignores
|
26
|
+
s.test_files = Dir['test/**/*','spec/**/*','features/**/*'] - ignores
|
27
|
+
# s.executables = Dir['bin/*'] - ignores
|
28
|
+
s.require_paths = ["lib"]
|
29
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Uncomment this if you reference any of your controllers in activate
|
2
|
+
require_dependency 'application_controller'
|
3
|
+
|
4
|
+
require 'radiant-scheduler-extension'
|
5
|
+
|
6
|
+
class SchedulerExtension < Radiant::Extension
|
7
|
+
version RadiantSchedulerExtension::VERSION
|
8
|
+
description RadiantSchedulerExtension::DESCRIPTION
|
9
|
+
url RadiantSchedulerExtension::URL
|
10
|
+
|
11
|
+
def activate
|
12
|
+
Page.send :include, Scheduler::PageExtensions
|
13
|
+
SiteController.send :include, Scheduler::ControllerExtensions
|
14
|
+
admin.pages.edit.add :extended_metadata, "edit_scheduler_meta"
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
cd ~
|
4
|
+
git clone git://github.com/radiant/radiant.git
|
5
|
+
cd ~/radiant
|
6
|
+
if [[ $RADIANT_VERSION != "master" ]]
|
7
|
+
then
|
8
|
+
git checkout -b $RADIANT_VERSION $RADIANT_VERSION
|
9
|
+
fi
|
10
|
+
cp -r ~/builds/*/radiant-scheduler-extension vendor/extensions/scheduler
|
11
|
+
bundle install
|
12
|
+
|
13
|
+
case $DB in
|
14
|
+
"mysql" )
|
15
|
+
mysql -e 'create database radiant_test;'
|
16
|
+
cp spec/ci/database.mysql.yml config/database.yml;;
|
17
|
+
"postgres" )
|
18
|
+
psql -c 'create database radiant_test;' -U postgres
|
19
|
+
cp spec/ci/database.postgresql.yml config/database.yml;;
|
20
|
+
esac
|
21
|
+
|
22
|
+
bundle exec rake db:migrate
|
23
|
+
bundle exec rake db:migrate:extensions
|
data/spec/ci/script
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.expand_path '../../spec_helper', __FILE__
|
2
|
+
|
3
|
+
describe "Scheduler::ControllerExtensions", :type => :controller do
|
4
|
+
dataset :pages_with_scheduling
|
5
|
+
controller_name :site
|
6
|
+
|
7
|
+
it "should not render invisible pages in live mode" do
|
8
|
+
get :show_page, :url => ['expired']
|
9
|
+
response.should_not be_success
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should render invisible pages in dev mode" do
|
13
|
+
request.host = "dev.example.com"
|
14
|
+
get :show_page, :url => ['expired']
|
15
|
+
response.should be_success
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class PagesWithSchedulingDataset < Dataset::Base
|
2
|
+
uses :pages
|
3
|
+
|
4
|
+
def load
|
5
|
+
create_page "Expired", :appears_on => 2.days.ago.to_date, :expires_on => Date.yesterday
|
6
|
+
create_page "Expired blank start", :expires_on => Date.yesterday
|
7
|
+
create_page "Blank schedule"
|
8
|
+
create_page "Visible blank start", :expires_on => Date.tomorrow
|
9
|
+
create_page "Visible", :appears_on => Date.yesterday, :expires_on => Date.tomorrow
|
10
|
+
create_page "Visible blank end", :appears_on => Date.yesterday
|
11
|
+
create_page "Unappeared blank end", :appears_on => Date.tomorrow
|
12
|
+
create_page "Unappeared", :appears_on => Date.tomorrow, :expires_on => 2.days.from_now.to_date
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require File.expand_path '../../spec_helper', __FILE__
|
2
|
+
|
3
|
+
describe "Scheduler::PageExtensions" do
|
4
|
+
dataset :pages_with_scheduling
|
5
|
+
|
6
|
+
describe "finding pages" do
|
7
|
+
it "should wrap the find_by_path method" do
|
8
|
+
Page.should respond_to(:find_by_path_with_scheduling)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should find visible pages in both modes" do
|
12
|
+
[true, false].each do |live|
|
13
|
+
[:blank_schedule, :visible, :visible_blank_start, :visible_blank_end].each do |page|
|
14
|
+
Page.find_by_path(pages(page).url, live).should == pages(page)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should not find pages scheduled outside the window when live" do
|
20
|
+
[:expired, :expired_blank_start, :unappeared, :unappeared_blank_end].each do |page|
|
21
|
+
Page.find_by_path(pages(page).url).should_not == pages(page)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should find pages scheduled outside the window when dev" do
|
26
|
+
[:expired, :expired_blank_start, :unappeared, :unappeared_blank_end].each do |page|
|
27
|
+
Page.find_by_path(pages(page).url, false).should == pages(page)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "scheduling interrogators" do
|
33
|
+
before :each do
|
34
|
+
@page = Page.new
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "appeared?" do
|
38
|
+
it "should be true when the page has no limits" do
|
39
|
+
@page.should be_appeared
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should be true when the page has no start date" do
|
43
|
+
@page.appears_on = nil
|
44
|
+
@page.expires_on = Date.tomorrow
|
45
|
+
@page.should be_appeared
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should be true when the start date is in the past" do
|
49
|
+
@page.appears_on = Date.yesterday
|
50
|
+
@page.expires_on = Date.tomorrow
|
51
|
+
@page.should be_appeared
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should be false when the start date is in the future" do
|
55
|
+
@page.appears_on = Date.tomorrow
|
56
|
+
@page.should_not be_appeared
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "expired?" do
|
61
|
+
it "should be false when the page has no limits" do
|
62
|
+
@page.should_not be_expired
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should be false when the page has no end date" do
|
66
|
+
@page.appears_on = Date.yesterday
|
67
|
+
@page.expires_on = nil
|
68
|
+
@page.should_not be_expired
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should be false when the end date is in the future" do
|
72
|
+
@page.expires_on = Date.tomorrow
|
73
|
+
@page.should_not be_expired
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should be true when the end date is in the past" do
|
77
|
+
@page.expires_on = Date.yesterday
|
78
|
+
@page.should be_expired
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "visible?" do
|
83
|
+
before :each do
|
84
|
+
@page.stub!(:published?).and_return(true)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should be true when the page is appeared and not expired" do
|
88
|
+
@page.stub!(:appeared?).and_return(true)
|
89
|
+
@page.stub!(:expired?).and_return(false)
|
90
|
+
@page.should be_visible
|
91
|
+
end
|
92
|
+
it "should be false when the page has not appeared" do
|
93
|
+
@page.stub!(:appeared?).and_return(false)
|
94
|
+
@page.stub!(:expired?).and_return(false)
|
95
|
+
@page.should_not be_visible
|
96
|
+
end
|
97
|
+
it "should be false when the page has expired" do
|
98
|
+
@page.stub!(:appeared?).and_return(true)
|
99
|
+
@page.stub!(:expired?).and_return(true)
|
100
|
+
@page.should_not be_visible
|
101
|
+
end
|
102
|
+
it "should be false when the page is unpublished" do
|
103
|
+
@page.stub!(:published?).and_return(false)
|
104
|
+
@page.should_not be_visible
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "<r:children>" do
|
110
|
+
before :each do
|
111
|
+
raise "dev site configured" if Radiant::Config['dev.site']
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should render only visible children in live mode" do
|
115
|
+
pages(:home).should render("<r:children:each><r:title /> </r:children:each>").matching(%r{^((?!expired)(?!unappeared).)*$}i)
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should render all children in dev mode" do
|
119
|
+
pages(:home).should render("<r:children:each><r:title /> </r:children:each>").matching(%r{expired}i).on("dev.example.com")
|
120
|
+
pages(:home).should render("<r:children:each><r:title /> </r:children:each>").matching(%r{unappeared}i).on("dev.example.com")
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
unless defined? RADIANT_ROOT
|
2
|
+
ENV["RAILS_ENV"] = "test"
|
3
|
+
case
|
4
|
+
when ENV["RADIANT_ENV_FILE"]
|
5
|
+
require ENV["RADIANT_ENV_FILE"]
|
6
|
+
when File.dirname(__FILE__) =~ %r{vendor/radiant/vendor/extensions}
|
7
|
+
require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../../../")}/config/environment"
|
8
|
+
else
|
9
|
+
require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../")}/config/environment"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
require "#{RADIANT_ROOT}/spec/spec_helper"
|
13
|
+
|
14
|
+
Dataset::Resolver.default << (File.dirname(__FILE__) + "/datasets")
|
15
|
+
|
16
|
+
if File.directory?(File.dirname(__FILE__) + "/matchers")
|
17
|
+
Dir[File.dirname(__FILE__) + "/matchers/*.rb"].each {|file| require file }
|
18
|
+
end
|
19
|
+
|
20
|
+
Spec::Runner.configure do |config|
|
21
|
+
# config.use_transactional_fixtures = true
|
22
|
+
# config.use_instantiated_fixtures = false
|
23
|
+
# config.fixture_path = RAILS_ROOT + '/spec/fixtures'
|
24
|
+
|
25
|
+
# You can declare fixtures for each behaviour like this:
|
26
|
+
# describe "...." do
|
27
|
+
# fixtures :table_a, :table_b
|
28
|
+
#
|
29
|
+
# Alternatively, if you prefer to declare them only once, you can
|
30
|
+
# do so here, like so ...
|
31
|
+
#
|
32
|
+
# config.global_fixtures = :table_a, :table_b
|
33
|
+
#
|
34
|
+
# If you declare global fixtures, be aware that they will be declared
|
35
|
+
# for all of your examples, even those that don't use them.
|
36
|
+
end
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: radiant-scheduler-extension
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Sean Cribbs
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-03-29 00:00:00 -05:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: Allows setting of appearance and expiration dates for pages.
|
23
|
+
email:
|
24
|
+
- sean@basho.com
|
25
|
+
executables: []
|
26
|
+
|
27
|
+
extensions: []
|
28
|
+
|
29
|
+
extra_rdoc_files: []
|
30
|
+
|
31
|
+
files:
|
32
|
+
- app/views/admin/pages/_edit_scheduler_meta.html.haml
|
33
|
+
- cucumber.yml
|
34
|
+
- db/migrate/001_add_schedule_fields.rb
|
35
|
+
- features/support/env.rb
|
36
|
+
- features/support/paths.rb
|
37
|
+
- lib/radiant-scheduler-extension.rb
|
38
|
+
- lib/scheduler/controller_extensions.rb
|
39
|
+
- lib/scheduler/page_extensions.rb
|
40
|
+
- lib/tasks/scheduler_extension_tasks.rake
|
41
|
+
- public/javascripts/date_selector.js
|
42
|
+
- public/javascripts/lowpro.js
|
43
|
+
- radiant-scheduler-extension.gemspec
|
44
|
+
- Rakefile
|
45
|
+
- README.md
|
46
|
+
- scheduler_extension.rb
|
47
|
+
- spec/ci/before_script
|
48
|
+
- spec/ci/script
|
49
|
+
- spec/controllers/controller_extensions_spec.rb
|
50
|
+
- spec/datasets/pages_with_scheduling_dataset.rb
|
51
|
+
- spec/models/page_extensions_spec.rb
|
52
|
+
- spec/spec.opts
|
53
|
+
- spec/spec_helper.rb
|
54
|
+
has_rdoc: true
|
55
|
+
homepage: https://github.com/radiant/radiant-scheduler-extension
|
56
|
+
licenses: []
|
57
|
+
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
hash: 3
|
69
|
+
segments:
|
70
|
+
- 0
|
71
|
+
version: "0"
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
hash: 3
|
78
|
+
segments:
|
79
|
+
- 0
|
80
|
+
version: "0"
|
81
|
+
requirements: []
|
82
|
+
|
83
|
+
rubyforge_project:
|
84
|
+
rubygems_version: 1.3.9.3
|
85
|
+
signing_key:
|
86
|
+
specification_version: 3
|
87
|
+
summary: Scheduler extension for Radiant CMS
|
88
|
+
test_files:
|
89
|
+
- spec/ci/before_script
|
90
|
+
- spec/ci/script
|
91
|
+
- spec/controllers/controller_extensions_spec.rb
|
92
|
+
- spec/datasets/pages_with_scheduling_dataset.rb
|
93
|
+
- spec/models/page_extensions_spec.rb
|
94
|
+
- spec/spec.opts
|
95
|
+
- spec/spec_helper.rb
|
96
|
+
- features/support/env.rb
|
97
|
+
- features/support/paths.rb
|