slices 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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