exo_cms 0.0.1

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 (515) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +34 -0
  4. data/app/assets/images/exo/admin/favicon.ico +0 -0
  5. data/app/assets/images/exo/admin/logo.png +0 -0
  6. data/app/assets/images/exo/admin/logo_mini.png +0 -0
  7. data/app/assets/javascripts/exo/admin/application.js +77 -0
  8. data/app/assets/javascripts/exo/admin/ckeditor_init.js +7 -0
  9. data/app/assets/javascripts/exo/admin/ckeditor_path.js.erb +6 -0
  10. data/app/assets/javascripts/exo/admin/editor_overrides.js.coffee +40 -0
  11. data/app/assets/javascripts/exo/admin/epiceditor.js +2947 -0
  12. data/app/assets/javascripts/exo/admin/responsive-tables.js +67 -0
  13. data/app/assets/javascripts/exo/delayed_foundation.js +7 -0
  14. data/app/assets/stylesheets/exo/admin/application.sass +68 -0
  15. data/app/assets/stylesheets/exo/admin/components/colorize.sass +9 -0
  16. data/app/assets/stylesheets/exo/admin/components/content.sass +28 -0
  17. data/app/assets/stylesheets/exo/admin/components/header.sass +8 -0
  18. data/app/assets/stylesheets/exo/admin/components/sidebar.sass +19 -0
  19. data/app/assets/stylesheets/exo/admin/components/tables.sass +14 -0
  20. data/app/assets/stylesheets/exo/admin/components/wrapper.sass +15 -0
  21. data/app/assets/stylesheets/exo/admin/epiceditor/epic-light.css +12 -0
  22. data/app/assets/stylesheets/exo/admin/epiceditor/epiceditor.css +70 -0
  23. data/app/assets/stylesheets/exo/admin/epiceditor/github.css +368 -0
  24. data/app/assets/stylesheets/exo/admin/foundation_and_overrides.scss +1003 -0
  25. data/app/assets/stylesheets/exo/admin/mercury.css +36 -0
  26. data/app/assets/stylesheets/exo/admin/mercury_overrides.css +17 -0
  27. data/app/assets/stylesheets/exo/admin/responsive-tables.css +38 -0
  28. data/app/assets/stylesheets/exo/foundation_and_overrides copy.scss +993 -0
  29. data/app/controllers/concerns/exo/admin/site_slug_id_filter.rb +25 -0
  30. data/app/controllers/concerns/exo/host_filter.rb +22 -0
  31. data/app/controllers/concerns/exo/route_filter.rb +19 -0
  32. data/app/controllers/exo/admin/application_controller.rb +12 -0
  33. data/app/controllers/exo/admin/assets_controller.rb +41 -0
  34. data/app/controllers/exo/admin/ckeditor_assets_controller.rb +29 -0
  35. data/app/controllers/exo/admin/ckeditor_blocks_controller.rb +23 -0
  36. data/app/controllers/exo/admin/contributors_controller.rb +5 -0
  37. data/app/controllers/exo/admin/items_controller.rb +80 -0
  38. data/app/controllers/exo/admin/pages_controller.rb +23 -0
  39. data/app/controllers/exo/admin/resources_controller.rb +5 -0
  40. data/app/controllers/exo/admin/routes_controller.rb +11 -0
  41. data/app/controllers/exo/admin/sessions_controller.rb +6 -0
  42. data/app/controllers/exo/admin/settings_controller.rb +66 -0
  43. data/app/controllers/exo/admin/site_controller.rb +4 -0
  44. data/app/controllers/exo/application_controller.rb +6 -0
  45. data/app/controllers/exo/gridfs_controller.rb +32 -0
  46. data/app/controllers/exo/pages_controller.rb +24 -0
  47. data/app/exceptions/exo/route/unknow_path_error.rb +13 -0
  48. data/app/exceptions/exo/site/unknow_host_error.rb +12 -0
  49. data/app/helpers/application_helper.rb +2 -0
  50. data/app/helpers/exo/block_helper.rb +21 -0
  51. data/app/helpers/exo/pagination_helper.rb +21 -0
  52. data/app/helpers/exo/resources_helper.rb +65 -0
  53. data/app/models/concerns/exo/document.rb +8 -0
  54. data/app/models/exo/asset.rb +32 -0
  55. data/app/models/exo/block.rb +13 -0
  56. data/app/models/exo/contributor.rb +42 -0
  57. data/app/models/exo/resource/item/abstract_relation.rb +21 -0
  58. data/app/models/exo/resource/item/abstract_value.rb +35 -0
  59. data/app/models/exo/resource/item/asset_value.rb +17 -0
  60. data/app/models/exo/resource/item/belongs_to_value.rb +26 -0
  61. data/app/models/exo/resource/item/has_many_value.rb +24 -0
  62. data/app/models/exo/resource/item/list_value.rb +23 -0
  63. data/app/models/exo/resource/item/markdown_value.rb +52 -0
  64. data/app/models/exo/resource/item/simple_value.rb +15 -0
  65. data/app/models/exo/resource/item.rb +169 -0
  66. data/app/models/exo/resource/item_asset.rb +20 -0
  67. data/app/models/exo/resource/meta_errors.rb +24 -0
  68. data/app/models/exo/resource/meta_field.rb +20 -0
  69. data/app/models/exo/resource/meta_relation.rb +25 -0
  70. data/app/models/exo/resource/meta_value.rb +60 -0
  71. data/app/models/exo/resource.rb +40 -0
  72. data/app/models/exo/route/page.rb +11 -0
  73. data/app/models/exo/route/redirection.rb +8 -0
  74. data/app/models/exo/route.rb +38 -0
  75. data/app/models/exo/service.rb +13 -0
  76. data/app/models/exo/setting.rb +31 -0
  77. data/app/models/exo/site.rb +63 -0
  78. data/app/presenters/exo/item_presenter.rb +19 -0
  79. data/app/presenters/exo/resource_presenter.rb +25 -0
  80. data/app/presenters/exo/route_presenter.rb +33 -0
  81. data/app/presenters/exo/scope_wraper.rb +58 -0
  82. data/app/presenters/exo/site_presenter.rb +35 -0
  83. data/app/resources/templates/application.html.haml +11 -0
  84. data/app/resources/templates/page.html.haml +3 -0
  85. data/app/services/ckeditor_railser.rb +78 -0
  86. data/app/services/exo/config.rb +138 -0
  87. data/app/services/exo/generator.rb +59 -0
  88. data/app/services/exo/item_builder.rb +39 -0
  89. data/app/services/exo/mongoid.rb +1 -0
  90. data/app/services/exo/path_matcher.rb +55 -0
  91. data/app/services/exo/tick.rb +36 -0
  92. data/app/services/exo/view_generator.rb +74 -0
  93. data/app/uploaders/exo/asset_uploader.rb +62 -0
  94. data/app/uploaders/exo/item_asset_uploader.rb +71 -0
  95. data/app/uploaders/exo/upload_versions.rb +58 -0
  96. data/app/views/contributors/confirmations/new.html.haml +9 -0
  97. data/app/views/contributors/mailer/confirmation_instructions.html.haml +4 -0
  98. data/app/views/contributors/mailer/reset_password_instructions.html.haml +6 -0
  99. data/app/views/contributors/mailer/unlock_instructions.html.haml +5 -0
  100. data/app/views/contributors/passwords/edit.html.haml +11 -0
  101. data/app/views/contributors/passwords/new.html.haml +9 -0
  102. data/app/views/contributors/registrations/edit.html.haml +18 -0
  103. data/app/views/contributors/registrations/new.html.haml +10 -0
  104. data/app/views/contributors/sessions/new.html.haml +10 -0
  105. data/app/views/contributors/shared/_links.haml +19 -0
  106. data/app/views/contributors/unlocks/new.html.haml +9 -0
  107. data/app/views/exo/admin/application/_bheader.html.haml +27 -0
  108. data/app/views/exo/admin/application/_editor_header.html.haml +12 -0
  109. data/app/views/exo/admin/application/_header.html.haml +40 -0
  110. data/app/views/exo/admin/application/_meta_errors.html.haml +5 -0
  111. data/app/views/exo/admin/application/_sidebar.html.haml +22 -0
  112. data/app/views/exo/admin/application/_sidenav.html.haml +48 -0
  113. data/app/views/exo/admin/application/fields/_image.html.haml +3 -0
  114. data/app/views/exo/admin/application/fields/_markdown.html.haml +3 -0
  115. data/app/views/exo/admin/assets/_form.html.haml +11 -0
  116. data/app/views/exo/admin/assets/edit.html.haml +7 -0
  117. data/app/views/exo/admin/assets/index.html.haml +15 -0
  118. data/app/views/exo/admin/assets/new.html.haml +5 -0
  119. data/app/views/exo/admin/assets/show.html.haml +14 -0
  120. data/app/views/exo/admin/ckeditor_assets/create.html.erb +8 -0
  121. data/app/views/exo/admin/contributors/index.html.haml +21 -0
  122. data/app/views/exo/admin/contributors/show.html.haml +26 -0
  123. data/app/views/exo/admin/items/_form.html.haml +20 -0
  124. data/app/views/exo/admin/items/edit.html.haml +16 -0
  125. data/app/views/exo/admin/items/new.html.haml +8 -0
  126. data/app/views/exo/admin/items/show.html.haml +44 -0
  127. data/app/views/exo/admin/resources/index.html.haml +13 -0
  128. data/app/views/exo/admin/resources/show.html.haml +36 -0
  129. data/app/views/exo/admin/routes/edit.html.haml +1 -0
  130. data/app/views/exo/admin/routes/index.html.haml +22 -0
  131. data/app/views/exo/admin/routes/show.html.haml +39 -0
  132. data/app/views/exo/admin/settings/_form.html.haml +21 -0
  133. data/app/views/exo/admin/settings/edit.html.haml +22 -0
  134. data/app/views/exo/admin/settings/index.html.haml +33 -0
  135. data/app/views/exo/admin/settings/new.html.haml +11 -0
  136. data/app/views/exo/admin/site/show.html.haml +0 -0
  137. data/app/views/exo/errors/_unknow_host.html.haml +2 -0
  138. data/app/views/exo/errors/_unknow_path.html.haml +2 -0
  139. data/app/views/layouts/application.html.erb +19 -0
  140. data/app/views/layouts/devise.html.haml +18 -0
  141. data/app/views/layouts/exo/admin/application.html.haml +22 -0
  142. data/app/views/layouts/exo/admin/editor.html.haml +11 -0
  143. data/app/views/layouts/exo/error.html.haml +1 -0
  144. data/config/initializers/carrier_wave.rb +35 -0
  145. data/config/initializers/devise.rb +246 -0
  146. data/config/initializers/simple_form_foundation.rb +27 -0
  147. data/config/routes.rb +42 -0
  148. data/lib/exo/engine.rb +7 -0
  149. data/lib/exo/regexp.rb +6 -0
  150. data/lib/exo/version.rb +3 -0
  151. data/lib/exo.rb +30 -0
  152. data/lib/tasks/ckeditor.rake +9 -0
  153. data/lib/tasks/contributor.rake +11 -0
  154. data/lib/tasks/exo_engine_tasks.rake +4 -0
  155. data/lib/tasks/generate.rake +8 -0
  156. data/lib/tasks/migration.rake +13 -0
  157. data/lib/tasks/seed.rake +7 -0
  158. data/spec/dummy/Rakefile +6 -0
  159. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  160. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  161. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  162. data/spec/dummy/app/controllers/exo/admin/customs_controller.rb +7 -0
  163. data/spec/dummy/app/controllers/my_app/site_customs_controller.rb +10 -0
  164. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  165. data/spec/dummy/app/views/a_sierra_theme/a_page.html.haml +1 -0
  166. data/spec/dummy/app/views/a_sierra_theme/a_page_with_block.html.haml +3 -0
  167. data/spec/dummy/app/views/demo/home.html.haml +3 -0
  168. data/spec/dummy/app/views/demo/posts/show.html.haml +3 -0
  169. data/spec/dummy/app/views/exo/admin/customs/index.html.haml +1 -0
  170. data/spec/dummy/app/views/layouts/a_sierra_theme/application.html.erb +14 -0
  171. data/spec/dummy/app/views/layouts/demo/application.html.haml +11 -0
  172. data/spec/dummy/app/views/my_app/site_customs/index.html.haml +1 -0
  173. data/spec/dummy/bin/bundle +3 -0
  174. data/spec/dummy/bin/rails +4 -0
  175. data/spec/dummy/bin/rake +4 -0
  176. data/spec/dummy/config/application.rb +27 -0
  177. data/spec/dummy/config/boot.rb +5 -0
  178. data/spec/dummy/config/database.yml +25 -0
  179. data/spec/dummy/config/environment.rb +5 -0
  180. data/spec/dummy/config/environments/development.rb +29 -0
  181. data/spec/dummy/config/environments/production.rb +80 -0
  182. data/spec/dummy/config/environments/test.rb +36 -0
  183. data/spec/dummy/config/exo/demo.yml +13 -0
  184. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  185. data/spec/dummy/config/initializers/exo.rb +3 -0
  186. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  187. data/spec/dummy/config/initializers/inflections.rb +16 -0
  188. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  189. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  190. data/spec/dummy/config/initializers/session_store.rb +3 -0
  191. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  192. data/spec/dummy/config/locales/en.yml +23 -0
  193. data/spec/dummy/config/mongoid.yml +78 -0
  194. data/spec/dummy/config/routes.rb +14 -0
  195. data/spec/dummy/config.ru +4 -0
  196. data/spec/dummy/log/development.log +16106 -0
  197. data/spec/dummy/log/test.log +0 -0
  198. data/spec/dummy/public/404.html +58 -0
  199. data/spec/dummy/public/422.html +58 -0
  200. data/spec/dummy/public/500.html +57 -0
  201. data/spec/dummy/public/favicon.ico +0 -0
  202. data/spec/dummy/tmp/cache/assets/development/sass/02ac2db4c4233a0290d18f376407059bb8c9d43c/colorize.sassc +0 -0
  203. data/spec/dummy/tmp/cache/assets/development/sass/02ac2db4c4233a0290d18f376407059bb8c9d43c/content.sassc +0 -0
  204. data/spec/dummy/tmp/cache/assets/development/sass/02ac2db4c4233a0290d18f376407059bb8c9d43c/header.sassc +0 -0
  205. data/spec/dummy/tmp/cache/assets/development/sass/02ac2db4c4233a0290d18f376407059bb8c9d43c/sidebar.sassc +0 -0
  206. data/spec/dummy/tmp/cache/assets/development/sass/02ac2db4c4233a0290d18f376407059bb8c9d43c/tables.sassc +0 -0
  207. data/spec/dummy/tmp/cache/assets/development/sass/1117950fc1adf8b31294376d9067eb7feadc7de0/colorize.sassc +0 -0
  208. data/spec/dummy/tmp/cache/assets/development/sass/1117950fc1adf8b31294376d9067eb7feadc7de0/content.sassc +0 -0
  209. data/spec/dummy/tmp/cache/assets/development/sass/1117950fc1adf8b31294376d9067eb7feadc7de0/header.sassc +0 -0
  210. data/spec/dummy/tmp/cache/assets/development/sass/1117950fc1adf8b31294376d9067eb7feadc7de0/sidebar.sassc +0 -0
  211. data/spec/dummy/tmp/cache/assets/development/sass/1117950fc1adf8b31294376d9067eb7feadc7de0/tables.sassc +0 -0
  212. data/spec/dummy/tmp/cache/assets/development/sass/1117950fc1adf8b31294376d9067eb7feadc7de0/wrapper.sassc +0 -0
  213. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_accordion.scssc +0 -0
  214. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_alert-boxes.scssc +0 -0
  215. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_block-grid.scssc +0 -0
  216. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_breadcrumbs.scssc +0 -0
  217. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_button-groups.scssc +0 -0
  218. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_buttons.scssc +0 -0
  219. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_clearing.scssc +0 -0
  220. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_dropdown-buttons.scssc +0 -0
  221. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_dropdown.scssc +0 -0
  222. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_flex-video.scssc +0 -0
  223. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_forms.scssc +0 -0
  224. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_global.scssc +0 -0
  225. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_grid.scssc +0 -0
  226. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_inline-lists.scssc +0 -0
  227. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_joyride.scssc +0 -0
  228. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_keystrokes.scssc +0 -0
  229. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_labels.scssc +0 -0
  230. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_magellan.scssc +0 -0
  231. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_offcanvas.scssc +0 -0
  232. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_orbit.scssc +0 -0
  233. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_pagination.scssc +0 -0
  234. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_panels.scssc +0 -0
  235. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_pricing-tables.scssc +0 -0
  236. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_progress-bars.scssc +0 -0
  237. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_reveal.scssc +0 -0
  238. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_side-nav.scssc +0 -0
  239. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_split-buttons.scssc +0 -0
  240. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_sub-nav.scssc +0 -0
  241. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_switch.scssc +0 -0
  242. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_tables.scssc +0 -0
  243. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_tabs.scssc +0 -0
  244. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_thumbs.scssc +0 -0
  245. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_tooltips.scssc +0 -0
  246. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_top-bar.scssc +0 -0
  247. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_type.scssc +0 -0
  248. data/spec/dummy/tmp/cache/assets/development/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_visibility.scssc +0 -0
  249. data/spec/dummy/tmp/cache/assets/development/sass/54bc91a8b995104fa44ba264c53dbd8d0a6ce1eb/font-awesome.css.erbc +0 -0
  250. data/spec/dummy/tmp/cache/assets/development/sass/868bdcd3c8e6bd73d750879e972f235f451b00af/application.sassc +0 -0
  251. data/spec/dummy/tmp/cache/assets/development/sass/868bdcd3c8e6bd73d750879e972f235f451b00af/foundation_and_overrides.scssc +0 -0
  252. data/spec/dummy/tmp/cache/assets/development/sass/868bdcd3c8e6bd73d750879e972f235f451b00af/responsive-tables.cssc +0 -0
  253. data/spec/dummy/tmp/cache/assets/development/sass/8b743f22fdd8d5759b06979128c44050617d3f73/_functions.scssc +0 -0
  254. data/spec/dummy/tmp/cache/assets/development/sass/e0f19c3e3433ffd972f6483c2c2cebc0799332b3/application.sassc +0 -0
  255. data/spec/dummy/tmp/cache/assets/development/sass/e0f19c3e3433ffd972f6483c2c2cebc0799332b3/foundation_and_overrides.scssc +0 -0
  256. data/spec/dummy/tmp/cache/assets/development/sass/e0f19c3e3433ffd972f6483c2c2cebc0799332b3/responsive-tables.cssc +0 -0
  257. data/spec/dummy/tmp/cache/assets/development/sass/f3df6673492a24d914db00517c65da0ef5b69493/foundation.scssc +0 -0
  258. data/spec/dummy/tmp/cache/assets/development/sprockets/0123c70bd5e30312ff515d82eb213b31 +0 -0
  259. data/spec/dummy/tmp/cache/assets/development/sprockets/017471d0ab6ff8d09517b238fe3e7c74 +0 -0
  260. data/spec/dummy/tmp/cache/assets/development/sprockets/01abbc5a8fe11302981249d2cba594c0 +0 -0
  261. data/spec/dummy/tmp/cache/assets/development/sprockets/06d7650b175d7850bc0819d0411e6c0d +0 -0
  262. data/spec/dummy/tmp/cache/assets/development/sprockets/0b8cbd6c43ff11ed3ab96976a2e98e2b +0 -0
  263. data/spec/dummy/tmp/cache/assets/development/sprockets/0d6d0302ec5d1661cb20ead3e3cfe729 +0 -0
  264. data/spec/dummy/tmp/cache/assets/development/sprockets/0e0e73dbc12f68468bc9813d331495fb +0 -0
  265. data/spec/dummy/tmp/cache/assets/development/sprockets/12134b245bdcf4dfc47cdd9b2fd05f85 +0 -0
  266. data/spec/dummy/tmp/cache/assets/development/sprockets/142b7b2297d55a50c6d15fd20457cd59 +0 -0
  267. data/spec/dummy/tmp/cache/assets/development/sprockets/183ad03b62e09a44e33c6a89e49be502 +0 -0
  268. data/spec/dummy/tmp/cache/assets/development/sprockets/18c44637348179373a9122410e9dd97f +0 -0
  269. data/spec/dummy/tmp/cache/assets/development/sprockets/1cca26710c02dc6d0fa2b562b7fd4402 +0 -0
  270. data/spec/dummy/tmp/cache/assets/development/sprockets/1cf836008e16cfe5e1057d9f8eda6957 +0 -0
  271. data/spec/dummy/tmp/cache/assets/development/sprockets/23dd3f3a5bc3511847dbe6fc5c7a2baf +0 -0
  272. data/spec/dummy/tmp/cache/assets/development/sprockets/24b1f3579f65df49c0801f4abd62b0ea +0 -0
  273. data/spec/dummy/tmp/cache/assets/development/sprockets/24cee8f6e43f5162d55fd1dcb8387207 +0 -0
  274. data/spec/dummy/tmp/cache/assets/development/sprockets/25909c1bc6f6dc8570bba1f681a299f3 +0 -0
  275. data/spec/dummy/tmp/cache/assets/development/sprockets/262d063e267800f380425e714dfb7377 +0 -0
  276. data/spec/dummy/tmp/cache/assets/development/sprockets/27cec58cf4ba029687152cedc2fb4f31 +0 -0
  277. data/spec/dummy/tmp/cache/assets/development/sprockets/2868201e7752aa2d76c04d6807493e17 +0 -0
  278. data/spec/dummy/tmp/cache/assets/development/sprockets/2bd6f962abf45ed50b78d14ba0904581 +0 -0
  279. data/spec/dummy/tmp/cache/assets/development/sprockets/2d93710bc1b018dbbf68607d1b862b19 +0 -0
  280. data/spec/dummy/tmp/cache/assets/development/sprockets/3176c16477eed7502d9646e03412a047 +0 -0
  281. data/spec/dummy/tmp/cache/assets/development/sprockets/3360fbdb00ed8ef59706becb7dbb41e4 +0 -0
  282. data/spec/dummy/tmp/cache/assets/development/sprockets/34f82b1703cb8350ca18f16392967779 +0 -0
  283. data/spec/dummy/tmp/cache/assets/development/sprockets/3cde31b62e801f1864e3566e83c43dfc +0 -0
  284. data/spec/dummy/tmp/cache/assets/development/sprockets/3f13b6783888bfdb18d8c58b24f62606 +0 -0
  285. data/spec/dummy/tmp/cache/assets/development/sprockets/3f2f49fd9b6431526dee6aaf69dc8dc0 +0 -0
  286. data/spec/dummy/tmp/cache/assets/development/sprockets/447161da5ee42f4d3b0702a328975499 +0 -0
  287. data/spec/dummy/tmp/cache/assets/development/sprockets/44920eab3d362e37f9841d9e5adb59f9 +0 -0
  288. data/spec/dummy/tmp/cache/assets/development/sprockets/4887510ef9b27d0f87d2fcdad555fa2c +0 -0
  289. data/spec/dummy/tmp/cache/assets/development/sprockets/4c363b6936cf63c23770ad8490f24dca +0 -0
  290. data/spec/dummy/tmp/cache/assets/development/sprockets/4cef1887720ac882dcc3c407779b5cc6 +0 -0
  291. data/spec/dummy/tmp/cache/assets/development/sprockets/4f53b61a3cb95e2cbe41669c8e52a6e9 +0 -0
  292. data/spec/dummy/tmp/cache/assets/development/sprockets/59f85eee3687eb4ed457a687daec4864 +0 -0
  293. data/spec/dummy/tmp/cache/assets/development/sprockets/5e3f3fcd99b13708971f707016d43020 +0 -0
  294. data/spec/dummy/tmp/cache/assets/development/sprockets/5e9e6e4b1c4bb6da2f89517e17fdca8b +0 -0
  295. data/spec/dummy/tmp/cache/assets/development/sprockets/5ea76ce82eaf44306e67d57bd758126e +0 -0
  296. data/spec/dummy/tmp/cache/assets/development/sprockets/65602f2f2646ac35105f3ee8fe0bb8c3 +0 -0
  297. data/spec/dummy/tmp/cache/assets/development/sprockets/676293868de93b34edf64d211de01c51 +0 -0
  298. data/spec/dummy/tmp/cache/assets/development/sprockets/6aa2f8ad895ab9c85a6adaa546654be7 +0 -0
  299. data/spec/dummy/tmp/cache/assets/development/sprockets/6df5499acca0edd86677d5beec5c2b18 +0 -0
  300. data/spec/dummy/tmp/cache/assets/development/sprockets/7124dbdd12afd3bc07d58a0ec3dddc65 +0 -0
  301. data/spec/dummy/tmp/cache/assets/development/sprockets/727bcd360e44edbb990ea53347896a64 +0 -0
  302. data/spec/dummy/tmp/cache/assets/development/sprockets/76d85daa1c4000f529bf26504ebde8d9 +0 -0
  303. data/spec/dummy/tmp/cache/assets/development/sprockets/7a776312a5e143757ba6fddbc33d3569 +0 -0
  304. data/spec/dummy/tmp/cache/assets/development/sprockets/7b2ab31b2e6b523c32133b2ccd942519 +0 -0
  305. data/spec/dummy/tmp/cache/assets/development/sprockets/7f3979d7c79893750db9cc673b603964 +0 -0
  306. data/spec/dummy/tmp/cache/assets/development/sprockets/7fd0671e74c96f97acc46cf12b97d34f +0 -0
  307. data/spec/dummy/tmp/cache/assets/development/sprockets/808539184f4890b8bc0cd62ddaae16f5 +0 -0
  308. data/spec/dummy/tmp/cache/assets/development/sprockets/818cac0ff4010d81cb9e6a2c5a17f494 +0 -0
  309. data/spec/dummy/tmp/cache/assets/development/sprockets/892ddb9c90db88e3c27b9bcdf13cc874 +0 -0
  310. data/spec/dummy/tmp/cache/assets/development/sprockets/8995a544bd8d9493c79f811452bc53c1 +0 -0
  311. data/spec/dummy/tmp/cache/assets/development/sprockets/8c37b2f2c33ead52769fac2c7aed8483 +0 -0
  312. data/spec/dummy/tmp/cache/assets/development/sprockets/911cbd3125fce783b8020caeb73c3d63 +0 -0
  313. data/spec/dummy/tmp/cache/assets/development/sprockets/9125c95d28b56e41a01d40a3ac95b3a1 +0 -0
  314. data/spec/dummy/tmp/cache/assets/development/sprockets/916fb1d316cf3a47b5fb8750981d020d +0 -0
  315. data/spec/dummy/tmp/cache/assets/development/sprockets/9a65689623c3096b82dd97819ef02b79 +0 -0
  316. data/spec/dummy/tmp/cache/assets/development/sprockets/9fb3b25a6094ba7546ef5d57ff154168 +0 -0
  317. data/spec/dummy/tmp/cache/assets/development/sprockets/a96fdd6e9cbae9c2f20789797d959cec +0 -0
  318. data/spec/dummy/tmp/cache/assets/development/sprockets/aaf89432055b14102caf4b80b7119d89 +0 -0
  319. data/spec/dummy/tmp/cache/assets/development/sprockets/ab360497106ba942135a8adee9a5d590 +0 -0
  320. data/spec/dummy/tmp/cache/assets/development/sprockets/ad82812e3dc354abb67d844dd84bf3cc +0 -0
  321. data/spec/dummy/tmp/cache/assets/development/sprockets/afb9268b0a27b448fa720fbbc0bb9628 +0 -0
  322. data/spec/dummy/tmp/cache/assets/development/sprockets/afec8da62e80022371c6a4bd3b0b8f9c +0 -0
  323. data/spec/dummy/tmp/cache/assets/development/sprockets/b0d34688aa81dc13171af7ebc2f6dda8 +0 -0
  324. data/spec/dummy/tmp/cache/assets/development/sprockets/b1ffcbb1bdbbfb46264e1f4b71d5dcf4 +0 -0
  325. data/spec/dummy/tmp/cache/assets/development/sprockets/b30e4385f999fefded784baa3436ecd3 +0 -0
  326. data/spec/dummy/tmp/cache/assets/development/sprockets/b4306f0aa05caff091d0998f73413207 +0 -0
  327. data/spec/dummy/tmp/cache/assets/development/sprockets/b682063a22ac7898fb5fab4c62304705 +0 -0
  328. data/spec/dummy/tmp/cache/assets/development/sprockets/b9df96c25252b82da00791df6d017199 +0 -0
  329. data/spec/dummy/tmp/cache/assets/development/sprockets/bab9d2b1312356b02c042aa46731369e +0 -0
  330. data/spec/dummy/tmp/cache/assets/development/sprockets/bc2974a23dfae3959d75891cba8e32f0 +0 -0
  331. data/spec/dummy/tmp/cache/assets/development/sprockets/bdebcd2a8dbc156cff64abc02cd9e669 +0 -0
  332. data/spec/dummy/tmp/cache/assets/development/sprockets/c08bb167930ddaf0637f6bb96a02f85f +0 -0
  333. data/spec/dummy/tmp/cache/assets/development/sprockets/c097d88a1c22b3559f635ed573e4f48f +0 -0
  334. data/spec/dummy/tmp/cache/assets/development/sprockets/c4a4a83aca2e4e9e987ec76930c664e7 +0 -0
  335. data/spec/dummy/tmp/cache/assets/development/sprockets/c4fcfae26eaf97324dd20db9b9e6e259 +0 -0
  336. data/spec/dummy/tmp/cache/assets/development/sprockets/c5b02654a71cc6e5646941d266964c8a +0 -0
  337. data/spec/dummy/tmp/cache/assets/development/sprockets/ca6ae3b0212906e203a675ad02a7729e +0 -0
  338. data/spec/dummy/tmp/cache/assets/development/sprockets/d081c4fcdb728213519ed45cd995eb11 +0 -0
  339. data/spec/dummy/tmp/cache/assets/development/sprockets/d0dd43eb5157bf58a581d451b38b381b +0 -0
  340. data/spec/dummy/tmp/cache/assets/development/sprockets/d15ce5f6879e59975286ede4318a248e +0 -0
  341. data/spec/dummy/tmp/cache/assets/development/sprockets/d340ed6abb07538c8a7f5628f96a321c +0 -0
  342. data/spec/dummy/tmp/cache/assets/development/sprockets/dbd4c6c705c6eef71f281c5f10aa1c12 +0 -0
  343. data/spec/dummy/tmp/cache/assets/development/sprockets/e3efb6f18e9d6f7f1f7a2bfdb4a616d3 +0 -0
  344. data/spec/dummy/tmp/cache/assets/development/sprockets/e868832c39075b1130b5eb600824cabf +0 -0
  345. data/spec/dummy/tmp/cache/assets/development/sprockets/e94c184030f553c4dba8000a3112597c +0 -0
  346. data/spec/dummy/tmp/cache/assets/development/sprockets/efdfaf9c072c61f54f6fe482990d4901 +0 -0
  347. data/spec/dummy/tmp/cache/assets/development/sprockets/f3a1c84ec35a01045bb6b638d1fd975f +0 -0
  348. data/spec/dummy/tmp/cache/assets/development/sprockets/f444a8ac8dfe6e3fecd2ee1b3eb660d2 +0 -0
  349. data/spec/dummy/tmp/cache/assets/development/sprockets/f5cb53dda9311da5c302df8dde1b98f0 +0 -0
  350. data/spec/dummy/tmp/cache/assets/development/sprockets/f873fbedb1dd78144b7a2d9203979b73 +0 -0
  351. data/spec/dummy/tmp/cache/assets/development/sprockets/f94211d95775b7b113ff83fbab9d96e5 +0 -0
  352. data/spec/dummy/tmp/cache/assets/development/sprockets/fd421c922607b7e36737167c1d5f12f4 +0 -0
  353. data/spec/dummy/tmp/cache/assets/development/sprockets/fde03b35c3de27e23e356c238f398a51 +0 -0
  354. data/spec/dummy/tmp/cache/assets/development/sprockets/fe26af7b1d422e2d98a6f52452bc8576 +0 -0
  355. data/spec/dummy/tmp/cache/assets/development/sprockets/fe2acad4ddcdce6fe73b2deba6fc235d +0 -0
  356. data/spec/dummy/tmp/cache/assets/test/sass/02ac2db4c4233a0290d18f376407059bb8c9d43c/colorize.sassc +0 -0
  357. data/spec/dummy/tmp/cache/assets/test/sass/02ac2db4c4233a0290d18f376407059bb8c9d43c/content.sassc +0 -0
  358. data/spec/dummy/tmp/cache/assets/test/sass/02ac2db4c4233a0290d18f376407059bb8c9d43c/header.sassc +0 -0
  359. data/spec/dummy/tmp/cache/assets/test/sass/02ac2db4c4233a0290d18f376407059bb8c9d43c/sidebar.sassc +0 -0
  360. data/spec/dummy/tmp/cache/assets/test/sass/02ac2db4c4233a0290d18f376407059bb8c9d43c/tables.sassc +0 -0
  361. data/spec/dummy/tmp/cache/assets/test/sass/1117950fc1adf8b31294376d9067eb7feadc7de0/colorize.sassc +0 -0
  362. data/spec/dummy/tmp/cache/assets/test/sass/1117950fc1adf8b31294376d9067eb7feadc7de0/content.sassc +0 -0
  363. data/spec/dummy/tmp/cache/assets/test/sass/1117950fc1adf8b31294376d9067eb7feadc7de0/header.sassc +0 -0
  364. data/spec/dummy/tmp/cache/assets/test/sass/1117950fc1adf8b31294376d9067eb7feadc7de0/sidebar.sassc +0 -0
  365. data/spec/dummy/tmp/cache/assets/test/sass/1117950fc1adf8b31294376d9067eb7feadc7de0/tables.sassc +0 -0
  366. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_accordion.scssc +0 -0
  367. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_alert-boxes.scssc +0 -0
  368. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_block-grid.scssc +0 -0
  369. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_breadcrumbs.scssc +0 -0
  370. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_button-groups.scssc +0 -0
  371. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_buttons.scssc +0 -0
  372. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_clearing.scssc +0 -0
  373. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_dropdown-buttons.scssc +0 -0
  374. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_dropdown.scssc +0 -0
  375. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_flex-video.scssc +0 -0
  376. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_forms.scssc +0 -0
  377. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_global.scssc +0 -0
  378. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_grid.scssc +0 -0
  379. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_inline-lists.scssc +0 -0
  380. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_joyride.scssc +0 -0
  381. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_keystrokes.scssc +0 -0
  382. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_labels.scssc +0 -0
  383. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_magellan.scssc +0 -0
  384. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_offcanvas.scssc +0 -0
  385. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_orbit.scssc +0 -0
  386. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_pagination.scssc +0 -0
  387. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_panels.scssc +0 -0
  388. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_pricing-tables.scssc +0 -0
  389. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_progress-bars.scssc +0 -0
  390. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_reveal.scssc +0 -0
  391. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_side-nav.scssc +0 -0
  392. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_split-buttons.scssc +0 -0
  393. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_sub-nav.scssc +0 -0
  394. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_switch.scssc +0 -0
  395. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_tables.scssc +0 -0
  396. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_tabs.scssc +0 -0
  397. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_thumbs.scssc +0 -0
  398. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_tooltips.scssc +0 -0
  399. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_top-bar.scssc +0 -0
  400. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_type.scssc +0 -0
  401. data/spec/dummy/tmp/cache/assets/test/sass/4d2c02410fd2c7d987069b7a4033b725cb831f1f/_visibility.scssc +0 -0
  402. data/spec/dummy/tmp/cache/assets/test/sass/54bc91a8b995104fa44ba264c53dbd8d0a6ce1eb/font-awesome.css.erbc +0 -0
  403. data/spec/dummy/tmp/cache/assets/test/sass/868bdcd3c8e6bd73d750879e972f235f451b00af/application.sassc +0 -0
  404. data/spec/dummy/tmp/cache/assets/test/sass/868bdcd3c8e6bd73d750879e972f235f451b00af/foundation_and_overrides.scssc +0 -0
  405. data/spec/dummy/tmp/cache/assets/test/sass/868bdcd3c8e6bd73d750879e972f235f451b00af/responsive-tables.cssc +0 -0
  406. data/spec/dummy/tmp/cache/assets/test/sass/8b743f22fdd8d5759b06979128c44050617d3f73/_functions.scssc +0 -0
  407. data/spec/dummy/tmp/cache/assets/test/sass/e0f19c3e3433ffd972f6483c2c2cebc0799332b3/application.sassc +0 -0
  408. data/spec/dummy/tmp/cache/assets/test/sass/e0f19c3e3433ffd972f6483c2c2cebc0799332b3/foundation_and_overrides.scssc +0 -0
  409. data/spec/dummy/tmp/cache/assets/test/sass/e0f19c3e3433ffd972f6483c2c2cebc0799332b3/responsive-tables.cssc +0 -0
  410. data/spec/dummy/tmp/cache/assets/test/sass/f3df6673492a24d914db00517c65da0ef5b69493/foundation.scssc +0 -0
  411. data/spec/dummy/tmp/cache/assets/test/sprockets/0123c70bd5e30312ff515d82eb213b31 +0 -0
  412. data/spec/dummy/tmp/cache/assets/test/sprockets/017471d0ab6ff8d09517b238fe3e7c74 +0 -0
  413. data/spec/dummy/tmp/cache/assets/test/sprockets/01abbc5a8fe11302981249d2cba594c0 +0 -0
  414. data/spec/dummy/tmp/cache/assets/test/sprockets/0436b299d86917aa132d8d63a5d974b8 +0 -0
  415. data/spec/dummy/tmp/cache/assets/test/sprockets/0b8cbd6c43ff11ed3ab96976a2e98e2b +0 -0
  416. data/spec/dummy/tmp/cache/assets/test/sprockets/0d6d0302ec5d1661cb20ead3e3cfe729 +0 -0
  417. data/spec/dummy/tmp/cache/assets/test/sprockets/0e0e73dbc12f68468bc9813d331495fb +0 -0
  418. data/spec/dummy/tmp/cache/assets/test/sprockets/12134b245bdcf4dfc47cdd9b2fd05f85 +0 -0
  419. data/spec/dummy/tmp/cache/assets/test/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
  420. data/spec/dummy/tmp/cache/assets/test/sprockets/183ad03b62e09a44e33c6a89e49be502 +0 -0
  421. data/spec/dummy/tmp/cache/assets/test/sprockets/18c44637348179373a9122410e9dd97f +0 -0
  422. data/spec/dummy/tmp/cache/assets/test/sprockets/1cca26710c02dc6d0fa2b562b7fd4402 +0 -0
  423. data/spec/dummy/tmp/cache/assets/test/sprockets/1cf836008e16cfe5e1057d9f8eda6957 +0 -0
  424. data/spec/dummy/tmp/cache/assets/test/sprockets/24b1f3579f65df49c0801f4abd62b0ea +0 -0
  425. data/spec/dummy/tmp/cache/assets/test/sprockets/24cee8f6e43f5162d55fd1dcb8387207 +0 -0
  426. data/spec/dummy/tmp/cache/assets/test/sprockets/25909c1bc6f6dc8570bba1f681a299f3 +0 -0
  427. data/spec/dummy/tmp/cache/assets/test/sprockets/262d063e267800f380425e714dfb7377 +0 -0
  428. data/spec/dummy/tmp/cache/assets/test/sprockets/26df301c0de83087195edeb5e88bde36 +0 -0
  429. data/spec/dummy/tmp/cache/assets/test/sprockets/27cec58cf4ba029687152cedc2fb4f31 +0 -0
  430. data/spec/dummy/tmp/cache/assets/test/sprockets/2868201e7752aa2d76c04d6807493e17 +0 -0
  431. data/spec/dummy/tmp/cache/assets/test/sprockets/2bd6f962abf45ed50b78d14ba0904581 +0 -0
  432. data/spec/dummy/tmp/cache/assets/test/sprockets/2d93710bc1b018dbbf68607d1b862b19 +0 -0
  433. data/spec/dummy/tmp/cache/assets/test/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
  434. data/spec/dummy/tmp/cache/assets/test/sprockets/3360fbdb00ed8ef59706becb7dbb41e4 +0 -0
  435. data/spec/dummy/tmp/cache/assets/test/sprockets/34f82b1703cb8350ca18f16392967779 +0 -0
  436. data/spec/dummy/tmp/cache/assets/test/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  437. data/spec/dummy/tmp/cache/assets/test/sprockets/3cde31b62e801f1864e3566e83c43dfc +0 -0
  438. data/spec/dummy/tmp/cache/assets/test/sprockets/3f2f49fd9b6431526dee6aaf69dc8dc0 +0 -0
  439. data/spec/dummy/tmp/cache/assets/test/sprockets/447161da5ee42f4d3b0702a328975499 +0 -0
  440. data/spec/dummy/tmp/cache/assets/test/sprockets/44920eab3d362e37f9841d9e5adb59f9 +0 -0
  441. data/spec/dummy/tmp/cache/assets/test/sprockets/4887510ef9b27d0f87d2fcdad555fa2c +0 -0
  442. data/spec/dummy/tmp/cache/assets/test/sprockets/4c363b6936cf63c23770ad8490f24dca +0 -0
  443. data/spec/dummy/tmp/cache/assets/test/sprockets/4cef1887720ac882dcc3c407779b5cc6 +0 -0
  444. data/spec/dummy/tmp/cache/assets/test/sprockets/4f53b61a3cb95e2cbe41669c8e52a6e9 +0 -0
  445. data/spec/dummy/tmp/cache/assets/test/sprockets/59f85eee3687eb4ed457a687daec4864 +0 -0
  446. data/spec/dummy/tmp/cache/assets/test/sprockets/5e3f3fcd99b13708971f707016d43020 +0 -0
  447. data/spec/dummy/tmp/cache/assets/test/sprockets/5e9e6e4b1c4bb6da2f89517e17fdca8b +0 -0
  448. data/spec/dummy/tmp/cache/assets/test/sprockets/65602f2f2646ac35105f3ee8fe0bb8c3 +0 -0
  449. data/spec/dummy/tmp/cache/assets/test/sprockets/676293868de93b34edf64d211de01c51 +0 -0
  450. data/spec/dummy/tmp/cache/assets/test/sprockets/6aa2f8ad895ab9c85a6adaa546654be7 +0 -0
  451. data/spec/dummy/tmp/cache/assets/test/sprockets/6df5499acca0edd86677d5beec5c2b18 +0 -0
  452. data/spec/dummy/tmp/cache/assets/test/sprockets/7124dbdd12afd3bc07d58a0ec3dddc65 +0 -0
  453. data/spec/dummy/tmp/cache/assets/test/sprockets/76d85daa1c4000f529bf26504ebde8d9 +0 -0
  454. data/spec/dummy/tmp/cache/assets/test/sprockets/7a776312a5e143757ba6fddbc33d3569 +0 -0
  455. data/spec/dummy/tmp/cache/assets/test/sprockets/7f3979d7c79893750db9cc673b603964 +0 -0
  456. data/spec/dummy/tmp/cache/assets/test/sprockets/7fd0671e74c96f97acc46cf12b97d34f +0 -0
  457. data/spec/dummy/tmp/cache/assets/test/sprockets/808539184f4890b8bc0cd62ddaae16f5 +0 -0
  458. data/spec/dummy/tmp/cache/assets/test/sprockets/892ddb9c90db88e3c27b9bcdf13cc874 +0 -0
  459. data/spec/dummy/tmp/cache/assets/test/sprockets/8995a544bd8d9493c79f811452bc53c1 +0 -0
  460. data/spec/dummy/tmp/cache/assets/test/sprockets/8c37b2f2c33ead52769fac2c7aed8483 +0 -0
  461. data/spec/dummy/tmp/cache/assets/test/sprockets/911cbd3125fce783b8020caeb73c3d63 +0 -0
  462. data/spec/dummy/tmp/cache/assets/test/sprockets/9125c95d28b56e41a01d40a3ac95b3a1 +0 -0
  463. data/spec/dummy/tmp/cache/assets/test/sprockets/916fb1d316cf3a47b5fb8750981d020d +0 -0
  464. data/spec/dummy/tmp/cache/assets/test/sprockets/9a65689623c3096b82dd97819ef02b79 +0 -0
  465. data/spec/dummy/tmp/cache/assets/test/sprockets/9fb3b25a6094ba7546ef5d57ff154168 +0 -0
  466. data/spec/dummy/tmp/cache/assets/test/sprockets/ad82812e3dc354abb67d844dd84bf3cc +0 -0
  467. data/spec/dummy/tmp/cache/assets/test/sprockets/afec8da62e80022371c6a4bd3b0b8f9c +0 -0
  468. data/spec/dummy/tmp/cache/assets/test/sprockets/b1ffcbb1bdbbfb46264e1f4b71d5dcf4 +0 -0
  469. data/spec/dummy/tmp/cache/assets/test/sprockets/b30e4385f999fefded784baa3436ecd3 +0 -0
  470. data/spec/dummy/tmp/cache/assets/test/sprockets/b682063a22ac7898fb5fab4c62304705 +0 -0
  471. data/spec/dummy/tmp/cache/assets/test/sprockets/b9df96c25252b82da00791df6d017199 +0 -0
  472. data/spec/dummy/tmp/cache/assets/test/sprockets/bab9d2b1312356b02c042aa46731369e +0 -0
  473. data/spec/dummy/tmp/cache/assets/test/sprockets/bc2974a23dfae3959d75891cba8e32f0 +0 -0
  474. data/spec/dummy/tmp/cache/assets/test/sprockets/bdebcd2a8dbc156cff64abc02cd9e669 +0 -0
  475. data/spec/dummy/tmp/cache/assets/test/sprockets/c4a4a83aca2e4e9e987ec76930c664e7 +0 -0
  476. data/spec/dummy/tmp/cache/assets/test/sprockets/c4fcfae26eaf97324dd20db9b9e6e259 +0 -0
  477. data/spec/dummy/tmp/cache/assets/test/sprockets/ca6ae3b0212906e203a675ad02a7729e +0 -0
  478. data/spec/dummy/tmp/cache/assets/test/sprockets/ccf123d21b7729fcc4a32f35152ad7ea +0 -0
  479. data/spec/dummy/tmp/cache/assets/test/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
  480. data/spec/dummy/tmp/cache/assets/test/sprockets/d081c4fcdb728213519ed45cd995eb11 +0 -0
  481. data/spec/dummy/tmp/cache/assets/test/sprockets/d15ce5f6879e59975286ede4318a248e +0 -0
  482. data/spec/dummy/tmp/cache/assets/test/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  483. data/spec/dummy/tmp/cache/assets/test/sprockets/dbd4c6c705c6eef71f281c5f10aa1c12 +0 -0
  484. data/spec/dummy/tmp/cache/assets/test/sprockets/e3efb6f18e9d6f7f1f7a2bfdb4a616d3 +0 -0
  485. data/spec/dummy/tmp/cache/assets/test/sprockets/e868832c39075b1130b5eb600824cabf +0 -0
  486. data/spec/dummy/tmp/cache/assets/test/sprockets/f3a1c84ec35a01045bb6b638d1fd975f +0 -0
  487. data/spec/dummy/tmp/cache/assets/test/sprockets/f444a8ac8dfe6e3fecd2ee1b3eb660d2 +0 -0
  488. data/spec/dummy/tmp/cache/assets/test/sprockets/f5cb53dda9311da5c302df8dde1b98f0 +0 -0
  489. data/spec/dummy/tmp/cache/assets/test/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
  490. data/spec/dummy/tmp/cache/assets/test/sprockets/f94211d95775b7b113ff83fbab9d96e5 +0 -0
  491. data/spec/dummy/tmp/cache/assets/test/sprockets/fd421c922607b7e36737167c1d5f12f4 +0 -0
  492. data/spec/dummy/tmp/cache/assets/test/sprockets/fde03b35c3de27e23e356c238f398a51 +0 -0
  493. data/spec/dummy/tmp/cache/assets/test/sprockets/fe26af7b1d422e2d98a6f52452bc8576 +0 -0
  494. data/spec/dummy/tmp/pids/server.pid +1 -0
  495. data/spec/features/admin/collaborators/sign_in_spec.rb +17 -0
  496. data/spec/features/admin/custom/global_service_controller_spec.rb +22 -0
  497. data/spec/features/admin/custom/sidebar_services_spec.rb +29 -0
  498. data/spec/features/admin/custom/site_service_controller_spec.rb +37 -0
  499. data/spec/features/admin/domains/domian_routing_spec.rb +34 -0
  500. data/spec/features/admin/routes/details_spec.rb +24 -0
  501. data/spec/features/front/domain_routing_spec.rb +34 -0
  502. data/spec/features/front/page_blocks/defined_spec.rb +30 -0
  503. data/spec/features/front/page_blocks/undefined_spec.rb +19 -0
  504. data/spec/features/front/routes/page_spec.rb +18 -0
  505. data/spec/features/front/routes/redirection_spec.rb +19 -0
  506. data/spec/models/sierra/contributor_spec.rb +7 -0
  507. data/spec/models/sierra/site_spec.rb +37 -0
  508. data/spec/spec_helper.rb +56 -0
  509. data/spec/support/contributor_support.rb +75 -0
  510. data/spec/support/factories/block_factory.rb +10 -0
  511. data/spec/support/factories/contributor_factory.rb +8 -0
  512. data/spec/support/factories/route_factory.rb +18 -0
  513. data/spec/support/factories/service_factory.rb +6 -0
  514. data/spec/support/factories/site_factory.rb +9 -0
  515. metadata +1583 -0
@@ -0,0 +1,2947 @@
1
+ /**
2
+ * EpicEditor - An Embeddable JavaScript Markdown Editor (https://github.com/OscarGodson/EpicEditor)
3
+ * Copyright (c) 2011-2012, Oscar Godson. (MIT Licensed)
4
+ */
5
+
6
+ (function (window, undefined) {
7
+ /**
8
+ * Applies attributes to a DOM object
9
+ * @param {object} context The DOM obj you want to apply the attributes to
10
+ * @param {object} attrs A key/value pair of attributes you want to apply
11
+ * @returns {undefined}
12
+ */
13
+ function _applyAttrs(context, attrs) {
14
+ for (var attr in attrs) {
15
+ if (attrs.hasOwnProperty(attr)) {
16
+ context.setAttribute(attr, attrs[attr]);
17
+ }
18
+ }
19
+ }
20
+
21
+ /**
22
+ * Applies styles to a DOM object
23
+ * @param {object} context The DOM obj you want to apply the attributes to
24
+ * @param {object} attrs A key/value pair of attributes you want to apply
25
+ * @returns {undefined}
26
+ */
27
+ function _applyStyles(context, attrs) {
28
+ for (var attr in attrs) {
29
+ if (attrs.hasOwnProperty(attr)) {
30
+ context.style[attr] = attrs[attr];
31
+ }
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Returns a DOM objects computed style
37
+ * @param {object} el The element you want to get the style from
38
+ * @param {string} styleProp The property you want to get from the element
39
+ * @returns {string} Returns a string of the value. If property is not set it will return a blank string
40
+ */
41
+ function _getStyle(el, styleProp) {
42
+ var x = el
43
+ , y = null;
44
+ if (window.getComputedStyle) {
45
+ y = document.defaultView.getComputedStyle(x, null).getPropertyValue(styleProp);
46
+ }
47
+ else if (x.currentStyle) {
48
+ y = x.currentStyle[styleProp];
49
+ }
50
+ return y;
51
+ }
52
+
53
+ /**
54
+ * Saves the current style state for the styles requested, then applies styles
55
+ * to overwrite the existing one. The old styles are returned as an object so
56
+ * you can pass it back in when you want to revert back to the old style
57
+ * @param {object} el The element to get the styles of
58
+ * @param {string} type Can be "save" or "apply". apply will just apply styles you give it. Save will write styles
59
+ * @param {object} styles Key/value style/property pairs
60
+ * @returns {object}
61
+ */
62
+ function _saveStyleState(el, type, styles) {
63
+ var returnState = {}
64
+ , style;
65
+ if (type === 'save') {
66
+ for (style in styles) {
67
+ if (styles.hasOwnProperty(style)) {
68
+ returnState[style] = _getStyle(el, style);
69
+ }
70
+ }
71
+ // After it's all done saving all the previous states, change the styles
72
+ _applyStyles(el, styles);
73
+ }
74
+ else if (type === 'apply') {
75
+ _applyStyles(el, styles);
76
+ }
77
+ return returnState;
78
+ }
79
+
80
+ /**
81
+ * Gets an elements total width including it's borders and padding
82
+ * @param {object} el The element to get the total width of
83
+ * @returns {int}
84
+ */
85
+ function _outerWidth(el) {
86
+ var b = parseInt(_getStyle(el, 'border-left-width'), 10) + parseInt(_getStyle(el, 'border-right-width'), 10)
87
+ , p = parseInt(_getStyle(el, 'padding-left'), 10) + parseInt(_getStyle(el, 'padding-right'), 10)
88
+ , w = el.offsetWidth
89
+ , t;
90
+ // For IE in case no border is set and it defaults to "medium"
91
+ if (isNaN(b)) { b = 0; }
92
+ t = b + p + w;
93
+ return t;
94
+ }
95
+
96
+ /**
97
+ * Gets an elements total height including it's borders and padding
98
+ * @param {object} el The element to get the total width of
99
+ * @returns {int}
100
+ */
101
+ function _outerHeight(el) {
102
+ var b = parseInt(_getStyle(el, 'border-top-width'), 10) + parseInt(_getStyle(el, 'border-bottom-width'), 10)
103
+ , p = parseInt(_getStyle(el, 'padding-top'), 10) + parseInt(_getStyle(el, 'padding-bottom'), 10)
104
+ , w = parseInt(_getStyle(el, 'height'), 10)
105
+ , t;
106
+ // For IE in case no border is set and it defaults to "medium"
107
+ if (isNaN(b)) { b = 0; }
108
+ t = b + p + w;
109
+ return t;
110
+ }
111
+
112
+ /**
113
+ * Inserts a <link> tag specifically for CSS
114
+ * @param {string} path The path to the CSS file
115
+ * @param {object} context In what context you want to apply this to (document, iframe, etc)
116
+ * @param {string} id An id for you to reference later for changing properties of the <link>
117
+ * @returns {undefined}
118
+ */
119
+ function _insertCSSLink(path, context, id) {
120
+ id = id || '';
121
+ var headID = context.getElementsByTagName("head")[0]
122
+ , cssNode = context.createElement('link');
123
+
124
+ _applyAttrs(cssNode, {
125
+ type: 'text/css'
126
+ , id: id
127
+ , rel: 'stylesheet'
128
+ , href: path
129
+ , name: path
130
+ , media: 'screen'
131
+ });
132
+
133
+ headID.appendChild(cssNode);
134
+ }
135
+
136
+ // Simply replaces a class (o), to a new class (n) on an element provided (e)
137
+ function _replaceClass(e, o, n) {
138
+ e.className = e.className.replace(o, n);
139
+ }
140
+
141
+ // Feature detects an iframe to get the inner document for writing to
142
+ function _getIframeInnards(el) {
143
+ return el.contentDocument || el.contentWindow.document;
144
+ }
145
+
146
+ // Grabs the text from an element and preserves whitespace
147
+ function _getText(el) {
148
+ var theText;
149
+ // Make sure to check for type of string because if the body of the page
150
+ // doesn't have any text it'll be "" which is falsey and will go into
151
+ // the else which is meant for Firefox and shit will break
152
+ if (typeof document.body.innerText == 'string') {
153
+ theText = el.innerText;
154
+ }
155
+ else {
156
+ // First replace <br>s before replacing the rest of the HTML
157
+ theText = el.innerHTML.replace(/<br>/gi, "\n");
158
+ // Now we can clean the HTML
159
+ theText = theText.replace(/<(?:.|\n)*?>/gm, '');
160
+ // Now fix HTML entities
161
+ theText = theText.replace(/&lt;/gi, '<');
162
+ theText = theText.replace(/&gt;/gi, '>');
163
+ }
164
+ return theText;
165
+ }
166
+
167
+ function _setText(el, content) {
168
+ // Don't convert lt/gt characters as HTML when viewing the editor window
169
+ // TODO: Write a test to catch regressions for this
170
+ content = content.replace(/</g, '&lt;');
171
+ content = content.replace(/>/g, '&gt;');
172
+ content = content.replace(/\n/g, '<br>');
173
+
174
+ // Make sure to there aren't two spaces in a row (replace one with &nbsp;)
175
+ // If you find and replace every space with a &nbsp; text will not wrap.
176
+ // Hence the name (Non-Breaking-SPace).
177
+ // TODO: Probably need to test this somehow...
178
+ content = content.replace(/<br>\s/g, '<br>&nbsp;')
179
+ content = content.replace(/\s\s\s/g, '&nbsp; &nbsp;')
180
+ content = content.replace(/\s\s/g, '&nbsp; ')
181
+ content = content.replace(/^ /, '&nbsp;')
182
+
183
+ el.innerHTML = content;
184
+ return true;
185
+ }
186
+
187
+ /**
188
+ * Converts the 'raw' format of a file's contents into plaintext
189
+ * @param {string} content Contents of the file
190
+ * @returns {string} the sanitized content
191
+ */
192
+ function _sanitizeRawContent(content) {
193
+ // Get this, 2 spaces in a content editable actually converts to:
194
+ // 0020 00a0, meaning, "space no-break space". So, manually convert
195
+ // no-break spaces to spaces again before handing to marked.
196
+ // Also, WebKit converts no-break to unicode equivalent and FF HTML.
197
+ return content.replace(/\u00a0/g, ' ').replace(/&nbsp;/g, ' ');
198
+ }
199
+
200
+ /**
201
+ * Will return the version number if the browser is IE. If not will return -1
202
+ * TRY NEVER TO USE THIS AND USE FEATURE DETECTION IF POSSIBLE
203
+ * @returns {Number} -1 if false or the version number if true
204
+ */
205
+ function _isIE() {
206
+ var rv = -1 // Return value assumes failure.
207
+ , ua = navigator.userAgent
208
+ , re;
209
+ if (navigator.appName == 'Microsoft Internet Explorer') {
210
+ re = /MSIE ([0-9]{1,}[\.0-9]{0,})/;
211
+ if (re.exec(ua) != null) {
212
+ rv = parseFloat(RegExp.$1, 10);
213
+ }
214
+ }
215
+ return rv;
216
+ }
217
+
218
+ /**
219
+ * Same as the isIE(), but simply returns a boolean
220
+ * THIS IS TERRIBLE AND IS ONLY USED BECAUSE FULLSCREEN IN SAFARI IS BORKED
221
+ * If some other engine uses WebKit and has support for fullscreen they
222
+ * probably wont get native fullscreen until Safari's fullscreen is fixed
223
+ * @returns {Boolean} true if Safari
224
+ */
225
+ function _isSafari() {
226
+ var n = window.navigator;
227
+ return n.userAgent.indexOf('Safari') > -1 && n.userAgent.indexOf('Chrome') == -1;
228
+ }
229
+
230
+ /**
231
+ * Same as the isIE(), but simply returns a boolean
232
+ * THIS IS TERRIBLE ONLY USE IF ABSOLUTELY NEEDED
233
+ * @returns {Boolean} true if Safari
234
+ */
235
+ function _isFirefox() {
236
+ var n = window.navigator;
237
+ return n.userAgent.indexOf('Firefox') > -1 && n.userAgent.indexOf('Seamonkey') == -1;
238
+ }
239
+
240
+ /**
241
+ * Determines if supplied value is a function
242
+ * @param {object} object to determine type
243
+ */
244
+ function _isFunction(functionToCheck) {
245
+ var getType = {};
246
+ return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
247
+ }
248
+
249
+ /**
250
+ * Overwrites obj1's values with obj2's and adds obj2's if non existent in obj1
251
+ * @param {boolean} [deepMerge=false] If true, will deep merge meaning it will merge sub-objects like {obj:obj2{foo:'bar'}}
252
+ * @param {object} first object
253
+ * @param {object} second object
254
+ * @returnss {object} a new object based on obj1 and obj2
255
+ */
256
+ function _mergeObjs() {
257
+ // copy reference to target object
258
+ var target = arguments[0] || {}
259
+ , i = 1
260
+ , length = arguments.length
261
+ , deep = false
262
+ , options
263
+ , name
264
+ , src
265
+ , copy
266
+
267
+ // Handle a deep copy situation
268
+ if (typeof target === "boolean") {
269
+ deep = target;
270
+ target = arguments[1] || {};
271
+ // skip the boolean and the target
272
+ i = 2;
273
+ }
274
+
275
+ // Handle case when target is a string or something (possible in deep copy)
276
+ if (typeof target !== "object" && !_isFunction(target)) {
277
+ target = {};
278
+ }
279
+ // extend jQuery itself if only one argument is passed
280
+ if (length === i) {
281
+ target = this;
282
+ --i;
283
+ }
284
+
285
+ for (; i < length; i++) {
286
+ // Only deal with non-null/undefined values
287
+ if ((options = arguments[i]) != null) {
288
+ // Extend the base object
289
+ for (name in options) {
290
+ // @NOTE: added hasOwnProperty check
291
+ if (options.hasOwnProperty(name)) {
292
+ src = target[name];
293
+ copy = options[name];
294
+ // Prevent never-ending loop
295
+ if (target === copy) {
296
+ continue;
297
+ }
298
+ // Recurse if we're merging object values
299
+ if (deep && copy && typeof copy === "object" && !copy.nodeType) {
300
+ target[name] = _mergeObjs(deep,
301
+ // Never move original objects, clone them
302
+ src || (copy.length != null ? [] : {})
303
+ , copy);
304
+ } else if (copy !== undefined) { // Don't bring in undefined values
305
+ target[name] = copy;
306
+ }
307
+ }
308
+ }
309
+ }
310
+ }
311
+
312
+ // Return the modified object
313
+ return target;
314
+ }
315
+
316
+ /**
317
+ * Initiates the EpicEditor object and sets up offline storage as well
318
+ * @class Represents an EpicEditor instance
319
+ * @param {object} options An optional customization object
320
+ * @returns {object} EpicEditor will be returned
321
+ */
322
+ function EpicEditor(options) {
323
+ // Default settings will be overwritten/extended by options arg
324
+ var self = this
325
+ , opts = options || {}
326
+ , _defaultFileSchema
327
+ , _defaultFile
328
+ , defaults = { container: 'epiceditor'
329
+ , basePath: 'epiceditor'
330
+ , textarea: undefined
331
+ , clientSideStorage: true
332
+ , localStorageName: 'epiceditor'
333
+ , useNativeFullscreen: true
334
+ , file: { name: null
335
+ , defaultContent: ''
336
+ , autoSave: 100 // Set to false for no auto saving
337
+ }
338
+ , theme: { base: '/themes/base/epiceditor.css'
339
+ , preview: '/themes/preview/github.css'
340
+ , editor: '/themes/editor/epic-dark.css'
341
+ }
342
+ , focusOnLoad: false
343
+ , shortcut: { modifier: 18 // alt keycode
344
+ , fullscreen: 70 // f keycode
345
+ , preview: 80 // p keycode
346
+ }
347
+ , string: { togglePreview: 'Toggle Preview Mode'
348
+ , toggleEdit: 'Toggle Edit Mode'
349
+ , toggleFullscreen: 'Enter Fullscreen'
350
+ }
351
+ , parser: typeof marked == 'function' ? marked : null
352
+ , autogrow: false
353
+ , button: { fullscreen: true
354
+ , preview: true
355
+ , bar: "auto"
356
+ }
357
+ }
358
+ , defaultStorage
359
+ , autogrowDefaults = { minHeight: 80
360
+ , maxHeight: false
361
+ , scroll: true
362
+ };
363
+
364
+ self.settings = _mergeObjs(true, defaults, opts);
365
+
366
+ var buttons = self.settings.button;
367
+ self._fullscreenEnabled = typeof(buttons) === 'object' ? typeof buttons.fullscreen === 'undefined' || buttons.fullscreen : buttons === true;
368
+ self._editEnabled = typeof(buttons) === 'object' ? typeof buttons.edit === 'undefined' || buttons.edit : buttons === true;
369
+ self._previewEnabled = typeof(buttons) === 'object' ? typeof buttons.preview === 'undefined' || buttons.preview : buttons === true;
370
+
371
+ if (!(typeof self.settings.parser == 'function' && typeof self.settings.parser('TEST') == 'string')) {
372
+ self.settings.parser = function (str) {
373
+ return str;
374
+ }
375
+ }
376
+
377
+ if (self.settings.autogrow) {
378
+ if (self.settings.autogrow === true) {
379
+ self.settings.autogrow = autogrowDefaults;
380
+ }
381
+ else {
382
+ self.settings.autogrow = _mergeObjs(true, autogrowDefaults, self.settings.autogrow);
383
+ }
384
+ self._oldHeight = -1;
385
+ }
386
+
387
+ // If you put an absolute link as the path of any of the themes ignore the basePath
388
+ // preview theme
389
+ if (!self.settings.theme.preview.match(/^https?:\/\//)) {
390
+ self.settings.theme.preview = self.settings.basePath + self.settings.theme.preview;
391
+ }
392
+ // editor theme
393
+ if (!self.settings.theme.editor.match(/^https?:\/\//)) {
394
+ self.settings.theme.editor = self.settings.basePath + self.settings.theme.editor;
395
+ }
396
+ // base theme
397
+ if (!self.settings.theme.base.match(/^https?:\/\//)) {
398
+ self.settings.theme.base = self.settings.basePath + self.settings.theme.base;
399
+ }
400
+
401
+ // Grab the container element and save it to self.element
402
+ // if it's a string assume it's an ID and if it's an object
403
+ // assume it's a DOM element
404
+ if (typeof self.settings.container == 'string') {
405
+ self.element = document.getElementById(self.settings.container);
406
+ }
407
+ else if (typeof self.settings.container == 'object') {
408
+ self.element = self.settings.container;
409
+ }
410
+
411
+ if (typeof self.settings.textarea == 'undefined' && typeof self.element != 'undefined') {
412
+ var textareas = self.element.getElementsByTagName('textarea');
413
+ if (textareas.length > 0) {
414
+ self.settings.textarea = textareas[0];
415
+ _applyStyles(self.settings.textarea, {
416
+ display: 'none'
417
+ });
418
+ }
419
+ }
420
+
421
+ // Figure out the file name. If no file name is given we'll use the ID.
422
+ // If there's no ID either we'll use a namespaced file name that's incremented
423
+ // based on the calling order. As long as it doesn't change, drafts will be saved.
424
+ if (!self.settings.file.name) {
425
+ if (typeof self.settings.container == 'string') {
426
+ self.settings.file.name = self.settings.container;
427
+ }
428
+ else if (typeof self.settings.container == 'object') {
429
+ if (self.element.id) {
430
+ self.settings.file.name = self.element.id;
431
+ }
432
+ else {
433
+ if (!EpicEditor._data.unnamedEditors) {
434
+ EpicEditor._data.unnamedEditors = [];
435
+ }
436
+ EpicEditor._data.unnamedEditors.push(self);
437
+ self.settings.file.name = '__epiceditor-untitled-' + EpicEditor._data.unnamedEditors.length;
438
+ }
439
+ }
440
+ }
441
+
442
+ if (self.settings.button.bar === "show") {
443
+ self.settings.button.bar = true;
444
+ }
445
+
446
+ if (self.settings.button.bar === "hide") {
447
+ self.settings.button.bar = false;
448
+ }
449
+
450
+ // Protect the id and overwrite if passed in as an option
451
+ // TODO: Put underscrore to denote that this is private
452
+ self._instanceId = 'epiceditor-' + Math.round(Math.random() * 100000);
453
+ self._storage = {};
454
+ self._canSave = true;
455
+
456
+ // Setup local storage of files
457
+ self._defaultFileSchema = function () {
458
+ return {
459
+ content: self.settings.file.defaultContent
460
+ , created: new Date()
461
+ , modified: new Date()
462
+ }
463
+ }
464
+
465
+ if (localStorage && self.settings.clientSideStorage) {
466
+ this._storage = localStorage;
467
+ if (this._storage[self.settings.localStorageName] && self.getFiles(self.settings.file.name) === undefined) {
468
+ _defaultFile = self._defaultFileSchema();
469
+ _defaultFile.content = self.settings.file.defaultContent;
470
+ }
471
+ }
472
+
473
+ if (!this._storage[self.settings.localStorageName]) {
474
+ defaultStorage = {};
475
+ defaultStorage[self.settings.file.name] = self._defaultFileSchema();
476
+ defaultStorage = JSON.stringify(defaultStorage);
477
+ this._storage[self.settings.localStorageName] = defaultStorage;
478
+ }
479
+
480
+ // A string to prepend files with to save draft versions of files
481
+ // and reset all preview drafts on each load!
482
+ self._previewDraftLocation = '__draft-';
483
+ self._storage[self._previewDraftLocation + self.settings.localStorageName] = self._storage[self.settings.localStorageName];
484
+
485
+ // This needs to replace the use of classes to check the state of EE
486
+ self._eeState = {
487
+ fullscreen: false
488
+ , preview: false
489
+ , edit: false
490
+ , loaded: false
491
+ , unloaded: false
492
+ }
493
+
494
+ // Now that it exists, allow binding of events if it doesn't exist yet
495
+ if (!self.events) {
496
+ self.events = {};
497
+ }
498
+
499
+ return this;
500
+ }
501
+
502
+ /**
503
+ * Inserts the EpicEditor into the DOM via an iframe and gets it ready for editing and previewing
504
+ * @returns {object} EpicEditor will be returned
505
+ */
506
+ EpicEditor.prototype.load = function (callback) {
507
+
508
+ // Get out early if it's already loaded
509
+ if (this.is('loaded')) { return this; }
510
+
511
+ // TODO: Gotta get the privates with underscores!
512
+ // TODO: Gotta document what these are for...
513
+ var self = this
514
+ , _HtmlTemplates
515
+ , iframeElement
516
+ , baseTag
517
+ , utilBtns
518
+ , utilBar
519
+ , utilBarTimer
520
+ , keypressTimer
521
+ , mousePos = { y: -1, x: -1 }
522
+ , _elementStates
523
+ , _isInEdit
524
+ , nativeFs = false
525
+ , nativeFsWebkit = false
526
+ , nativeFsMoz = false
527
+ , nativeFsW3C = false
528
+ , fsElement
529
+ , isMod = false
530
+ , isCtrl = false
531
+ , eventableIframes
532
+ , i // i is reused for loops
533
+ , boundAutogrow;
534
+
535
+ // Startup is a way to check if this EpicEditor is starting up. Useful for
536
+ // checking and doing certain things before EpicEditor emits a load event.
537
+ self._eeState.startup = true;
538
+
539
+ if (self.settings.useNativeFullscreen) {
540
+ nativeFsWebkit = document.body.webkitRequestFullScreen ? true : false;
541
+ nativeFsMoz = document.body.mozRequestFullScreen ? true : false;
542
+ nativeFsW3C = document.body.requestFullscreen ? true : false;
543
+ nativeFs = nativeFsWebkit || nativeFsMoz || nativeFsW3C;
544
+ }
545
+
546
+ // Fucking Safari's native fullscreen works terribly
547
+ // REMOVE THIS IF SAFARI 7 WORKS BETTER
548
+ if (_isSafari()) {
549
+ nativeFs = false;
550
+ nativeFsWebkit = false;
551
+ }
552
+
553
+ // It opens edit mode by default (for now);
554
+ if (!self.is('edit') && !self.is('preview')) {
555
+ self._eeState.edit = true;
556
+ }
557
+
558
+ callback = callback || function () {};
559
+
560
+ // The editor HTML
561
+ // TODO: edit-mode class should be dynamically added
562
+ _HtmlTemplates = {
563
+ // This is wrapping iframe element. It contains the other two iframes and the utilbar
564
+ chrome: '<div id="epiceditor-wrapper" class="epiceditor-edit-mode">' +
565
+ '<iframe frameborder="0" id="epiceditor-editor-frame"></iframe>' +
566
+ '<iframe frameborder="0" id="epiceditor-previewer-frame"></iframe>' +
567
+ '<div id="epiceditor-utilbar">' +
568
+ (self._previewEnabled ? '<button title="' + this.settings.string.togglePreview + '" class="epiceditor-toggle-btn epiceditor-toggle-preview-btn"></button> ' : '') +
569
+ (self._editEnabled ? '<button title="' + this.settings.string.toggleEdit + '" class="epiceditor-toggle-btn epiceditor-toggle-edit-btn"></button> ' : '') +
570
+ (self._fullscreenEnabled ? '<button title="' + this.settings.string.toggleFullscreen + '" class="epiceditor-fullscreen-btn"></button>' : '') +
571
+ '</div>' +
572
+ '</div>'
573
+
574
+ // The previewer is just an empty box for the generated HTML to go into
575
+ , previewer: '<div id="epiceditor-preview"></div>'
576
+ , editor: '<!doctype HTML>'
577
+ };
578
+
579
+ // Write an iframe and then select it for the editor
580
+ iframeElement = document.createElement('iframe');
581
+ _applyAttrs(iframeElement, {
582
+ scrolling: 'no',
583
+ frameborder: 0,
584
+ id: self._instanceId
585
+ });
586
+
587
+
588
+ self.element.appendChild(iframeElement);
589
+
590
+ // Because browsers add things like invisible padding and margins and stuff
591
+ // to iframes, we need to set manually set the height so that the height
592
+ // doesn't keep increasing (by 2px?) every time reflow() is called.
593
+ // FIXME: Figure out how to fix this without setting this
594
+ self.element.style.height = self.element.offsetHeight + 'px';
595
+
596
+ // Store a reference to the iframeElement itself
597
+ self.iframeElement = iframeElement;
598
+
599
+ // Grab the innards of the iframe (returns the document.body)
600
+ // TODO: Change self.iframe to self.iframeDocument
601
+ self.iframe = _getIframeInnards(iframeElement);
602
+ self.iframe.open();
603
+ self.iframe.write(_HtmlTemplates.chrome);
604
+
605
+ // Now that we got the innards of the iframe, we can grab the other iframes
606
+ self.editorIframe = self.iframe.getElementById('epiceditor-editor-frame')
607
+ self.previewerIframe = self.iframe.getElementById('epiceditor-previewer-frame');
608
+
609
+ // Setup the editor iframe
610
+ self.editorIframeDocument = _getIframeInnards(self.editorIframe);
611
+ self.editorIframeDocument.open();
612
+ // Need something for... you guessed it, Firefox
613
+ self.editorIframeDocument.write(_HtmlTemplates.editor);
614
+ self.editorIframeDocument.close();
615
+
616
+ // Setup the previewer iframe
617
+ self.previewerIframeDocument = _getIframeInnards(self.previewerIframe);
618
+ self.previewerIframeDocument.open();
619
+ self.previewerIframeDocument.write(_HtmlTemplates.previewer);
620
+
621
+ // Base tag is added so that links will open a new tab and not inside of the iframes
622
+ baseTag = self.previewerIframeDocument.createElement('base');
623
+ baseTag.target = '_blank';
624
+ self.previewerIframeDocument.getElementsByTagName('head')[0].appendChild(baseTag);
625
+
626
+ self.previewerIframeDocument.close();
627
+
628
+ self.reflow();
629
+
630
+ // Insert Base Stylesheet
631
+ _insertCSSLink(self.settings.theme.base, self.iframe, 'theme');
632
+
633
+ // Insert Editor Stylesheet
634
+ _insertCSSLink(self.settings.theme.editor, self.editorIframeDocument, 'theme');
635
+
636
+ // Insert Previewer Stylesheet
637
+ _insertCSSLink(self.settings.theme.preview, self.previewerIframeDocument, 'theme');
638
+
639
+ // Add a relative style to the overall wrapper to keep CSS relative to the editor
640
+ self.iframe.getElementById('epiceditor-wrapper').style.position = 'relative';
641
+
642
+ // Set the position to relative so we hide them with left: -999999px
643
+ self.editorIframe.style.position = 'absolute';
644
+ self.previewerIframe.style.position = 'absolute';
645
+
646
+ // Now grab the editor and previewer for later use
647
+ self.editor = self.editorIframeDocument.body;
648
+ self.previewer = self.previewerIframeDocument.getElementById('epiceditor-preview');
649
+
650
+ self.editor.contentEditable = true;
651
+
652
+ // Firefox's <body> gets all fucked up so, to be sure, we need to hardcode it
653
+ self.iframe.body.style.height = this.element.offsetHeight + 'px';
654
+
655
+ // Should actually check what mode it's in!
656
+ self.previewerIframe.style.left = '-999999px';
657
+
658
+ // Keep long lines from being longer than the editor
659
+ this.editorIframeDocument.body.style.wordWrap = 'break-word';
660
+
661
+ // FIXME figure out why it needs +2 px
662
+ if (_isIE() > -1) {
663
+ this.previewer.style.height = parseInt(_getStyle(this.previewer, 'height'), 10) + 2;
664
+ }
665
+
666
+ // If there is a file to be opened with that filename and it has content...
667
+ this.open(self.settings.file.name);
668
+
669
+ if (self.settings.focusOnLoad) {
670
+ // We need to wait until all three iframes are done loading by waiting until the parent
671
+ // iframe's ready state == complete, then we can focus on the contenteditable
672
+ self.iframe.addEventListener('readystatechange', function () {
673
+ if (self.iframe.readyState == 'complete') {
674
+ self.focus();
675
+ }
676
+ });
677
+ }
678
+
679
+ // Because IE scrolls the whole window to hash links, we need our own
680
+ // method of scrolling the iframe to an ID from clicking a hash
681
+ self.previewerIframeDocument.addEventListener('click', function (e) {
682
+ var el = e.target
683
+ , body = self.previewerIframeDocument.body;
684
+ if (el.nodeName == 'A') {
685
+ // Make sure the link is a hash and the link is local to the iframe
686
+ if (el.hash && el.hostname == window.location.hostname) {
687
+ // Prevent the whole window from scrolling
688
+ e.preventDefault();
689
+ // Prevent opening a new window
690
+ el.target = '_self';
691
+ // Scroll to the matching element, if an element exists
692
+ if (body.querySelector(el.hash)) {
693
+ body.scrollTop = body.querySelector(el.hash).offsetTop;
694
+ }
695
+ }
696
+ }
697
+ });
698
+
699
+ utilBtns = self.iframe.getElementById('epiceditor-utilbar');
700
+
701
+ // TODO: Move into fullscreen setup function (_setupFullscreen)
702
+ _elementStates = {}
703
+ self._goFullscreen = function (el, callback) {
704
+ callback = callback || function () {};
705
+ var wait = 0;
706
+ this._fixScrollbars('auto');
707
+
708
+ if (self.is('fullscreen')) {
709
+ self._exitFullscreen(el, callback);
710
+ return;
711
+ }
712
+
713
+ if (nativeFs) {
714
+ if (nativeFsWebkit) {
715
+ el.webkitRequestFullScreen();
716
+ wait = 750;
717
+ }
718
+ else if (nativeFsMoz) {
719
+ el.mozRequestFullScreen();
720
+ }
721
+ else if (nativeFsW3C) {
722
+ el.requestFullscreen();
723
+ }
724
+ }
725
+
726
+ _isInEdit = self.is('edit');
727
+
728
+
729
+ // Why does this need to be in a randomly "750"ms setTimeout? WebKit's
730
+ // implementation of fullscreen seem to trigger the webkitfullscreenchange
731
+ // event _after_ everything is done. Instead, it triggers _during_ the
732
+ // transition. This means calculations of what's half, 100%, etc are wrong
733
+ // so to combat this we throw down the hammer with a setTimeout and wait
734
+ // to trigger our calculation code.
735
+ // See: https://code.google.com/p/chromium/issues/detail?id=181116
736
+ setTimeout(function () {
737
+ // Set the state of EE in fullscreen
738
+ // We set edit and preview to true also because they're visible
739
+ // we might want to allow fullscreen edit mode without preview (like a "zen" mode)
740
+ self._eeState.fullscreen = true;
741
+ self._eeState.edit = true;
742
+ self._eeState.preview = true;
743
+
744
+ // Cache calculations
745
+ var windowInnerWidth = window.innerWidth
746
+ , windowInnerHeight = window.innerHeight
747
+ , windowOuterWidth = window.outerWidth
748
+ , windowOuterHeight = window.outerHeight;
749
+
750
+ // Without this the scrollbars will get hidden when scrolled to the bottom in faux fullscreen (see #66)
751
+ if (!nativeFs) {
752
+ windowOuterHeight = window.innerHeight;
753
+ }
754
+
755
+ // This MUST come first because the editor is 100% width so if we change the width of the iframe or wrapper
756
+ // the editor's width wont be the same as before
757
+ _elementStates.editorIframe = _saveStyleState(self.editorIframe, 'save', {
758
+ 'width': windowOuterWidth / 2 + 'px'
759
+ , 'height': windowOuterHeight + 'px'
760
+ , 'float': 'left' // Most browsers
761
+ , 'cssFloat': 'left' // FF
762
+ , 'styleFloat': 'left' // Older IEs
763
+ , 'display': 'block'
764
+ , 'position': 'static'
765
+ , 'left': ''
766
+ });
767
+
768
+ // the previewer
769
+ _elementStates.previewerIframe = _saveStyleState(self.previewerIframe, 'save', {
770
+ 'width': windowOuterWidth / 2 + 'px'
771
+ , 'height': windowOuterHeight + 'px'
772
+ , 'float': 'right' // Most browsers
773
+ , 'cssFloat': 'right' // FF
774
+ , 'styleFloat': 'right' // Older IEs
775
+ , 'display': 'block'
776
+ , 'position': 'static'
777
+ , 'left': ''
778
+ });
779
+
780
+ // Setup the containing element CSS for fullscreen
781
+ _elementStates.element = _saveStyleState(self.element, 'save', {
782
+ 'position': 'fixed'
783
+ , 'top': '0'
784
+ , 'left': '0'
785
+ , 'width': '100%'
786
+ , 'z-index': '9999' // Most browsers
787
+ , 'zIndex': '9999' // Firefox
788
+ , 'border': 'none'
789
+ , 'margin': '0'
790
+ // Should use the base styles background!
791
+ , 'background': _getStyle(self.editor, 'background-color') // Try to hide the site below
792
+ , 'height': windowInnerHeight + 'px'
793
+ });
794
+
795
+ // The iframe element
796
+ _elementStates.iframeElement = _saveStyleState(self.iframeElement, 'save', {
797
+ 'width': windowOuterWidth + 'px'
798
+ , 'height': windowInnerHeight + 'px'
799
+ });
800
+
801
+ // ...Oh, and hide the buttons and prevent scrolling
802
+ utilBtns.style.visibility = 'hidden';
803
+
804
+ if (!nativeFs) {
805
+ document.body.style.overflow = 'hidden';
806
+ }
807
+
808
+ self.preview();
809
+
810
+ self.focus();
811
+
812
+ self.emit('fullscreenenter');
813
+
814
+ callback.call(self);
815
+ }, wait);
816
+
817
+ };
818
+
819
+ self._exitFullscreen = function (el, callback) {
820
+ callback = callback || function () {};
821
+ this._fixScrollbars();
822
+
823
+ _saveStyleState(self.element, 'apply', _elementStates.element);
824
+ _saveStyleState(self.iframeElement, 'apply', _elementStates.iframeElement);
825
+ _saveStyleState(self.editorIframe, 'apply', _elementStates.editorIframe);
826
+ _saveStyleState(self.previewerIframe, 'apply', _elementStates.previewerIframe);
827
+
828
+ // We want to always revert back to the original styles in the CSS so,
829
+ // if it's a fluid width container it will expand on resize and not get
830
+ // stuck at a specific width after closing fullscreen.
831
+ self.element.style.width = self._eeState.reflowWidth ? self._eeState.reflowWidth : '';
832
+ self.element.style.height = self._eeState.reflowHeight ? self._eeState.reflowHeight : '';
833
+
834
+ utilBtns.style.visibility = 'visible';
835
+
836
+ // Put the editor back in the right state
837
+ // TODO: This is ugly... how do we make this nicer?
838
+ // setting fullscreen to false here prevents the
839
+ // native fs callback from calling this function again
840
+ self._eeState.fullscreen = false;
841
+
842
+ if (!nativeFs) {
843
+ document.body.style.overflow = 'auto';
844
+ }
845
+ else {
846
+ if (nativeFsWebkit) {
847
+ document.webkitCancelFullScreen();
848
+ }
849
+ else if (nativeFsMoz) {
850
+ document.mozCancelFullScreen();
851
+ }
852
+ else if (nativeFsW3C) {
853
+ document.exitFullscreen();
854
+ }
855
+ }
856
+
857
+ if (_isInEdit) {
858
+ self.edit();
859
+ }
860
+ else {
861
+ self.preview();
862
+ }
863
+
864
+ self.reflow();
865
+
866
+ self.emit('fullscreenexit');
867
+
868
+ callback.call(self);
869
+ };
870
+
871
+ // This setups up live previews by triggering preview() IF in fullscreen on keyup
872
+ self.editor.addEventListener('keyup', function () {
873
+ if (keypressTimer) {
874
+ window.clearTimeout(keypressTimer);
875
+ }
876
+ keypressTimer = window.setTimeout(function () {
877
+ if (self.is('fullscreen')) {
878
+ self.preview();
879
+ }
880
+ }, 250);
881
+ });
882
+
883
+ fsElement = self.iframeElement;
884
+
885
+ // Sets up the onclick event on utility buttons
886
+ utilBtns.addEventListener('click', function (e) {
887
+ var targetClass = e.target.className;
888
+ if (targetClass.indexOf('epiceditor-toggle-preview-btn') > -1) {
889
+ self.preview();
890
+ }
891
+ else if (targetClass.indexOf('epiceditor-toggle-edit-btn') > -1) {
892
+ self.edit();
893
+ }
894
+ else if (targetClass.indexOf('epiceditor-fullscreen-btn') > -1) {
895
+ self._goFullscreen(fsElement);
896
+ }
897
+ });
898
+
899
+ // Sets up the NATIVE fullscreen editor/previewer for WebKit
900
+ if (nativeFsWebkit) {
901
+ document.addEventListener('webkitfullscreenchange', function () {
902
+ if (!document.webkitIsFullScreen && self._eeState.fullscreen) {
903
+ self._exitFullscreen(fsElement);
904
+ }
905
+ }, false);
906
+ }
907
+ else if (nativeFsMoz) {
908
+ document.addEventListener('mozfullscreenchange', function () {
909
+ if (!document.mozFullScreen && self._eeState.fullscreen) {
910
+ self._exitFullscreen(fsElement);
911
+ }
912
+ }, false);
913
+ }
914
+ else if (nativeFsW3C) {
915
+ document.addEventListener('fullscreenchange', function () {
916
+ if (document.fullscreenElement == null && self._eeState.fullscreen) {
917
+ self._exitFullscreen(fsElement);
918
+ }
919
+ }, false);
920
+ }
921
+
922
+ // TODO: Move utilBar stuff into a utilBar setup function (_setupUtilBar)
923
+ utilBar = self.iframe.getElementById('epiceditor-utilbar');
924
+
925
+ // Hide it at first until they move their mouse
926
+ if (self.settings.button.bar !== true) {
927
+ utilBar.style.display = 'none';
928
+ }
929
+
930
+ utilBar.addEventListener('mouseover', function () {
931
+ if (utilBarTimer) {
932
+ clearTimeout(utilBarTimer);
933
+ }
934
+ });
935
+
936
+ function utilBarHandler(e) {
937
+ if (self.settings.button.bar !== "auto") {
938
+ return;
939
+ }
940
+ // Here we check if the mouse has moves more than 5px in any direction before triggering the mousemove code
941
+ // we do this for 2 reasons:
942
+ // 1. On Mac OS X lion when you scroll and it does the iOS like "jump" when it hits the top/bottom of the page itll fire off
943
+ // a mousemove of a few pixels depending on how hard you scroll
944
+ // 2. We give a slight buffer to the user in case he barely touches his touchpad or mouse and not trigger the UI
945
+ if (Math.abs(mousePos.y - e.pageY) >= 5 || Math.abs(mousePos.x - e.pageX) >= 5) {
946
+ utilBar.style.display = 'block';
947
+ // if we have a timer already running, kill it out
948
+ if (utilBarTimer) {
949
+ clearTimeout(utilBarTimer);
950
+ }
951
+
952
+ // begin a new timer that hides our object after 1000 ms
953
+ utilBarTimer = window.setTimeout(function () {
954
+ utilBar.style.display = 'none';
955
+ }, 1000);
956
+ }
957
+ mousePos = { y: e.pageY, x: e.pageX };
958
+ }
959
+
960
+ // Add keyboard shortcuts for convenience.
961
+ function shortcutHandler(e) {
962
+ if (e.keyCode == self.settings.shortcut.modifier) { isMod = true } // check for modifier press(default is alt key), save to var
963
+ if (e.keyCode == 17) { isCtrl = true } // check for ctrl/cmnd press, in order to catch ctrl/cmnd + s
964
+ if (e.keyCode == 18) { isCtrl = false }
965
+
966
+ // Check for alt+p and make sure were not in fullscreen - default shortcut to switch to preview
967
+ if (isMod === true && e.keyCode == self.settings.shortcut.preview && !self.is('fullscreen')) {
968
+ e.preventDefault();
969
+ if (self.is('edit') && self._previewEnabled) {
970
+ self.preview();
971
+ }
972
+ else if (self._editEnabled) {
973
+ self.edit();
974
+ }
975
+ }
976
+ // Check for alt+f - default shortcut to make editor fullscreen
977
+ if (isMod === true && e.keyCode == self.settings.shortcut.fullscreen && self._fullscreenEnabled) {
978
+ e.preventDefault();
979
+ self._goFullscreen(fsElement);
980
+ }
981
+
982
+ // Set the modifier key to false once *any* key combo is completed
983
+ // or else, on Windows, hitting the alt key will lock the isMod state to true (ticket #133)
984
+ if (isMod === true && e.keyCode !== self.settings.shortcut.modifier) {
985
+ isMod = false;
986
+ }
987
+
988
+ // When a user presses "esc", revert everything!
989
+ if (e.keyCode == 27 && self.is('fullscreen')) {
990
+ self._exitFullscreen(fsElement);
991
+ }
992
+
993
+ // Check for ctrl + s (since a lot of people do it out of habit) and make it do nothing
994
+ if (isCtrl === true && e.keyCode == 83) {
995
+ self.save();
996
+ e.preventDefault();
997
+ isCtrl = false;
998
+ }
999
+
1000
+ // Do the same for Mac now (metaKey == cmd).
1001
+ if (e.metaKey && e.keyCode == 83) {
1002
+ self.save();
1003
+ e.preventDefault();
1004
+ }
1005
+
1006
+ }
1007
+
1008
+ function shortcutUpHandler(e) {
1009
+ if (e.keyCode == self.settings.shortcut.modifier) { isMod = false }
1010
+ if (e.keyCode == 17) { isCtrl = false }
1011
+ }
1012
+
1013
+ function pasteHandler(e) {
1014
+ var content;
1015
+ if (e.clipboardData) {
1016
+ //FF 22, Webkit, "standards"
1017
+ e.preventDefault();
1018
+ content = e.clipboardData.getData("text/plain");
1019
+ self.editorIframeDocument.execCommand("insertText", false, content);
1020
+ }
1021
+ else if (window.clipboardData) {
1022
+ //IE, "nasty"
1023
+ e.preventDefault();
1024
+ content = window.clipboardData.getData("Text");
1025
+ content = content.replace(/</g, '&lt;');
1026
+ content = content.replace(/>/g, '&gt;');
1027
+ content = content.replace(/\n/g, '<br>');
1028
+ content = content.replace(/\r/g, ''); //fuck you, ie!
1029
+ content = content.replace(/<br>\s/g, '<br>&nbsp;')
1030
+ content = content.replace(/\s\s\s/g, '&nbsp; &nbsp;')
1031
+ content = content.replace(/\s\s/g, '&nbsp; ')
1032
+ self.editorIframeDocument.selection.createRange().pasteHTML(content);
1033
+ }
1034
+ }
1035
+
1036
+ // Hide and show the util bar based on mouse movements
1037
+ eventableIframes = [self.previewerIframeDocument, self.editorIframeDocument];
1038
+
1039
+ for (i = 0; i < eventableIframes.length; i++) {
1040
+ eventableIframes[i].addEventListener('mousemove', function (e) {
1041
+ utilBarHandler(e);
1042
+ });
1043
+ eventableIframes[i].addEventListener('scroll', function (e) {
1044
+ utilBarHandler(e);
1045
+ });
1046
+ eventableIframes[i].addEventListener('keyup', function (e) {
1047
+ shortcutUpHandler(e);
1048
+ });
1049
+ eventableIframes[i].addEventListener('keydown', function (e) {
1050
+ shortcutHandler(e);
1051
+ });
1052
+ eventableIframes[i].addEventListener('paste', function (e) {
1053
+ pasteHandler(e);
1054
+ });
1055
+ }
1056
+
1057
+ // Save the document every 100ms by default
1058
+ // TODO: Move into autosave setup function (_setupAutoSave)
1059
+ if (self.settings.file.autoSave) {
1060
+ self._saveIntervalTimer = window.setInterval(function () {
1061
+ if (!self._canSave) {
1062
+ return;
1063
+ }
1064
+ self.save(false, true);
1065
+ }, self.settings.file.autoSave);
1066
+ }
1067
+
1068
+ // Update a textarea automatically if a textarea is given so you don't need
1069
+ // AJAX to submit a form and instead fall back to normal form behavior
1070
+ if (self.settings.textarea) {
1071
+ self._setupTextareaSync();
1072
+ }
1073
+
1074
+ window.addEventListener('resize', function () {
1075
+ // If NOT webkit, and in fullscreen, we need to account for browser resizing
1076
+ // we don't care about webkit because you can't resize in webkit's fullscreen
1077
+ if (self.is('fullscreen')) {
1078
+ _applyStyles(self.iframeElement, {
1079
+ 'width': window.outerWidth + 'px'
1080
+ , 'height': window.innerHeight + 'px'
1081
+ });
1082
+
1083
+ _applyStyles(self.element, {
1084
+ 'height': window.innerHeight + 'px'
1085
+ });
1086
+
1087
+ _applyStyles(self.previewerIframe, {
1088
+ 'width': window.outerWidth / 2 + 'px'
1089
+ , 'height': window.innerHeight + 'px'
1090
+ });
1091
+
1092
+ _applyStyles(self.editorIframe, {
1093
+ 'width': window.outerWidth / 2 + 'px'
1094
+ , 'height': window.innerHeight + 'px'
1095
+ });
1096
+ }
1097
+ // Makes the editor support fluid width when not in fullscreen mode
1098
+ else if (!self.is('fullscreen')) {
1099
+ self.reflow();
1100
+ }
1101
+ });
1102
+
1103
+ // Set states before flipping edit and preview modes
1104
+ self._eeState.loaded = true;
1105
+ self._eeState.unloaded = false;
1106
+
1107
+ if (self.is('preview')) {
1108
+ self.preview();
1109
+ }
1110
+ else {
1111
+ self.edit();
1112
+ }
1113
+
1114
+ self.iframe.close();
1115
+ self._eeState.startup = false;
1116
+
1117
+ if (self.settings.autogrow) {
1118
+ self._fixScrollbars();
1119
+
1120
+ boundAutogrow = function () {
1121
+ setTimeout(function () {
1122
+ self._autogrow();
1123
+ }, 1);
1124
+ };
1125
+
1126
+ //for if autosave is disabled or very slow
1127
+ ['keydown', 'keyup', 'paste', 'cut'].forEach(function (ev) {
1128
+ self.getElement('editor').addEventListener(ev, boundAutogrow);
1129
+ });
1130
+
1131
+ self.on('__update', boundAutogrow);
1132
+ self.on('edit', function () {
1133
+ setTimeout(boundAutogrow, 50)
1134
+ });
1135
+ self.on('preview', function () {
1136
+ setTimeout(boundAutogrow, 50)
1137
+ });
1138
+
1139
+ //for browsers that have rendering delays
1140
+ setTimeout(boundAutogrow, 50);
1141
+ boundAutogrow();
1142
+ }
1143
+
1144
+ // The callback and call are the same thing, but different ways to access them
1145
+ callback.call(this);
1146
+ this.emit('load');
1147
+ return this;
1148
+ }
1149
+
1150
+ EpicEditor.prototype._setupTextareaSync = function () {
1151
+ var self = this
1152
+ , _syncTextarea;
1153
+
1154
+ // Even if autoSave is false, we want to make sure to keep the textarea synced
1155
+ // with the editor's content. One bad thing about this tho is that we're
1156
+ // creating two timers now in some configurations. We keep the textarea synced
1157
+ // by saving and opening the textarea content from the draft file storage.
1158
+ self._textareaSaveTimer = window.setInterval(function () {
1159
+ if (!self._canSave) {
1160
+ return;
1161
+ }
1162
+ self.save(true);
1163
+ }, 100);
1164
+
1165
+ _syncTextarea = function () {
1166
+ // TODO: Figure out root cause for having to do this ||.
1167
+ // This only happens for draft files. Probably has something to do with
1168
+ // the fact draft files haven't been saved by the time this is called.
1169
+ // TODO: Add test for this case.
1170
+ // Get the file.name each time as it can change. DO NOT save this to a
1171
+ // var outside of this closure or the editor will stop syncing when the
1172
+ // file is changed with importFile or open.
1173
+ self._textareaElement.value = self.exportFile(self.settings.file.name, 'text', true) || self.settings.file.defaultContent;
1174
+ }
1175
+
1176
+ if (typeof self.settings.textarea == 'string') {
1177
+ self._textareaElement = document.getElementById(self.settings.textarea);
1178
+ }
1179
+ else if (typeof self.settings.textarea == 'object') {
1180
+ self._textareaElement = self.settings.textarea;
1181
+ }
1182
+
1183
+ // On page load, if there's content in the textarea that means one of two
1184
+ // different things:
1185
+ //
1186
+ // 1. The editor didn't load and the user was writing in the textarea and
1187
+ // now he refreshed the page or the JS loaded and the textarea now has
1188
+ // content. If this is the case the user probably expects his content is
1189
+ // moved into the editor and not lose what he typed.
1190
+ //
1191
+ // 2. The developer put content in the textarea from some server side
1192
+ // code. In this case, the textarea will take precedence.
1193
+ //
1194
+ // If the developer wants drafts to be recoverable they should check if
1195
+ // the local file in localStorage's modified date is newer than the server.
1196
+ if (self._textareaElement.value !== '') {
1197
+ self.importFile(self.settings.file.name, self._textareaElement.value);
1198
+
1199
+ // manually save draft after import so there is no delay between the
1200
+ // import and exporting in _syncTextarea. Without this, _syncTextarea
1201
+ // will pull the saved data from localStorage which will be <=100ms old.
1202
+ self.save(true);
1203
+ }
1204
+
1205
+ // Update the textarea on load and pull from drafts
1206
+ _syncTextarea();
1207
+
1208
+ // Make sure to keep it updated
1209
+ self.on('__update', _syncTextarea);
1210
+ self.on('__create', _syncTextarea);
1211
+ self.on('__save', _syncTextarea);
1212
+ }
1213
+
1214
+ /**
1215
+ * Will NOT focus the editor if the editor is still starting up AND
1216
+ * focusOnLoad is set to false. This allows you to place this in code that
1217
+ * gets fired during .load() without worrying about it overriding the user's
1218
+ * option. For example use cases see preview() and edit().
1219
+ * @returns {undefined}
1220
+ */
1221
+
1222
+ // Prevent focus when the user sets focusOnLoad to false by checking if the
1223
+ // editor is starting up AND if focusOnLoad is true
1224
+ EpicEditor.prototype._focusExceptOnLoad = function () {
1225
+ var self = this;
1226
+ if ((self._eeState.startup && self.settings.focusOnLoad) || !self._eeState.startup) {
1227
+ self.focus();
1228
+ }
1229
+ }
1230
+
1231
+ /**
1232
+ * Will remove the editor, but not offline files
1233
+ * @returns {object} EpicEditor will be returned
1234
+ */
1235
+ EpicEditor.prototype.unload = function (callback) {
1236
+
1237
+ // Make sure the editor isn't already unloaded.
1238
+ if (this.is('unloaded')) {
1239
+ throw new Error('Editor isn\'t loaded');
1240
+ }
1241
+
1242
+ var self = this
1243
+ , editor = window.parent.document.getElementById(self._instanceId);
1244
+
1245
+ editor.parentNode.removeChild(editor);
1246
+ self._eeState.loaded = false;
1247
+ self._eeState.unloaded = true;
1248
+ callback = callback || function () {};
1249
+
1250
+ if (self.settings.textarea) {
1251
+ self.removeListener('__update');
1252
+ }
1253
+
1254
+ if (self._saveIntervalTimer) {
1255
+ window.clearInterval(self._saveIntervalTimer);
1256
+ }
1257
+ if (self._textareaSaveTimer) {
1258
+ window.clearInterval(self._textareaSaveTimer);
1259
+ }
1260
+
1261
+ callback.call(this);
1262
+ self.emit('unload');
1263
+ return self;
1264
+ }
1265
+
1266
+ /**
1267
+ * reflow allows you to dynamically re-fit the editor in the parent without
1268
+ * having to unload and then reload the editor again.
1269
+ *
1270
+ * reflow will also emit a `reflow` event and will return the new dimensions.
1271
+ * If it's called without params it'll return the new width and height and if
1272
+ * it's called with just width or just height it'll just return the width or
1273
+ * height. It's returned as an object like: { width: '100px', height: '1px' }
1274
+ *
1275
+ * @param {string|null} kind Can either be 'width' or 'height' or null
1276
+ * if null, both the height and width will be resized
1277
+ * @param {function} callback A function to fire after the reflow is finished.
1278
+ * Will return the width / height in an obj as the first param of the callback.
1279
+ * @returns {object} EpicEditor will be returned
1280
+ */
1281
+ EpicEditor.prototype.reflow = function (kind, callback) {
1282
+ var self = this
1283
+ , widthDiff = _outerWidth(self.element) - self.element.offsetWidth
1284
+ , heightDiff = _outerHeight(self.element) - self.element.offsetHeight
1285
+ , elements = [self.iframeElement, self.editorIframe, self.previewerIframe]
1286
+ , eventData = {}
1287
+ , newWidth
1288
+ , newHeight;
1289
+
1290
+ if (typeof kind == 'function') {
1291
+ callback = kind;
1292
+ kind = null;
1293
+ }
1294
+
1295
+ if (!callback) {
1296
+ callback = function () {};
1297
+ }
1298
+
1299
+ for (var x = 0; x < elements.length; x++) {
1300
+ if (!kind || kind == 'width') {
1301
+ newWidth = self.element.offsetWidth - widthDiff + 'px';
1302
+ elements[x].style.width = newWidth;
1303
+ self._eeState.reflowWidth = newWidth;
1304
+ eventData.width = newWidth;
1305
+ }
1306
+ if (!kind || kind == 'height') {
1307
+ newHeight = self.element.offsetHeight - heightDiff + 'px';
1308
+ elements[x].style.height = newHeight;
1309
+ self._eeState.reflowHeight = newHeight
1310
+ eventData.height = newHeight;
1311
+ }
1312
+ }
1313
+
1314
+ self.emit('reflow', eventData);
1315
+ callback.call(this, eventData);
1316
+ return self;
1317
+ }
1318
+
1319
+ /**
1320
+ * Will take the markdown and generate a preview view based on the theme
1321
+ * @returns {object} EpicEditor will be returned
1322
+ */
1323
+ EpicEditor.prototype.preview = function () {
1324
+ var self = this
1325
+ , x
1326
+ , theme = self.settings.theme.preview
1327
+ , anchors;
1328
+
1329
+ _replaceClass(self.getElement('wrapper'), 'epiceditor-edit-mode', 'epiceditor-preview-mode');
1330
+
1331
+ // Check if no CSS theme link exists
1332
+ if (!self.previewerIframeDocument.getElementById('theme')) {
1333
+ _insertCSSLink(theme, self.previewerIframeDocument, 'theme');
1334
+ }
1335
+ else if (self.previewerIframeDocument.getElementById('theme').name !== theme) {
1336
+ self.previewerIframeDocument.getElementById('theme').href = theme;
1337
+ }
1338
+
1339
+ // Save a preview draft since it might not be saved to the real file yet
1340
+ self.save(true);
1341
+
1342
+ // Add the generated draft HTML into the previewer
1343
+ self.previewer.innerHTML = self.exportFile(null, 'html', true);
1344
+
1345
+ // Hide the editor and display the previewer
1346
+ if (!self.is('fullscreen')) {
1347
+ self.editorIframe.style.left = '-999999px';
1348
+ self.previewerIframe.style.left = '';
1349
+ self._eeState.preview = true;
1350
+ self._eeState.edit = false;
1351
+ self._focusExceptOnLoad();
1352
+ }
1353
+
1354
+ self.emit('preview');
1355
+ return self;
1356
+ }
1357
+
1358
+ /**
1359
+ * Helper to focus on the editor iframe. Will figure out which iframe to
1360
+ * focus on based on which one is active and will handle the cross browser
1361
+ * issues with focusing on the iframe vs the document body.
1362
+ * @returns {object} EpicEditor will be returned
1363
+ */
1364
+ EpicEditor.prototype.focus = function (pageload) {
1365
+ var self = this
1366
+ , isPreview = self.is('preview')
1367
+ , focusElement = isPreview ? self.previewerIframeDocument.body
1368
+ : self.editorIframeDocument.body;
1369
+
1370
+ if (_isFirefox() && isPreview) {
1371
+ focusElement = self.previewerIframe;
1372
+ }
1373
+
1374
+ focusElement.focus();
1375
+ return this;
1376
+ }
1377
+
1378
+ /**
1379
+ * Puts the editor into fullscreen mode
1380
+ * @returns {object} EpicEditor will be returned
1381
+ */
1382
+ EpicEditor.prototype.enterFullscreen = function (callback) {
1383
+ callback = callback || function () {};
1384
+ if (this.is('fullscreen')) {
1385
+ callback.call(this);
1386
+ return this;
1387
+ }
1388
+ this._goFullscreen(this.iframeElement, callback);
1389
+ return this;
1390
+ }
1391
+
1392
+ /**
1393
+ * Closes fullscreen mode if opened
1394
+ * @returns {object} EpicEditor will be returned
1395
+ */
1396
+ EpicEditor.prototype.exitFullscreen = function (callback) {
1397
+ callback = callback || function () {};
1398
+ if (!this.is('fullscreen')) {
1399
+ callback.call(this);
1400
+ return this;
1401
+ }
1402
+ this._exitFullscreen(this.iframeElement, callback);
1403
+ return this;
1404
+ }
1405
+
1406
+ /**
1407
+ * Hides the preview and shows the editor again
1408
+ * @returns {object} EpicEditor will be returned
1409
+ */
1410
+ EpicEditor.prototype.edit = function () {
1411
+ var self = this;
1412
+ _replaceClass(self.getElement('wrapper'), 'epiceditor-preview-mode', 'epiceditor-edit-mode');
1413
+ self._eeState.preview = false;
1414
+ self._eeState.edit = true;
1415
+ self.editorIframe.style.left = '';
1416
+ self.previewerIframe.style.left = '-999999px';
1417
+ self._focusExceptOnLoad();
1418
+ self.emit('edit');
1419
+ return this;
1420
+ }
1421
+
1422
+ /**
1423
+ * Grabs a specificed HTML node. Use it as a shortcut to getting the iframe contents
1424
+ * @param {String} name The name of the node (can be document, body, editor, previewer, or wrapper)
1425
+ * @returns {Object|Null}
1426
+ */
1427
+ EpicEditor.prototype.getElement = function (name) {
1428
+ var available = {
1429
+ "container": this.element
1430
+ , "wrapper": this.iframe.getElementById('epiceditor-wrapper')
1431
+ , "wrapperIframe": this.iframeElement
1432
+ , "editor": this.editorIframeDocument
1433
+ , "editorIframe": this.editorIframe
1434
+ , "previewer": this.previewerIframeDocument
1435
+ , "previewerIframe": this.previewerIframe
1436
+ }
1437
+
1438
+ // Check that the given string is a possible option and verify the editor isn't unloaded
1439
+ // without this, you'd be given a reference to an object that no longer exists in the DOM
1440
+ if (!available[name] || this.is('unloaded')) {
1441
+ return null;
1442
+ }
1443
+ else {
1444
+ return available[name];
1445
+ }
1446
+ }
1447
+
1448
+ /**
1449
+ * Returns a boolean of each "state" of the editor. For example "editor.is('loaded')" // returns true/false
1450
+ * @param {String} what the state you want to check for
1451
+ * @returns {Boolean}
1452
+ */
1453
+ EpicEditor.prototype.is = function (what) {
1454
+ var self = this;
1455
+ switch (what) {
1456
+ case 'loaded':
1457
+ return self._eeState.loaded;
1458
+ case 'unloaded':
1459
+ return self._eeState.unloaded
1460
+ case 'preview':
1461
+ return self._eeState.preview
1462
+ case 'edit':
1463
+ return self._eeState.edit;
1464
+ case 'fullscreen':
1465
+ return self._eeState.fullscreen;
1466
+ // TODO: This "works", but the tests are saying otherwise. Come back to this
1467
+ // and figure out how to fix it.
1468
+ // case 'focused':
1469
+ // return document.activeElement == self.iframeElement;
1470
+ default:
1471
+ return false;
1472
+ }
1473
+ }
1474
+
1475
+ /**
1476
+ * Opens a file
1477
+ * @param {string} name The name of the file you want to open
1478
+ * @returns {object} EpicEditor will be returned
1479
+ */
1480
+ EpicEditor.prototype.open = function (name) {
1481
+ var self = this
1482
+ , defaultContent = self.settings.file.defaultContent
1483
+ , fileObj;
1484
+ name = name || self.settings.file.name;
1485
+ self.settings.file.name = name;
1486
+ if (this._storage[self.settings.localStorageName]) {
1487
+ fileObj = self.exportFile(name);
1488
+ if (fileObj !== undefined) {
1489
+ _setText(self.editor, fileObj);
1490
+ self.emit('read');
1491
+ }
1492
+ else {
1493
+ _setText(self.editor, defaultContent);
1494
+ self.save(); // ensure a save
1495
+ self.emit('create');
1496
+ }
1497
+ self.previewer.innerHTML = self.exportFile(null, 'html');
1498
+ self.emit('open');
1499
+ }
1500
+ return this;
1501
+ }
1502
+
1503
+ /**
1504
+ * Saves content for offline use
1505
+ * @returns {object} EpicEditor will be returned
1506
+ */
1507
+ EpicEditor.prototype.save = function (_isPreviewDraft, _isAuto) {
1508
+ var self = this
1509
+ , storage
1510
+ , isUpdate = false
1511
+ , isNew = false
1512
+ , file = self.settings.file.name
1513
+ , previewDraftName = ''
1514
+ , data = this._storage[previewDraftName + self.settings.localStorageName]
1515
+ , content = _getText(this.editor);
1516
+
1517
+ if (_isPreviewDraft) {
1518
+ previewDraftName = self._previewDraftLocation;
1519
+ }
1520
+
1521
+ // This could have been false but since we're manually saving
1522
+ // we know it's save to start autoSaving again
1523
+ this._canSave = true;
1524
+
1525
+ // Guard against storage being wiped out without EpicEditor knowing
1526
+ // TODO: Emit saving error - storage seems to have been wiped
1527
+ if (data) {
1528
+ storage = JSON.parse(this._storage[previewDraftName + self.settings.localStorageName]);
1529
+
1530
+ // If the file doesn't exist we need to create it
1531
+ if (storage[file] === undefined) {
1532
+ storage[file] = self._defaultFileSchema();
1533
+ isNew = true;
1534
+ }
1535
+
1536
+ // If it does, we need to check if the content is different and
1537
+ // if it is, send the update event and update the timestamp
1538
+ else if (content !== storage[file].content) {
1539
+ storage[file].modified = new Date();
1540
+ isUpdate = true;
1541
+ }
1542
+ //don't bother autosaving if the content hasn't actually changed
1543
+ else if (_isAuto) {
1544
+ return;
1545
+ }
1546
+
1547
+ storage[file].content = content;
1548
+ this._storage[previewDraftName + self.settings.localStorageName] = JSON.stringify(storage);
1549
+
1550
+ // If it's a new file, send a create event as well as a private one for
1551
+ // use internally.
1552
+ if (isNew) {
1553
+ self.emit('create');
1554
+ self.emit('__create');
1555
+ }
1556
+
1557
+ // After the content is actually changed, emit update so it emits the
1558
+ // updated content. Also send a private event for interal use.
1559
+ if (isUpdate) {
1560
+ self.emit('update');
1561
+ self.emit('__update');
1562
+ }
1563
+
1564
+ if (_isAuto) {
1565
+ this.emit('autosave');
1566
+ }
1567
+ else if (!_isPreviewDraft) {
1568
+ this.emit('save');
1569
+ self.emit('__save');
1570
+ }
1571
+ }
1572
+
1573
+ return this;
1574
+ }
1575
+
1576
+ /**
1577
+ * Removes a page
1578
+ * @param {string} name The name of the file you want to remove from localStorage
1579
+ * @returns {object} EpicEditor will be returned
1580
+ */
1581
+ EpicEditor.prototype.remove = function (name) {
1582
+ var self = this
1583
+ , s;
1584
+ name = name || self.settings.file.name;
1585
+
1586
+ // If you're trying to delete a page you have open, block saving
1587
+ if (name == self.settings.file.name) {
1588
+ self._canSave = false;
1589
+ }
1590
+
1591
+ s = JSON.parse(this._storage[self.settings.localStorageName]);
1592
+ delete s[name];
1593
+ this._storage[self.settings.localStorageName] = JSON.stringify(s);
1594
+ this.emit('remove');
1595
+ return this;
1596
+ };
1597
+
1598
+ /**
1599
+ * Renames a file
1600
+ * @param {string} oldName The old file name
1601
+ * @param {string} newName The new file name
1602
+ * @returns {object} EpicEditor will be returned
1603
+ */
1604
+ EpicEditor.prototype.rename = function (oldName, newName) {
1605
+ var self = this
1606
+ , s = JSON.parse(this._storage[self.settings.localStorageName]);
1607
+ s[newName] = s[oldName];
1608
+ delete s[oldName];
1609
+ this._storage[self.settings.localStorageName] = JSON.stringify(s);
1610
+ self.open(newName);
1611
+ return this;
1612
+ };
1613
+
1614
+ /**
1615
+ * Imports a file and it's contents and opens it
1616
+ * @param {string} name The name of the file you want to import (will overwrite existing files!)
1617
+ * @param {string} content Content of the file you want to import
1618
+ * @param {string} kind The kind of file you want to import (TBI)
1619
+ * @param {object} meta Meta data you want to save with your file.
1620
+ * @returns {object} EpicEditor will be returned
1621
+ */
1622
+ EpicEditor.prototype.importFile = function (name, content, kind, meta) {
1623
+ var self = this;
1624
+
1625
+ name = name || self.settings.file.name;
1626
+ content = content || '';
1627
+ kind = kind || 'md';
1628
+ meta = meta || {};
1629
+
1630
+ // Set our current file to the new file and update the content
1631
+ self.settings.file.name = name;
1632
+ _setText(self.editor, content);
1633
+
1634
+ self.save();
1635
+
1636
+ if (self.is('fullscreen')) {
1637
+ self.preview();
1638
+ }
1639
+
1640
+ //firefox has trouble with importing and working out the size right away
1641
+ if (self.settings.autogrow) {
1642
+ setTimeout(function () {
1643
+ self._autogrow();
1644
+ }, 50);
1645
+ }
1646
+
1647
+ return this;
1648
+ };
1649
+
1650
+ /**
1651
+ * Gets the local filestore
1652
+ * @param {string} name Name of the file in the store
1653
+ * @returns {object|undefined} the local filestore, or a specific file in the store, if a name is given
1654
+ */
1655
+ EpicEditor.prototype._getFileStore = function (name, _isPreviewDraft) {
1656
+ var previewDraftName = ''
1657
+ , store;
1658
+ if (_isPreviewDraft) {
1659
+ previewDraftName = this._previewDraftLocation;
1660
+ }
1661
+ store = JSON.parse(this._storage[previewDraftName + this.settings.localStorageName]);
1662
+ if (name) {
1663
+ return store[name];
1664
+ }
1665
+ else {
1666
+ return store;
1667
+ }
1668
+ }
1669
+
1670
+ /**
1671
+ * Exports a file as a string in a supported format
1672
+ * @param {string} name Name of the file you want to export (case sensitive)
1673
+ * @param {string} kind Kind of file you want the content in (currently supports html and text, default is the format the browser "wants")
1674
+ * @returns {string|undefined} The content of the file in the content given or undefined if it doesn't exist
1675
+ */
1676
+ EpicEditor.prototype.exportFile = function (name, kind, _isPreviewDraft) {
1677
+ var self = this
1678
+ , file
1679
+ , content;
1680
+
1681
+ name = name || self.settings.file.name;
1682
+ kind = kind || 'text';
1683
+
1684
+ file = self._getFileStore(name, _isPreviewDraft);
1685
+
1686
+ // If the file doesn't exist just return early with undefined
1687
+ if (file === undefined) {
1688
+ return;
1689
+ }
1690
+
1691
+ content = file.content;
1692
+
1693
+ switch (kind) {
1694
+ case 'html':
1695
+ content = _sanitizeRawContent(content);
1696
+ return self.settings.parser(content);
1697
+ case 'text':
1698
+ return _sanitizeRawContent(content);
1699
+ case 'json':
1700
+ file.content = _sanitizeRawContent(file.content);
1701
+ return JSON.stringify(file);
1702
+ case 'raw':
1703
+ return content;
1704
+ default:
1705
+ return content;
1706
+ }
1707
+ }
1708
+
1709
+ /**
1710
+ * Gets the contents and metadata for files
1711
+ * @param {string} name Name of the file whose data you want (case sensitive)
1712
+ * @param {boolean} excludeContent whether the contents of files should be excluded
1713
+ * @returns {object} An object with the names and data of every file, or just the data of one file if a name was given
1714
+ */
1715
+ EpicEditor.prototype.getFiles = function (name, excludeContent) {
1716
+ var file
1717
+ , data = this._getFileStore(name);
1718
+
1719
+ if (name) {
1720
+ if (data !== undefined) {
1721
+ if (excludeContent) {
1722
+ delete data.content;
1723
+ }
1724
+ else {
1725
+ data.content = _sanitizeRawContent(data.content);
1726
+ }
1727
+ }
1728
+ return data;
1729
+ }
1730
+ else {
1731
+ for (file in data) {
1732
+ if (data.hasOwnProperty(file)) {
1733
+ if (excludeContent) {
1734
+ delete data[file].content;
1735
+ }
1736
+ else {
1737
+ data[file].content = _sanitizeRawContent(data[file].content);
1738
+ }
1739
+ }
1740
+ }
1741
+ return data;
1742
+ }
1743
+ }
1744
+
1745
+ // EVENTS
1746
+ // TODO: Support for namespacing events like "preview.foo"
1747
+ /**
1748
+ * Sets up an event handler for a specified event
1749
+ * @param {string} ev The event name
1750
+ * @param {function} handler The callback to run when the event fires
1751
+ * @returns {object} EpicEditor will be returned
1752
+ */
1753
+ EpicEditor.prototype.on = function (ev, handler) {
1754
+ var self = this;
1755
+ if (!this.events[ev]) {
1756
+ this.events[ev] = [];
1757
+ }
1758
+ this.events[ev].push(handler);
1759
+ return self;
1760
+ };
1761
+
1762
+ /**
1763
+ * This will emit or "trigger" an event specified
1764
+ * @param {string} ev The event name
1765
+ * @param {any} data Any data you want to pass into the callback
1766
+ * @returns {object} EpicEditor will be returned
1767
+ */
1768
+ EpicEditor.prototype.emit = function (ev, data) {
1769
+ var self = this
1770
+ , x;
1771
+
1772
+ data = data || self.getFiles(self.settings.file.name);
1773
+
1774
+ if (!this.events[ev]) {
1775
+ return;
1776
+ }
1777
+
1778
+ function invokeHandler(handler) {
1779
+ handler.call(self, data);
1780
+ }
1781
+
1782
+ for (x = 0; x < self.events[ev].length; x++) {
1783
+ invokeHandler(self.events[ev][x]);
1784
+ }
1785
+
1786
+ return self;
1787
+ };
1788
+
1789
+ /**
1790
+ * Will remove any listeners added from EpicEditor.on()
1791
+ * @param {string} ev The event name
1792
+ * @param {function} handler Handler to remove
1793
+ * @returns {object} EpicEditor will be returned
1794
+ */
1795
+ EpicEditor.prototype.removeListener = function (ev, handler) {
1796
+ var self = this;
1797
+ if (!handler) {
1798
+ this.events[ev] = [];
1799
+ return self;
1800
+ }
1801
+ if (!this.events[ev]) {
1802
+ return self;
1803
+ }
1804
+ // Otherwise a handler and event exist, so take care of it
1805
+ this.events[ev].splice(this.events[ev].indexOf(handler), 1);
1806
+ return self;
1807
+ }
1808
+
1809
+ /**
1810
+ * Handles autogrowing the editor
1811
+ */
1812
+ EpicEditor.prototype._autogrow = function () {
1813
+ var editorHeight
1814
+ , newHeight
1815
+ , minHeight
1816
+ , maxHeight
1817
+ , el
1818
+ , style
1819
+ , maxedOut = false;
1820
+
1821
+ //autogrow in fullscreen is nonsensical
1822
+ if (!this.is('fullscreen')) {
1823
+ if (this.is('edit')) {
1824
+ el = this.getElement('editor').documentElement;
1825
+ }
1826
+ else {
1827
+ el = this.getElement('previewer').documentElement;
1828
+ }
1829
+
1830
+ editorHeight = _outerHeight(el);
1831
+ newHeight = editorHeight;
1832
+
1833
+ //handle minimum
1834
+ minHeight = this.settings.autogrow.minHeight;
1835
+ if (typeof minHeight === 'function') {
1836
+ minHeight = minHeight(this);
1837
+ }
1838
+
1839
+ if (minHeight && newHeight < minHeight) {
1840
+ newHeight = minHeight;
1841
+ }
1842
+
1843
+ //handle maximum
1844
+ maxHeight = this.settings.autogrow.maxHeight;
1845
+ if (typeof maxHeight === 'function') {
1846
+ maxHeight = maxHeight(this);
1847
+ }
1848
+
1849
+ if (maxHeight && newHeight > maxHeight) {
1850
+ newHeight = maxHeight;
1851
+ maxedOut = true;
1852
+ }
1853
+
1854
+ if (maxedOut) {
1855
+ this._fixScrollbars('auto');
1856
+ } else {
1857
+ this._fixScrollbars('hidden');
1858
+ }
1859
+
1860
+ //actual resize
1861
+ if (newHeight != this.oldHeight) {
1862
+ this.getElement('container').style.height = newHeight + 'px';
1863
+ this.reflow();
1864
+ if (this.settings.autogrow.scroll) {
1865
+ window.scrollBy(0, newHeight - this.oldHeight);
1866
+ }
1867
+ this.oldHeight = newHeight;
1868
+ }
1869
+ }
1870
+ }
1871
+
1872
+ /**
1873
+ * Shows or hides scrollbars based on the autogrow setting
1874
+ * @param {string} forceSetting a value to force the overflow to
1875
+ */
1876
+ EpicEditor.prototype._fixScrollbars = function (forceSetting) {
1877
+ var setting;
1878
+ if (this.settings.autogrow) {
1879
+ setting = 'hidden';
1880
+ }
1881
+ else {
1882
+ setting = 'auto';
1883
+ }
1884
+ setting = forceSetting || setting;
1885
+ this.getElement('editor').documentElement.style.overflow = setting;
1886
+ this.getElement('previewer').documentElement.style.overflow = setting;
1887
+ }
1888
+
1889
+ EpicEditor.version = '0.2.2';
1890
+
1891
+ // Used to store information to be shared across editors
1892
+ EpicEditor._data = {};
1893
+
1894
+ window.EpicEditor = EpicEditor;
1895
+ })(window);
1896
+
1897
+ /**
1898
+ * marked - a markdown parser
1899
+ * Copyright (c) 2011-2013, Christopher Jeffrey. (MIT Licensed)
1900
+ * https://github.com/chjj/marked
1901
+ */
1902
+
1903
+ ;(function() {
1904
+
1905
+ /**
1906
+ * Block-Level Grammar
1907
+ */
1908
+
1909
+ var block = {
1910
+ newline: /^\n+/,
1911
+ code: /^( {4}[^\n]+\n*)+/,
1912
+ fences: noop,
1913
+ hr: /^( *[-*_]){3,} *(?:\n+|$)/,
1914
+ heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,
1915
+ nptable: noop,
1916
+ lheading: /^([^\n]+)\n *(=|-){3,} *\n*/,
1917
+ blockquote: /^( *>[^\n]+(\n[^\n]+)*\n*)+/,
1918
+ list: /^( *)(bull) [\s\S]+?(?:hr|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
1919
+ html: /^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/,
1920
+ def: /^ *\[([^\]]+)\]: *([^\s]+)(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
1921
+ table: noop,
1922
+ paragraph: /^([^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+\n*/,
1923
+ text: /^[^\n]+/
1924
+ };
1925
+
1926
+ block.bullet = /(?:[*+-]|\d+\.)/;
1927
+ block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;
1928
+ block.item = replace(block.item, 'gm')
1929
+ (/bull/g, block.bullet)
1930
+ ();
1931
+
1932
+ block.list = replace(block.list)
1933
+ (/bull/g, block.bullet)
1934
+ ('hr', /\n+(?=(?: *[-*_]){3,} *(?:\n+|$))/)
1935
+ ();
1936
+
1937
+ block._tag = '(?!(?:'
1938
+ + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code'
1939
+ + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo'
1940
+ + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|@)\\b';
1941
+
1942
+ block.html = replace(block.html)
1943
+ ('comment', /<!--[\s\S]*?-->/)
1944
+ ('closed', /<(tag)[\s\S]+?<\/\1>/)
1945
+ ('closing', /<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/)
1946
+ (/tag/g, block._tag)
1947
+ ();
1948
+
1949
+ block.paragraph = replace(block.paragraph)
1950
+ ('hr', block.hr)
1951
+ ('heading', block.heading)
1952
+ ('lheading', block.lheading)
1953
+ ('blockquote', block.blockquote)
1954
+ ('tag', '<' + block._tag)
1955
+ ('def', block.def)
1956
+ ();
1957
+
1958
+ /**
1959
+ * Normal Block Grammar
1960
+ */
1961
+
1962
+ block.normal = merge({}, block);
1963
+
1964
+ /**
1965
+ * GFM Block Grammar
1966
+ */
1967
+
1968
+ block.gfm = merge({}, block.normal, {
1969
+ fences: /^ *(`{3,}|~{3,}) *(\w+)? *\n([\s\S]+?)\s*\1 *(?:\n+|$)/,
1970
+ paragraph: /^/
1971
+ });
1972
+
1973
+ block.gfm.paragraph = replace(block.paragraph)
1974
+ ('(?!', '(?!' + block.gfm.fences.source.replace('\\1', '\\2') + '|')
1975
+ ();
1976
+
1977
+ /**
1978
+ * GFM + Tables Block Grammar
1979
+ */
1980
+
1981
+ block.tables = merge({}, block.gfm, {
1982
+ nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,
1983
+ table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/
1984
+ });
1985
+
1986
+ /**
1987
+ * Block Lexer
1988
+ */
1989
+
1990
+ function Lexer(options) {
1991
+ this.tokens = [];
1992
+ this.tokens.links = {};
1993
+ this.options = options || marked.defaults;
1994
+ this.rules = block.normal;
1995
+
1996
+ if (this.options.gfm) {
1997
+ if (this.options.tables) {
1998
+ this.rules = block.tables;
1999
+ } else {
2000
+ this.rules = block.gfm;
2001
+ }
2002
+ }
2003
+ }
2004
+
2005
+ /**
2006
+ * Expose Block Rules
2007
+ */
2008
+
2009
+ Lexer.rules = block;
2010
+
2011
+ /**
2012
+ * Static Lex Method
2013
+ */
2014
+
2015
+ Lexer.lex = function(src, options) {
2016
+ var lexer = new Lexer(options);
2017
+ return lexer.lex(src);
2018
+ };
2019
+
2020
+ /**
2021
+ * Preprocessing
2022
+ */
2023
+
2024
+ Lexer.prototype.lex = function(src) {
2025
+ src = src
2026
+ .replace(/\r\n|\r/g, '\n')
2027
+ .replace(/\t/g, ' ')
2028
+ .replace(/\u00a0/g, ' ')
2029
+ .replace(/\u2424/g, '\n');
2030
+
2031
+ return this.token(src, true);
2032
+ };
2033
+
2034
+ /**
2035
+ * Lexing
2036
+ */
2037
+
2038
+ Lexer.prototype.token = function(src, top) {
2039
+ var src = src.replace(/^ +$/gm, '')
2040
+ , next
2041
+ , loose
2042
+ , cap
2043
+ , item
2044
+ , space
2045
+ , i
2046
+ , l;
2047
+
2048
+ while (src) {
2049
+ // newline
2050
+ if (cap = this.rules.newline.exec(src)) {
2051
+ src = src.substring(cap[0].length);
2052
+ if (cap[0].length > 1) {
2053
+ this.tokens.push({
2054
+ type: 'space'
2055
+ });
2056
+ }
2057
+ }
2058
+
2059
+ // code
2060
+ if (cap = this.rules.code.exec(src)) {
2061
+ src = src.substring(cap[0].length);
2062
+ cap = cap[0].replace(/^ {4}/gm, '');
2063
+ this.tokens.push({
2064
+ type: 'code',
2065
+ text: !this.options.pedantic
2066
+ ? cap.replace(/\n+$/, '')
2067
+ : cap
2068
+ });
2069
+ continue;
2070
+ }
2071
+
2072
+ // fences (gfm)
2073
+ if (cap = this.rules.fences.exec(src)) {
2074
+ src = src.substring(cap[0].length);
2075
+ this.tokens.push({
2076
+ type: 'code',
2077
+ lang: cap[2],
2078
+ text: cap[3]
2079
+ });
2080
+ continue;
2081
+ }
2082
+
2083
+ // heading
2084
+ if (cap = this.rules.heading.exec(src)) {
2085
+ src = src.substring(cap[0].length);
2086
+ this.tokens.push({
2087
+ type: 'heading',
2088
+ depth: cap[1].length,
2089
+ text: cap[2]
2090
+ });
2091
+ continue;
2092
+ }
2093
+
2094
+ // table no leading pipe (gfm)
2095
+ if (top && (cap = this.rules.nptable.exec(src))) {
2096
+ src = src.substring(cap[0].length);
2097
+
2098
+ item = {
2099
+ type: 'table',
2100
+ header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
2101
+ align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
2102
+ cells: cap[3].replace(/\n$/, '').split('\n')
2103
+ };
2104
+
2105
+ for (i = 0; i < item.align.length; i++) {
2106
+ if (/^ *-+: *$/.test(item.align[i])) {
2107
+ item.align[i] = 'right';
2108
+ } else if (/^ *:-+: *$/.test(item.align[i])) {
2109
+ item.align[i] = 'center';
2110
+ } else if (/^ *:-+ *$/.test(item.align[i])) {
2111
+ item.align[i] = 'left';
2112
+ } else {
2113
+ item.align[i] = null;
2114
+ }
2115
+ }
2116
+
2117
+ for (i = 0; i < item.cells.length; i++) {
2118
+ item.cells[i] = item.cells[i].split(/ *\| */);
2119
+ }
2120
+
2121
+ this.tokens.push(item);
2122
+
2123
+ continue;
2124
+ }
2125
+
2126
+ // lheading
2127
+ if (cap = this.rules.lheading.exec(src)) {
2128
+ src = src.substring(cap[0].length);
2129
+ this.tokens.push({
2130
+ type: 'heading',
2131
+ depth: cap[2] === '=' ? 1 : 2,
2132
+ text: cap[1]
2133
+ });
2134
+ continue;
2135
+ }
2136
+
2137
+ // hr
2138
+ if (cap = this.rules.hr.exec(src)) {
2139
+ src = src.substring(cap[0].length);
2140
+ this.tokens.push({
2141
+ type: 'hr'
2142
+ });
2143
+ continue;
2144
+ }
2145
+
2146
+ // blockquote
2147
+ if (cap = this.rules.blockquote.exec(src)) {
2148
+ src = src.substring(cap[0].length);
2149
+
2150
+ this.tokens.push({
2151
+ type: 'blockquote_start'
2152
+ });
2153
+
2154
+ cap = cap[0].replace(/^ *> ?/gm, '');
2155
+
2156
+ // Pass `top` to keep the current
2157
+ // "toplevel" state. This is exactly
2158
+ // how markdown.pl works.
2159
+ this.token(cap, top);
2160
+
2161
+ this.tokens.push({
2162
+ type: 'blockquote_end'
2163
+ });
2164
+
2165
+ continue;
2166
+ }
2167
+
2168
+ // list
2169
+ if (cap = this.rules.list.exec(src)) {
2170
+ src = src.substring(cap[0].length);
2171
+
2172
+ this.tokens.push({
2173
+ type: 'list_start',
2174
+ ordered: isFinite(cap[2])
2175
+ });
2176
+
2177
+ // Get each top-level item.
2178
+ cap = cap[0].match(this.rules.item);
2179
+
2180
+ next = false;
2181
+ l = cap.length;
2182
+ i = 0;
2183
+
2184
+ for (; i < l; i++) {
2185
+ item = cap[i];
2186
+
2187
+ // Remove the list item's bullet
2188
+ // so it is seen as the next token.
2189
+ space = item.length;
2190
+ item = item.replace(/^ *([*+-]|\d+\.) +/, '');
2191
+
2192
+ // Outdent whatever the
2193
+ // list item contains. Hacky.
2194
+ if (~item.indexOf('\n ')) {
2195
+ space -= item.length;
2196
+ item = !this.options.pedantic
2197
+ ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '')
2198
+ : item.replace(/^ {1,4}/gm, '');
2199
+ }
2200
+
2201
+ // Determine whether item is loose or not.
2202
+ // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
2203
+ // for discount behavior.
2204
+ loose = next || /\n\n(?!\s*$)/.test(item);
2205
+ if (i !== l - 1) {
2206
+ next = item[item.length-1] === '\n';
2207
+ if (!loose) loose = next;
2208
+ }
2209
+
2210
+ this.tokens.push({
2211
+ type: loose
2212
+ ? 'loose_item_start'
2213
+ : 'list_item_start'
2214
+ });
2215
+
2216
+ // Recurse.
2217
+ this.token(item, false);
2218
+
2219
+ this.tokens.push({
2220
+ type: 'list_item_end'
2221
+ });
2222
+ }
2223
+
2224
+ this.tokens.push({
2225
+ type: 'list_end'
2226
+ });
2227
+
2228
+ continue;
2229
+ }
2230
+
2231
+ // html
2232
+ if (cap = this.rules.html.exec(src)) {
2233
+ src = src.substring(cap[0].length);
2234
+ this.tokens.push({
2235
+ type: this.options.sanitize
2236
+ ? 'paragraph'
2237
+ : 'html',
2238
+ pre: cap[1] === 'pre',
2239
+ text: cap[0]
2240
+ });
2241
+ continue;
2242
+ }
2243
+
2244
+ // def
2245
+ if (top && (cap = this.rules.def.exec(src))) {
2246
+ src = src.substring(cap[0].length);
2247
+ this.tokens.links[cap[1].toLowerCase()] = {
2248
+ href: cap[2],
2249
+ title: cap[3]
2250
+ };
2251
+ continue;
2252
+ }
2253
+
2254
+ // table (gfm)
2255
+ if (top && (cap = this.rules.table.exec(src))) {
2256
+ src = src.substring(cap[0].length);
2257
+
2258
+ item = {
2259
+ type: 'table',
2260
+ header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
2261
+ align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
2262
+ cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n')
2263
+ };
2264
+
2265
+ for (i = 0; i < item.align.length; i++) {
2266
+ if (/^ *-+: *$/.test(item.align[i])) {
2267
+ item.align[i] = 'right';
2268
+ } else if (/^ *:-+: *$/.test(item.align[i])) {
2269
+ item.align[i] = 'center';
2270
+ } else if (/^ *:-+ *$/.test(item.align[i])) {
2271
+ item.align[i] = 'left';
2272
+ } else {
2273
+ item.align[i] = null;
2274
+ }
2275
+ }
2276
+
2277
+ for (i = 0; i < item.cells.length; i++) {
2278
+ item.cells[i] = item.cells[i]
2279
+ .replace(/^ *\| *| *\| *$/g, '')
2280
+ .split(/ *\| */);
2281
+ }
2282
+
2283
+ this.tokens.push(item);
2284
+
2285
+ continue;
2286
+ }
2287
+
2288
+ // top-level paragraph
2289
+ if (top && (cap = this.rules.paragraph.exec(src))) {
2290
+ src = src.substring(cap[0].length);
2291
+ this.tokens.push({
2292
+ type: 'paragraph',
2293
+ text: cap[0]
2294
+ });
2295
+ continue;
2296
+ }
2297
+
2298
+ // text
2299
+ if (cap = this.rules.text.exec(src)) {
2300
+ // Top-level should never reach here.
2301
+ src = src.substring(cap[0].length);
2302
+ this.tokens.push({
2303
+ type: 'text',
2304
+ text: cap[0]
2305
+ });
2306
+ continue;
2307
+ }
2308
+
2309
+ if (src) {
2310
+ throw new
2311
+ Error('Infinite loop on byte: ' + src.charCodeAt(0));
2312
+ }
2313
+ }
2314
+
2315
+ return this.tokens;
2316
+ };
2317
+
2318
+ /**
2319
+ * Inline-Level Grammar
2320
+ */
2321
+
2322
+ var inline = {
2323
+ escape: /^\\([\\`*{}\[\]()#+\-.!_>|])/,
2324
+ autolink: /^<([^ >]+(@|:\/)[^ >]+)>/,
2325
+ url: noop,
2326
+ tag: /^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,
2327
+ link: /^!?\[(inside)\]\(href\)/,
2328
+ reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/,
2329
+ nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
2330
+ strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,
2331
+ em: /^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,
2332
+ code: /^(`+)([\s\S]*?[^`])\1(?!`)/,
2333
+ br: /^ {2,}\n(?!\s*$)/,
2334
+ del: noop,
2335
+ text: /^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/
2336
+ };
2337
+
2338
+ inline._inside = /(?:\[[^\]]*\]|[^\]]|\](?=[^\[]*\]))*/;
2339
+ inline._href = /\s*<?([^\s]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/;
2340
+
2341
+ inline.link = replace(inline.link)
2342
+ ('inside', inline._inside)
2343
+ ('href', inline._href)
2344
+ ();
2345
+
2346
+ inline.reflink = replace(inline.reflink)
2347
+ ('inside', inline._inside)
2348
+ ();
2349
+
2350
+ /**
2351
+ * Normal Inline Grammar
2352
+ */
2353
+
2354
+ inline.normal = merge({}, inline);
2355
+
2356
+ /**
2357
+ * Pedantic Inline Grammar
2358
+ */
2359
+
2360
+ inline.pedantic = merge({}, inline.normal, {
2361
+ strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
2362
+ em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/
2363
+ });
2364
+
2365
+ /**
2366
+ * GFM Inline Grammar
2367
+ */
2368
+
2369
+ inline.gfm = merge({}, inline.normal, {
2370
+ escape: replace(inline.escape)('])', '~])')(),
2371
+ url: /^(https?:\/\/[^\s]+[^.,:;"')\]\s])/,
2372
+ del: /^~{2,}([\s\S]+?)~{2,}/,
2373
+ text: replace(inline.text)
2374
+ (']|', '~]|')
2375
+ ('|', '|https?://|')
2376
+ ()
2377
+ });
2378
+
2379
+ /**
2380
+ * GFM + Line Breaks Inline Grammar
2381
+ */
2382
+
2383
+ inline.breaks = merge({}, inline.gfm, {
2384
+ br: replace(inline.br)('{2,}', '*')(),
2385
+ text: replace(inline.gfm.text)('{2,}', '*')()
2386
+ });
2387
+
2388
+ /**
2389
+ * Inline Lexer & Compiler
2390
+ */
2391
+
2392
+ function InlineLexer(links, options) {
2393
+ this.options = options || marked.defaults;
2394
+ this.links = links;
2395
+ this.rules = inline.normal;
2396
+
2397
+ if (!this.links) {
2398
+ throw new
2399
+ Error('Tokens array requires a `links` property.');
2400
+ }
2401
+
2402
+ if (this.options.gfm) {
2403
+ if (this.options.breaks) {
2404
+ this.rules = inline.breaks;
2405
+ } else {
2406
+ this.rules = inline.gfm;
2407
+ }
2408
+ } else if (this.options.pedantic) {
2409
+ this.rules = inline.pedantic;
2410
+ }
2411
+ }
2412
+
2413
+ /**
2414
+ * Expose Inline Rules
2415
+ */
2416
+
2417
+ InlineLexer.rules = inline;
2418
+
2419
+ /**
2420
+ * Static Lexing/Compiling Method
2421
+ */
2422
+
2423
+ InlineLexer.output = function(src, links, opt) {
2424
+ var inline = new InlineLexer(links, opt);
2425
+ return inline.output(src);
2426
+ };
2427
+
2428
+ /**
2429
+ * Lexing/Compiling
2430
+ */
2431
+
2432
+ InlineLexer.prototype.output = function(src) {
2433
+ var out = ''
2434
+ , link
2435
+ , text
2436
+ , href
2437
+ , cap;
2438
+
2439
+ while (src) {
2440
+ // escape
2441
+ if (cap = this.rules.escape.exec(src)) {
2442
+ src = src.substring(cap[0].length);
2443
+ out += cap[1];
2444
+ continue;
2445
+ }
2446
+
2447
+ // autolink
2448
+ if (cap = this.rules.autolink.exec(src)) {
2449
+ src = src.substring(cap[0].length);
2450
+ if (cap[2] === '@') {
2451
+ text = cap[1][6] === ':'
2452
+ ? this.mangle(cap[1].substring(7))
2453
+ : this.mangle(cap[1]);
2454
+ href = this.mangle('mailto:') + text;
2455
+ } else {
2456
+ text = escape(cap[1]);
2457
+ href = text;
2458
+ }
2459
+ out += '<a href="'
2460
+ + href
2461
+ + '">'
2462
+ + text
2463
+ + '</a>';
2464
+ continue;
2465
+ }
2466
+
2467
+ // url (gfm)
2468
+ if (cap = this.rules.url.exec(src)) {
2469
+ src = src.substring(cap[0].length);
2470
+ text = escape(cap[1]);
2471
+ href = text;
2472
+ out += '<a href="'
2473
+ + href
2474
+ + '">'
2475
+ + text
2476
+ + '</a>';
2477
+ continue;
2478
+ }
2479
+
2480
+ // tag
2481
+ if (cap = this.rules.tag.exec(src)) {
2482
+ src = src.substring(cap[0].length);
2483
+ out += this.options.sanitize
2484
+ ? escape(cap[0])
2485
+ : cap[0];
2486
+ continue;
2487
+ }
2488
+
2489
+ // link
2490
+ if (cap = this.rules.link.exec(src)) {
2491
+ src = src.substring(cap[0].length);
2492
+ out += this.outputLink(cap, {
2493
+ href: cap[2],
2494
+ title: cap[3]
2495
+ });
2496
+ continue;
2497
+ }
2498
+
2499
+ // reflink, nolink
2500
+ if ((cap = this.rules.reflink.exec(src))
2501
+ || (cap = this.rules.nolink.exec(src))) {
2502
+ src = src.substring(cap[0].length);
2503
+ link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
2504
+ link = this.links[link.toLowerCase()];
2505
+ if (!link || !link.href) {
2506
+ out += cap[0][0];
2507
+ src = cap[0].substring(1) + src;
2508
+ continue;
2509
+ }
2510
+ out += this.outputLink(cap, link);
2511
+ continue;
2512
+ }
2513
+
2514
+ // strong
2515
+ if (cap = this.rules.strong.exec(src)) {
2516
+ src = src.substring(cap[0].length);
2517
+ out += '<strong>'
2518
+ + this.output(cap[2] || cap[1])
2519
+ + '</strong>';
2520
+ continue;
2521
+ }
2522
+
2523
+ // em
2524
+ if (cap = this.rules.em.exec(src)) {
2525
+ src = src.substring(cap[0].length);
2526
+ out += '<em>'
2527
+ + this.output(cap[2] || cap[1])
2528
+ + '</em>';
2529
+ continue;
2530
+ }
2531
+
2532
+ // code
2533
+ if (cap = this.rules.code.exec(src)) {
2534
+ src = src.substring(cap[0].length);
2535
+ out += '<code>'
2536
+ + escape(cap[2], true)
2537
+ + '</code>';
2538
+ continue;
2539
+ }
2540
+
2541
+ // br
2542
+ if (cap = this.rules.br.exec(src)) {
2543
+ src = src.substring(cap[0].length);
2544
+ out += '<br>';
2545
+ continue;
2546
+ }
2547
+
2548
+ // del (gfm)
2549
+ if (cap = this.rules.del.exec(src)) {
2550
+ src = src.substring(cap[0].length);
2551
+ out += '<del>'
2552
+ + this.output(cap[1])
2553
+ + '</del>';
2554
+ continue;
2555
+ }
2556
+
2557
+ // text
2558
+ if (cap = this.rules.text.exec(src)) {
2559
+ src = src.substring(cap[0].length);
2560
+ out += escape(cap[0]);
2561
+ continue;
2562
+ }
2563
+
2564
+ if (src) {
2565
+ throw new
2566
+ Error('Infinite loop on byte: ' + src.charCodeAt(0));
2567
+ }
2568
+ }
2569
+
2570
+ return out;
2571
+ };
2572
+
2573
+ /**
2574
+ * Compile Link
2575
+ */
2576
+
2577
+ InlineLexer.prototype.outputLink = function(cap, link) {
2578
+ if (cap[0][0] !== '!') {
2579
+ return '<a href="'
2580
+ + escape(link.href)
2581
+ + '"'
2582
+ + (link.title
2583
+ ? ' title="'
2584
+ + escape(link.title)
2585
+ + '"'
2586
+ : '')
2587
+ + '>'
2588
+ + this.output(cap[1])
2589
+ + '</a>';
2590
+ } else {
2591
+ return '<img src="'
2592
+ + escape(link.href)
2593
+ + '" alt="'
2594
+ + escape(cap[1])
2595
+ + '"'
2596
+ + (link.title
2597
+ ? ' title="'
2598
+ + escape(link.title)
2599
+ + '"'
2600
+ : '')
2601
+ + '>';
2602
+ }
2603
+ };
2604
+
2605
+ /**
2606
+ * Mangle Links
2607
+ */
2608
+
2609
+ InlineLexer.prototype.mangle = function(text) {
2610
+ var out = ''
2611
+ , l = text.length
2612
+ , i = 0
2613
+ , ch;
2614
+
2615
+ for (; i < l; i++) {
2616
+ ch = text.charCodeAt(i);
2617
+ if (Math.random() > 0.5) {
2618
+ ch = 'x' + ch.toString(16);
2619
+ }
2620
+ out += '&#' + ch + ';';
2621
+ }
2622
+
2623
+ return out;
2624
+ };
2625
+
2626
+ /**
2627
+ * Parsing & Compiling
2628
+ */
2629
+
2630
+ function Parser(options) {
2631
+ this.tokens = [];
2632
+ this.token = null;
2633
+ this.options = options || marked.defaults;
2634
+ }
2635
+
2636
+ /**
2637
+ * Static Parse Method
2638
+ */
2639
+
2640
+ Parser.parse = function(src, options) {
2641
+ var parser = new Parser(options);
2642
+ return parser.parse(src);
2643
+ };
2644
+
2645
+ /**
2646
+ * Parse Loop
2647
+ */
2648
+
2649
+ Parser.prototype.parse = function(src) {
2650
+ this.inline = new InlineLexer(src.links, this.options);
2651
+ this.tokens = src.reverse();
2652
+
2653
+ var out = '';
2654
+ while (this.next()) {
2655
+ out += this.tok();
2656
+ }
2657
+
2658
+ return out;
2659
+ };
2660
+
2661
+ /**
2662
+ * Next Token
2663
+ */
2664
+
2665
+ Parser.prototype.next = function() {
2666
+ return this.token = this.tokens.pop();
2667
+ };
2668
+
2669
+ /**
2670
+ * Preview Next Token
2671
+ */
2672
+
2673
+ Parser.prototype.peek = function() {
2674
+ return this.tokens[this.tokens.length-1] || 0;
2675
+ };
2676
+
2677
+ /**
2678
+ * Parse Text Tokens
2679
+ */
2680
+
2681
+ Parser.prototype.parseText = function() {
2682
+ var body = this.token.text;
2683
+
2684
+ while (this.peek().type === 'text') {
2685
+ body += '\n' + this.next().text;
2686
+ }
2687
+
2688
+ return this.inline.output(body);
2689
+ };
2690
+
2691
+ /**
2692
+ * Parse Current Token
2693
+ */
2694
+
2695
+ Parser.prototype.tok = function() {
2696
+ switch (this.token.type) {
2697
+ case 'space': {
2698
+ return '';
2699
+ }
2700
+ case 'hr': {
2701
+ return '<hr>\n';
2702
+ }
2703
+ case 'heading': {
2704
+ return '<h'
2705
+ + this.token.depth
2706
+ + '>'
2707
+ + this.inline.output(this.token.text)
2708
+ + '</h'
2709
+ + this.token.depth
2710
+ + '>\n';
2711
+ }
2712
+ case 'code': {
2713
+ if (this.options.highlight) {
2714
+ var code = this.options.highlight(this.token.text, this.token.lang);
2715
+ if (code != null && code !== this.token.text) {
2716
+ this.token.escaped = true;
2717
+ this.token.text = code;
2718
+ }
2719
+ }
2720
+
2721
+ if (!this.token.escaped) {
2722
+ this.token.text = escape(this.token.text, true);
2723
+ }
2724
+
2725
+ return '<pre><code'
2726
+ + (this.token.lang
2727
+ ? ' class="lang-'
2728
+ + this.token.lang
2729
+ + '"'
2730
+ : '')
2731
+ + '>'
2732
+ + this.token.text
2733
+ + '</code></pre>\n';
2734
+ }
2735
+ case 'table': {
2736
+ var body = ''
2737
+ , heading
2738
+ , i
2739
+ , row
2740
+ , cell
2741
+ , j;
2742
+
2743
+ // header
2744
+ body += '<thead>\n<tr>\n';
2745
+ for (i = 0; i < this.token.header.length; i++) {
2746
+ heading = this.inline.output(this.token.header[i]);
2747
+ body += this.token.align[i]
2748
+ ? '<th align="' + this.token.align[i] + '">' + heading + '</th>\n'
2749
+ : '<th>' + heading + '</th>\n';
2750
+ }
2751
+ body += '</tr>\n</thead>\n';
2752
+
2753
+ // body
2754
+ body += '<tbody>\n'
2755
+ for (i = 0; i < this.token.cells.length; i++) {
2756
+ row = this.token.cells[i];
2757
+ body += '<tr>\n';
2758
+ for (j = 0; j < row.length; j++) {
2759
+ cell = this.inline.output(row[j]);
2760
+ body += this.token.align[j]
2761
+ ? '<td align="' + this.token.align[j] + '">' + cell + '</td>\n'
2762
+ : '<td>' + cell + '</td>\n';
2763
+ }
2764
+ body += '</tr>\n';
2765
+ }
2766
+ body += '</tbody>\n';
2767
+
2768
+ return '<table>\n'
2769
+ + body
2770
+ + '</table>\n';
2771
+ }
2772
+ case 'blockquote_start': {
2773
+ var body = '';
2774
+
2775
+ while (this.next().type !== 'blockquote_end') {
2776
+ body += this.tok();
2777
+ }
2778
+
2779
+ return '<blockquote>\n'
2780
+ + body
2781
+ + '</blockquote>\n';
2782
+ }
2783
+ case 'list_start': {
2784
+ var type = this.token.ordered ? 'ol' : 'ul'
2785
+ , body = '';
2786
+
2787
+ while (this.next().type !== 'list_end') {
2788
+ body += this.tok();
2789
+ }
2790
+
2791
+ return '<'
2792
+ + type
2793
+ + '>\n'
2794
+ + body
2795
+ + '</'
2796
+ + type
2797
+ + '>\n';
2798
+ }
2799
+ case 'list_item_start': {
2800
+ var body = '';
2801
+
2802
+ while (this.next().type !== 'list_item_end') {
2803
+ body += this.token.type === 'text'
2804
+ ? this.parseText()
2805
+ : this.tok();
2806
+ }
2807
+
2808
+ return '<li>'
2809
+ + body
2810
+ + '</li>\n';
2811
+ }
2812
+ case 'loose_item_start': {
2813
+ var body = '';
2814
+
2815
+ while (this.next().type !== 'list_item_end') {
2816
+ body += this.tok();
2817
+ }
2818
+
2819
+ return '<li>'
2820
+ + body
2821
+ + '</li>\n';
2822
+ }
2823
+ case 'html': {
2824
+ return !this.token.pre && !this.options.pedantic
2825
+ ? this.inline.output(this.token.text)
2826
+ : this.token.text;
2827
+ }
2828
+ case 'paragraph': {
2829
+ return '<p>'
2830
+ + this.inline.output(this.token.text)
2831
+ + '</p>\n';
2832
+ }
2833
+ case 'text': {
2834
+ return '<p>'
2835
+ + this.parseText()
2836
+ + '</p>\n';
2837
+ }
2838
+ }
2839
+ };
2840
+
2841
+ /**
2842
+ * Helpers
2843
+ */
2844
+
2845
+ function escape(html, encode) {
2846
+ return html
2847
+ .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&amp;')
2848
+ .replace(/</g, '&lt;')
2849
+ .replace(/>/g, '&gt;')
2850
+ .replace(/"/g, '&quot;')
2851
+ .replace(/'/g, '&#39;');
2852
+ }
2853
+
2854
+ function replace(regex, opt) {
2855
+ regex = regex.source;
2856
+ opt = opt || '';
2857
+ return function self(name, val) {
2858
+ if (!name) return new RegExp(regex, opt);
2859
+ val = val.source || val;
2860
+ val = val.replace(/(^|[^\[])\^/g, '$1');
2861
+ regex = regex.replace(name, val);
2862
+ return self;
2863
+ };
2864
+ }
2865
+
2866
+ function noop() {}
2867
+ noop.exec = noop;
2868
+
2869
+ function merge(obj) {
2870
+ var i = 1
2871
+ , target
2872
+ , key;
2873
+
2874
+ for (; i < arguments.length; i++) {
2875
+ target = arguments[i];
2876
+ for (key in target) {
2877
+ if (Object.prototype.hasOwnProperty.call(target, key)) {
2878
+ obj[key] = target[key];
2879
+ }
2880
+ }
2881
+ }
2882
+
2883
+ return obj;
2884
+ }
2885
+
2886
+ /**
2887
+ * Marked
2888
+ */
2889
+
2890
+ function marked(src, opt) {
2891
+ try {
2892
+ return Parser.parse(Lexer.lex(src, opt), opt);
2893
+ } catch (e) {
2894
+ e.message += '\nPlease report this to https://github.com/chjj/marked.';
2895
+ if ((opt || marked.defaults).silent) {
2896
+ return 'An error occured:\n' + e.message;
2897
+ }
2898
+ throw e;
2899
+ }
2900
+ }
2901
+
2902
+ /**
2903
+ * Options
2904
+ */
2905
+
2906
+ marked.options =
2907
+ marked.setOptions = function(opt) {
2908
+ marked.defaults = opt;
2909
+ return marked;
2910
+ };
2911
+
2912
+ marked.defaults = {
2913
+ gfm: true,
2914
+ tables: true,
2915
+ breaks: false,
2916
+ pedantic: false,
2917
+ sanitize: false,
2918
+ silent: false,
2919
+ highlight: null
2920
+ };
2921
+
2922
+ /**
2923
+ * Expose
2924
+ */
2925
+
2926
+ marked.Parser = Parser;
2927
+ marked.parser = Parser.parse;
2928
+
2929
+ marked.Lexer = Lexer;
2930
+ marked.lexer = Lexer.lex;
2931
+
2932
+ marked.InlineLexer = InlineLexer;
2933
+ marked.inlineLexer = InlineLexer.output;
2934
+
2935
+ marked.parse = marked;
2936
+
2937
+ if (typeof module !== 'undefined') {
2938
+ module.exports = marked;
2939
+ } else if (typeof define === 'function' && define.amd) {
2940
+ define(function() { return marked; });
2941
+ } else {
2942
+ this.marked = marked;
2943
+ }
2944
+
2945
+ }).call(function() {
2946
+ return this || (typeof window !== 'undefined' ? window : global);
2947
+ }());