archangel 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (517) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +17 -0
  3. data/.editorconfig +19 -0
  4. data/.eslintignore +6 -0
  5. data/.eslintrc.json +40 -0
  6. data/.github/ISSUE_TEMPLATE.md +23 -0
  7. data/.github/PULL_REQUEST_TEMPLATE.md +18 -0
  8. data/.gitignore +22 -0
  9. data/.hound.yml +21 -0
  10. data/.mdlrc +2 -0
  11. data/.reek +35 -0
  12. data/.rspec +4 -0
  13. data/.rubocop.yml +28 -0
  14. data/.rubocop_disabled.yml +2 -0
  15. data/.rubocop_enabled.yml +2 -0
  16. data/.rubocop_todo.yml +7 -0
  17. data/.scss-lint.yml +22 -0
  18. data/.travis.yml +16 -0
  19. data/.yardopts +10 -0
  20. data/CODE_OF_CONDUCT.md +43 -0
  21. data/CONTRIBUTING.md +150 -0
  22. data/Gemfile +36 -0
  23. data/MIT-LICENSE +21 -0
  24. data/README.md +129 -0
  25. data/Rakefile +27 -0
  26. data/app/assets/config/archangel_manifest.js +14 -0
  27. data/app/assets/images/archangel/fallback/asset.png +0 -0
  28. data/app/assets/images/archangel/fallback/avatar.png +0 -0
  29. data/app/assets/images/archangel/fallback/default.png +0 -0
  30. data/app/assets/images/archangel/fallback/favicon.ico +0 -0
  31. data/app/assets/images/archangel/fallback/large_avatar.png +0 -0
  32. data/app/assets/images/archangel/fallback/large_logo.png +0 -0
  33. data/app/assets/images/archangel/fallback/logo.png +0 -0
  34. data/app/assets/images/archangel/fallback/medium_avatar.png +0 -0
  35. data/app/assets/images/archangel/fallback/medium_logo.png +0 -0
  36. data/app/assets/images/archangel/fallback/small_asset.png +0 -0
  37. data/app/assets/images/archangel/fallback/small_avatar.png +0 -0
  38. data/app/assets/images/archangel/fallback/small_logo.png +0 -0
  39. data/app/assets/images/archangel/fallback/tiny_asset.png +0 -0
  40. data/app/assets/images/archangel/fallback/tiny_avatar.png +0 -0
  41. data/app/assets/images/archangel/fallback/tiny_logo.png +0 -0
  42. data/app/assets/javascripts/archangel/input/datetime_picker.js +46 -0
  43. data/app/assets/javascripts/archangel/input/meta_keywords.js +22 -0
  44. data/app/assets/javascripts/archangel/input/parent/collection.js +0 -0
  45. data/app/assets/javascripts/archangel/input/parent/field.js +0 -0
  46. data/app/assets/javascripts/archangel/input/parent/page.js +0 -0
  47. data/app/assets/javascripts/archangel/input/wysiwyg.js.erb +51 -0
  48. data/app/assets/javascripts/archangel/object/flash.js +49 -0
  49. data/app/assets/javascripts/archangel/object/routes/auth.js +22 -0
  50. data/app/assets/javascripts/archangel/object/routes/backend.js +103 -0
  51. data/app/assets/javascripts/archangel/object/routes/frontend.js +24 -0
  52. data/app/assets/javascripts/archangel/object/routes.js.erb +84 -0
  53. data/app/assets/javascripts/archangel/object/translate/datetime_picker.js.erb +26 -0
  54. data/app/assets/javascripts/archangel/object/translate/sortable.js.erb +10 -0
  55. data/app/assets/javascripts/archangel/object/translate.js +8 -0
  56. data/app/assets/javascripts/archangel/object/url.js +24 -0
  57. data/app/assets/javascripts/archangel/object.js +25 -0
  58. data/app/assets/javascripts/archangel/sortable.js +72 -0
  59. data/app/assets/stylesheets/archangel/.keep +0 -0
  60. data/app/controllers/archangel/application_controller.rb +177 -0
  61. data/app/controllers/archangel/auth/registrations_controller.rb +22 -0
  62. data/app/controllers/archangel/auth_controller.rb +20 -0
  63. data/app/controllers/archangel/backend/assets_controller.rb +338 -0
  64. data/app/controllers/archangel/backend/collections_controller.rb +257 -0
  65. data/app/controllers/archangel/backend/dashboards_controller.rb +27 -0
  66. data/app/controllers/archangel/backend/entries_controller.rb +344 -0
  67. data/app/controllers/archangel/backend/pages_controller.rb +299 -0
  68. data/app/controllers/archangel/backend/profiles_controller.rb +211 -0
  69. data/app/controllers/archangel/backend/sites_controller.rb +175 -0
  70. data/app/controllers/archangel/backend/templates_controller.rb +270 -0
  71. data/app/controllers/archangel/backend/users_controller.rb +366 -0
  72. data/app/controllers/archangel/backend/widgets_controller.rb +264 -0
  73. data/app/controllers/archangel/backend_controller.rb +21 -0
  74. data/app/controllers/archangel/frontend/pages_controller.rb +158 -0
  75. data/app/controllers/archangel/frontend_controller.rb +10 -0
  76. data/app/controllers/concerns/archangel/actionable_concern.rb +152 -0
  77. data/app/controllers/concerns/archangel/authenticatable_concern.rb +14 -0
  78. data/app/controllers/concerns/archangel/authorizable_concern.rb +14 -0
  79. data/app/controllers/concerns/archangel/paginatable_concern.rb +34 -0
  80. data/app/controllers/concerns/archangel/seoable_concern.rb +40 -0
  81. data/app/controllers/concerns/archangel/skip_authorizable_concern.rb +14 -0
  82. data/app/controllers/concerns/archangel/themable_concern.rb +27 -0
  83. data/app/helpers/archangel/application_helper.rb +34 -0
  84. data/app/helpers/archangel/auth_helper.rb +9 -0
  85. data/app/helpers/archangel/backend/assets_helper.rb +14 -0
  86. data/app/helpers/archangel/backend/collections_helper.rb +14 -0
  87. data/app/helpers/archangel/backend/dashboards_helper.rb +14 -0
  88. data/app/helpers/archangel/backend/entries_helper.rb +14 -0
  89. data/app/helpers/archangel/backend/pages_helper.rb +14 -0
  90. data/app/helpers/archangel/backend/sites_helper.rb +14 -0
  91. data/app/helpers/archangel/backend/templates_helper.rb +14 -0
  92. data/app/helpers/archangel/backend/users_helper.rb +14 -0
  93. data/app/helpers/archangel/backend/widgets_helper.rb +14 -0
  94. data/app/helpers/archangel/backend_helper.rb +9 -0
  95. data/app/helpers/archangel/flash_helper.rb +22 -0
  96. data/app/helpers/archangel/frontend/pages_helper.rb +14 -0
  97. data/app/helpers/archangel/frontend_helper.rb +9 -0
  98. data/app/inputs/archangel/date_picker_input.rb +27 -0
  99. data/app/inputs/archangel/date_time_picker_input.rb +27 -0
  100. data/app/inputs/archangel/field_classification_input.rb +32 -0
  101. data/app/inputs/archangel/language_input.rb +31 -0
  102. data/app/inputs/archangel/meta_keywords_input.rb +27 -0
  103. data/app/inputs/archangel/role_input.rb +31 -0
  104. data/app/inputs/archangel/theme_input.rb +29 -0
  105. data/app/inputs/archangel/time_picker_input.rb +27 -0
  106. data/app/inputs/archangel/wysiwyg_input.rb +15 -0
  107. data/app/jobs/archangel/application_job.rb +9 -0
  108. data/app/mailers/archangel/application_mailer.rb +12 -0
  109. data/app/models/archangel/application_record.rb +10 -0
  110. data/app/models/archangel/asset.rb +42 -0
  111. data/app/models/archangel/collection.rb +51 -0
  112. data/app/models/archangel/entry.rb +22 -0
  113. data/app/models/archangel/field.rb +30 -0
  114. data/app/models/archangel/page.rb +123 -0
  115. data/app/models/archangel/site.rb +54 -0
  116. data/app/models/archangel/template.rb +37 -0
  117. data/app/models/archangel/user.rb +62 -0
  118. data/app/models/archangel/widget.rb +62 -0
  119. data/app/policies/archangel/application_policy.rb +142 -0
  120. data/app/policies/archangel/asset_policy.rb +17 -0
  121. data/app/policies/archangel/collection_policy.rb +9 -0
  122. data/app/policies/archangel/entry_policy.rb +17 -0
  123. data/app/policies/archangel/page_policy.rb +9 -0
  124. data/app/policies/archangel/site_policy.rb +27 -0
  125. data/app/policies/archangel/template_policy.rb +9 -0
  126. data/app/policies/archangel/user_policy.rb +9 -0
  127. data/app/policies/archangel/widget_policy.rb +9 -0
  128. data/app/services/archangel/render_service.rb +98 -0
  129. data/app/services/archangel/template_render_service.rb +40 -0
  130. data/app/themes/default/assets/javascripts/default/auth/.keep +0 -0
  131. data/app/themes/default/assets/javascripts/default/auth.js +18 -0
  132. data/app/themes/default/assets/javascripts/default/backend/core.js +7 -0
  133. data/app/themes/default/assets/javascripts/default/backend.js +50 -0
  134. data/app/themes/default/assets/javascripts/default/common/cards.js +28 -0
  135. data/app/themes/default/assets/javascripts/default/common/flash.js +48 -0
  136. data/app/themes/default/assets/javascripts/default/common/navigation.js +30 -0
  137. data/app/themes/default/assets/javascripts/default/common/sidebar.js +114 -0
  138. data/app/themes/default/assets/javascripts/default/frontend/.keep +0 -0
  139. data/app/themes/default/assets/javascripts/default/frontend.js +18 -0
  140. data/app/themes/default/assets/stylesheets/default/auth/core.scss +15 -0
  141. data/app/themes/default/assets/stylesheets/default/auth.css +13 -0
  142. data/app/themes/default/assets/stylesheets/default/backend/core.scss +22 -0
  143. data/app/themes/default/assets/stylesheets/default/backend.css +13 -0
  144. data/app/themes/default/assets/stylesheets/default/common/_bootstrap.scss +49 -0
  145. data/app/themes/default/assets/stylesheets/default/common/_selectize.scss +299 -0
  146. data/app/themes/default/assets/stylesheets/default/common/_variables.scss +82 -0
  147. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_animate.scss +27 -0
  148. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_aside.scss +67 -0
  149. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_avatars.scss +42 -0
  150. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_badge.scss +3 -0
  151. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_breadcrumb-menu.scss +36 -0
  152. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_breadcrumb.scss +4 -0
  153. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_buttons.scss +408 -0
  154. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_callout.scss +58 -0
  155. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_card.scss +199 -0
  156. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_charts.scss +9 -0
  157. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_dropdown-menu-right.scss +8 -0
  158. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_dropdown.scss +70 -0
  159. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_footer.scss +9 -0
  160. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_forms.scss +4 -0
  161. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_grid.scss +15 -0
  162. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_input-group.scss +5 -0
  163. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_layout.scss +390 -0
  164. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_loading.scss +128 -0
  165. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_mixins.scss +1 -0
  166. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_mobile.scss +0 -0
  167. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_modal.scss +12 -0
  168. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_nav.scss +39 -0
  169. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_navbar.scss +122 -0
  170. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_others.scss +4 -0
  171. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_progress.scss +15 -0
  172. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_rtl.scss +287 -0
  173. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_sidebar.scss +462 -0
  174. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_switches.scss +286 -0
  175. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_tables.scss +20 -0
  176. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_temp.scss +39 -0
  177. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_typography.scss +36 -0
  178. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_utilities.scss +3 -0
  179. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_variables.scss +208 -0
  180. data/app/themes/default/assets/stylesheets/default/common/bootstrap/_widgets.scss +251 -0
  181. data/app/themes/default/assets/stylesheets/default/common/bootstrap/mixins/_all.scss +117 -0
  182. data/app/themes/default/assets/stylesheets/default/common/bootstrap/utilities/_background.scss +20 -0
  183. data/app/themes/default/assets/stylesheets/default/common/bootstrap/utilities/_borders.scss +17 -0
  184. data/app/themes/default/assets/stylesheets/default/common/bootstrap/utilities/_display.scss +14 -0
  185. data/app/themes/default/assets/stylesheets/default/frontend/core.scss +15 -0
  186. data/app/themes/default/assets/stylesheets/default/frontend.css +13 -0
  187. data/app/themes/default/views/layouts/auth.html.erb +35 -0
  188. data/app/themes/default/views/layouts/backend/_footer.html.erb +7 -0
  189. data/app/themes/default/views/layouts/backend/_header.html.erb +31 -0
  190. data/app/themes/default/views/layouts/backend/_sidebar.html.erb +53 -0
  191. data/app/themes/default/views/layouts/backend.html.erb +37 -0
  192. data/app/themes/default/views/layouts/frontend.liquid +22 -0
  193. data/app/uploaders/archangel/application_uploader.rb +65 -0
  194. data/app/uploaders/archangel/asset_uploader.rb +52 -0
  195. data/app/uploaders/archangel/avatar_uploader.rb +44 -0
  196. data/app/uploaders/archangel/favicon_uploader.rb +38 -0
  197. data/app/uploaders/archangel/logo_uploader.rb +44 -0
  198. data/app/views/archangel/backend/assets/_buttons.html.erb +18 -0
  199. data/app/views/archangel/backend/assets/_form.html.erb +27 -0
  200. data/app/views/archangel/backend/assets/edit.html.erb +21 -0
  201. data/app/views/archangel/backend/assets/index.html.erb +64 -0
  202. data/app/views/archangel/backend/assets/new.html.erb +21 -0
  203. data/app/views/archangel/backend/assets/show.html.erb +53 -0
  204. data/app/views/archangel/backend/collections/_buttons.html.erb +20 -0
  205. data/app/views/archangel/backend/collections/_field_fields.html.erb +9 -0
  206. data/app/views/archangel/backend/collections/_form.html.erb +24 -0
  207. data/app/views/archangel/backend/collections/edit.html.erb +21 -0
  208. data/app/views/archangel/backend/collections/index.html.erb +60 -0
  209. data/app/views/archangel/backend/collections/new.html.erb +21 -0
  210. data/app/views/archangel/backend/collections/show.html.erb +44 -0
  211. data/app/views/archangel/backend/dashboards/_buttons.html.erb +3 -0
  212. data/app/views/archangel/backend/dashboards/show.html.erb +23 -0
  213. data/app/views/archangel/backend/entries/_buttons.html.erb +20 -0
  214. data/app/views/archangel/backend/entries/_form.html.erb +25 -0
  215. data/app/views/archangel/backend/entries/edit.html.erb +21 -0
  216. data/app/views/archangel/backend/entries/index.html.erb +68 -0
  217. data/app/views/archangel/backend/entries/new.html.erb +21 -0
  218. data/app/views/archangel/backend/entries/show.html.erb +28 -0
  219. data/app/views/archangel/backend/pages/_buttons.html.erb +18 -0
  220. data/app/views/archangel/backend/pages/_form.html.erb +19 -0
  221. data/app/views/archangel/backend/pages/edit.html.erb +21 -0
  222. data/app/views/archangel/backend/pages/index.html.erb +68 -0
  223. data/app/views/archangel/backend/pages/new.html.erb +21 -0
  224. data/app/views/archangel/backend/pages/show.html.erb +49 -0
  225. data/app/views/archangel/backend/profiles/_buttons.html.erb +9 -0
  226. data/app/views/archangel/backend/profiles/_form.html.erb +31 -0
  227. data/app/views/archangel/backend/profiles/edit.html.erb +21 -0
  228. data/app/views/archangel/backend/profiles/show.html.erb +36 -0
  229. data/app/views/archangel/backend/sites/_additional_colors.html.erb +49 -0
  230. data/app/views/archangel/backend/sites/_alerts.html.erb +130 -0
  231. data/app/views/archangel/backend/sites/_badge.html.erb +99 -0
  232. data/app/views/archangel/backend/sites/_button.html.erb +243 -0
  233. data/app/views/archangel/backend/sites/_buttons.html.erb +9 -0
  234. data/app/views/archangel/backend/sites/_buttons_group.html.erb +230 -0
  235. data/app/views/archangel/backend/sites/_cards.html.erb +441 -0
  236. data/app/views/archangel/backend/sites/_carousel.html.erb +159 -0
  237. data/app/views/archangel/backend/sites/_colors.html.erb +41 -0
  238. data/app/views/archangel/backend/sites/_display_headings.html.erb +41 -0
  239. data/app/views/archangel/backend/sites/_dl.html.erb +38 -0
  240. data/app/views/archangel/backend/sites/_dropdowns.html.erb +475 -0
  241. data/app/views/archangel/backend/sites/_form.html.erb +37 -0
  242. data/app/views/archangel/backend/sites/_forms.html.erb +1176 -0
  243. data/app/views/archangel/backend/sites/_grays.html.erb +21 -0
  244. data/app/views/archangel/backend/sites/_headings.html.erb +21 -0
  245. data/app/views/archangel/backend/sites/_inline_text.html.erb +45 -0
  246. data/app/views/archangel/backend/sites/_jumbotron.html.erb +45 -0
  247. data/app/views/archangel/backend/sites/_list_group.html.erb +254 -0
  248. data/app/views/archangel/backend/sites/_modals.html.erb +41 -0
  249. data/app/views/archangel/backend/sites/_navs.html.erb +389 -0
  250. data/app/views/archangel/backend/sites/_pagination.html.erb +160 -0
  251. data/app/views/archangel/backend/sites/_social_buttons.html.erb +746 -0
  252. data/app/views/archangel/backend/sites/_tables.html.erb +372 -0
  253. data/app/views/archangel/backend/sites/edit.html.erb +21 -0
  254. data/app/views/archangel/backend/sites/sample.html.erb +38 -0
  255. data/app/views/archangel/backend/sites/show.html.erb +48 -0
  256. data/app/views/archangel/backend/templates/_buttons.html.erb +18 -0
  257. data/app/views/archangel/backend/templates/_form.html.erb +14 -0
  258. data/app/views/archangel/backend/templates/edit.html.erb +21 -0
  259. data/app/views/archangel/backend/templates/index.html.erb +60 -0
  260. data/app/views/archangel/backend/templates/new.html.erb +21 -0
  261. data/app/views/archangel/backend/templates/show.html.erb +34 -0
  262. data/app/views/archangel/backend/users/_buttons.html.erb +18 -0
  263. data/app/views/archangel/backend/users/_form.html.erb +24 -0
  264. data/app/views/archangel/backend/users/edit.html.erb +21 -0
  265. data/app/views/archangel/backend/users/index.html.erb +62 -0
  266. data/app/views/archangel/backend/users/new.html.erb +21 -0
  267. data/app/views/archangel/backend/users/show.html.erb +36 -0
  268. data/app/views/archangel/backend/widgets/_buttons.html.erb +18 -0
  269. data/app/views/archangel/backend/widgets/_form.html.erb +14 -0
  270. data/app/views/archangel/backend/widgets/edit.html.erb +21 -0
  271. data/app/views/archangel/backend/widgets/index.html.erb +60 -0
  272. data/app/views/archangel/backend/widgets/new.html.erb +21 -0
  273. data/app/views/archangel/backend/widgets/show.html.erb +42 -0
  274. data/app/views/archangel/errors/error_401.html.erb +13 -0
  275. data/app/views/archangel/errors/error_401.json.erb +4 -0
  276. data/app/views/archangel/errors/error_404.html.erb +13 -0
  277. data/app/views/archangel/errors/error_404.json.erb +4 -0
  278. data/app/views/archangel/frontend/pages/show.json.jbuilder +9 -0
  279. data/app/views/archangel/shared/_auth_token.html.erb +8 -0
  280. data/app/views/archangel/shared/_flash_messages.html.erb +9 -0
  281. data/app/views/devise/confirmations/new.html.erb +18 -0
  282. data/app/views/devise/mailer/confirmation_instructions.html.erb +5 -0
  283. data/app/views/devise/mailer/email_changed.html.erb +7 -0
  284. data/app/views/devise/mailer/password_change.html.erb +3 -0
  285. data/app/views/devise/mailer/reset_password_instructions.html.erb +8 -0
  286. data/app/views/devise/mailer/unlock_instructions.html.erb +7 -0
  287. data/app/views/devise/passwords/edit.html.erb +28 -0
  288. data/app/views/devise/passwords/new.html.erb +18 -0
  289. data/app/views/devise/registrations/edit.html.erb +45 -0
  290. data/app/views/devise/registrations/new.html.erb +41 -0
  291. data/app/views/devise/sessions/new.html.erb +28 -0
  292. data/app/views/devise/shared/_links.html.erb +41 -0
  293. data/app/views/devise/unlocks/new.html.erb +18 -0
  294. data/app/views/kaminari/_first_page.html.erb +11 -0
  295. data/app/views/kaminari/_gap.html.erb +8 -0
  296. data/app/views/kaminari/_last_page.html.erb +11 -0
  297. data/app/views/kaminari/_next_page.html.erb +11 -0
  298. data/app/views/kaminari/_page.html.erb +12 -0
  299. data/app/views/kaminari/_paginator.html.erb +25 -0
  300. data/app/views/kaminari/_prev_page.html.erb +11 -0
  301. data/archangel.gemspec +52 -0
  302. data/archangel.png +0 -0
  303. data/bin/rails +13 -0
  304. data/config/brakeman.yml +3 -0
  305. data/config/initializers/assets.rb +12 -0
  306. data/config/initializers/carrierwave.rb +14 -0
  307. data/config/initializers/devise.rb +341 -0
  308. data/config/initializers/kaminari.rb +13 -0
  309. data/config/initializers/liquid.rb +4 -0
  310. data/config/initializers/meta_tags.rb +31 -0
  311. data/config/initializers/simple_form.rb +177 -0
  312. data/config/initializers/simple_form_bootstrap.rb +177 -0
  313. data/config/locales/active_record.en.yml +80 -0
  314. data/config/locales/devise.en.yml +64 -0
  315. data/config/locales/devise_invitable.en.yml +31 -0
  316. data/config/locales/en.yml +109 -0
  317. data/config/locales/kaminari.en.yml +17 -0
  318. data/config/locales/responders.en.yml +12 -0
  319. data/config/locales/simple_form.en.yml +34 -0
  320. data/config/routes.rb +176 -0
  321. data/db/migrate/20171003191001_create_archangel_sites.rb +19 -0
  322. data/db/migrate/20171003210347_create_archangel_users.rb +68 -0
  323. data/db/migrate/20171005205520_create_archangel_templates.rb +18 -0
  324. data/db/migrate/20171005224054_create_archangel_pages.rb +30 -0
  325. data/db/migrate/20171006175939_create_archangel_widgets.rb +20 -0
  326. data/db/migrate/20171006184844_create_archangel_assets.rb +18 -0
  327. data/db/migrate/20171006194044_create_archangel_collections.rb +17 -0
  328. data/db/migrate/20171006194855_create_archangel_fields.rb +23 -0
  329. data/db/migrate/20171006195023_create_archangel_entries.rb +18 -0
  330. data/db/seeds.rb +65 -0
  331. data/docs/Developers.md +3 -0
  332. data/docs/ExtensionDevelopers.md +3 -0
  333. data/docs/ExtensionIdeas.md +132 -0
  334. data/docs/Release.md +25 -0
  335. data/docs/ThemeDevelopers.md +3 -0
  336. data/lib/archangel/application_responder.rb +15 -0
  337. data/lib/archangel/config.rb +21 -0
  338. data/lib/archangel/constants/language.rb +13 -0
  339. data/lib/archangel/constants/role.rb +13 -0
  340. data/lib/archangel/constants/theme.rb +18 -0
  341. data/lib/archangel/engine.rb +70 -0
  342. data/lib/archangel/i18n.rb +40 -0
  343. data/lib/archangel/liquid/drop.rb +119 -0
  344. data/lib/archangel/liquid/drops/page_drop.rb +33 -0
  345. data/lib/archangel/liquid/drops/site_drop.rb +30 -0
  346. data/lib/archangel/liquid/filters/link_to_filter.rb +35 -0
  347. data/lib/archangel/liquid/tags/application_tag.rb +17 -0
  348. data/lib/archangel/liquid/tags/asset_tag.rb +146 -0
  349. data/lib/archangel/liquid/tags/collection_tag.rb +135 -0
  350. data/lib/archangel/liquid/tags/collectionfor_tag.rb +91 -0
  351. data/lib/archangel/liquid/tags/csrf_meta_tags_tag.rb +28 -0
  352. data/lib/archangel/liquid/tags/gist_tag.rb +102 -0
  353. data/lib/archangel/liquid/tags/locale_tag.rb +27 -0
  354. data/lib/archangel/liquid/tags/meta_tags_tag.rb +30 -0
  355. data/lib/archangel/liquid/tags/text_direction_tag.rb +28 -0
  356. data/lib/archangel/liquid/tags/theme_javascript_tag.rb +30 -0
  357. data/lib/archangel/liquid/tags/theme_stylesheet_tag.rb +30 -0
  358. data/lib/archangel/liquid/tags/vimeo_tag.rb +125 -0
  359. data/lib/archangel/liquid/tags/widget_tag.rb +91 -0
  360. data/lib/archangel/liquid/tags/youtube_tag.rb +138 -0
  361. data/lib/archangel/liquid_view.rb +92 -0
  362. data/lib/archangel/testing_support/context/view_controller_context.rb +55 -0
  363. data/lib/archangel/testing_support/factories/archangel_assets.rb +20 -0
  364. data/lib/archangel/testing_support/factories/archangel_collections.rb +23 -0
  365. data/lib/archangel/testing_support/factories/archangel_entries.rb +17 -0
  366. data/lib/archangel/testing_support/factories/archangel_fields.rb +20 -0
  367. data/lib/archangel/testing_support/factories/archangel_pages.rb +39 -0
  368. data/lib/archangel/testing_support/factories/archangel_sites.rb +22 -0
  369. data/lib/archangel/testing_support/factories/archangel_templates.rb +22 -0
  370. data/lib/archangel/testing_support/factories/archangel_users.rb +53 -0
  371. data/lib/archangel/testing_support/factories/archangel_widgets.rb +19 -0
  372. data/lib/archangel/testing_support/fixtures/favicon.png +0 -0
  373. data/lib/archangel/testing_support/fixtures/image.gif +0 -0
  374. data/lib/archangel/testing_support/fixtures/stylesheet.css +3 -0
  375. data/lib/archangel/testing_support/helpers/authorization_helpers.rb +58 -0
  376. data/lib/archangel/testing_support/helpers/controller_helpers.rb +20 -0
  377. data/lib/archangel/testing_support/helpers/form_helpers.rb +18 -0
  378. data/lib/archangel/testing_support/helpers/routing_helpers.rb +22 -0
  379. data/lib/archangel/testing_support/matchers/match_stdout.rb +37 -0
  380. data/lib/archangel/testing_support/matchers/pundit.rb +17 -0
  381. data/lib/archangel/testing_support/rake/dummy_rake.rb +63 -0
  382. data/lib/archangel/testing_support/shared_contexts/rake.rb +26 -0
  383. data/lib/archangel/testing_support/support/capybara.rb +9 -0
  384. data/lib/archangel/testing_support/support/carrierwave.rb +13 -0
  385. data/lib/archangel/testing_support/support/controller.rb +11 -0
  386. data/lib/archangel/testing_support/support/database_cleaner.rb +22 -0
  387. data/lib/archangel/testing_support/support/devise.rb +7 -0
  388. data/lib/archangel/testing_support/support/factory_bot.rb +17 -0
  389. data/lib/archangel/testing_support/support/inputs.rb +24 -0
  390. data/lib/archangel/testing_support/support/liquid_filters.rb +23 -0
  391. data/lib/archangel/testing_support/support/liquid_tags.rb +23 -0
  392. data/lib/archangel/testing_support/support/shoulda.rb +12 -0
  393. data/lib/archangel/testing_support/support/verify_partial_doubles.rb +21 -0
  394. data/lib/archangel/testing_support/support.rb +26 -0
  395. data/lib/archangel/theme/themable_controller.rb +90 -0
  396. data/lib/archangel/version.rb +16 -0
  397. data/lib/archangel.rb +93 -0
  398. data/lib/generators/archangel/dummy/dummy_generator.rb +148 -0
  399. data/lib/generators/archangel/dummy/templates/config/database.yml +48 -0
  400. data/lib/generators/archangel/install/install_generator.rb +202 -0
  401. data/lib/generators/archangel/install/templates/.env.sample +4 -0
  402. data/lib/generators/archangel/install/templates/config/archangel.yml +54 -0
  403. data/lib/generators/archangel/install/templates/config/initializers/carrierwave.rb +15 -0
  404. data/lib/generators/archangel/install/templates/config/initializers/devise.rb +17 -0
  405. data/lib/generators/archangel/install/templates/vendor/assets/javascripts/archangel/auth.js +3 -0
  406. data/lib/generators/archangel/install/templates/vendor/assets/javascripts/archangel/backend.js +3 -0
  407. data/lib/generators/archangel/install/templates/vendor/assets/javascripts/archangel/frontend.js +3 -0
  408. data/lib/generators/archangel/install/templates/vendor/assets/stylesheets/archangel/auth.css +5 -0
  409. data/lib/generators/archangel/install/templates/vendor/assets/stylesheets/archangel/backend.css +5 -0
  410. data/lib/generators/archangel/install/templates/vendor/assets/stylesheets/archangel/frontend.css +5 -0
  411. data/lib/tasks/archangel/version_task.rake +8 -0
  412. data/lib/templates/erb/scaffold/_form.html.erb +13 -0
  413. data/package.json +33 -0
  414. data/spec/controllers/archangel/application_controller_spec.rb +183 -0
  415. data/spec/controllers/archangel/auth/registrations_controller_spec.rb +10 -0
  416. data/spec/controllers/archangel/auth_controller_spec.rb +8 -0
  417. data/spec/controllers/archangel/backend/assets_controller_spec.rb +237 -0
  418. data/spec/controllers/archangel/backend/collections_controller_spec.rb +252 -0
  419. data/spec/controllers/archangel/backend/dashboards_controller_spec.rb +27 -0
  420. data/spec/controllers/archangel/backend/entries_controller_spec.rb +273 -0
  421. data/spec/controllers/archangel/backend/pages_controller_spec.rb +204 -0
  422. data/spec/controllers/archangel/backend/profiles_controller_spec.rb +115 -0
  423. data/spec/controllers/archangel/backend/sites_controller_spec.rb +137 -0
  424. data/spec/controllers/archangel/backend/templates_controller_spec.rb +190 -0
  425. data/spec/controllers/archangel/backend/users_controller_spec.rb +181 -0
  426. data/spec/controllers/archangel/backend/widgets_controller_spec.rb +190 -0
  427. data/spec/controllers/archangel/backend_controller_spec.rb +8 -0
  428. data/spec/controllers/archangel/frontend/pages_controller_spec.rb +54 -0
  429. data/spec/controllers/archangel/frontend_controller_spec.rb +8 -0
  430. data/spec/features/auth/log_in_spec.rb +96 -0
  431. data/spec/features/auth/log_out_spec.rb +18 -0
  432. data/spec/features/auth/registration_spec.rb +27 -0
  433. data/spec/features/backend/duplicate_page_slugs_spec.rb +41 -0
  434. data/spec/features/frontend/drop_variables_spec.rb +125 -0
  435. data/spec/features/frontend/homepage_redirect_spec.rb +15 -0
  436. data/spec/helpers/archangel/application_helper_spec.rb +30 -0
  437. data/spec/helpers/archangel/auth_helper_spec.rb +8 -0
  438. data/spec/helpers/archangel/backend/assets_helper_spec.rb +10 -0
  439. data/spec/helpers/archangel/backend/collections_helper_spec.rb +10 -0
  440. data/spec/helpers/archangel/backend/dashboards_helper_spec.rb +10 -0
  441. data/spec/helpers/archangel/backend/entries_helper_spec.rb +10 -0
  442. data/spec/helpers/archangel/backend/pages_helper_spec.rb +10 -0
  443. data/spec/helpers/archangel/backend/sites_helper_spec.rb +10 -0
  444. data/spec/helpers/archangel/backend/templates_helper_spec.rb +10 -0
  445. data/spec/helpers/archangel/backend/users_helper_spec.rb +10 -0
  446. data/spec/helpers/archangel/backend/widgets_helper_spec.rb +10 -0
  447. data/spec/helpers/archangel/backend_helper_spec.rb +8 -0
  448. data/spec/helpers/archangel/flash_helper_spec.rb +29 -0
  449. data/spec/helpers/archangel/frontend/pages_helper_spec.rb +10 -0
  450. data/spec/helpers/archangel/frontend_helper_spec.rb +8 -0
  451. data/spec/inputs/archangel/date_picker_input_spec.rb +15 -0
  452. data/spec/inputs/archangel/date_time_picker_input_spec.rb +15 -0
  453. data/spec/inputs/archangel/field_classification_input_spec.rb +19 -0
  454. data/spec/inputs/archangel/language_input_spec.rb +17 -0
  455. data/spec/inputs/archangel/meta_keywords_input_spec.rb +13 -0
  456. data/spec/inputs/archangel/role_input_spec.rb +18 -0
  457. data/spec/inputs/archangel/theme_input_spec.rb +17 -0
  458. data/spec/inputs/archangel/time_picker_input_spec.rb +15 -0
  459. data/spec/inputs/archangel/wysiwyg_input_spec.rb +15 -0
  460. data/spec/jobs/archangel/application_job_spec.rb +8 -0
  461. data/spec/lib/archangel/liquid/filters/link_to_filter_spec.rb +63 -0
  462. data/spec/lib/archangel/liquid/tags/application_tag_spec.rb +12 -0
  463. data/spec/lib/archangel/liquid/tags/asset_tag_spec.rb +90 -0
  464. data/spec/lib/archangel/liquid/tags/collection_tag_spec.rb +105 -0
  465. data/spec/lib/archangel/liquid/tags/collectionfor_tag_spec.rb +164 -0
  466. data/spec/lib/archangel/liquid/tags/csrf_meta_tags_tag_spec.rb +33 -0
  467. data/spec/lib/archangel/liquid/tags/gist_tag_spec.rb +42 -0
  468. data/spec/lib/archangel/liquid/tags/locale_tag_spec.rb +22 -0
  469. data/spec/lib/archangel/liquid/tags/meta_tags_tag_spec.rb +24 -0
  470. data/spec/lib/archangel/liquid/tags/text_direction_tag_spec.rb +32 -0
  471. data/spec/lib/archangel/liquid/tags/theme_javascript_tag_spec.rb +25 -0
  472. data/spec/lib/archangel/liquid/tags/theme_stylesheet_tag_spec.rb +27 -0
  473. data/spec/lib/archangel/liquid/tags/vimeo_tag_spec.rb +39 -0
  474. data/spec/lib/archangel/liquid/tags/widget_tag_spec.rb +59 -0
  475. data/spec/lib/archangel/liquid/tags/youtube_tag_spec.rb +39 -0
  476. data/spec/lib/tasks/archangel/version_task_spec.rb +13 -0
  477. data/spec/mailers/archangel/application_job_spec.rb +8 -0
  478. data/spec/models/archangel/asset_spec.rb +32 -0
  479. data/spec/models/archangel/collection_spec.rb +25 -0
  480. data/spec/models/archangel/entry_spec.rb +20 -0
  481. data/spec/models/archangel/field_spec.rb +29 -0
  482. data/spec/models/archangel/page_spec.rb +94 -0
  483. data/spec/models/archangel/site_spec.rb +11 -0
  484. data/spec/models/archangel/template_spec.rb +24 -0
  485. data/spec/models/archangel/user_spec.rb +66 -0
  486. data/spec/models/archangel/widget_spec.rb +29 -0
  487. data/spec/policies/archangel/application_policy_spec.rb +8 -0
  488. data/spec/policies/archangel/asset_policy_spec.rb +22 -0
  489. data/spec/policies/archangel/collection_policy_spec.rb +20 -0
  490. data/spec/policies/archangel/entries_policy_spec.rb +22 -0
  491. data/spec/policies/archangel/page_policy_spec.rb +20 -0
  492. data/spec/policies/archangel/site_policy_spec.rb +30 -0
  493. data/spec/policies/archangel/template_policy_spec.rb +20 -0
  494. data/spec/policies/archangel/user_policy_spec.rb +20 -0
  495. data/spec/policies/archangel/widget_policy_spec.rb +20 -0
  496. data/spec/rails_helper.rb +46 -0
  497. data/spec/services/archangel/render_service_spec.rb +37 -0
  498. data/spec/services/archangel/template_render_service_spec.rb +42 -0
  499. data/spec/spec_helper.rb +23 -0
  500. data/spec/support/coveralls.rb +5 -0
  501. data/spec/uploaders/archangel/application_uploader_spec.rb +15 -0
  502. data/spec/uploaders/archangel/asset_uploader_spec.rb +71 -0
  503. data/spec/uploaders/archangel/avatar_uploader_spec.rb +50 -0
  504. data/spec/uploaders/archangel/favicon_uploader_spec.rb +39 -0
  505. data/spec/uploaders/archangel/logo_uploader_spec.rb +50 -0
  506. data/vendor/assets/icons/trumbowyg/icons.svg +1 -0
  507. data/vendor/assets/javascripts/sortable/sortable.js +3 -0
  508. data/vendor/assets/javascripts/trumbowyg/plugins/allowtagsfrompaste/trumbowyg.allowtagsfrompaste.js +143 -0
  509. data/vendor/assets/javascripts/trumbowyg/plugins/base64/trumbowyg.base64.js +129 -0
  510. data/vendor/assets/javascripts/trumbowyg/plugins/cleanpaste/trumbowyg.cleanpaste.js +110 -0
  511. data/vendor/assets/javascripts/trumbowyg/plugins/colors/trumbowyg.colors.js +183 -0
  512. data/vendor/assets/javascripts/trumbowyg/plugins/noembed/trumbowyg.noembed.js +104 -0
  513. data/vendor/assets/javascripts/trumbowyg/plugins/upload/trumbowyg.upload.js +261 -0
  514. data/vendor/assets/javascripts/trumbowyg/trumbowyg.js +1765 -0
  515. data/vendor/assets/stylesheets/trumbowyg/_trumbowyg.scss +831 -0
  516. data/vendor/assets/stylesheets/trumbowyg/plugins/colors/_trumbowyg.colors.scss +38 -0
  517. metadata +1003 -0
@@ -0,0 +1,1765 @@
1
+ jQuery.trumbowyg = {
2
+ langs: {
3
+ en: {
4
+ viewHTML: 'View HTML',
5
+
6
+ undo: 'Undo',
7
+ redo: 'Redo',
8
+
9
+ formatting: 'Formatting',
10
+ p: 'Paragraph',
11
+ blockquote: 'Quote',
12
+ code: 'Code',
13
+ header: 'Header',
14
+
15
+ bold: 'Bold',
16
+ italic: 'Italic',
17
+ strikethrough: 'Stroke',
18
+ underline: 'Underline',
19
+
20
+ strong: 'Strong',
21
+ em: 'Emphasis',
22
+ del: 'Deleted',
23
+
24
+ superscript: 'Superscript',
25
+ subscript: 'Subscript',
26
+
27
+ unorderedList: 'Unordered list',
28
+ orderedList: 'Ordered list',
29
+
30
+ insertImage: 'Insert Image',
31
+ link: 'Link',
32
+ createLink: 'Insert link',
33
+ unlink: 'Remove link',
34
+
35
+ justifyLeft: 'Align Left',
36
+ justifyCenter: 'Align Center',
37
+ justifyRight: 'Align Right',
38
+ justifyFull: 'Align Justify',
39
+
40
+ horizontalRule: 'Insert horizontal rule',
41
+ removeformat: 'Remove format',
42
+
43
+ fullscreen: 'Fullscreen',
44
+
45
+ close: 'Close',
46
+
47
+ submit: 'Confirm',
48
+ reset: 'Cancel',
49
+
50
+ required: 'Required',
51
+ description: 'Description',
52
+ title: 'Title',
53
+ text: 'Text',
54
+ target: 'Target',
55
+ width: 'Width'
56
+ }
57
+ },
58
+
59
+ // Plugins
60
+ plugins: {},
61
+
62
+ // SVG Path globally
63
+ svgPath: null,
64
+
65
+ hideButtonTexts: null
66
+ };
67
+
68
+ // Makes default options read-only
69
+ Object.defineProperty(jQuery.trumbowyg, 'defaultOptions', {
70
+ value: {
71
+ lang: 'en',
72
+
73
+ fixedBtnPane: false,
74
+ fixedFullWidth: false,
75
+ autogrow: false,
76
+ autogrowOnEnter: false,
77
+ imageWidthModalEdit: false,
78
+
79
+ prefix: 'trumbowyg-',
80
+
81
+ semantic: true,
82
+ resetCss: false,
83
+ removeformatPasted: false,
84
+ tagsToRemove: [],
85
+ btns: [
86
+ ['viewHTML'],
87
+ ['undo', 'redo'], // Only supported in Blink browsers
88
+ ['formatting'],
89
+ ['strong', 'em', 'del'],
90
+ ['superscript', 'subscript'],
91
+ ['link'],
92
+ ['insertImage'],
93
+ ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull'],
94
+ ['unorderedList', 'orderedList'],
95
+ ['horizontalRule'],
96
+ ['removeformat'],
97
+ ['fullscreen']
98
+ ],
99
+ // For custom button definitions
100
+ btnsDef: {},
101
+
102
+ inlineElementsSelector: 'a,abbr,acronym,b,caption,cite,code,col,dfn,dir,dt,dd,em,font,hr,i,kbd,li,q,span,strikeout,strong,sub,sup,u',
103
+
104
+ pasteHandlers: [],
105
+
106
+ // imgDblClickHandler: default is defined in constructor
107
+
108
+ plugins: {}
109
+ },
110
+ writable: false,
111
+ enumerable: true,
112
+ configurable: false
113
+ });
114
+
115
+ (function (navigator, window, document, $) {
116
+ 'use strict';
117
+
118
+ var CONFIRM_EVENT = 'tbwconfirm',
119
+ CANCEL_EVENT = 'tbwcancel';
120
+
121
+ $.fn.trumbowyg = function (options, params) {
122
+ var trumbowygDataName = 'trumbowyg';
123
+ if (options === Object(options) || !options) {
124
+ return this.each(function () {
125
+ if (!$(this).data(trumbowygDataName)) {
126
+ $(this).data(trumbowygDataName, new Trumbowyg(this, options));
127
+ }
128
+ });
129
+ }
130
+ if (this.length === 1) {
131
+ try {
132
+ var t = $(this).data(trumbowygDataName);
133
+ switch (options) {
134
+ // Exec command
135
+ case 'execCmd':
136
+ return t.execCmd(params.cmd, params.param, params.forceCss);
137
+
138
+ // Modal box
139
+ case 'openModal':
140
+ return t.openModal(params.title, params.content);
141
+ case 'closeModal':
142
+ return t.closeModal();
143
+ case 'openModalInsert':
144
+ return t.openModalInsert(params.title, params.fields, params.callback);
145
+
146
+ // Range
147
+ case 'saveRange':
148
+ return t.saveRange();
149
+ case 'getRange':
150
+ return t.range;
151
+ case 'getRangeText':
152
+ return t.getRangeText();
153
+ case 'restoreRange':
154
+ return t.restoreRange();
155
+
156
+ // Enable/disable
157
+ case 'enable':
158
+ return t.setDisabled(false);
159
+ case 'disable':
160
+ return t.setDisabled(true);
161
+
162
+ // Toggle
163
+ case 'toggle':
164
+ return t.toggle();
165
+
166
+ // Destroy
167
+ case 'destroy':
168
+ return t.destroy();
169
+
170
+ // Empty
171
+ case 'empty':
172
+ return t.empty();
173
+
174
+ // HTML
175
+ case 'html':
176
+ return t.html(params);
177
+ }
178
+ } catch (c) {
179
+ }
180
+ }
181
+
182
+ return false;
183
+ };
184
+
185
+ // @param: editorElem is the DOM element
186
+ var Trumbowyg = function (editorElem, options) {
187
+ var t = this,
188
+ trumbowygIconsId = 'trumbowyg-icons',
189
+ $trumbowyg = $.trumbowyg;
190
+
191
+ // Get the document of the element. It use to makes the plugin
192
+ // compatible on iframes.
193
+ t.doc = editorElem.ownerDocument || document;
194
+
195
+ // jQuery object of the editor
196
+ t.$ta = $(editorElem); // $ta : Textarea
197
+ t.$c = $(editorElem); // $c : creator
198
+
199
+ options = options || {};
200
+
201
+ // Localization management
202
+ if (options.lang != null || $trumbowyg.langs[options.lang] != null) {
203
+ t.lang = $.extend(true, {}, $trumbowyg.langs.en, $trumbowyg.langs[options.lang]);
204
+ } else {
205
+ t.lang = $trumbowyg.langs.en;
206
+ }
207
+
208
+ t.hideButtonTexts = $trumbowyg.hideButtonTexts != null ? $trumbowyg.hideButtonTexts : options.hideButtonTexts;
209
+
210
+ // SVG path
211
+ var svgPathOption = $trumbowyg.svgPath != null ? $trumbowyg.svgPath : options.svgPath;
212
+ t.hasSvg = svgPathOption !== false;
213
+ t.svgPath = !!t.doc.querySelector('base') ? window.location.href.split('#')[0] : '';
214
+ if ($('#' + trumbowygIconsId, t.doc).length === 0 && svgPathOption !== false) {
215
+ if (svgPathOption == null) {
216
+ // Hack to get svgPathOption based on trumbowyg.js path
217
+ var scriptElements = document.getElementsByTagName('script');
218
+ for (var i = 0; i < scriptElements.length; i += 1) {
219
+ var source = scriptElements[i].src;
220
+ var matches = source.match('trumbowyg(\.min)?\.js');
221
+ if (matches != null) {
222
+ svgPathOption = source.substring(0, source.indexOf(matches[0])) + 'ui/icons.svg';
223
+ }
224
+ }
225
+ if (svgPathOption == null) {
226
+ console.warn('You must define svgPath: https://goo.gl/CfTY9U'); // jshint ignore:line
227
+ }
228
+ }
229
+
230
+ var div = t.doc.createElement('div');
231
+ div.id = trumbowygIconsId;
232
+ t.doc.body.insertBefore(div, t.doc.body.childNodes[0]);
233
+ $.ajax({
234
+ async: true,
235
+ type: 'GET',
236
+ contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
237
+ dataType: 'xml',
238
+ crossDomain: true,
239
+ url: svgPathOption,
240
+ data: null,
241
+ beforeSend: null,
242
+ complete: null,
243
+ success: function (data) {
244
+ div.innerHTML = new XMLSerializer().serializeToString(data.documentElement);
245
+ }
246
+ });
247
+ }
248
+
249
+ /**
250
+ * When the button is associated to a empty object
251
+ * fn and title attributs are defined from the button key value
252
+ *
253
+ * For example
254
+ * foo: {}
255
+ * is equivalent to :
256
+ * foo: {
257
+ * fn: 'foo',
258
+ * title: this.lang.foo
259
+ * }
260
+ */
261
+ var h = t.lang.header, // Header translation
262
+ isBlinkFunction = function () {
263
+ return (window.chrome || (window.Intl && Intl.v8BreakIterator)) && 'CSS' in window;
264
+ };
265
+ t.btnsDef = {
266
+ viewHTML: {
267
+ fn: 'toggle'
268
+ },
269
+
270
+ undo: {
271
+ isSupported: isBlinkFunction,
272
+ key: 'Z'
273
+ },
274
+ redo: {
275
+ isSupported: isBlinkFunction,
276
+ key: 'Y'
277
+ },
278
+
279
+ p: {
280
+ fn: 'formatBlock'
281
+ },
282
+ blockquote: {
283
+ fn: 'formatBlock'
284
+ },
285
+ h1: {
286
+ fn: 'formatBlock',
287
+ title: h + ' 1'
288
+ },
289
+ h2: {
290
+ fn: 'formatBlock',
291
+ title: h + ' 2'
292
+ },
293
+ h3: {
294
+ fn: 'formatBlock',
295
+ title: h + ' 3'
296
+ },
297
+ h4: {
298
+ fn: 'formatBlock',
299
+ title: h + ' 4'
300
+ },
301
+ subscript: {
302
+ tag: 'sub'
303
+ },
304
+ superscript: {
305
+ tag: 'sup'
306
+ },
307
+
308
+ bold: {
309
+ key: 'B',
310
+ tag: 'b'
311
+ },
312
+ italic: {
313
+ key: 'I',
314
+ tag: 'i'
315
+ },
316
+ underline: {
317
+ tag: 'u'
318
+ },
319
+ strikethrough: {
320
+ tag: 'strike'
321
+ },
322
+
323
+ strong: {
324
+ fn: 'bold',
325
+ key: 'B'
326
+ },
327
+ em: {
328
+ fn: 'italic',
329
+ key: 'I'
330
+ },
331
+ del: {
332
+ fn: 'strikethrough'
333
+ },
334
+
335
+ createLink: {
336
+ key: 'K',
337
+ tag: 'a'
338
+ },
339
+ unlink: {},
340
+
341
+ insertImage: {},
342
+
343
+ justifyLeft: {
344
+ tag: 'left',
345
+ forceCss: true
346
+ },
347
+ justifyCenter: {
348
+ tag: 'center',
349
+ forceCss: true
350
+ },
351
+ justifyRight: {
352
+ tag: 'right',
353
+ forceCss: true
354
+ },
355
+ justifyFull: {
356
+ tag: 'justify',
357
+ forceCss: true
358
+ },
359
+
360
+ unorderedList: {
361
+ fn: 'insertUnorderedList',
362
+ tag: 'ul'
363
+ },
364
+ orderedList: {
365
+ fn: 'insertOrderedList',
366
+ tag: 'ol'
367
+ },
368
+
369
+ horizontalRule: {
370
+ fn: 'insertHorizontalRule'
371
+ },
372
+
373
+ removeformat: {},
374
+
375
+ fullscreen: {
376
+ class: 'trumbowyg-not-disable'
377
+ },
378
+ close: {
379
+ fn: 'destroy',
380
+ class: 'trumbowyg-not-disable'
381
+ },
382
+
383
+ // Dropdowns
384
+ formatting: {
385
+ dropdown: ['p', 'blockquote', 'h1', 'h2', 'h3', 'h4'],
386
+ ico: 'p'
387
+ },
388
+ link: {
389
+ dropdown: ['createLink', 'unlink']
390
+ }
391
+ };
392
+
393
+ // Defaults Options
394
+ t.o = $.extend(true, {}, $trumbowyg.defaultOptions, options);
395
+ if (!t.o.hasOwnProperty('imgDblClickHandler')) {
396
+ t.o.imgDblClickHandler = t.getDefaultImgDblClickHandler();
397
+ }
398
+
399
+ t.disabled = t.o.disabled || (editorElem.nodeName === 'TEXTAREA' && editorElem.disabled);
400
+
401
+ if (options.btns) {
402
+ t.o.btns = options.btns;
403
+ } else if (!t.o.semantic) {
404
+ t.o.btns[3] = ['bold', 'italic', 'underline', 'strikethrough'];
405
+ }
406
+
407
+ $.each(t.o.btnsDef, function (btnName, btnDef) {
408
+ t.addBtnDef(btnName, btnDef);
409
+ });
410
+
411
+ // put this here in the event it would be merged in with options
412
+ t.eventNamespace = 'trumbowyg-event';
413
+
414
+ // Keyboard shortcuts are load in this array
415
+ t.keys = [];
416
+
417
+ // Tag to button dynamically hydrated
418
+ t.tagToButton = {};
419
+ t.tagHandlers = [];
420
+
421
+ // Admit multiple paste handlers
422
+ t.pasteHandlers = [].concat(t.o.pasteHandlers);
423
+
424
+ // Check if browser is IE
425
+ t.isIE = (navigator.userAgent.indexOf('MSIE') !== -1 || navigator.appVersion.indexOf('Trident/') !== -1);
426
+
427
+ t.init();
428
+ };
429
+
430
+ Trumbowyg.prototype = {
431
+ DEFAULT_SEMANTIC_MAP: {
432
+ 'b': 'strong',
433
+ 'i': 'em',
434
+ 's': 'del',
435
+ 'strike': 'del',
436
+ 'div': 'p'
437
+ },
438
+
439
+ init: function () {
440
+ var t = this;
441
+ t.height = t.$ta.height();
442
+
443
+ t.initPlugins();
444
+
445
+ try {
446
+ // Disable image resize, try-catch for old IE
447
+ t.doc.execCommand('enableObjectResizing', false, false);
448
+ t.doc.execCommand('defaultParagraphSeparator', false, 'p');
449
+ } catch (e) {
450
+ }
451
+
452
+ t.buildEditor();
453
+ t.buildBtnPane();
454
+
455
+ t.fixedBtnPaneEvents();
456
+
457
+ t.buildOverlay();
458
+
459
+ setTimeout(function () {
460
+ if (t.disabled) {
461
+ t.setDisabled(true);
462
+ }
463
+ t.$c.trigger('tbwinit');
464
+ });
465
+ },
466
+
467
+ addBtnDef: function (btnName, btnDef) {
468
+ this.btnsDef[btnName] = btnDef;
469
+ },
470
+
471
+ buildEditor: function () {
472
+ var t = this,
473
+ prefix = t.o.prefix,
474
+ html = '';
475
+
476
+ t.$box = $('<div/>', {
477
+ class: prefix + 'box ' + prefix + 'editor-visible ' + prefix + t.o.lang + ' trumbowyg'
478
+ });
479
+
480
+ // $ta = Textarea
481
+ // $ed = Editor
482
+ t.isTextarea = t.$ta.is('textarea');
483
+ if (t.isTextarea) {
484
+ html = t.$ta.val();
485
+ t.$ed = $('<div/>');
486
+ t.$box
487
+ .insertAfter(t.$ta)
488
+ .append(t.$ed, t.$ta);
489
+ } else {
490
+ t.$ed = t.$ta;
491
+ html = t.$ed.html();
492
+
493
+ t.$ta = $('<textarea/>', {
494
+ name: t.$ta.attr('id'),
495
+ height: t.height
496
+ }).val(html);
497
+
498
+ t.$box
499
+ .insertAfter(t.$ed)
500
+ .append(t.$ta, t.$ed);
501
+ t.syncCode();
502
+ }
503
+
504
+ t.$ta
505
+ .addClass(prefix + 'textarea')
506
+ .attr('tabindex', -1)
507
+ ;
508
+
509
+ t.$ed
510
+ .addClass(prefix + 'editor')
511
+ .attr({
512
+ contenteditable: true,
513
+ dir: t.lang._dir || 'ltr'
514
+ })
515
+ .html(html)
516
+ ;
517
+
518
+ if (t.o.tabindex) {
519
+ t.$ed.attr('tabindex', t.o.tabindex);
520
+ }
521
+
522
+ if (t.$c.is('[placeholder]')) {
523
+ t.$ed.attr('placeholder', t.$c.attr('placeholder'));
524
+ }
525
+
526
+ if (t.$c.is('[spellcheck]')) {
527
+ t.$ed.attr('spellcheck', t.$c.attr('spellcheck'));
528
+ }
529
+
530
+ if (t.o.resetCss) {
531
+ t.$ed.addClass(prefix + 'reset-css');
532
+ }
533
+
534
+ if (!t.o.autogrow) {
535
+ t.$ta.add(t.$ed).css({
536
+ height: t.height
537
+ });
538
+ }
539
+
540
+ t.semanticCode();
541
+
542
+ if (t.o.autogrowOnEnter) {
543
+ t.$ed.addClass(prefix + 'autogrow-on-enter');
544
+ }
545
+
546
+ var ctrl = false,
547
+ composition = false,
548
+ debounceButtonPaneStatus,
549
+ updateEventName = 'keyup';
550
+
551
+ t.$ed
552
+ .on('dblclick', 'img', t.o.imgDblClickHandler)
553
+ .on('keydown', function (e) {
554
+ if ((e.ctrlKey || e.metaKey) && !e.altKey) {
555
+ ctrl = true;
556
+ var key = t.keys[String.fromCharCode(e.which).toUpperCase()];
557
+
558
+ try {
559
+ t.execCmd(key.fn, key.param);
560
+ return false;
561
+ } catch (c) {
562
+ }
563
+ }
564
+ })
565
+ .on('compositionstart compositionupdate', function () {
566
+ composition = true;
567
+ })
568
+ .on(updateEventName + ' compositionend', function (e) {
569
+ if (e.type === 'compositionend') {
570
+ composition = false;
571
+ } else if (composition) {
572
+ return;
573
+ }
574
+
575
+ var keyCode = e.which;
576
+
577
+ if (keyCode >= 37 && keyCode <= 40) {
578
+ return;
579
+ }
580
+
581
+ if ((e.ctrlKey || e.metaKey) && (keyCode === 89 || keyCode === 90)) {
582
+ t.$c.trigger('tbwchange');
583
+ } else if (!ctrl && keyCode !== 17) {
584
+ var compositionEndIE = t.isIE ? e.type === 'compositionend' : true;
585
+ t.semanticCode(false, compositionEndIE && keyCode === 13);
586
+ t.$c.trigger('tbwchange');
587
+ } else if (typeof e.which === 'undefined') {
588
+ t.semanticCode(false, false, true);
589
+ }
590
+
591
+ setTimeout(function () {
592
+ ctrl = false;
593
+ }, 50);
594
+ })
595
+ .on('mouseup keydown keyup', function (e) {
596
+ if ((!e.ctrlKey && !e.metaKey) || e.altKey) {
597
+ setTimeout(function () { // "hold on" to the ctrl key for 50ms
598
+ ctrl = false;
599
+ }, 50);
600
+ }
601
+ clearTimeout(debounceButtonPaneStatus);
602
+ debounceButtonPaneStatus = setTimeout(function () {
603
+ t.updateButtonPaneStatus();
604
+ }, 50);
605
+ })
606
+ .on('focus blur', function (e) {
607
+ t.$c.trigger('tbw' + e.type);
608
+ if (e.type === 'blur') {
609
+ $('.' + prefix + 'active-button', t.$btnPane).removeClass(prefix + 'active-button ' + prefix + 'active');
610
+ }
611
+ if (t.o.autogrowOnEnter) {
612
+ if (t.autogrowOnEnterDontClose) {
613
+ return;
614
+ }
615
+ if (e.type === 'focus') {
616
+ t.autogrowOnEnterWasFocused = true;
617
+ t.autogrowEditorOnEnter();
618
+ }
619
+ else if (!t.o.autogrow) {
620
+ t.$ed.css({height: t.$ed.css('min-height')});
621
+ t.$c.trigger('tbwresize');
622
+ }
623
+ }
624
+ })
625
+ .on('cut', function () {
626
+ setTimeout(function () {
627
+ t.semanticCode(false, true);
628
+ t.$c.trigger('tbwchange');
629
+ }, 0);
630
+ })
631
+ .on('paste', function (e) {
632
+ if (t.o.removeformatPasted) {
633
+ e.preventDefault();
634
+
635
+ if (window.getSelection && window.getSelection().deleteFromDocument) {
636
+ window.getSelection().deleteFromDocument();
637
+ }
638
+
639
+ try {
640
+ // IE
641
+ var text = window.clipboardData.getData('Text');
642
+
643
+ try {
644
+ // <= IE10
645
+ t.doc.selection.createRange().pasteHTML(text);
646
+ } catch (c) {
647
+ // IE 11
648
+ t.doc.getSelection().getRangeAt(0).insertNode(t.doc.createTextNode(text));
649
+ }
650
+ t.$c.trigger('tbwchange', e);
651
+ } catch (d) {
652
+ // Not IE
653
+ t.execCmd('insertText', (e.originalEvent || e).clipboardData.getData('text/plain'));
654
+ }
655
+ }
656
+
657
+ // Call pasteHandlers
658
+ $.each(t.pasteHandlers, function (i, pasteHandler) {
659
+ pasteHandler(e);
660
+ });
661
+
662
+ setTimeout(function () {
663
+ t.semanticCode(false, true);
664
+ t.$c.trigger('tbwpaste', e);
665
+ }, 0);
666
+ });
667
+
668
+ t.$ta
669
+ .on('keyup', function () {
670
+ t.$c.trigger('tbwchange');
671
+ })
672
+ .on('paste', function () {
673
+ setTimeout(function () {
674
+ t.$c.trigger('tbwchange');
675
+ }, 0);
676
+ });
677
+
678
+ t.$box.on('keydown', function (e) {
679
+ if (e.which === 27 && $('.' + prefix + 'modal-box', t.$box).length === 1) {
680
+ t.closeModal();
681
+ return false;
682
+ }
683
+ });
684
+ },
685
+
686
+ //autogrow when entering logic
687
+ autogrowEditorOnEnter: function () {
688
+ var t = this;
689
+ t.$ed.removeClass('autogrow-on-enter');
690
+ var oldHeight = t.$ed[0].clientHeight;
691
+ t.$ed.height('auto');
692
+ var totalHeight = t.$ed[0].scrollHeight;
693
+ t.$ed.addClass('autogrow-on-enter');
694
+ if (oldHeight !== totalHeight) {
695
+ t.$ed.height(oldHeight);
696
+ setTimeout(function () {
697
+ t.$ed.css({height: totalHeight});
698
+ t.$c.trigger('tbwresize');
699
+ }, 0);
700
+ }
701
+ },
702
+
703
+ // Build button pane, use o.btns option
704
+ buildBtnPane: function () {
705
+ var t = this,
706
+ prefix = t.o.prefix;
707
+
708
+ var $btnPane = t.$btnPane = $('<div/>', {
709
+ class: prefix + 'button-pane'
710
+ });
711
+
712
+ $.each(t.o.btns, function (i, btnGrp) {
713
+ if (!$.isArray(btnGrp)) {
714
+ btnGrp = [btnGrp];
715
+ }
716
+
717
+ var $btnGroup = $('<div/>', {
718
+ class: prefix + 'button-group ' + ((btnGrp.indexOf('fullscreen') >= 0) ? prefix + 'right' : '')
719
+ });
720
+ $.each(btnGrp, function (i, btn) {
721
+ try { // Prevent buildBtn error
722
+ if (t.isSupportedBtn(btn)) { // It's a supported button
723
+ $btnGroup.append(t.buildBtn(btn));
724
+ }
725
+ } catch (c) {
726
+ }
727
+ });
728
+
729
+ if ($btnGroup.html().trim().length > 0) {
730
+ $btnPane.append($btnGroup);
731
+ }
732
+ });
733
+
734
+ t.$box.prepend($btnPane);
735
+ },
736
+
737
+ // Build a button and his action
738
+ buildBtn: function (btnName) { // btnName is name of the button
739
+ var t = this,
740
+ prefix = t.o.prefix,
741
+ btn = t.btnsDef[btnName],
742
+ isDropdown = btn.dropdown,
743
+ hasIcon = btn.hasIcon != null ? btn.hasIcon : true,
744
+ textDef = t.lang[btnName] || btnName,
745
+
746
+ $btn = $('<button/>', {
747
+ type: 'button',
748
+ class: prefix + btnName + '-button ' + (btn.class || '') + (!hasIcon ? ' ' + prefix + 'textual-button' : ''),
749
+ html: t.hasSvg && hasIcon ?
750
+ '<svg><use xlink:href="' + t.svgPath + '#' + prefix + (btn.ico || btnName).replace(/([A-Z]+)/g, '-$1').toLowerCase() + '"/></svg>' :
751
+ t.hideButtonTexts ? '' : (btn.text || btn.title || t.lang[btnName] || btnName),
752
+ title: (btn.title || btn.text || textDef) + ((btn.key) ? ' (Ctrl + ' + btn.key + ')' : ''),
753
+ tabindex: -1,
754
+ mousedown: function () {
755
+ if (!isDropdown || $('.' + btnName + '-' + prefix + 'dropdown', t.$box).is(':hidden')) {
756
+ $('body', t.doc).trigger('mousedown');
757
+ }
758
+
759
+ if ((t.$btnPane.hasClass(prefix + 'disable') || t.$box.hasClass(prefix + 'disabled')) &&
760
+ !$(this).hasClass(prefix + 'active') &&
761
+ !$(this).hasClass(prefix + 'not-disable')) {
762
+ return false;
763
+ }
764
+
765
+ t.execCmd((isDropdown ? 'dropdown' : false) || btn.fn || btnName, btn.param || btnName, btn.forceCss);
766
+
767
+ return false;
768
+ }
769
+ });
770
+
771
+ if (isDropdown) {
772
+ $btn.addClass(prefix + 'open-dropdown');
773
+ var dropdownPrefix = prefix + 'dropdown',
774
+ dropdownOptions = { // the dropdown
775
+ class: dropdownPrefix + '-' + btnName + ' ' + dropdownPrefix + ' ' + prefix + 'fixed-top'
776
+ };
777
+ dropdownOptions['data-' + dropdownPrefix] = btnName;
778
+ var $dropdown = $('<div/>', dropdownOptions);
779
+ $.each(isDropdown, function (i, def) {
780
+ if (t.btnsDef[def] && t.isSupportedBtn(def)) {
781
+ $dropdown.append(t.buildSubBtn(def));
782
+ }
783
+ });
784
+ t.$box.append($dropdown.hide());
785
+ } else if (btn.key) {
786
+ t.keys[btn.key] = {
787
+ fn: btn.fn || btnName,
788
+ param: btn.param || btnName
789
+ };
790
+ }
791
+
792
+ if (!isDropdown) {
793
+ t.tagToButton[(btn.tag || btnName).toLowerCase()] = btnName;
794
+ }
795
+
796
+ return $btn;
797
+ },
798
+
799
+ // Build a button for dropdown menu
800
+ // @param n : name of the subbutton
801
+ buildSubBtn: function (btnName) {
802
+ var t = this,
803
+ prefix = t.o.prefix,
804
+ btn = t.btnsDef[btnName],
805
+ hasIcon = btn.hasIcon != null ? btn.hasIcon : true;
806
+
807
+ if (btn.key) {
808
+ t.keys[btn.key] = {
809
+ fn: btn.fn || btnName,
810
+ param: btn.param || btnName
811
+ };
812
+ }
813
+
814
+ t.tagToButton[(btn.tag || btnName).toLowerCase()] = btnName;
815
+
816
+ return $('<button/>', {
817
+ type: 'button',
818
+ class: prefix + btnName + '-dropdown-button' + (btn.ico ? ' ' + prefix + btn.ico + '-button' : ''),
819
+ html: t.hasSvg && hasIcon ? '<svg><use xlink:href="' + t.svgPath + '#' + prefix + (btn.ico || btnName).replace(/([A-Z]+)/g, '-$1').toLowerCase() + '"/></svg>' + (btn.text || btn.title || t.lang[btnName] || btnName) : (btn.text || btn.title || t.lang[btnName] || btnName),
820
+ title: ((btn.key) ? ' (Ctrl + ' + btn.key + ')' : null),
821
+ style: btn.style || null,
822
+ mousedown: function () {
823
+ $('body', t.doc).trigger('mousedown');
824
+
825
+ t.execCmd(btn.fn || btnName, btn.param || btnName, btn.forceCss);
826
+
827
+ return false;
828
+ }
829
+ });
830
+ },
831
+
832
+ // Check if button is supported
833
+ isSupportedBtn: function (b) {
834
+ try {
835
+ return this.btnsDef[b].isSupported();
836
+ } catch (c) {
837
+ }
838
+ return true;
839
+ },
840
+
841
+ // Build overlay for modal box
842
+ buildOverlay: function () {
843
+ var t = this;
844
+ t.$overlay = $('<div/>', {
845
+ class: t.o.prefix + 'overlay'
846
+ }).appendTo(t.$box);
847
+ return t.$overlay;
848
+ },
849
+
850
+ showOverlay: function () {
851
+ var t = this;
852
+ $(window).trigger('scroll');
853
+ t.$overlay.fadeIn(200);
854
+ t.$box.addClass(t.o.prefix + 'box-blur');
855
+ },
856
+
857
+ hideOverlay: function () {
858
+ var t = this;
859
+ t.$overlay.fadeOut(50);
860
+ t.$box.removeClass(t.o.prefix + 'box-blur');
861
+ },
862
+
863
+ // Management of fixed button pane
864
+ fixedBtnPaneEvents: function () {
865
+ var t = this,
866
+ fixedFullWidth = t.o.fixedFullWidth,
867
+ $box = t.$box;
868
+
869
+ if (!t.o.fixedBtnPane) {
870
+ return;
871
+ }
872
+
873
+ t.isFixed = false;
874
+
875
+ $(window)
876
+ .on('scroll.' + t.eventNamespace + ' resize.' + t.eventNamespace, function () {
877
+ if (!$box) {
878
+ return;
879
+ }
880
+
881
+ t.syncCode();
882
+
883
+ var scrollTop = $(window).scrollTop(),
884
+ offset = $box.offset().top + 1,
885
+ bp = t.$btnPane,
886
+ oh = bp.outerHeight() - 2;
887
+
888
+ if ((scrollTop - offset > 0) && ((scrollTop - offset - t.height) < 0)) {
889
+ if (!t.isFixed) {
890
+ t.isFixed = true;
891
+ bp.css({
892
+ position: 'fixed',
893
+ top: 0,
894
+ left: fixedFullWidth ? '0' : 'auto',
895
+ zIndex: 7
896
+ });
897
+ $([t.$ta, t.$ed]).css({marginTop: bp.height()});
898
+ }
899
+ bp.css({
900
+ width: fixedFullWidth ? '100%' : (($box.width() - 1) + 'px')
901
+ });
902
+
903
+ $('.' + t.o.prefix + 'fixed-top', $box).css({
904
+ position: fixedFullWidth ? 'fixed' : 'absolute',
905
+ top: fixedFullWidth ? oh : oh + (scrollTop - offset) + 'px',
906
+ zIndex: 15
907
+ });
908
+ } else if (t.isFixed) {
909
+ t.isFixed = false;
910
+ bp.removeAttr('style');
911
+ $([t.$ta, t.$ed]).css({marginTop: 0});
912
+ $('.' + t.o.prefix + 'fixed-top', $box).css({
913
+ position: 'absolute',
914
+ top: oh
915
+ });
916
+ }
917
+ });
918
+ },
919
+
920
+ // Disable editor
921
+ setDisabled: function (disable) {
922
+ var t = this,
923
+ prefix = t.o.prefix;
924
+
925
+ t.disabled = disable;
926
+
927
+ if (disable) {
928
+ t.$ta.attr('disabled', true);
929
+ } else {
930
+ t.$ta.removeAttr('disabled');
931
+ }
932
+ t.$box.toggleClass(prefix + 'disabled', disable);
933
+ t.$ed.attr('contenteditable', !disable);
934
+ },
935
+
936
+ // Destroy the editor
937
+ destroy: function () {
938
+ var t = this,
939
+ prefix = t.o.prefix;
940
+
941
+ if (t.isTextarea) {
942
+ t.$box.after(
943
+ t.$ta
944
+ .css({height: ''})
945
+ .val(t.html())
946
+ .removeClass(prefix + 'textarea')
947
+ .show()
948
+ );
949
+ } else {
950
+ t.$box.after(
951
+ t.$ed
952
+ .css({height: ''})
953
+ .removeClass(prefix + 'editor')
954
+ .removeAttr('contenteditable')
955
+ .removeAttr('dir')
956
+ .html(t.html())
957
+ .show()
958
+ );
959
+ }
960
+
961
+ t.$ed.off('dblclick', 'img');
962
+
963
+ t.destroyPlugins();
964
+
965
+ t.$box.remove();
966
+ t.$c.removeData('trumbowyg');
967
+ $('body').removeClass(prefix + 'body-fullscreen');
968
+ t.$c.trigger('tbwclose');
969
+ $(window).off('scroll.' + t.eventNamespace + ' resize.' + t.eventNamespace);
970
+ },
971
+
972
+ // Empty the editor
973
+ empty: function () {
974
+ this.$ta.val('');
975
+ this.syncCode(true);
976
+ },
977
+
978
+ // Function call when click on viewHTML button
979
+ toggle: function () {
980
+ var t = this,
981
+ prefix = t.o.prefix;
982
+
983
+ if (t.o.autogrowOnEnter) {
984
+ t.autogrowOnEnterDontClose = !t.$box.hasClass(prefix + 'editor-hidden');
985
+ }
986
+
987
+ t.semanticCode(false, true);
988
+
989
+ setTimeout(function () {
990
+ t.doc.activeElement.blur();
991
+ t.$box.toggleClass(prefix + 'editor-hidden ' + prefix + 'editor-visible');
992
+ t.$btnPane.toggleClass(prefix + 'disable');
993
+ $('.' + prefix + 'viewHTML-button', t.$btnPane).toggleClass(prefix + 'active');
994
+ if (t.$box.hasClass(prefix + 'editor-visible')) {
995
+ t.$ta.attr('tabindex', -1);
996
+ } else {
997
+ t.$ta.removeAttr('tabindex');
998
+ }
999
+
1000
+ if (t.o.autogrowOnEnter && !t.autogrowOnEnterDontClose) {
1001
+ t.autogrowEditorOnEnter();
1002
+ }
1003
+ }, 0);
1004
+ },
1005
+
1006
+ // Open dropdown when click on a button which open that
1007
+ dropdown: function (name) {
1008
+ var t = this,
1009
+ d = t.doc,
1010
+ prefix = t.o.prefix,
1011
+ $dropdown = $('[data-' + prefix + 'dropdown=' + name + ']', t.$box),
1012
+ $btn = $('.' + prefix + name + '-button', t.$btnPane),
1013
+ show = $dropdown.is(':hidden');
1014
+
1015
+ $('body', d).trigger('mousedown');
1016
+
1017
+ if (show) {
1018
+ var o = $btn.offset().left;
1019
+ $btn.addClass(prefix + 'active');
1020
+
1021
+ $dropdown.css({
1022
+ position: 'absolute',
1023
+ top: $btn.offset().top - t.$btnPane.offset().top + $btn.outerHeight(),
1024
+ left: (t.o.fixedFullWidth && t.isFixed) ? o + 'px' : (o - t.$btnPane.offset().left) + 'px'
1025
+ }).show();
1026
+
1027
+ $(window).trigger('scroll');
1028
+
1029
+ $('body', d).on('mousedown.' + t.eventNamespace, function (e) {
1030
+ if (!$dropdown.is(e.target)) {
1031
+ $('.' + prefix + 'dropdown', t.$box).hide();
1032
+ $('.' + prefix + 'active', t.$btnPane).removeClass(prefix + 'active');
1033
+ $('body', d).off('mousedown.' + t.eventNamespace);
1034
+ }
1035
+ });
1036
+ }
1037
+ },
1038
+
1039
+ // HTML Code management
1040
+ html: function (html) {
1041
+ var t = this;
1042
+
1043
+ if (html != null) {
1044
+ t.$ta.val(html);
1045
+ t.syncCode(true);
1046
+ t.$c.trigger('tbwchange');
1047
+ return t;
1048
+ }
1049
+
1050
+ return t.$ta.val();
1051
+ },
1052
+
1053
+ syncTextarea: function () {
1054
+ var t = this;
1055
+ t.$ta.val(t.$ed.text().trim().length > 0 || t.$ed.find('hr,img,embed,iframe,input').length > 0 ? t.$ed.html() : '');
1056
+ },
1057
+
1058
+ syncCode: function (force) {
1059
+ var t = this;
1060
+ if (!force && t.$ed.is(':visible')) {
1061
+ t.syncTextarea();
1062
+ } else {
1063
+ // wrap the content in a div it's easier to get the innerhtml
1064
+ var html = $('<div>').html(t.$ta.val());
1065
+ //scrub the html before loading into the doc
1066
+ var safe = $('<div>').append(html);
1067
+ $(t.o.tagsToRemove.join(','), safe).remove();
1068
+ t.$ed.html(safe.contents().html());
1069
+ }
1070
+
1071
+ if (t.o.autogrow) {
1072
+ t.height = t.$ed.height();
1073
+ if (t.height !== t.$ta.css('height')) {
1074
+ t.$ta.css({height: t.height});
1075
+ t.$c.trigger('tbwresize');
1076
+ }
1077
+ }
1078
+ if (t.o.autogrowOnEnter) {
1079
+ // t.autogrowEditorOnEnter();
1080
+ t.$ed.height('auto');
1081
+ var totalheight = t.autogrowOnEnterWasFocused ? t.$ed[0].scrollHeight : t.$ed.css('min-height');
1082
+ if (totalheight !== t.$ta.css('height')) {
1083
+ t.$ed.css({height: totalheight});
1084
+ t.$c.trigger('tbwresize');
1085
+ }
1086
+ }
1087
+ },
1088
+
1089
+ // Analyse and update to semantic code
1090
+ // @param force : force to sync code from textarea
1091
+ // @param full : wrap text nodes in <p>
1092
+ // @param keepRange : leave selection range as it is
1093
+ semanticCode: function (force, full, keepRange) {
1094
+ var t = this;
1095
+ t.saveRange();
1096
+ t.syncCode(force);
1097
+
1098
+ if (t.o.semantic) {
1099
+ t.semanticTag('b');
1100
+ t.semanticTag('i');
1101
+ t.semanticTag('s');
1102
+ t.semanticTag('strike');
1103
+
1104
+ if (full) {
1105
+ var inlineElementsSelector = t.o.inlineElementsSelector,
1106
+ blockElementsSelector = ':not(' + inlineElementsSelector + ')';
1107
+
1108
+ // Wrap text nodes in span for easier processing
1109
+ t.$ed.contents().filter(function () {
1110
+ return this.nodeType === 3 && this.nodeValue.trim().length > 0;
1111
+ }).wrap('<span data-tbw/>');
1112
+
1113
+ // Wrap groups of inline elements in paragraphs (recursive)
1114
+ var wrapInlinesInParagraphsFrom = function ($from) {
1115
+ if ($from.length !== 0) {
1116
+ var $finalParagraph = $from.nextUntil(blockElementsSelector).addBack().wrapAll('<p/>').parent(),
1117
+ $nextElement = $finalParagraph.nextAll(inlineElementsSelector).first();
1118
+ $finalParagraph.next('br').remove();
1119
+ wrapInlinesInParagraphsFrom($nextElement);
1120
+ }
1121
+ };
1122
+ wrapInlinesInParagraphsFrom(t.$ed.children(inlineElementsSelector).first());
1123
+
1124
+ t.semanticTag('div', true);
1125
+
1126
+ // Unwrap paragraphs content, containing nothing usefull
1127
+ t.$ed.find('p').filter(function () {
1128
+ // Don't remove currently being edited element
1129
+ if (t.range && this === t.range.startContainer) {
1130
+ return false;
1131
+ }
1132
+ return $(this).text().trim().length === 0 && $(this).children().not('br,span').length === 0;
1133
+ }).contents().unwrap();
1134
+
1135
+ // Get rid of temporial span's
1136
+ $('[data-tbw]', t.$ed).contents().unwrap();
1137
+
1138
+ // Remove empty <p>
1139
+ t.$ed.find('p:empty').remove();
1140
+ }
1141
+
1142
+ if (!keepRange) {
1143
+ t.restoreRange();
1144
+ }
1145
+
1146
+ t.syncTextarea();
1147
+ }
1148
+ },
1149
+
1150
+ semanticTag: function (oldTag, copyAttributes) {
1151
+ var newTag;
1152
+
1153
+ if (this.o.semantic != null && typeof this.o.semantic === 'object' && this.o.semantic.hasOwnProperty(oldTag)) {
1154
+ newTag = this.o.semantic[oldTag];
1155
+ } else if (this.o.semantic === true && this.DEFAULT_SEMANTIC_MAP.hasOwnProperty(oldTag)) {
1156
+ newTag = this.DEFAULT_SEMANTIC_MAP[oldTag];
1157
+ } else {
1158
+ return;
1159
+ }
1160
+
1161
+ $(oldTag, this.$ed).each(function () {
1162
+ var $oldTag = $(this);
1163
+ $oldTag.wrap('<' + newTag + '/>');
1164
+ if (copyAttributes) {
1165
+ $.each($oldTag.prop('attributes'), function () {
1166
+ $oldTag.parent().attr(this.name, this.value);
1167
+ });
1168
+ }
1169
+ $oldTag.contents().unwrap();
1170
+ });
1171
+ },
1172
+
1173
+ // Function call when user click on "Insert Link"
1174
+ createLink: function () {
1175
+ var t = this,
1176
+ documentSelection = t.doc.getSelection(),
1177
+ node = documentSelection.focusNode,
1178
+ text = new XMLSerializer().serializeToString(documentSelection.getRangeAt(0).cloneContents()),
1179
+ url,
1180
+ title,
1181
+ target;
1182
+
1183
+ while (['A', 'DIV'].indexOf(node.nodeName) < 0) {
1184
+ node = node.parentNode;
1185
+ }
1186
+
1187
+ if (node && node.nodeName === 'A') {
1188
+ var $a = $(node);
1189
+ text = $a.text();
1190
+ url = $a.attr('href');
1191
+ title = $a.attr('title');
1192
+ target = $a.attr('target');
1193
+ var range = t.doc.createRange();
1194
+ range.selectNode(node);
1195
+ documentSelection.removeAllRanges();
1196
+ documentSelection.addRange(range);
1197
+ }
1198
+
1199
+ t.saveRange();
1200
+
1201
+ t.openModalInsert(t.lang.createLink, {
1202
+ url: {
1203
+ label: 'URL',
1204
+ required: true,
1205
+ value: url
1206
+ },
1207
+ title: {
1208
+ label: t.lang.title,
1209
+ value: title
1210
+ },
1211
+ text: {
1212
+ label: t.lang.text,
1213
+ value: text
1214
+ },
1215
+ target: {
1216
+ label: t.lang.target,
1217
+ value: target
1218
+ }
1219
+ }, function (v) { // v is value
1220
+ var link = $(['<a href="', v.url, '">', v.text || v.url, '</a>'].join(''));
1221
+ if (v.title.length > 0) {
1222
+ link.attr('title', v.title);
1223
+ }
1224
+ if (v.target.length > 0) {
1225
+ link.attr('target', v.target);
1226
+ }
1227
+ t.range.deleteContents();
1228
+ t.range.insertNode(link[0]);
1229
+ t.syncCode();
1230
+ t.$c.trigger('tbwchange');
1231
+ return true;
1232
+ });
1233
+ },
1234
+
1235
+ unlink: function () {
1236
+ var t = this,
1237
+ documentSelection = t.doc.getSelection(),
1238
+ node = documentSelection.focusNode;
1239
+
1240
+ if (documentSelection.isCollapsed) {
1241
+ while (['A', 'DIV'].indexOf(node.nodeName) < 0) {
1242
+ node = node.parentNode;
1243
+ }
1244
+
1245
+ if (node && node.nodeName === 'A') {
1246
+ var range = t.doc.createRange();
1247
+ range.selectNode(node);
1248
+ documentSelection.removeAllRanges();
1249
+ documentSelection.addRange(range);
1250
+ }
1251
+ }
1252
+ t.execCmd('unlink', undefined, undefined, true);
1253
+ },
1254
+
1255
+ insertImage: function () {
1256
+ var t = this;
1257
+ t.saveRange();
1258
+
1259
+ var options = {
1260
+ url: {
1261
+ label: 'URL',
1262
+ required: true
1263
+ },
1264
+ alt: {
1265
+ label: t.lang.description,
1266
+ value: t.getRangeText()
1267
+ }
1268
+ };
1269
+
1270
+ if (t.o.imageWidthModalEdit) {
1271
+ options.width = {};
1272
+ }
1273
+
1274
+ t.openModalInsert(t.lang.insertImage, options, function (v) { // v are values
1275
+ t.execCmd('insertImage', v.url);
1276
+ var $img = $('img[src="' + v.url + '"]:not([alt])', t.$box);
1277
+ $img.attr('alt', v.alt);
1278
+
1279
+ if (t.o.imageWidthModalEdit) {
1280
+ $img.attr({
1281
+ width: v.width
1282
+ });
1283
+ }
1284
+
1285
+ t.syncCode();
1286
+ t.$c.trigger('tbwchange');
1287
+
1288
+ return true;
1289
+ });
1290
+ },
1291
+
1292
+ fullscreen: function () {
1293
+ var t = this,
1294
+ prefix = t.o.prefix,
1295
+ fullscreenCssClass = prefix + 'fullscreen',
1296
+ isFullscreen;
1297
+
1298
+ t.$box.toggleClass(fullscreenCssClass);
1299
+ isFullscreen = t.$box.hasClass(fullscreenCssClass);
1300
+ $('body').toggleClass(prefix + 'body-fullscreen', isFullscreen);
1301
+ $(window).trigger('scroll');
1302
+ t.$c.trigger('tbw' + (isFullscreen ? 'open' : 'close') + 'fullscreen');
1303
+ },
1304
+
1305
+ /*
1306
+ * Call method of trumbowyg if exist
1307
+ * else try to call anonymous function
1308
+ * and finaly native execCommand
1309
+ */
1310
+ execCmd: function (cmd, param, forceCss, skipTrumbowyg) {
1311
+ var t = this;
1312
+ skipTrumbowyg = !!skipTrumbowyg || '';
1313
+
1314
+ if (cmd !== 'dropdown') {
1315
+ t.$ed.focus();
1316
+ }
1317
+
1318
+ try {
1319
+ t.doc.execCommand('styleWithCSS', false, forceCss || false);
1320
+ } catch (c) {
1321
+ }
1322
+
1323
+ try {
1324
+ t[cmd + skipTrumbowyg](param);
1325
+ } catch (c) {
1326
+ try {
1327
+ cmd(param);
1328
+ } catch (e2) {
1329
+ if (cmd === 'insertHorizontalRule') {
1330
+ param = undefined;
1331
+ } else if (cmd === 'formatBlock' && t.isIE) {
1332
+ param = '<' + param + '>';
1333
+ }
1334
+
1335
+ t.doc.execCommand(cmd, false, param);
1336
+
1337
+ t.syncCode();
1338
+ t.semanticCode(false, true);
1339
+ }
1340
+
1341
+ if (cmd !== 'dropdown') {
1342
+ t.updateButtonPaneStatus();
1343
+ t.$c.trigger('tbwchange');
1344
+ }
1345
+ }
1346
+ },
1347
+
1348
+ // Open a modal box
1349
+ openModal: function (title, content) {
1350
+ var t = this,
1351
+ prefix = t.o.prefix;
1352
+
1353
+ // No open a modal box when exist other modal box
1354
+ if ($('.' + prefix + 'modal-box', t.$box).length > 0) {
1355
+ return false;
1356
+ }
1357
+ if (t.o.autogrowOnEnter) {
1358
+ t.autogrowOnEnterDontClose = true;
1359
+ }
1360
+
1361
+ t.saveRange();
1362
+ t.showOverlay();
1363
+
1364
+ // Disable all btnPane btns
1365
+ t.$btnPane.addClass(prefix + 'disable');
1366
+
1367
+ // Build out of ModalBox, it's the mask for animations
1368
+ var $modal = $('<div/>', {
1369
+ class: prefix + 'modal ' + prefix + 'fixed-top'
1370
+ }).css({
1371
+ top: t.$btnPane.height()
1372
+ }).appendTo(t.$box);
1373
+
1374
+ // Click on overlay close modal by cancelling them
1375
+ t.$overlay.one('click', function () {
1376
+ $modal.trigger(CANCEL_EVENT);
1377
+ return false;
1378
+ });
1379
+
1380
+ // Build the form
1381
+ var $form = $('<form/>', {
1382
+ action: '',
1383
+ html: content
1384
+ })
1385
+ .on('submit', function () {
1386
+ $modal.trigger(CONFIRM_EVENT);
1387
+ return false;
1388
+ })
1389
+ .on('reset', function () {
1390
+ $modal.trigger(CANCEL_EVENT);
1391
+ return false;
1392
+ })
1393
+ .on('submit reset', function () {
1394
+ if (t.o.autogrowOnEnter) {
1395
+ t.autogrowOnEnterDontClose = false;
1396
+ }
1397
+ });
1398
+
1399
+ // Build ModalBox and animate to show them
1400
+ var $box = $('<div/>', {
1401
+ class: prefix + 'modal-box',
1402
+ html: $form
1403
+ })
1404
+ .css({
1405
+ top: '-' + t.$btnPane.outerHeight() + 'px',
1406
+ opacity: 0
1407
+ })
1408
+ .appendTo($modal)
1409
+ .animate({
1410
+ top: 0,
1411
+ opacity: 1
1412
+ }, 100);
1413
+
1414
+ // Append title
1415
+ $('<span/>', {
1416
+ text: title,
1417
+ class: prefix + 'modal-title'
1418
+ }).prependTo($box);
1419
+
1420
+ $modal.height($box.outerHeight() + 10);
1421
+
1422
+ // Focus in modal box
1423
+ $('input:first', $box).focus();
1424
+
1425
+ // Append Confirm and Cancel buttons
1426
+ t.buildModalBtn('submit', $box);
1427
+ t.buildModalBtn('reset', $box);
1428
+
1429
+ $(window).trigger('scroll');
1430
+
1431
+ return $modal;
1432
+ },
1433
+
1434
+ // @param n is name of modal
1435
+ buildModalBtn: function (n, $modal) {
1436
+ var t = this,
1437
+ prefix = t.o.prefix;
1438
+
1439
+ return $('<button/>', {
1440
+ class: prefix + 'modal-button ' + prefix + 'modal-' + n,
1441
+ type: n,
1442
+ text: t.lang[n] || n
1443
+ }).appendTo($('form', $modal));
1444
+ },
1445
+
1446
+ // close current modal box
1447
+ closeModal: function () {
1448
+ var t = this,
1449
+ prefix = t.o.prefix;
1450
+
1451
+ t.$btnPane.removeClass(prefix + 'disable');
1452
+ t.$overlay.off();
1453
+
1454
+ // Find the modal box
1455
+ var $modalBox = $('.' + prefix + 'modal-box', t.$box);
1456
+
1457
+ $modalBox.animate({
1458
+ top: '-' + $modalBox.height()
1459
+ }, 100, function () {
1460
+ $modalBox.parent().remove();
1461
+ t.hideOverlay();
1462
+ });
1463
+
1464
+ t.restoreRange();
1465
+ },
1466
+
1467
+ // Preformated build and management modal
1468
+ openModalInsert: function (title, fields, cmd) {
1469
+ var t = this,
1470
+ prefix = t.o.prefix,
1471
+ lg = t.lang,
1472
+ html = '';
1473
+
1474
+ $.each(fields, function (fieldName, field) {
1475
+ var l = field.label || fieldName,
1476
+ n = field.name || fieldName,
1477
+ a = field.attributes || {};
1478
+
1479
+ var attr = Object.keys(a).map(function (prop) {
1480
+ return prop + '="' + a[prop] + '"';
1481
+ }).join(' ');
1482
+
1483
+ html += '<label><input type="' + (field.type || 'text') + '" name="' + n + '"' +
1484
+ (field.type === 'checkbox' && field.value ? ' checked="checked"' : ' value="' + (field.value || '').replace(/"/g, '&quot;')) +
1485
+ '"' + attr + '><span class="' + prefix + 'input-infos"><span>' +
1486
+ (lg[l] ? lg[l] : l) +
1487
+ '</span></span></label>';
1488
+ });
1489
+
1490
+ return t.openModal(title, html)
1491
+ .on(CONFIRM_EVENT, function () {
1492
+ var $form = $('form', $(this)),
1493
+ valid = true,
1494
+ values = {};
1495
+
1496
+ $.each(fields, function (fieldName, field) {
1497
+ var n = field.name || fieldName;
1498
+
1499
+ var $field = $('input[name="' + n + '"]', $form),
1500
+ inputType = $field.attr('type');
1501
+
1502
+ switch (inputType.toLowerCase()) {
1503
+ case 'checkbox':
1504
+ values[n] = $field.is(':checked');
1505
+ break;
1506
+ case 'radio':
1507
+ values[n] = $field.filter(':checked').val();
1508
+ break;
1509
+ default:
1510
+ values[n] = $.trim($field.val());
1511
+ break;
1512
+ }
1513
+ // Validate value
1514
+ if (field.required && values[n] === '') {
1515
+ valid = false;
1516
+ t.addErrorOnModalField($field, t.lang.required);
1517
+ } else if (field.pattern && !field.pattern.test(values[n])) {
1518
+ valid = false;
1519
+ t.addErrorOnModalField($field, field.patternError);
1520
+ }
1521
+ });
1522
+
1523
+ if (valid) {
1524
+ t.restoreRange();
1525
+
1526
+ if (cmd(values, fields)) {
1527
+ t.syncCode();
1528
+ t.$c.trigger('tbwchange');
1529
+ t.closeModal();
1530
+ $(this).off(CONFIRM_EVENT);
1531
+ }
1532
+ }
1533
+ })
1534
+ .one(CANCEL_EVENT, function () {
1535
+ $(this).off(CONFIRM_EVENT);
1536
+ t.closeModal();
1537
+ });
1538
+ },
1539
+
1540
+ addErrorOnModalField: function ($field, err) {
1541
+ var prefix = this.o.prefix,
1542
+ $label = $field.parent();
1543
+
1544
+ $field
1545
+ .on('change keyup', function () {
1546
+ $label.removeClass(prefix + 'input-error');
1547
+ });
1548
+
1549
+ $label
1550
+ .addClass(prefix + 'input-error')
1551
+ .find('input+span')
1552
+ .append(
1553
+ $('<span/>', {
1554
+ class: prefix + 'msg-error',
1555
+ text: err
1556
+ })
1557
+ );
1558
+ },
1559
+
1560
+ getDefaultImgDblClickHandler: function () {
1561
+ var t = this;
1562
+
1563
+ return function () {
1564
+ var $img = $(this),
1565
+ src = $img.attr('src'),
1566
+ base64 = '(Base64)';
1567
+
1568
+ if (src.indexOf('data:image') === 0) {
1569
+ src = base64;
1570
+ }
1571
+
1572
+ var options = {
1573
+ url: {
1574
+ label: 'URL',
1575
+ value: src,
1576
+ required: true
1577
+ },
1578
+ alt: {
1579
+ label: t.lang.description,
1580
+ value: $img.attr('alt')
1581
+ }
1582
+ };
1583
+
1584
+ if (t.o.imageWidthModalEdit) {
1585
+ options.width = {
1586
+ value: $img.attr('width') ? $img.attr('width') : ''
1587
+ };
1588
+ }
1589
+
1590
+ t.openModalInsert(t.lang.insertImage, options, function (v) {
1591
+ if (v.src !== base64) {
1592
+ $img.attr({
1593
+ src: v.url
1594
+ });
1595
+ }
1596
+ $img.attr({
1597
+ alt: v.alt
1598
+ });
1599
+
1600
+ if (t.o.imageWidthModalEdit) {
1601
+ if (parseInt(v.width) > 0) {
1602
+ $img.attr({
1603
+ width: v.width
1604
+ });
1605
+ } else {
1606
+ $img.removeAttr('width');
1607
+ }
1608
+ }
1609
+
1610
+ return true;
1611
+ });
1612
+ return false;
1613
+ };
1614
+ },
1615
+
1616
+ // Range management
1617
+ saveRange: function () {
1618
+ var t = this,
1619
+ documentSelection = t.doc.getSelection();
1620
+
1621
+ t.range = null;
1622
+
1623
+ if (documentSelection.rangeCount) {
1624
+ var savedRange = t.range = documentSelection.getRangeAt(0),
1625
+ range = t.doc.createRange(),
1626
+ rangeStart;
1627
+ range.selectNodeContents(t.$ed[0]);
1628
+ range.setEnd(savedRange.startContainer, savedRange.startOffset);
1629
+ rangeStart = (range + '').length;
1630
+ t.metaRange = {
1631
+ start: rangeStart,
1632
+ end: rangeStart + (savedRange + '').length
1633
+ };
1634
+ }
1635
+ },
1636
+
1637
+ restoreRange: function () {
1638
+ var t = this,
1639
+ metaRange = t.metaRange,
1640
+ savedRange = t.range,
1641
+ documentSelection = t.doc.getSelection(),
1642
+ range;
1643
+
1644
+ if (!savedRange) {
1645
+ return;
1646
+ }
1647
+
1648
+ if (metaRange && metaRange.start !== metaRange.end) { // Algorithm from http://jsfiddle.net/WeWy7/3/
1649
+ var charIndex = 0,
1650
+ nodeStack = [t.$ed[0]],
1651
+ node,
1652
+ foundStart = false,
1653
+ stop = false;
1654
+
1655
+ range = t.doc.createRange();
1656
+
1657
+ while (!stop && (node = nodeStack.pop())) {
1658
+ if (node.nodeType === 3) {
1659
+ var nextCharIndex = charIndex + node.length;
1660
+ if (!foundStart && metaRange.start >= charIndex && metaRange.start <= nextCharIndex) {
1661
+ range.setStart(node, metaRange.start - charIndex);
1662
+ foundStart = true;
1663
+ }
1664
+ if (foundStart && metaRange.end >= charIndex && metaRange.end <= nextCharIndex) {
1665
+ range.setEnd(node, metaRange.end - charIndex);
1666
+ stop = true;
1667
+ }
1668
+ charIndex = nextCharIndex;
1669
+ } else {
1670
+ var cn = node.childNodes,
1671
+ i = cn.length;
1672
+
1673
+ while (i > 0) {
1674
+ i -= 1;
1675
+ nodeStack.push(cn[i]);
1676
+ }
1677
+ }
1678
+ }
1679
+ }
1680
+
1681
+ documentSelection.removeAllRanges();
1682
+ documentSelection.addRange(range || savedRange);
1683
+ },
1684
+
1685
+ getRangeText: function () {
1686
+ return this.range + '';
1687
+ },
1688
+
1689
+ updateButtonPaneStatus: function () {
1690
+ var t = this,
1691
+ prefix = t.o.prefix,
1692
+ tags = t.getTagsRecursive(t.doc.getSelection().focusNode),
1693
+ activeClasses = prefix + 'active-button ' + prefix + 'active';
1694
+
1695
+ $('.' + prefix + 'active-button', t.$btnPane).removeClass(activeClasses);
1696
+ $.each(tags, function (i, tag) {
1697
+ var btnName = t.tagToButton[tag.toLowerCase()],
1698
+ $btn = $('.' + prefix + btnName + '-button', t.$btnPane);
1699
+
1700
+ if ($btn.length > 0) {
1701
+ $btn.addClass(activeClasses);
1702
+ } else {
1703
+ try {
1704
+ $btn = $('.' + prefix + 'dropdown .' + prefix + btnName + '-dropdown-button', t.$box);
1705
+ var dropdownBtnName = $btn.parent().data('dropdown');
1706
+ $('.' + prefix + dropdownBtnName + '-button', t.$box).addClass(activeClasses);
1707
+ } catch (e) {
1708
+ }
1709
+ }
1710
+ });
1711
+ },
1712
+
1713
+ getTagsRecursive: function (element, tags) {
1714
+ var t = this;
1715
+ tags = tags || (element && element.tagName ? [element.tagName] : []);
1716
+
1717
+ if (element && element.parentNode) {
1718
+ element = element.parentNode;
1719
+ } else {
1720
+ return tags;
1721
+ }
1722
+
1723
+ var tag = element.tagName;
1724
+ if (tag === 'DIV') {
1725
+ return tags;
1726
+ }
1727
+ if (tag === 'P' && element.style.textAlign !== '') {
1728
+ tags.push(element.style.textAlign);
1729
+ }
1730
+
1731
+ $.each(t.tagHandlers, function (i, tagHandler) {
1732
+ tags = tags.concat(tagHandler(element, t));
1733
+ });
1734
+
1735
+ tags.push(tag);
1736
+
1737
+ return t.getTagsRecursive(element, tags).filter(function (tag) {
1738
+ return tag != null;
1739
+ });
1740
+ },
1741
+
1742
+ // Plugins
1743
+ initPlugins: function () {
1744
+ var t = this;
1745
+ t.loadedPlugins = [];
1746
+ $.each($.trumbowyg.plugins, function (name, plugin) {
1747
+ if (!plugin.shouldInit || plugin.shouldInit(t)) {
1748
+ plugin.init(t);
1749
+ if (plugin.tagHandler) {
1750
+ t.tagHandlers.push(plugin.tagHandler);
1751
+ }
1752
+ t.loadedPlugins.push(plugin);
1753
+ }
1754
+ });
1755
+ },
1756
+
1757
+ destroyPlugins: function () {
1758
+ $.each(this.loadedPlugins, function (i, plugin) {
1759
+ if (plugin.destroy) {
1760
+ plugin.destroy();
1761
+ }
1762
+ });
1763
+ }
1764
+ };
1765
+ })(navigator, window, document, jQuery);