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.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +30 -0
  4. data/Rakefile +24 -0
  5. data/app/assets/javascripts/effective_pages.js +0 -0
  6. data/app/assets/stylesheets/effective_pages/dropdown-submenu.css +7 -0
  7. data/app/assets/stylesheets/effective_pages.css.scss +1 -0
  8. data/app/controllers/admin/menus_controller.rb +79 -0
  9. data/app/controllers/admin/pages_controller.rb +95 -0
  10. data/app/controllers/effective/pages_controller.rb +24 -0
  11. data/app/helpers/effective_menus_helper.rb +151 -0
  12. data/app/helpers/effective_pages_helper.rb +14 -0
  13. data/app/models/effective/access_denied.rb +17 -0
  14. data/app/models/effective/datatables/menus.rb +16 -0
  15. data/app/models/effective/datatables/pages.rb +19 -0
  16. data/app/models/effective/menu.rb +152 -0
  17. data/app/models/effective/menu_item.rb +68 -0
  18. data/app/models/effective/page.rb +32 -0
  19. data/app/views/admin/menu_items/_actions.html.haml +4 -0
  20. data/app/views/admin/menu_items/_expand.html.haml +2 -0
  21. data/app/views/admin/menu_items/_item.html.haml +13 -0
  22. data/app/views/admin/menu_items/_new.html.haml +3 -0
  23. data/app/views/admin/menus/_actions.html.haml +4 -0
  24. data/app/views/admin/menus/_form.html.haml +9 -0
  25. data/app/views/admin/menus/edit.html.haml +2 -0
  26. data/app/views/admin/menus/index.html.haml +9 -0
  27. data/app/views/admin/menus/new.html.haml +2 -0
  28. data/app/views/admin/pages/_actions.html.haml +14 -0
  29. data/app/views/admin/pages/_form.html.haml +31 -0
  30. data/app/views/admin/pages/edit.html.haml +2 -0
  31. data/app/views/admin/pages/index.html.haml +9 -0
  32. data/app/views/admin/pages/new.html.haml +2 -0
  33. data/config/routes.rb +28 -0
  34. data/db/migrate/01_create_effective_pages.rb.erb +52 -0
  35. data/lib/effective_pages/engine.rb +19 -0
  36. data/lib/effective_pages/version.rb +3 -0
  37. data/lib/effective_pages.rb +73 -0
  38. data/lib/generators/effective_pages/install_generator.rb +43 -0
  39. data/lib/generators/templates/README +1 -0
  40. data/lib/generators/templates/effective_pages.rb +49 -0
  41. data/lib/generators/templates/example.html.haml +6 -0
  42. data/lib/tasks/effective_pages_tasks.rake +29 -0
  43. data/spec/controllers/effective/pages_controller_spec.rb +66 -0
  44. data/spec/dummy/README.rdoc +261 -0
  45. data/spec/dummy/Rakefile +7 -0
  46. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  47. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  48. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  49. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  50. data/spec/dummy/app/views/effective/pages/example.html.haml +2 -0
  51. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  52. data/spec/dummy/config/application.rb +66 -0
  53. data/spec/dummy/config/boot.rb +10 -0
  54. data/spec/dummy/config/database.yml +25 -0
  55. data/spec/dummy/config/environment.rb +5 -0
  56. data/spec/dummy/config/environments/development.rb +37 -0
  57. data/spec/dummy/config/environments/production.rb +67 -0
  58. data/spec/dummy/config/environments/test.rb +37 -0
  59. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  60. data/spec/dummy/config/initializers/inflections.rb +15 -0
  61. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  62. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  63. data/spec/dummy/config/initializers/session_store.rb +8 -0
  64. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  65. data/spec/dummy/config/locales/en.yml +5 -0
  66. data/spec/dummy/config/routes.rb +60 -0
  67. data/spec/dummy/config.ru +4 -0
  68. data/spec/dummy/db/migrate/01_create_effective_pages.rb +51 -0
  69. data/spec/dummy/db/schema.rb +52 -0
  70. data/spec/dummy/db/test.sqlite3 +0 -0
  71. data/spec/dummy/public/404.html +26 -0
  72. data/spec/dummy/public/422.html +26 -0
  73. data/spec/dummy/public/500.html +25 -0
  74. data/spec/dummy/public/favicon.ico +0 -0
  75. data/spec/dummy/script/rails +6 -0
  76. data/spec/dummy/spec_link +3 -0
  77. data/spec/effective_pages_spec.rb +7 -0
  78. data/spec/helpers/effective_menus_helper_spec.rb +281 -0
  79. data/spec/models/effective/menu_spec.rb +133 -0
  80. data/spec/spec_helper.rb +43 -0
  81. data/spec/support/factories.rb +15 -0
  82. 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,4 @@
1
+ .actions
2
+ %span.add-item.glyphicon.glyphicon-plus-sign
3
+ %span.remove-item.glyphicon.glyphicon-trash
4
+
@@ -0,0 +1,2 @@
1
+ %ul.dropdown-menu
2
+ %li.effective-menu-expand &nbsp;
@@ -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,3 @@
1
+ %li.new-item
2
+ %a{:href => ''}
3
+ = render :partial => 'admin/menu_items/item', :locals => {:item => item, :form => form}
@@ -0,0 +1,4 @@
1
+ %span.actions
2
+ = link_to 'Edit', effective_pages.edit_admin_menu_path(menu)
3
+ = '-'
4
+ = link_to 'Delete', effective_pages.admin_menu_path(menu), :data => {:method => :delete, :confirm => "Are you sure? This menu will be made unavailable."}
@@ -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,2 @@
1
+ %h2= @page_title
2
+ = render :partial => 'form', :as => :menu, :object => @menu
@@ -0,0 +1,9 @@
1
+ %h2= @page_title
2
+
3
+ - unless @datatable.collection.length > 0
4
+ %p
5
+ There are no menus
6
+ - else
7
+ = render_datatable @datatable
8
+
9
+ %p= link_to 'New Menu', effective_pages.new_admin_menu_path, :class => 'btn btn-primary'
@@ -0,0 +1,2 @@
1
+ %h2= @page_title
2
+ = render :partial => 'form', :as => :menu, :object => @menu
@@ -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