effective_pages 0.8.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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +30 -0
- data/Rakefile +24 -0
- data/app/assets/javascripts/effective_pages.js +0 -0
- data/app/assets/stylesheets/effective_pages/dropdown-submenu.css +7 -0
- data/app/assets/stylesheets/effective_pages.css.scss +1 -0
- data/app/controllers/admin/menus_controller.rb +79 -0
- data/app/controllers/admin/pages_controller.rb +95 -0
- data/app/controllers/effective/pages_controller.rb +24 -0
- data/app/helpers/effective_menus_helper.rb +151 -0
- data/app/helpers/effective_pages_helper.rb +14 -0
- data/app/models/effective/access_denied.rb +17 -0
- data/app/models/effective/datatables/menus.rb +16 -0
- data/app/models/effective/datatables/pages.rb +19 -0
- data/app/models/effective/menu.rb +152 -0
- data/app/models/effective/menu_item.rb +68 -0
- data/app/models/effective/page.rb +32 -0
- data/app/views/admin/menu_items/_actions.html.haml +4 -0
- data/app/views/admin/menu_items/_expand.html.haml +2 -0
- data/app/views/admin/menu_items/_item.html.haml +13 -0
- data/app/views/admin/menu_items/_new.html.haml +3 -0
- data/app/views/admin/menus/_actions.html.haml +4 -0
- data/app/views/admin/menus/_form.html.haml +9 -0
- data/app/views/admin/menus/edit.html.haml +2 -0
- data/app/views/admin/menus/index.html.haml +9 -0
- data/app/views/admin/menus/new.html.haml +2 -0
- data/app/views/admin/pages/_actions.html.haml +14 -0
- data/app/views/admin/pages/_form.html.haml +31 -0
- data/app/views/admin/pages/edit.html.haml +2 -0
- data/app/views/admin/pages/index.html.haml +9 -0
- data/app/views/admin/pages/new.html.haml +2 -0
- data/config/routes.rb +28 -0
- data/db/migrate/01_create_effective_pages.rb.erb +52 -0
- data/lib/effective_pages/engine.rb +19 -0
- data/lib/effective_pages/version.rb +3 -0
- data/lib/effective_pages.rb +73 -0
- data/lib/generators/effective_pages/install_generator.rb +43 -0
- data/lib/generators/templates/README +1 -0
- data/lib/generators/templates/effective_pages.rb +49 -0
- data/lib/generators/templates/example.html.haml +6 -0
- data/lib/tasks/effective_pages_tasks.rake +29 -0
- data/spec/controllers/effective/pages_controller_spec.rb +66 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/views/effective/pages/example.html.haml +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config/application.rb +66 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +67 -0
- data/spec/dummy/config/environments/test.rb +37 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +60 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/migrate/01_create_effective_pages.rb +51 -0
- data/spec/dummy/db/schema.rb +52 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/dummy/spec_link +3 -0
- data/spec/effective_pages_spec.rb +7 -0
- data/spec/helpers/effective_menus_helper_spec.rb +281 -0
- data/spec/models/effective/menu_spec.rb +133 -0
- data/spec/spec_helper.rb +43 -0
- data/spec/support/factories.rb +15 -0
- metadata +261 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
module EffectivePagesHelper
|
2
|
+
def effective_page_header_tags(options = {})
|
3
|
+
@page ||= nil
|
4
|
+
|
5
|
+
[ content_tag(:title, (@page_title || options[:title] || @page.try(:title))),
|
6
|
+
"<meta content='#{options[:meta_description] || @page.try(:meta_description)}' name='description' />",
|
7
|
+
].join("\n").html_safe
|
8
|
+
end
|
9
|
+
|
10
|
+
def application_root_to_effective_pages_slug
|
11
|
+
Rails.application.routes.routes.find { |r| r.name == 'root' and r.defaults[:controller] == 'Effective::Pages' and r.defaults[:action] == 'show' }.defaults[:id] rescue nil
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
unless defined?(Effective::AccessDenied)
|
2
|
+
module Effective
|
3
|
+
class AccessDenied < StandardError
|
4
|
+
attr_reader :action, :subject
|
5
|
+
|
6
|
+
def initialize(message = nil, action = nil, subject = nil)
|
7
|
+
@message = message
|
8
|
+
@action = action
|
9
|
+
@subject = subject
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
@message || I18n.t(:'unauthorized.default', :default => 'Access Denied')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
if defined?(EffectiveDatatables)
|
2
|
+
module Effective
|
3
|
+
module Datatables
|
4
|
+
class Menus < Effective::Datatable
|
5
|
+
table_column :id
|
6
|
+
|
7
|
+
table_column :title
|
8
|
+
table_column :actions, :sortable => false, :filter => false, :partial => '/admin/menus/actions'
|
9
|
+
|
10
|
+
def collection
|
11
|
+
Effective::Menu.all
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
if defined?(EffectiveDatatables)
|
2
|
+
module Effective
|
3
|
+
module Datatables
|
4
|
+
class Pages < Effective::Datatable
|
5
|
+
table_column :id
|
6
|
+
|
7
|
+
table_column :title
|
8
|
+
table_column :slug
|
9
|
+
table_column :draft
|
10
|
+
|
11
|
+
table_column :actions, :sortable => false, :filter => false, :partial => '/admin/pages/actions'
|
12
|
+
|
13
|
+
def collection
|
14
|
+
Effective::Page.all
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
module Effective
|
2
|
+
class Menu < ActiveRecord::Base
|
3
|
+
has_many :menu_items, :dependent => :delete_all
|
4
|
+
|
5
|
+
self.table_name = EffectivePages.menus_table_name.to_s
|
6
|
+
attr_protected() if Rails::VERSION::MAJOR == 3
|
7
|
+
|
8
|
+
structure do
|
9
|
+
title :string, :validates => [:presence, :uniqueness]
|
10
|
+
timestamps
|
11
|
+
end
|
12
|
+
|
13
|
+
accepts_nested_attributes_for :menu_items, :allow_destroy => true
|
14
|
+
|
15
|
+
before_create do
|
16
|
+
if self.menu_items.find { |menu_item| menu_item.lft == 1 }.blank?
|
17
|
+
menu_items.build(:title => 'Home', :url => '#', :lft => 1, :rgt => 2)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.update_from_effective_regions!(params)
|
22
|
+
(params || {}).each do |menu_id, attributes|
|
23
|
+
menu = Effective::Menu.find(menu_id)
|
24
|
+
|
25
|
+
attributes[:menu_items_attributes].each { |_, atts| atts[:menuable_type] = 'Effective::Page' if atts[:menuable_type].blank? }
|
26
|
+
menu.attributes = attributes
|
27
|
+
|
28
|
+
# So when we render the menu, we don't include the Root/Home item.
|
29
|
+
# It has a left of 1 and a right of max(items.right)
|
30
|
+
right = attributes[:menu_items_attributes].map { |_, atts| atts[:rgt].to_i }.max
|
31
|
+
menu.menu_items.find { |menu_item| menu_item.lft == 1 }.rgt = right + 1
|
32
|
+
|
33
|
+
menu.save!
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def contains?(obj)
|
38
|
+
menu_items.find { |menu_item| menu_item.url == obj || menu_item.menuable == obj }.present?
|
39
|
+
end
|
40
|
+
|
41
|
+
# This is the entry point to the DSL method for creating menu items
|
42
|
+
def build(&block)
|
43
|
+
raise 'build must be called with a block' if !block_given?
|
44
|
+
|
45
|
+
root = menu_items.build(:title => 'Home', :url => '#', :lft => 1, :rgt => 2)
|
46
|
+
root.parent = true
|
47
|
+
instance_exec(&block) # A call to dropdown or item
|
48
|
+
root.rgt = menu_items.map(&:rgt).max
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def dropdown(title, options = {}, &block)
|
55
|
+
raise 'dropdown must be called with a block' if !block_given?
|
56
|
+
raise 'dropdown menu_items may not have a URL' if options.kind_of?(String) || options.kind_of?(Symbol)
|
57
|
+
raise 'dropdown second parameter expects a Hash' unless options.kind_of?(Hash)
|
58
|
+
|
59
|
+
prev_item = menu_items.last
|
60
|
+
|
61
|
+
if prev_item.parent == true # This came from root or dropdown
|
62
|
+
lft = prev_item.lft + 1 # Go down. from lft
|
63
|
+
rgt = prev_item.rgt + 1
|
64
|
+
else
|
65
|
+
lft = prev_item.rgt + 1 # Go accross. from rgt
|
66
|
+
rgt = prev_item.rgt + 2
|
67
|
+
end
|
68
|
+
|
69
|
+
# Make room for new item by shifting everything after me up by 2
|
70
|
+
menu_items.each do |item|
|
71
|
+
item.rgt = (item.rgt + 2) if item.rgt > (lft - 1)
|
72
|
+
item.lft = (item.lft + 2) if item.lft > (lft - 1)
|
73
|
+
end
|
74
|
+
|
75
|
+
atts = build_menu_item_attributes(title, '', options).merge({:lft => lft, :rgt => rgt})
|
76
|
+
|
77
|
+
dropdown = menu_items.build(atts)
|
78
|
+
dropdown.parent = true
|
79
|
+
|
80
|
+
instance_exec(&block)
|
81
|
+
|
82
|
+
# Level up
|
83
|
+
dropdown.rgt = menu_items.last.rgt + 1 # Level up
|
84
|
+
dropdown.parent = false
|
85
|
+
menu_items << menu_items.delete(dropdown) # Put myself on the end of the array
|
86
|
+
end
|
87
|
+
|
88
|
+
# The URL parameter can be:
|
89
|
+
# - an Effective::Page object
|
90
|
+
# - the symbol :divider
|
91
|
+
# - a symbol or string that ends with _path
|
92
|
+
# - or a string that is the url
|
93
|
+
|
94
|
+
def item(title, url = '#', options = {})
|
95
|
+
raise 'item third parameter expects a Hash' unless options.kind_of?(Hash)
|
96
|
+
|
97
|
+
prev_item = menu_items.last
|
98
|
+
|
99
|
+
if prev_item.parent == true # This came from root or dropdown
|
100
|
+
lft = prev_item.lft + 1 # Go down. from lft
|
101
|
+
rgt = prev_item.rgt + 1
|
102
|
+
else
|
103
|
+
lft = prev_item.rgt + 1 # Go accross, from rgt
|
104
|
+
rgt = prev_item.rgt + 2
|
105
|
+
end
|
106
|
+
|
107
|
+
menu_items.each do |item|
|
108
|
+
item.rgt = (item.rgt + 2) if item.rgt > (lft - 1)
|
109
|
+
item.lft = (item.lft + 2) if item.lft > (lft - 1)
|
110
|
+
end
|
111
|
+
|
112
|
+
atts = build_menu_item_attributes(title, url, options).merge({:lft => lft, :rgt => rgt})
|
113
|
+
|
114
|
+
menu_items.build(atts)
|
115
|
+
end
|
116
|
+
|
117
|
+
def divider(options = {})
|
118
|
+
item(:divider, :divider, options)
|
119
|
+
end
|
120
|
+
|
121
|
+
def build_menu_item_attributes(title, url, options)
|
122
|
+
options[:roles_mask] ||= 0 if (options.delete(:signed_in) || options.delete(:private))
|
123
|
+
options[:roles_mask] ||= -1 if (options.delete(:signed_out) || options.delete(:guest))
|
124
|
+
|
125
|
+
options[:classes] = options.delete(:class)
|
126
|
+
|
127
|
+
if title == :divider || url == :divider || options[:divider] == true
|
128
|
+
options[:title] = 'divider'
|
129
|
+
options[:special] = 'divider'
|
130
|
+
elsif title.kind_of?(Effective::Page)
|
131
|
+
options[:title] = title.title
|
132
|
+
options[:menuable] = title
|
133
|
+
options[:url] = '#'
|
134
|
+
elsif url.kind_of?(Effective::Page)
|
135
|
+
options[:title] = title.presence || url.title
|
136
|
+
options[:menuable] = url
|
137
|
+
options[:url] = '#'
|
138
|
+
elsif url.to_s.end_with?('_path')
|
139
|
+
options[:title] = title
|
140
|
+
options[:special] = url
|
141
|
+
else
|
142
|
+
options[:title] = title
|
143
|
+
options[:url] = url
|
144
|
+
end
|
145
|
+
|
146
|
+
options
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Effective
|
2
|
+
class MenuItem < ActiveRecord::Base
|
3
|
+
attr_accessor :parent # This gets set on the Root node and a node created by Dropdown, so the item function knows whether to go down or to go accross
|
4
|
+
|
5
|
+
belongs_to :menu, :inverse_of => :menu_items
|
6
|
+
belongs_to :menuable, :polymorphic => true # Optionaly belong to an object
|
7
|
+
|
8
|
+
self.table_name = EffectivePages.menu_items_table_name.to_s
|
9
|
+
attr_protected() if Rails::VERSION::MAJOR == 3
|
10
|
+
|
11
|
+
structure do
|
12
|
+
title :string, :validates => [:presence]
|
13
|
+
|
14
|
+
url :string
|
15
|
+
special :string # divider / search / *_path
|
16
|
+
|
17
|
+
classes :string
|
18
|
+
new_window :boolean, :default => false, :validates => [:inclusion => {:in => [true, false]}]
|
19
|
+
roles_mask :integer, :default => nil # 0 is going to mean logged in, nil is going to mean public, > 0 will be future implementation of roles masking
|
20
|
+
|
21
|
+
lft :integer, :validates => [:presence], :index => true
|
22
|
+
rgt :integer, :validates => [:presence]
|
23
|
+
end
|
24
|
+
|
25
|
+
default_scope -> { includes(:menuable).order(:lft) }
|
26
|
+
|
27
|
+
def leaf?
|
28
|
+
(rgt.to_i - lft.to_i) == 1
|
29
|
+
end
|
30
|
+
|
31
|
+
def dropdown?
|
32
|
+
!leaf?
|
33
|
+
end
|
34
|
+
|
35
|
+
def divider?
|
36
|
+
leaf? && special == 'divider'
|
37
|
+
end
|
38
|
+
|
39
|
+
# For now it's just logged in or not?
|
40
|
+
# This will work with effective_roles one day...
|
41
|
+
def visible_for?(user)
|
42
|
+
can_view_page = (
|
43
|
+
if menuable.kind_of?(Effective::Page) && defined?(EffectiveRoles) && menuable.is_role_restricted?
|
44
|
+
(user.roles_match_with?(menuable) rescue false)
|
45
|
+
else
|
46
|
+
true
|
47
|
+
end
|
48
|
+
)
|
49
|
+
|
50
|
+
can_view_menu_item = (
|
51
|
+
if roles_mask == nil
|
52
|
+
true
|
53
|
+
elsif roles_mask == -1 # Am I logged out?
|
54
|
+
user.blank?
|
55
|
+
elsif roles_mask == 0 # Am I logged in?
|
56
|
+
user.present?
|
57
|
+
elsif roles_mask > 0 && defined?(EffectiveRoles)
|
58
|
+
user.present? && (user.roles_match_with?(roles_mask) rescue false)
|
59
|
+
else
|
60
|
+
false
|
61
|
+
end
|
62
|
+
)
|
63
|
+
|
64
|
+
can_view_page && can_view_menu_item
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Effective
|
2
|
+
class Page < ActiveRecord::Base
|
3
|
+
acts_as_sluggable
|
4
|
+
acts_as_role_restricted if defined?(EffectiveRoles)
|
5
|
+
acts_as_regionable if defined?(EffectiveRegions)
|
6
|
+
|
7
|
+
self.table_name = EffectivePages.pages_table_name.to_s
|
8
|
+
|
9
|
+
structure do
|
10
|
+
title :string, :validates => [:presence]
|
11
|
+
meta_description :string, :validates => [:presence]
|
12
|
+
|
13
|
+
draft :boolean, :default => false
|
14
|
+
|
15
|
+
layout :string, :default => 'application', :validates => [:presence]
|
16
|
+
template :string, :validates => [:presence]
|
17
|
+
|
18
|
+
slug :string
|
19
|
+
roles_mask :integer, :default => 0
|
20
|
+
|
21
|
+
timestamps
|
22
|
+
end
|
23
|
+
|
24
|
+
scope :drafts, -> { where(:draft => true) }
|
25
|
+
scope :published, -> { where(:draft => false) }
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
|
32
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
.menu-item
|
2
|
+
= form.fields_for :menu_items, item do |f|
|
3
|
+
= f.hidden_field :title, :autocomplete => 'off'
|
4
|
+
= f.hidden_field :menuable_id, :autocomplete => 'off'
|
5
|
+
= f.hidden_field :menuable_type, :autocomplete => 'off'
|
6
|
+
= f.hidden_field :url, :autocomplete => 'off'
|
7
|
+
= f.hidden_field :special, :autocomplete => 'off'
|
8
|
+
= f.hidden_field :new_window, :autocomplete => 'off'
|
9
|
+
= f.hidden_field :classes, :autocomplete => 'off'
|
10
|
+
= f.hidden_field :roles_mask, :autocomplete => 'off'
|
11
|
+
= f.hidden_field :lft, :autocomplete => 'off'
|
12
|
+
= f.hidden_field :rgt, :autocomplete => 'off'
|
13
|
+
= f.hidden_field :_destroy, :autocomplete => 'off'
|
@@ -0,0 +1,9 @@
|
|
1
|
+
= simple_form_for(menu, (EffectivePages.simple_form_options || {}).merge(:url => (menu.persisted? ? effective_pages.admin_menu_path(menu) : effective_pages.admin_menus_path))) do |f|
|
2
|
+
= f.input :title, :hint => "Give this menu a title"
|
3
|
+
|
4
|
+
.form-group
|
5
|
+
.col-xs-12
|
6
|
+
.form-group
|
7
|
+
.pull-right
|
8
|
+
= f.button :submit, 'Save'
|
9
|
+
= link_to 'Cancel', effective_pages.admin_menus_path
|
@@ -0,0 +1,14 @@
|
|
1
|
+
%span.actions
|
2
|
+
= link_to 'Visit', effective_pages.page_path(page), :target => '_blank'
|
3
|
+
= '-'
|
4
|
+
- if defined?(EffectiveRegions)
|
5
|
+
= link_to 'Edit Content', effective_regions.edit_path(page), 'data-no-turbolink' => true
|
6
|
+
= '-'
|
7
|
+
= link_to 'Edit', effective_pages.edit_admin_page_path(page.id)
|
8
|
+
= '-'
|
9
|
+
= link_to 'Delete', effective_pages.admin_page_path(page.id), :data => {:method => :delete, :confirm => "Are you sure? This page will be made unavailable."}
|
10
|
+
/ = '-'
|
11
|
+
/ - if page.draft?
|
12
|
+
/ = link_to "Undraft", effective_orders.undraft_admin_page_path(page)
|
13
|
+
/ - else
|
14
|
+
/ = link_to "Draft", effective_orders.draft_admin_page_path(page), :data => {:method => :delete, :confirm => "Are you sure? This page will be made unavailable."}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
= simple_form_for(page, (EffectivePages.simple_form_options || {}).merge(:url => (page.persisted? ? effective_pages.admin_page_path(page.id) : effective_pages.admin_pages_path))) do |f|
|
2
|
+
= f.input :title, :hint => "Give this page a title"
|
3
|
+
= f.input :draft, :hint => "Save this page as a draft. It will not be accessible on the website."
|
4
|
+
|
5
|
+
- if !f.object.new_record? || f.object.errors.include?(:slug)
|
6
|
+
- current_url = (effective_pages.page_url(f.object) rescue nil)
|
7
|
+
= f.input :slug, :hint => "The slug controls this page's internet address. Be careful, changing the slug will break links that other websites may have to the old address.<br>#{('This page is currently reachable via ' + link_to(current_url.gsub(f.object.slug, '<strong>' + f.object.slug + '</strong>').html_safe, current_url)) if current_url }".html_safe
|
8
|
+
|
9
|
+
= f.input :meta_description, :hint => "A one or two sentence summary of this page. Appears on Google search results underneath the page title."
|
10
|
+
|
11
|
+
- if EffectivePages.pages.length == 1
|
12
|
+
= f.input :template, :as => :hidden, :value => EffectivePages.pages.first
|
13
|
+
- else
|
14
|
+
= f.input :template, :as => :select, :collection => EffectivePages.pages, :include_blank => false
|
15
|
+
|
16
|
+
- if EffectivePages.layouts.length == 1
|
17
|
+
= f.input :layout, :as => :hidden, :value => EffectivePages.layouts.first
|
18
|
+
- else
|
19
|
+
= f.input :layout, :as => :select, :collection => EffectivePages.layouts, :include_blank => false
|
20
|
+
|
21
|
+
- if defined?(EffectiveRoles) and f.object.respond_to?(:roles)
|
22
|
+
= f.input :roles, :collection => EffectiveRoles.roles_collection(f.object), :as => :check_boxes, :hint => '* leave blank for a regular public page that anyone can view'
|
23
|
+
|
24
|
+
.form-group
|
25
|
+
.col-xs-12
|
26
|
+
.form-group
|
27
|
+
.pull-right
|
28
|
+
= f.button :submit, 'Save'
|
29
|
+
- if defined?(EffectiveRegions)
|
30
|
+
= f.button :submit, 'Save and Edit Content'
|
31
|
+
= link_to 'Cancel', effective_pages.admin_pages_path
|