effective_pages 2.0.8 → 3.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +11 -70
  4. data/app/controllers/admin/menus_controller.rb +6 -48
  5. data/app/controllers/admin/pages_controller.rb +11 -102
  6. data/app/controllers/effective/pages_controller.rb +14 -8
  7. data/app/datatables/effective_pages_datatable.rb +20 -2
  8. data/app/datatables/effective_pages_menu_datatable.rb +38 -0
  9. data/app/helpers/effective_menus_helper.rb +31 -136
  10. data/app/helpers/effective_pages_helper.rb +4 -2
  11. data/app/models/effective/page.rb +65 -22
  12. data/app/views/admin/menus/index.html.haml +10 -2
  13. data/app/views/admin/pages/_form.html.haml +20 -25
  14. data/app/views/admin/pages/_form_access.html.haml +10 -0
  15. data/app/views/admin/pages/_form_content.html.haml +3 -0
  16. data/app/views/admin/pages/_form_menu.html.haml +16 -0
  17. data/app/views/admin/pages/_form_page.html.haml +31 -0
  18. data/app/views/admin/pages/_rich_text_areas.html.haml +2 -0
  19. data/app/views/effective/pages/_menu.html.haml +20 -0
  20. data/config/effective_pages.rb +9 -38
  21. data/config/routes.rb +6 -9
  22. data/db/migrate/01_create_effective_pages.rb.erb +12 -30
  23. data/lib/effective_pages.rb +17 -61
  24. data/lib/effective_pages/version.rb +1 -1
  25. data/lib/generators/effective_pages/install_generator.rb +1 -1
  26. data/lib/generators/templates/example.html.haml +4 -5
  27. data/lib/tasks/effective_pages_tasks.rake +1 -1
  28. metadata +11 -38
  29. data/app/datatables/effective_menus_datatable.rb +0 -16
  30. data/app/helpers/effective_breadcrumbs_helper.rb +0 -41
  31. data/app/helpers/effective_menus_admin_helper.rb +0 -8
  32. data/app/models/effective/access_denied.rb +0 -17
  33. data/app/models/effective/menu.rb +0 -172
  34. data/app/models/effective/menu_item.rb +0 -78
  35. data/app/views/admin/menu_items/_actions.html.haml +0 -4
  36. data/app/views/admin/menu_items/_expand.html.haml +0 -2
  37. data/app/views/admin/menu_items/_item.html.haml +0 -13
  38. data/app/views/admin/menu_items/_new.html.haml +0 -3
  39. data/app/views/admin/menus/_actions.html.haml +0 -2
  40. data/app/views/admin/menus/_form.html.haml +0 -4
  41. data/app/views/admin/menus/edit.html.haml +0 -3
  42. data/app/views/admin/menus/new.html.haml +0 -3
  43. data/app/views/admin/menus/show.html.haml +0 -39
  44. data/app/views/admin/pages/_actions.html.haml +0 -7
  45. data/app/views/admin/pages/_roles.html.haml +0 -1
  46. data/app/views/admin/pages/edit.html.haml +0 -3
  47. data/app/views/admin/pages/index.html.haml +0 -6
  48. data/app/views/admin/pages/new.html.haml +0 -3
@@ -1,156 +1,51 @@
1
+ # frozen_string_literal: true
1
2
  module EffectiveMenusHelper
2
- def render_menu(menu, options = {}, &block)
3
- menu = Effective::Menu.find_by_title(menu.to_s) if menu.kind_of?(String) || menu.kind_of?(Symbol)
4
- return "<ul class='nav navbar-nav'><li>Menu '#{menu}' does not exist</li></ul>".html_safe if !menu.present?
5
3
 
6
- if (effectively_editting? && EffectivePages.authorized?(controller, :edit, menu) rescue false)
7
- options[:menu_id] = menu.id
8
- form_for(menu, :url => '/') { |form| options[:form] = form }
9
- end
10
-
11
- menu_items = menu.menu_items
12
-
13
- if menu.new_record?
14
- menu_items = menu_items.to_a.sort! { |a, b| a.lft <=> b.lft }
15
- end
4
+ def render_menu(name, options = {}, &block)
5
+ name = name.to_s
6
+ menu = Array(EffectivePages.menus).find { |menu| menu.to_s == name }
16
7
 
17
- if block_given? && options[:form].blank?
18
- render_menu_items(menu_items, options) { yield }
19
- else
20
- render_menu_items(menu_items, options)
8
+ if menu.blank?
9
+ raise("unable to find menu #{name}. Please add it to config/initializers/effective_pages.rb")
21
10
  end
22
11
 
23
- # if options[:for_editor]
24
- #else
25
- # Rails.cache.fetch(menu) { render_menu_items(menu.menu_items, options) }
26
- #end
12
+ content_tag(:ul, options) { render('effective/pages/menu', menu: menu) }
27
13
  end
28
14
 
29
- def render_menu_items(items, options = {}, &block)
30
- if options[:form].present? && options[:form].kind_of?(ActionView::Helpers::FormBuilder) == false
31
- raise 'Expecting ActionView::Helpers::FormBuilder object for :form => option'
32
- end
33
-
34
- html = ''
35
-
36
- if options[:form]
37
- html << "<ul class='nav navbar-nav effective-menu #{options[:class]}'"
38
- html << " data-effective-menu-id='#{options[:menu_id] || 0}'"
39
- html << " data-effective-menu-expand-html=\"#{render(:partial => 'admin/menu_items/expand').gsub('"', "'").gsub("\n", '').gsub(' ', '')}\""
40
- html << " data-effective-menu-new-html=\"#{render(:partial => 'admin/menu_items/new', :locals => { :item => Effective::MenuItem.new(), :form => options[:form] }).gsub('"', "'").gsub("\n", '').gsub(' ', '').gsub('[0]', '[:new]').gsub('_0_', '_:new_')}\""
15
+ def render_breadcrumbs(menu, page = @page, root: 'Home')
16
+ return breadcrumbs_root_url(page, root: root) if request.path == '/'
17
+ return breadcrumbs_fallback(page, root: root) unless page.kind_of?(Effective::Page)
41
18
 
42
- maxdepth = ((options[:maxdepth].presence || EffectivePages.menu[:maxdepth].presence).to_i rescue 0)
43
- html << " data-effective-menu-maxdepth='#{maxdepth}'" if maxdepth > 0
19
+ parents = [page.menu_parent].compact
44
20
 
45
- html << ">"
46
- else
47
- html << "<ul class='nav navbar-nav#{' ' + options[:class].to_s if options[:class].present?}'>"
21
+ content_tag(:ol, class: 'breadcrumb') do
22
+ (
23
+ [content_tag(:li, link_to(root, root_path, title: root), class: 'breadcrumb-item')] +
24
+ parents.map do |page|
25
+ url = (page.menu_url.presence || effective_pages.page_path(page))
26
+ content_tag(:li, link_to(page, url, title: page.title), class: 'breadcrumb-item')
27
+ end +
28
+ [content_tag(:li, page, class: 'breadcrumb-item active', 'aria-current': 'page')]
29
+ ).join.html_safe
48
30
  end
49
-
50
- stack = [items.to_a.first] # A stack to keep track of rgt values.
51
- skip_to_lft = 0 # This lets us skip over nodes we don't have visible_for? permission to see
52
-
53
- items.each_with_index do |item, index|
54
- next if index == 0 # We always skip the first Root node
55
-
56
- # This allows us to skip over nodes we don't have permission to view
57
- next if item.lft < skip_to_lft
58
- if options[:form].blank? && !item.visible_for?(defined?(current_user) ? current_user : nil)
59
- skip_to_lft = item.rgt + 1
60
- next
61
- end
62
-
63
- if stack.size > 1
64
- html << "<ul class='dropdown-menu'>" if (item.rgt < stack.last.rgt) # Level down?
65
-
66
- while item.rgt > stack.last.rgt # Level up?
67
- stack.pop
68
- html << "</ul></li>" if (item.rgt > stack.last.rgt)
69
- end
70
- end
71
-
72
- # Render the <li>...</li> with carets only on top level dropdowns, not sub dropdowns
73
- html << render_menu_item(item, stack.size == 1, options)
74
-
75
- stack.push(item)
76
- end
77
-
78
- while stack.size > 0
79
- item = stack.pop
80
-
81
- if stack.size == 0 # Very last one
82
- html << render(:partial => 'admin/menu_items/actions') if options[:form]
83
- html << (capture(&block) || '') if block_given? && !options[:form]
84
- html << '</ul>'
85
- elsif item.leaf? == false
86
- html << '</ul></li>'
87
- end
88
- end
89
-
90
- html.html_safe
91
31
  end
92
32
 
93
- private
94
-
95
- # This is where we actually build out an li item
96
- def render_menu_item(item, caret, options)
97
- html = ""
98
-
99
- url = (
100
- if item.menuable.kind_of?(Effective::Page)
101
- effective_pages.page_path(item.menuable)
102
- elsif item.divider?
103
- nil
104
- elsif (item.special || '').end_with?('_path')
105
- self.send(item.special) rescue nil
106
- elsif item.url.present?
107
- item.url
108
- end
109
- ).presence || '#'
33
+ alias_method :render_breadcrumb, :render_breadcrumbs
110
34
 
111
- classes = (item.classes || '').split(' ')
112
- classes << 'active' if EffectivePages.menu[:apply_active_class] && request.try(:fullpath) == url
113
- classes << 'divider' if item.divider?
114
- classes << 'dropdown' if item.dropdown?
115
- classes = classes.join(' ')
116
-
117
- if item.leaf?
118
- html << (classes.present? ? "<li class='#{classes}'>" : "<li>")
119
-
120
- if !item.divider? || options[:form] # Show the URL in edit mode, but not normally
121
- html << render_menu_item_anchor_tag(item, caret, url)
122
- end
123
-
124
- if options[:form]
125
- html << render(:partial => 'admin/menu_items/item', :locals => { :item => item, :form => options[:form] })
126
- end
127
-
128
- html << "</li>"
129
- else
130
- html << (classes.present? ? "<li class='#{classes}'>" : "<li>")
131
- html << render_menu_item_anchor_tag(item, caret, url)
132
-
133
- if options[:form]
134
- html << render(:partial => 'admin/menu_items/item', :locals => { :item => item, :form => options[:form] })
135
- end
35
+ def breadcrumbs_root_url(page = @page, root: 'Home')
36
+ content_tag(:ol, class: 'breadcrumb') do
37
+ content_tag(:li, root, class: 'breadcrumb-item active', 'aria-current': 'page')
136
38
  end
137
-
138
- html
139
39
  end
140
40
 
141
- def render_menu_item_anchor_tag(item, caret, url)
142
- new_window_html = (item.new_window? ? " target='_blank'" : "")
41
+ def breadcrumbs_fallback(page = @page, root: 'Home')
42
+ label = (page if page.kind_of?(Effective::Page)) || @page_title || 'Here'
143
43
 
144
- if item.special == 'destroy_user_session_path'
145
- "<a href='#{url}' data-method='delete' data-no-turbolink='true'>#{item.title}</a>"
146
- elsif item.dropdown?
147
- ''.tap do |html|
148
- html << "<a href='#{url}' data-toggle='dropdown'#{new_window_html}>#{item.title}"
149
- html << "<span class='caret'></span>" if caret
150
- html << "</a>"
151
- end
152
- else
153
- "<a href='#{url}'#{new_window_html}>#{item.title}</a>"
44
+ content_tag(:ol, class: 'breadcrumb') do
45
+ [
46
+ content_tag(:li, link_to(root, root_path, title: root), class: 'breadcrumb-item'),
47
+ content_tag(:li, label, class: 'breadcrumb-item active', 'aria-current': 'page')
48
+ ].join.html_safe
154
49
  end
155
50
  end
156
51
 
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module EffectivePagesHelper
2
4
 
3
5
  def effective_pages_body_classes
4
6
  [
5
7
  params[:controller].to_s.parameterize,
6
8
  params[:action],
7
- ((user_signed_in? ? 'signed-in'.freeze : 'not-signed-in'.freeze) rescue nil),
8
- (@page.template rescue nil),
9
+ (user_signed_in? ? 'signed-in' : 'not-signed-in'),
10
+ (@page.template if @page && @page.respond_to?(:template)),
9
11
  @body_classes
10
12
  ].compact.join(' ')
11
13
  end
@@ -1,27 +1,55 @@
1
1
  module Effective
2
2
  class Page < ActiveRecord::Base
3
+ attr_accessor :current_user
4
+
5
+ # These parent / children are for the menu as well
6
+ belongs_to :menu_parent, class_name: 'Effective::Page', optional: true
7
+
8
+ has_many :menu_children, -> { Effective::Page.menuable }, class_name: 'Effective::Page',
9
+ foreign_key: :menu_parent_id, inverse_of: :menu_parent
10
+
3
11
  acts_as_role_restricted
4
- acts_as_regionable
5
12
  acts_as_slugged
13
+ has_many_rich_texts
6
14
 
7
- has_many :menu_items, as: :menuable, dependent: :destroy
15
+ log_changes if respond_to?(:log_changes)
8
16
 
9
17
  self.table_name = EffectivePages.pages_table_name.to_s
10
18
 
11
- # structure do
12
- # title :string
13
- # meta_description :string
19
+ effective_resource do
20
+ title :string
21
+ meta_description :string
22
+
23
+ draft :boolean
24
+
25
+ layout :string
26
+ template :string
27
+
28
+ slug :string
29
+
30
+ # Menu stuff
31
+ menu :boolean
32
+ menu_name :string
14
33
 
15
- # draft :boolean
34
+ menu_url :string
35
+ menu_position :integer
16
36
 
17
- # layout :string
18
- # template :string
37
+ # Access
38
+ roles_mask :integer
39
+ authenticate_user :boolean
19
40
 
20
- # slug :string
21
- # roles_mask :integer
41
+ timestamps
42
+ end
22
43
 
23
- # timestamps
24
- # end
44
+ before_validation(if: -> { menu? && menu_position.blank? }) do
45
+ self.menu_position = (self.class.where(menu_parent: menu_parent).maximum(:menu_position) || -1) + 1
46
+ end
47
+
48
+ validate(if: -> { menu_url.present? }) do
49
+ unless menu_url.start_with?('http://') || menu_url.start_with?('https://') || menu_url.start_with?('/')
50
+ self.errors.add(:menu_url, "must start with http(s):// or /")
51
+ end
52
+ end
25
53
 
26
54
  validates :title, presence: true, length: { maximum: 255 }
27
55
  validates :meta_description, presence: true, length: { maximum: 150 }
@@ -29,11 +57,27 @@ module Effective
29
57
  validates :layout, presence: true
30
58
  validates :template, presence: true
31
59
 
32
- scope :drafts, -> { where(draft: true) }
60
+ validates :menu_name, if: -> { menu? && EffectivePages.menus.present? },
61
+ presence: true, inclusion: { in: EffectivePages.menus.map(&:to_s) }
62
+
63
+ # validates :menu_position, if: -> { menu? },
64
+ # presence: true, uniqueness: { scope: [:menu_name, :menu_parent_id] }
65
+
66
+ scope :deep, -> { includes(:menu_parent, menu_children: :menu_parent) }
67
+
68
+ scope :draft, -> { where(draft: true) }
33
69
  scope :published, -> { where(draft: false) }
34
70
  scope :sorted, -> { order(:title) }
71
+ scope :on_menu, -> { where(menu: true) }
35
72
  scope :except_home, -> { where.not(title: 'Home') }
36
73
 
74
+ scope :menuable, -> { where(menu: true).order(:menu_position) }
75
+ scope :menu_deep, -> { includes(:menu_parent, :menu_children) }
76
+
77
+ scope :for_menu, -> (name) { menuable.where(menu_name: name) }
78
+ scope :for_menu_root, -> (name) { for_menu(name).menu_deep.root_level }
79
+ scope :root_level, -> { where(menu_parent_id: nil) }
80
+
37
81
  def to_s
38
82
  title
39
83
  end
@@ -43,24 +87,23 @@ module Effective
43
87
  end
44
88
 
45
89
  # Returns a duplicated post object, or throws an exception
46
- def duplicate!
90
+ def duplicate
47
91
  Page.new(attributes.except('id', 'updated_at', 'created_at')).tap do |page|
48
92
  page.title = page.title + ' (Copy)'
49
93
  page.slug = page.slug + '-copy'
50
- page.draft = true
51
94
 
52
- regions.each do |region|
53
- page.regions.build(region.attributes.except('id', 'updated_at', 'created_at'))
95
+ rich_texts.each do |rt|
96
+ page.send("rich_text_#{rt.name}=", rt.body)
54
97
  end
55
98
 
56
- page.save!
99
+ page.draft = true
57
100
  end
58
101
  end
59
102
 
103
+ def duplicate!
104
+ duplicate.tap { |page| page.save! }
105
+ end
106
+
60
107
  end
61
108
 
62
109
  end
63
-
64
-
65
-
66
-
@@ -1,3 +1,11 @@
1
- %h1.effective-admin-heading= @page_title
1
+ %h1= @page_title
2
2
 
3
- = render_datatable(@datatable)
3
+ %p
4
+ Click Reorder to drag & drop reorder the menu.
5
+ Edit an item to reorder its children items.
6
+
7
+ - EffectivePages.menus.each do |menu|
8
+ %h2 #{menu.to_s.titleize} Menu
9
+ - datatable = EffectivePagesMenuDatatable.new(menu: menu)
10
+ = render_datatable(datatable, simple: true, inline: true)
11
+ %hr
@@ -1,32 +1,27 @@
1
- = effective_form_with(model: page, url: (page.persisted? ? effective_pages.admin_page_path(page.id) : effective_pages.admin_pages_path)) do |f|
2
- = f.text_field :title, hint: 'The title of your page.', input_html: { maxlength: 255 }
1
+ - if inline_datatable? && inline_datatable.attributes[:menu]
2
+ - menu = inline_datatable.attributes[:menu]
3
+ - datatable = EffectivePagesMenuDatatable.new(menu: menu, menu_parent_id: page.id)
3
4
 
4
- - if (templates = EffectivePages.templates).length == 1
5
- = f.hidden_field :template, value: templates.first
6
- - else
7
- = f.select :template, templates
5
+ = render '/admin/pages/form_menu', page: page
8
6
 
9
- - if (layouts = EffectivePages.layouts).length == 1
10
- = f.hidden_field :layout, value: layouts.first
11
- - else
12
- = f.select :layout, layouts
7
+ - if datatable.present?(self)
8
+ %h3 Children
9
+ = render_datatable(datatable, simple: true, inline: true)
13
10
 
14
- - if f.object.persisted? || f.object.errors.include?(:slug)
15
- - current_url = (effective_pages.page_url(f.object) rescue nil)
16
- = f.text_field :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
11
+ - else
12
+ = tabs do
13
+ = tab 'Page' do
14
+ = render '/admin/pages/form_page', page: page
17
15
 
18
- = f.text_field :meta_description, hint: "A one or two sentence summary of this page. Appears on Google search results underneath the page title.", input_html: { maxlength: 150 }
16
+ - if page.persisted?
17
+ = tab 'Content' do
18
+ = render '/admin/pages/form_content', page: page
19
19
 
20
- = render partial: '/admin/pages/additional_fields', locals: { page: page, form: f, f: f }
20
+ = tab 'Menu' do
21
+ = render '/admin/pages/form_menu', page: page
21
22
 
22
- = f.check_box :draft, label: 'Save this page as a draft. It will not be accessible on the website.'
23
+ = tab 'Access' do
24
+ = render '/admin/pages/form_access', page: page
23
25
 
24
- - if defined?(EffectiveRoles) and f.object.respond_to?(:roles) && EffectivePages.use_effective_roles
25
- = render partial: '/admin/pages/roles', locals: { page: page, form: f, f: f }
26
-
27
- = f.submit do
28
- = f.save 'Save'
29
- = f.save 'Save and Edit Content', class: 'btn btn-secondary'
30
- = f.save 'Save and View', class: 'btn btn-secondary'
31
- - if f.object.persisted?
32
- = f.save 'Duplicate', class: 'btn btn-info'
26
+ = tab 'Logs' do
27
+ = render_datatable(page.log_changes_datatable, inline: true, namespace: :admin)
@@ -0,0 +1,10 @@
1
+ = effective_form_with(model: page, url: page.persisted? ? effective_pages.admin_page_path(page.id) : effective_pages.admin_pages_path) do |f|
2
+ = f.check_box :authenticate_user, label: 'Yes, the user must be be signed in to view this page'
3
+
4
+ - if EffectivePages.use_effective_roles
5
+ = f.checks :roles, EffectiveRoles.roles_collection(f.object)
6
+
7
+ %p.text-hint
8
+ * leave blank for a regular public page that anyone can view
9
+
10
+ = f.submit
@@ -0,0 +1,3 @@
1
+ = effective_form_with(model: page, url: page.persisted? ? effective_pages.admin_page_path(page.id) : effective_pages.admin_pages_path) do |f|
2
+ = render '/admin/pages/rich_text_areas', page: page, f: f
3
+ = effective_submit(f)
@@ -0,0 +1,16 @@
1
+ = effective_form_with(model: page, url: page.persisted? ? effective_pages.admin_page_path(page.id) : effective_pages.admin_pages_path) do |f|
2
+ = f.check_box :menu, label: 'Yes, display this page on the menu'
3
+
4
+ = f.show_if :menu, true do
5
+
6
+ - if (menus = EffectivePages.menus).length > 1
7
+ = f.select :menu_name, menus
8
+ - else
9
+ = f.hidden_field :menu_name, value: menus.first
10
+
11
+ = f.text_field :menu_url, label: "Redirect to path or url instead of displaying page",
12
+ hint: "Must start with http(s):// or /"
13
+
14
+ = f.select :menu_parent_id, Effective::Page.menuable.root_level, placeholder: 'None. Top level menu.'
15
+
16
+ = f.submit
@@ -0,0 +1,31 @@
1
+ = effective_form_with(model: page, url: page.persisted? ? effective_pages.admin_page_path(page.id) : effective_pages.admin_pages_path) do |f|
2
+ = f.text_field :title, hint: 'The title of your page.'
3
+
4
+ = f.check_box :draft,
5
+ label: 'Save this page as a draft. It will not be accessible on the website.'
6
+
7
+ = f.text_field :meta_description,
8
+ hint: "A one or two sentence summary of this page. Appears on Google search results underneath the page title.",
9
+ input_html: { maxlength: 150 }
10
+
11
+ - if (layouts = EffectivePages.layouts).length > 1
12
+ = f.select :layout, layouts
13
+ - else
14
+ = f.hidden_field :layout, value: layouts.first
15
+
16
+ - if (templates = EffectivePages.templates).length > 1
17
+ = f.select :template, templates
18
+ - else
19
+ = f.hidden_field :template, value: templates.first
20
+
21
+ - if f.object.persisted? || f.object.errors.include?(:slug)
22
+ - current_url = (effective_pages.page_url(f.object) rescue nil)
23
+ = f.text_field :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
24
+
25
+ = render partial: '/admin/pages/additional_fields', locals: { page: page, form: f, f: f }
26
+
27
+ -# This is for duplicate
28
+ - if f.object.new_record? && f.object.rich_texts.present?
29
+ = render partial: '/admin/pages/rich_text_areas', locals: { page: page, form: f, f: f }
30
+
31
+ = effective_submit(f)