caboose-cms 0.4.109 → 0.4.110

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.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MGJlYTVjNGY4Y2I2NjgwMDA0MDAwOTZhMTJlM2RlNGJlYzEyZjJiNw==
4
+ YTBjODNmMjE5YzM2OTc1YzdlNzQwNGUzYmFmN2RkYzdjNTk1MTRhMA==
5
5
  data.tar.gz: !binary |-
6
- OTgwYmYzMGYwY2MyMDJmNWFkNzI0MTgzNjNlZTA5MjFlYjM4MGJjYg==
6
+ NDc1ZGI2MmNlZTA1NTkxYWExYTEyYzEzMmE1M2Y5NjZiMDk2ZDBhNA==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- NzUyMGIyNmU0M2U0MjkxMTY4MTU5ZDRiYzk1ZmM1N2YzZGMyZDUxYjE3ZWYy
10
- ZGU4NmU2OTAxZmFlMWQxNjVlZmViODgwMmI0MThiOWY1MGRkYTU3ZjUwNWRi
11
- YjFhZGU2ZDUwZTcwMzVlZGNkZTU4YTliNGMzNDljOTgyMDBlOTA=
9
+ NWM4YzA4ZmE0ZmI4NzczMWI1NWEzY2NlODVlMjQ5ZmYxMjA4MWEwMzE3MDE2
10
+ YTU5ODgyNzk3NTc5YjU2NTdjNzNhZDkyOGVmODBmZmZjYWNiYzM1N2Q4MGVj
11
+ NGM5ZWUyMzRhNjM3NTFiMTg2YmFiYzI3MTk2YmY2MDk4ZmMwMjA=
12
12
  data.tar.gz: !binary |-
13
- Yzg2Nzk0NTUyNDU1NzY1MjUzNmM2MzE4MDMzZjBkNmQwMjI1NGU3NmRjNTM5
14
- MjJkZDMwOGRjYTc2YWZhM2ViZGRmY2Y5NjdkYmQwNzMzZjljMmMwMTMxYzY3
15
- Y2U2YjQzOTZjYzY2MjEzNzFhYjM4YTUwYjZhOTM0Y2M1YjEzZGM=
13
+ NDMzMzQ4OTk1ZTViYzYxZDQ4ZmVhNGNlZmFkZmViYjE3NjczZWVhOWZlNzEx
14
+ OTM3ZjAxNDc2ZWFlYzAyY2MyZWRiMzRhMjI0OTY2ZmZhYWI0MTljZDk5MTAy
15
+ NWM2ZGNlNTVjYTljNmQ1ZjBkNjhkODAyOTExYWJhZTU1Zjc4NzA=
@@ -103,7 +103,7 @@ div.mb_container form.mb_file_form div.mb_placeholder span {
103
103
  }
104
104
 
105
105
  .mb_container input[type='checkbox'] {
106
- /*position: absolute;*/
106
+ position: absolute;
107
107
  top: 4px;
108
108
  left: 0;
109
109
  z-index: 19;
@@ -0,0 +1,77 @@
1
+
2
+ module Caboose
3
+ class CalendarsController < ApplicationController
4
+
5
+ helper :application
6
+
7
+ def before_action
8
+ @page = Page.page_with_uri(request.host_with_port, '/admin')
9
+ end
10
+
11
+ # GET /admin/calendars
12
+ def admin_index
13
+ return if !user_is_allowed('calendars', 'view')
14
+ render :file => 'caboose/extras/error_invalid_site' and return if @site.nil?
15
+
16
+ @calendars = Calendar.reorder(:name).all
17
+ render :layout => 'caboose/admin'
18
+ end
19
+
20
+ # GET /admin/calendars/:id
21
+ def admin_edit
22
+ return unless user_is_allowed('calendars', 'edit')
23
+ @calendar = Calendar.find(params[:id])
24
+
25
+ @d = params[:d] ? DateTime.iso8601(params[:d]) : DateTime.now
26
+ @d = @d - (@d.strftime('%-d').to_i-1)
27
+
28
+ render :layout => 'caboose/admin'
29
+ end
30
+
31
+ # PUT /admin/calendars/:id
32
+ def admin_update
33
+ return unless user_is_allowed('calendars', 'edit')
34
+
35
+ resp = StdClass.new({'attributes' => {}})
36
+ calendar = Calendar.find(params[:id])
37
+
38
+ save = true
39
+ params.each do |name, value|
40
+ case name
41
+ when 'name' then calendar.name = value
42
+ when 'description' then calendar.description = value
43
+ end
44
+ end
45
+
46
+ resp.success = save && calendar.save
47
+ render :json => resp
48
+ end
49
+
50
+ # POST /admin/calendars
51
+ def admin_add
52
+ return unless user_is_allowed('calendars', 'edit')
53
+
54
+ resp = StdClass.new
55
+ calendar = Calendar.new
56
+ calendar.name = params[:name]
57
+ calendar.site_id = @site.id
58
+
59
+ if calendar.name.nil? || calendar.name.strip.length == 0
60
+ resp.error = "Please enter a calendar name."
61
+ else
62
+ calendar.save
63
+ resp.redirect = "/admin/calendars/#{calendar.id}"
64
+ end
65
+ render :json => resp
66
+ end
67
+
68
+ # DELETE /admin/calendars/:id
69
+ def admin_delete
70
+ return unless user_is_allowed('calendars', 'delete')
71
+ Calendar.find(params[:id]).destroy
72
+ resp = StdClass.new({ 'redirect' => "/admin/calendars" })
73
+ render :json => resp
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,95 @@
1
+
2
+ module Caboose
3
+ class EventGroupsController < ApplicationController
4
+
5
+ helper :application
6
+
7
+ # GET /admin/calendars/:calendar_id/events/:id
8
+ def admin_edit
9
+ return unless user_is_allowed('calendars', 'edit')
10
+ @event = CalendarEvent.find(params[:id])
11
+ render :layout => 'caboose/modal'
12
+ end
13
+
14
+ # GET /admin/calendars/:calendar_id/events/new
15
+ def admin_new
16
+ return unless user_is_allowed('calendars', 'edit')
17
+ @calendar = Calendar.find(params[:calendar_id])
18
+ @begin_date = DateTime.iso8601(params[:begin_date])
19
+ render :layout => 'caboose/modal'
20
+ end
21
+
22
+ # PUT /admin/calendars/:calendar_id/events/:id
23
+ def admin_update
24
+ return unless user_is_allowed('calendars', 'edit')
25
+
26
+ resp = StdClass.new
27
+ event = CalendarEvent.find(params[:id])
28
+
29
+ save = true
30
+ params.each do |name, value|
31
+ case name
32
+ when 'name' then event.name = value
33
+ when 'description' then event.description = value
34
+ end
35
+ end
36
+
37
+ resp.success = save && event.save
38
+ render :json => resp
39
+ end
40
+
41
+ # POST /admin/calendars/:calendar_id/events
42
+ def admin_add
43
+ return unless user_is_allowed('calendars', 'edit')
44
+
45
+ resp = StdClass.new
46
+ event = CalendarEvent.new
47
+ event.calendar_id = params[:calendar_id]
48
+ event.name = params[:name]
49
+ event.begin_date = DateTime.iso8601("#{params[:begin_date]}T10:00:00-05:00")
50
+ event.end_date = DateTime.iso8601("#{params[:begin_date]}T10:00:00-05:00")
51
+ event.all_day = true
52
+
53
+ if event.name.nil? || event.name.strip.length == 0
54
+ resp.error = "Please enter an event name."
55
+ else
56
+ event.save
57
+ resp.redirect = "/admin/calendars/#{event.calendar_id}/events/#{event.id}"
58
+ end
59
+ render :json => resp
60
+ end
61
+
62
+ # DELETE /admin/calendars/:id
63
+ def admin_delete
64
+ return unless user_is_allowed('calendars', 'delete')
65
+ Calendar.find(params[:id]).destroy
66
+ resp = StdClass.new({ 'redirect' => "/admin/calendars" })
67
+ render :json => resp
68
+ end
69
+
70
+ # GET /admin/event-groups/period-options
71
+ def admin_period_options
72
+ render :json => [
73
+ { 'value' => CalendarEventGroup::PERIOD_DAY , 'text' => CalendarEventGroup::PERIOD_DAY },
74
+ { 'value' => CalendarEventGroup::PERIOD_WEEK , 'text' => CalendarEventGroup::PERIOD_WEEK },
75
+ { 'value' => CalendarEventGroup::PERIOD_MONTH , 'text' => CalendarEventGroup::PERIOD_MONTH },
76
+ { 'value' => CalendarEventGroup::PERIOD_YEAR , 'text' => CalendarEventGroup::PERIOD_YEAR },
77
+ ]
78
+ end
79
+
80
+ # GET /admin/event-groups/frequency-options
81
+ def admin_frequency_options
82
+ arr = (1..30).collect{ |i| { 'value' => i, 'text' => i }}
83
+ render :json => arr
84
+ end
85
+
86
+ # GET /admin/event-groups/repeat-by-options
87
+ def admin_repeat_by_options
88
+ render :json => [
89
+ { 'value' => CalendarEventGroup::REPEAT_BY_DAY_OF_MONTH , 'text' => 'same day of the month' },
90
+ { 'value' => CalendarEventGroup::REPEAT_BY_DAY_OF_WEEK , 'text' => 'same day of the week' },
91
+ ]
92
+ end
93
+
94
+ end
95
+ end
@@ -0,0 +1,99 @@
1
+
2
+ module Caboose
3
+ class EventsController < ApplicationController
4
+
5
+ helper :application
6
+
7
+ # GET /admin/calendars/:calendar_id/events/:id
8
+ def admin_edit
9
+ return unless user_is_allowed('calendars', 'edit')
10
+ @event = CalendarEvent.find(params[:id])
11
+ if @event.calendar_event_group_id.nil?
12
+ g = CalendarEventGroup.create
13
+ @event.calendar_event_group_id = g.id
14
+ @event.save
15
+ end
16
+ render :layout => 'caboose/modal'
17
+ end
18
+
19
+ # GET /admin/calendars/:calendar_id/events/new
20
+ def admin_new
21
+ return unless user_is_allowed('calendars', 'edit')
22
+ @calendar = Calendar.find(params[:calendar_id])
23
+ @begin_date = DateTime.iso8601(params[:begin_date])
24
+ render :layout => 'caboose/modal'
25
+ end
26
+
27
+ # POST /admin/calendars/:calendar_id/events
28
+ def admin_add
29
+ return unless user_is_allowed('calendars', 'edit')
30
+
31
+ resp = StdClass.new
32
+ event = CalendarEvent.new
33
+ event.calendar_id = params[:calendar_id]
34
+ event.name = params[:name]
35
+ event.begin_date = DateTime.iso8601("#{params[:begin_date]}T10:00:00-05:00")
36
+ event.end_date = DateTime.iso8601("#{params[:begin_date]}T10:00:00-05:00")
37
+ event.all_day = true
38
+
39
+ if event.name.nil? || event.name.strip.length == 0
40
+ resp.error = "Please enter an event name."
41
+ else
42
+ event.save
43
+ resp.redirect = "/admin/calendars/#{event.calendar_id}/events/#{event.id}"
44
+ end
45
+ render :json => resp
46
+ end
47
+
48
+ # PUT /admin/calendars/:calendar_id/events/:id
49
+ def admin_update
50
+ return unless user_is_allowed('calendars', 'edit')
51
+
52
+ resp = StdClass.new({ 'attributes' => {} })
53
+ event = CalendarEvent.find(params[:id])
54
+
55
+ save = true
56
+ params.each do |name, value|
57
+ case name
58
+ when 'name' then event.name = value
59
+ when 'location' then event.location = value
60
+ when 'description' then event.description = value
61
+ when 'all_day' then event.all_day = value
62
+ when 'begin_date'
63
+ t = event.begin_date ? event.begin_date.strftime('%H:%M %z') : '10:00 -0500'
64
+ event.begin_date = DateTime.strptime("#{value} #{t}", '%m/%d/%Y %H:%M %z')
65
+ when 'begin_time'
66
+ d = event.begin_date ? event.begin_date.strftime('%Y-%m-%d') : DateTime.now.strftime('%Y-%m-%d')
67
+ event.begin_date = DateTime.strptime("#{d} #{value}", '%Y-%m-%d %H:%M %z')
68
+ when 'end_date'
69
+ t = event.end_date ? event.end_date.strftime('%H:%M %z') : '10:00 -0500'
70
+ event.end_date = DateTime.strptime("#{value} #{t}", '%m/%d/%Y %H:%M %z')
71
+ when 'end_time'
72
+ d = event.end_date ? event.end_date.strftime('%Y-%m-%d') : DateTime.now.strftime('%Y-%m-%d')
73
+ event.end_date = DateTime.strptime("#{d} #{value}", '%Y-%m-%d %H:%M %z')
74
+ when 'repeats'
75
+ g = event.calendar_event_group
76
+ if value == true || value.to_i == 1
77
+ g.date_start = event.begin_date.to_date if g.date_start.nil?
78
+ g.date_end = event.end_date.to_date if g.date_end.nil?
79
+ g.save
80
+ end
81
+ event.repeats = value
82
+
83
+ end
84
+ end
85
+
86
+ resp.success = save && event.save
87
+ render :json => resp
88
+ end
89
+
90
+ # DELETE /admin/calendars/:calendar_id/events/:id
91
+ def admin_delete
92
+ return unless user_is_allowed('calendars', 'delete')
93
+ CalendarEvent.find(params[:id]).destroy
94
+ resp = StdClass.new({ 'redirect' => "/admin/calendars" })
95
+ render :json => resp
96
+ end
97
+
98
+ end
99
+ end
@@ -0,0 +1,7 @@
1
+ module Caboose
2
+ class Calendar < ActiveRecord::Base
3
+ self.table_name = "calendars"
4
+ has_many :calendar_events, :dependent => :destroy
5
+ attr_accessible :id, :site_id, :name, :description
6
+ end
7
+ end
@@ -0,0 +1,26 @@
1
+ module Caboose
2
+ class CalendarEvent < ActiveRecord::Base
3
+ self.table_name = "calendar_events"
4
+
5
+ belongs_to :calendar
6
+ belongs_to :calendar_event_group
7
+ attr_accessible :id ,
8
+ :calendar_id ,
9
+ :calendar_event_group_id ,
10
+ :name ,
11
+ :description ,
12
+ :location ,
13
+ :begin_date ,
14
+ :end_date ,
15
+ :all_day
16
+
17
+ def self.events_for_day(calendar_id, d)
18
+ q = ["calendar_id = ?
19
+ and concat(date_part('year', begin_date),date_part('month', begin_date),date_part('day', begin_date)) <= ?
20
+ and concat(date_part('year', end_date ),date_part('month', end_date ),date_part('day', end_date )) >= ?",
21
+ calendar_id, d.strftime('%Y%m%d'), d.strftime('%Y%m%d')]
22
+ self.where(q).reorder(:begin_date).all
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,30 @@
1
+ module Caboose
2
+ class CalendarEventGroup < ActiveRecord::Base
3
+ self.table_name = "calendar_event_groups"
4
+
5
+ has_many :calendar_events
6
+ attr_accessible :id ,
7
+ :period , # Daily, weekly, monthly, or yearly
8
+ :frequency ,
9
+ :repeat_by , # Used for monthly repeats
10
+ :sun ,
11
+ :mon ,
12
+ :tue ,
13
+ :wed ,
14
+ :thu ,
15
+ :fri ,
16
+ :sat ,
17
+ :date_start ,
18
+ :repeat_count , # How many times the repeat occurs
19
+ :date_end
20
+
21
+ PERIOD_DAY = 'Day'
22
+ PERIOD_WEEK = 'Week'
23
+ PERIOD_MONTH = 'Month'
24
+ PERIOD_YEAR = 'Year'
25
+
26
+ REPEAT_BY_DAY_OF_MONTH = 'Day of month'
27
+ REPEAT_BY_DAY_OF_WEEK = 'Day of week'
28
+
29
+ end
30
+ end
@@ -30,7 +30,19 @@ class Caboose::Schema < Caboose::Utilities::Schema
30
30
  Caboose::AbOption => [:text],
31
31
  Caboose::User => [:timezone],
32
32
  #Caboose::Field => [:child_block_id],
33
- Caboose::BlockType => [:layout_function]
33
+ Caboose::BlockType => [:layout_function],
34
+ Caboose::CalendarEventGroup => [
35
+ :repeat_period ,
36
+ :repeat_sun ,
37
+ :repeat_mon ,
38
+ :repeat_tue ,
39
+ :repeat_wed ,
40
+ :repeat_thu ,
41
+ :repeat_fri ,
42
+ :repeat_sat ,
43
+ :repeat_start ,
44
+ :repeat_end
45
+ ]
34
46
  }
35
47
  end
36
48
 
@@ -119,6 +131,37 @@ class Caboose::Schema < Caboose::Utilities::Schema
119
131
  [ :name , :string ],
120
132
  [ :description , :string ]
121
133
  ],
134
+ Caboose::Calendar => [
135
+ [ :site_id , :integer ],
136
+ [ :name , :string ],
137
+ [ :description , :text ]
138
+ ],
139
+ Caboose::CalendarEvent => [
140
+ [ :calendar_id , :integer ],
141
+ [ :calendar_event_group_id , :integer ],
142
+ [ :name , :string ],
143
+ [ :description , :text ],
144
+ [ :location , :string ],
145
+ [ :begin_date , :datetime ],
146
+ [ :end_date , :datetime ],
147
+ [ :all_day , :boolean , { :default => false }],
148
+ [ :repeats , :boolean , { :default => false }]
149
+ ],
150
+ Caboose::CalendarEventGroup => [
151
+ [ :frequency , :integer , { :default => 1 }],
152
+ [ :period , :string , { :default => 'Week' }],
153
+ [ :repeat_by , :string ],
154
+ [ :sun , :boolean , { :default => false }],
155
+ [ :mon , :boolean , { :default => false }],
156
+ [ :tue , :boolean , { :default => false }],
157
+ [ :wed , :boolean , { :default => false }],
158
+ [ :thu , :boolean , { :default => false }],
159
+ [ :fri , :boolean , { :default => false }],
160
+ [ :sat , :boolean , { :default => false }],
161
+ [ :date_start , :date ],
162
+ [ :repeat_count , :integer ],
163
+ [ :date_end , :date ]
164
+ ],
122
165
  Caboose::DatabaseSession => [
123
166
  [ :session_id , :string , :null => false ],
124
167
  [ :data , :text ],
@@ -0,0 +1,123 @@
1
+
2
+ <h1>Edit Calendar</h1>
3
+ <p><div id='calendar_<%= @calendar.id %>_name' ></div></p>
4
+ <p><div id='calendar_<%= @calendar.id %>_description' ></div></p>
5
+
6
+ <div id='calendar'>
7
+ <h2><%= @d.strftime('%B %Y') %></h2>
8
+ <p>
9
+ <input type='button' value='< Previous Month' onclick="window.location='/admin/calendars/<%= @calendar.id %>?d=<%= (@d - 1.month).strftime('%Y-%m-%d') %>';" />
10
+ <input type='button' value='Current Month' onclick="window.location='/admin/calendars/<%= @calendar.id %>?d=<%= DateTime.now.strftime('%Y-%m-%d') %>';" />
11
+ <input type='button' value='Next Month >' onclick="window.location='/admin/calendars/<%= @calendar.id %>?d=<%= (@d + 1.month).strftime('%Y-%m-%d') %>';" />
12
+ </p>
13
+ <p><a href='/admin/calendars/<%= @calendar.id %>/events/new' id='new_event'>New Event</a></p>
14
+ <table>
15
+ <tr>
16
+ <th>Sun</th>
17
+ <th>Mon</th>
18
+ <th>Tue</th>
19
+ <th>Wed</th>
20
+ <th>Thu</th>
21
+ <th>Fri</th>
22
+ <th>Sat</th>
23
+ </tr>
24
+ <%
25
+ days_in_previous_month = (@d - (@d - 1.month)).to_f.ceil.to_i
26
+ days_in_month = ((@d + 1.month) - @d).to_f.ceil.to_i
27
+ start_day = @d.strftime('%w').to_i
28
+ %>
29
+ <tr>
30
+ <% (0...start_day).each do |i| %><td class='blank'><span class='day'><%= days_in_previous_month - (start_day - i) %></span></td><% end %>
31
+ <% day = 1 %>
32
+ <% while day <= days_in_month %>
33
+ <% d = @d + (day-1).days %>
34
+ <% if (day + start_day-1) % 7 == 0 %></tr><% if day < days_in_month %><tr><% end %><% end %>
35
+ <td id='day_<%= d.strftime('%Y-%m-%d') %>'>
36
+ <span class='day'><%= day %></span>
37
+ <% events = Caboose::CalendarEvent.events_for_day(@calendar.id, d) %>
38
+ <% if events.count > 0 %>
39
+ <ul>
40
+ <% events.each do |ev| %><li><%= ev.name %></li><% end %>
41
+ </ul>
42
+ <% end %>
43
+ </td>
44
+ <% day = day + 1 %>
45
+ <% end %>
46
+ <% last_day = (start_day + days_in_month) % 7 %>
47
+ <% remaining_days = 7 - last_day %>
48
+ <% if last_day > 0 %><% (0...remaining_days).each do |i| %><td class='blank'><span class='day'><%= (i + 1) %></span></td><% end %><% end %>
49
+ <% if (start_day + days_in_month) != 0 %></tr><% end %>
50
+ </table><br />
51
+ </div>
52
+
53
+ <div id='message'></div>
54
+ <div id='controls'>
55
+ <input type='button' value='Back' onclick="window.location='/admin/calendars';" />
56
+ <input type='button' value='Delete Calendar' onclick="delete_calendar(<%= @calendar.id %>);" />
57
+ </div>
58
+
59
+ <% content_for :caboose_js do %>
60
+ <%= javascript_include_tag "caboose/model/all" %>
61
+ <script type="text/javascript">
62
+
63
+ $(document).ready(function() {
64
+ new ModelBinder({
65
+ name: 'Calendar',
66
+ id: <%= @calendar.id %>,
67
+ update_url: '/admin/calendars/<%= @calendar.id %>',
68
+ authenticity_token: '<%= form_authenticity_token %>',
69
+ attributes: [
70
+ { name: 'name' , nice_name: 'Name' , type: 'text' , value: <%= raw Caboose.json(@calendar.name ) %>, width: 400 },
71
+ { name: 'description' , nice_name: 'Description' , type: 'textarea', value: <%= raw Caboose.json(@calendar.description ) %>, width: 400, height: 100 }
72
+ ]
73
+ });
74
+ $('#calendar td')
75
+ .mouseover(function(e) { $(this).addClass('over'); })
76
+ .mouseout(function(e) { $(this).removeClass('over'); })
77
+ .click(function(e) {
78
+ e.preventDefault();
79
+ if (!$(this).hasClass('blank'))
80
+ {
81
+ var d = $(this).attr('id').replace('day_', '');
82
+ caboose_modal_url('/admin/calendars/<%= @calendar.id %>/events/new?begin_date=' + d);
83
+ }
84
+ });
85
+ });
86
+
87
+ function delete_calendar(calendar_id, confirm)
88
+ {
89
+ if (!confirm)
90
+ {
91
+ var p = $('<p/>').addClass('note confirm')
92
+ .append('Are you sure you want to delete the calendar? ')
93
+ .append($('<input/>').attr('type','button').val('Yes').click(function() { delete_calendar(calendar_id, true); })).append(' ')
94
+ .append($('<input/>').attr('type','button').val('No').click(function() { $('#message').empty(); }));
95
+ $('#message').empty().append(p);
96
+ return;
97
+ }
98
+ $('#message').html("<p class='loading'>Deleting calendar...</p>");
99
+ $.ajax({
100
+ url: '/admin/calendars/' + calendar_id,
101
+ type: 'delete',
102
+ success: function(resp) {
103
+ if (resp.error) $('#message').html("<p class='note error'>" + resp.error + "</p>");
104
+ if (resp.redirect) window.location = resp.redirect;
105
+ }
106
+ });
107
+ }
108
+
109
+ </script>
110
+ <% end %>
111
+
112
+ <% content_for :caboose_css do %>
113
+ <style type='text/css'>
114
+
115
+ #calendar table { border-collapse: collapse; width: 95%; }
116
+ #calendar th { border: #666 1px solid; background: #666; color: #fff; margin: 0; padding: 4px 8px; }
117
+ #calendar td { border: #666 1px solid; position: relative; margin: 0; padding: 0; height: 100px; vertical-align: top; }
118
+ #calendar td.blank { background: #efefef; border: #666 1px solid; }
119
+ #calendar td.over { background: #ffcc99; }
120
+ #calendar td span.day { display: block; float: left; border-right: #666 1px solid; border-bottom: #666 1px solid; width: 20px; text-align: center; }
121
+
122
+ </style>
123
+ <% end %>
@@ -0,0 +1,63 @@
1
+ <h1>Calendars</h1>
2
+
3
+ <form action='/admin/calendars' method='get' class='search_form'>
4
+ <input type='text' name='name_like' placeholder='Name' />
5
+ <input type='submit' value='Search' />
6
+ </form>
7
+
8
+ <p><a href='/admin/calendars/new' id='new_calendar'>New Calendar</a></p>
9
+
10
+ <form action='/admin/calendars' method='post' id='new_calendar_form'>
11
+ <input type='hidden' name='authenticity_token' value='<%= form_authenticity_token %>'>
12
+ <p><input type='text' name='name' id='name' placeholder='Name' style='width: 500px;'></p>
13
+ <div id='new_message'></div>
14
+ <p>
15
+ <input type='button' value='Cancel' onclick="$('#new_calendar_form').slideToggle();">
16
+ <input type='submit' value='Add New Calendar' onclick="add_calendar(); return false;">
17
+ </p>
18
+ </form>
19
+
20
+ <% if @calendars.count > 0 %>
21
+ <table class='data'>
22
+ <tr>
23
+ <th>Name</th>
24
+ <th>Description</th>
25
+ </tr>
26
+ <% @calendars.each do |calendar| %>
27
+ <tr onclick="window.location='/admin/calendars/<%= calendar.id %>';">
28
+ <td><%= calendar.name %></td>
29
+ <td><%= calendar.description %></td>
30
+ </tr>
31
+ <% end %>
32
+ </table>
33
+ <% else %>
34
+ <p>There are no calendars right now.</p>
35
+ <% end %>
36
+
37
+ <% content_for :caboose_js do %>
38
+ <script type='text/javascript'>
39
+
40
+ $(document).ready(function() {
41
+ $('#new_calendar_form').hide();
42
+ $('#new_calendar').click(function(e) {
43
+ e.preventDefault();
44
+ $('#new_calendar_form').slideToggle();
45
+ });
46
+ });
47
+
48
+ function add_calendar()
49
+ {
50
+ $('#new_message').html("<p class='loading'>Adding calendar...</p>");
51
+ $.ajax({
52
+ url: '/admin/calendars',
53
+ type: 'post',
54
+ data: $('#new_calendar_form').serialize(),
55
+ success: function(resp) {
56
+ if (resp.error) $('#new_message').html("<p class='note error'>" + resp.error + "</p>");
57
+ if (resp.redirect) window.location = resp.redirect;
58
+ }
59
+ });
60
+ }
61
+
62
+ </script>
63
+ <% end %>
@@ -0,0 +1,195 @@
1
+ <%
2
+ e = @event
3
+ c = @event.calendar
4
+ g = @event.calendar_event_group
5
+ %>
6
+ <h1>Edit Event</h1>
7
+ <p><div id='calendarevent_<%= e.id %>_name' ></div></p>
8
+ <p><div id='calendarevent_<%= e.id %>_location' ></div></p>
9
+ <p><div id='calendarevent_<%= @event.id %>_description' ></div></p>
10
+ <div id='datetime_container' class='<%= @event.all_day ? 'all_day' : 'non_all_day' %>'>
11
+ <div id='calendarevent_<%= e.id %>_begin_date' ></div>
12
+ <div id='calendarevent_<%= e.id %>_begin_time' ></div>
13
+ <div id='calendarevent_<%= e.id %>_end_date' ></div>
14
+ <div id='calendarevent_<%= e.id %>_end_time' ></div>
15
+ <div class='spacer'></div>
16
+ </div>
17
+ <p id='all_day_repeats_container'>
18
+ <div id='calendarevent_<%= e.id %>_all_day' ></div>
19
+ <div id='calendarevent_<%= e.id %>_repeats' ></div>
20
+ </p>
21
+ <div id='repeat_container' <% if !e.repeats %>style='display: none;'<% end %>>
22
+ <p id='repeat_every'>Repeat every:</p>
23
+ <div id='calendareventgroup_<%= g.id %>_frequency' ></div>
24
+ <div id='calendareventgroup_<%= g.id %>_period' ></div>
25
+ <div id='repeat_by_container' <% if g.repeat_by != Caboose::CalendarEventGroup::PERIOD_MONTH %>style='display: none;'<% end %>>
26
+ <div id='calendareventgroup_<%= g.id %>_repeat_by' ></div>
27
+ </div>
28
+ <div id='week_container' <% if g.repeat_by != Caboose::CalendarEventGroup::PERIOD_WEEK %>style='display: none;'<% end %>>
29
+ <table class='data'>
30
+ <tr><th>S</th><th>M</th><th>T</th><th>W</th><th>R</th><th>F</th><th>S</th></tr>
31
+ <tr>
32
+ <td><div id='calendareventgroup_<%= g.id %>_sun'></div></td>
33
+ <td><div id='calendareventgroup_<%= g.id %>_mon'></div></td>
34
+ <td><div id='calendareventgroup_<%= g.id %>_tue'></div></td>
35
+ <td><div id='calendareventgroup_<%= g.id %>_wed'></div></td>
36
+ <td><div id='calendareventgroup_<%= g.id %>_thu'></div></td>
37
+ <td><div id='calendareventgroup_<%= g.id %>_fri'></div></td>
38
+ <td><div id='calendareventgroup_<%= g.id %>_sat'></div></td>
39
+ </tr>
40
+ </table>
41
+ </div>
42
+ <p id='repeat_dates_container'>
43
+ <div id='calendareventgroup_<%= g.id %>_date_start' ></div>
44
+ <div id='calendareventgroup_<%= g.id %>_date_end' ></div>
45
+ </p>
46
+ </div>
47
+
48
+
49
+ <div id='message'></div>
50
+ <p>
51
+ <input type='button' value='Close' onclick="modal.close();" />
52
+ <input type='button' value='Delete Event' onclick="delete_event(<%= @event.id %>);" />
53
+ </p>
54
+
55
+ <% content_for :caboose_js do %>
56
+ <%= javascript_include_tag "caboose/model/all" %>
57
+ <%= javascript_include_tag "jquery-ui/datepicker" %>
58
+ <script type="text/javascript">
59
+
60
+ var modal = false;
61
+ $(window).ready(function() {
62
+ modal = new CabooseModal(600);
63
+ });
64
+
65
+ $(document).ready(function() {
66
+ new ModelBinder({
67
+ name: 'CalendarEvent',
68
+ id: <%= @event.id %>,
69
+ update_url: '/admin/calendars/<%= c.id %>/events/<%= e.id %>',
70
+ authenticity_token: '<%= form_authenticity_token %>',
71
+ attributes: [
72
+ { name: 'name' , nice_name: 'Name' , type: 'text' , value: <%= raw Caboose.json(e.name ) %>, width: 430 },
73
+ { name: 'description' , nice_name: 'Description' , type: 'textarea' , value: <%= raw Caboose.json(e.description ) %>, width: 430, height: 100 },
74
+ { name: 'location' , nice_name: 'Location' , type: 'text' , value: <%= raw Caboose.json(e.location ) %>, width: 430 },
75
+ { name: 'begin_date' , nice_name: 'Begin date' , type: 'date' , value: <%= raw Caboose.json(e.begin_date.strftime('%m/%d/%Y') ) %>, width: 200 },
76
+ { name: 'begin_time' , nice_name: 'Begin time' , type: 'time' , value: <%= raw Caboose.json(e.begin_date.strftime('%I:%M %P') ) %>, width: 200 , fixed_placeholder: false },
77
+ { name: 'end_date' , nice_name: 'End date' , type: 'date' , value: <%= raw Caboose.json(e.end_date.strftime('%m/%d/%Y') ) %>, width: 200 },
78
+ { name: 'end_time' , nice_name: 'End time' , type: 'time' , value: <%= raw Caboose.json(e.end_date.strftime('%I:%M %P') ) %>, width: 200 , fixed_placeholder: false },
79
+ { name: 'all_day' , nice_name: 'All day' , type: 'checkbox' , value: <%= raw e.all_day ? 1 : 0 %>, width: 100, after_update: after_all_day_update },
80
+ { name: 'repeats' , nice_name: 'Repeats' , type: 'checkbox' , value: <%= raw e.repeats ? 1 : 0 %>, width: 100, after_update: after_repeats_update }
81
+ ],
82
+ on_load: function() { modal.autosize(); }
83
+ });
84
+ new ModelBinder({
85
+ name: 'CalendarEventGroup',
86
+ id: <%= g.id %>,
87
+ update_url: '/admin/calendars/<%= c.id %>/event-groups/<%= g.id %>',
88
+ authenticity_token: '<%= form_authenticity_token %>',
89
+ attributes: [
90
+ { name: 'frequency' , nice_name: 'Repeat every' , type: 'select' , value: <%= raw Caboose.json(g.frequency ) %>, width: 40 , fixed_placeholder: false, options_url: '/admin/event-groups/frequency-options' },
91
+ { name: 'period' , nice_name: 'Repeats' , type: 'select' , value: <%= raw Caboose.json(g.period ) %>, width: 80 , fixed_placeholder: false, options_url: '/admin/event-groups/period-options', after_update: after_period_update },
92
+ { name: 'repeat_by' , nice_name: 'Repeat by' , type: 'select' , value: <%= raw Caboose.json(g.repeat_by ) %>, width: 140 , fixed_placeholder: false, options_url: '/admin/event-groups/repeat-by-options' },
93
+ { name: 'date_start' , nice_name: 'Start' , type: 'date' , value: <%= raw Caboose.json(g.date_start ? g.date_start.strftime('%m/%d/%Y') : '') %>, width: 200, align: 'right' },
94
+ { name: 'date_end' , nice_name: 'End' , type: 'date' , value: <%= raw Caboose.json(g.date_end ? g.date_end.strftime( '%m/%d/%Y') : '') %>, width: 200, align: 'right' },
95
+ { name: 'sun' , nice_name: 'sun' , type: 'checkbox' , value: <%= raw g.sun ? 1 : 0 %>, width: 21 , fixed_placeholder: false },
96
+ { name: 'mon' , nice_name: 'mon' , type: 'checkbox' , value: <%= raw g.mon ? 1 : 0 %>, width: 21 , fixed_placeholder: false },
97
+ { name: 'tue' , nice_name: 'tue' , type: 'checkbox' , value: <%= raw g.tue ? 1 : 0 %>, width: 21 , fixed_placeholder: false },
98
+ { name: 'wed' , nice_name: 'wed' , type: 'checkbox' , value: <%= raw g.wed ? 1 : 0 %>, width: 21 , fixed_placeholder: false },
99
+ { name: 'thu' , nice_name: 'thu' , type: 'checkbox' , value: <%= raw g.thu ? 1 : 0 %>, width: 21 , fixed_placeholder: false },
100
+ { name: 'fri' , nice_name: 'fri' , type: 'checkbox' , value: <%= raw g.fri ? 1 : 0 %>, width: 21 , fixed_placeholder: false },
101
+ { name: 'sat' , nice_name: 'sat' , type: 'checkbox' , value: <%= raw g.sat ? 1 : 0 %>, width: 21 , fixed_placeholder: false }
102
+ ],
103
+ on_load: function() { modal.autosize(); }
104
+ });
105
+ });
106
+
107
+ function delete_event(event_id, confirm)
108
+ {
109
+ if (!confirm)
110
+ {
111
+ var p = $('<p/>').addClass('note confirm')
112
+ .append('Are you sure you want to delete the event? ')
113
+ .append($('<input/>').attr('type','button').val('Yes').click(function() { delete_event(event_id, true); })).append(' ')
114
+ .append($('<input/>').attr('type','button').val('No').click(function() { $('#message').empty(); }));
115
+ modal.autosize(p);
116
+ return;
117
+ }
118
+ modal.autosize("<p class='loading'>Deleting event...</p>");
119
+ $.ajax({
120
+ url: '/admin/calendars/<%= c.id %>/events/' + event_id,
121
+ type: 'delete',
122
+ success: function(resp) {
123
+ if (resp.error) modal.autosize("<p class='note error'>" + resp.error + "</p>");
124
+ if (resp.redirect) window.location = resp.redirect;
125
+ }
126
+ });
127
+ }
128
+
129
+ function after_all_day_update()
130
+ {
131
+ var el = $('#datetime_container');
132
+ if (el.hasClass('all_day'))
133
+ el.removeClass('all_day').addClass('non_all_day');
134
+ else
135
+ el.removeClass('non_all_day').addClass('all_day');
136
+ modal.autosize();
137
+ }
138
+
139
+ function after_repeats_update()
140
+ {
141
+ var el = $('#repeat_container');
142
+ el.slideToggle(function() { modal.autosize(); });
143
+ }
144
+
145
+ function after_period_update()
146
+ {
147
+ var period = $('#calendareventgroup_<%= g.id %>_period').val();
148
+ if (period == 'Week')
149
+ $('#week_container').show();
150
+ else
151
+ $('#week_container').hide();
152
+ if (period == 'Month')
153
+ $('#repeat_by_container').show();
154
+ else
155
+ $('#repeat_by_container').hide();
156
+ modal.autosize();
157
+ }
158
+
159
+ </script>
160
+ <% end %>
161
+
162
+ <% content_for :caboose_css do %>
163
+ <%= stylesheet_link_tag "jquery-ui/datepicker" %>
164
+ <style type='text/css'>
165
+
166
+ #datetime_container { }
167
+
168
+ #calendarevent_<%= e.id %>_all_day_container { float: left; width: 130px; }
169
+ #calendarevent_<%= e.id %>_repeats_container { width: 200px; }
170
+
171
+ div.all_day #calendarevent_<%= e.id %>_begin_date_container { float: left; width: 230px; }
172
+ div.all_day #calendarevent_<%= e.id %>_begin_time_container { display: none; }
173
+ div.all_day #calendarevent_<%= e.id %>_end_date_container { width: 230px; }
174
+ div.all_day #calendarevent_<%= e.id %>_end_time_container { display: none; }
175
+ div.all_day .spacer { clear: left; }
176
+
177
+ div.non_all_day #calendarevent_<%= e.id %>_begin_date_container { float: left; width: 230px; margin-bottom: 10px; }
178
+ div.non_all_day #calendarevent_<%= e.id %>_begin_time_container { width: 230px; margin-bottom: 10px; }
179
+ div.non_all_day #calendarevent_<%= e.id %>_end_date_container { float: left; width: 230px; clear: left; }
180
+ div.non_all_day #calendarevent_<%= e.id %>_end_time_container { float: left; width: 230px; }
181
+ div.non_all_day .spacer { clear: left; }
182
+
183
+ #repeat_every { float: left; width: 110px; }
184
+ #calendareventgroup_<%= g.id %>_frequency_container { float: left; width: 70px; }
185
+ #calendareventgroup_<%= g.id %>_period_container { float: left; width: 110px; }
186
+ #calendareventgroup_<%= g.id %>_repeat_by { float: left; width: 100px; }
187
+
188
+ #repeat_dates_container { clear: left; }
189
+ #calendareventgroup_<%= g.id %>_date_start_container { float: left; width: 230px; }
190
+ #calendareventgroup_<%= g.id %>_date_end_container { width: 230px; }
191
+
192
+ #week_container { clear: left; padding-top: 10px; }
193
+
194
+ </style>
195
+ <% end %>
@@ -0,0 +1,67 @@
1
+ <h1>Calendars</h1>
2
+
3
+ <form action='/admin/calendars' method='get' class='search_form'>
4
+ <input type='text' name='name_like' placeholder='Name' />
5
+ <input type='submit' value='Search' />
6
+ </form>
7
+
8
+ <p><a href='/admin/calendars/new' id='new_calendar'>New Calendar</a></p>
9
+
10
+ <form action='/admin/calendars' method='post' id='new_calendar_form'>
11
+ <input type='hidden' name='authenticity_token' value='<%= form_authenticity_token %>'>
12
+ <p><input type='text' name='name' id='name' placeholder='Name' style='width: 500px;'></p>
13
+ <div id='new_message'></div>
14
+ <p>
15
+ <input type='button' value='Cancel' onclick="$('#new_calendar_form').slideToggle();">
16
+ <input type='submit' value='Add New Calendar' onclick="add_calendar(); return false;">
17
+ </p>
18
+ </form>
19
+
20
+ <% if @calendars.count > 0 %>
21
+ <table class='data'>
22
+ <tr>
23
+ <%= raw @gen.sortable_table_headings({
24
+ 'name' => 'Name',
25
+ 'description' => 'Description'
26
+ })
27
+ %>
28
+ </tr>
29
+ <% @calendars.each do |calendar| %>
30
+ <tr onclick="window.location='/admin/calendars/<%= calendar.id %>';">
31
+ <td><%= calendar.name %></td>
32
+ <td><%= calendar.description %></td>
33
+ </tr>
34
+ <% end %>
35
+ </table>
36
+ <p><%= raw @gen.generate %></p>
37
+ <% else %>
38
+ <p>There are no calendars right now.</p>
39
+ <% end %>
40
+
41
+ <% content_for :caboose_js do %>
42
+ <script type='text/javascript'>
43
+
44
+ $(document).ready(function() {
45
+ $('#new_calendar_form').hide();
46
+ $('#new_calendar').click(function(e) {
47
+ e.preventDefault();
48
+ $('#new_calendar_form').slideToggle();
49
+ });
50
+ });
51
+
52
+ function add_calendar()
53
+ {
54
+ $('#new_message').html("<p class='loading'>Adding calendar...</p>");
55
+ $.ajax({
56
+ url: '/admin/calendars',
57
+ type: 'post',
58
+ data: $('#new_calendar_form').serialize(),
59
+ success: function(resp) {
60
+ if (resp.error) $('#new_message').html("<p class='note error'>" + resp.error + "</p>");
61
+ if (resp.redirect) window.location = resp.redirect;
62
+ }
63
+ });
64
+ }
65
+
66
+ </script>
67
+ <% end %>
@@ -0,0 +1,38 @@
1
+
2
+ <h1>New Event</h1>
3
+
4
+ <form action='/admin/calendars/<%= @calendar.id %>' method='post' id='new_event_form'>
5
+ <input type='hidden' name='authenticity_token' value='<%= form_authenticity_token %>'>
6
+ <input type='hidden' name='begin_date' value='<%= @begin_date.strftime('%Y-%m-%d') %>'>
7
+ <p><input type='text' name='name' id='name' placeholder='Name' style='width: 500px;'></p>
8
+ <div id='message'></div>
9
+ <p>
10
+ <input type='button' value='Cancel' onclick="modal.close();" />
11
+ <input type='submit' value='Add New Event' onclick="add_event(); return false;" />
12
+ </p>
13
+ </form>
14
+
15
+ <% content_for :caboose_js do %>
16
+ <%= javascript_include_tag "caboose/model/all" %>
17
+ <script type="text/javascript">
18
+
19
+ $(window).load(function() {
20
+ modal = new CabooseModal(600);
21
+ });
22
+
23
+ function add_event()
24
+ {
25
+ $('#message').html("<p class='loading'>Adding event...</p>");
26
+ $.ajax({
27
+ url: '/admin/calendars/<%= @calendar.id %>/events',
28
+ type: 'post',
29
+ data: $('#new_event_form').serialize(),
30
+ success: function(resp) {
31
+ if (resp.error) $('#message').html("<p class='note error'>" + resp.error + "</p>");
32
+ if (resp.redirect) window.location = resp.redirect;
33
+ }
34
+ });
35
+ }
36
+
37
+ </script>
38
+ <% end %>
@@ -39,7 +39,14 @@
39
39
  :logged_in_user => @logged_in_user,
40
40
  :site => @site
41
41
  })
42
- str.gsub!('|CABOOSE_CSS|' , yield(:css))
42
+
43
+ protocol = request.protocol
44
+ css = yield(:css)
45
+ css.gsub!("<link href=\"//", "<link href=\"#{protocol}")
46
+ css.gsub!("<link href='//" , "<link href='#{protocol}")
47
+
48
+ str.gsub!('|CABOOSE_CSS|' , css)
49
+ str.gsub!('|CABOOSE_PROTOCOL|' , protocol)
43
50
  str.gsub!('|CABOOSE_JAVASCRIPT|' , yield(:js))
44
51
  str.gsub!('|CABOOSE_CSRF|' , csrf_meta_tags)
45
52
 
data/config/routes.rb CHANGED
@@ -208,6 +208,23 @@ Caboose::Engine.routes.draw do
208
208
  get "admin/posts" => "posts#admin_index"
209
209
  post "admin/posts" => "posts#admin_add"
210
210
  delete "admin/posts/:id" => "posts#admin_delete"
211
+
212
+ get "admin/calendars" => "calendars#admin_index"
213
+ get "admin/calendars/:id" => "calendars#admin_edit"
214
+ put "admin/calendars/:id" => "calendars#admin_update"
215
+ post "admin/calendars" => "calendars#admin_add"
216
+ delete "admin/calendars" => "calendars#admin_delete"
217
+
218
+ get "admin/calendars/:calendar_id/events" => "events#admin_index"
219
+ get "admin/calendars/:calendar_id/events/new" => "events#admin_new"
220
+ get "admin/calendars/:calendar_id/events/:id" => "events#admin_edit"
221
+ put "admin/calendars/:calendar_id/events/:id" => "events#admin_update"
222
+ post "admin/calendars/:calendar_id/events" => "events#admin_add"
223
+ delete "admin/calendars/:calendar_id/events" => "events#admin_delete"
224
+
225
+ get "admin/event-groups/period-options" => "event_groups#admin_period_options"
226
+ get "admin/event-groups/frequency-options" => "event_groups#admin_frequency_options"
227
+ get "admin/event-groups/repeat-by-options" => "event_groups#admin_repeat_by_options"
211
228
 
212
229
  get "admin/ab-variants" => "ab_variants#admin_index"
213
230
  get "admin/ab-variants/new" => "ab_variants#admin_new"
@@ -1,3 +1,3 @@
1
1
  module Caboose
2
- VERSION = '0.4.109'
2
+ VERSION = '0.4.110'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: caboose-cms
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.109
4
+ version: 0.4.110
5
5
  platform: ruby
6
6
  authors:
7
7
  - William Barry
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-08 00:00:00.000000000 Z
11
+ date: 2014-09-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -268,6 +268,9 @@ files:
268
268
  - app/controllers/caboose/block_type_store_controller.rb
269
269
  - app/controllers/caboose/block_types_controller.rb
270
270
  - app/controllers/caboose/blocks_controller.rb
271
+ - app/controllers/caboose/calendars_controller.rb
272
+ - app/controllers/caboose/event_groups_controller.rb
273
+ - app/controllers/caboose/events_controller.rb
271
274
  - app/controllers/caboose/images_controller.rb
272
275
  - app/controllers/caboose/login_controller.rb
273
276
  - app/controllers/caboose/logout_controller.rb
@@ -303,6 +306,9 @@ files:
303
306
  - app/models/caboose/block_type_source.rb
304
307
  - app/models/caboose/block_type_summary.rb
305
308
  - app/models/caboose/caboose_plugin.rb
309
+ - app/models/caboose/calendar.rb
310
+ - app/models/caboose/calendar_event.rb
311
+ - app/models/caboose/calendar_event_group.rb
306
312
  - app/models/caboose/core_plugin.rb
307
313
  - app/models/caboose/crumbtrail.rb
308
314
  - app/models/caboose/database_session.rb
@@ -388,6 +394,11 @@ files:
388
394
  - app/views/caboose/blocks/admin_edit_richtext.html.erb
389
395
  - app/views/caboose/blocks/admin_new.html.erb
390
396
  - app/views/caboose/blocks/admin_render_second_level.json.erb
397
+ - app/views/caboose/calendars/admin_edit.html.erb
398
+ - app/views/caboose/calendars/admin_index.html.erb
399
+ - app/views/caboose/events/admin_edit.html.erb
400
+ - app/views/caboose/events/admin_index.html.erb
401
+ - app/views/caboose/events/admin_new.html.erb
391
402
  - app/views/caboose/extras/error.html.erb
392
403
  - app/views/caboose/extras/error404.html.erb
393
404
  - app/views/caboose/extras/error_invalid_site.html.erb