slices 1.0.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 (215) hide show
  1. data/CHANGELOG.md +3 -0
  2. data/README.md +51 -0
  3. data/Rakefile +9 -0
  4. data/app/assets/images/slices/ajax-loader.gif +0 -0
  5. data/app/assets/images/slices/asset-background.png +0 -0
  6. data/app/assets/images/slices/asset-spinner.gif +0 -0
  7. data/app/assets/images/slices/bg_header.gif +0 -0
  8. data/app/assets/images/slices/black-Linen.png +0 -0
  9. data/app/assets/images/slices/calendar.svg +68 -0
  10. data/app/assets/images/slices/chosen-sprite.png +0 -0
  11. data/app/assets/images/slices/drag-handle.svg +9 -0
  12. data/app/assets/images/slices/icon_admins.png +0 -0
  13. data/app/assets/images/slices/icon_app.png +0 -0
  14. data/app/assets/images/slices/icon_assets.png +0 -0
  15. data/app/assets/images/slices/icon_collapse.png +0 -0
  16. data/app/assets/images/slices/icon_drag.png +0 -0
  17. data/app/assets/images/slices/icon_files.png +0 -0
  18. data/app/assets/images/slices/icon_generic_file.png +0 -0
  19. data/app/assets/images/slices/icon_images.png +0 -0
  20. data/app/assets/images/slices/icon_padlock.png +0 -0
  21. data/app/assets/images/slices/icon_page.png +0 -0
  22. data/app/assets/images/slices/icon_search.png +0 -0
  23. data/app/assets/images/slices/icon_set-link.png +0 -0
  24. data/app/assets/images/slices/icon_set.png +0 -0
  25. data/app/assets/images/slices/icon_sitemap.png +0 -0
  26. data/app/assets/images/slices/icon_snippets.png +0 -0
  27. data/app/assets/images/slices/icon_template.jpg +0 -0
  28. data/app/assets/images/slices/icon_upload_happy.png +0 -0
  29. data/app/assets/images/slices/icon_upload_sad.png +0 -0
  30. data/app/assets/images/slices/icon_upload_thinking.png +0 -0
  31. data/app/assets/images/slices/noise.png +0 -0
  32. data/app/assets/images/slices/sitemap_icon_ghost.png +0 -0
  33. data/app/assets/images/slices/sitemap_icon_home.png +0 -0
  34. data/app/assets/images/slices/sitemap_icon_page.png +0 -0
  35. data/app/assets/images/slices/sitemap_icon_set_page.png +0 -0
  36. data/app/assets/images/slices/sitemap_icon_virtual_page.png +0 -0
  37. data/app/assets/images/slices/sitemap_overlay.png +0 -0
  38. data/app/assets/images/slices/spinner.gif +0 -0
  39. data/app/assets/images/slices/trash.png +0 -0
  40. data/app/assets/javascripts/admin.js.erb +18 -0
  41. data/app/assets/javascripts/slices/app/backbones/admins.js +114 -0
  42. data/app/assets/javascripts/slices/app/backbones/entries.js +172 -0
  43. data/app/assets/javascripts/slices/app/backbones/generic.js +101 -0
  44. data/app/assets/javascripts/slices/app/backbones/snippets.js +113 -0
  45. data/app/assets/javascripts/slices/app/helpers/assets.js +61 -0
  46. data/app/assets/javascripts/slices/app/helpers/breadcrumbs.js +30 -0
  47. data/app/assets/javascripts/slices/app/helpers/composer.js +26 -0
  48. data/app/assets/javascripts/slices/app/helpers/date_field.js +16 -0
  49. data/app/assets/javascripts/slices/app/helpers/get_value.js +31 -0
  50. data/app/assets/javascripts/slices/app/helpers/icon_upload_names.js.erb +5 -0
  51. data/app/assets/javascripts/slices/app/helpers/layout.js +20 -0
  52. data/app/assets/javascripts/slices/app/helpers/sitemap.js +150 -0
  53. data/app/assets/javascripts/slices/app/helpers/slice_preview.js +48 -0
  54. data/app/assets/javascripts/slices/app/helpers/tagging.js +73 -0
  55. data/app/assets/javascripts/slices/app/helpers/token_field.js +17 -0
  56. data/app/assets/javascripts/slices/app/helpers/upload_icons.js.erb +5 -0
  57. data/app/assets/javascripts/slices/app/helpers/uploader.js +127 -0
  58. data/app/assets/javascripts/slices/app/models/asset.js +29 -0
  59. data/app/assets/javascripts/slices/app/models/asset_collection.js +41 -0
  60. data/app/assets/javascripts/slices/app/models/attachment.js +29 -0
  61. data/app/assets/javascripts/slices/app/models/attachment_collection.js +7 -0
  62. data/app/assets/javascripts/slices/app/models/composer_item.js +1 -0
  63. data/app/assets/javascripts/slices/app/models/composer_item_collection.js +3 -0
  64. data/app/assets/javascripts/slices/app/models/file.js +103 -0
  65. data/app/assets/javascripts/slices/app/models/page.js +186 -0
  66. data/app/assets/javascripts/slices/app/models/s3_file.js +64 -0
  67. data/app/assets/javascripts/slices/app/slices.js +661 -0
  68. data/app/assets/javascripts/slices/app/views/asset_editor_view.js.erb +209 -0
  69. data/app/assets/javascripts/slices/app/views/asset_library_view.js +720 -0
  70. data/app/assets/javascripts/slices/app/views/asset_thumb_view.js.erb +191 -0
  71. data/app/assets/javascripts/slices/app/views/attachment_composer_view.js +350 -0
  72. data/app/assets/javascripts/slices/app/views/attachment_view.js +101 -0
  73. data/app/assets/javascripts/slices/app/views/calendar_view.js +198 -0
  74. data/app/assets/javascripts/slices/app/views/composer_item_view.js +54 -0
  75. data/app/assets/javascripts/slices/app/views/composer_view.js +130 -0
  76. data/app/assets/javascripts/slices/app/views/date_field_view.js +177 -0
  77. data/app/assets/javascripts/slices/app/views/file_view.js +142 -0
  78. data/app/assets/javascripts/slices/app/views/token_field_view.js +253 -0
  79. data/app/assets/javascripts/slices/lib/freeze.js +14 -0
  80. data/app/assets/javascripts/slices/lib/human_file_size.js +16 -0
  81. data/app/assets/javascripts/slices/lib/json_patch.js +9 -0
  82. data/app/assets/javascripts/slices/lib/moment.js +47 -0
  83. data/app/assets/javascripts/slices/lib/plugins.js +101 -0
  84. data/app/assets/javascripts/slices/lib/sortable.js +14 -0
  85. data/app/assets/javascripts/slices/slices.js +27 -0
  86. data/app/assets/javascripts/slices/vendor/autoscroll.js +188 -0
  87. data/app/assets/javascripts/slices/vendor/backbone.js +38 -0
  88. data/app/assets/javascripts/slices/vendor/handlebars.js +1920 -0
  89. data/app/assets/javascripts/slices/vendor/jqmodal.js +69 -0
  90. data/app/assets/javascripts/slices/vendor/jquery-ui.js +274 -0
  91. data/app/assets/javascripts/slices/vendor/jquery-ui_nested-sortable.js +357 -0
  92. data/app/assets/javascripts/slices/vendor/jquery.ajaxprogress.js +76 -0
  93. data/app/assets/javascripts/slices/vendor/jquery.js +2 -0
  94. data/app/assets/javascripts/slices/vendor/livefield.js +459 -0
  95. data/app/assets/javascripts/slices/vendor/moment.js +6 -0
  96. data/app/assets/javascripts/slices/vendor/rails.js +315 -0
  97. data/app/assets/javascripts/slices/vendor/underscore-string.js +1 -0
  98. data/app/assets/javascripts/slices/vendor/underscore.js +5 -0
  99. data/app/assets/stylesheets/admin.css +1 -0
  100. data/app/assets/stylesheets/slices/admin.css.erb +2237 -0
  101. data/app/assets/stylesheets/slices/reset_html5.css +106 -0
  102. data/app/assets/stylesheets/slices/slices.css +7 -0
  103. data/app/controllers/admin/admin_controller.rb +10 -0
  104. data/app/controllers/admin/admins_controller.rb +76 -0
  105. data/app/controllers/admin/assets_controller.rb +53 -0
  106. data/app/controllers/admin/auth/omniauth_callbacks_controller.rb +15 -0
  107. data/app/controllers/admin/auth/passwords_controller.rb +4 -0
  108. data/app/controllers/admin/auth/sessions_controller.rb +4 -0
  109. data/app/controllers/admin/entries_controller.rb +88 -0
  110. data/app/controllers/admin/page_search_controller.rb +12 -0
  111. data/app/controllers/admin/pages_controller.rb +103 -0
  112. data/app/controllers/admin/site_maps_controller.rb +15 -0
  113. data/app/controllers/admin/snippets_controller.rb +33 -0
  114. data/app/controllers/application_controller.rb +4 -0
  115. data/app/controllers/pages_controller.rb +45 -0
  116. data/app/controllers/slices_controller.rb +63 -0
  117. data/app/controllers/static_assets_controller.rb +52 -0
  118. data/app/helpers/admin/admin_helper.rb +63 -0
  119. data/app/helpers/admin/assets_helper.rb +36 -0
  120. data/app/helpers/admin/entries_helper.rb +13 -0
  121. data/app/helpers/admin/site_maps_helper.rb +104 -0
  122. data/app/helpers/assets_helper.rb +64 -0
  123. data/app/helpers/navigation_helper.rb +195 -0
  124. data/app/helpers/pages_helper.rb +119 -0
  125. data/app/models/admin.rb +34 -0
  126. data/app/models/asset.rb +211 -0
  127. data/app/models/attachment.rb +11 -0
  128. data/app/models/layout.rb +44 -0
  129. data/app/models/page.rb +214 -0
  130. data/app/models/placeholder_slice.rb +8 -0
  131. data/app/models/set_page.rb +12 -0
  132. data/app/models/set_slice.rb +57 -0
  133. data/app/models/site_map.rb +24 -0
  134. data/app/models/slice.rb +80 -0
  135. data/app/models/snippet.rb +21 -0
  136. data/app/observers/asset_observer.rb +6 -0
  137. data/app/observers/page_observer.rb +37 -0
  138. data/app/presenters/entry_presenter.rb +17 -0
  139. data/app/presenters/page_presenter.rb +67 -0
  140. data/app/presenters/presenter.rb +9 -0
  141. data/app/presenters/set_page_presenter.rb +2 -0
  142. data/app/views/admin/admins/index.html.erb +26 -0
  143. data/app/views/admin/admins/show.html.erb +27 -0
  144. data/app/views/admin/assets/index.html.erb +1 -0
  145. data/app/views/admin/auth/passwords/edit.html.erb +20 -0
  146. data/app/views/admin/auth/passwords/new.html.erb +14 -0
  147. data/app/views/admin/auth/sessions/_form.html.erb +35 -0
  148. data/app/views/admin/auth/sessions/new.html.erb +14 -0
  149. data/app/views/admin/entries/index.html.erb +32 -0
  150. data/app/views/admin/pages/_breadcrumbs.html.erb +32 -0
  151. data/app/views/admin/pages/_slices.html.erb +27 -0
  152. data/app/views/admin/pages/new.html.erb +14 -0
  153. data/app/views/admin/pages/show.html.erb +50 -0
  154. data/app/views/admin/shared/_asset_storage.html.erb +17 -0
  155. data/app/views/admin/shared/_custom_links.html.erb +1 -0
  156. data/app/views/admin/shared/_custom_navigation.html.erb +1 -0
  157. data/app/views/admin/shared/_navigation.html.erb +5 -0
  158. data/app/views/admin/site_maps/_page_li.html.erb +20 -0
  159. data/app/views/admin/site_maps/_set_page_li.html.erb +23 -0
  160. data/app/views/admin/site_maps/index.html.erb +29 -0
  161. data/app/views/admin/snippets/form.html.erb +12 -0
  162. data/app/views/admin/snippets/index.html.erb +20 -0
  163. data/app/views/admin/snippets/update.html.erb +0 -0
  164. data/app/views/layouts/admin.html.erb +72 -0
  165. data/lib/ext/file_store_cache.rb +18 -0
  166. data/lib/generators/humans/USAGE +8 -0
  167. data/lib/generators/humans/humans_generator.rb +10 -0
  168. data/lib/generators/humans/templates/humans.txt +6 -0
  169. data/lib/generators/slice/USAGE +28 -0
  170. data/lib/generators/slice/slice_generator.rb +123 -0
  171. data/lib/generators/slice/templates/main_fields.hbs +11 -0
  172. data/lib/generators/slice/templates/meta_fields.hbs +11 -0
  173. data/lib/generators/slice/templates/page.rb +19 -0
  174. data/lib/generators/slice/templates/presenter.rb +53 -0
  175. data/lib/generators/slice/templates/set.html.erb +8 -0
  176. data/lib/generators/slice/templates/set_slice.rb +14 -0
  177. data/lib/generators/slice/templates/set_slice_fields.hbs +5 -0
  178. data/lib/generators/slice/templates/show.html.erb +48 -0
  179. data/lib/generators/slice/templates/show_slice.rb +20 -0
  180. data/lib/generators/slice/templates/slice.rb +58 -0
  181. data/lib/generators/slice/templates/slice_fields.hbs +74 -0
  182. data/lib/generators/templates/slices.rb +211 -0
  183. data/lib/mongo_search.rb +84 -0
  184. data/lib/paperclip_validator.rb +5 -0
  185. data/lib/rack_utf8_fix.rb +10 -0
  186. data/lib/sRGB.icc +0 -0
  187. data/lib/set_link_renderer.rb +31 -0
  188. data/lib/slices.rb +68 -0
  189. data/lib/slices/asset/maker.rb +55 -0
  190. data/lib/slices/asset/rename.rb +67 -0
  191. data/lib/slices/available_slices.rb +43 -0
  192. data/lib/slices/cms_form_builder.rb +42 -0
  193. data/lib/slices/config.rb +93 -0
  194. data/lib/slices/container_parser.rb +70 -0
  195. data/lib/slices/generator_macros.rb +36 -0
  196. data/lib/slices/has_attachments.rb +111 -0
  197. data/lib/slices/has_slices.rb +88 -0
  198. data/lib/slices/i18n.rb +6 -0
  199. data/lib/slices/i18n/backend.rb +32 -0
  200. data/lib/slices/i18n_backend.rb +24 -0
  201. data/lib/slices/paperclip.rb +13 -0
  202. data/lib/slices/position_helper.rb +98 -0
  203. data/lib/slices/renderer.rb +52 -0
  204. data/lib/slices/slices_engine.rb +51 -0
  205. data/lib/slices/split_date_time_field.rb +14 -0
  206. data/lib/slices/tasks/assets.rake +35 -0
  207. data/lib/slices/tasks/db.rake +50 -0
  208. data/lib/slices/tasks/seeds.rake +93 -0
  209. data/lib/slices/tasks/validate.rake +62 -0
  210. data/lib/slices/tree.rb +306 -0
  211. data/lib/slices/version.rb +4 -0
  212. data/lib/slices/will_paginate.rb +12 -0
  213. data/lib/slices/will_paginate_mongoid.rb +45 -0
  214. data/lib/standard_tree.rb +193 -0
  215. metadata +483 -0
@@ -0,0 +1,52 @@
1
+ module Slices
2
+ class Renderer
3
+ include ActiveSupport::Benchmarkable
4
+
5
+ def initialize(options = {})
6
+ options.assert_valid_keys(:controller, :current_page, :slices, :params)
7
+
8
+ @controller = options[:controller] || SlicesController.new
9
+ @current_page = options[:current_page]
10
+ @slices = options[:slices]
11
+ @params = options[:params] || {}
12
+ end
13
+
14
+ def render_slices(slices = @slices)
15
+ [].tap do |rendered|
16
+ slices.each do |slice|
17
+ benchmark "Prepared #{slice.reference}" do
18
+ slice.setup(setup_options)
19
+ slice.prepare(@params)
20
+ end
21
+ rendered << slice.render
22
+ end
23
+ end.join("\n").html_safe
24
+ end
25
+
26
+ def render_container(container, slices = @slices)
27
+ render_slices slices.where(container: container)
28
+ end
29
+
30
+ def render_to_string(*args)
31
+ @controller.render_to_string(*args)
32
+ end
33
+
34
+ def fragment_exist?(cache_key)
35
+ @controller.fragment_exist?(cache_key)
36
+ end
37
+
38
+ private
39
+
40
+ def setup_options
41
+ {
42
+ renderer: self,
43
+ current_page: @current_page
44
+ }
45
+ end
46
+
47
+ def logger
48
+ Rails.logger
49
+ end
50
+ end
51
+ end
52
+
@@ -0,0 +1,51 @@
1
+ module Slices
2
+ class Engine < Rails::Engine; end
3
+
4
+ def self.autoload_slices(app, root)
5
+ slice_dirs = Dir.glob(File.expand_path('app/slices/*', root))
6
+ app.config.autoload_paths.push(*slice_dirs.select { |f| File.directory?(f) })
7
+ end
8
+
9
+ # We need to add the app's own app/slices/* directories to the load
10
+ # path so that slices created within the application are automatically
11
+ # detected. This means that we need a Railtie as well as an engine, as
12
+ # an engine's initializer can't add autoload_paths that aren't scoped
13
+ # within the engine itself (for some stupid reason).
14
+ class Railtie < Rails::Railtie
15
+ initializer :autoload_slices, before: :set_autoload_paths do |app|
16
+ Slices.autoload_slices(app, Slices.gem_path) if Slices.test_environment?
17
+ Slices.autoload_slices(app, Rails.root)
18
+ end
19
+
20
+ initializer :active_mongoid_observers do
21
+ config.mongoid.observers.concat [:page_observer, :asset_observer]
22
+ end
23
+
24
+ initializer :load_config_initializers, after: :config_initializers do |app|
25
+ Dir.glob('config/initializers/*.rb').each do |initializer|
26
+ load(initializer)
27
+ end
28
+ end
29
+
30
+ initializer :slices_will_paginate_active_view, after: 'will_paginate.action_view' do
31
+ load Slices.gem_path + '/lib/set_link_renderer.rb'
32
+ end
33
+
34
+ initializer :slices_precompile_hook do |app|
35
+ app.config.assets.precompile += %w(slices/slices.css slices/slices.js)
36
+ end
37
+
38
+ config.after_initialize do
39
+ Slices.load_slice_classes_into_object_space(Rails.root)
40
+ end
41
+
42
+ rake_tasks do
43
+ root = Slices.gem_path + '/lib/'
44
+
45
+ Dir[root + 'slices/tasks/*.rake'].each do |taskfile|
46
+ load taskfile.sub root, ''
47
+ end
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,14 @@
1
+ module Slices #:nodoc:
2
+ module SplitDateTimeField #:nodoc:
3
+ extend ActiveSupport::Concern #:nodoc:
4
+
5
+ module ClassMethods #:nodoc:
6
+
7
+ def split_date_time_field(name)
8
+ raise Error, 'Split date time is depreciated use dateField instead'
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
+
@@ -0,0 +1,35 @@
1
+ module Slices
2
+ module Tasks
3
+
4
+ def self.remove_all_unused_styles
5
+ Asset.all.each do |asset|
6
+ remove_unused_styles(asset)
7
+ end
8
+ end
9
+
10
+ def self.remove_unused_styles(asset)
11
+ asset.remove_attribute :dimensions
12
+
13
+ used_styles = asset.file_dimensions.symbolize_keys.keys << :original
14
+ styles_to_clear = asset.file.styles.keys - used_styles
15
+ asset.file.clear(*styles_to_clear)
16
+ asset.save
17
+ end
18
+ end
19
+ end
20
+
21
+ namespace :slices do
22
+ namespace :assets do
23
+
24
+ desc "Mark assets for reprocessing"
25
+ task reprocess: :environment do
26
+ Asset.update_all(file_dimensions: {})
27
+ end
28
+
29
+ desc "Remove unused asset styles"
30
+ task remove_unused_styles: :environment do
31
+ Slices::Tasks.remove_all_unused_styles
32
+ end
33
+ end
34
+ end
35
+
@@ -0,0 +1,50 @@
1
+ module Slices
2
+ module Tasks
3
+ def self.create_indexes
4
+ [
5
+ Slices.gem_path + '/app/models/**/*.rb',
6
+ Rails.root.join('app/slices/**/*.rb')
7
+ ].each do |pattern|
8
+ Dir.glob(pattern).each do |file|
9
+ model = determine_model(file)
10
+ if model
11
+ model.create_indexes
12
+ Logger.new($stdout).info("Generated indexes for #{model}")
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ def self.determine_model(file)
19
+ segments = file[0 .. -4].split('/')
20
+ start = if segments.include?('slices')
21
+ segments.rindex('slices') + 2
22
+ else
23
+ segments.rindex('models') + 1
24
+ end
25
+ model_path = segments[start .. -1]
26
+ klass = model_path.map { |path| path.camelize }.join('::').constantize
27
+ if klass.ancestors.include?(::Mongoid::Document) && !klass.embedded
28
+ return klass
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ namespace :slices do
35
+ namespace :db do
36
+ desc "Create indexes for slices models"
37
+ task create_indexes: "db:mongoid:create_indexes" do
38
+ Slices::Tasks.create_indexes
39
+ end
40
+ end
41
+
42
+ namespace :migrate do
43
+ desc "Rename description field to meta_description"
44
+ task meta_description: :environment do
45
+ Page.collection.update({},
46
+ {'$rename' => {'description' => 'meta_description'}},
47
+ multi: true)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,93 @@
1
+ require 'highline/import'
2
+
3
+ module Slices
4
+ module Tasks
5
+ class Exists < RuntimeError; end
6
+
7
+ def self.with_message(text)
8
+ print "#{text} ... "
9
+ yield
10
+ puts "ok"
11
+ rescue Exists
12
+ puts "(exists)"
13
+ end
14
+
15
+ def self.make_virtual(role, name, status)
16
+ with_message("Creating #{status} page") do
17
+ if Page.where(role: role).count > 0
18
+ raise Exists
19
+ end
20
+ Page.make(role: role, name: name, active: true)
21
+ end
22
+ end
23
+
24
+ def self.make_home
25
+ with_message("Creating home page") do
26
+ raise Exists if Page.where(path: '/').any?
27
+ Page.make(name: 'Home',
28
+ permalink: '',
29
+ show_in_nav: true,
30
+ active: true)
31
+ end
32
+ end
33
+
34
+ def self.make_errors
35
+ make_virtual('not_found', 'Page not found', 404)
36
+ make_virtual('error', 'Whoops', 500)
37
+ end
38
+
39
+ def self.make_admin
40
+ if Admin.exists?
41
+ say "Creating admin user ... (exists)"
42
+ return
43
+ end
44
+
45
+ say("Creating admin user ...")
46
+
47
+ admin = Admin.create!(
48
+ name: ask('Enter a name: '),
49
+ email: ask('Enter an email address: '),
50
+ password: ask('Enter a password: ') { |q| q.echo = false }
51
+ )
52
+ admin.super_user = true
53
+ admin.save!
54
+
55
+ say("... ok")
56
+ end
57
+
58
+ def self.make_all
59
+ make_home
60
+ make_errors
61
+ make_admin
62
+ end
63
+ end
64
+ end
65
+
66
+ namespace :slices do
67
+ namespace :seed do
68
+ task :foo do
69
+ puts "Bar"
70
+ end
71
+
72
+ desc "Create the home page"
73
+ task :home => :environment do
74
+ Slices::Tasks.make_home
75
+ end
76
+
77
+ desc "Create 404 and 500 pages"
78
+ task :errors => :home do
79
+ Slices::Tasks.make_errors
80
+ end
81
+
82
+ desc "With Associates admin user"
83
+ task :admin => :environment do
84
+ Slices::Tasks.make_admin
85
+ end
86
+ end
87
+
88
+ desc "Seed home, errors and admin user"
89
+ task :seed => :environment do
90
+ Slices::Tasks.make_all
91
+ end
92
+ end
93
+
@@ -0,0 +1,62 @@
1
+ require 'open-uri'
2
+
3
+ module Slices
4
+ module Tasks
5
+
6
+ class PageHtml
7
+ attr_accessor :body
8
+
9
+ def initialize(host, path)
10
+ document = open('http://' + host + path)
11
+ self.body = document.present? ? document.read : nil
12
+ self.body.gsub!(/\?[0-9]{10}/, '') # remove cache busting
13
+ rescue OpenURI::HTTPError
14
+ end
15
+
16
+ def ==(other)
17
+ body == other.body
18
+ end
19
+
20
+ def temp_path
21
+ write_to_tempfile unless @tempfile
22
+ @tempfile.path
23
+ end
24
+
25
+ private
26
+
27
+ def write_to_tempfile
28
+ @tempfile = Tempfile.new('html').tap do |file|
29
+ file << body
30
+ file.flush
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+ def self.validate(local, remote)
37
+ hosts = [local, remote]
38
+ Page.all.each do |page|
39
+ puts page.path
40
+ a, b = hosts.map do |host|
41
+ PageHtml.new(host, page.path)
42
+ end
43
+ if a != b
44
+ puts `diff #{a.temp_path} #{b.temp_path}`
45
+ puts ''
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+ end
52
+
53
+ namespace :slices do
54
+
55
+ desc "Validate localhost is the same is live"
56
+ task valdiate: :environment do
57
+ remote = ENV['HOST']
58
+ local = ENV.include?('LOCAL') ? ENV['LOCAL'] : 'localhost:3000'
59
+ Slices::Tasks.validate(remote, local)
60
+ end
61
+ end
62
+
@@ -0,0 +1,306 @@
1
+ module Slices
2
+
3
+ # Containers the tree behaviour.
4
+ module Tree
5
+ extend ActiveSupport::Concern
6
+
7
+ # @!attribute [rw] path
8
+ # The path for the page, this is a unique identifier and directly maps to
9
+ # the page's url.
10
+ #
11
+ # @return [String]
12
+
13
+ # @!attribute [rw] external_url
14
+ # An optional url override for the page, if this is set then this page will
15
+ # link to the url in any navigation block. Usefull when linking to a
16
+ # different website.
17
+ #
18
+ # @return [String]
19
+
20
+ # @!attribute [rw] position
21
+ # The page's position amongst it's siblings
22
+ #
23
+ # @return [Integer]
24
+
25
+ # @!attribute [rw] show_in_nav
26
+ # If the +page+ should be shown in any navigation block.
27
+ #
28
+ # @return [Boolean]
29
+
30
+ # @!method minimal
31
+ # A scope with the minmial attributes needed to render navigaiton
32
+ #
33
+ # @!scope class
34
+ # @return [Mongoid::Criteria]
35
+ #
36
+ included do
37
+ field :path
38
+ field :external_url, type: String, default: ''
39
+ field :position, type: Integer
40
+ field :show_in_nav, type: Boolean, default: false
41
+
42
+ scope :minimal, only: %w(page_id path external_url name has_content)
43
+ index :path
44
+
45
+ belongs_to :page
46
+
47
+ attr_accessor :parent_id
48
+
49
+ before_create :ensure_path_unique
50
+ before_save :ensure_path_unique
51
+ end
52
+
53
+ module ClassMethods
54
+
55
+ # Get the home page.
56
+ #
57
+ # @return [Page]
58
+ #
59
+ def home
60
+ find_by_path('/')
61
+ end
62
+
63
+ # Finds a page by it's path, a {Page::NotFound} is raised if the page
64
+ # can't be found.
65
+ #
66
+ # @return [Page]
67
+ #
68
+ def find_by_path(path)
69
+ first(conditions: { path: path }) || (raise Page::NotFound.new(path))
70
+ end
71
+ alias :f :find_by_path
72
+
73
+ # Generate a +path+ from attributes.
74
+ #
75
+ # @param [Hash] attributes
76
+ # @option attributes [String] :parent_path Path to parent page
77
+ # @option attributes [Page] :parent Parent page
78
+ # @option attributes [String] :name Page name
79
+ # @option attributes [String] :permalink Permalink
80
+ # @param [Page] parent
81
+ # @return [String]
82
+ #
83
+ def path_from_attributes(attributes, parent = nil)
84
+ parent_path = if parent
85
+ parent.path
86
+ else
87
+ attributes[:parent_path] || attributes[:parent].try(:path).to_s
88
+ end
89
+ permalink = attributes.delete(:permalink) || attributes[:name]
90
+ File.join(parent_path, permalink.to_url)
91
+ end
92
+ end
93
+
94
+ # Get the parent page.
95
+ #
96
+ # @return [Page]
97
+ def parent
98
+ page
99
+ end
100
+
101
+ # Set the parent.
102
+ #
103
+ # @param [Page] page The new parent
104
+ # @return [Page]
105
+ #
106
+ def parent=(page)
107
+ self.page = page
108
+ end
109
+
110
+ # Is this page the home page
111
+ #
112
+ def home?
113
+ path == '/'
114
+ end
115
+
116
+ # A list of pages descended from this page ordered by +position+
117
+ #
118
+ # @return [Mongoid::Criteria]
119
+ #
120
+ def children
121
+ Page.where(page_id: self.id).ascending(:position)
122
+ end
123
+
124
+ # A list of the page's ancestors
125
+ #
126
+ # @return [Array<Page>]
127
+ #
128
+ def ancestors
129
+ ancestors = []
130
+ parent_page = parent
131
+ while parent_page
132
+ ancestors << parent_page
133
+ parent_page = parent_page.parent
134
+ end
135
+ ancestors
136
+ end
137
+
138
+ # Is the current page a descendant of +page+
139
+ #
140
+ # @param [Page] page
141
+ #
142
+ def descended_from?(page)
143
+ ancestors.include?(page)
144
+ end
145
+
146
+ # The navigaiton path for a page, the navigation differers from +path+
147
+ # in a few important ways. If #external_url is set then it is returned. If
148
+ # the page has no content and children then the path of it's first child is
149
+ # returned
150
+ #
151
+ # @return [String]
152
+ #
153
+ def navigation_path
154
+ if external_url?
155
+ external_url
156
+ elsif home? || has_content?
157
+ path
158
+ else
159
+ navigable_children.first ? navigable_children.first.path : path
160
+ end
161
+ end
162
+
163
+ # A list of child pages which have both +active+ and +show_in_nav+ set to
164
+ # true. Only the attributes needed to render #navigation are returned,
165
+ # these are: +page_id+, +path+, +external_url+, +name+ and +has_content+.
166
+ #
167
+ # @return [Mongoid::Criteria]
168
+ #
169
+ def navigable_children
170
+ page_children.minimal.where(active: true, show_in_nav: true)
171
+ end
172
+
173
+ # The parent pages's navigable children, including this page
174
+ #
175
+ # @return [Mongoid::Criteria]
176
+ #
177
+ def peers
178
+ parent.nil? ? [] : parent.navigable_children
179
+ end
180
+
181
+ # An array of the parent page's children excluding this page
182
+ #
183
+ # @return [Mongoid::Criteria]
184
+ #
185
+ def siblings
186
+ parent.nil? ? [] : parent.children.where(:_id.nin => [id])
187
+ end
188
+
189
+ # An array of the parent page's children excluding this page, ordered by
190
+ # position
191
+ #
192
+ # @return [Mongoid::Criteria]
193
+ #
194
+ def siblings_by_position
195
+ siblings.ascending(:position)
196
+ end
197
+
198
+ # Is this page the first child of its parent
199
+ #
200
+ def first_sibling?
201
+ parent.nil? ? false : peers.first == self
202
+ end
203
+
204
+ # Is this page the last child of its parent
205
+ #
206
+ def last_sibling?
207
+ parent.nil? ? false : peers.last == self
208
+ end
209
+
210
+ # The previous page(of the parent's active children), or nil
211
+ # if this is the first page.
212
+ #
213
+ # @return [Page]
214
+ #
215
+ def previous_sibling
216
+ index_in_peers < 1 ? nil : peers[index_in_peers - 1]
217
+ end
218
+
219
+ # The next page(of the parent's active children), or nil
220
+ # if this is the last page.
221
+ #
222
+ # @return [Page]
223
+ #
224
+ def next_sibling
225
+ peers[index_in_peers + 1]
226
+ end
227
+
228
+ # A list of children which are either {Page} or {SetPage}
229
+ #
230
+ # @return [Mongoid::Criteria]
231
+ #
232
+ def page_children
233
+ children.where(:_type.in => [nil, 'Page', 'SetPage'])
234
+ end
235
+
236
+ # A list of children which are neither {Page} or {SetPage}
237
+ #
238
+ # @return [Mongoid::Criteria]
239
+ #
240
+ def entry_children
241
+ children.where(:_type.nin => ['Page', 'SetPage'])
242
+ end
243
+
244
+ # Recusivly decend from this page and update each descendants path,
245
+ # useful when changing permalinks.
246
+ #
247
+ # @return [Mongoid::Criteria]
248
+ #
249
+ def update_path_for_children
250
+ children.each do |child|
251
+ child.path = child.generate_path
252
+ child.save!
253
+ child.update_path_for_children
254
+ end
255
+ end
256
+
257
+ # Generate a new +path+ for the page, using it's parent's path and the
258
+ # permalink.
259
+ #
260
+ # @return [String]
261
+ #
262
+ def generate_path
263
+ unless home?
264
+ File.join(parent.path, permalink)
265
+ else
266
+ '/'
267
+ end
268
+ end
269
+
270
+ # The permalink for the page
271
+ #
272
+ # @return [String]
273
+ #
274
+ def permalink
275
+ if path
276
+ File.basename(path)
277
+ else
278
+ name.to_url
279
+ end
280
+ end
281
+
282
+ private
283
+
284
+ # Make sure that a page's path is unique amoungst it's siblings, if there is
285
+ # a dupliacation then +-1+ is appended
286
+ #
287
+ # @return [void]
288
+ #
289
+ def ensure_path_unique
290
+ if parent.present? && path_changed?
291
+ matching_paths = siblings.where(path: /#{path}(-\d+)?$/)
292
+ if matching_paths.any?
293
+ self.path << "-#{matching_paths.length}"
294
+ end
295
+ end
296
+ end
297
+
298
+ # The page's position amoung it's peers
299
+ #
300
+ # @return [Integer]
301
+ #
302
+ def index_in_peers
303
+ parent.nil? ? 0 : peers.only(:id).entries.index(self)
304
+ end
305
+ end
306
+ end