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,195 @@
1
+ # This module provices helpers for rendering common navigation from page structure
2
+ #
3
+ # primary_navigation
4
+ # # => <ul id="primary_navigation">
5
+ # # => <li class="first active nav-home"><a href="/">Home page</a></li>
6
+ # # => <li class="nav-about"><a href="/about">About us</a></li>
7
+ # # => <li class="last nav-blog"><a href="/blog">Blog</a></li>
8
+ # # => </ul>
9
+ #
10
+ module NavigationHelper
11
+
12
+ # primary_navigation
13
+ # # => <ul id="primary_navigation">
14
+ # # => <li class="first active nav-home"><a href="/">Home page</a></li>
15
+ # # => <li class="nav-about"><a href="/about">About us</a></li>
16
+ # # => <li class="nav-parent"><a href="/parent">Parent</a></li>
17
+ # # => <li class="nav-aunt"><a href="/aunt">Aunt</a></li>
18
+ # # => <li class="nav-uncle"><a href="/uncle">Uncle</a></li>
19
+ # # => <li class="last nav-blog"><a href="/blog">Blog</a></li>
20
+ # # => </ul>
21
+ #
22
+ # @param [String] id Dom id of the surronding ul element
23
+ # @return [String]
24
+ #
25
+ def primary_navigation(id = 'primary_navigation')
26
+ cache 'page/navigation/' + id + '/' + @page.id.to_s do
27
+ benchmark 'Rendered primary_navigation' do
28
+ nav = navigation(page: Page.home, depth: 1, id: id)
29
+ safe_concat nav
30
+ ''
31
+ end
32
+ end
33
+ end
34
+
35
+ # Render the secondary navigation if the current page isn't already rendered
36
+ # in the primary navigation.
37
+ #
38
+ # secondary_navigaiton :depth => 10
39
+ # # => <ul id="secondary_navigation">
40
+ # # => <li class="first nav-parent-child"><a href="/parent/child">Child</a>
41
+ # # => <ul>
42
+ # # => <li class="first last nav-parent-child-grand-child"><a href="/parent/child/grand-child">Grand child</a></li>
43
+ # # => </ul>
44
+ # # => </li>
45
+ # # => <li class="nav-parent-sibling"><a href="/parent/sibling">Sibling</a></li>
46
+ # # => <li class="last nav-parent-youngest"><a href="/parent/youngest">Youngest</a></li>
47
+ # # => </ul>
48
+ #
49
+ # @param [Hash] options
50
+ # @option options [Integer] :depth (1) Number of levels to render
51
+ # @option options [Integer] :id ('secondary_navigation') Id of surrounding UL
52
+ # @return [String]
53
+ #
54
+ def secondary_navigation(options = {})
55
+ cache 'page/navigation/secondary/' + @page.id.to_s do
56
+ benchmark 'Rendered secondary_navigation' do
57
+ secondary_ancestors = (@page.ancestors[-2] || @page).navigable_children
58
+ nav = if secondary_ancestors.empty? || @page.home?
59
+ ''
60
+ else
61
+ page = secondary_ancestors.first
62
+ navigation(options.reverse_merge(page: page, id: 'secondary_navigation'))
63
+ end
64
+ safe_concat nav
65
+ ''
66
+ end
67
+ end
68
+ end
69
+
70
+ # Use the page structure to render nested navigation, each visible page is
71
+ # rendered in a +li+ element with child pages in a nested +ul+
72
+ #
73
+ # The first +li+ in a +ul+ has the class +first+, the last +li+ in a +ul+ has
74
+ # the class +last+ and the current page has the class +active+.
75
+ #
76
+ # Each +li+ has a class identifier generated from it's path and prefixed with
77
+ # +nav+, for example a page with path +/parent/child+ would have the class
78
+ # +nav-parent-child+. This identifier is a class is to avoid duplicate +ids+
79
+ # if multiple navigation elements are used on a page.
80
+ #
81
+ # The home page is assumed to be at the same level as it's children.
82
+ #
83
+ # navigation depth: 4, page: Page.home, id: 'navigation'
84
+ # # => <ul id="navigation">
85
+ # # => <li class="first nav-home"><a href="/">Home page</a></li>
86
+ # # => <li class="nav-about"><a href="/about">About us</a></li>
87
+ # # => <li class="active nav-parent">
88
+ # # => <a href="/parent">Parent</a>
89
+ # # => <ul>
90
+ # # => <li class="first nav-parent-child">
91
+ # # => <a href="/parent/child">Child</a>
92
+ # # => <ul>
93
+ # # => <li class="first last nav-parent-child-grand-child"><a href="/parent/child/grand-child">Grand child</a></li>
94
+ # # => </ul>
95
+ # # => </li>
96
+ # # => <li class="nav-parent-sibling"><a href="/parent/sibling">Sibling</a></li>
97
+ # # => <li class="last nav-parent-youngest"><a href="/parent/youngest">Youngest</a></li>
98
+ # # => </ul>
99
+ # # => </li>
100
+ # # => <li class="nav-aunt"><a href="/aunt">Aunt</a></li>
101
+ # # => <li class="nav-uncle"><a href="/uncle">Uncle</a></li>
102
+ # # => <li class="last nav-blog"><a href="/blog">Blog</a></li>
103
+ # # => </ul>
104
+ #
105
+ # navigation depth: 1, page: Page.home
106
+ # # => <ul>
107
+ # # => <li class="first nav-home"><a href="/">Home page</a></li>
108
+ # # => <li class="nav-about"><a href="/about">About us</a></li>
109
+ # # => <li class="active nav-parent"><a href="/parent">Parent</a></li>
110
+ # # => <li class="nav-aunt"><a href="/aunt">Aunt</a></li>
111
+ # # => <li class="nav-uncle"><a href="/uncle">Uncle</a></li>
112
+ # # => <li class="last nav-blog"><a href="/blog">Blog</a></li>
113
+ # # => </ul>
114
+ #
115
+ # @param [Hash] options
116
+ # @option options [Integer] :depth (1) Number of levels to render
117
+ # @option options [Integer] :page (current_page) Page to start rendering
118
+ # @option options [Integer] :id Id of surrounding UL
119
+ # @option options [Integer] :class Class of surrounding UL
120
+ # @return [String]
121
+ #
122
+ def navigation(options = {})
123
+ options.reverse_merge!( page: @page, depth: 1 )
124
+ page = options[:page]
125
+ children = page.home? ? page.navigable_children : page.parent.navigable_children
126
+ html = (is_home_and_shown_in_nav?(page) ? nav_link(Page.home) : '').html_safe
127
+ html << list_items(children, options[:depth])
128
+ content_tag(:ul, html, options.slice(:id, :class))
129
+ end
130
+
131
+ # Link to a +page+, using the page name as the link name.
132
+ #
133
+ # link_to Page.find_by_path('/parent')
134
+ # # => <a href="/parent">Parent</a>
135
+ #
136
+ # @param [Page] page Page to link to
137
+ # @return [String]
138
+ #
139
+ def link_to_page(page)
140
+ link_to(page.name, page.navigation_path)
141
+ end
142
+
143
+ private
144
+
145
+ def list_items(pages, depth)
146
+ depth -= 1
147
+ pages.inject(''.html_safe) do |html, page|
148
+ html << nav_link(page, depth)
149
+ end
150
+ end
151
+
152
+ def nav_link(page, depth = 0)
153
+ content_tag(:li, class: nav_list_item_class(page)) do
154
+ html = link_to_page(page)
155
+ if (depth > 0) && page.navigable_children.any?
156
+ html << content_tag(:ul, list_items(page.navigable_children, depth))
157
+ end
158
+ html
159
+ end + "\n"
160
+ end
161
+
162
+ def is_home_and_shown_in_nav?(page)
163
+ page.home? && page.show_in_nav?
164
+ end
165
+
166
+ def is_first_sibling?(page)
167
+ if page.home? || is_home_and_shown_in_nav?(page.parent)
168
+ page.home?
169
+ else
170
+ page.first_sibling?
171
+ end
172
+ end
173
+
174
+ def is_active_sibling?(page)
175
+ if page.home?
176
+ page == @page
177
+ else
178
+ (page == @page) || @page.descended_from?(page)
179
+ end
180
+ end
181
+
182
+ def nav_list_item_class(page)
183
+ classes = []
184
+ classes << 'first' if is_first_sibling?(page)
185
+ classes << 'active' if is_active_sibling?(page)
186
+ classes << 'last' if page.last_sibling?
187
+ classes << nav_list_identifier(page)
188
+ classes.empty? ? nil : classes.join(' ')
189
+ end
190
+
191
+ def nav_list_identifier(page)
192
+ page.home? ? 'nav-home' : ('nav' + page.path.gsub('/', '-'))
193
+ end
194
+ end
195
+
@@ -0,0 +1,119 @@
1
+ # This module provides helper methods rendering pages and slices.
2
+ module PagesHelper
3
+ include NavigationHelper
4
+ include AssetsHelper
5
+
6
+ # Defines and renders a container in a layout.
7
+ #
8
+ # For example adding the following to a layout will create two containers called
9
+ # container_one and container_two
10
+ #
11
+ # <article class="container_one">
12
+ # <%= container "container_one" %>
13
+ # </article>
14
+ # <aside class="container_two">
15
+ # <%= container "container_two" %>
16
+ # </aside>
17
+ #
18
+ # @param [String] container Name of container
19
+ # @return [String] Contents of rendered slices in container
20
+ #
21
+ def container(container, options = {})
22
+ if @slice_renderer.present?
23
+ @slice_renderer.render_container(container)
24
+ end
25
+ end
26
+
27
+ # Returns the text with all the Textile[http://www.textism.com/tools/textile] codes turned into HTML tags.
28
+ #
29
+ # @param [String] text Text to be textilized
30
+ # @return [String] Textilized string
31
+ #
32
+ def textilize(text)
33
+ unless text.blank?
34
+ red_cloth = RedCloth.new(text)
35
+ red_cloth.no_span_caps = true
36
+ red_cloth.to_html.html_safe
37
+ end
38
+ end
39
+
40
+ # Converts markdown to html.
41
+ #
42
+ # @param [String] text
43
+ # @return [String]
44
+ #
45
+ def markdown(text)
46
+ return if text.blank?
47
+ renderer = Redcarpet::Render::HTML.new(hard_wrap: true)
48
+ parser = Redcarpet::Markdown.new(renderer)
49
+ parser.render(text).html_safe
50
+ end
51
+
52
+ # Quick link to edit the current page in the CMS
53
+ #
54
+ # @return [String] Edit in CMS link
55
+ #
56
+ def edit_in_cms
57
+ if admin_signed_in?
58
+ links = []
59
+ links << link_to("Edit #{@page.class.to_s.underscore.humanize} in CMS", admin_page_path(@page), target: '_blank')
60
+ links << link_to('Edit template in CMS', admin_page_path(@page.parent, entries: 1), target: '_blank') if @page.entry?
61
+ content = links.collect {|l| content_tag(:p, l) }.join.html_safe
62
+
63
+ content_tag(:div, content, id: 'edit_in_cms')
64
+ end
65
+ end
66
+
67
+ # Translates the key and return an empty string if there is no translation.
68
+ #
69
+ #
70
+ # @param [String] key Translation key
71
+ # @param [Hash] options
72
+ # @return [String] Translated key
73
+ #
74
+ def if_t(key, options = {})
75
+ translate(key, options) || ''
76
+ end
77
+
78
+ # Asynchronous Google Analytics Tracking Code. Only added in production when the
79
+ # user is not signed into the CMS.
80
+ #
81
+ # google_analytics_tracking_code('ABC132')
82
+ #
83
+ # To track multiple accounts use the following
84
+ #
85
+ # google_analytics_tracking_code('ABC132', 'DEF546')
86
+ #
87
+ # @param [Array, String] web_property_ids Web property ID(s)
88
+ # @return [String] GA embed code
89
+ #
90
+ def google_analytics_tracking_code(*web_property_ids)
91
+ if web_property_ids.first.present? && add_tracking_code?
92
+ analytics_que = "['_setAccount', '#{web_property_ids.shift}'], ['_trackPageview'], ['_trackPageLoadTime']"
93
+ web_property_ids.each_with_index do |web_property_id, index|
94
+ account = (index + 98).chr
95
+ analytics_que << ", ['#{account}._setAccount', '#{web_property_id}'], ['#{account}._trackPageview'], ['#{account}._trackPageLoadTime']"
96
+ end
97
+
98
+ javascript_tag <<-JAVASCRIPT
99
+ var _gaq = _gaq || [];
100
+ _gaq.push(#{analytics_que});
101
+ (function() {
102
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
103
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
104
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
105
+ })();
106
+ JAVASCRIPT
107
+ end
108
+ end
109
+
110
+ # If a tracking code should be added to the page. Returns +true+ when the app
111
+ # is in production mode and the user is not signed into the CMS
112
+ #
113
+ # @return [Boolean]
114
+ #
115
+ def add_tracking_code?
116
+ Rails.env.production? && ! admin_signed_in?
117
+ end
118
+ end
119
+
@@ -0,0 +1,34 @@
1
+ class Admin
2
+ include Mongoid::Document
3
+ include MongoSearch::Searchable
4
+
5
+ field :name
6
+ field :super_user, type: Boolean, :default => false
7
+
8
+ text_search_in :name, :email
9
+
10
+ validates_uniqueness_of :email, case_sensitive: false, scope: :site_id
11
+
12
+ validates_presence_of :email
13
+ attr_accessible :email, :password, :password_confirmation, :name
14
+
15
+ devise :database_authenticatable, :recoverable, :rememberable,
16
+ :trackable, :validatable, :omniauthable
17
+
18
+ def self.find_for_domain(domain)
19
+ where(email: /@#{domain}$/).first
20
+ end
21
+
22
+ def super?
23
+ self.super_user == true
24
+ end
25
+
26
+ def as_json(options)
27
+ super(options).tap do |json|
28
+ if current_admin = options[:current_admin]
29
+ json[:current_admin] = current_admin.id == id
30
+ end
31
+ end
32
+ end
33
+ end
34
+
@@ -0,0 +1,211 @@
1
+ class Asset
2
+ include Mongoid::Document
3
+ include Mongoid::Timestamps
4
+ include MongoSearch::Searchable
5
+ include Mongoid::Paperclip
6
+
7
+ IMAGE_REGEX = /(jpg|jpeg|gif|png)/
8
+ NORMALIZED_EXTENSIONS = { ".jpeg" => ".jpg" }
9
+
10
+ validates_with PaperclipValidator
11
+
12
+ field :file_content_type, type: String
13
+ field :file_file_name, type: String
14
+ field :file_file_size, type: Integer
15
+ field :file_fingerprint, type: String
16
+ field :file_dimensions, type: Hash, default: {}
17
+
18
+ field :destroyed_at, type: Time
19
+ field :tags, type: String
20
+ field :page_cache, type: Array, default: []
21
+
22
+ text_search_in :file_file_name, :tags, :search_page_cache
23
+
24
+ has_mongoid_attached_file :file,
25
+ default_style: :admin,
26
+ max_size: 50.megabytes,
27
+ url: '/system/:attachment/:mon_year/:id/:style/:filename',
28
+ path: ':rails_root/public:url',
29
+ only_process: [:admin],
30
+ styles: Slices::Config.asset_styles,
31
+ convert_options: Slices::Config.asset_convert_options
32
+
33
+ before_file_post_process :is_image?
34
+ after_file_post_process :store_dimensions, if: :is_image?
35
+ before_save :reset_file_dimensions!, if: :file_fingerprint_changed?
36
+ before_save :rename_file
37
+
38
+ has_and_belongs_to_many :pages
39
+
40
+ index(
41
+ [
42
+ [ :destroyed_at, Mongo::ASCENDING ],
43
+ [ :created_at, Mongo::DESCENDING ]
44
+ ]
45
+ )
46
+ index :file_fingerprint
47
+ index :_keywords
48
+
49
+ def self.make(*args)
50
+ Slices::Asset::Maker.run(*args)
51
+ end
52
+
53
+ def self.search_for(term = nil)
54
+ if term.present?
55
+ all.ordered_active.text_search term
56
+ else
57
+ all.ordered_active
58
+ end
59
+ end
60
+
61
+ def self.ordered_active
62
+ where(destroyed_at: nil).desc(:created_at)
63
+ end
64
+
65
+ def as_json(options = nil)
66
+ {
67
+ id: id.to_s,
68
+ name: name,
69
+ file_file_name: file_file_name,
70
+ file_file_size: file_file_size,
71
+ file_content_type: file_content_type,
72
+ asset_url: admin_image_url,
73
+ original_url: file.url(:original),
74
+ errors: errors,
75
+ created_at: created_at,
76
+ tags: tags,
77
+ pages: page_cache
78
+ }
79
+ end
80
+
81
+ # Returns the url for the asset style. The style is created if it has not already
82
+ # been generated
83
+ #
84
+ # @param [Symbol] style The asset style is defined in Slice::Config.asset_styles
85
+ def url_for(style)
86
+ reprocess_for(style)
87
+ file.url(style)
88
+ end
89
+
90
+ # Returns the dimensions the asset style. The style is created if it has not already
91
+ # been generated
92
+ #
93
+ # asset.dimensions_for(:main)
94
+ # "100x100"
95
+ #
96
+ # @param [Symbol] style The asset style is defined in Slice::Config.asset_styles
97
+ def dimensions_for(style)
98
+ reprocess_for(style)
99
+ file_dimensions[style.to_s]
100
+ end
101
+
102
+ def reprocess_for(style)
103
+ if file.styles.has_key?(style) && ! file_dimensions.has_key?(style.to_s)
104
+ file.reprocess!(style)
105
+ save
106
+ end
107
+ rescue Errors::NotIdentifiedByImageMagickError, Errno::ENOENT
108
+ end
109
+
110
+ def reprocess_for!(style)
111
+ file.clear(style)
112
+ file_dimensions.delete(style.to_s)
113
+ file.flush_deletes
114
+ reprocess_for(style)
115
+ end
116
+
117
+ def store_dimensions
118
+ file.queued_for_write.each do |style, adapter|
119
+ geometry = Paperclip::Geometry.from_file(adapter)
120
+ self.file_dimensions[style.to_s] = geometry.to_s
121
+ end
122
+ end
123
+
124
+ def is_image?
125
+ file_content_type.present? && file_content_type.match(IMAGE_REGEX).present?
126
+ end
127
+
128
+ def file_extension
129
+ if file_file_name.present?
130
+ file_file_name.split('.').last.downcase
131
+ else
132
+ ''
133
+ end
134
+ end
135
+
136
+ def admin_image_url
137
+ if !new_record? && is_image?
138
+ url_for(:admin)
139
+ end
140
+ end
141
+
142
+ def soft_destroy!
143
+ update_attributes!(destroyed_at: Time.now)
144
+ end
145
+
146
+ def soft_destroyed?
147
+ destroyed_at.present?
148
+ end
149
+
150
+ def soft_restore!
151
+ update_attributes!(destroyed_at: nil)
152
+ end
153
+
154
+ def name
155
+ @new_name || file_file_name
156
+ end
157
+
158
+ def name=(new_name)
159
+ @new_name = new_name
160
+ end
161
+
162
+ def update_page_cache
163
+ self.page_cache = pages.collect do |page|
164
+ { id: page.id, name: page.name, path: page.path }
165
+ end
166
+ end
167
+
168
+ def search_page_cache
169
+ page_cache.inject([]) do |memo, page|
170
+ memo << page['name']
171
+ memo << page['path']
172
+ end.join ' '
173
+ end
174
+
175
+ # Reset all stored file dimensions except for the origin and admin styles,
176
+ # this is used when a new version of an asset is uploaded
177
+ #
178
+ def reset_file_dimensions!
179
+ self.file_dimensions.keys.each do |key|
180
+ next if key == 'original' || key == 'admin'
181
+ self.file_dimensions.delete key
182
+ end
183
+ end
184
+
185
+ private
186
+
187
+ def rename_file
188
+ return if @new_name.blank?
189
+
190
+ new_file_name = sanitize(@new_name)
191
+ Slices::Asset::Rename.run(file, new_file_name)
192
+ file.instance_write(:file_name, new_file_name)
193
+ set_keywords
194
+ @new_name = nil
195
+ end
196
+
197
+ def sanitize(filename)
198
+ file.send(:cleanup_filename, filename).tap do |name|
199
+ if self.file && self.file.original_filename
200
+ extension = normalized_extension
201
+ name << extension unless name.ends_with?(extension)
202
+ end
203
+ end
204
+ end
205
+
206
+ def normalized_extension
207
+ extname = File.extname(file.original_filename).downcase
208
+ NORMALIZED_EXTENSIONS.fetch(extname, extname)
209
+ end
210
+
211
+ end