effective_pages 2.0.8 → 3.0.2

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.
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)