blocky 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 (397) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/README.md +59 -0
  4. data/Rakefile +34 -0
  5. data/app/assets/javascripts/blocky/application.js +21 -0
  6. data/app/assets/javascripts/blocky/flash_messages.js +20 -0
  7. data/app/assets/stylesheets/blocky/_imports.scss +3 -0
  8. data/app/assets/stylesheets/blocky/_variables.scss +37 -0
  9. data/app/assets/stylesheets/blocky/application.css.scss +20 -0
  10. data/app/assets/stylesheets/blocky/application/content_blocks/edit.scss +13 -0
  11. data/app/assets/stylesheets/blocky/application/content_blocks/index.scss +81 -0
  12. data/app/assets/stylesheets/blocky/application/layout.scss +30 -0
  13. data/app/assets/stylesheets/blocky/shared/buttons.scss +119 -0
  14. data/app/assets/stylesheets/blocky/shared/flash_messages.scss +28 -0
  15. data/app/assets/stylesheets/blocky/shared/typography/body.scss +91 -0
  16. data/app/assets/stylesheets/blocky/shared/typography/forms.scss +81 -0
  17. data/app/assets/stylesheets/blocky/shared/typography/headings.scss +38 -0
  18. data/app/assets/stylesheets/blocky/shared/typography/lists.scss +22 -0
  19. data/app/controllers/blocky/application_controller.rb +7 -0
  20. data/app/controllers/blocky/content_blocks_controller.rb +44 -0
  21. data/app/controllers/concerns/blocky/auth.rb +53 -0
  22. data/app/helpers/blocky/application_helper.rb +26 -0
  23. data/app/helpers/blocky_helper.rb +18 -0
  24. data/app/models/blocky/ability.rb +11 -0
  25. data/app/models/blocky/content_block.rb +27 -0
  26. data/app/views/blocky/content_blocks/_content_block.html.erb +1 -0
  27. data/app/views/blocky/content_blocks/edit.html.erb +44 -0
  28. data/app/views/blocky/content_blocks/index.html.erb +84 -0
  29. data/app/views/layouts/blocky/_flash_messages.html.erb +13 -0
  30. data/app/views/layouts/blocky/application.html.erb +20 -0
  31. data/config/jshint.json +28 -0
  32. data/config/routes.rb +3 -0
  33. data/db/migrate/20140326210255_create_blocky_content_blocks.rb +12 -0
  34. data/lib/blocky.rb +8 -0
  35. data/lib/blocky/engine.rb +19 -0
  36. data/lib/blocky/version.rb +3 -0
  37. data/lib/generators/blocky/install_generator.rb +94 -0
  38. data/lib/tasks/blocky_tasks.rake +4 -0
  39. data/spec/dummy/README.rdoc +28 -0
  40. data/spec/dummy/Rakefile +6 -0
  41. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  42. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  43. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  44. data/spec/dummy/app/controllers/static_pages_controller.rb +7 -0
  45. data/spec/dummy/app/helpers/application_helper.rb +3 -0
  46. data/spec/dummy/app/models/blocky/ability.rb +9 -0
  47. data/spec/dummy/app/views/layouts/application.html.erb +25 -0
  48. data/spec/dummy/app/views/static_pages/contact.html.erb +5 -0
  49. data/spec/dummy/app/views/static_pages/home.html.erb +7 -0
  50. data/spec/dummy/bin/bundle +3 -0
  51. data/spec/dummy/bin/rails +4 -0
  52. data/spec/dummy/bin/rake +4 -0
  53. data/spec/dummy/config.ru +4 -0
  54. data/spec/dummy/config/application.rb +23 -0
  55. data/spec/dummy/config/boot.rb +5 -0
  56. data/spec/dummy/config/database.yml +25 -0
  57. data/spec/dummy/config/environment.rb +5 -0
  58. data/spec/dummy/config/environments/development.rb +29 -0
  59. data/spec/dummy/config/environments/production.rb +80 -0
  60. data/spec/dummy/config/environments/test.rb +36 -0
  61. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  62. data/spec/dummy/config/initializers/blocky.rb +4 -0
  63. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  64. data/spec/dummy/config/initializers/inflections.rb +16 -0
  65. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  66. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  67. data/spec/dummy/config/initializers/session_store.rb +3 -0
  68. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  69. data/spec/dummy/config/locales/en.yml +23 -0
  70. data/spec/dummy/config/routes.rb +5 -0
  71. data/spec/dummy/db/development.sqlite3 +0 -0
  72. data/spec/dummy/db/migrate/20140326211453_create_blocky_content_blocks.blocky.rb +13 -0
  73. data/spec/dummy/db/schema.rb +25 -0
  74. data/spec/dummy/db/test.sqlite3 +0 -0
  75. data/spec/dummy/log/development.log +14038 -0
  76. data/spec/dummy/log/test.log +151 -0
  77. data/spec/dummy/public/404.html +58 -0
  78. data/spec/dummy/public/422.html +58 -0
  79. data/spec/dummy/public/500.html +57 -0
  80. data/spec/dummy/public/favicon.ico +0 -0
  81. data/spec/dummy/tmp/cache/E76/DE0/blocky%2Fcontent_blocks%2F1-20140326211612204255000 +1 -0
  82. data/spec/dummy/tmp/cache/E79/630/blocky%2Fcontent_blocks%2F5-20140326220740803013000 +0 -0
  83. data/spec/dummy/tmp/cache/E7D/2C0/blocky%2Fcontent_blocks%2F2-20140326211803501817000 +1 -0
  84. data/spec/dummy/tmp/cache/E7F/480/blocky%2Fcontent_blocks%2F2-20140326211735204743000 +0 -0
  85. data/spec/dummy/tmp/cache/E83/720/blocky%2Fcontent_blocks%2F3-20140326212218805733000 +0 -0
  86. data/spec/dummy/tmp/cache/E85/4F0/blocky%2Fcontent_blocks%2F1-20140326211503578725000 +0 -0
  87. data/spec/dummy/tmp/cache/E8A/C50/blocky%2Fcontent_blocks%2F6-20140520152442627674000 +0 -0
  88. data/spec/dummy/tmp/cache/E8F/EE0/blocky%2Fcontent_blocks%2F4-20140326214706659274000 +0 -0
  89. data/spec/dummy/tmp/cache/E95/020/blocky%2Fcontent_blocks%2F4-20140520170832796695000 +2 -0
  90. data/spec/dummy/tmp/cache/assets/development/sass/1330522281c54fc4f0f88c06856f0cc8052c25e8/buttons.scssc +0 -0
  91. data/spec/dummy/tmp/cache/assets/development/sass/1330522281c54fc4f0f88c06856f0cc8052c25e8/flash_messages.scssc +0 -0
  92. data/spec/dummy/tmp/cache/assets/development/sass/16328cd177fa77f37d597cddc166b9d61ed7bdc6/_compact.scssc +0 -0
  93. data/spec/dummy/tmp/cache/assets/development/sass/16328cd177fa77f37d597cddc166b9d61ed7bdc6/_flex-grid.scssc +0 -0
  94. data/spec/dummy/tmp/cache/assets/development/sass/16328cd177fa77f37d597cddc166b9d61ed7bdc6/_grid-width.scssc +0 -0
  95. data/spec/dummy/tmp/cache/assets/development/sass/16328cd177fa77f37d597cddc166b9d61ed7bdc6/_linear-gradient.scssc +0 -0
  96. data/spec/dummy/tmp/cache/assets/development/sass/16328cd177fa77f37d597cddc166b9d61ed7bdc6/_modular-scale.scssc +0 -0
  97. data/spec/dummy/tmp/cache/assets/development/sass/16328cd177fa77f37d597cddc166b9d61ed7bdc6/_px-to-em.scssc +0 -0
  98. data/spec/dummy/tmp/cache/assets/development/sass/16328cd177fa77f37d597cddc166b9d61ed7bdc6/_radial-gradient.scssc +0 -0
  99. data/spec/dummy/tmp/cache/assets/development/sass/16328cd177fa77f37d597cddc166b9d61ed7bdc6/_tint-shade.scssc +0 -0
  100. data/spec/dummy/tmp/cache/assets/development/sass/16328cd177fa77f37d597cddc166b9d61ed7bdc6/_transition-property-name.scssc +0 -0
  101. data/spec/dummy/tmp/cache/assets/development/sass/2ed2f1a12f96a5966a3f2f4a226be3068f8e609b/_bordered-pulled.scssc +0 -0
  102. data/spec/dummy/tmp/cache/assets/development/sass/2ed2f1a12f96a5966a3f2f4a226be3068f8e609b/_core.scssc +0 -0
  103. data/spec/dummy/tmp/cache/assets/development/sass/2ed2f1a12f96a5966a3f2f4a226be3068f8e609b/_fixed-width.scssc +0 -0
  104. data/spec/dummy/tmp/cache/assets/development/sass/2ed2f1a12f96a5966a3f2f4a226be3068f8e609b/_icons.scssc +0 -0
  105. data/spec/dummy/tmp/cache/assets/development/sass/2ed2f1a12f96a5966a3f2f4a226be3068f8e609b/_larger.scssc +0 -0
  106. data/spec/dummy/tmp/cache/assets/development/sass/2ed2f1a12f96a5966a3f2f4a226be3068f8e609b/_list.scssc +0 -0
  107. data/spec/dummy/tmp/cache/assets/development/sass/2ed2f1a12f96a5966a3f2f4a226be3068f8e609b/_mixins.scssc +0 -0
  108. data/spec/dummy/tmp/cache/assets/development/sass/2ed2f1a12f96a5966a3f2f4a226be3068f8e609b/_path.scssc +0 -0
  109. data/spec/dummy/tmp/cache/assets/development/sass/2ed2f1a12f96a5966a3f2f4a226be3068f8e609b/_rotated-flipped.scssc +0 -0
  110. data/spec/dummy/tmp/cache/assets/development/sass/2ed2f1a12f96a5966a3f2f4a226be3068f8e609b/_spinning.scssc +0 -0
  111. data/spec/dummy/tmp/cache/assets/development/sass/2ed2f1a12f96a5966a3f2f4a226be3068f8e609b/_stacked.scssc +0 -0
  112. data/spec/dummy/tmp/cache/assets/development/sass/2ed2f1a12f96a5966a3f2f4a226be3068f8e609b/_variables.scssc +0 -0
  113. data/spec/dummy/tmp/cache/assets/development/sass/3fb34f29991e8a63348b29fade50f1de29147c8f/_fill-parent.scssc +0 -0
  114. data/spec/dummy/tmp/cache/assets/development/sass/3fb34f29991e8a63348b29fade50f1de29147c8f/_grid.scssc +0 -0
  115. data/spec/dummy/tmp/cache/assets/development/sass/3fb34f29991e8a63348b29fade50f1de29147c8f/_media.scssc +0 -0
  116. data/spec/dummy/tmp/cache/assets/development/sass/3fb34f29991e8a63348b29fade50f1de29147c8f/_omega.scssc +0 -0
  117. data/spec/dummy/tmp/cache/assets/development/sass/3fb34f29991e8a63348b29fade50f1de29147c8f/_outer-container.scssc +0 -0
  118. data/spec/dummy/tmp/cache/assets/development/sass/3fb34f29991e8a63348b29fade50f1de29147c8f/_pad.scssc +0 -0
  119. data/spec/dummy/tmp/cache/assets/development/sass/3fb34f29991e8a63348b29fade50f1de29147c8f/_private.scssc +0 -0
  120. data/spec/dummy/tmp/cache/assets/development/sass/3fb34f29991e8a63348b29fade50f1de29147c8f/_reset.scssc +0 -0
  121. data/spec/dummy/tmp/cache/assets/development/sass/3fb34f29991e8a63348b29fade50f1de29147c8f/_row.scssc +0 -0
  122. data/spec/dummy/tmp/cache/assets/development/sass/3fb34f29991e8a63348b29fade50f1de29147c8f/_shift.scssc +0 -0
  123. data/spec/dummy/tmp/cache/assets/development/sass/3fb34f29991e8a63348b29fade50f1de29147c8f/_span-columns.scssc +0 -0
  124. data/spec/dummy/tmp/cache/assets/development/sass/3fb34f29991e8a63348b29fade50f1de29147c8f/_to-deprecate.scssc +0 -0
  125. data/spec/dummy/tmp/cache/assets/development/sass/3fb34f29991e8a63348b29fade50f1de29147c8f/_visual-grid.scssc +0 -0
  126. data/spec/dummy/tmp/cache/assets/development/sass/45a8d9925ba36450f7e3862c914588ae8720003e/_imports.scssc +0 -0
  127. data/spec/dummy/tmp/cache/assets/development/sass/45a8d9925ba36450f7e3862c914588ae8720003e/_variables.scssc +0 -0
  128. data/spec/dummy/tmp/cache/assets/development/sass/45a8d9925ba36450f7e3862c914588ae8720003e/application.css.scssc +0 -0
  129. data/spec/dummy/tmp/cache/assets/development/sass/4808b00b5a9150b9a854e6877435916328f1da81/_deprecated-webkit-gradient.scssc +0 -0
  130. data/spec/dummy/tmp/cache/assets/development/sass/4808b00b5a9150b9a854e6877435916328f1da81/_gradient-positions-parser.scssc +0 -0
  131. data/spec/dummy/tmp/cache/assets/development/sass/4808b00b5a9150b9a854e6877435916328f1da81/_linear-positions-parser.scssc +0 -0
  132. data/spec/dummy/tmp/cache/assets/development/sass/4808b00b5a9150b9a854e6877435916328f1da81/_radial-arg-parser.scssc +0 -0
  133. data/spec/dummy/tmp/cache/assets/development/sass/4808b00b5a9150b9a854e6877435916328f1da81/_radial-positions-parser.scssc +0 -0
  134. data/spec/dummy/tmp/cache/assets/development/sass/4808b00b5a9150b9a854e6877435916328f1da81/_render-gradients.scssc +0 -0
  135. data/spec/dummy/tmp/cache/assets/development/sass/4808b00b5a9150b9a854e6877435916328f1da81/_shape-size-stripper.scssc +0 -0
  136. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_animation.scssc +0 -0
  137. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_appearance.scssc +0 -0
  138. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_backface-visibility.scssc +0 -0
  139. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_background-image.scssc +0 -0
  140. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_background.scssc +0 -0
  141. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_border-image.scssc +0 -0
  142. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_border-radius.scssc +0 -0
  143. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_box-sizing.scssc +0 -0
  144. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_columns.scssc +0 -0
  145. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_flex-box.scssc +0 -0
  146. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_font-face.scssc +0 -0
  147. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_hidpi-media-query.scssc +0 -0
  148. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_image-rendering.scssc +0 -0
  149. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_inline-block.scssc +0 -0
  150. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_keyframes.scssc +0 -0
  151. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_linear-gradient.scssc +0 -0
  152. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_perspective.scssc +0 -0
  153. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_placeholder.scssc +0 -0
  154. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_radial-gradient.scssc +0 -0
  155. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_transform.scssc +0 -0
  156. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_transition.scssc +0 -0
  157. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_user-select.scssc +0 -0
  158. data/spec/dummy/tmp/cache/assets/development/sass/7c52080b507114ff73ac654c0177e1dca603143b/body.scssc +0 -0
  159. data/spec/dummy/tmp/cache/assets/development/sass/7c52080b507114ff73ac654c0177e1dca603143b/forms.scssc +0 -0
  160. data/spec/dummy/tmp/cache/assets/development/sass/7c52080b507114ff73ac654c0177e1dca603143b/headings.scssc +0 -0
  161. data/spec/dummy/tmp/cache/assets/development/sass/7c52080b507114ff73ac654c0177e1dca603143b/lists.scssc +0 -0
  162. data/spec/dummy/tmp/cache/assets/development/sass/86088d5ecf0a5701fd22ac3b30255eb2bb94f550/_bourbon-deprecated-upcoming.scssc +0 -0
  163. data/spec/dummy/tmp/cache/assets/development/sass/9de1036a47599e1de75fdb2094e4960ffb18ffcf/_grid.scssc +0 -0
  164. data/spec/dummy/tmp/cache/assets/development/sass/9de1036a47599e1de75fdb2094e4960ffb18ffcf/_visual-grid.scssc +0 -0
  165. data/spec/dummy/tmp/cache/assets/development/sass/9e0f22094073a2727149869ee451bf5155b6b1de/_bourbon.scssc +0 -0
  166. data/spec/dummy/tmp/cache/assets/development/sass/9e0f22094073a2727149869ee451bf5155b6b1de/_neat.scssc +0 -0
  167. data/spec/dummy/tmp/cache/assets/development/sass/9e0f22094073a2727149869ee451bf5155b6b1de/font-awesome.scssc +0 -0
  168. data/spec/dummy/tmp/cache/assets/development/sass/ab9fa2e8e2c4d0592ca80a0885630a538ee36082/_neat-helpers.scssc +0 -0
  169. data/spec/dummy/tmp/cache/assets/development/sass/ccd8e9094d6c3af8a9493bb7fee8eae1bf9b4c52/_new-breakpoint.scssc +0 -0
  170. data/spec/dummy/tmp/cache/assets/development/sass/ccd8e9094d6c3af8a9493bb7fee8eae1bf9b4c52/_private.scssc +0 -0
  171. data/spec/dummy/tmp/cache/assets/development/sass/e32eee285e5a7bcd847a61263245c70a38794724/edit.scssc +0 -0
  172. data/spec/dummy/tmp/cache/assets/development/sass/e32eee285e5a7bcd847a61263245c70a38794724/index.scssc +0 -0
  173. data/spec/dummy/tmp/cache/assets/development/sass/e792c58dcbc932d7b0bbb62be67c59da9796d04b/_button.scssc +0 -0
  174. data/spec/dummy/tmp/cache/assets/development/sass/e792c58dcbc932d7b0bbb62be67c59da9796d04b/_clearfix.scssc +0 -0
  175. data/spec/dummy/tmp/cache/assets/development/sass/e792c58dcbc932d7b0bbb62be67c59da9796d04b/_font-family.scssc +0 -0
  176. data/spec/dummy/tmp/cache/assets/development/sass/e792c58dcbc932d7b0bbb62be67c59da9796d04b/_hide-text.scssc +0 -0
  177. data/spec/dummy/tmp/cache/assets/development/sass/e792c58dcbc932d7b0bbb62be67c59da9796d04b/_html5-input-types.scssc +0 -0
  178. data/spec/dummy/tmp/cache/assets/development/sass/e792c58dcbc932d7b0bbb62be67c59da9796d04b/_position.scssc +0 -0
  179. data/spec/dummy/tmp/cache/assets/development/sass/e792c58dcbc932d7b0bbb62be67c59da9796d04b/_prefixer.scssc +0 -0
  180. data/spec/dummy/tmp/cache/assets/development/sass/e792c58dcbc932d7b0bbb62be67c59da9796d04b/_retina-image.scssc +0 -0
  181. data/spec/dummy/tmp/cache/assets/development/sass/e792c58dcbc932d7b0bbb62be67c59da9796d04b/_size.scssc +0 -0
  182. data/spec/dummy/tmp/cache/assets/development/sass/e792c58dcbc932d7b0bbb62be67c59da9796d04b/_timing-functions.scssc +0 -0
  183. data/spec/dummy/tmp/cache/assets/development/sass/e792c58dcbc932d7b0bbb62be67c59da9796d04b/_triangle.scssc +0 -0
  184. data/spec/dummy/tmp/cache/assets/development/sass/f6aab6f5c59ff0b3c4ff7e11237d10cd45b5353c/layout.scssc +0 -0
  185. data/spec/dummy/tmp/cache/assets/development/sprockets/00b315c99e05fd17eb6ee63c05874610 +0 -0
  186. data/spec/dummy/tmp/cache/assets/development/sprockets/0427945c7b4a3a10bc1b628680c1ac11 +0 -0
  187. data/spec/dummy/tmp/cache/assets/development/sprockets/07a7381ab720b1fcdf48d09e6f4aca3f +0 -0
  188. data/spec/dummy/tmp/cache/assets/development/sprockets/0cba4b819c78286147e22cce1cff06de +0 -0
  189. data/spec/dummy/tmp/cache/assets/development/sprockets/0d045cc286244754010ae01a766487a9 +0 -0
  190. data/spec/dummy/tmp/cache/assets/development/sprockets/0f027312d7555aefd043bc94e678e35d +0 -0
  191. data/spec/dummy/tmp/cache/assets/development/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
  192. data/spec/dummy/tmp/cache/assets/development/sprockets/1590fdab706270c5d350582acc774711 +0 -0
  193. data/spec/dummy/tmp/cache/assets/development/sprockets/17d324480cb2a2a47bedc3ef4d022b6d +0 -0
  194. data/spec/dummy/tmp/cache/assets/development/sprockets/21eee9c5050e081a5a1cf91cea7d268f +0 -0
  195. data/spec/dummy/tmp/cache/assets/development/sprockets/2510376ff2d7b20d23bc38a46fcfa055 +0 -0
  196. data/spec/dummy/tmp/cache/assets/development/sprockets/26dfb7b51a72e95c414c783f2ba69222 +0 -0
  197. data/spec/dummy/tmp/cache/assets/development/sprockets/2a12abb42fb17b6520d9d9ad589a20af +0 -0
  198. data/spec/dummy/tmp/cache/assets/development/sprockets/2a87faf509efd55dfdbd16b0fa8239b4 +0 -0
  199. data/spec/dummy/tmp/cache/assets/development/sprockets/2c9e3b499f8c7da0a3d3de2510f78846 +0 -0
  200. data/spec/dummy/tmp/cache/assets/development/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
  201. data/spec/dummy/tmp/cache/assets/development/sprockets/2fd51b2bfa391389108d8de69733d84b +0 -0
  202. data/spec/dummy/tmp/cache/assets/development/sprockets/3075115024bd10f7adb951b3baec184c +0 -0
  203. data/spec/dummy/tmp/cache/assets/development/sprockets/309a524a7494382fd3346a82b810c20b +0 -0
  204. data/spec/dummy/tmp/cache/assets/development/sprockets/346918a665641c5e32c0bb368f032b07 +0 -0
  205. data/spec/dummy/tmp/cache/assets/development/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  206. data/spec/dummy/tmp/cache/assets/development/sprockets/359a931a346dd73a40ea88a3e145e4b3 +0 -0
  207. data/spec/dummy/tmp/cache/assets/development/sprockets/3a8c2654e77e18a35f25642e5e3bcb88 +0 -0
  208. data/spec/dummy/tmp/cache/assets/development/sprockets/3a8ecba67750b8ad7df3ba9362857756 +0 -0
  209. data/spec/dummy/tmp/cache/assets/development/sprockets/3aa302111c6bea14859db6fea34cdb94 +0 -0
  210. data/spec/dummy/tmp/cache/assets/development/sprockets/3b05198beb41d8fc2b4a207128140f89 +0 -0
  211. data/spec/dummy/tmp/cache/assets/development/sprockets/3cc020c5e2e8ab2e4acd1b5a9fdc16ab +0 -0
  212. data/spec/dummy/tmp/cache/assets/development/sprockets/3d89aed04c6cdc16f097a2aabfa2b867 +0 -0
  213. data/spec/dummy/tmp/cache/assets/development/sprockets/420c2d9425469c305387fa81d5ef1b30 +0 -0
  214. data/spec/dummy/tmp/cache/assets/development/sprockets/43272d99260411613e550de399f08be8 +0 -0
  215. data/spec/dummy/tmp/cache/assets/development/sprockets/4443d626de6926b2b0e97011a9d3283b +0 -0
  216. data/spec/dummy/tmp/cache/assets/development/sprockets/44ee5490907c6fdf6530eeea203a3908 +0 -0
  217. data/spec/dummy/tmp/cache/assets/development/sprockets/4c016d0efbbfe9fa01bc4d8d800f8548 +0 -0
  218. data/spec/dummy/tmp/cache/assets/development/sprockets/4f41b46926dc79b6d378c60e5e12b1ee +0 -0
  219. data/spec/dummy/tmp/cache/assets/development/sprockets/4ffbbdb75e91e3ef76f0356bd8c9fdb9 +0 -0
  220. data/spec/dummy/tmp/cache/assets/development/sprockets/530a0c5b0d469facb66e00ca952d9069 +0 -0
  221. data/spec/dummy/tmp/cache/assets/development/sprockets/562b764135bc28106a524123de319fac +0 -0
  222. data/spec/dummy/tmp/cache/assets/development/sprockets/59cc9befd7bad256dc0139dc7c32cf34 +0 -0
  223. data/spec/dummy/tmp/cache/assets/development/sprockets/5d455edb538f158d67b710d4c8fb9674 +0 -0
  224. data/spec/dummy/tmp/cache/assets/development/sprockets/5ea0f006a9c4f5744aa307242e43d11a +0 -0
  225. data/spec/dummy/tmp/cache/assets/development/sprockets/5f1b58c3d1b05b422b95fad6ff1236ad +0 -0
  226. data/spec/dummy/tmp/cache/assets/development/sprockets/5f9c0944ded3ac10412c77764c4f83a2 +0 -0
  227. data/spec/dummy/tmp/cache/assets/development/sprockets/613e23bb1e9f2c8dbd8166803b85c57c +0 -0
  228. data/spec/dummy/tmp/cache/assets/development/sprockets/61ac9eefcbf748e59c79b6e84835babd +0 -0
  229. data/spec/dummy/tmp/cache/assets/development/sprockets/64b57c4794688cd1dd9723c89f564ad7 +0 -0
  230. data/spec/dummy/tmp/cache/assets/development/sprockets/65e56fb427fa0e3e148515a9ea998426 +0 -0
  231. data/spec/dummy/tmp/cache/assets/development/sprockets/7344269d882a3e910b3806b007dd9d71 +0 -0
  232. data/spec/dummy/tmp/cache/assets/development/sprockets/74104d7c75a8966708478065693bcca8 +0 -0
  233. data/spec/dummy/tmp/cache/assets/development/sprockets/7415c9f0125deaebddd1c12b2c26367d +0 -0
  234. data/spec/dummy/tmp/cache/assets/development/sprockets/7703867f0dd6cf86b9eb01bce45d1f02 +0 -0
  235. data/spec/dummy/tmp/cache/assets/development/sprockets/7871e5235770cb3607023e3646149255 +0 -0
  236. data/spec/dummy/tmp/cache/assets/development/sprockets/7ce4b6005f5a1535c297ebe897723551 +0 -0
  237. data/spec/dummy/tmp/cache/assets/development/sprockets/8035b3603b1f603513ce0491ef5ee98c +0 -0
  238. data/spec/dummy/tmp/cache/assets/development/sprockets/87b0d0df42a2bb5d08a5417fd0274a6e +0 -0
  239. data/spec/dummy/tmp/cache/assets/development/sprockets/88b8972e085bc284af152cf7b158334c +0 -0
  240. data/spec/dummy/tmp/cache/assets/development/sprockets/8b94d81de8f6f238037cab9a4a1a0c30 +0 -0
  241. data/spec/dummy/tmp/cache/assets/development/sprockets/8bd0f5b2d316327657d6709cf130a550 +0 -0
  242. data/spec/dummy/tmp/cache/assets/development/sprockets/8efc9dce2839c7874c8c1bb8a662acc9 +0 -0
  243. data/spec/dummy/tmp/cache/assets/development/sprockets/8f4e3d9674b96cae9b98843cd6ff18c8 +0 -0
  244. data/spec/dummy/tmp/cache/assets/development/sprockets/92a576a2bd81929a26b143cc02e430aa +0 -0
  245. data/spec/dummy/tmp/cache/assets/development/sprockets/9e58e917e624e0cd3b2154f01fd24962 +0 -0
  246. data/spec/dummy/tmp/cache/assets/development/sprockets/a0c1f9e3d33f8de75d0a594321456c9b +0 -0
  247. data/spec/dummy/tmp/cache/assets/development/sprockets/a147199745f7229dc608d3cefce5df45 +0 -0
  248. data/spec/dummy/tmp/cache/assets/development/sprockets/a72bca95d5c7b54e61fa4e0ebc869d4e +0 -0
  249. data/spec/dummy/tmp/cache/assets/development/sprockets/abe150382a2bf6466264fe8866f34157 +0 -0
  250. data/spec/dummy/tmp/cache/assets/development/sprockets/afa453539adb4bc1f7d844dcb361460d +0 -0
  251. data/spec/dummy/tmp/cache/assets/development/sprockets/b0352a0c3bd6f28326a7fc34fa51fa16 +0 -0
  252. data/spec/dummy/tmp/cache/assets/development/sprockets/b0f77fd26cd7fd672f25ec90ed445fe9 +0 -0
  253. data/spec/dummy/tmp/cache/assets/development/sprockets/b0f88bcdb6579681288ffc314d36281b +0 -0
  254. data/spec/dummy/tmp/cache/assets/development/sprockets/b304f5f5248fd1b0910adaeaf4430da9 +0 -0
  255. data/spec/dummy/tmp/cache/assets/development/sprockets/b5dd7a2c9cdd592440c578b49edff2cb +0 -0
  256. data/spec/dummy/tmp/cache/assets/development/sprockets/b7f85043b18466180fb325cbc7e709c8 +0 -0
  257. data/spec/dummy/tmp/cache/assets/development/sprockets/b8096cbfaf0aa44715d5411926461117 +0 -0
  258. data/spec/dummy/tmp/cache/assets/development/sprockets/b95fb213c4dd9e7f1fdfe6120e88e2e8 +0 -0
  259. data/spec/dummy/tmp/cache/assets/development/sprockets/be95835376c06f09fdd2a3afc3dd195e +0 -0
  260. data/spec/dummy/tmp/cache/assets/development/sprockets/bfacd7859276780db88488d7d7e41b59 +0 -0
  261. data/spec/dummy/tmp/cache/assets/development/sprockets/c954e3a10a9a27d22bbde51534852ce1 +0 -0
  262. data/spec/dummy/tmp/cache/assets/development/sprockets/cee17b1df9168d8c9075ec7c74d54cb5 +0 -0
  263. data/spec/dummy/tmp/cache/assets/development/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
  264. data/spec/dummy/tmp/cache/assets/development/sprockets/d101f82ff4e2f7db4eb46cc760aa1d3f +0 -0
  265. data/spec/dummy/tmp/cache/assets/development/sprockets/d1689f5fa7ff2ae20077f9de7fd130e1 +0 -0
  266. data/spec/dummy/tmp/cache/assets/development/sprockets/d31431b0b9739b7fa0843ccfd02d002b +0 -0
  267. data/spec/dummy/tmp/cache/assets/development/sprockets/d410c298186b4110d38ff3d89b29c925 +0 -0
  268. data/spec/dummy/tmp/cache/assets/development/sprockets/d4b45f77d1eed50fcf90f8c5511b99b6 +0 -0
  269. data/spec/dummy/tmp/cache/assets/development/sprockets/d62a46b4d21da7c9091dcbd8fa01da34 +0 -0
  270. data/spec/dummy/tmp/cache/assets/development/sprockets/d6477bff4412a5abb06c8acf405c8a87 +0 -0
  271. data/spec/dummy/tmp/cache/assets/development/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  272. data/spec/dummy/tmp/cache/assets/development/sprockets/d985df74e53b12bfa3af9ea8608eacb2 +0 -0
  273. data/spec/dummy/tmp/cache/assets/development/sprockets/d9db27d114516b1b698ad3520e97d1f9 +0 -0
  274. data/spec/dummy/tmp/cache/assets/development/sprockets/da4c7a7a685a765695e1d781590e6a5a +0 -0
  275. data/spec/dummy/tmp/cache/assets/development/sprockets/dbe056c885afef869a46852b1faa9e60 +0 -0
  276. data/spec/dummy/tmp/cache/assets/development/sprockets/df0a14f77c1e0b39c4284d6d6fce9c97 +0 -0
  277. data/spec/dummy/tmp/cache/assets/development/sprockets/e01e232c8b2f8d1711bcd44d6e017dab +0 -0
  278. data/spec/dummy/tmp/cache/assets/development/sprockets/e333f0976af05d7bc7eca78d1383c2af +0 -0
  279. data/spec/dummy/tmp/cache/assets/development/sprockets/e95f94b756f854b191f179bd558ce3c6 +0 -0
  280. data/spec/dummy/tmp/cache/assets/development/sprockets/eb20aca7df145f553b39afab67d5c0a0 +0 -0
  281. data/spec/dummy/tmp/cache/assets/development/sprockets/edaff551a27cde6faf69cc45ba5de859 +0 -0
  282. data/spec/dummy/tmp/cache/assets/development/sprockets/ee44b9ce0a9a1e5e56418ab84da62277 +0 -0
  283. data/spec/dummy/tmp/cache/assets/development/sprockets/f345273bfd8088c36038c49a5fae3218 +0 -0
  284. data/spec/dummy/tmp/cache/assets/development/sprockets/f3a1691881f9395089d43324197ff863 +0 -0
  285. data/spec/dummy/tmp/cache/assets/development/sprockets/f4e80d0621ebd9be1b2b595ce543037f +0 -0
  286. data/spec/dummy/tmp/cache/assets/development/sprockets/f521da421cda5fe4162aeec62e594eca +0 -0
  287. data/spec/dummy/tmp/cache/assets/development/sprockets/f6fcfc1f4419ff69a85c86234ba475cf +0 -0
  288. data/spec/dummy/tmp/cache/assets/development/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
  289. data/spec/dummy/tmp/cache/assets/development/sprockets/f8c2ccce91ab7c89ad8922be93c6f363 +0 -0
  290. data/spec/dummy/tmp/cache/assets/development/sprockets/fa0df232aba62e841f6077c735cec1aa +0 -0
  291. data/spec/dummy/tmp/cache/assets/development/sprockets/fb892eb8050ec923cc8084138b1e6e28 +0 -0
  292. data/spec/factories/content_block_factory.rb +13 -0
  293. data/spec/models/blocky/content_block_spec.rb +37 -0
  294. data/spec/spec_helper.rb +34 -0
  295. data/vendor/assets/fonts/blocky/FontAwesome.otf +0 -0
  296. data/vendor/assets/fonts/blocky/fontawesome-webfont.eot +0 -0
  297. data/vendor/assets/fonts/blocky/fontawesome-webfont.svg +414 -0
  298. data/vendor/assets/fonts/blocky/fontawesome-webfont.ttf +0 -0
  299. data/vendor/assets/fonts/blocky/fontawesome-webfont.woff +0 -0
  300. data/vendor/assets/javascripts/blocky/bootstrap.js +1951 -0
  301. data/vendor/assets/javascripts/blocky/codemirror/closebrackets.js +84 -0
  302. data/vendor/assets/javascripts/blocky/codemirror/codemirror.js +4442 -0
  303. data/vendor/assets/javascripts/blocky/codemirror/formatting.js +1 -0
  304. data/vendor/assets/javascripts/blocky/codemirror/xml.js +1 -0
  305. data/vendor/assets/javascripts/blocky/jquery.js +10338 -0
  306. data/vendor/assets/javascripts/blocky/jquery_ujs.js +398 -0
  307. data/vendor/assets/javascripts/blocky/summernote.js +3922 -0
  308. data/vendor/assets/stylesheets/blocky/_bourbon.scss +59 -0
  309. data/vendor/assets/stylesheets/blocky/_neat.scss +21 -0
  310. data/vendor/assets/stylesheets/blocky/bootstrap.css +5785 -0
  311. data/vendor/assets/stylesheets/blocky/bootstrap.css.map +1 -0
  312. data/vendor/assets/stylesheets/blocky/bourbon/_bourbon-deprecated-upcoming.scss +13 -0
  313. data/vendor/assets/stylesheets/blocky/bourbon/addons/_button.scss +273 -0
  314. data/vendor/assets/stylesheets/blocky/bourbon/addons/_clearfix.scss +29 -0
  315. data/vendor/assets/stylesheets/blocky/bourbon/addons/_font-family.scss +5 -0
  316. data/vendor/assets/stylesheets/blocky/bourbon/addons/_hide-text.scss +5 -0
  317. data/vendor/assets/stylesheets/blocky/bourbon/addons/_html5-input-types.scss +56 -0
  318. data/vendor/assets/stylesheets/blocky/bourbon/addons/_position.scss +42 -0
  319. data/vendor/assets/stylesheets/blocky/bourbon/addons/_prefixer.scss +49 -0
  320. data/vendor/assets/stylesheets/blocky/bourbon/addons/_retina-image.scss +32 -0
  321. data/vendor/assets/stylesheets/blocky/bourbon/addons/_size.scss +44 -0
  322. data/vendor/assets/stylesheets/blocky/bourbon/addons/_timing-functions.scss +32 -0
  323. data/vendor/assets/stylesheets/blocky/bourbon/addons/_triangle.scss +45 -0
  324. data/vendor/assets/stylesheets/blocky/bourbon/css3/_animation.scss +52 -0
  325. data/vendor/assets/stylesheets/blocky/bourbon/css3/_appearance.scss +3 -0
  326. data/vendor/assets/stylesheets/blocky/bourbon/css3/_backface-visibility.scss +6 -0
  327. data/vendor/assets/stylesheets/blocky/bourbon/css3/_background-image.scss +48 -0
  328. data/vendor/assets/stylesheets/blocky/bourbon/css3/_background.scss +103 -0
  329. data/vendor/assets/stylesheets/blocky/bourbon/css3/_border-image.scss +55 -0
  330. data/vendor/assets/stylesheets/blocky/bourbon/css3/_border-radius.scss +22 -0
  331. data/vendor/assets/stylesheets/blocky/bourbon/css3/_box-sizing.scss +4 -0
  332. data/vendor/assets/stylesheets/blocky/bourbon/css3/_columns.scss +47 -0
  333. data/vendor/assets/stylesheets/blocky/bourbon/css3/_flex-box.scss +52 -0
  334. data/vendor/assets/stylesheets/blocky/bourbon/css3/_font-face.scss +23 -0
  335. data/vendor/assets/stylesheets/blocky/bourbon/css3/_hidpi-media-query.scss +10 -0
  336. data/vendor/assets/stylesheets/blocky/bourbon/css3/_image-rendering.scss +13 -0
  337. data/vendor/assets/stylesheets/blocky/bourbon/css3/_inline-block.scss +8 -0
  338. data/vendor/assets/stylesheets/blocky/bourbon/css3/_keyframes.scss +43 -0
  339. data/vendor/assets/stylesheets/blocky/bourbon/css3/_linear-gradient.scss +41 -0
  340. data/vendor/assets/stylesheets/blocky/bourbon/css3/_perspective.scss +8 -0
  341. data/vendor/assets/stylesheets/blocky/bourbon/css3/_placeholder.scss +29 -0
  342. data/vendor/assets/stylesheets/blocky/bourbon/css3/_radial-gradient.scss +44 -0
  343. data/vendor/assets/stylesheets/blocky/bourbon/css3/_transform.scss +15 -0
  344. data/vendor/assets/stylesheets/blocky/bourbon/css3/_transition.scss +34 -0
  345. data/vendor/assets/stylesheets/blocky/bourbon/css3/_user-select.scss +3 -0
  346. data/vendor/assets/stylesheets/blocky/bourbon/functions/_compact.scss +11 -0
  347. data/vendor/assets/stylesheets/blocky/bourbon/functions/_flex-grid.scss +39 -0
  348. data/vendor/assets/stylesheets/blocky/bourbon/functions/_grid-width.scss +13 -0
  349. data/vendor/assets/stylesheets/blocky/bourbon/functions/_linear-gradient.scss +13 -0
  350. data/vendor/assets/stylesheets/blocky/bourbon/functions/_modular-scale.scss +40 -0
  351. data/vendor/assets/stylesheets/blocky/bourbon/functions/_px-to-em.scss +8 -0
  352. data/vendor/assets/stylesheets/blocky/bourbon/functions/_radial-gradient.scss +23 -0
  353. data/vendor/assets/stylesheets/blocky/bourbon/functions/_tint-shade.scss +9 -0
  354. data/vendor/assets/stylesheets/blocky/bourbon/functions/_transition-property-name.scss +22 -0
  355. data/vendor/assets/stylesheets/blocky/bourbon/helpers/_deprecated-webkit-gradient.scss +39 -0
  356. data/vendor/assets/stylesheets/blocky/bourbon/helpers/_gradient-positions-parser.scss +13 -0
  357. data/vendor/assets/stylesheets/blocky/bourbon/helpers/_linear-positions-parser.scss +61 -0
  358. data/vendor/assets/stylesheets/blocky/bourbon/helpers/_radial-arg-parser.scss +69 -0
  359. data/vendor/assets/stylesheets/blocky/bourbon/helpers/_radial-positions-parser.scss +18 -0
  360. data/vendor/assets/stylesheets/blocky/bourbon/helpers/_render-gradients.scss +26 -0
  361. data/vendor/assets/stylesheets/blocky/bourbon/helpers/_shape-size-stripper.scss +10 -0
  362. data/vendor/assets/stylesheets/blocky/codemirror/codemirror.css +1 -0
  363. data/vendor/assets/stylesheets/blocky/codemirror/monokai.css +1 -0
  364. data/vendor/assets/stylesheets/blocky/font-awesome.scss +17 -0
  365. data/vendor/assets/stylesheets/blocky/font-awesome/_bordered-pulled.scss +16 -0
  366. data/vendor/assets/stylesheets/blocky/font-awesome/_core.scss +12 -0
  367. data/vendor/assets/stylesheets/blocky/font-awesome/_fixed-width.scss +6 -0
  368. data/vendor/assets/stylesheets/blocky/font-awesome/_icons.scss +412 -0
  369. data/vendor/assets/stylesheets/blocky/font-awesome/_larger.scss +13 -0
  370. data/vendor/assets/stylesheets/blocky/font-awesome/_list.scss +19 -0
  371. data/vendor/assets/stylesheets/blocky/font-awesome/_mixins.scss +20 -0
  372. data/vendor/assets/stylesheets/blocky/font-awesome/_path.scss +13 -0
  373. data/vendor/assets/stylesheets/blocky/font-awesome/_rotated-flipped.scss +9 -0
  374. data/vendor/assets/stylesheets/blocky/font-awesome/_spinning.scss +30 -0
  375. data/vendor/assets/stylesheets/blocky/font-awesome/_stacked.scss +20 -0
  376. data/vendor/assets/stylesheets/blocky/font-awesome/_variables.scss +381 -0
  377. data/vendor/assets/stylesheets/blocky/neat/_neat-helpers.scss +7 -0
  378. data/vendor/assets/stylesheets/blocky/neat/functions/_new-breakpoint.scss +16 -0
  379. data/vendor/assets/stylesheets/blocky/neat/functions/_private.scss +125 -0
  380. data/vendor/assets/stylesheets/blocky/neat/grid/_fill-parent.scss +7 -0
  381. data/vendor/assets/stylesheets/blocky/neat/grid/_grid.scss +5 -0
  382. data/vendor/assets/stylesheets/blocky/neat/grid/_media.scss +51 -0
  383. data/vendor/assets/stylesheets/blocky/neat/grid/_omega.scss +79 -0
  384. data/vendor/assets/stylesheets/blocky/neat/grid/_outer-container.scss +8 -0
  385. data/vendor/assets/stylesheets/blocky/neat/grid/_pad.scss +8 -0
  386. data/vendor/assets/stylesheets/blocky/neat/grid/_private.scss +50 -0
  387. data/vendor/assets/stylesheets/blocky/neat/grid/_reset.scss +12 -0
  388. data/vendor/assets/stylesheets/blocky/neat/grid/_row.scss +17 -0
  389. data/vendor/assets/stylesheets/blocky/neat/grid/_shift.scss +16 -0
  390. data/vendor/assets/stylesheets/blocky/neat/grid/_span-columns.scss +45 -0
  391. data/vendor/assets/stylesheets/blocky/neat/grid/_to-deprecate.scss +57 -0
  392. data/vendor/assets/stylesheets/blocky/neat/grid/_visual-grid.scss +41 -0
  393. data/vendor/assets/stylesheets/blocky/neat/settings/_grid.scss +7 -0
  394. data/vendor/assets/stylesheets/blocky/neat/settings/_visual-grid.scss +5 -0
  395. data/vendor/assets/stylesheets/blocky/normalize.css +423 -0
  396. data/vendor/assets/stylesheets/blocky/summernote.css +1 -0
  397. metadata +991 -0
@@ -0,0 +1,398 @@
1
+ (function($, undefined) {
2
+
3
+ /**
4
+ * Unobtrusive scripting adapter for jQuery
5
+ * https://github.com/rails/jquery-ujs
6
+ *
7
+ * Requires jQuery 1.7.0 or later.
8
+ *
9
+ * Released under the MIT license
10
+ *
11
+ */
12
+
13
+ // Cut down on the number of issues from people inadvertently including jquery_ujs twice
14
+ // by detecting and raising an error when it happens.
15
+ if ( $.rails !== undefined ) {
16
+ $.error('jquery-ujs has already been loaded!');
17
+ }
18
+
19
+ // Shorthand to make it a little easier to call public rails functions from within rails.js
20
+ var rails;
21
+ var $document = $(document);
22
+
23
+ $.rails = rails = {
24
+ // Link elements bound by jquery-ujs
25
+ linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote], a[data-disable-with]',
26
+
27
+ // Button elements bound by jquery-ujs
28
+ buttonClickSelector: 'button[data-remote]',
29
+
30
+ // Select elements bound by jquery-ujs
31
+ inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]',
32
+
33
+ // Form elements bound by jquery-ujs
34
+ formSubmitSelector: 'form',
35
+
36
+ // Form input elements bound by jquery-ujs
37
+ formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type])',
38
+
39
+ // Form input elements disabled during form submission
40
+ disableSelector: 'input[data-disable-with], button[data-disable-with], textarea[data-disable-with]',
41
+
42
+ // Form input elements re-enabled after form submission
43
+ enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled',
44
+
45
+ // Form required input elements
46
+ requiredInputSelector: 'input[name][required]:not([disabled]),textarea[name][required]:not([disabled])',
47
+
48
+ // Form file input elements
49
+ fileInputSelector: 'input[type=file]',
50
+
51
+ // Link onClick disable selector with possible reenable after remote submission
52
+ linkDisableSelector: 'a[data-disable-with]',
53
+
54
+ // Make sure that every Ajax request sends the CSRF token
55
+ CSRFProtection: function(xhr) {
56
+ var token = $('meta[name="csrf-token"]').attr('content');
57
+ if (token) xhr.setRequestHeader('X-CSRF-Token', token);
58
+ },
59
+
60
+ // making sure that all forms have actual up-to-date token(cached forms contain old one)
61
+ refreshCSRFTokens: function(){
62
+ var csrfToken = $('meta[name=csrf-token]').attr('content');
63
+ var csrfParam = $('meta[name=csrf-param]').attr('content');
64
+ $('form input[name="' + csrfParam + '"]').val(csrfToken);
65
+ },
66
+
67
+ // Triggers an event on an element and returns false if the event result is false
68
+ fire: function(obj, name, data) {
69
+ var event = $.Event(name);
70
+ obj.trigger(event, data);
71
+ return event.result !== false;
72
+ },
73
+
74
+ // Default confirm dialog, may be overridden with custom confirm dialog in $.rails.confirm
75
+ confirm: function(message) {
76
+ return confirm(message);
77
+ },
78
+
79
+ // Default ajax function, may be overridden with custom function in $.rails.ajax
80
+ ajax: function(options) {
81
+ return $.ajax(options);
82
+ },
83
+
84
+ // Default way to get an element's href. May be overridden at $.rails.href.
85
+ href: function(element) {
86
+ return element.attr('href');
87
+ },
88
+
89
+ // Submits "remote" forms and links with ajax
90
+ handleRemote: function(element) {
91
+ var method, url, data, elCrossDomain, crossDomain, withCredentials, dataType, options;
92
+
93
+ if (rails.fire(element, 'ajax:before')) {
94
+ elCrossDomain = element.data('cross-domain');
95
+ crossDomain = elCrossDomain === undefined ? null : elCrossDomain;
96
+ withCredentials = element.data('with-credentials') || null;
97
+ dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType);
98
+
99
+ if (element.is('form')) {
100
+ method = element.attr('method');
101
+ url = element.attr('action');
102
+ data = element.serializeArray();
103
+ // memoized value from clicked submit button
104
+ var button = element.data('ujs:submit-button');
105
+ if (button) {
106
+ data.push(button);
107
+ element.data('ujs:submit-button', null);
108
+ }
109
+ } else if (element.is(rails.inputChangeSelector)) {
110
+ method = element.data('method');
111
+ url = element.data('url');
112
+ data = element.serialize();
113
+ if (element.data('params')) data = data + "&" + element.data('params');
114
+ } else if (element.is(rails.buttonClickSelector)) {
115
+ method = element.data('method') || 'get';
116
+ url = element.data('url');
117
+ data = element.serialize();
118
+ if (element.data('params')) data = data + "&" + element.data('params');
119
+ } else {
120
+ method = element.data('method');
121
+ url = rails.href(element);
122
+ data = element.data('params') || null;
123
+ }
124
+
125
+ options = {
126
+ type: method || 'GET', data: data, dataType: dataType,
127
+ // stopping the "ajax:beforeSend" event will cancel the ajax request
128
+ beforeSend: function(xhr, settings) {
129
+ if (settings.dataType === undefined) {
130
+ xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script);
131
+ }
132
+ return rails.fire(element, 'ajax:beforeSend', [xhr, settings]);
133
+ },
134
+ success: function(data, status, xhr) {
135
+ element.trigger('ajax:success', [data, status, xhr]);
136
+ },
137
+ complete: function(xhr, status) {
138
+ element.trigger('ajax:complete', [xhr, status]);
139
+ },
140
+ error: function(xhr, status, error) {
141
+ element.trigger('ajax:error', [xhr, status, error]);
142
+ },
143
+ crossDomain: crossDomain
144
+ };
145
+
146
+ // There is no withCredentials for IE6-8 when
147
+ // "Enable native XMLHTTP support" is disabled
148
+ if (withCredentials) {
149
+ options.xhrFields = {
150
+ withCredentials: withCredentials
151
+ };
152
+ }
153
+
154
+ // Only pass url to `ajax` options if not blank
155
+ if (url) { options.url = url; }
156
+
157
+ var jqxhr = rails.ajax(options);
158
+ element.trigger('ajax:send', jqxhr);
159
+ return jqxhr;
160
+ } else {
161
+ return false;
162
+ }
163
+ },
164
+
165
+ // Handles "data-method" on links such as:
166
+ // <a href="/users/5" data-method="delete" rel="nofollow" data-confirm="Are you sure?">Delete</a>
167
+ handleMethod: function(link) {
168
+ var href = rails.href(link),
169
+ method = link.data('method'),
170
+ target = link.attr('target'),
171
+ csrfToken = $('meta[name=csrf-token]').attr('content'),
172
+ csrfParam = $('meta[name=csrf-param]').attr('content'),
173
+ form = $('<form method="post" action="' + href + '"></form>'),
174
+ metadataInput = '<input name="_method" value="' + method + '" type="hidden" />';
175
+
176
+ if (csrfParam !== undefined && csrfToken !== undefined) {
177
+ metadataInput += '<input name="' + csrfParam + '" value="' + csrfToken + '" type="hidden" />';
178
+ }
179
+
180
+ if (target) { form.attr('target', target); }
181
+
182
+ form.hide().append(metadataInput).appendTo('body');
183
+ form.submit();
184
+ },
185
+
186
+ /* Disables form elements:
187
+ - Caches element value in 'ujs:enable-with' data store
188
+ - Replaces element text with value of 'data-disable-with' attribute
189
+ - Sets disabled property to true
190
+ */
191
+ disableFormElements: function(form) {
192
+ form.find(rails.disableSelector).each(function() {
193
+ var element = $(this), method = element.is('button') ? 'html' : 'val';
194
+ element.data('ujs:enable-with', element[method]());
195
+ element[method](element.data('disable-with'));
196
+ element.prop('disabled', true);
197
+ });
198
+ },
199
+
200
+ /* Re-enables disabled form elements:
201
+ - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`)
202
+ - Sets disabled property to false
203
+ */
204
+ enableFormElements: function(form) {
205
+ form.find(rails.enableSelector).each(function() {
206
+ var element = $(this), method = element.is('button') ? 'html' : 'val';
207
+ if (element.data('ujs:enable-with')) element[method](element.data('ujs:enable-with'));
208
+ element.prop('disabled', false);
209
+ });
210
+ },
211
+
212
+ /* For 'data-confirm' attribute:
213
+ - Fires `confirm` event
214
+ - Shows the confirmation dialog
215
+ - Fires the `confirm:complete` event
216
+
217
+ Returns `true` if no function stops the chain and user chose yes; `false` otherwise.
218
+ Attaching a handler to the element's `confirm` event that returns a `falsy` value cancels the confirmation dialog.
219
+ Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes this function
220
+ return false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog.
221
+ */
222
+ allowAction: function(element) {
223
+ var message = element.data('confirm'),
224
+ answer = false, callback;
225
+ if (!message) { return true; }
226
+
227
+ if (rails.fire(element, 'confirm')) {
228
+ answer = rails.confirm(message);
229
+ callback = rails.fire(element, 'confirm:complete', [answer]);
230
+ }
231
+ return answer && callback;
232
+ },
233
+
234
+ // Helper function which checks for blank inputs in a form that match the specified CSS selector
235
+ blankInputs: function(form, specifiedSelector, nonBlank) {
236
+ var inputs = $(), input, valueToCheck,
237
+ selector = specifiedSelector || 'input,textarea',
238
+ allInputs = form.find(selector);
239
+
240
+ allInputs.each(function() {
241
+ input = $(this);
242
+ valueToCheck = input.is('input[type=checkbox],input[type=radio]') ? input.is(':checked') : input.val();
243
+ // If nonBlank and valueToCheck are both truthy, or nonBlank and valueToCheck are both falsey
244
+ if (!valueToCheck === !nonBlank) {
245
+
246
+ // Don't count unchecked required radio if other radio with same name is checked
247
+ if (input.is('input[type=radio]') && allInputs.filter('input[type=radio]:checked[name="' + input.attr('name') + '"]').length) {
248
+ return true; // Skip to next input
249
+ }
250
+
251
+ inputs = inputs.add(input);
252
+ }
253
+ });
254
+ return inputs.length ? inputs : false;
255
+ },
256
+
257
+ // Helper function which checks for non-blank inputs in a form that match the specified CSS selector
258
+ nonBlankInputs: function(form, specifiedSelector) {
259
+ return rails.blankInputs(form, specifiedSelector, true); // true specifies nonBlank
260
+ },
261
+
262
+ // Helper function, needed to provide consistent behavior in IE
263
+ stopEverything: function(e) {
264
+ $(e.target).trigger('ujs:everythingStopped');
265
+ e.stopImmediatePropagation();
266
+ return false;
267
+ },
268
+
269
+ // replace element's html with the 'data-disable-with' after storing original html
270
+ // and prevent clicking on it
271
+ disableElement: function(element) {
272
+ element.data('ujs:enable-with', element.html()); // store enabled state
273
+ element.html(element.data('disable-with')); // set to disabled state
274
+ element.bind('click.railsDisable', function(e) { // prevent further clicking
275
+ return rails.stopEverything(e);
276
+ });
277
+ },
278
+
279
+ // restore element to its original state which was disabled by 'disableElement' above
280
+ enableElement: function(element) {
281
+ if (element.data('ujs:enable-with') !== undefined) {
282
+ element.html(element.data('ujs:enable-with')); // set to old enabled state
283
+ element.removeData('ujs:enable-with'); // clean up cache
284
+ }
285
+ element.unbind('click.railsDisable'); // enable element
286
+ }
287
+
288
+ };
289
+
290
+ if (rails.fire($document, 'rails:attachBindings')) {
291
+
292
+ $.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { rails.CSRFProtection(xhr); }});
293
+
294
+ $document.delegate(rails.linkDisableSelector, 'ajax:complete', function() {
295
+ rails.enableElement($(this));
296
+ });
297
+
298
+ $document.delegate(rails.linkClickSelector, 'click.rails', function(e) {
299
+ var link = $(this), method = link.data('method'), data = link.data('params'), metaClick = e.metaKey || e.ctrlKey;
300
+ if (!rails.allowAction(link)) return rails.stopEverything(e);
301
+
302
+ if (!metaClick && link.is(rails.linkDisableSelector)) rails.disableElement(link);
303
+
304
+ if (link.data('remote') !== undefined) {
305
+ if (metaClick && (!method || method === 'GET') && !data) { return true; }
306
+
307
+ var handleRemote = rails.handleRemote(link);
308
+ // response from rails.handleRemote() will either be false or a deferred object promise.
309
+ if (handleRemote === false) {
310
+ rails.enableElement(link);
311
+ } else {
312
+ handleRemote.error( function() { rails.enableElement(link); } );
313
+ }
314
+ return false;
315
+
316
+ } else if (link.data('method')) {
317
+ rails.handleMethod(link);
318
+ return false;
319
+ }
320
+ });
321
+
322
+ $document.delegate(rails.buttonClickSelector, 'click.rails', function(e) {
323
+ var button = $(this);
324
+ if (!rails.allowAction(button)) return rails.stopEverything(e);
325
+
326
+ rails.handleRemote(button);
327
+ return false;
328
+ });
329
+
330
+ $document.delegate(rails.inputChangeSelector, 'change.rails', function(e) {
331
+ var link = $(this);
332
+ if (!rails.allowAction(link)) return rails.stopEverything(e);
333
+
334
+ rails.handleRemote(link);
335
+ return false;
336
+ });
337
+
338
+ $document.delegate(rails.formSubmitSelector, 'submit.rails', function(e) {
339
+ var form = $(this),
340
+ remote = form.data('remote') !== undefined,
341
+ blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector),
342
+ nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector);
343
+
344
+ if (!rails.allowAction(form)) return rails.stopEverything(e);
345
+
346
+ // skip other logic when required values are missing or file upload is present
347
+ if (blankRequiredInputs && form.attr("novalidate") == undefined && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) {
348
+ return rails.stopEverything(e);
349
+ }
350
+
351
+ if (remote) {
352
+ if (nonBlankFileInputs) {
353
+ // slight timeout so that the submit button gets properly serialized
354
+ // (make it easy for event handler to serialize form without disabled values)
355
+ setTimeout(function(){ rails.disableFormElements(form); }, 13);
356
+ var aborted = rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]);
357
+
358
+ // re-enable form elements if event bindings return false (canceling normal form submission)
359
+ if (!aborted) { setTimeout(function(){ rails.enableFormElements(form); }, 13); }
360
+
361
+ return aborted;
362
+ }
363
+
364
+ rails.handleRemote(form);
365
+ return false;
366
+
367
+ } else {
368
+ // slight timeout so that the submit button gets properly serialized
369
+ setTimeout(function(){ rails.disableFormElements(form); }, 13);
370
+ }
371
+ });
372
+
373
+ $document.delegate(rails.formInputClickSelector, 'click.rails', function(event) {
374
+ var button = $(this);
375
+
376
+ if (!rails.allowAction(button)) return rails.stopEverything(event);
377
+
378
+ // register the pressed submit button
379
+ var name = button.attr('name'),
380
+ data = name ? {name:name, value:button.val()} : null;
381
+
382
+ button.closest('form').data('ujs:submit-button', data);
383
+ });
384
+
385
+ $document.delegate(rails.formSubmitSelector, 'ajax:beforeSend.rails', function(event) {
386
+ if (this == event.target) rails.disableFormElements($(this));
387
+ });
388
+
389
+ $document.delegate(rails.formSubmitSelector, 'ajax:complete.rails', function(event) {
390
+ if (this == event.target) rails.enableFormElements($(this));
391
+ });
392
+
393
+ $(function(){
394
+ rails.refreshCSRFTokens();
395
+ });
396
+ }
397
+
398
+ })( jQuery );
@@ -0,0 +1,3922 @@
1
+ /**
2
+ * Super simple wysiwyg editor on Bootstrap v0.5.2
3
+ * http://hackerwins.github.io/summernote/
4
+ *
5
+ * summernote.js
6
+ * Copyright 2013 Alan Hong. and outher contributors
7
+ * summernote may be freely distributed under the MIT license./
8
+ *
9
+ * Date: 2014-05-16T11:28Z
10
+ */
11
+ (function (factory) {
12
+ /* global define */
13
+ if (typeof define === 'function' && define.amd) {
14
+ // AMD. Register as an anonymous module.
15
+ define(['jquery', 'codemirror'], factory);
16
+ } else {
17
+ // Browser globals: jQuery, CodeMirror
18
+ factory(window.jQuery, window.CodeMirror);
19
+ }
20
+ }(function ($, CodeMirror) {
21
+
22
+
23
+
24
+ if ('function' !== typeof Array.prototype.reduce) {
25
+ /**
26
+ * Array.prototype.reduce fallback
27
+ *
28
+ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
29
+ */
30
+ Array.prototype.reduce = function (callback, optInitialValue) {
31
+ var idx, value, length = this.length >>> 0, isValueSet = false;
32
+ if (1 < arguments.length) {
33
+ value = optInitialValue;
34
+ isValueSet = true;
35
+ }
36
+ for (idx = 0; length > idx; ++idx) {
37
+ if (this.hasOwnProperty(idx)) {
38
+ if (isValueSet) {
39
+ value = callback(value, this[idx], idx, this);
40
+ } else {
41
+ value = this[idx];
42
+ isValueSet = true;
43
+ }
44
+ }
45
+ }
46
+ if (!isValueSet) {
47
+ throw new TypeError('Reduce of empty array with no initial value');
48
+ }
49
+ return value;
50
+ };
51
+ }
52
+
53
+ /**
54
+ * Object which check platform and agent
55
+ */
56
+ var agent = {
57
+ bMac: navigator.appVersion.indexOf('Mac') > -1,
58
+ bMSIE: navigator.userAgent.indexOf('MSIE') > -1 || navigator.userAgent.indexOf('Trident') > -1,
59
+ bFF: navigator.userAgent.indexOf('Firefox') > -1,
60
+ jqueryVersion: parseFloat($.fn.jquery),
61
+ bCodeMirror: !!CodeMirror
62
+ };
63
+
64
+ /**
65
+ * func utils (for high-order func's arg)
66
+ */
67
+ var func = (function () {
68
+ var eq = function (elA) {
69
+ return function (elB) {
70
+ return elA === elB;
71
+ };
72
+ };
73
+
74
+ var eq2 = function (elA, elB) {
75
+ return elA === elB;
76
+ };
77
+
78
+ var ok = function () {
79
+ return true;
80
+ };
81
+
82
+ var fail = function () {
83
+ return false;
84
+ };
85
+
86
+ var not = function (f) {
87
+ return function () {
88
+ return !f.apply(f, arguments);
89
+ };
90
+ };
91
+
92
+ var self = function (a) {
93
+ return a;
94
+ };
95
+
96
+ var idCounter = 0;
97
+
98
+ /**
99
+ * generate a globally-unique id
100
+ *
101
+ * @param {String} [prefix]
102
+ */
103
+ var uniqueId = function (prefix) {
104
+ var id = ++idCounter + '';
105
+ return prefix ? prefix + id : id;
106
+ };
107
+
108
+ /**
109
+ * returns bnd (bounds) from rect
110
+ *
111
+ * - IE Compatability Issue: http://goo.gl/sRLOAo
112
+ * - Scroll Issue: http://goo.gl/sNjUc
113
+ *
114
+ * @param {Rect} rect
115
+ * @return {Object} bounds
116
+ * @return {Number} bounds.top
117
+ * @return {Number} bounds.left
118
+ * @return {Number} bounds.width
119
+ * @return {Number} bounds.height
120
+ */
121
+ var rect2bnd = function (rect) {
122
+ var $document = $(document);
123
+ return {
124
+ top: rect.top + $document.scrollTop(),
125
+ left: rect.left + $document.scrollLeft(),
126
+ width: rect.right - rect.left,
127
+ height: rect.bottom - rect.top
128
+ };
129
+ };
130
+
131
+ /**
132
+ * returns a copy of the object where the keys have become the values and the values the keys.
133
+ * @param {Object} obj
134
+ * @return {Object}
135
+ */
136
+ var invertObject = function (obj) {
137
+ var inverted = {};
138
+ for (var key in obj) {
139
+ if (obj.hasOwnProperty(key)) {
140
+ inverted[obj[key]] = key;
141
+ }
142
+ }
143
+ return inverted;
144
+ };
145
+
146
+ return {
147
+ eq: eq,
148
+ eq2: eq2,
149
+ ok: ok,
150
+ fail: fail,
151
+ not: not,
152
+ self: self,
153
+ uniqueId: uniqueId,
154
+ rect2bnd: rect2bnd,
155
+ invertObject: invertObject
156
+ };
157
+ })();
158
+
159
+ /**
160
+ * list utils
161
+ */
162
+ var list = (function () {
163
+ /**
164
+ * returns the first element of an array.
165
+ * @param {Array} array
166
+ */
167
+ var head = function (array) {
168
+ return array[0];
169
+ };
170
+
171
+ /**
172
+ * returns the last element of an array.
173
+ * @param {Array} array
174
+ */
175
+ var last = function (array) {
176
+ return array[array.length - 1];
177
+ };
178
+
179
+ /**
180
+ * returns everything but the last entry of the array.
181
+ * @param {Array} array
182
+ */
183
+ var initial = function (array) {
184
+ return array.slice(0, array.length - 1);
185
+ };
186
+
187
+ /**
188
+ * returns the rest of the elements in an array.
189
+ * @param {Array} array
190
+ */
191
+ var tail = function (array) {
192
+ return array.slice(1);
193
+ };
194
+
195
+ /**
196
+ * returns next item.
197
+ * @param {Array} array
198
+ */
199
+ var next = function (array, item) {
200
+ var idx = array.indexOf(item);
201
+ if (idx === -1) { return null; }
202
+
203
+ return array[idx + 1];
204
+ };
205
+
206
+ /**
207
+ * returns prev item.
208
+ * @param {Array} array
209
+ */
210
+ var prev = function (array, item) {
211
+ var idx = array.indexOf(item);
212
+ if (idx === -1) { return null; }
213
+
214
+ return array[idx - 1];
215
+ };
216
+
217
+ /**
218
+ * get sum from a list
219
+ * @param {Array} array - array
220
+ * @param {Function} fn - iterator
221
+ */
222
+ var sum = function (array, fn) {
223
+ fn = fn || func.self;
224
+ return array.reduce(function (memo, v) {
225
+ return memo + fn(v);
226
+ }, 0);
227
+ };
228
+
229
+ /**
230
+ * returns a copy of the collection with array type.
231
+ * @param {Collection} collection - collection eg) node.childNodes, ...
232
+ */
233
+ var from = function (collection) {
234
+ var result = [], idx = -1, length = collection.length;
235
+ while (++idx < length) {
236
+ result[idx] = collection[idx];
237
+ }
238
+ return result;
239
+ };
240
+
241
+ /**
242
+ * cluster elements by predicate function.
243
+ * @param {Array} array - array
244
+ * @param {Function} fn - predicate function for cluster rule
245
+ * @param {Array[]}
246
+ */
247
+ var clusterBy = function (array, fn) {
248
+ if (array.length === 0) { return []; }
249
+ var aTail = tail(array);
250
+ return aTail.reduce(function (memo, v) {
251
+ var aLast = last(memo);
252
+ if (fn(last(aLast), v)) {
253
+ aLast[aLast.length] = v;
254
+ } else {
255
+ memo[memo.length] = [v];
256
+ }
257
+ return memo;
258
+ }, [[head(array)]]);
259
+ };
260
+
261
+ /**
262
+ * returns a copy of the array with all falsy values removed
263
+ * @param {Array} array - array
264
+ * @param {Function} fn - predicate function for cluster rule
265
+ */
266
+ var compact = function (array) {
267
+ var aResult = [];
268
+ for (var idx = 0, sz = array.length; idx < sz; idx ++) {
269
+ if (array[idx]) { aResult.push(array[idx]); }
270
+ }
271
+ return aResult;
272
+ };
273
+
274
+ return { head: head, last: last, initial: initial, tail: tail,
275
+ prev: prev, next: next, sum: sum, from: from,
276
+ compact: compact, clusterBy: clusterBy };
277
+ })();
278
+
279
+ /**
280
+ * Dom functions
281
+ */
282
+ var dom = (function () {
283
+ /**
284
+ * returns whether node is `note-editable` or not.
285
+ *
286
+ * @param {Element} node
287
+ * @return {Boolean}
288
+ */
289
+ var isEditable = function (node) {
290
+ return node && $(node).hasClass('note-editable');
291
+ };
292
+
293
+ var isControlSizing = function (node) {
294
+ return node && $(node).hasClass('note-control-sizing');
295
+ };
296
+
297
+ /**
298
+ * build layoutInfo from $editor(.note-editor)
299
+ *
300
+ * @param {jQuery} $editor
301
+ * @return {Object}
302
+ */
303
+ var buildLayoutInfo = function ($editor) {
304
+ var makeFinder;
305
+
306
+ // air mode
307
+ if ($editor.hasClass('note-air-editor')) {
308
+ var id = list.last($editor.attr('id').split('-'));
309
+ makeFinder = function (sIdPrefix) {
310
+ return function () { return $(sIdPrefix + id); };
311
+ };
312
+
313
+ return {
314
+ editor: function () { return $editor; },
315
+ editable: function () { return $editor; },
316
+ popover: makeFinder('#note-popover-'),
317
+ handle: makeFinder('#note-handle-'),
318
+ dialog: makeFinder('#note-dialog-')
319
+ };
320
+
321
+ // frame mode
322
+ } else {
323
+ makeFinder = function (sClassName) {
324
+ return function () { return $editor.find(sClassName); };
325
+ };
326
+ return {
327
+ editor: function () { return $editor; },
328
+ dropzone: makeFinder('.note-dropzone'),
329
+ toolbar: makeFinder('.note-toolbar'),
330
+ editable: makeFinder('.note-editable'),
331
+ codable: makeFinder('.note-codable'),
332
+ statusbar: makeFinder('.note-statusbar'),
333
+ popover: makeFinder('.note-popover'),
334
+ handle: makeFinder('.note-handle'),
335
+ dialog: makeFinder('.note-dialog')
336
+ };
337
+ }
338
+ };
339
+
340
+ /**
341
+ * returns predicate which judge whether nodeName is same
342
+ * @param {String} sNodeName
343
+ */
344
+ var makePredByNodeName = function (sNodeName) {
345
+ // nodeName is always uppercase.
346
+ return function (node) {
347
+ return node && node.nodeName === sNodeName;
348
+ };
349
+ };
350
+
351
+ var isPara = function (node) {
352
+ // Chrome(v31.0), FF(v25.0.1) use DIV for paragraph
353
+ return node && /^DIV|^P|^LI|^H[1-7]/.test(node.nodeName);
354
+ };
355
+
356
+ var isList = function (node) {
357
+ return node && /^UL|^OL/.test(node.nodeName);
358
+ };
359
+
360
+ var isCell = function (node) {
361
+ return node && /^TD|^TH/.test(node.nodeName);
362
+ };
363
+
364
+ /**
365
+ * find nearest ancestor predicate hit
366
+ *
367
+ * @param {Element} node
368
+ * @param {Function} pred - predicate function
369
+ */
370
+ var ancestor = function (node, pred) {
371
+ while (node) {
372
+ if (pred(node)) { return node; }
373
+ if (isEditable(node)) { break; }
374
+
375
+ node = node.parentNode;
376
+ }
377
+ return null;
378
+ };
379
+
380
+ /**
381
+ * returns new array of ancestor nodes (until predicate hit).
382
+ *
383
+ * @param {Element} node
384
+ * @param {Function} [optional] pred - predicate function
385
+ */
386
+ var listAncestor = function (node, pred) {
387
+ pred = pred || func.fail;
388
+
389
+ var aAncestor = [];
390
+ ancestor(node, function (el) {
391
+ aAncestor.push(el);
392
+ return pred(el);
393
+ });
394
+ return aAncestor;
395
+ };
396
+
397
+ /**
398
+ * returns common ancestor node between two nodes.
399
+ *
400
+ * @param {Element} nodeA
401
+ * @param {Element} nodeB
402
+ */
403
+ var commonAncestor = function (nodeA, nodeB) {
404
+ var aAncestor = listAncestor(nodeA);
405
+ for (var n = nodeB; n; n = n.parentNode) {
406
+ if ($.inArray(n, aAncestor) > -1) { return n; }
407
+ }
408
+ return null; // difference document area
409
+ };
410
+
411
+ /**
412
+ * listing all Nodes between two nodes.
413
+ * FIXME: nodeA and nodeB must be sorted, use comparePoints later.
414
+ *
415
+ * @param {Element} nodeA
416
+ * @param {Element} nodeB
417
+ */
418
+ var listBetween = function (nodeA, nodeB) {
419
+ var aNode = [];
420
+
421
+ var bStart = false, bEnd = false;
422
+
423
+ // DFS(depth first search) with commonAcestor.
424
+ (function fnWalk(node) {
425
+ if (!node) { return; } // traverse fisnish
426
+ if (node === nodeA) { bStart = true; } // start point
427
+ if (bStart && !bEnd) { aNode.push(node); } // between
428
+ if (node === nodeB) { bEnd = true; return; } // end point
429
+
430
+ for (var idx = 0, sz = node.childNodes.length; idx < sz; idx++) {
431
+ fnWalk(node.childNodes[idx]);
432
+ }
433
+ })(commonAncestor(nodeA, nodeB));
434
+
435
+ return aNode;
436
+ };
437
+
438
+ /**
439
+ * listing all previous siblings (until predicate hit).
440
+ * @param {Element} node
441
+ * @param {Function} [optional] pred - predicate function
442
+ */
443
+ var listPrev = function (node, pred) {
444
+ pred = pred || func.fail;
445
+
446
+ var aNext = [];
447
+ while (node) {
448
+ aNext.push(node);
449
+ if (pred(node)) { break; }
450
+ node = node.previousSibling;
451
+ }
452
+ return aNext;
453
+ };
454
+
455
+ /**
456
+ * listing next siblings (until predicate hit).
457
+ *
458
+ * @param {Element} node
459
+ * @param {Function} [pred] - predicate function
460
+ */
461
+ var listNext = function (node, pred) {
462
+ pred = pred || func.fail;
463
+
464
+ var aNext = [];
465
+ while (node) {
466
+ aNext.push(node);
467
+ if (pred(node)) { break; }
468
+ node = node.nextSibling;
469
+ }
470
+ return aNext;
471
+ };
472
+
473
+ /**
474
+ * listing descendant nodes
475
+ *
476
+ * @param {Element} node
477
+ * @param {Function} [pred] - predicate function
478
+ */
479
+ var listDescendant = function (node, pred) {
480
+ var aDescendant = [];
481
+ pred = pred || func.ok;
482
+
483
+ // start DFS(depth first search) with node
484
+ (function fnWalk(current) {
485
+ if (node !== current && pred(current)) {
486
+ aDescendant.push(current);
487
+ }
488
+ for (var idx = 0, sz = current.childNodes.length; idx < sz; idx++) {
489
+ fnWalk(current.childNodes[idx]);
490
+ }
491
+ })(node);
492
+
493
+ return aDescendant;
494
+ };
495
+
496
+ /**
497
+ * insert node after preceding
498
+ *
499
+ * @param {Element} node
500
+ * @param {Element} preceding - predicate function
501
+ */
502
+ var insertAfter = function (node, preceding) {
503
+ var next = preceding.nextSibling, parent = preceding.parentNode;
504
+ if (next) {
505
+ parent.insertBefore(node, next);
506
+ } else {
507
+ parent.appendChild(node);
508
+ }
509
+ return node;
510
+ };
511
+
512
+ /**
513
+ * append elements.
514
+ *
515
+ * @param {Element} node
516
+ * @param {Collection} aChild
517
+ */
518
+ var appends = function (node, aChild) {
519
+ $.each(aChild, function (idx, child) {
520
+ node.appendChild(child);
521
+ });
522
+ return node;
523
+ };
524
+
525
+ var isText = makePredByNodeName('#text');
526
+
527
+ /**
528
+ * returns #text's text size or element's childNodes size
529
+ *
530
+ * @param {Element} node
531
+ */
532
+ var length = function (node) {
533
+ if (isText(node)) { return node.nodeValue.length; }
534
+ return node.childNodes.length;
535
+ };
536
+
537
+ /**
538
+ * returns offset from parent.
539
+ *
540
+ * @param {Element} node
541
+ */
542
+ var position = function (node) {
543
+ var offset = 0;
544
+ while ((node = node.previousSibling)) { offset += 1; }
545
+ return offset;
546
+ };
547
+
548
+ /**
549
+ * return offsetPath(array of offset) from ancestor
550
+ *
551
+ * @param {Element} ancestor - ancestor node
552
+ * @param {Element} node
553
+ */
554
+ var makeOffsetPath = function (ancestor, node) {
555
+ var aAncestor = list.initial(listAncestor(node, func.eq(ancestor)));
556
+ return $.map(aAncestor, position).reverse();
557
+ };
558
+
559
+ /**
560
+ * return element from offsetPath(array of offset)
561
+ *
562
+ * @param {Element} ancestor - ancestor node
563
+ * @param {array} aOffset - offsetPath
564
+ */
565
+ var fromOffsetPath = function (ancestor, aOffset) {
566
+ var current = ancestor;
567
+ for (var i = 0, sz = aOffset.length; i < sz; i++) {
568
+ current = current.childNodes[aOffset[i]];
569
+ }
570
+ return current;
571
+ };
572
+
573
+ /**
574
+ * split element or #text
575
+ *
576
+ * @param {Element} node
577
+ * @param {Number} offset
578
+ */
579
+ var splitData = function (node, offset) {
580
+ if (offset === 0) { return node; }
581
+ if (offset >= length(node)) { return node.nextSibling; }
582
+
583
+ // splitText
584
+ if (isText(node)) { return node.splitText(offset); }
585
+
586
+ // splitElement
587
+ var child = node.childNodes[offset];
588
+ node = insertAfter(node.cloneNode(false), node);
589
+ return appends(node, listNext(child));
590
+ };
591
+
592
+ /**
593
+ * split dom tree by boundaryPoint(pivot and offset)
594
+ *
595
+ * @param {Element} root
596
+ * @param {Element} pivot - this will be boundaryPoint's node
597
+ * @param {Number} offset - this will be boundaryPoint's offset
598
+ */
599
+ var split = function (root, pivot, offset) {
600
+ var aAncestor = listAncestor(pivot, func.eq(root));
601
+ if (aAncestor.length === 1) { return splitData(pivot, offset); }
602
+ return aAncestor.reduce(function (node, parent) {
603
+ var clone = parent.cloneNode(false);
604
+ insertAfter(clone, parent);
605
+ if (node === pivot) {
606
+ node = splitData(node, offset);
607
+ }
608
+ appends(clone, listNext(node));
609
+ return clone;
610
+ });
611
+ };
612
+
613
+ /**
614
+ * remove node, (bRemoveChild: remove child or not)
615
+ * @param {Element} node
616
+ * @param {Boolean} bRemoveChild
617
+ */
618
+ var remove = function (node, bRemoveChild) {
619
+ if (!node || !node.parentNode) { return; }
620
+ if (node.removeNode) { return node.removeNode(bRemoveChild); }
621
+
622
+ var elParent = node.parentNode;
623
+ if (!bRemoveChild) {
624
+ var aNode = [];
625
+ var i, sz;
626
+ for (i = 0, sz = node.childNodes.length; i < sz; i++) {
627
+ aNode.push(node.childNodes[i]);
628
+ }
629
+
630
+ for (i = 0, sz = aNode.length; i < sz; i++) {
631
+ elParent.insertBefore(aNode[i], node);
632
+ }
633
+ }
634
+
635
+ elParent.removeChild(node);
636
+ };
637
+
638
+ var html = function ($node) {
639
+ return dom.isTextarea($node[0]) ? $node.val() : $node.html();
640
+ };
641
+
642
+ return {
643
+ blank: agent.bMSIE ? '&nbsp;' : '<br/>',
644
+ emptyPara: '<p><br/></p>',
645
+ isEditable: isEditable,
646
+ isControlSizing: isControlSizing,
647
+ buildLayoutInfo: buildLayoutInfo,
648
+ isText: isText,
649
+ isPara: isPara,
650
+ isList: isList,
651
+ isTable: makePredByNodeName('TABLE'),
652
+ isCell: isCell,
653
+ isAnchor: makePredByNodeName('A'),
654
+ isDiv: makePredByNodeName('DIV'),
655
+ isLi: makePredByNodeName('LI'),
656
+ isSpan: makePredByNodeName('SPAN'),
657
+ isB: makePredByNodeName('B'),
658
+ isU: makePredByNodeName('U'),
659
+ isS: makePredByNodeName('S'),
660
+ isI: makePredByNodeName('I'),
661
+ isImg: makePredByNodeName('IMG'),
662
+ isTextarea: makePredByNodeName('TEXTAREA'),
663
+ ancestor: ancestor,
664
+ listAncestor: listAncestor,
665
+ listNext: listNext,
666
+ listPrev: listPrev,
667
+ listDescendant: listDescendant,
668
+ commonAncestor: commonAncestor,
669
+ listBetween: listBetween,
670
+ insertAfter: insertAfter,
671
+ position: position,
672
+ makeOffsetPath: makeOffsetPath,
673
+ fromOffsetPath: fromOffsetPath,
674
+ split: split,
675
+ remove: remove,
676
+ html: html
677
+ };
678
+ })();
679
+
680
+ var settings = {
681
+ // version
682
+ version: '0.5.2',
683
+
684
+ /**
685
+ * options
686
+ */
687
+ options: {
688
+ width: null, // set editor width
689
+ height: null, // set editable height, ex) 300
690
+
691
+ focus: false, // set focus after initilize summernote
692
+
693
+ tabsize: 4, // size of tab ex) 2 or 4
694
+ styleWithSpan: true, // style with span (Chrome and FF only)
695
+
696
+ disableLinkTarget: false, // hide link Target Checkbox
697
+ disableDragAndDrop: false, // disable drag and drop event
698
+
699
+ codemirror: { // codemirror options
700
+ mode: 'text/html',
701
+ lineNumbers: true
702
+ },
703
+
704
+ // language
705
+ lang: 'en-US', // language 'en-US', 'ko-KR', ...
706
+ direction: null, // text direction, ex) 'rtl'
707
+
708
+ // toolbar
709
+ toolbar: [
710
+ ['style', ['style']],
711
+ ['font', ['bold', 'italic', 'underline', 'superscript', 'subscript', 'strikethrough', 'clear']],
712
+ ['fontname', ['fontname']],
713
+ // ['fontsize', ['fontsize']], // Still buggy
714
+ ['color', ['color']],
715
+ ['para', ['ul', 'ol', 'paragraph']],
716
+ ['height', ['height']],
717
+ ['table', ['table']],
718
+ ['insert', ['link', 'picture', 'video']],
719
+ ['view', ['fullscreen', 'codeview']],
720
+ ['help', ['help']]
721
+ ],
722
+
723
+ // air mode: inline editor
724
+ airMode: false,
725
+ // airPopover: [
726
+ // ['style', ['style']],
727
+ // ['font', ['bold', 'italic', 'underline', 'clear']],
728
+ // ['fontname', ['fontname']],
729
+ // ['fontsize', ['fontsize']], // Still buggy
730
+ // ['color', ['color']],
731
+ // ['para', ['ul', 'ol', 'paragraph']],
732
+ // ['height', ['height']],
733
+ // ['table', ['table']],
734
+ // ['insert', ['link', 'picture', 'video']],
735
+ // ['help', ['help']]
736
+ // ],
737
+ airPopover: [
738
+ ['color', ['color']],
739
+ ['font', ['bold', 'underline', 'clear']],
740
+ ['para', ['ul', 'paragraph']],
741
+ ['table', ['table']],
742
+ ['insert', ['link', 'picture']]
743
+ ],
744
+
745
+ // style tag
746
+ styleTags: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
747
+
748
+ // default fontName
749
+ defaultFontName: 'Arial',
750
+
751
+ // fontName
752
+ fontNames: [
753
+ 'Serif', 'Sans', 'Arial', 'Arial Black', 'Courier',
754
+ 'Courier New', 'Comic Sans MS', 'Helvetica', 'Impact', 'Lucida Grande',
755
+ 'Lucida Sans', 'Tahoma', 'Times', 'Times New Roman', 'Verdana'
756
+ ],
757
+
758
+ // pallete colors(n x n)
759
+ colors: [
760
+ ['#000000', '#424242', '#636363', '#9C9C94', '#CEC6CE', '#EFEFEF', '#F7F7F7', '#FFFFFF'],
761
+ ['#FF0000', '#FF9C00', '#FFFF00', '#00FF00', '#00FFFF', '#0000FF', '#9C00FF', '#FF00FF'],
762
+ ['#F7C6CE', '#FFE7CE', '#FFEFC6', '#D6EFD6', '#CEDEE7', '#CEE7F7', '#D6D6E7', '#E7D6DE'],
763
+ ['#E79C9C', '#FFC69C', '#FFE79C', '#B5D6A5', '#A5C6CE', '#9CC6EF', '#B5A5D6', '#D6A5BD'],
764
+ ['#E76363', '#F7AD6B', '#FFD663', '#94BD7B', '#73A5AD', '#6BADDE', '#8C7BC6', '#C67BA5'],
765
+ ['#CE0000', '#E79439', '#EFC631', '#6BA54A', '#4A7B8C', '#3984C6', '#634AA5', '#A54A7B'],
766
+ ['#9C0000', '#B56308', '#BD9400', '#397B21', '#104A5A', '#085294', '#311873', '#731842'],
767
+ ['#630000', '#7B3900', '#846300', '#295218', '#083139', '#003163', '#21104A', '#4A1031']
768
+ ],
769
+
770
+ // fontSize
771
+ fontSizes: ['8', '9', '10', '11', '12', '14', '18', '24', '36'],
772
+
773
+ // lineHeight
774
+ lineHeights: ['1.0', '1.2', '1.4', '1.5', '1.6', '1.8', '2.0', '3.0'],
775
+
776
+ // callbacks
777
+ oninit: null, // initialize
778
+ onfocus: null, // editable has focus
779
+ onblur: null, // editable out of focus
780
+ onenter: null, // enter key pressed
781
+ onkeyup: null, // keyup
782
+ onkeydown: null, // keydown
783
+ onImageUpload: null, // imageUploadHandler
784
+ onImageUploadError: null, // imageUploadErrorHandler
785
+ onToolbarClick: null,
786
+
787
+ keyMap: {
788
+ pc: {
789
+ 'CTRL+Z': 'undo',
790
+ 'CTRL+Y': 'redo',
791
+ 'TAB': 'tab',
792
+ 'SHIFT+TAB': 'untab',
793
+ 'CTRL+B': 'bold',
794
+ 'CTRL+I': 'italic',
795
+ 'CTRL+U': 'underline',
796
+ 'CTRL+SHIFT+S': 'strikethrough',
797
+ 'CTRL+BACKSLASH': 'removeFormat',
798
+ 'CTRL+SHIFT+L': 'justifyLeft',
799
+ 'CTRL+SHIFT+E': 'justifyCenter',
800
+ 'CTRL+SHIFT+R': 'justifyRight',
801
+ 'CTRL+SHIFT+J': 'justifyFull',
802
+ 'CTRL+SHIFT+NUM7': 'insertUnorderedList',
803
+ 'CTRL+SHIFT+NUM8': 'insertOrderedList',
804
+ 'CTRL+LEFTBRACKET': 'outdent',
805
+ 'CTRL+RIGHTBRACKET': 'indent',
806
+ 'CTRL+NUM0': 'formatPara',
807
+ 'CTRL+NUM1': 'formatH1',
808
+ 'CTRL+NUM2': 'formatH2',
809
+ 'CTRL+NUM3': 'formatH3',
810
+ 'CTRL+NUM4': 'formatH4',
811
+ 'CTRL+NUM5': 'formatH5',
812
+ 'CTRL+NUM6': 'formatH6',
813
+ 'CTRL+ENTER': 'insertHorizontalRule'
814
+ },
815
+
816
+ mac: {
817
+ 'CMD+Z': 'undo',
818
+ 'CMD+SHIFT+Z': 'redo',
819
+ 'TAB': 'tab',
820
+ 'SHIFT+TAB': 'untab',
821
+ 'CMD+B': 'bold',
822
+ 'CMD+I': 'italic',
823
+ 'CMD+U': 'underline',
824
+ 'CMD+SHIFT+S': 'strikethrough',
825
+ 'CMD+BACKSLASH': 'removeFormat',
826
+ 'CMD+SHIFT+L': 'justifyLeft',
827
+ 'CMD+SHIFT+E': 'justifyCenter',
828
+ 'CMD+SHIFT+R': 'justifyRight',
829
+ 'CMD+SHIFT+J': 'justifyFull',
830
+ 'CMD+SHIFT+NUM7': 'insertUnorderedList',
831
+ 'CMD+SHIFT+NUM8': 'insertOrderedList',
832
+ 'CMD+LEFTBRACKET': 'outdent',
833
+ 'CMD+RIGHTBRACKET': 'indent',
834
+ 'CMD+NUM0': 'formatPara',
835
+ 'CMD+NUM1': 'formatH1',
836
+ 'CMD+NUM2': 'formatH2',
837
+ 'CMD+NUM3': 'formatH3',
838
+ 'CMD+NUM4': 'formatH4',
839
+ 'CMD+NUM5': 'formatH5',
840
+ 'CMD+NUM6': 'formatH6',
841
+ 'CMD+ENTER': 'insertHorizontalRule'
842
+ }
843
+ }
844
+ },
845
+
846
+ // default language: en-US
847
+ lang: {
848
+ 'en-US': {
849
+ font: {
850
+ bold: 'Bold',
851
+ italic: 'Italic',
852
+ underline: 'Underline',
853
+ strikethrough: 'Strikethrough',
854
+ clear: 'Remove Font Style',
855
+ height: 'Line Height',
856
+ name: 'Font Family',
857
+ size: 'Font Size'
858
+ },
859
+ image: {
860
+ image: 'Picture',
861
+ insert: 'Insert Image',
862
+ resizeFull: 'Resize Full',
863
+ resizeHalf: 'Resize Half',
864
+ resizeQuarter: 'Resize Quarter',
865
+ floatLeft: 'Float Left',
866
+ floatRight: 'Float Right',
867
+ floatNone: 'Float None',
868
+ dragImageHere: 'Drag an image here',
869
+ selectFromFiles: 'Select from files',
870
+ url: 'Image URL',
871
+ remove: 'Remove Image'
872
+ },
873
+ link: {
874
+ link: 'Link',
875
+ insert: 'Insert Link',
876
+ unlink: 'Unlink',
877
+ edit: 'Edit',
878
+ textToDisplay: 'Text to display',
879
+ url: 'To what URL should this link go?',
880
+ openInNewWindow: 'Open in new window'
881
+ },
882
+ video: {
883
+ video: 'Video',
884
+ videoLink: 'Video Link',
885
+ insert: 'Insert Video',
886
+ url: 'Video URL?',
887
+ providers: '(YouTube, Vimeo, Vine, Instagram, or DailyMotion)'
888
+ },
889
+ table: {
890
+ table: 'Table'
891
+ },
892
+ hr: {
893
+ insert: 'Insert Horizontal Rule'
894
+ },
895
+ style: {
896
+ style: 'Style',
897
+ normal: 'Normal',
898
+ blockquote: 'Quote',
899
+ pre: 'Code',
900
+ h1: 'Header 1',
901
+ h2: 'Header 2',
902
+ h3: 'Header 3',
903
+ h4: 'Header 4',
904
+ h5: 'Header 5',
905
+ h6: 'Header 6'
906
+ },
907
+ lists: {
908
+ unordered: 'Unordered list',
909
+ ordered: 'Ordered list'
910
+ },
911
+ options: {
912
+ help: 'Help',
913
+ fullscreen: 'Full Screen',
914
+ codeview: 'Code View'
915
+ },
916
+ paragraph: {
917
+ paragraph: 'Paragraph',
918
+ outdent: 'Outdent',
919
+ indent: 'Indent',
920
+ left: 'Align left',
921
+ center: 'Align center',
922
+ right: 'Align right',
923
+ justify: 'Justify full'
924
+ },
925
+ color: {
926
+ recent: 'Recent Color',
927
+ more: 'More Color',
928
+ background: 'BackColor',
929
+ foreground: 'FontColor',
930
+ transparent: 'Transparent',
931
+ setTransparent: 'Set transparent',
932
+ reset: 'Reset',
933
+ resetToDefault: 'Reset to default'
934
+ },
935
+ shortcut: {
936
+ shortcuts: 'Keyboard shortcuts',
937
+ close: 'Close',
938
+ textFormatting: 'Text formatting',
939
+ action: 'Action',
940
+ paragraphFormatting: 'Paragraph formatting',
941
+ documentStyle: 'Document Style'
942
+ },
943
+ history: {
944
+ undo: 'Undo',
945
+ redo: 'Redo'
946
+ }
947
+ }
948
+ }
949
+ };
950
+
951
+ /**
952
+ * Async functions which returns `Promise`
953
+ */
954
+ var async = (function () {
955
+ /**
956
+ * read contents of file as representing URL
957
+ *
958
+ * @param {File} file
959
+ * @return {Promise} - then: sDataUrl
960
+ */
961
+ var readFileAsDataURL = function (file) {
962
+ return $.Deferred(function (deferred) {
963
+ $.extend(new FileReader(), {
964
+ onload: function (e) {
965
+ var sDataURL = e.target.result;
966
+ deferred.resolve(sDataURL);
967
+ },
968
+ onerror: function () {
969
+ deferred.reject(this);
970
+ }
971
+ }).readAsDataURL(file);
972
+ }).promise();
973
+ };
974
+
975
+ /**
976
+ * create `<image>` from url string
977
+ *
978
+ * @param {String} sUrl
979
+ * @return {Promise} - then: $image
980
+ */
981
+ var createImage = function (sUrl) {
982
+ return $.Deferred(function (deferred) {
983
+ $('<img>').one('load', function () {
984
+ deferred.resolve($(this));
985
+ }).one('error abort', function () {
986
+ deferred.reject($(this));
987
+ }).css({
988
+ display: 'none'
989
+ }).appendTo(document.body).attr('src', sUrl);
990
+ }).promise();
991
+ };
992
+
993
+ return {
994
+ readFileAsDataURL: readFileAsDataURL,
995
+ createImage: createImage
996
+ };
997
+ })();
998
+
999
+ /**
1000
+ * Object for keycodes.
1001
+ */
1002
+ var key = {
1003
+ isEdit: function (keyCode) {
1004
+ return [8, 9, 13, 32].indexOf(keyCode) !== -1;
1005
+ },
1006
+ nameFromCode: {
1007
+ '8': 'BACKSPACE',
1008
+ '9': 'TAB',
1009
+ '13': 'ENTER',
1010
+ '32': 'SPACE',
1011
+
1012
+ // Number: 0-9
1013
+ '48': 'NUM0',
1014
+ '49': 'NUM1',
1015
+ '50': 'NUM2',
1016
+ '51': 'NUM3',
1017
+ '52': 'NUM4',
1018
+ '53': 'NUM5',
1019
+ '54': 'NUM6',
1020
+ '55': 'NUM7',
1021
+ '56': 'NUM8',
1022
+
1023
+ // Alphabet: a-z
1024
+ '66': 'B',
1025
+ '69': 'E',
1026
+ '73': 'I',
1027
+ '74': 'J',
1028
+ '75': 'K',
1029
+ '76': 'L',
1030
+ '82': 'R',
1031
+ '83': 'S',
1032
+ '85': 'U',
1033
+ '89': 'Y',
1034
+ '90': 'Z',
1035
+
1036
+ '191': 'SLASH',
1037
+ '219': 'LEFTBRACKET',
1038
+ '220': 'BACKSLASH',
1039
+ '221': 'RIGHTBRACKET'
1040
+ }
1041
+ };
1042
+
1043
+ /**
1044
+ * Style
1045
+ * @class
1046
+ */
1047
+ var Style = function () {
1048
+ /**
1049
+ * passing an array of style properties to .css()
1050
+ * will result in an object of property-value pairs.
1051
+ * (compability with version < 1.9)
1052
+ *
1053
+ * @param {jQuery} $obj
1054
+ * @param {Array} propertyNames - An array of one or more CSS properties.
1055
+ * @returns {Object}
1056
+ */
1057
+ var jQueryCSS = function ($obj, propertyNames) {
1058
+ if (agent.jqueryVersion < 1.9) {
1059
+ var result = {};
1060
+ $.each(propertyNames, function (idx, propertyName) {
1061
+ result[propertyName] = $obj.css(propertyName);
1062
+ });
1063
+ return result;
1064
+ }
1065
+ return $obj.css.call($obj, propertyNames);
1066
+ };
1067
+
1068
+ /**
1069
+ * paragraph level style
1070
+ *
1071
+ * @param {WrappedRange} rng
1072
+ * @param {Object} oStyle
1073
+ */
1074
+ this.stylePara = function (rng, oStyle) {
1075
+ $.each(rng.nodes(dom.isPara), function (idx, elPara) {
1076
+ $(elPara).css(oStyle);
1077
+ });
1078
+ };
1079
+
1080
+ /**
1081
+ * get current style on cursor
1082
+ *
1083
+ * @param {WrappedRange} rng
1084
+ * @param {Element} elTarget - target element on event
1085
+ * @return {Object} - object contains style properties.
1086
+ */
1087
+ this.current = function (rng, elTarget) {
1088
+ var $cont = $(dom.isText(rng.sc) ? rng.sc.parentNode : rng.sc);
1089
+ var properties = ['font-family', 'font-size', 'text-align', 'list-style-type', 'line-height'];
1090
+ var oStyle = jQueryCSS($cont, properties) || {};
1091
+
1092
+ oStyle['font-size'] = parseInt(oStyle['font-size'], 10);
1093
+
1094
+ // document.queryCommandState for toggle state
1095
+ oStyle['font-bold'] = document.queryCommandState('bold') ? 'bold' : 'normal';
1096
+ oStyle['font-italic'] = document.queryCommandState('italic') ? 'italic' : 'normal';
1097
+ oStyle['font-underline'] = document.queryCommandState('underline') ? 'underline' : 'normal';
1098
+ oStyle['font-strikethrough'] = document.queryCommandState('strikeThrough') ? 'strikethrough' : 'normal';
1099
+ oStyle['font-superscript'] = document.queryCommandState('superscript') ? 'superscript' : 'normal';
1100
+ oStyle['font-subscript'] = document.queryCommandState('subscript') ? 'subscript' : 'normal';
1101
+
1102
+ // list-style-type to list-style(unordered, ordered)
1103
+ if (!rng.isOnList()) {
1104
+ oStyle['list-style'] = 'none';
1105
+ } else {
1106
+ var aOrderedType = ['circle', 'disc', 'disc-leading-zero', 'square'];
1107
+ var bUnordered = $.inArray(oStyle['list-style-type'], aOrderedType) > -1;
1108
+ oStyle['list-style'] = bUnordered ? 'unordered' : 'ordered';
1109
+ }
1110
+
1111
+ var elPara = dom.ancestor(rng.sc, dom.isPara);
1112
+ if (elPara && elPara.style['line-height']) {
1113
+ oStyle['line-height'] = elPara.style.lineHeight;
1114
+ } else {
1115
+ var lineHeight = parseInt(oStyle['line-height'], 10) / parseInt(oStyle['font-size'], 10);
1116
+ oStyle['line-height'] = lineHeight.toFixed(1);
1117
+ }
1118
+
1119
+ oStyle.image = dom.isImg(elTarget) && elTarget;
1120
+ oStyle.anchor = rng.isOnAnchor() && dom.ancestor(rng.sc, dom.isAnchor);
1121
+ oStyle.aAncestor = dom.listAncestor(rng.sc, dom.isEditable);
1122
+ oStyle.range = rng;
1123
+
1124
+ return oStyle;
1125
+ };
1126
+ };
1127
+
1128
+ /**
1129
+ * range module
1130
+ */
1131
+ var range = (function () {
1132
+ var bW3CRangeSupport = !!document.createRange;
1133
+
1134
+ /**
1135
+ * return boundaryPoint from TextRange, inspired by Andy Na's HuskyRange.js
1136
+ * @param {TextRange} textRange
1137
+ * @param {Boolean} bStart
1138
+ * @return {BoundaryPoint}
1139
+ */
1140
+ var textRange2bp = function (textRange, bStart) {
1141
+ var elCont = textRange.parentElement(), nOffset;
1142
+
1143
+ var tester = document.body.createTextRange(), elPrevCont;
1144
+ var aChild = list.from(elCont.childNodes);
1145
+ for (nOffset = 0; nOffset < aChild.length; nOffset++) {
1146
+ if (dom.isText(aChild[nOffset])) { continue; }
1147
+ tester.moveToElementText(aChild[nOffset]);
1148
+ if (tester.compareEndPoints('StartToStart', textRange) >= 0) { break; }
1149
+ elPrevCont = aChild[nOffset];
1150
+ }
1151
+
1152
+ if (nOffset !== 0 && dom.isText(aChild[nOffset - 1])) {
1153
+ var textRangeStart = document.body.createTextRange(), elCurText = null;
1154
+ textRangeStart.moveToElementText(elPrevCont || elCont);
1155
+ textRangeStart.collapse(!elPrevCont);
1156
+ elCurText = elPrevCont ? elPrevCont.nextSibling : elCont.firstChild;
1157
+
1158
+ var pointTester = textRange.duplicate();
1159
+ pointTester.setEndPoint('StartToStart', textRangeStart);
1160
+ var nTextCount = pointTester.text.replace(/[\r\n]/g, '').length;
1161
+
1162
+ while (nTextCount > elCurText.nodeValue.length && elCurText.nextSibling) {
1163
+ nTextCount -= elCurText.nodeValue.length;
1164
+ elCurText = elCurText.nextSibling;
1165
+ }
1166
+
1167
+ /* jshint ignore:start */
1168
+ var sDummy = elCurText.nodeValue; //enforce IE to re-reference elCurText, hack
1169
+ /* jshint ignore:end */
1170
+
1171
+ if (bStart && elCurText.nextSibling && dom.isText(elCurText.nextSibling) &&
1172
+ nTextCount === elCurText.nodeValue.length) {
1173
+ nTextCount -= elCurText.nodeValue.length;
1174
+ elCurText = elCurText.nextSibling;
1175
+ }
1176
+
1177
+ elCont = elCurText;
1178
+ nOffset = nTextCount;
1179
+ }
1180
+
1181
+ return {cont: elCont, offset: nOffset};
1182
+ };
1183
+
1184
+ /**
1185
+ * return TextRange from boundary point (inspired by google closure-library)
1186
+ * @param {BoundaryPoint} bp
1187
+ * @return {TextRange}
1188
+ */
1189
+ var bp2textRange = function (bp) {
1190
+ var textRangeInfo = function (elCont, nOffset) {
1191
+ var elNode, bCollapseToStart;
1192
+
1193
+ if (dom.isText(elCont)) {
1194
+ var aPrevText = dom.listPrev(elCont, func.not(dom.isText));
1195
+ var elPrevCont = list.last(aPrevText).previousSibling;
1196
+ elNode = elPrevCont || elCont.parentNode;
1197
+ nOffset += list.sum(list.tail(aPrevText), dom.length);
1198
+ bCollapseToStart = !elPrevCont;
1199
+ } else {
1200
+ elNode = elCont.childNodes[nOffset] || elCont;
1201
+ if (dom.isText(elNode)) {
1202
+ return textRangeInfo(elNode, nOffset);
1203
+ }
1204
+
1205
+ nOffset = 0;
1206
+ bCollapseToStart = false;
1207
+ }
1208
+
1209
+ return {cont: elNode, collapseToStart: bCollapseToStart, offset: nOffset};
1210
+ };
1211
+
1212
+ var textRange = document.body.createTextRange();
1213
+ var info = textRangeInfo(bp.cont, bp.offset);
1214
+
1215
+ textRange.moveToElementText(info.cont);
1216
+ textRange.collapse(info.collapseToStart);
1217
+ textRange.moveStart('character', info.offset);
1218
+ return textRange;
1219
+ };
1220
+
1221
+ /**
1222
+ * Wrapped Range
1223
+ *
1224
+ * @param {Element} sc - start container
1225
+ * @param {Number} so - start offset
1226
+ * @param {Element} ec - end container
1227
+ * @param {Number} eo - end offset
1228
+ */
1229
+ var WrappedRange = function (sc, so, ec, eo) {
1230
+ this.sc = sc;
1231
+ this.so = so;
1232
+ this.ec = ec;
1233
+ this.eo = eo;
1234
+
1235
+ // nativeRange: get nativeRange from sc, so, ec, eo
1236
+ var nativeRange = function () {
1237
+ if (bW3CRangeSupport) {
1238
+ var w3cRange = document.createRange();
1239
+ w3cRange.setStart(sc, so);
1240
+ w3cRange.setEnd(ec, eo);
1241
+ return w3cRange;
1242
+ } else {
1243
+ var textRange = bp2textRange({cont: sc, offset: so});
1244
+ textRange.setEndPoint('EndToEnd', bp2textRange({cont: ec, offset: eo}));
1245
+ return textRange;
1246
+ }
1247
+ };
1248
+
1249
+ /**
1250
+ * select update visible range
1251
+ */
1252
+ this.select = function () {
1253
+ var nativeRng = nativeRange();
1254
+ if (bW3CRangeSupport) {
1255
+ var selection = document.getSelection();
1256
+ if (selection.rangeCount > 0) { selection.removeAllRanges(); }
1257
+ selection.addRange(nativeRng);
1258
+ } else {
1259
+ nativeRng.select();
1260
+ }
1261
+ };
1262
+
1263
+ /**
1264
+ * returns matched nodes on range
1265
+ *
1266
+ * @param {Function} pred - predicate function
1267
+ * @return {Element[]}
1268
+ */
1269
+ this.nodes = function (pred) {
1270
+ var aNode = dom.listBetween(sc, ec);
1271
+ var aMatched = list.compact($.map(aNode, function (node) {
1272
+ return dom.ancestor(node, pred);
1273
+ }));
1274
+ return $.map(list.clusterBy(aMatched, func.eq2), list.head);
1275
+ };
1276
+
1277
+ /**
1278
+ * returns commonAncestor of range
1279
+ * @return {Element} - commonAncestor
1280
+ */
1281
+ this.commonAncestor = function () {
1282
+ return dom.commonAncestor(sc, ec);
1283
+ };
1284
+
1285
+ /**
1286
+ * makeIsOn: return isOn(pred) function
1287
+ */
1288
+ var makeIsOn = function (pred) {
1289
+ return function () {
1290
+ var elAncestor = dom.ancestor(sc, pred);
1291
+ return !!elAncestor && (elAncestor === dom.ancestor(ec, pred));
1292
+ };
1293
+ };
1294
+
1295
+ // isOnEditable: judge whether range is on editable or not
1296
+ this.isOnEditable = makeIsOn(dom.isEditable);
1297
+ // isOnList: judge whether range is on list node or not
1298
+ this.isOnList = makeIsOn(dom.isList);
1299
+ // isOnAnchor: judge whether range is on anchor node or not
1300
+ this.isOnAnchor = makeIsOn(dom.isAnchor);
1301
+ // isOnAnchor: judge whether range is on cell node or not
1302
+ this.isOnCell = makeIsOn(dom.isCell);
1303
+ // isCollapsed: judge whether range was collapsed
1304
+ this.isCollapsed = function () { return sc === ec && so === eo; };
1305
+
1306
+ /**
1307
+ * insert node at current cursor
1308
+ * @param {Element} node
1309
+ */
1310
+ this.insertNode = function (node) {
1311
+ var nativeRng = nativeRange();
1312
+ if (bW3CRangeSupport) {
1313
+ nativeRng.insertNode(node);
1314
+ } else {
1315
+ nativeRng.pasteHTML(node.outerHTML); // NOTE: missing node reference.
1316
+ }
1317
+ };
1318
+
1319
+ this.toString = function () {
1320
+ var nativeRng = nativeRange();
1321
+ return bW3CRangeSupport ? nativeRng.toString() : nativeRng.text;
1322
+ };
1323
+
1324
+ /**
1325
+ * create offsetPath bookmark
1326
+ * @param {Element} elEditable
1327
+ */
1328
+ this.bookmark = function (elEditable) {
1329
+ return {
1330
+ s: { path: dom.makeOffsetPath(elEditable, sc), offset: so },
1331
+ e: { path: dom.makeOffsetPath(elEditable, ec), offset: eo }
1332
+ };
1333
+ };
1334
+
1335
+ /**
1336
+ * getClientRects
1337
+ * @return {Rect[]}
1338
+ */
1339
+ this.getClientRects = function () {
1340
+ var nativeRng = nativeRange();
1341
+ return nativeRng.getClientRects();
1342
+ };
1343
+ };
1344
+
1345
+ return {
1346
+ /**
1347
+ * create Range Object From arguments or Browser Selection
1348
+ *
1349
+ * @param {Element} sc - start container
1350
+ * @param {Number} so - start offset
1351
+ * @param {Element} ec - end container
1352
+ * @param {Number} eo - end offset
1353
+ */
1354
+ create : function (sc, so, ec, eo) {
1355
+ if (arguments.length === 0) { // from Browser Selection
1356
+ if (bW3CRangeSupport) { // webkit, firefox
1357
+ var selection = document.getSelection();
1358
+ if (selection.rangeCount === 0) { return null; }
1359
+
1360
+ var nativeRng = selection.getRangeAt(0);
1361
+ sc = nativeRng.startContainer;
1362
+ so = nativeRng.startOffset;
1363
+ ec = nativeRng.endContainer;
1364
+ eo = nativeRng.endOffset;
1365
+ } else { // IE8: TextRange
1366
+ var textRange = document.selection.createRange();
1367
+ var textRangeEnd = textRange.duplicate();
1368
+ textRangeEnd.collapse(false);
1369
+ var textRangeStart = textRange;
1370
+ textRangeStart.collapse(true);
1371
+
1372
+ var bpStart = textRange2bp(textRangeStart, true),
1373
+ bpEnd = textRange2bp(textRangeEnd, false);
1374
+
1375
+ sc = bpStart.cont;
1376
+ so = bpStart.offset;
1377
+ ec = bpEnd.cont;
1378
+ eo = bpEnd.offset;
1379
+ }
1380
+ } else if (arguments.length === 2) { //collapsed
1381
+ ec = sc;
1382
+ eo = so;
1383
+ }
1384
+ return new WrappedRange(sc, so, ec, eo);
1385
+ },
1386
+
1387
+ /**
1388
+ * create WrappedRange from node
1389
+ *
1390
+ * @param {Element} node
1391
+ * @return {WrappedRange}
1392
+ */
1393
+ createFromNode: function (node) {
1394
+ return this.create(node, 0, node, 1);
1395
+ },
1396
+
1397
+ /**
1398
+ * create WrappedRange from Bookmark
1399
+ *
1400
+ * @param {Element} elEditable
1401
+ * @param {Obkect} bookmark
1402
+ * @return {WrappedRange}
1403
+ */
1404
+ createFromBookmark : function (elEditable, bookmark) {
1405
+ var sc = dom.fromOffsetPath(elEditable, bookmark.s.path);
1406
+ var so = bookmark.s.offset;
1407
+ var ec = dom.fromOffsetPath(elEditable, bookmark.e.path);
1408
+ var eo = bookmark.e.offset;
1409
+ return new WrappedRange(sc, so, ec, eo);
1410
+ }
1411
+ };
1412
+ })();
1413
+
1414
+ /**
1415
+ * Table
1416
+ * @class
1417
+ */
1418
+ var Table = function () {
1419
+ /**
1420
+ * handle tab key
1421
+ *
1422
+ * @param {WrappedRange} rng
1423
+ * @param {Boolean} bShift
1424
+ */
1425
+ this.tab = function (rng, bShift) {
1426
+ var elCell = dom.ancestor(rng.commonAncestor(), dom.isCell);
1427
+ var elTable = dom.ancestor(elCell, dom.isTable);
1428
+ var aCell = dom.listDescendant(elTable, dom.isCell);
1429
+
1430
+ var elNext = list[bShift ? 'prev' : 'next'](aCell, elCell);
1431
+ if (elNext) {
1432
+ range.create(elNext, 0).select();
1433
+ }
1434
+ };
1435
+
1436
+ /**
1437
+ * create empty table element
1438
+ *
1439
+ * @param {Number} nRow
1440
+ * @param {Number} nCol
1441
+ */
1442
+ this.createTable = function (nCol, nRow) {
1443
+ var aTD = [], sTD;
1444
+ for (var idxCol = 0; idxCol < nCol; idxCol++) {
1445
+ aTD.push('<td>' + dom.blank + '</td>');
1446
+ }
1447
+ sTD = aTD.join('');
1448
+
1449
+ var aTR = [], sTR;
1450
+ for (var idxRow = 0; idxRow < nRow; idxRow++) {
1451
+ aTR.push('<tr>' + sTD + '</tr>');
1452
+ }
1453
+ sTR = aTR.join('');
1454
+ var sTable = '<table class="table table-bordered">' + sTR + '</table>';
1455
+
1456
+ return $(sTable)[0];
1457
+ };
1458
+ };
1459
+
1460
+ /**
1461
+ * Editor
1462
+ * @class
1463
+ */
1464
+ var Editor = function () {
1465
+
1466
+ var style = new Style();
1467
+ var table = new Table();
1468
+
1469
+ /**
1470
+ * save current range
1471
+ *
1472
+ * @param {jQuery} $editable
1473
+ */
1474
+ this.saveRange = function ($editable) {
1475
+ $editable.data('range', range.create());
1476
+ };
1477
+
1478
+ /**
1479
+ * restore lately range
1480
+ *
1481
+ * @param {jQuery} $editable
1482
+ */
1483
+ this.restoreRange = function ($editable) {
1484
+ var rng = $editable.data('range');
1485
+ if (rng) { rng.select(); }
1486
+ };
1487
+
1488
+ /**
1489
+ * current style
1490
+ * @param {Element} elTarget
1491
+ */
1492
+ this.currentStyle = function (elTarget) {
1493
+ var rng = range.create();
1494
+ return rng.isOnEditable() && style.current(rng, elTarget);
1495
+ };
1496
+
1497
+ /**
1498
+ * undo
1499
+ * @param {jQuery} $editable
1500
+ */
1501
+ this.undo = function ($editable) {
1502
+ $editable.data('NoteHistory').undo($editable);
1503
+ };
1504
+
1505
+ /**
1506
+ * redo
1507
+ * @param {jQuery} $editable
1508
+ */
1509
+ this.redo = function ($editable) {
1510
+ $editable.data('NoteHistory').redo($editable);
1511
+ };
1512
+
1513
+ /**
1514
+ * record Undo
1515
+ * @param {jQuery} $editable
1516
+ */
1517
+ var recordUndo = this.recordUndo = function ($editable) {
1518
+ $editable.data('NoteHistory').recordUndo($editable);
1519
+ };
1520
+
1521
+ /* jshint ignore:start */
1522
+ // native commands(with execCommand), generate function for execCommand
1523
+ var aCmd = ['bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript',
1524
+ 'justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull',
1525
+ 'insertOrderedList', 'insertUnorderedList',
1526
+ 'indent', 'outdent', 'formatBlock', 'removeFormat',
1527
+ 'backColor', 'foreColor', 'insertHorizontalRule', 'fontName'];
1528
+
1529
+ for (var idx = 0, len = aCmd.length; idx < len; idx ++) {
1530
+ this[aCmd[idx]] = (function (sCmd) {
1531
+ return function ($editable, sValue) {
1532
+ recordUndo($editable);
1533
+ document.execCommand(sCmd, false, sValue);
1534
+ };
1535
+ })(aCmd[idx]);
1536
+ }
1537
+ /* jshint ignore:end */
1538
+
1539
+ /**
1540
+ * @param {jQuery} $editable
1541
+ * @param {WrappedRange} rng
1542
+ * @param {Number} nTabsize
1543
+ */
1544
+ var insertTab = function ($editable, rng, nTabsize) {
1545
+ recordUndo($editable);
1546
+ var sNbsp = new Array(nTabsize + 1).join('&nbsp;');
1547
+ rng.insertNode($('<span id="noteTab">' + sNbsp + '</span>')[0]);
1548
+ var $tab = $('#noteTab').removeAttr('id');
1549
+ rng = range.create($tab[0], 1);
1550
+ rng.select();
1551
+ dom.remove($tab[0]);
1552
+ };
1553
+
1554
+ /**
1555
+ * handle tab key
1556
+ * @param {jQuery} $editable
1557
+ * @param {Number} nTabsize
1558
+ * @param {Boolean} bShift
1559
+ */
1560
+ this.tab = function ($editable, options) {
1561
+ var rng = range.create();
1562
+ if (rng.isCollapsed() && rng.isOnCell()) {
1563
+ table.tab(rng);
1564
+ } else {
1565
+ insertTab($editable, rng, options.tabsize);
1566
+ }
1567
+ };
1568
+
1569
+ /**
1570
+ * handle shift+tab key
1571
+ */
1572
+ this.untab = function () {
1573
+ var rng = range.create();
1574
+ if (rng.isCollapsed() && rng.isOnCell()) {
1575
+ table.tab(rng, true);
1576
+ }
1577
+ };
1578
+
1579
+ /**
1580
+ * insert image
1581
+ *
1582
+ * @param {jQuery} $editable
1583
+ * @param {String} sUrl
1584
+ */
1585
+ this.insertImage = function ($editable, sUrl) {
1586
+ async.createImage(sUrl).then(function ($image) {
1587
+ recordUndo($editable);
1588
+ $image.css({
1589
+ display: '',
1590
+ width: Math.min($editable.width(), $image.width())
1591
+ });
1592
+ range.create().insertNode($image[0]);
1593
+ }).fail(function () {
1594
+ var callbacks = $editable.data('callbacks');
1595
+ if (callbacks.onImageUploadError) {
1596
+ callbacks.onImageUploadError();
1597
+ }
1598
+ });
1599
+ };
1600
+
1601
+ /**
1602
+ * insert video
1603
+ * @param {jQuery} $editable
1604
+ * @param {String} sUrl
1605
+ */
1606
+ this.insertVideo = function ($editable, sUrl) {
1607
+ recordUndo($editable);
1608
+
1609
+ // video url patterns(youtube, instagram, vimeo, dailymotion)
1610
+ var ytRegExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
1611
+ var ytMatch = sUrl.match(ytRegExp);
1612
+
1613
+ var igRegExp = /\/\/instagram.com\/p\/(.[a-zA-Z0-9]*)/;
1614
+ var igMatch = sUrl.match(igRegExp);
1615
+
1616
+ var vRegExp = /\/\/vine.co\/v\/(.[a-zA-Z0-9]*)/;
1617
+ var vMatch = sUrl.match(vRegExp);
1618
+
1619
+ var vimRegExp = /\/\/(player.)?vimeo.com\/([a-z]*\/)*([0-9]{6,11})[?]?.*/;
1620
+ var vimMatch = sUrl.match(vimRegExp);
1621
+
1622
+ var dmRegExp = /.+dailymotion.com\/(video|hub)\/([^_]+)[^#]*(#video=([^_&]+))?/;
1623
+ var dmMatch = sUrl.match(dmRegExp);
1624
+
1625
+ var $video;
1626
+ if (ytMatch && ytMatch[2].length === 11) {
1627
+ var youtubeId = ytMatch[2];
1628
+ $video = $('<iframe>')
1629
+ .attr('src', '//www.youtube.com/embed/' + youtubeId)
1630
+ .attr('width', '640').attr('height', '360');
1631
+ } else if (igMatch && igMatch[0].length > 0) {
1632
+ $video = $('<iframe>')
1633
+ .attr('src', igMatch[0] + '/embed/')
1634
+ .attr('width', '612').attr('height', '710')
1635
+ .attr('scrolling', 'no')
1636
+ .attr('allowtransparency', 'true');
1637
+ } else if (vMatch && vMatch[0].length > 0) {
1638
+ $video = $('<iframe>')
1639
+ .attr('src', vMatch[0] + '/embed/simple')
1640
+ .attr('width', '600').attr('height', '600')
1641
+ .attr('class', 'vine-embed');
1642
+ } else if (vimMatch && vimMatch[3].length > 0) {
1643
+ $video = $('<iframe webkitallowfullscreen mozallowfullscreen allowfullscreen>')
1644
+ .attr('src', '//player.vimeo.com/video/' + vimMatch[3])
1645
+ .attr('width', '640').attr('height', '360');
1646
+ } else if (dmMatch && dmMatch[2].length > 0) {
1647
+ $video = $('<iframe>')
1648
+ .attr('src', '//www.dailymotion.com/embed/video/' + dmMatch[2])
1649
+ .attr('width', '640').attr('height', '360');
1650
+ } else {
1651
+ // this is not a known video link. Now what, Cat? Now what?
1652
+ }
1653
+
1654
+ if ($video) {
1655
+ $video.attr('frameborder', 0);
1656
+ range.create().insertNode($video[0]);
1657
+ }
1658
+ };
1659
+
1660
+ /**
1661
+ * formatBlock
1662
+ *
1663
+ * @param {jQuery} $editable
1664
+ * @param {String} sTagName
1665
+ */
1666
+ this.formatBlock = function ($editable, sTagName) {
1667
+ recordUndo($editable);
1668
+ sTagName = agent.bMSIE ? '<' + sTagName + '>' : sTagName;
1669
+ document.execCommand('FormatBlock', false, sTagName);
1670
+ };
1671
+
1672
+ this.formatPara = function ($editable) {
1673
+ this.formatBlock($editable, 'P');
1674
+ };
1675
+
1676
+ /* jshint ignore:start */
1677
+ for (var idx = 1; idx <= 6; idx ++) {
1678
+ this['formatH' + idx] = function (idx) {
1679
+ return function ($editable) {
1680
+ this.formatBlock($editable, 'H' + idx);
1681
+ };
1682
+ }(idx);
1683
+ };
1684
+ /* jshint ignore:end */
1685
+
1686
+ /**
1687
+ * fontsize
1688
+ * FIXME: Still buggy
1689
+ *
1690
+ * @param {jQuery} $editable
1691
+ * @param {String} sValue - px
1692
+ */
1693
+ this.fontSize = function ($editable, sValue) {
1694
+ recordUndo($editable);
1695
+ document.execCommand('fontSize', false, 3);
1696
+ if (agent.bFF) {
1697
+ // firefox: <font size="3"> to <span style='font-size={sValue}px;'>, buggy
1698
+ $editable.find('font[size=3]').removeAttr('size').css('font-size', sValue + 'px');
1699
+ } else {
1700
+ // chrome: <span style="font-size: medium"> to <span style='font-size={sValue}px;'>
1701
+ $editable.find('span').filter(function () {
1702
+ return this.style.fontSize === 'medium';
1703
+ }).css('font-size', sValue + 'px');
1704
+ }
1705
+ };
1706
+
1707
+ /**
1708
+ * lineHeight
1709
+ * @param {jQuery} $editable
1710
+ * @param {String} sValue
1711
+ */
1712
+ this.lineHeight = function ($editable, sValue) {
1713
+ recordUndo($editable);
1714
+ style.stylePara(range.create(), {lineHeight: sValue});
1715
+ };
1716
+
1717
+ /**
1718
+ * unlink
1719
+ * @param {jQuery} $editable
1720
+ */
1721
+ this.unlink = function ($editable) {
1722
+ var rng = range.create();
1723
+ if (rng.isOnAnchor()) {
1724
+ recordUndo($editable);
1725
+ var elAnchor = dom.ancestor(rng.sc, dom.isAnchor);
1726
+ rng = range.createFromNode(elAnchor);
1727
+ rng.select();
1728
+ document.execCommand('unlink');
1729
+ }
1730
+ };
1731
+
1732
+ /**
1733
+ * create link
1734
+ *
1735
+ * @param {jQuery} $editable
1736
+ * @param {String} sLinkUrl
1737
+ * @param {Boolean} bNewWindow
1738
+ */
1739
+ this.createLink = function ($editable, sLinkText, sLinkUrl, bNewWindow) {
1740
+ var rng = range.create();
1741
+ recordUndo($editable);
1742
+
1743
+ // protocol
1744
+ var sLinkUrlWithProtocol = sLinkUrl;
1745
+ if (sLinkUrl.indexOf('@') !== -1 && sLinkUrl.indexOf(':') === -1) {
1746
+ sLinkUrlWithProtocol = 'mailto:' + sLinkUrl;
1747
+ } else if (sLinkUrl.indexOf('://') === -1) {
1748
+ sLinkUrlWithProtocol = 'http://' + sLinkUrl;
1749
+ }
1750
+
1751
+ // createLink when range collapsed (IE, Firefox).
1752
+ if ((agent.bMSIE || agent.bFF) && rng.isCollapsed()) {
1753
+ rng.insertNode($('<A id="linkAnchor">' + sLinkText + '</A>')[0]);
1754
+ var $anchor = $('#linkAnchor').attr('href', sLinkUrlWithProtocol).removeAttr('id');
1755
+ rng = range.createFromNode($anchor[0]);
1756
+ rng.select();
1757
+ } else {
1758
+ document.execCommand('createlink', false, sLinkUrlWithProtocol);
1759
+ }
1760
+
1761
+ // target
1762
+ $.each(rng.nodes(dom.isAnchor), function (idx, elAnchor) {
1763
+ // update link text
1764
+ $(elAnchor).html(sLinkText);
1765
+ if (bNewWindow) {
1766
+ $(elAnchor).attr('target', '_blank');
1767
+ } else {
1768
+ $(elAnchor).removeAttr('target');
1769
+ }
1770
+ });
1771
+ };
1772
+
1773
+ /**
1774
+ * get link info
1775
+ *
1776
+ * @return {Promise}
1777
+ */
1778
+ this.getLinkInfo = function () {
1779
+ var rng = range.create();
1780
+ var bNewWindow = true;
1781
+ var sUrl = '';
1782
+
1783
+ // If range on anchor expand range on anchor(for edit link).
1784
+ if (rng.isOnAnchor()) {
1785
+ var elAnchor = dom.ancestor(rng.sc, dom.isAnchor);
1786
+ rng = range.createFromNode(elAnchor);
1787
+ bNewWindow = $(elAnchor).attr('target') === '_blank';
1788
+ sUrl = elAnchor.href;
1789
+ }
1790
+
1791
+ return {
1792
+ text: rng.toString(),
1793
+ url: sUrl,
1794
+ newWindow: bNewWindow
1795
+ };
1796
+ };
1797
+
1798
+ /**
1799
+ * get video info
1800
+ *
1801
+ * @return {Object}
1802
+ */
1803
+ this.getVideoInfo = function () {
1804
+ var rng = range.create();
1805
+
1806
+ if (rng.isOnAnchor()) {
1807
+ var elAnchor = dom.ancestor(rng.sc, dom.isAnchor);
1808
+ rng = range.createFromNode(elAnchor);
1809
+ }
1810
+
1811
+ return {
1812
+ text: rng.toString()
1813
+ };
1814
+ };
1815
+
1816
+ this.color = function ($editable, sObjColor) {
1817
+ var oColor = JSON.parse(sObjColor);
1818
+ var foreColor = oColor.foreColor, backColor = oColor.backColor;
1819
+
1820
+ recordUndo($editable);
1821
+ if (foreColor) { document.execCommand('foreColor', false, foreColor); }
1822
+ if (backColor) { document.execCommand('backColor', false, backColor); }
1823
+ };
1824
+
1825
+ this.insertTable = function ($editable, sDim) {
1826
+ recordUndo($editable);
1827
+ var aDim = sDim.split('x');
1828
+ range.create().insertNode(table.createTable(aDim[0], aDim[1]));
1829
+ };
1830
+
1831
+ /**
1832
+ * @param {jQuery} $editable
1833
+ * @param {String} sValue
1834
+ * @param {jQuery} $target
1835
+ */
1836
+ this.floatMe = function ($editable, sValue, $target) {
1837
+ recordUndo($editable);
1838
+ $target.css('float', sValue);
1839
+ };
1840
+
1841
+ /**
1842
+ * resize overlay element
1843
+ * @param {jQuery} $editable
1844
+ * @param {String} sValue
1845
+ * @param {jQuery} $target - target element
1846
+ */
1847
+ this.resize = function ($editable, sValue, $target) {
1848
+ recordUndo($editable);
1849
+
1850
+ $target.css({
1851
+ width: $editable.width() * sValue + 'px',
1852
+ height: ''
1853
+ });
1854
+ };
1855
+
1856
+ /**
1857
+ * @param {Position} pos
1858
+ * @param {jQuery} $target - target element
1859
+ * @param {Boolean} [bKeepRatio] - keep ratio
1860
+ */
1861
+ this.resizeTo = function (pos, $target, bKeepRatio) {
1862
+ var szImage;
1863
+ if (bKeepRatio) {
1864
+ var newRatio = pos.y / pos.x;
1865
+ var ratio = $target.data('ratio');
1866
+ szImage = {
1867
+ width: ratio > newRatio ? pos.x : pos.y / ratio,
1868
+ height: ratio > newRatio ? pos.x * ratio : pos.y
1869
+ };
1870
+ } else {
1871
+ szImage = {
1872
+ width: pos.x,
1873
+ height: pos.y
1874
+ };
1875
+ }
1876
+
1877
+ $target.css(szImage);
1878
+ };
1879
+
1880
+ /**
1881
+ * remove media object
1882
+ *
1883
+ * @param {jQuery} $editable
1884
+ * @param {String} sValue - dummy argument (for keep interface)
1885
+ * @param {jQuery} $target - target element
1886
+ */
1887
+ this.removeMedia = function ($editable, sValue, $target) {
1888
+ recordUndo($editable);
1889
+ $target.detach();
1890
+ };
1891
+ };
1892
+
1893
+ /**
1894
+ * History
1895
+ * @class
1896
+ */
1897
+ var History = function () {
1898
+ var aUndo = [], aRedo = [];
1899
+
1900
+ var makeSnap = function ($editable) {
1901
+ var elEditable = $editable[0], rng = range.create();
1902
+ return {
1903
+ contents: $editable.html(),
1904
+ bookmark: rng.bookmark(elEditable),
1905
+ scrollTop: $editable.scrollTop()
1906
+ };
1907
+ };
1908
+
1909
+ var applySnap = function ($editable, oSnap) {
1910
+ $editable.html(oSnap.contents).scrollTop(oSnap.scrollTop);
1911
+ range.createFromBookmark($editable[0], oSnap.bookmark).select();
1912
+ };
1913
+
1914
+ this.undo = function ($editable) {
1915
+ var oSnap = makeSnap($editable);
1916
+ if (aUndo.length === 0) { return; }
1917
+ applySnap($editable, aUndo.pop());
1918
+ aRedo.push(oSnap);
1919
+ };
1920
+
1921
+ this.redo = function ($editable) {
1922
+ var oSnap = makeSnap($editable);
1923
+ if (aRedo.length === 0) { return; }
1924
+ applySnap($editable, aRedo.pop());
1925
+ aUndo.push(oSnap);
1926
+ };
1927
+
1928
+ this.recordUndo = function ($editable) {
1929
+ aRedo = [];
1930
+ aUndo.push(makeSnap($editable));
1931
+ };
1932
+ };
1933
+
1934
+ /**
1935
+ * Button
1936
+ */
1937
+ var Button = function () {
1938
+ /**
1939
+ * update button status
1940
+ *
1941
+ * @param {jQuery} $container
1942
+ * @param {Object} oStyle
1943
+ */
1944
+ this.update = function ($container, oStyle) {
1945
+ /**
1946
+ * handle dropdown's check mark (for fontname, fontsize, lineHeight).
1947
+ * @param {jQuery} $btn
1948
+ * @param {Number} nValue
1949
+ */
1950
+ var checkDropdownMenu = function ($btn, nValue) {
1951
+ $btn.find('.dropdown-menu li a').each(function () {
1952
+ // always compare string to avoid creating another func.
1953
+ var bChecked = ($(this).data('value') + '') === (nValue + '');
1954
+ this.className = bChecked ? 'checked' : '';
1955
+ });
1956
+ };
1957
+
1958
+ /**
1959
+ * update button state(active or not).
1960
+ *
1961
+ * @param {String} sSelector
1962
+ * @param {Function} pred
1963
+ */
1964
+ var btnState = function (sSelector, pred) {
1965
+ var $btn = $container.find(sSelector);
1966
+ $btn.toggleClass('active', pred());
1967
+ };
1968
+
1969
+ // fontname
1970
+ var $fontname = $container.find('.note-fontname');
1971
+ if ($fontname.length > 0) {
1972
+ var selectedFont = oStyle['font-family'];
1973
+ if (!!selectedFont) {
1974
+ selectedFont = list.head(selectedFont.split(','));
1975
+ selectedFont = selectedFont.replace(/\'/g, '');
1976
+ $fontname.find('.note-current-fontname').text(selectedFont);
1977
+ checkDropdownMenu($fontname, selectedFont);
1978
+ }
1979
+ }
1980
+
1981
+ // fontsize
1982
+ var $fontsize = $container.find('.note-fontsize');
1983
+ $fontsize.find('.note-current-fontsize').text(oStyle['font-size']);
1984
+ checkDropdownMenu($fontsize, parseFloat(oStyle['font-size']));
1985
+
1986
+ // lineheight
1987
+ var $lineHeight = $container.find('.note-height');
1988
+ checkDropdownMenu($lineHeight, parseFloat(oStyle['line-height']));
1989
+
1990
+ btnState('button[data-event="bold"]', function () {
1991
+ return oStyle['font-bold'] === 'bold';
1992
+ });
1993
+ btnState('button[data-event="italic"]', function () {
1994
+ return oStyle['font-italic'] === 'italic';
1995
+ });
1996
+ btnState('button[data-event="underline"]', function () {
1997
+ return oStyle['font-underline'] === 'underline';
1998
+ });
1999
+ btnState('button[data-event="strikethrough"]', function () {
2000
+ return oStyle['font-strikethrough'] === 'strikethrough';
2001
+ });
2002
+ btnState('button[data-event="superscript"]', function () {
2003
+ return oStyle['font-superscript'] === 'superscript';
2004
+ });
2005
+ btnState('button[data-event="subscript"]', function () {
2006
+ return oStyle['font-subscript'] === 'subscript';
2007
+ });
2008
+ btnState('button[data-event="justifyLeft"]', function () {
2009
+ return oStyle['text-align'] === 'left' || oStyle['text-align'] === 'start';
2010
+ });
2011
+ btnState('button[data-event="justifyCenter"]', function () {
2012
+ return oStyle['text-align'] === 'center';
2013
+ });
2014
+ btnState('button[data-event="justifyRight"]', function () {
2015
+ return oStyle['text-align'] === 'right';
2016
+ });
2017
+ btnState('button[data-event="justifyFull"]', function () {
2018
+ return oStyle['text-align'] === 'justify';
2019
+ });
2020
+ btnState('button[data-event="insertUnorderedList"]', function () {
2021
+ return oStyle['list-style'] === 'unordered';
2022
+ });
2023
+ btnState('button[data-event="insertOrderedList"]', function () {
2024
+ return oStyle['list-style'] === 'ordered';
2025
+ });
2026
+ };
2027
+
2028
+ /**
2029
+ * update recent color
2030
+ *
2031
+ * @param {Element} elBtn
2032
+ * @param {String} sEvent
2033
+ * @param {sValue} sValue
2034
+ */
2035
+ this.updateRecentColor = function (elBtn, sEvent, sValue) {
2036
+ var $color = $(elBtn).closest('.note-color');
2037
+ var $recentColor = $color.find('.note-recent-color');
2038
+ var oColor = JSON.parse($recentColor.attr('data-value'));
2039
+ oColor[sEvent] = sValue;
2040
+ $recentColor.attr('data-value', JSON.stringify(oColor));
2041
+ var sKey = sEvent === 'backColor' ? 'background-color' : 'color';
2042
+ $recentColor.find('i').css(sKey, sValue);
2043
+ };
2044
+ };
2045
+
2046
+ /**
2047
+ * Toolbar
2048
+ */
2049
+ var Toolbar = function () {
2050
+ var button = new Button();
2051
+
2052
+ this.update = function ($toolbar, oStyle) {
2053
+ button.update($toolbar, oStyle);
2054
+ };
2055
+
2056
+ this.updateRecentColor = function (elBtn, sEvent, sValue) {
2057
+ button.updateRecentColor(elBtn, sEvent, sValue);
2058
+ };
2059
+
2060
+ /**
2061
+ * activate buttons exclude codeview
2062
+ * @param {jQuery} $toolbar
2063
+ */
2064
+ this.activate = function ($toolbar) {
2065
+ $toolbar.find('button').not('button[data-event="codeview"]').removeClass('disabled');
2066
+ };
2067
+
2068
+ /**
2069
+ * deactivate buttons exclude codeview
2070
+ * @param {jQuery} $toolbar
2071
+ */
2072
+ this.deactivate = function ($toolbar) {
2073
+ $toolbar.find('button').not('button[data-event="codeview"]').addClass('disabled');
2074
+ };
2075
+
2076
+ this.updateFullscreen = function ($container, bFullscreen) {
2077
+ var $btn = $container.find('button[data-event="fullscreen"]');
2078
+ $btn.toggleClass('active', bFullscreen);
2079
+ };
2080
+
2081
+ this.updateCodeview = function ($container, bCodeview) {
2082
+ var $btn = $container.find('button[data-event="codeview"]');
2083
+ $btn.toggleClass('active', bCodeview);
2084
+ };
2085
+ };
2086
+
2087
+ /**
2088
+ * Popover (http://getbootstrap.com/javascript/#popovers)
2089
+ */
2090
+ var Popover = function () {
2091
+ var button = new Button();
2092
+
2093
+ /**
2094
+ * show popover
2095
+ * @param {jQuery} popover
2096
+ * @param {Element} elPlaceholder - placeholder for popover
2097
+ */
2098
+ var showPopover = function ($popover, elPlaceholder) {
2099
+ var $placeholder = $(elPlaceholder);
2100
+ var pos = $placeholder.position();
2101
+
2102
+ // include margin
2103
+ var height = $placeholder.outerHeight(true);
2104
+
2105
+ // display popover below placeholder.
2106
+ $popover.css({
2107
+ display: 'block',
2108
+ left: pos.left,
2109
+ top: pos.top + height
2110
+ });
2111
+ };
2112
+
2113
+ var PX_POPOVER_ARROW_OFFSET_X = 20;
2114
+
2115
+ /**
2116
+ * update current state
2117
+ * @param {jQuery} $popover - popover container
2118
+ * @param {Object} oStyle - style object
2119
+ * @param {Boolean} isAirMode
2120
+ */
2121
+ this.update = function ($popover, oStyle, isAirMode) {
2122
+ button.update($popover, oStyle);
2123
+
2124
+ var $linkPopover = $popover.find('.note-link-popover');
2125
+
2126
+ if (oStyle.anchor) {
2127
+ var $anchor = $linkPopover.find('a');
2128
+ $anchor.attr('href', oStyle.anchor.href).html(oStyle.anchor.href);
2129
+ showPopover($linkPopover, oStyle.anchor);
2130
+ } else {
2131
+ $linkPopover.hide();
2132
+ }
2133
+
2134
+ var $imagePopover = $popover.find('.note-image-popover');
2135
+ if (oStyle.image) {
2136
+ showPopover($imagePopover, oStyle.image);
2137
+ } else {
2138
+ $imagePopover.hide();
2139
+ }
2140
+
2141
+ if (isAirMode) {
2142
+ var $airPopover = $popover.find('.note-air-popover');
2143
+ if (!oStyle.range.isCollapsed()) {
2144
+ var bnd = func.rect2bnd(list.last(oStyle.range.getClientRects()));
2145
+ $airPopover.css({
2146
+ display: 'block',
2147
+ left: Math.max(bnd.left + bnd.width / 2 - PX_POPOVER_ARROW_OFFSET_X, 0),
2148
+ top: bnd.top + bnd.height
2149
+ });
2150
+ } else {
2151
+ $airPopover.hide();
2152
+ }
2153
+ }
2154
+ };
2155
+
2156
+ this.updateRecentColor = function (elBtn, sEvent, sValue) {
2157
+ button.updateRecentColor(elBtn, sEvent, sValue);
2158
+ };
2159
+
2160
+ /**
2161
+ * hide all popovers
2162
+ * @param {jQuery} $popover - popover contaienr
2163
+ */
2164
+ this.hide = function ($popover) {
2165
+ $popover.children().hide();
2166
+ };
2167
+ };
2168
+
2169
+ /**
2170
+ * Handle
2171
+ */
2172
+ var Handle = function () {
2173
+ /**
2174
+ * update handle
2175
+ * @param {jQuery} $handle
2176
+ * @param {Object} oStyle
2177
+ */
2178
+ this.update = function ($handle, oStyle) {
2179
+ var $selection = $handle.find('.note-control-selection');
2180
+ if (oStyle.image) {
2181
+ var $image = $(oStyle.image);
2182
+ var pos = $image.position();
2183
+
2184
+ // include margin
2185
+ var szImage = {
2186
+ w: $image.outerWidth(true),
2187
+ h: $image.outerHeight(true)
2188
+ };
2189
+
2190
+ $selection.css({
2191
+ display: 'block',
2192
+ left: pos.left,
2193
+ top: pos.top,
2194
+ width: szImage.w,
2195
+ height: szImage.h
2196
+ }).data('target', oStyle.image); // save current image element.
2197
+ var sSizing = szImage.w + 'x' + szImage.h;
2198
+ $selection.find('.note-control-selection-info').text(sSizing);
2199
+ } else {
2200
+ $selection.hide();
2201
+ }
2202
+ };
2203
+
2204
+ this.hide = function ($handle) {
2205
+ $handle.children().hide();
2206
+ };
2207
+ };
2208
+
2209
+ /**
2210
+ * Dialog
2211
+ *
2212
+ * @class
2213
+ */
2214
+ var Dialog = function () {
2215
+
2216
+ /**
2217
+ * toggle button status
2218
+ *
2219
+ * @param {jQuery} $btn
2220
+ * @param {Boolean} bEnable
2221
+ */
2222
+ var toggleBtn = function ($btn, bEnable) {
2223
+ $btn.toggleClass('disabled', !bEnable);
2224
+ $btn.attr('disabled', !bEnable);
2225
+ };
2226
+
2227
+ /**
2228
+ * show image dialog
2229
+ *
2230
+ * @param {jQuery} $editable
2231
+ * @param {jQuery} $dialog
2232
+ * @return {Promise}
2233
+ */
2234
+ this.showImageDialog = function ($editable, $dialog) {
2235
+ return $.Deferred(function (deferred) {
2236
+ var $imageDialog = $dialog.find('.note-image-dialog');
2237
+
2238
+ var $imageInput = $dialog.find('.note-image-input'),
2239
+ $imageUrl = $dialog.find('.note-image-url'),
2240
+ $imageBtn = $dialog.find('.note-image-btn');
2241
+
2242
+ $imageDialog.one('shown.bs.modal', function () {
2243
+ // Cloning imageInput to clear element.
2244
+ $imageInput.replaceWith($imageInput.clone()
2245
+ .on('change', function () {
2246
+ $imageDialog.modal('hide');
2247
+ deferred.resolve(this.files);
2248
+ })
2249
+ );
2250
+
2251
+ $imageBtn.click(function (event) {
2252
+ event.preventDefault();
2253
+
2254
+ $imageDialog.modal('hide');
2255
+ deferred.resolve($imageUrl.val());
2256
+ });
2257
+
2258
+ $imageUrl.keyup(function () {
2259
+ toggleBtn($imageBtn, $imageUrl.val());
2260
+ }).val('').focus();
2261
+ }).one('hidden.bs.modal', function () {
2262
+ $editable.focus();
2263
+ $imageInput.off('change');
2264
+ $imageUrl.off('keyup');
2265
+ $imageBtn.off('click');
2266
+ }).modal('show');
2267
+ });
2268
+ };
2269
+
2270
+ /**
2271
+ * Show video dialog and set event handlers on dialog controls.
2272
+ *
2273
+ * @param {jQuery} $dialog
2274
+ * @param {Object} videoInfo
2275
+ * @return {Promise}
2276
+ */
2277
+ this.showVideoDialog = function ($editable, $dialog, videoInfo) {
2278
+ return $.Deferred(function (deferred) {
2279
+ var $videoDialog = $dialog.find('.note-video-dialog');
2280
+ var $videoUrl = $videoDialog.find('.note-video-url'),
2281
+ $videoBtn = $videoDialog.find('.note-video-btn');
2282
+
2283
+ $videoDialog.one('shown.bs.modal', function () {
2284
+ $videoUrl.val(videoInfo.text).keyup(function () {
2285
+ toggleBtn($videoBtn, $videoUrl.val());
2286
+ }).trigger('keyup').trigger('focus');
2287
+
2288
+ $videoBtn.click(function (event) {
2289
+ event.preventDefault();
2290
+
2291
+ $videoDialog.modal('hide');
2292
+ deferred.resolve($videoUrl.val());
2293
+ });
2294
+ }).one('hidden.bs.modal', function () {
2295
+ $editable.focus();
2296
+ $videoUrl.off('keyup');
2297
+ $videoBtn.off('click');
2298
+ }).modal('show');
2299
+ });
2300
+ };
2301
+
2302
+ /**
2303
+ * Show link dialog and set event handlers on dialog controls.
2304
+ *
2305
+ * @param {jQuery} $dialog
2306
+ * @param {Object} linkInfo
2307
+ * @return {Promise}
2308
+ */
2309
+ this.showLinkDialog = function ($editable, $dialog, linkInfo) {
2310
+ return $.Deferred(function (deferred) {
2311
+ var $linkDialog = $dialog.find('.note-link-dialog');
2312
+
2313
+ var $linkText = $linkDialog.find('.note-link-text'),
2314
+ $linkUrl = $linkDialog.find('.note-link-url'),
2315
+ $linkBtn = $linkDialog.find('.note-link-btn'),
2316
+ $openInNewWindow = $linkDialog.find('input[type=checkbox]');
2317
+
2318
+ $linkDialog.one('shown.bs.modal', function () {
2319
+ $linkText.val(linkInfo.text);
2320
+
2321
+ $linkText.keyup(function () {
2322
+ // if linktext was modified by keyup,
2323
+ // stop cloning text from linkUrl
2324
+ linkInfo.text = $linkText.val();
2325
+ });
2326
+
2327
+ // if no url was given, copy text to url
2328
+ if (!linkInfo.url) {
2329
+ linkInfo.url = linkInfo.text;
2330
+ toggleBtn($linkBtn, linkInfo.text);
2331
+ }
2332
+
2333
+ $linkUrl.keyup(function () {
2334
+ toggleBtn($linkBtn, $linkUrl.val());
2335
+ // display same link on `Text to display` input
2336
+ // when create a new link
2337
+ if (!linkInfo.text) {
2338
+ $linkText.val($linkUrl.val());
2339
+ }
2340
+ }).val(linkInfo.url).trigger('focus').trigger('select');
2341
+
2342
+ $openInNewWindow.prop('checked', linkInfo.newWindow);
2343
+
2344
+ $linkBtn.one('click', function (event) {
2345
+ event.preventDefault();
2346
+
2347
+ $linkDialog.modal('hide');
2348
+ deferred.resolve($linkText.val(), $linkUrl.val(), $openInNewWindow.is(':checked'));
2349
+ });
2350
+ }).one('hidden.bs.modal', function () {
2351
+ $editable.focus();
2352
+ $linkUrl.off('keyup');
2353
+ }).modal('show');
2354
+ }).promise();
2355
+ };
2356
+
2357
+ /**
2358
+ * show help dialog
2359
+ *
2360
+ * @param {jQuery} $dialog
2361
+ */
2362
+ this.showHelpDialog = function ($editable, $dialog) {
2363
+ var $helpDialog = $dialog.find('.note-help-dialog');
2364
+
2365
+ $helpDialog.one('hidden.bs.modal', function () {
2366
+ $editable.focus();
2367
+ }).modal('show');
2368
+ };
2369
+ };
2370
+
2371
+ /**
2372
+ * EventHandler
2373
+ */
2374
+ var EventHandler = function () {
2375
+ var $document = $(document);
2376
+
2377
+ var editor = new Editor();
2378
+ var toolbar = new Toolbar(), popover = new Popover();
2379
+ var handle = new Handle(), dialog = new Dialog();
2380
+
2381
+ /**
2382
+ * returns makeLayoutInfo from editor's descendant node.
2383
+ *
2384
+ * @param {Element} descendant
2385
+ * @returns {Object}
2386
+ */
2387
+ var makeLayoutInfo = function (descendant) {
2388
+ var $target = $(descendant).closest('.note-editor, .note-air-editor, .note-air-layout');
2389
+
2390
+ if ($target.length === 0) { return null; }
2391
+
2392
+ var $editor;
2393
+ if ($target.is('.note-editor, .note-air-editor')) {
2394
+ $editor = $target;
2395
+ } else {
2396
+ $editor = $('#note-editor-' + list.last($target.attr('id').split('-')));
2397
+ }
2398
+
2399
+ return dom.buildLayoutInfo($editor);
2400
+ };
2401
+
2402
+ /**
2403
+ * insert Images from file array.
2404
+ *
2405
+ * @param {jQuery} $editable
2406
+ * @param {File[]} files
2407
+ */
2408
+ var insertImages = function ($editable, files) {
2409
+ editor.restoreRange($editable);
2410
+ var callbacks = $editable.data('callbacks');
2411
+
2412
+ // If onImageUpload options setted
2413
+ if (callbacks.onImageUpload) {
2414
+ callbacks.onImageUpload(files, editor, $editable);
2415
+ // else insert Image as dataURL
2416
+ } else {
2417
+ $.each(files, function (idx, file) {
2418
+ async.readFileAsDataURL(file).then(function (sDataURL) {
2419
+ editor.insertImage($editable, sDataURL);
2420
+ }).fail(function () {
2421
+ if (callbacks.onImageUploadError) {
2422
+ callbacks.onImageUploadError();
2423
+ }
2424
+ });
2425
+ });
2426
+ }
2427
+ };
2428
+
2429
+ var hMousedown = function (event) {
2430
+ //preventDefault Selection for FF, IE8+
2431
+ if (dom.isImg(event.target)) {
2432
+ event.preventDefault();
2433
+ }
2434
+ };
2435
+
2436
+ var hToolbarAndPopoverUpdate = function (event) {
2437
+ // delay for range after mouseup
2438
+ setTimeout(function () {
2439
+ var oLayoutInfo = makeLayoutInfo(event.currentTarget || event.target);
2440
+ var oStyle = editor.currentStyle(event.target);
2441
+ if (!oStyle) { return; }
2442
+
2443
+ var isAirMode = oLayoutInfo.editor().data('options').airMode;
2444
+ if (!isAirMode) {
2445
+ toolbar.update(oLayoutInfo.toolbar(), oStyle);
2446
+ }
2447
+
2448
+ popover.update(oLayoutInfo.popover(), oStyle, isAirMode);
2449
+ handle.update(oLayoutInfo.handle(), oStyle);
2450
+ }, 0);
2451
+ };
2452
+
2453
+ var hScroll = function (event) {
2454
+ var oLayoutInfo = makeLayoutInfo(event.currentTarget || event.target);
2455
+ //hide popover and handle when scrolled
2456
+ popover.hide(oLayoutInfo.popover());
2457
+ handle.hide(oLayoutInfo.handle());
2458
+ };
2459
+
2460
+ /**
2461
+ * paste clipboard image
2462
+ *
2463
+ * @param {Event} event
2464
+ */
2465
+ var hPasteClipboardImage = function (event) {
2466
+ var originalEvent = event.originalEvent;
2467
+ if (!originalEvent.clipboardData ||
2468
+ !originalEvent.clipboardData.items ||
2469
+ !originalEvent.clipboardData.items.length) {
2470
+ return;
2471
+ }
2472
+
2473
+ var oLayoutInfo = makeLayoutInfo(event.currentTarget || event.target);
2474
+ var item = list.head(originalEvent.clipboardData.items);
2475
+ var bClipboardImage = item.kind === 'file' && item.type.indexOf('image/') !== -1;
2476
+
2477
+ if (bClipboardImage) {
2478
+ insertImages(oLayoutInfo.editable(), [item.getAsFile()]);
2479
+ }
2480
+ };
2481
+
2482
+ /**
2483
+ * `mousedown` event handler on $handle
2484
+ * - controlSizing: resize image
2485
+ *
2486
+ * @param {MouseEvent} event
2487
+ */
2488
+ var hHandleMousedown = function (event) {
2489
+ if (dom.isControlSizing(event.target)) {
2490
+ event.preventDefault();
2491
+ event.stopPropagation();
2492
+
2493
+ var oLayoutInfo = makeLayoutInfo(event.target),
2494
+ $handle = oLayoutInfo.handle(), $popover = oLayoutInfo.popover(),
2495
+ $editable = oLayoutInfo.editable();
2496
+
2497
+ var elTarget = $handle.find('.note-control-selection').data('target'),
2498
+ $target = $(elTarget), posStart = $target.offset(),
2499
+ scrollTop = $document.scrollTop();
2500
+
2501
+ $document.on('mousemove', function (event) {
2502
+ editor.resizeTo({
2503
+ x: event.clientX - posStart.left,
2504
+ y: event.clientY - (posStart.top - scrollTop)
2505
+ }, $target, !event.shiftKey);
2506
+
2507
+ handle.update($handle, {image: elTarget});
2508
+ popover.update($popover, {image: elTarget});
2509
+ }).one('mouseup', function () {
2510
+ $document.off('mousemove');
2511
+ });
2512
+
2513
+ if (!$target.data('ratio')) { // original ratio.
2514
+ $target.data('ratio', $target.height() / $target.width());
2515
+ }
2516
+
2517
+ editor.recordUndo($editable);
2518
+ }
2519
+ };
2520
+
2521
+ var hToolbarAndPopoverMousedown = function (event) {
2522
+ // prevent default event when insertTable (FF, Webkit)
2523
+ var $btn = $(event.target).closest('[data-event]');
2524
+ if ($btn.length > 0) {
2525
+ event.preventDefault();
2526
+ }
2527
+ };
2528
+
2529
+ var toggleFullscreen = function (oLayoutInfo) {
2530
+ var $editor = oLayoutInfo.editor(),
2531
+ $toolbar = oLayoutInfo.toolbar(),
2532
+ $editable = oLayoutInfo.editable(),
2533
+ $codable = oLayoutInfo.codable();
2534
+
2535
+ var options = $editor.data('options');
2536
+
2537
+ var $scrollbar = $('html, body');
2538
+
2539
+ var resize = function (size) {
2540
+ $editor.css('width', size.w);
2541
+ $editable.css('height', size.h);
2542
+ $codable.css('height', size.h);
2543
+ if ($codable.data('cmEditor')) {
2544
+ $codable.data('cmEditor').setSize(null, size.h);
2545
+ }
2546
+ };
2547
+
2548
+ $editor.toggleClass('fullscreen');
2549
+ var isFullscreen = $editor.hasClass('fullscreen');
2550
+ if (isFullscreen) {
2551
+ $editable.data('orgHeight', $editable.css('height'));
2552
+
2553
+ $(window).on('resize', function () {
2554
+ resize({
2555
+ w: $(window).width(),
2556
+ h: $(window).height() - $toolbar.outerHeight()
2557
+ });
2558
+ }).trigger('resize');
2559
+
2560
+ $scrollbar.css('overflow', 'hidden');
2561
+ } else {
2562
+ $(window).off('resize');
2563
+ resize({
2564
+ w: options.width || '',
2565
+ h: $editable.data('orgHeight')
2566
+ });
2567
+ $scrollbar.css('overflow', 'visible');
2568
+ }
2569
+
2570
+ toolbar.updateFullscreen($toolbar, isFullscreen);
2571
+ };
2572
+
2573
+ var toggleCodeView = function (oLayoutInfo) {
2574
+ var $editor = oLayoutInfo.editor(),
2575
+ $toolbar = oLayoutInfo.toolbar(),
2576
+ $editable = oLayoutInfo.editable(),
2577
+ $codable = oLayoutInfo.codable(),
2578
+ $popover = oLayoutInfo.popover();
2579
+
2580
+ var options = $editor.data('options');
2581
+
2582
+ var cmEditor, server;
2583
+
2584
+ $editor.toggleClass('codeview');
2585
+
2586
+ var bCodeview = $editor.hasClass('codeview');
2587
+ if (bCodeview) {
2588
+ $codable.val($editable.html());
2589
+ $codable.height($editable.height());
2590
+ toolbar.deactivate($toolbar);
2591
+ popover.hide($popover);
2592
+ $codable.focus();
2593
+
2594
+ // activate CodeMirror as codable
2595
+ if (agent.bCodeMirror) {
2596
+ cmEditor = CodeMirror.fromTextArea($codable[0], options.codemirror);
2597
+
2598
+ // CodeMirror TernServer
2599
+ if (options.codemirror.tern) {
2600
+ server = new CodeMirror.TernServer(options.codemirror.tern);
2601
+ cmEditor.ternServer = server;
2602
+ cmEditor.on('cursorActivity', function (cm) {
2603
+ server.updateArgHints(cm);
2604
+ });
2605
+ }
2606
+
2607
+ // CodeMirror hasn't Padding.
2608
+ cmEditor.setSize(null, $editable.outerHeight());
2609
+ // autoFormatRange If formatting included
2610
+ if (cmEditor.autoFormatRange) {
2611
+ cmEditor.autoFormatRange({line: 0, ch: 0}, {
2612
+ line: cmEditor.lineCount(),
2613
+ ch: cmEditor.getTextArea().value.length
2614
+ });
2615
+ }
2616
+ $codable.data('cmEditor', cmEditor);
2617
+ }
2618
+ } else {
2619
+ // deactivate CodeMirror as codable
2620
+ if (agent.bCodeMirror) {
2621
+ cmEditor = $codable.data('cmEditor');
2622
+ $codable.val(cmEditor.getValue());
2623
+ cmEditor.toTextArea();
2624
+ }
2625
+
2626
+ $editable.html($codable.val() || dom.emptyPara);
2627
+ $editable.height(options.height ? $codable.height() : 'auto');
2628
+
2629
+ toolbar.activate($toolbar);
2630
+ $editable.focus();
2631
+ }
2632
+
2633
+ toolbar.updateCodeview(oLayoutInfo.toolbar(), bCodeview);
2634
+ };
2635
+
2636
+ var hToolbarAndPopoverClick = function (event) {
2637
+ var $btn = $(event.target).closest('[data-event]');
2638
+
2639
+ if ($btn.length > 0) {
2640
+ var sEvent = $btn.attr('data-event'), sValue = $btn.attr('data-value');
2641
+
2642
+ var oLayoutInfo = makeLayoutInfo(event.target);
2643
+ var $dialog = oLayoutInfo.dialog(),
2644
+ $editable = oLayoutInfo.editable();
2645
+
2646
+ // before command: detect control selection element($target)
2647
+ var $target;
2648
+ if ($.inArray(sEvent, ['resize', 'floatMe', 'removeMedia']) !== -1) {
2649
+ var $selection = oLayoutInfo.handle().find('.note-control-selection');
2650
+ $target = $($selection.data('target'));
2651
+ }
2652
+
2653
+ if (editor[sEvent]) { // on command
2654
+ $editable.trigger('focus');
2655
+ editor[sEvent]($editable, sValue, $target);
2656
+ }
2657
+
2658
+ // after command
2659
+ if ($.inArray(sEvent, ['backColor', 'foreColor']) !== -1) {
2660
+ var options = oLayoutInfo.editor().data('options', options);
2661
+ var module = options.airMode ? popover : toolbar;
2662
+ module.updateRecentColor(list.head($btn), sEvent, sValue);
2663
+ } else if (sEvent === 'showLinkDialog') { // popover to dialog
2664
+ $editable.focus();
2665
+ var linkInfo = editor.getLinkInfo();
2666
+
2667
+ editor.saveRange($editable);
2668
+ dialog.showLinkDialog($editable, $dialog, linkInfo).then(function (sLinkText, sLinkUrl, bNewWindow) {
2669
+ editor.restoreRange($editable);
2670
+ editor.createLink($editable, sLinkText, sLinkUrl, bNewWindow);
2671
+ // hide popover after creating link
2672
+ popover.hide(oLayoutInfo.popover());
2673
+ });
2674
+ } else if (sEvent === 'showImageDialog') {
2675
+ $editable.focus();
2676
+
2677
+ dialog.showImageDialog($editable, $dialog).then(function (data) {
2678
+ if (typeof data === 'string') {
2679
+ editor.restoreRange($editable);
2680
+ editor.insertImage($editable, data);
2681
+ } else {
2682
+ insertImages($editable, data);
2683
+ }
2684
+ });
2685
+ } else if (sEvent === 'showVideoDialog') {
2686
+ $editable.focus();
2687
+ var videoInfo = editor.getVideoInfo();
2688
+
2689
+ editor.saveRange($editable);
2690
+ dialog.showVideoDialog($editable, $dialog, videoInfo).then(function (sUrl) {
2691
+ editor.restoreRange($editable);
2692
+ editor.insertVideo($editable, sUrl);
2693
+ });
2694
+ } else if (sEvent === 'showHelpDialog') {
2695
+ dialog.showHelpDialog($editable, $dialog);
2696
+ } else if (sEvent === 'fullscreen') {
2697
+ toggleFullscreen(oLayoutInfo);
2698
+ } else if (sEvent === 'codeview') {
2699
+ toggleCodeView(oLayoutInfo);
2700
+ }
2701
+
2702
+ hToolbarAndPopoverUpdate(event);
2703
+ }
2704
+ };
2705
+
2706
+ var EDITABLE_PADDING = 24;
2707
+ /**
2708
+ * `mousedown` event handler on statusbar
2709
+ *
2710
+ * @param {MouseEvent} event
2711
+ */
2712
+ var hStatusbarMousedown = function (event) {
2713
+ event.preventDefault();
2714
+ event.stopPropagation();
2715
+
2716
+ var $editable = makeLayoutInfo(event.target).editable();
2717
+ var nEditableTop = $editable.offset().top - $document.scrollTop();
2718
+
2719
+ $document.on('mousemove', function (event) {
2720
+ var nHeight = event.clientY - (nEditableTop + EDITABLE_PADDING);
2721
+ $editable.height(nHeight);
2722
+ }).one('mouseup', function () {
2723
+ $document.off('mousemove');
2724
+ });
2725
+ };
2726
+
2727
+ var PX_PER_EM = 18;
2728
+ var hDimensionPickerMove = function (event) {
2729
+ var $picker = $(event.target.parentNode); // target is mousecatcher
2730
+ var $dimensionDisplay = $picker.next();
2731
+ var $catcher = $picker.find('.note-dimension-picker-mousecatcher');
2732
+ var $highlighted = $picker.find('.note-dimension-picker-highlighted');
2733
+ var $unhighlighted = $picker.find('.note-dimension-picker-unhighlighted');
2734
+
2735
+ var posOffset;
2736
+ // HTML5 with jQuery - e.offsetX is undefined in Firefox
2737
+ if (event.offsetX === undefined) {
2738
+ var posCatcher = $(event.target).offset();
2739
+ posOffset = {
2740
+ x: event.pageX - posCatcher.left,
2741
+ y: event.pageY - posCatcher.top
2742
+ };
2743
+ } else {
2744
+ posOffset = {
2745
+ x: event.offsetX,
2746
+ y: event.offsetY
2747
+ };
2748
+ }
2749
+
2750
+ var dim = {
2751
+ c: Math.ceil(posOffset.x / PX_PER_EM) || 1,
2752
+ r: Math.ceil(posOffset.y / PX_PER_EM) || 1
2753
+ };
2754
+
2755
+ $highlighted.css({ width: dim.c + 'em', height: dim.r + 'em' });
2756
+ $catcher.attr('data-value', dim.c + 'x' + dim.r);
2757
+
2758
+ if (3 < dim.c && dim.c < 10) { // 5~10
2759
+ $unhighlighted.css({ width: dim.c + 1 + 'em'});
2760
+ }
2761
+
2762
+ if (3 < dim.r && dim.r < 10) { // 5~10
2763
+ $unhighlighted.css({ height: dim.r + 1 + 'em'});
2764
+ }
2765
+
2766
+ $dimensionDisplay.html(dim.c + ' x ' + dim.r);
2767
+ };
2768
+
2769
+ /**
2770
+ * attach Drag and Drop Events
2771
+ *
2772
+ * @param {Object} oLayoutInfo - layout Informations
2773
+ */
2774
+ var attachDragAndDropEvent = function (oLayoutInfo) {
2775
+ var collection = $(), $dropzone = oLayoutInfo.dropzone,
2776
+ $dropzoneMessage = oLayoutInfo.dropzone.find('.note-dropzone-message');
2777
+
2778
+ // show dropzone on dragenter when dragging a object to document.
2779
+ $document.on('dragenter', function (e) {
2780
+ var bCodeview = oLayoutInfo.editor.hasClass('codeview');
2781
+ if (!bCodeview && collection.length === 0) {
2782
+ oLayoutInfo.editor.addClass('dragover');
2783
+ $dropzone.width(oLayoutInfo.editor.width());
2784
+ $dropzone.height(oLayoutInfo.editor.height());
2785
+ $dropzoneMessage.text('Drag Image Here');
2786
+ }
2787
+ collection = collection.add(e.target);
2788
+ }).on('dragleave', function (e) {
2789
+ collection = collection.not(e.target);
2790
+ if (collection.length === 0) {
2791
+ oLayoutInfo.editor.removeClass('dragover');
2792
+ }
2793
+ }).on('drop', function () {
2794
+ collection = $();
2795
+ oLayoutInfo.editor.removeClass('dragover');
2796
+ });
2797
+
2798
+ // change dropzone's message on hover.
2799
+ $dropzone.on('dragenter', function () {
2800
+ $dropzone.addClass('hover');
2801
+ $dropzoneMessage.text('Drop Image');
2802
+ }).on('dragleave', function () {
2803
+ $dropzone.removeClass('hover');
2804
+ $dropzoneMessage.text('Drag Image Here');
2805
+ });
2806
+
2807
+ // attach dropImage
2808
+ $dropzone.on('drop', function (event) {
2809
+ event.preventDefault();
2810
+
2811
+ var dataTransfer = event.originalEvent.dataTransfer;
2812
+ if (dataTransfer && dataTransfer.files) {
2813
+ var oLayoutInfo = makeLayoutInfo(event.currentTarget || event.target);
2814
+ oLayoutInfo.editable().focus();
2815
+ insertImages(oLayoutInfo.editable(), dataTransfer.files);
2816
+ }
2817
+ }).on('dragover', false); // prevent default dragover event
2818
+ };
2819
+
2820
+
2821
+ /**
2822
+ * bind KeyMap on keydown
2823
+ *
2824
+ * @param {Object} oLayoutInfo
2825
+ * @param {Object} keyMap
2826
+ */
2827
+ this.bindKeyMap = function (oLayoutInfo, keyMap) {
2828
+ var $editor = oLayoutInfo.editor;
2829
+ var $editable = oLayoutInfo.editable;
2830
+
2831
+ $editable.on('keydown', function (event) {
2832
+ var aKey = [];
2833
+
2834
+ // modifier
2835
+ if (event.metaKey) { aKey.push('CMD'); }
2836
+ if (event.ctrlKey) { aKey.push('CTRL'); }
2837
+ if (event.shiftKey) { aKey.push('SHIFT'); }
2838
+
2839
+ // keycode
2840
+ var keyName = key.nameFromCode[event.keyCode];
2841
+ if (keyName) { aKey.push(keyName); }
2842
+
2843
+ var handler = keyMap[aKey.join('+')];
2844
+ if (handler) {
2845
+ event.preventDefault();
2846
+
2847
+ editor[handler]($editable, $editor.data('options'));
2848
+ } else if (key.isEdit(event.keyCode)) {
2849
+ editor.recordUndo($editable);
2850
+ }
2851
+ });
2852
+ };
2853
+
2854
+ /**
2855
+ * attach eventhandler
2856
+ *
2857
+ * @param {Object} oLayoutInfo - layout Informations
2858
+ * @param {Object} options - user options include custom event handlers
2859
+ * @param {Function} options.enter - enter key handler
2860
+ */
2861
+ this.attach = function (oLayoutInfo, options) {
2862
+ // handlers for editable
2863
+ this.bindKeyMap(oLayoutInfo, options.keyMap[agent.bMac ? 'mac' : 'pc']);
2864
+ oLayoutInfo.editable.on('mousedown', hMousedown);
2865
+ oLayoutInfo.editable.on('keyup mouseup', hToolbarAndPopoverUpdate);
2866
+ oLayoutInfo.editable.on('scroll', hScroll);
2867
+ oLayoutInfo.editable.on('paste', hPasteClipboardImage);
2868
+
2869
+ // handler for handle and popover
2870
+ oLayoutInfo.handle.on('mousedown', hHandleMousedown);
2871
+ oLayoutInfo.popover.on('click', hToolbarAndPopoverClick);
2872
+ oLayoutInfo.popover.on('mousedown', hToolbarAndPopoverMousedown);
2873
+
2874
+ // handlers for frame mode (toolbar, statusbar)
2875
+ if (!options.airMode) {
2876
+ // handler for drag and drop
2877
+ if (!options.disableDragAndDrop) {
2878
+ attachDragAndDropEvent(oLayoutInfo);
2879
+ }
2880
+
2881
+ // handler for toolbar
2882
+ oLayoutInfo.toolbar.on('click', hToolbarAndPopoverClick);
2883
+ oLayoutInfo.toolbar.on('mousedown', hToolbarAndPopoverMousedown);
2884
+
2885
+ // handler for statusbar
2886
+ oLayoutInfo.statusbar.on('mousedown', hStatusbarMousedown);
2887
+ }
2888
+
2889
+ // handler for table dimension
2890
+ var $catcherContainer = options.airMode ? oLayoutInfo.popover :
2891
+ oLayoutInfo.toolbar;
2892
+ var $catcher = $catcherContainer.find('.note-dimension-picker-mousecatcher');
2893
+ $catcher.on('mousemove', hDimensionPickerMove);
2894
+
2895
+ // save selection when focusout
2896
+ oLayoutInfo.editable.on('blur', function () {
2897
+ editor.saveRange(oLayoutInfo.editable);
2898
+ });
2899
+
2900
+ // save options on editor
2901
+ oLayoutInfo.editor.data('options', options);
2902
+
2903
+ // ret styleWithCSS for backColor / foreColor clearing with 'inherit'.
2904
+ if (options.styleWithSpan && !agent.bMSIE) {
2905
+ // protect FF Error: NS_ERROR_FAILURE: Failure
2906
+ setTimeout(function () {
2907
+ document.execCommand('styleWithCSS', 0, true);
2908
+ }, 0);
2909
+ }
2910
+
2911
+ // History
2912
+ oLayoutInfo.editable.data('NoteHistory', new History());
2913
+
2914
+ // basic event callbacks (lowercase)
2915
+ // enter, focus, blur, keyup, keydown
2916
+ if (options.onenter) {
2917
+ oLayoutInfo.editable.keypress(function (event) {
2918
+ if (event.keyCode === key.ENTER) { options.onenter(event); }
2919
+ });
2920
+ }
2921
+
2922
+ if (options.onfocus) { oLayoutInfo.editable.focus(options.onfocus); }
2923
+ if (options.onblur) { oLayoutInfo.editable.blur(options.onblur); }
2924
+ if (options.onkeyup) { oLayoutInfo.editable.keyup(options.onkeyup); }
2925
+ if (options.onkeydown) { oLayoutInfo.editable.keydown(options.onkeydown); }
2926
+ if (options.onpaste) { oLayoutInfo.editable.on('paste', options.onpaste); }
2927
+
2928
+ // callbacks for advanced features (camel)
2929
+ if (options.onToolbarClick) { oLayoutInfo.toolbar.click(options.onToolbarClick); }
2930
+ if (options.onChange) {
2931
+ var hChange = function () {
2932
+ options.onChange(oLayoutInfo.editable, oLayoutInfo.editable.html());
2933
+ };
2934
+
2935
+ if (agent.bMSIE) {
2936
+ var sDomEvents = 'DOMCharacterDataModified, DOMSubtreeModified, DOMNodeInserted';
2937
+ oLayoutInfo.editable.on(sDomEvents, hChange);
2938
+ } else {
2939
+ oLayoutInfo.editable.on('input', hChange);
2940
+ }
2941
+ }
2942
+
2943
+ // All editor status will be saved on editable with jquery's data
2944
+ // for support multiple editor with singleton object.
2945
+ oLayoutInfo.editable.data('callbacks', {
2946
+ onAutoSave: options.onAutoSave,
2947
+ onImageUpload: options.onImageUpload,
2948
+ onImageUploadError: options.onImageUploadError,
2949
+ onFileUpload: options.onFileUpload,
2950
+ onFileUploadError: options.onFileUpload
2951
+ });
2952
+ };
2953
+
2954
+ this.dettach = function (oLayoutInfo) {
2955
+ oLayoutInfo.editable.off();
2956
+
2957
+ oLayoutInfo.popover.off();
2958
+ oLayoutInfo.handle.off();
2959
+ oLayoutInfo.dialog.off();
2960
+
2961
+ if (oLayoutInfo.editor.data('options').airMode) {
2962
+ oLayoutInfo.dropzone.off();
2963
+ oLayoutInfo.toolbar.off();
2964
+ oLayoutInfo.statusbar.off();
2965
+ }
2966
+ };
2967
+ };
2968
+
2969
+ /**
2970
+ * renderer
2971
+ *
2972
+ * rendering toolbar and editable
2973
+ */
2974
+ var Renderer = function () {
2975
+
2976
+ /**
2977
+ * bootstrap button template
2978
+ *
2979
+ * @param {String} sLabel
2980
+ * @param {Object} [options]
2981
+ * @param {String} [options.event]
2982
+ * @param {String} [options.value]
2983
+ * @param {String} [options.title]
2984
+ * @param {String} [options.dropdown]
2985
+ */
2986
+ var tplButton = function (sLabel, options) {
2987
+ var event = options.event;
2988
+ var value = options.value;
2989
+ var title = options.title;
2990
+ var className = options.className;
2991
+ var dropdown = options.dropdown;
2992
+
2993
+ return '<button type="button"' +
2994
+ ' class="btn btn-default btn-sm btn-small' +
2995
+ (className ? ' ' + className : '') +
2996
+ (dropdown ? ' dropdown-toggle' : '') +
2997
+ '"' +
2998
+ (dropdown ? ' data-toggle="dropdown"' : '') +
2999
+ (title ? ' title="' + title + '"' : '') +
3000
+ (event ? ' data-event="' + event + '"' : '') +
3001
+ (value ? ' data-value=\'' + value + '\'' : '') +
3002
+ ' tabindex="-1">' +
3003
+ sLabel +
3004
+ (dropdown ? ' <span class="caret"></span>' : '') +
3005
+ '</button>' +
3006
+ (dropdown || '');
3007
+ };
3008
+
3009
+ /**
3010
+ * bootstrap icon button template
3011
+ *
3012
+ * @param {String} sIconClass
3013
+ * @param {Object} [options]
3014
+ * @param {String} [options.event]
3015
+ * @param {String} [options.value]
3016
+ * @param {String} [options.title]
3017
+ * @param {String} [options.dropdown]
3018
+ */
3019
+ var tplIconButton = function (sIconClass, options) {
3020
+ var sLabel = '<i class="' + sIconClass + '"></i>';
3021
+ return tplButton(sLabel, options);
3022
+ };
3023
+
3024
+ /**
3025
+ * bootstrap popover template
3026
+ *
3027
+ * @param {String} className
3028
+ * @param {String} content
3029
+ */
3030
+ var tplPopover = function (className, content) {
3031
+ return '<div class="' + className + ' popover bottom in" style="display: none;">' +
3032
+ '<div class="arrow"></div>' +
3033
+ '<div class="popover-content">' +
3034
+ content +
3035
+ '</div>' +
3036
+ '</div>';
3037
+ };
3038
+
3039
+ /**
3040
+ * bootstrap dialog template
3041
+ *
3042
+ * @param {String} className
3043
+ * @param {String} [title]
3044
+ * @param {String} body
3045
+ * @param {String} [footer]
3046
+ */
3047
+ var tplDialog = function (className, title, body, footer) {
3048
+ return '<div class="' + className + ' modal" aria-hidden="false">' +
3049
+ '<div class="modal-dialog">' +
3050
+ '<div class="modal-content">' +
3051
+ (title ?
3052
+ '<div class="modal-header">' +
3053
+ '<button type="button" class="close" aria-hidden="true" tabindex="-1">&times;</button>' +
3054
+ '<h4>' + title + '</h4>' +
3055
+ '</div>' : ''
3056
+ ) +
3057
+ '<form class="note-modal-form">' +
3058
+ '<div class="modal-body">' +
3059
+ '<div class="row-fluid">' + body + '</div>' +
3060
+ '</div>' +
3061
+ (footer ?
3062
+ '<div class="modal-footer">' + footer + '</div>' : ''
3063
+ ) +
3064
+ '</form>' +
3065
+ '</div>' +
3066
+ '</div>' +
3067
+ '</div>';
3068
+ };
3069
+
3070
+ var tplButtonInfo = {
3071
+ picture: function (lang) {
3072
+ return tplIconButton('fa fa-picture-o icon-picture', {
3073
+ event: 'showImageDialog',
3074
+ title: lang.image.image
3075
+ });
3076
+ },
3077
+ link: function (lang) {
3078
+ return tplIconButton('fa fa-link icon-link', {
3079
+ event: 'showLinkDialog',
3080
+ title: lang.link.link
3081
+ });
3082
+ },
3083
+ video: function (lang) {
3084
+ return tplIconButton('fa fa-youtube-play icon-play', {
3085
+ event: 'showVideoDialog',
3086
+ title: lang.video.video
3087
+ });
3088
+ },
3089
+ table: function (lang) {
3090
+ var dropdown = '<ul class="dropdown-menu">' +
3091
+ '<div class="note-dimension-picker">' +
3092
+ '<div class="note-dimension-picker-mousecatcher" data-event="insertTable" data-value="1x1"></div>' +
3093
+ '<div class="note-dimension-picker-highlighted"></div>' +
3094
+ '<div class="note-dimension-picker-unhighlighted"></div>' +
3095
+ '</div>' +
3096
+ '<div class="note-dimension-display"> 1 x 1 </div>' +
3097
+ '</ul>';
3098
+ return tplIconButton('fa fa-table icon-table', {
3099
+ title: lang.table.table,
3100
+ dropdown: dropdown
3101
+ });
3102
+ },
3103
+ style: function (lang, options) {
3104
+ var items = options.styleTags.reduce(function (memo, v) {
3105
+ var label = lang.style[v === 'p' ? 'normal' : v];
3106
+ return memo + '<li><a data-event="formatBlock" data-value="' + v + '">' +
3107
+ (
3108
+ (v === 'p' || v === 'pre') ? label :
3109
+ '<' + v + '>' + label + '</' + v + '>'
3110
+ ) +
3111
+ '</a></li>';
3112
+ }, '');
3113
+
3114
+ return tplIconButton('fa fa-magic icon-magic', {
3115
+ title: lang.style.style,
3116
+ dropdown: '<ul class="dropdown-menu">' + items + '</ul>'
3117
+ });
3118
+ },
3119
+ fontname: function (lang, options) {
3120
+ var items = options.fontNames.reduce(function (memo, v) {
3121
+ return memo + '<li><a data-event="fontName" data-value="' + v + '">' +
3122
+ '<i class="fa fa-check icon-ok"></i> ' + v +
3123
+ '</a></li>';
3124
+ }, '');
3125
+ var sLabel = '<span class="note-current-fontname">' +
3126
+ options.defaultFontName +
3127
+ '</span>';
3128
+ return tplButton(sLabel, {
3129
+ title: lang.font.name,
3130
+ dropdown: '<ul class="dropdown-menu">' + items + '</ul>'
3131
+ });
3132
+ },
3133
+ fontsize: function (lang, options) {
3134
+ var items = options.fontSizes.reduce(function (memo, v) {
3135
+ return memo + '<li><a data-event="fontSize" data-value="' + v + '">' +
3136
+ '<i class="fa fa-check icon-ok"></i> ' + v +
3137
+ '</a></li>';
3138
+ }, '');
3139
+
3140
+ var sLabel = '<span class="note-current-fontsize">11</span>';
3141
+ return tplButton(sLabel, {
3142
+ title: lang.font.size,
3143
+ dropdown: '<ul class="dropdown-menu">' + items + '</ul>'
3144
+ });
3145
+ },
3146
+
3147
+ color: function (lang) {
3148
+ var colorButtonLabel = '<i class="fa fa-font icon-font" style="color:black;background-color:yellow;"></i>';
3149
+ var colorButton = tplButton(colorButtonLabel, {
3150
+ className: 'note-recent-color',
3151
+ title: lang.color.recent,
3152
+ event: 'color',
3153
+ value: '{"backColor":"yellow"}'
3154
+ });
3155
+
3156
+ var dropdown = '<ul class="dropdown-menu">' +
3157
+ '<li>' +
3158
+ '<div class="btn-group">' +
3159
+ '<div class="note-palette-title">' + lang.color.background + '</div>' +
3160
+ '<div class="note-color-reset" data-event="backColor"' +
3161
+ ' data-value="inherit" title="' + lang.color.transparent + '">' +
3162
+ lang.color.setTransparent +
3163
+ '</div>' +
3164
+ '<div class="note-color-palette" data-target-event="backColor"></div>' +
3165
+ '</div>' +
3166
+ '<div class="btn-group">' +
3167
+ '<div class="note-palette-title">' + lang.color.foreground + '</div>' +
3168
+ '<div class="note-color-reset" data-event="foreColor" data-value="inherit" title="' + lang.color.reset + '">' +
3169
+ lang.color.resetToDefault +
3170
+ '</div>' +
3171
+ '<div class="note-color-palette" data-target-event="foreColor"></div>' +
3172
+ '</div>' +
3173
+ '</li>' +
3174
+ '</ul>';
3175
+
3176
+ var moreButton = tplButton('', {
3177
+ title: lang.color.more,
3178
+ dropdown: dropdown
3179
+ });
3180
+
3181
+ return colorButton + moreButton;
3182
+ },
3183
+ bold: function (lang) {
3184
+ return tplIconButton('fa fa-bold icon-bold', {
3185
+ event: 'bold',
3186
+ title: lang.font.bold
3187
+ });
3188
+ },
3189
+ italic: function (lang) {
3190
+ return tplIconButton('fa fa-italic icon-italic', {
3191
+ event: 'italic',
3192
+ title: lang.font.italic
3193
+ });
3194
+ },
3195
+ underline: function (lang) {
3196
+ return tplIconButton('fa fa-underline icon-underline', {
3197
+ event: 'underline',
3198
+ title: lang.font.underline
3199
+ });
3200
+ },
3201
+ strikethrough: function (lang) {
3202
+ return tplIconButton('fa fa-strikethrough icon-strikethrough', {
3203
+ event: 'strikethrough',
3204
+ title: lang.font.strikethrough
3205
+ });
3206
+ },
3207
+ superscript: function (lang) {
3208
+ return tplIconButton('fa fa-superscript icon-superscript', {
3209
+ event: 'superscript',
3210
+ title: lang.font.superscript
3211
+ });
3212
+ },
3213
+ subscript: function (lang) {
3214
+ return tplIconButton('fa fa-subscript icon-subscript', {
3215
+ event: 'subscript',
3216
+ title: lang.font.subscript
3217
+ });
3218
+ },
3219
+ clear: function (lang) {
3220
+ return tplIconButton('fa fa-eraser icon-eraser', {
3221
+ event: 'removeFormat',
3222
+ title: lang.font.clear
3223
+ });
3224
+ },
3225
+ ul: function (lang) {
3226
+ return tplIconButton('fa fa-list-ul icon-list-ul', {
3227
+ event: 'insertUnorderedList',
3228
+ title: lang.lists.unordered
3229
+ });
3230
+ },
3231
+ ol: function (lang) {
3232
+ return tplIconButton('fa fa-list-ol icon-list-ol', {
3233
+ event: 'insertOrderedList',
3234
+ title: lang.lists.ordered
3235
+ });
3236
+ },
3237
+ paragraph: function (lang) {
3238
+ var leftButton = tplIconButton('fa fa-align-left icon-align-left', {
3239
+ title: lang.paragraph.left,
3240
+ event: 'justifyLeft'
3241
+ });
3242
+ var centerButton = tplIconButton('fa fa-align-center icon-align-center', {
3243
+ title: lang.paragraph.center,
3244
+ event: 'justifyCenter'
3245
+ });
3246
+ var rightButton = tplIconButton('fa fa-align-right icon-align-right', {
3247
+ title: lang.paragraph.right,
3248
+ event: 'justifyRight'
3249
+ });
3250
+ var justifyButton = tplIconButton('fa fa-align-justify icon-align-justify', {
3251
+ title: lang.paragraph.justify,
3252
+ event: 'justifyFull'
3253
+ });
3254
+
3255
+ var outdentButton = tplIconButton('fa fa-outdent icon-indent-left', {
3256
+ title: lang.paragraph.outdent,
3257
+ event: 'outdent'
3258
+ });
3259
+ var indentButton = tplIconButton('fa fa-indent icon-indent-right', {
3260
+ title: lang.paragraph.indent,
3261
+ event: 'indent'
3262
+ });
3263
+
3264
+ var dropdown = '<div class="dropdown-menu">' +
3265
+ '<div class="note-align btn-group">' +
3266
+ leftButton + centerButton + rightButton + justifyButton +
3267
+ '</div>' +
3268
+ '<div class="note-list btn-group">' +
3269
+ indentButton + outdentButton +
3270
+ '</div>' +
3271
+ '</div>';
3272
+
3273
+ return tplIconButton('fa fa-align-left icon-align-left', {
3274
+ title: lang.paragraph.paragraph,
3275
+ dropdown: dropdown
3276
+ });
3277
+ },
3278
+ height: function (lang, options) {
3279
+ var items = options.lineHeights.reduce(function (memo, v) {
3280
+ return memo + '<li><a data-event="lineHeight" data-value="' + parseFloat(v) + '">' +
3281
+ '<i class="fa fa-check icon-ok"></i> ' + v +
3282
+ '</a></li>';
3283
+ }, '');
3284
+
3285
+ return tplIconButton('fa fa-text-height icon-text-height', {
3286
+ title: lang.font.height,
3287
+ dropdown: '<ul class="dropdown-menu">' + items + '</ul>'
3288
+ });
3289
+
3290
+ },
3291
+ help: function (lang) {
3292
+ return tplIconButton('fa fa-question icon-question', {
3293
+ event: 'showHelpDialog',
3294
+ title: lang.options.help
3295
+ });
3296
+ },
3297
+ fullscreen: function (lang) {
3298
+ return tplIconButton('fa fa-arrows-alt icon-fullscreen', {
3299
+ event: 'fullscreen',
3300
+ title: lang.options.fullscreen
3301
+ });
3302
+ },
3303
+ codeview: function (lang) {
3304
+ return tplIconButton('fa fa-code icon-code', {
3305
+ event: 'codeview',
3306
+ title: lang.options.codeview
3307
+ });
3308
+ },
3309
+ undo: function (lang) {
3310
+ return tplIconButton('fa fa-undo icon-undo', {
3311
+ event: 'undo',
3312
+ title: lang.history.undo
3313
+ });
3314
+ },
3315
+ redo: function (lang) {
3316
+ return tplIconButton('fa fa-repeat icon-repeat', {
3317
+ event: 'redo',
3318
+ title: lang.history.redo
3319
+ });
3320
+ }
3321
+ };
3322
+
3323
+ var tplPopovers = function (lang, options) {
3324
+ var tplLinkPopover = function () {
3325
+ var linkButton = tplIconButton('fa fa-edit icon-edit', {
3326
+ title: lang.link.edit,
3327
+ event: 'showLinkDialog'
3328
+ });
3329
+ var unlinkButton = tplIconButton('fa fa-unlink icon-unlink', {
3330
+ title: lang.link.unlink,
3331
+ event: 'unlink'
3332
+ });
3333
+ var content = '<a href="http://www.google.com" target="_blank">www.google.com</a>&nbsp;&nbsp;' +
3334
+ '<div class="note-insert btn-group">' +
3335
+ linkButton + unlinkButton +
3336
+ '</div>';
3337
+ return tplPopover('note-link-popover', content);
3338
+ };
3339
+
3340
+ var tplImagePopover = function () {
3341
+ var fullButton = tplButton('<span class="note-fontsize-10">100%</span>', {
3342
+ title: lang.image.resizeFull,
3343
+ event: 'resize',
3344
+ value: '1'
3345
+ });
3346
+ var halfButton = tplButton('<span class="note-fontsize-10">50%</span>', {
3347
+ title: lang.image.resizeHalf,
3348
+ event: 'resize',
3349
+ value: '0.5'
3350
+ });
3351
+ var quarterButton = tplButton('<span class="note-fontsize-10">25%</span>', {
3352
+ title: lang.image.resizeQuarter,
3353
+ event: 'resize',
3354
+ value: '0.25'
3355
+ });
3356
+
3357
+ var leftButton = tplIconButton('fa fa-align-left icon-align-left', {
3358
+ title: lang.image.floatLeft,
3359
+ event: 'floatMe',
3360
+ value: 'left'
3361
+ });
3362
+ var rightButton = tplIconButton('fa fa-align-right icon-align-right', {
3363
+ title: lang.image.floatRight,
3364
+ event: 'floatMe',
3365
+ value: 'right'
3366
+ });
3367
+ var justifyButton = tplIconButton('fa fa-align-justify icon-align-justify', {
3368
+ title: lang.image.floatNone,
3369
+ event: 'floatMe',
3370
+ value: 'none'
3371
+ });
3372
+
3373
+ var removeButton = tplIconButton('fa fa-trash-o icon-trash', {
3374
+ title: lang.image.remove,
3375
+ event: 'removeMedia',
3376
+ value: 'none'
3377
+ });
3378
+
3379
+ var content = '<div class="btn-group">' + fullButton + halfButton + quarterButton + '</div>' +
3380
+ '<div class="btn-group">' + leftButton + rightButton + justifyButton + '</div>' +
3381
+ '<div class="btn-group">' + removeButton + '</div>';
3382
+ return tplPopover('note-image-popover', content);
3383
+ };
3384
+
3385
+ var tplAirPopover = function () {
3386
+ var content = '';
3387
+ for (var idx = 0, sz = options.airPopover.length; idx < sz; idx ++) {
3388
+ var group = options.airPopover[idx];
3389
+ content += '<div class="note-' + group[0] + ' btn-group">';
3390
+ for (var i = 0, szGroup = group[1].length; i < szGroup; i++) {
3391
+ content += tplButtonInfo[group[1][i]](lang, options);
3392
+ }
3393
+ content += '</div>';
3394
+ }
3395
+
3396
+ return tplPopover('note-air-popover', content);
3397
+ };
3398
+
3399
+ return '<div class="note-popover">' +
3400
+ tplLinkPopover() +
3401
+ tplImagePopover() +
3402
+ (options.airMode ? tplAirPopover() : '') +
3403
+ '</div>';
3404
+ };
3405
+
3406
+ var tplHandles = function () {
3407
+ return '<div class="note-handle">' +
3408
+ '<div class="note-control-selection">' +
3409
+ '<div class="note-control-selection-bg"></div>' +
3410
+ '<div class="note-control-holder note-control-nw"></div>' +
3411
+ '<div class="note-control-holder note-control-ne"></div>' +
3412
+ '<div class="note-control-holder note-control-sw"></div>' +
3413
+ '<div class="note-control-sizing note-control-se"></div>' +
3414
+ '<div class="note-control-selection-info"></div>' +
3415
+ '</div>' +
3416
+ '</div>';
3417
+ };
3418
+
3419
+ /**
3420
+ * shortcut table template
3421
+ * @param {String} title
3422
+ * @param {String} body
3423
+ */
3424
+ var tplShortcut = function (title, body) {
3425
+ return '<table class="note-shortcut">' +
3426
+ '<thead>' +
3427
+ '<tr><th></th><th>' + title + '</th></tr>' +
3428
+ '</thead>' +
3429
+ '<tbody>' + body + '</tbody>' +
3430
+ '</table>';
3431
+ };
3432
+
3433
+ var tplShortcutText = function (lang) {
3434
+ var body = '<tr><td>⌘ + B</td><td>' + lang.font.bold + '</td></tr>' +
3435
+ '<tr><td>⌘ + I</td><td>' + lang.font.italic + '</td></tr>' +
3436
+ '<tr><td>⌘ + U</td><td>' + lang.font.underline + '</td></tr>' +
3437
+ '<tr><td>⌘ + ⇧ + S</td><td>' + lang.font.strikethrough + '</td></tr>' +
3438
+ '<tr><td>⌘ + \\</td><td>' + lang.font.clear + '</td></tr>';
3439
+
3440
+ return tplShortcut(lang.shortcut.textFormatting, body);
3441
+ };
3442
+
3443
+ var tplShortcutAction = function (lang) {
3444
+ var body = '<tr><td>⌘ + Z</td><td>' + lang.history.undo + '</td></tr>' +
3445
+ '<tr><td>⌘ + ⇧ + Z</td><td>' + lang.history.redo + '</td></tr>' +
3446
+ '<tr><td>⌘ + ]</td><td>' + lang.paragraph.indent + '</td></tr>' +
3447
+ '<tr><td>⌘ + [</td><td>' + lang.paragraph.outdent + '</td></tr>' +
3448
+ '<tr><td>⌘ + ENTER</td><td>' + lang.hr.insert + '</td></tr>';
3449
+
3450
+ return tplShortcut(lang.shortcut.action, body);
3451
+ };
3452
+
3453
+ var tplShortcutPara = function (lang) {
3454
+ var body = '<tr><td>⌘ + ⇧ + L</td><td>' + lang.paragraph.left + '</td></tr>' +
3455
+ '<tr><td>⌘ + ⇧ + E</td><td>' + lang.paragraph.center + '</td></tr>' +
3456
+ '<tr><td>⌘ + ⇧ + R</td><td>' + lang.paragraph.right + '</td></tr>' +
3457
+ '<tr><td>⌘ + ⇧ + J</td><td>' + lang.paragraph.justify + '</td></tr>' +
3458
+ '<tr><td>⌘ + ⇧ + NUM7</td><td>' + lang.lists.ordered + '</td></tr>' +
3459
+ '<tr><td>⌘ + ⇧ + NUM8</td><td>' + lang.lists.unordered + '</td></tr>';
3460
+
3461
+ return tplShortcut(lang.shortcut.paragraphFormatting, body);
3462
+ };
3463
+
3464
+ var tplShortcutStyle = function (lang) {
3465
+ var body = '<tr><td>⌘ + NUM0</td><td>' + lang.style.normal + '</td></tr>' +
3466
+ '<tr><td>⌘ + NUM1</td><td>' + lang.style.h1 + '</td></tr>' +
3467
+ '<tr><td>⌘ + NUM2</td><td>' + lang.style.h2 + '</td></tr>' +
3468
+ '<tr><td>⌘ + NUM3</td><td>' + lang.style.h3 + '</td></tr>' +
3469
+ '<tr><td>⌘ + NUM4</td><td>' + lang.style.h4 + '</td></tr>' +
3470
+ '<tr><td>⌘ + NUM5</td><td>' + lang.style.h5 + '</td></tr>' +
3471
+ '<tr><td>⌘ + NUM6</td><td>' + lang.style.h6 + '</td></tr>';
3472
+
3473
+ return tplShortcut(lang.shortcut.documentStyle, body);
3474
+ };
3475
+
3476
+ var tplExtraShortcuts = function (lang, options) {
3477
+ var extraKeys = options.extraKeys;
3478
+ var body = '';
3479
+ for (var key in extraKeys) {
3480
+ if (extraKeys.hasOwnProperty(key)) {
3481
+ body += '<tr><td>' + key + '</td><td>' + extraKeys[key] + '</td></tr>';
3482
+ }
3483
+ }
3484
+
3485
+ return tplShortcut(lang.shortcut.extraKeys, body);
3486
+ };
3487
+
3488
+ var tplShortcutTable = function (lang, options) {
3489
+ var template = '<table class="note-shortcut-layout">' +
3490
+ '<tbody>' +
3491
+ '<tr><td>' + tplShortcutAction(lang, options) + '</td><td>' + tplShortcutText(lang, options) + '</td></tr>' +
3492
+ '<tr><td>' + tplShortcutStyle(lang, options) + '</td><td>' + tplShortcutPara(lang, options) + '</td></tr>';
3493
+ if (options.extraKeys) {
3494
+ template += '<tr><td colspan="2">' + tplExtraShortcuts(lang, options) + '</td></tr>';
3495
+ }
3496
+ template += '</tbody</table>';
3497
+ return template;
3498
+ };
3499
+
3500
+ var replaceMacKeys = function (sHtml) {
3501
+ return sHtml.replace(/⌘/g, 'Ctrl').replace(/⇧/g, 'Shift');
3502
+ };
3503
+
3504
+ var tplDialogs = function (lang, options) {
3505
+ var tplImageDialog = function () {
3506
+ var body = '<h5>' + lang.image.selectFromFiles + '</h5>' +
3507
+ '<input class="note-image-input" type="file" name="files" accept="image/*" />' +
3508
+ '<h5>' + lang.image.url + '</h5>' +
3509
+ '<input class="note-image-url form-control span12" type="text" />';
3510
+ var footer = '<button href="#" class="btn btn-primary note-image-btn disabled" disabled>' + lang.image.insert + '</button>';
3511
+ return tplDialog('note-image-dialog', lang.image.insert, body, footer);
3512
+ };
3513
+
3514
+ var tplLinkDialog = function () {
3515
+ var body = '<div class="form-group">' +
3516
+ '<label>' + lang.link.textToDisplay + '</label>' +
3517
+ '<input class="note-link-text form-control span12" type="text" />' +
3518
+ '</div>' +
3519
+ '<div class="form-group">' +
3520
+ '<label>' + lang.link.url + '</label>' +
3521
+ '<input class="note-link-url form-control span12" type="text" />' +
3522
+ '</div>' +
3523
+ (!options.disableLinkTarget ?
3524
+ '<div class="checkbox">' +
3525
+ '<label>' + '<input type="checkbox" checked> ' +
3526
+ lang.link.openInNewWindow +
3527
+ '</label>' +
3528
+ '</div>' : ''
3529
+ );
3530
+ var footer = '<button href="#" class="btn btn-primary note-link-btn disabled" disabled>' + lang.link.insert + '</button>';
3531
+ return tplDialog('note-link-dialog', lang.link.insert, body, footer);
3532
+ };
3533
+
3534
+ var tplVideoDialog = function () {
3535
+ var body = '<div class="form-group">' +
3536
+ '<label>' + lang.video.url + '</label>&nbsp;<small class="text-muted">' + lang.video.providers + '</small>' +
3537
+ '<input class="note-video-url form-control span12" type="text" />' +
3538
+ '</div>';
3539
+ var footer = '<button href="#" class="btn btn-primary note-video-btn disabled" disabled>' + lang.video.insert + '</button>';
3540
+ return tplDialog('note-video-dialog', lang.video.insert, body, footer);
3541
+ };
3542
+
3543
+ var tplHelpDialog = function () {
3544
+ var body = '<a class="modal-close pull-right" aria-hidden="true" tabindex="-1">' + lang.shortcut.close + '</a>' +
3545
+ '<div class="title">' + lang.shortcut.shortcuts + '</div>' +
3546
+ (agent.bMac ? tplShortcutTable(lang, options) : replaceMacKeys(tplShortcutTable(lang, options))) +
3547
+ '<p class="text-center">' +
3548
+ '<a href="//hackerwins.github.io/summernote/" target="_blank">Summernote 0.5.2</a> · ' +
3549
+ '<a href="//github.com/HackerWins/summernote" target="_blank">Project</a> · ' +
3550
+ '<a href="//github.com/HackerWins/summernote/issues" target="_blank">Issues</a>' +
3551
+ '</p>';
3552
+ return tplDialog('note-help-dialog', '', body, '');
3553
+ };
3554
+
3555
+ return '<div class="note-dialog">' +
3556
+ tplImageDialog() +
3557
+ tplLinkDialog() +
3558
+ tplVideoDialog() +
3559
+ tplHelpDialog() +
3560
+ '</div>';
3561
+ };
3562
+
3563
+ var tplStatusbar = function () {
3564
+ return '<div class="note-resizebar">' +
3565
+ '<div class="note-icon-bar"></div>' +
3566
+ '<div class="note-icon-bar"></div>' +
3567
+ '<div class="note-icon-bar"></div>' +
3568
+ '</div>';
3569
+ };
3570
+
3571
+ var representShortcut = function (str) {
3572
+ if (agent.bMac) {
3573
+ str = str.replace('CMD', '⌘').replace('SHIFT', '⇧');
3574
+ }
3575
+
3576
+ return str.replace('BACKSLASH', '\\')
3577
+ .replace('SLASH', '/')
3578
+ .replace('LEFTBRACKET', '[')
3579
+ .replace('RIGHTBRACKET', ']');
3580
+ };
3581
+
3582
+ /**
3583
+ * createTooltip
3584
+ *
3585
+ * @param {jQuery} $container
3586
+ * @param {Object} keyMap
3587
+ * @param {String} [sPlacement]
3588
+ */
3589
+ var createTooltip = function ($container, keyMap, sPlacement) {
3590
+ var invertedKeyMap = func.invertObject(keyMap);
3591
+ var $buttons = $container.find('button');
3592
+
3593
+ $buttons.each(function (i, elBtn) {
3594
+ var $btn = $(elBtn);
3595
+ var sShortcut = invertedKeyMap[$btn.data('event')];
3596
+ if (sShortcut) {
3597
+ $btn.attr('title', function (i, v) {
3598
+ return v + ' (' + representShortcut(sShortcut) + ')';
3599
+ });
3600
+ }
3601
+ // bootstrap tooltip on btn-group bug
3602
+ // https://github.com/twitter/bootstrap/issues/5687
3603
+ }).tooltip({
3604
+ container: 'body',
3605
+ trigger: 'hover',
3606
+ placement: sPlacement || 'top'
3607
+ }).on('click', function () {
3608
+ $(this).tooltip('hide');
3609
+ });
3610
+ };
3611
+
3612
+ // createPalette
3613
+ var createPalette = function ($container, options) {
3614
+ var aaColor = options.colors;
3615
+ $container.find('.note-color-palette').each(function () {
3616
+ var $palette = $(this), sEvent = $palette.attr('data-target-event');
3617
+ var aPaletteContents = [];
3618
+ for (var row = 0, szRow = aaColor.length; row < szRow; row++) {
3619
+ var aColor = aaColor[row];
3620
+ var aButton = [];
3621
+ for (var col = 0, szCol = aColor.length; col < szCol; col++) {
3622
+ var sColor = aColor[col];
3623
+ aButton.push(['<button type="button" class="note-color-btn" style="background-color:', sColor,
3624
+ ';" data-event="', sEvent,
3625
+ '" data-value="', sColor,
3626
+ '" title="', sColor,
3627
+ '" data-toggle="button" tabindex="-1"></button>'].join(''));
3628
+ }
3629
+ aPaletteContents.push('<div>' + aButton.join('') + '</div>');
3630
+ }
3631
+ $palette.html(aPaletteContents.join(''));
3632
+ });
3633
+ };
3634
+
3635
+ /**
3636
+ * create summernote layout (air mode)
3637
+ *
3638
+ * @param {jQuery} $holder
3639
+ * @param {Object} options
3640
+ */
3641
+ this.createLayoutByAirMode = function ($holder, options) {
3642
+ var keyMap = options.keyMap[agent.bMac ? 'mac' : 'pc'];
3643
+ var langInfo = $.summernote.lang[options.lang];
3644
+
3645
+ var id = func.uniqueId();
3646
+
3647
+ $holder.addClass('note-air-editor note-editable');
3648
+ $holder.attr({
3649
+ 'id': 'note-editor-' + id,
3650
+ 'contentEditable': true
3651
+ });
3652
+
3653
+ var body = document.body;
3654
+
3655
+ // create Popover
3656
+ var $popover = $(tplPopovers(langInfo, options));
3657
+ $popover.addClass('note-air-layout');
3658
+ $popover.attr('id', 'note-popover-' + id);
3659
+ $popover.appendTo(body);
3660
+ createTooltip($popover, keyMap);
3661
+ createPalette($popover, options);
3662
+
3663
+ // create Handle
3664
+ var $handle = $(tplHandles());
3665
+ $handle.addClass('note-air-layout');
3666
+ $handle.attr('id', 'note-handle-' + id);
3667
+ $handle.appendTo(body);
3668
+
3669
+ // create Dialog
3670
+ var $dialog = $(tplDialogs(langInfo, options));
3671
+ $dialog.addClass('note-air-layout');
3672
+ $dialog.attr('id', 'note-dialog-' + id);
3673
+ $dialog.find('button.close, a.modal-close').click(function () {
3674
+ $(this).closest('.modal').modal('hide');
3675
+ });
3676
+ $dialog.appendTo(body);
3677
+ };
3678
+
3679
+ /**
3680
+ * create summernote layout (normal mode)
3681
+ *
3682
+ * @param {jQuery} $holder
3683
+ * @param {Object} options
3684
+ */
3685
+ this.createLayoutByFrame = function ($holder, options) {
3686
+ //01. create Editor
3687
+ var $editor = $('<div class="note-editor"></div>');
3688
+ if (options.width) {
3689
+ $editor.width(options.width);
3690
+ }
3691
+
3692
+ //02. statusbar (resizebar)
3693
+ if (options.height > 0) {
3694
+ $('<div class="note-statusbar">' + tplStatusbar() + '</div>').prependTo($editor);
3695
+ }
3696
+
3697
+ //03. create Editable
3698
+ var isContentEditable = !$holder.is(':disabled');
3699
+ var $editable = $('<div class="note-editable" contentEditable="' + isContentEditable + '"></div>')
3700
+ .prependTo($editor);
3701
+ if (options.height) {
3702
+ $editable.height(options.height);
3703
+ }
3704
+ if (options.direction) {
3705
+ $editable.attr('dir', options.direction);
3706
+ }
3707
+
3708
+ $editable.html(dom.html($holder) || dom.emptyPara);
3709
+
3710
+ //031. create codable
3711
+ $('<textarea class="note-codable"></textarea>').prependTo($editor);
3712
+
3713
+ var langInfo = $.summernote.lang[options.lang];
3714
+
3715
+ //04. create Toolbar
3716
+ var sToolbar = '';
3717
+ for (var idx = 0, sz = options.toolbar.length; idx < sz; idx ++) {
3718
+ var group = options.toolbar[idx];
3719
+ sToolbar += '<div class="note-' + group[0] + ' btn-group">';
3720
+ for (var i = 0, szGroup = group[1].length; i < szGroup; i++) {
3721
+ sToolbar += tplButtonInfo[group[1][i]](langInfo, options);
3722
+ }
3723
+ sToolbar += '</div>';
3724
+ }
3725
+
3726
+ sToolbar = '<div class="note-toolbar btn-toolbar">' + sToolbar + '</div>';
3727
+
3728
+ var $toolbar = $(sToolbar).prependTo($editor);
3729
+ var keyMap = options.keyMap[agent.bMac ? 'mac' : 'pc'];
3730
+ createPalette($toolbar, options);
3731
+ createTooltip($toolbar, keyMap, 'bottom');
3732
+
3733
+ //05. create Popover
3734
+ var $popover = $(tplPopovers(langInfo, options)).prependTo($editor);
3735
+ createPalette($popover, options);
3736
+ createTooltip($popover, keyMap);
3737
+
3738
+ //06. handle(control selection, ...)
3739
+ $(tplHandles()).prependTo($editor);
3740
+
3741
+ //07. create Dialog
3742
+ var $dialog = $(tplDialogs(langInfo, options)).prependTo($editor);
3743
+ $dialog.find('button.close, a.modal-close').click(function () {
3744
+ $(this).closest('.modal').modal('hide');
3745
+ });
3746
+
3747
+ //08. create Dropzone
3748
+ $('<div class="note-dropzone"><div class="note-dropzone-message"></div></div>').prependTo($editor);
3749
+
3750
+ //09. Editor/Holder switch
3751
+ $editor.insertAfter($holder);
3752
+ $holder.hide();
3753
+ };
3754
+
3755
+ this.noteEditorFromHolder = function ($holder) {
3756
+ if ($holder.hasClass('note-air-editor')) {
3757
+ return $holder;
3758
+ } else if ($holder.next().hasClass('note-editor')) {
3759
+ return $holder.next();
3760
+ } else {
3761
+ return $();
3762
+ }
3763
+ };
3764
+
3765
+ /**
3766
+ * create summernote layout
3767
+ *
3768
+ * @param {jQuery} $holder
3769
+ * @param {Object} options
3770
+ */
3771
+ this.createLayout = function ($holder, options) {
3772
+ if (this.noteEditorFromHolder($holder).length > 0) {
3773
+ return;
3774
+ }
3775
+
3776
+ if (options.airMode) {
3777
+ this.createLayoutByAirMode($holder, options);
3778
+ } else {
3779
+ this.createLayoutByFrame($holder, options);
3780
+ }
3781
+ };
3782
+
3783
+ /**
3784
+ * returns layoutInfo from holder
3785
+ *
3786
+ * @param {jQuery} $holder - placeholder
3787
+ * @returns {Object}
3788
+ */
3789
+ this.layoutInfoFromHolder = function ($holder) {
3790
+ var $editor = this.noteEditorFromHolder($holder);
3791
+ if (!$editor.length) { return; }
3792
+
3793
+ var layoutInfo = dom.buildLayoutInfo($editor);
3794
+ // cache all properties.
3795
+ for (var key in layoutInfo) {
3796
+ if (layoutInfo.hasOwnProperty(key)) {
3797
+ layoutInfo[key] = layoutInfo[key].call();
3798
+ }
3799
+ }
3800
+ return layoutInfo;
3801
+ };
3802
+
3803
+ /**
3804
+ * removeLayout
3805
+ *
3806
+ * @param {jQuery} $holder - placeholder
3807
+ */
3808
+ this.removeLayout = function ($holder) {
3809
+ var info = this.layoutInfoFromHolder($holder);
3810
+ if (!info) { return; }
3811
+ $holder.html(info.editable.html());
3812
+
3813
+ info.editor.remove();
3814
+ $holder.show();
3815
+ };
3816
+ };
3817
+
3818
+ // jQuery namespace for summernote
3819
+ $.summernote = $.summernote || {};
3820
+
3821
+ // extends default `settings`
3822
+ $.extend($.summernote, settings);
3823
+
3824
+ var renderer = new Renderer();
3825
+ var eventHandler = new EventHandler();
3826
+
3827
+ /**
3828
+ * extend jquery fn
3829
+ */
3830
+ $.fn.extend({
3831
+ /**
3832
+ * initialize summernote
3833
+ * - create editor layout and attach Mouse and keyboard events.
3834
+ *
3835
+ * @param {Object} options
3836
+ * @returns {this}
3837
+ */
3838
+ summernote: function (options) {
3839
+ // extend default options
3840
+ options = $.extend({}, $.summernote.options, options);
3841
+
3842
+ this.each(function (idx, elHolder) {
3843
+ var $holder = $(elHolder);
3844
+
3845
+ // createLayout with options
3846
+ renderer.createLayout($holder, options);
3847
+
3848
+ var info = renderer.layoutInfoFromHolder($holder);
3849
+ eventHandler.attach(info, options);
3850
+
3851
+ // Textarea: auto filling the code before form submit.
3852
+ if (dom.isTextarea($holder[0])) {
3853
+ $holder.closest('form').submit(function () {
3854
+ $holder.html($holder.code());
3855
+ });
3856
+ }
3857
+ });
3858
+
3859
+ // focus on first editable element
3860
+ if (this.first().length && options.focus) {
3861
+ var info = renderer.layoutInfoFromHolder(this.first());
3862
+ info.editable.focus();
3863
+ }
3864
+
3865
+ // callback on init
3866
+ if (this.length > 0 && options.oninit) {
3867
+ options.oninit();
3868
+ }
3869
+
3870
+ return this;
3871
+ },
3872
+ //
3873
+
3874
+ /**
3875
+ * get the HTML contents of note or set the HTML contents of note.
3876
+ *
3877
+ * @param {String} [sHTML] - HTML contents(optional, set)
3878
+ * @returns {this|String} - context(set) or HTML contents of note(get).
3879
+ */
3880
+ code: function (sHTML) {
3881
+ // get the HTML contents of note
3882
+ if (sHTML === undefined) {
3883
+ var $holder = this.first();
3884
+ if ($holder.length === 0) { return; }
3885
+ var info = renderer.layoutInfoFromHolder($holder);
3886
+ if (!!(info && info.editable)) {
3887
+ var bCodeview = info.editor.hasClass('codeview');
3888
+ if (bCodeview && agent.bCodeMirror) {
3889
+ info.codable.data('cmEditor').save();
3890
+ }
3891
+ return bCodeview ? info.codable.val() : info.editable.html();
3892
+ }
3893
+ return $holder.html();
3894
+ }
3895
+
3896
+ // set the HTML contents of note
3897
+ this.each(function (i, elHolder) {
3898
+ var info = renderer.layoutInfoFromHolder($(elHolder));
3899
+ if (info && info.editable) { info.editable.html(sHTML); }
3900
+ });
3901
+
3902
+ return this;
3903
+ },
3904
+
3905
+ /**
3906
+ * destroy Editor Layout and dettach Key and Mouse Event
3907
+ * @returns {this}
3908
+ */
3909
+ destroy: function () {
3910
+ this.each(function (idx, elHolder) {
3911
+ var $holder = $(elHolder);
3912
+
3913
+ var info = renderer.layoutInfoFromHolder($holder);
3914
+ if (!info || !info.editable) { return; }
3915
+ eventHandler.dettach(info);
3916
+ renderer.removeLayout($holder);
3917
+ });
3918
+
3919
+ return this;
3920
+ }
3921
+ });
3922
+ }));